This file should be read in conjunction with the secure boot specification. References to that document are included.
Power on (entirely in hardware)
Open Q: Whether SW has to configure PMP initial region.
Execution Begins with Mask ROM stage:
CRT Startup Code (Written in Assembly)
Disable Interrupts and set well-defined exception handler. This should keep initial execution deterministic.
Clean Device State Part 1. (See “Cleaning Device State” below) This includes enabling SRAM Scrambling.
Setup structures for C execution (CRT: .data
, .bss
sections, stack).
Jump into C code
Main Mask ROM Secure Boot Software (Written in C)
boot
:void boot(void) { boot_reason = read_boot_reason(); // Boot Policy Module // Clean Device State Part 2. // See "Cleaning Device State" Below. clean_device_state_part_2(boot_reason); // Chip-Specific Startup Module // Enable Memory Protection // - PMP Initial Region (if not done in power on) enable_memory_protection(); // Lockdown Module // Chip-specific startup functionality // **Open Q:** Proprietary Software Strategy. // - Clocks // - AST / Entropy. // - Flash // - Fuses chip_specific_startup(); // Chip-Specific Startup Module // Manufacturing boot-strapping intervention. // **Open Q:** How does the Mask ROM deal with chip recovery? // **Open Q:** Pin Strapping Configuration. manufacturing_boot_strap(boot_policy, boot_reason); // Bootstrap Module // Read Boot Policy for ROM_EXTs from flash (2.b) boot_policy = read_boot_policy(boot_reason); // Boot Policy Module // Determine which ROM_EXT slot is prioritised (2.b, 2.c.i) for ( current_rom_ext_manifest in rom_ext_manifests_to_try(boot_policy) ) { // Boot Policy Module // Sanity Check ROM_EXT Manifest (2.c.ii) // **Open Q:** Integration with Secure Boot Hardware // - Header Format (ROM_EXT Manifest Module) // - Plausible Key (??) // - Initial Digest Checks (Keys + Signature Module) // - **Open Q**: ROM_EXT Anti-rollback (??) if (!check_rom_ext_manifest(current_rom_ext_manifest)) { // Manifest Failure (check Boot Policy) if (try_next_on_manifest_failed(boot_policy)) // Boot Policy Module continue else break } // Find Public Key for ROM_EXT Image Signature (2.c.iii) // **Open Q:** Key Selection method/mechanism. rom_ext_pub_key = read_pub_key(current_rom_ext_manifest); // ROM_EXT Manifest Module rom_ext_pub_key_id = calculate_key_id(rom_ext_pub_key); // Keys + Signature Module if (!check_pub_key_id_valid(rom_ext_pub_key_id)) { // Keys + Signature Module // Manifest failure (Check Boot Policy) if (try_next_on_manifest_failed(boot_policy)) // Boot Policy Module continue else break } // Verify ROM_EXT Image Signature (2.c.iii) // **Open Q:** Integration with Secure Boot Hardware, OTBN if (!verify_rom_ext_signature(rom_ext_pub_key, current_rom_ext_manifest)) { // Hardened Jump Module // Manifest Failure (check Boot Policy) // **Open Q:** Does this need different logic to the check after // `check_rom_ext_manifest`? if (try_next_on_manifest_failed(boot_policy)) // Boot Policy Module continue else break } // Update Boot Policy on Successful Signature // **Open Q:** Does this ensure ROM_EXT Anti-rollback is updated? update_next_boot_policy(boot_policy, current_rom_ext_manifest); // Boot Policy Module // Beyond this point, you know `current_rom_ext_manifest` is valid and the // signature has been authenticated. // // The Mask ROM now has to do various things to prepare to execute the // current ROM_EXT. Most of this is initializing identities, keys, and // potentially locking down some hardware, before jumping to ROM_EXT. // // If any of these steps fail, we've probably left the hardware in an // invalid state and should just reboot (because it may not be possible to // undo the changes, for instance to write-enable bits). // System State Measurements (2.c.iv) measurements = perform_system_state_measurements(); // System State Module if (!boot_allowed_with_state(measurements)) { // System State Module // Lifecycle failure (no policy check) break } // CreatorRootKey (2.c.iv) // - This is only allowed to be done if we have verified the signature on // the current ROM_EXT. // **Open Q:** Does Mask ROM just lock down key manager inputs, and let the // ROM_EXT load the key? derive_and_lock_creator_root_key_inputs(measurements); // System State Module // Lock down Peripherals based on descriptors in ROM_EXT manifest. // - This does not cover key-related lockdown, which is done in // `derive_and_lock_creator_root_key_inputs`. peripheral_lockdown(current_rom_ext_manifest); // Lockdown Module // PMP Region for ROM_EXT (2.c.v) // **Open Q:** Integration with Secure Boot Hardware // **Open Q:** Do we need to prevent access to Mask ROM after final jump? pmp_unlock_rom_ext(); // Hardened Jump Module. // Transfer Execution to ROM_EXT (2.c.vi) // **Open Q:** Integration with Secure Boot Hardware // **Open Q:** Accumulated measurements to tie manifest to hardware config. if (!final_jump_to_rom_ext(current_rom_ext_manifest)) { // Hardened Jump Module // Boot Failure (no policy check) // - Because we may have locked some write-enable bits that any following // manifest cannot change if it needs to, we have to just reboot here. boot_failed(boot_policy, current_rom_ext_manifest); // Boot Policy Module } } // Boot failed for all ROM_EXTs allowed by boot policy boot_failed(boot_policy); // Boot Policy Module }
Execution Enters ROM_EXT stage.
Not covered by this document. Refer to Secure Boot document instead.
We've tried to divide the Mask ROM into self-contained modules that can be tested separately, rather than having to test the whole boot system at once.
The module descriptions below also include descriptions of: the DIFs that are required for that module to be implemented fully; the functionality expected at various milestones of the Mask ROM's development; and, optionally, pseudo-code for any subroutines that make up their implementation.
This manages reading the boot policy, updating the boot policy if required, and making decisions based on that policy. Most Boot Policy choices also depend on the Boot Reason, so reading and acting on that is part of this module's responsibility too.
DIFs Needed:
Milestone Expectations:
read_boot_policy() { // Parameters: // - initilized flash_ctrl DIF (for accessing flash info page) // Returns: // - Boot Policy Structure (see below) // 1. Uses dif_flash_ctrl to issue read of boot info page. // 2. Pull this into a struct to return. }
This manages reading and parsing ROM_EXT manifests.
The manifest format is defined in ROM_EXT Manifest Format
DIFs Needed:
Milestone Expectations:
This manages boot-strapping, chip recovery, and manufacturer loading of ROM_EXT images.
DIFs Needed:
Milestone Expectations
This is where, depending on lifecycle state, new flash images may be loaded onto the device (usually during manufacturing).
Open Q: Do we want hardware hardening for this? There are suggestions around having the hardware return nop
when reading this memory if we're not in the correct lifecycle state.
manufacturing_boot_strap() { // 1. Read lifecycle state to establish whether bootstrapping is allowed. // 2. Determine what kind of bootstrapping is being requested. // 3. Clear Flash (Both Banks) and SRAM (Retention) // **Open Q:** How much is cleared? Is the Creator Certificate cleared? // 4. Load ROM_EXT image into flash if allowed. // 5. Reboot device (causing loaded ROM_EXT to be verified then executed). }
This manages public key selection (for ROM_EXT validation), and calculating the digest and signature of the ROM_EXT image itself.
DIFs Needed:
Milestone Expectations:
This deals with how to initialize and clear any chip-specific hardware.
DIFs Needed:
This is responsible for managing memory protection regions as well as disallowing reconfiguration of peripherals. Some memory protection regions are also handled by the Hardened Jump module.
DIFs Needed:
Milestone Expectations:
peripheral_lockdown() { // Parameters: // - ROM_EXT manifest // - Handles for Peripheral DIFs // **Open Q:** When and How do we lock down peripheral configuration? // - We configure based on ROM_EXT manifest (signed) // - Only locks down these peripherals if info is provided. // What do we want to allow lockdown of? // - Alert Manager // - Pinmux / Padctrl // - Entropy Configuration // - Anything Else? // This is explicitly a separate step from locking the key manager inputs. In // particular, this depends on the ROM_EXT's choices, whereas the key manager // inputs getting locked is not something the ROM_EXT can choose. }
This module is responsible for managing the state associated with the hardware support for the jump into ROM_EXT.
DIFs Needed:
Milestone Expectations:
This deals with taking the system state measurements which are used to derive the CreatorRootKey
. Some of these measurements may cause boot to be halted.
DIFs Needed:
Milestone Expectations:
CreatorRootKey
derivation.Part of this process is done before we can execute any C code. In particular, we have to clear all registers and all of the main RAM before we setup the CRT (which allows us to execute any C code). This has to be done in assembly, as we cannot execute C without setting up the CRT.
We want to do as little as possible in the hand-written assembly, because it is so hard to verify and to test. Additionally, we want to use the DIFs (which are written in C) because we have verified them, rather than duplicating their functionality in assembly, if at all possible. So this means we wait until we are executing C to do the final parts of chip reset, especially parts that may depend on reading or writing any device state.
Unfortunately, it looks like we will have to enable SRAM scrambling (which requires entropy and the AST) from assembly.
Open Q: Can we zero all of SRAM before enabling scrambling? Should we? We certainly cannot zero it all after we have enabled scrambling.
// Will actually be implemented in assembly, as executed too early for C. clean_device_state_part_1() { // - Zero all ISA Registers // Depending on boot reason: // - Clear Main SRAM // This can either be done by overwriting all of SRAM, or by enabling SRAM // Scrambling. If we do both, we have to be careful about the order (see // above). // **Open Q:** Can we always do the same thing here, regardless of the boot // reason? } clean_device_state_part_2() { // Parameters: // - Boot Reason // Depending on boot reason: // - Clear Power Manager Scratch Registers // - Clear Retention SRAM // These clears *must* be done by Mask ROM. // - Reset most devices // **Open Q:** Which Devices are we leaving with state for ROM_EXT? }
This sets up execution so we can run C functions. We cannot execute any C code until we have setup the CRT.
DIFs Needed: None
Milestone Expectations:
Setting up the CRT involves: loading the .data
and .bss
sections, and setting up the stack and gp
(gp
may be used for referencing some data).
// Will actually be written in assembly crt_init() { // - Load `.data` initial image into SRAM. // - Zero `.bss` section. // - Zero stack. // - Load `sp`, `gp`. // **Open Q:** These are used by exception and interrupt handlers if written // in C. Should they be initialized as early as possible? We can turn off // the use of `gp` fairly easily, `sp` is an ABI issue. }
There is some data that is accessed by more than just the Mask ROM:
In order to keep the Mask ROM simple, a particular Mask ROM version will only support one version each of the following structures. This means they must be carefully designed to be extensible if the other systems accessing them may require additional data in these formats.
This is the storage for the IDs of keys that are used to sign a ROM_EXT. The ROM_EXT image contains the full key, this stores only the key id (which can be computed from the full key), and uses OTPs to decide whether a key is allowed to be used to validate a ROM_EXT.
Accessed by:
Needs to contain:
Stored In: OTP and ROM
Extensibility: None. A ROM_EXT can disable the use of a key, but cannot add new valid root key IDs. There will be a fixed set of root key IDs as part of a given Mask ROM version.
This is the in-flash structure that defines which ROM_EXT should be booted next, whether it should fall back to the other ROM_EXT if it fails to boot, and/or whether it should be marked as the primary ROM_EXT if it succeeds to validate.
This structure merely controls which ROM_EXT is validated and executed -- it does not contain any executable code. This means it does not need to be protected as securely as the ROM_EXT images, which contain code and are signed and validated before execution.
Accessed by:
Needs to contain:
Identifier (so we know we're reading the right thing)
This also acts like a version number, because the Mask ROM code that parses the boot policy can never be updated. Conversely, any changes to this structure require new Mask ROM parsing code, which should be denoted with a new identifier.
Which ROM_EXT slot should be chosen first (2.b, 2.c.i).
What to do if ROM_EXT does not validate (2.c.ii, 2.c.iii):
What to do if ROM_EXT validates successfully, just before jumping to ROM_EXT:
Checksum (of everything else).
Stored in: Flash (Info Partition)
Extensibility: None. This info only controls the actions of the Mask ROM. You cannot add functionality to the Mask ROM of a given chip, so there's no way to add other information to this structure.
The in-RAM representation also contains the boot reason (reset manager), because it needs to be checked most times the boot policy is also read.
Accessed by:
The manifest format is defined in ROM_EXT Manifest Format.
Stored in: Flash (at one of two fixed, memory-mapped, addresses)
Extensibility: