[mask_rom] Mask ROM Initial Commit
This is the initial framework which we will continue to develop the Mask
ROM upon. The Mask ROM is the first stage of the Secure Boot process. The
aim of the Mask ROM code is to have as few dependencies as possible, and
for all the code to be as simple as possible, so we re-implement some
structures in other parts of the `sw` tree, rather than pull them in
entirely.
This code is based on the implementations in `sw/device/boot_rom` and
`sw/device/exts/common`.
The Mask ROM currently does not do anything, and certainly cannot load
a flash image to execute yet. These are future additions that will be
made.
Signed-off-by: Sam Elliott <selliott@lowrisc.org>
diff --git a/sw/device/mask_rom/mask_rom.c b/sw/device/mask_rom/mask_rom.c
new file mode 100644
index 0000000..a9adcd3
--- /dev/null
+++ b/sw/device/mask_rom/mask_rom.c
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/mask_rom/mask_rom.h"
+#include "sw/device/lib/base/stdasm.h"
+
+void mask_rom_boot(void) {
+ asm volatile("unimp");
+ __builtin_unreachable();
+}
+
+void mask_rom_exception_handler(void) {}
+
+void mask_rom_nmi_handler(void) {}
diff --git a/sw/device/mask_rom/mask_rom.h b/sw/device/mask_rom/mask_rom.h
new file mode 100644
index 0000000..d37b364
--- /dev/null
+++ b/sw/device/mask_rom/mask_rom.h
@@ -0,0 +1,65 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_MASK_ROM_MASK_ROM_H_
+#define OPENTITAN_SW_DEVICE_MASK_ROM_MASK_ROM_H_
+
+#include <stdnoreturn.h>
+
+// Header Extern Guard (so header can be used from C and C++)
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/**
+ * Denotes functions that have to use the interrupt handler ABI.
+ *
+ * These must be 4-byte aligned, and they use a different calling convention.
+ */
+#define MASK_ROM_INTERRUPT_HANDLER_ABI __attribute__((aligned(4), interrupt))
+
+/**
+ * Denotes functions that have to be near the interrupt vector, because they
+ * are jumped to from it.
+ */
+#define MASK_ROM_VECTOR_FUNCTION __attribute__((section(".crt")))
+
+/**
+ * The first assembly procedure executed by the Mask ROM (defined in
+ * `mask_rom.S`).
+ *
+ * This procedure does not obey the standard RISC-V calling convention, so it
+ * must not be called from other C code.
+ */
+MASK_ROM_VECTOR_FUNCTION
+void _mask_rom_start_boot(void);
+
+/**
+ * The first C function executed by the Mask ROM (defined in `mask_rom.c`)
+ */
+noreturn void mask_rom_boot(void);
+
+/**
+ * Mask ROM hardware exception handler.
+ *
+ * This may not be able to be implemented in C.
+ */
+MASK_ROM_VECTOR_FUNCTION
+MASK_ROM_INTERRUPT_HANDLER_ABI
+void mask_rom_exception_handler(void);
+
+/**
+ * Mask ROM non-maskable interrupt handler.
+ *
+ * This may not be able to be implemented in C.
+ */
+MASK_ROM_VECTOR_FUNCTION
+MASK_ROM_INTERRUPT_HANDLER_ABI
+void mask_rom_nmi_handler(void);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+
+#endif // OPENTITAN_SW_DEVICE_MASK_ROM_MASK_ROM_H_
diff --git a/sw/device/mask_rom/mask_rom.ld b/sw/device/mask_rom/mask_rom.ld
new file mode 100644
index 0000000..29210f3
--- /dev/null
+++ b/sw/device/mask_rom/mask_rom.ld
@@ -0,0 +1,198 @@
+/* Copyright lowRISC contributors. */
+/* Licensed under the Apache License, Version 2.0, see LICENSE for details. */
+/* SPDX-License-Identifier: Apache-2.0 */
+
+/**
+ * Linker script for an OpenTitan Mask ROM.
+ *
+ * Portions of this file are Ibex-specific.
+ */
+
+OUTPUT_ARCH(riscv)
+GROUP(-lgcc)
+
+/**
+ * Indicate that there are no dynamic libraries, whatsoever.
+ */
+__DYNAMIC = 0;
+
+INCLUDE hw/top_earlgrey/sw/autogen/top_earlgrey_memory.ld
+
+/**
+ * The boot address, which indicates the location of the initial interrupt
+ * vector.
+ */
+_mask_rom_boot_address = ORIGIN(rom);
+
+/* Reserving 32 bytes at the top of the RAM for test utils. */
+_test_reserved_size = 0x20;
+_test_reserved_end = ORIGIN(ram_main) + LENGTH(ram_main);
+_test_reserved_start = _test_reserved_end - _test_reserved_size;
+
+/* Reserving space near the top of the RAM for the stack. */
+_stack_size = 0x2000 - _test_reserved_size;
+_stack_end = _test_reserved_start;
+_stack_start = _stack_end - _stack_size;
+
+/* Reserving 128 bytes at the top of ROM for chip info */
+_chip_info_size = 0x80;
+_chip_info_end = ORIGIN(rom) + LENGTH(rom);
+_chip_info_start = _chip_info_end - _chip_info_size;
+
+/**
+ * NOTE: We have to align each section to word boundaries as our current
+ * s19->slm conversion scripts are not able to handle non-word aligned sections.
+ */
+SECTIONS {
+ /**
+ * Ibex interrupt vector. See mask_rom_init.S for more information.
+ *
+ * This has to be set up at the boot address, so that execution jumps to the
+ * reset handler correctly.
+ */
+ .vectors _mask_rom_boot_address : ALIGN(256) {
+ KEEP(*(.vectors))
+ *(.vectors)
+ } > rom
+
+ /**
+ * C runtime (CRT) section, containing program initialization code.
+ *
+ * This is a separate section to `.text` so that the jumps inside `.vectors`
+ * will fit into the instruction encoding.
+ */
+ .crt : ALIGN(4) {
+ KEEP(*(.crt))
+ *(.crt)
+ } > rom
+
+ /**
+ * Standard text section, containing program code.
+ */
+ .text : ALIGN(4) {
+ *(.text)
+ *(.text.*)
+ } > rom
+
+ /**
+ * Read-only data section, containing all large compile-time constants, like
+ * strings.
+ */
+ .rodata : ALIGN(4) {
+ /* Small read-only data comes before regular read-only data for the same
+ * reasons as in the data section */
+ *(.srodata)
+ *(.srodata.*)
+ *(.rodata)
+ *(.rodata.*)
+ } > rom
+
+ /**
+ * Mutable data section, at the bottom of ram_main. This will be initialized
+ * from rom at runtime by the CRT.
+ *
+ * Load this by copying the bytes from [_data_init_start, _data_init_end] into
+ * the range [_data_start, _data_end].
+ */
+ .data ORIGIN(ram_main) : ALIGN(4) {
+ _data_start = .;
+ _data_init_start = LOADADDR(.data);
+
+ /* This will get loaded into `gp`, and the linker will use that register for
+ * accessing data within [-2048,2047] of `__global_pointer$`.
+ *
+ * This is much cheaper (for small data) than materializing the
+ * address and loading from that (which will take one extra instruction).
+ */
+ __global_pointer$ = . + 2048;
+
+ /* Small data should come before larger data. This helps to ensure small
+ * globals are within 2048 bytes of the value of `gp`, making their accesses
+ * hopefully only take one instruction. */
+ *(.sdata)
+ *(.sdata.*)
+
+ /* Other data will likely need multiple instructions to load, so we're less
+ * concerned about address materialisation taking more than one instruction.
+ */
+ *(.data)
+ *(.data.*)
+
+ /* Ensure section end is word-aligned. */
+ . = ALIGN(4);
+ _data_end = .;
+ _data_init_end = LOADADDR(.data) + SIZEOF(.data);
+
+ /* This puts it in ram_main at runtime (for the VMA), but puts the section
+ * into rom for load time (for the LMA). This is why `_data_init_*` uses
+ * `LOADADDR`.
+ *
+ * Using `AT>` means we don't have to keep track of the next free part of
+ * rom, as we do in our other linker scripts. */
+ } > ram_main AT> rom
+
+ /**
+ * Standard BSS section. This will be zeroed at runtime by the CRT.
+ */
+ .bss : ALIGN(4) {
+ _bss_start = .;
+
+ /* Small BSS comes before regular BSS for the same reasons as in the data
+ * section */
+ *(.sbss)
+ *(.sbss.*)
+ *(.bss)
+ *(.bss.*)
+
+ /* Any other uninitialized data. */
+ *(COMMON)
+
+ /* Ensure section end is word-aligned. */
+ . = ALIGN(4);
+ _bss_end = .;
+ } > ram_main
+
+ /**
+ * Immutable chip_info data, containing build-time-recorded information.
+ *
+ * This is the last thing in rom.
+ */
+ .chip_info _chip_info_start : ALIGN(4) {
+ KEEP(*(.chip_info))
+ *(.chip_info)
+ } > rom
+
+ /**
+ * STAB debug table.
+ */
+ .stab 0x0 (NOLOAD): {
+ [.stab]
+ }
+
+ /**
+ * STAB debug strings.
+ */
+ .stabstr 0x0 (NOLOAD): {
+ [.stabstr]
+ }
+
+ /**
+ * The following sections are used by DV to implement logging in an
+ * alternate way, which enables simulation speed up by completely avoiding
+ * any string format processing or even the actual transmission of log data
+ * to a real peripheral.
+ *
+ * These sections are marked as dummy so that they can still be extracted
+ * using readelf or similar utilities. As such, the content in these sections
+ * is not relevant for the actual SW code and can be safely discarded.
+ */
+
+ /**
+ * The following section contains log fields constructed from the logs using
+ * the log_fields_t struct defined in sw/device/lib/base/log.h. The size of
+ * each log field is fixed - 20 bytes, which is used as the delimiter.
+ */
+ .logs.fields 0x0 (DSECT): {
+ *(.logs.fields)
+ }
+}
diff --git a/sw/device/mask_rom/mask_rom_start.S b/sw/device/mask_rom/mask_rom_start.S
new file mode 100644
index 0000000..8de9ca9
--- /dev/null
+++ b/sw/device/mask_rom/mask_rom_start.S
@@ -0,0 +1,284 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey_memory.h"
+
+/**
+ * Mask ROM Interrupt Vector
+ */
+ .section .vectors, "ax"
+ .option push
+
+ // Disable RISC-V instruction compression: we need all instructions to
+ // be exactly word wide in the interrupt vector.
+ .option norvc
+
+ // Disable RISC-V linker relaxation, as it can compress instructions at
+ // link-time, which we also really don't want.
+ .option norelax
+
+/**
+ * `_mask_rom_interrupt_vector` is the boot-defined interrupt vector for Ibex,
+ * for the Mask ROM.
+ *
+ * The Mask ROM does not use interrupts, so there are only entries for Ibex's
+ * non-maskable interrupts, and for the hardware exception handler.
+ *
+ * Interrupt vectors in Ibex have 32 entries for 32 possible interrupts. The
+ * vector must be 256-byte aligned, as Ibex's vectoring mechanism requires that.
+ * Ibex uses the instruction directly after the boot interrupt vector when
+ * starting execution from reset, which we choose to make look like an extra
+ * entry.
+ *
+ * Thus this vector has exactly 33 4-byte entries.
+ *
+ * Only the following will be used by Ibex:
+ * - Exception Handler (Entry 0)
+ * - Machine Software Interrupt Handler (Entry 3)
+ * - Machine Timer Interrupt Handler (Entry 7)
+ * - Machine External Interrupt Handler (Entry 11)
+ * - Vendor Interrupt Handlers (Entries 16-31)
+ * - Reset Handler (Entry 32)
+ *
+ * More information about Ibex's interrupts can be found here:
+ * https://ibex-core.readthedocs.io/en/latest/exception_interrupts.html
+ */
+ .balign 256
+ .globl _mask_rom_interrupt_vector
+ .type _mask_rom_interrupt_vector, @function
+_mask_rom_interrupt_vector:
+
+ // RISC-V Standard (Vectored) Interrupt Handlers:
+
+ // Exception and User Software Interrupt Handler.
+ .extern mask_rom_exception_handler
+ j mask_rom_exception_handler
+ // Supervisor Software Interrupt Handler.
+ unimp
+ // Reserved.
+ unimp
+ // Machine Software Interrupt Handler.
+ unimp
+
+ // User Timer Interrupt Handler.
+ unimp
+ // Supervisor Timer Interrupt Handler.
+ unimp
+ // Reserved.
+ unimp
+ // Machine Timer Interrupt Handler.
+ unimp
+
+ // User External Interrupt Handler.
+ unimp
+ // Supervisor External Interrupt Handler.
+ unimp
+ // Reserved.
+ unimp
+ // Machine External Interrupt Handler.
+ unimp
+
+ // Reserved.
+ unimp
+ unimp
+ unimp
+ unimp
+
+ // Vendor Interrupt Handlers:
+
+ // On Ibex interrupt ids 30-16 are for "fast" interrupts.
+ .rept 15
+ unimp
+ .endr
+
+ // On Ibex interrupt id 31 is for non-maskable interrupts.
+ .extern mask_rom_nmi_handler
+ j mask_rom_nmi_handler
+
+ // Ibex Reset Handler:
+ j _mask_rom_start_boot
+
+ // Set size so this vector can be disassembled.
+ .size _mask_rom_interrupt_vector, .-_mask_rom_interrupt_vector
+
+ // Re-enable compressed instructions, linker relaxation.
+ .option pop
+
+
+/**
+ * Mask ROM runtime initialization code.
+ */
+
+ // NOTE: The "ax" flag below is necessary to ensure that this section
+ // is allocated executable space in ROM by the linker.
+ .section .crt, "ax"
+
+ // Linker Relaxation is disabled until `__global_pointer$` is setup, below,
+ // because otherwise some sequences may be turned into gp-relative sequences,
+ // which is incorrect when `gp` is not initialized.
+ .option push
+ .option norelax
+
+/**
+ * Entry point after reset. This symbol is jumped to from the handler
+ * for IRQ 32.
+ *
+ * then jumps to `mask_rom_boot
+ */
+ .globl _mask_rom_start_boot
+ .type _mask_rom_start_boot, @function
+_mask_rom_start_boot:
+
+ /**
+ * Disable Interrupts.
+ *
+ * We cannot disable exceptions, or Ibex's non-maskable interrupts (interrupt
+ * 31), so we still need to be careful.
+ */
+ // Clear `MIE` field of `mstatus`.
+ csrci mstatus, 0x8
+ // Clear all the machine-defined interrupts, `MEIE`, `MTIE`, and `MSIE` fields
+ // of `mie`.
+ li t0, 0xFFFF0888
+ csrc mie, t0
+
+ // Set up the stack pointer.
+ //
+ // If an exception fires, the handler is conventionaly only allowed to clobber
+ // memory at addresses below `sp`.
+ la sp, _stack_start
+
+ /**
+ * Set well-defined interrupt/exception handlers
+ *
+ * We actually booted with this value of `mtvec`, so we shouldn't need to do
+ * this.
+ *
+ * The lowest two bits should be `0b01` to ensure we use vectored interrupts.
+ */
+ la t0, _mask_rom_interrupt_vector
+ andi t0, t0, -4
+ ori t0, t0, 0b01
+ csrw mtvec, t0
+
+ /**
+ * Clean Device State Part 1
+ */
+
+ // Zero all writable registers except x2 (sp).
+ mv x1, zero
+ // NOT x2 (sp) - We have already set it to the right value above.
+ mv x3, zero
+ mv x4, zero
+ mv x5, zero
+ mv x6, zero
+ mv x7, zero
+ mv x8, zero
+ mv x9, zero
+ mv x10, zero
+ mv x11, zero
+ mv x12, zero
+ mv x13, zero
+ mv x14, zero
+ mv x15, zero
+ mv x16, zero
+ mv x17, zero
+ mv x18, zero
+ mv x19, zero
+ mv x20, zero
+ mv x21, zero
+ mv x22, zero
+ mv x23, zero
+ mv x24, zero
+ mv x25, zero
+ mv x26, zero
+ mv x27, zero
+ mv x28, zero
+ mv x29, zero
+ mv x30, zero
+ mv x31, zero
+
+ // TODO: Setup SRAM Scrambling
+ // Temporarily: Zero out ram_main
+ //
+ // `t0` is the address to start zeroing at.
+ // `t1` is the address to stop zeroing at.
+ li t0, TOP_EARLGREY_RAM_MAIN_BASE_ADDR
+ li t1, (TOP_EARLGREY_RAM_MAIN_BASE_ADDR + TOP_EARLGREY_RAM_MAIN_SIZE_BYTES)
+ bgeu t0, t1, sram_zero_end
+sram_zero_loop:
+ // TODO: Unroll loop
+ sw zero, 0x0(t0)
+ addi t0, t0, 0x4
+ bltu t0, t1, sram_zero_loop
+sram_zero_end:
+
+ /**
+ * Setup C Runtime
+ */
+
+ // Initialize the `.data` section.
+ //
+ // `t0` is the start address of `.data` (in RAM).
+ // `t1` is the end address of `.data` (in RAM).
+ // `t2` is the start address of `.data` (in ROM).
+ // `t3` is a scratch register for the copy.
+ la t0, _data_start
+ la t1, _data_end
+ la t2, _data_init_start
+ bgeu t0, t1, data_copy_loop_end
+data_copy_loop:
+ // TODO: Unroll this loop
+ lw t3, 0x0(t2)
+ sw t3, 0x0(t0)
+ addi t0, t0, 0x4
+ addi t2, t2, 0x4
+ bltu t0, t1, data_copy_loop
+data_copy_loop_end:
+
+ // Initialize the `.bss` section.
+ //
+ // We do this despite zeroing all of SRAM above, so that we still zero `.bss`
+ // once we've enabled SRAM scrambling.
+ //
+ // `t0` is the address to start zeroing at.
+ // `t1` is the address to stop zeroing at.
+ la t0, _bss_start
+ la t1, _bss_end
+ bgeu t0, t1, bss_zero_end
+bss_zero_loop:
+ // TODO: Unroll loop
+ sw zero, 0x0(t0)
+ addi t0, t0, 0x4
+ bltu t0, t1, bss_zero_loop
+bss_zero_end:
+
+ // Re-clobber all of the registers used to Setup the C Runtime.
+ mv t0, zero
+ mv t1, zero
+ mv t2, zero
+ mv t3, zero
+
+ // Setup global pointer.
+ //
+ // This requires that we disable linker relaxations, or it will be relaxed to
+ // `mv gp, gp`, so we disabled relaxations for all of `_mask_rom_start_boot`.
+ la gp, __global_pointer$
+
+ /**
+ * Jump to C Code
+ */
+ .extern mask_rom_boot
+ call mask_rom_boot
+
+ // Loop forever if mask_rom_boot somehow returns.
+1:
+ wfi
+ j 1b
+
+ // Set size so this function can be disassembled.
+ .size _mask_rom_start_boot, .-_mask_rom_start_boot
+
+ // Re-enable linker relaxation.
+ .option pop
diff --git a/sw/device/mask_rom/meson.build b/sw/device/mask_rom/meson.build
new file mode 100644
index 0000000..56ff423
--- /dev/null
+++ b/sw/device/mask_rom/meson.build
@@ -0,0 +1,49 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# Mask ROM Linker Parameters
+#
+# See sw/device/exts/common/flash_link.ld for additional info about these
+# parameters.
+rom_linkfile = files(['mask_rom.ld'])
+rom_link_args = [
+ '-Wl,-L,@0@'.format(meson.source_root()),
+ '-Wl,-T,@0@/@1@'.format(meson.source_root(), rom_linkfile[0]),
+ '-Wl,--build-id=none',
+]
+rom_link_deps = [rom_linkfile]
+
+foreach device_name, device_lib : sw_lib_arch_core_devices
+ mask_rom_elf = executable(
+ 'mask_rom_' + device_name,
+ sources: [
+ 'mask_rom_start.S',
+ 'mask_rom.c',
+ ],
+ name_suffix: 'elf',
+ link_args: rom_link_args,
+ link_depends: rom_link_deps,
+ dependencies: [
+ chip_info_h,
+ device_lib,
+ ],
+ )
+
+ mask_rom_embedded = custom_target(
+ 'mask_rom_' + device_name,
+ command: make_embedded_target,
+ input: mask_rom_elf,
+ output: make_embedded_target_outputs,
+ build_by_default: true,
+ )
+
+ custom_target(
+ 'mask_rom_export_' + device_name,
+ command: export_embedded_target,
+ input: [mask_rom_elf, mask_rom_embedded],
+ output: 'mask_rom_export_' + device_name,
+ build_always_stale: true,
+ build_by_default: true,
+ )
+endforeach
diff --git a/sw/device/meson.build b/sw/device/meson.build
index e19690a..7e21754 100644
--- a/sw/device/meson.build
+++ b/sw/device/meson.build
@@ -31,6 +31,7 @@
]
subdir('boot_rom')
+subdir('mask_rom')
subdir('examples')
subdir('tests')
subdir('benchmarks')