The Boot Sequence of Embedded Systems
BL1-Bootrom
At the moment of power-on, the CPU is uninitialized. Before it can tackle complex tasks, it must perform basic housekeeping: initializing essential clocks and watchdogs, detecting the boot source (SD, eMMC, SPI), and validating the security of the next stage.
This initial code is hard-coded into the SoC's internal ROM by the vendor and is known as the BootROM (or Mask ROM). When the processor comes out of reset, it jumps to a specific reset vector to execute this code.
ARM Architecture: Execution typically starts at
0x00000000or0xFFFF0000.
What BootROM Does:
Hardware Basics: Configures clocks to a speed sufficient for accessing flash memory, disables interrupts to prevent early crashes, and sets up the watchdog and initial stack.
Boot Media Detection: Reads system registers (often determined by hardware strap pins) to locate the software bootloader. This could be NAND/NOR flash, SD card, eMMC, or USB.
Handover:
XiP (Execute in Place): If the bootloader is in NOR flash, control transfers directly to it.
SRAM Copy: For other media (like SD or NAND), BootROM copies the bootloader into the CPU's internal SRAM and runs it there.
BL2-SPL(Secondary Program Loader)
The Bridge to External RAM. The SPL is an optional but common stage.
Why is it needed? If the internal SRAM is too small to hold the full-featured bootloader (like U-Boot), the SPL acts as a small, intermediate loader.At this stage, the large external RAM (DDR) is not yet initialized and cannot be used.
What SPL Does:
Initialize DDR SDRAM: This is its critical mission. It configures the complex timing and voltage settings required to "wake up" the external memory.
Load the Main Program: It reads the full U-Boot binary (
u-boot.bin) from storage into the now-active DDR.Jump: It sets the Program Counter (PC) to the entry point of U-Boot in external RAM and hands over control.
Code Entry Point:
Assembly Start: The first code executed is typically in
start.S.Code snippet.globl _start _start: b reset ...(Location:
arch/arm/cpu/armv8/start.S)Stack Initialization: Before the processor can run any C code, it must have a working stack.
Transition to C: After low-level setup (like stack initialization), it jumps to the C environment.
With SPL: The first C function is typically board_init_f() in common/spl/spl.c.
Without SPL: The first C function is board_init_f() in common/board_f.c.
BL3: The Main Bootloader
BL31 (Secure Monitor):
Stays resident in memory to handle EL3 runtime services (like power management via PSCI-Power State Coordination Interface). It acts as a permanent gateway that allows the "Normal World" (your OS) to request secure services (like power management via PSCI) from the "Secure World." It manages the context switch when moving from Secure to Non-secure states.Trusted Firmware-A (TF-A)BL32 (Trusted OS):
Optional. Runs a secure environment like OP-TEE for handling sensitive data (fingerprints, DRM). OP-TEEBL33 (Non-secure Bootloader):
This is your main bootloader, usually U-Boot. Manages hardware to load the Operating System (Linux).Key Responsibilities:
- Advanced Initialization: Network, USB, Storage, Display.
- Filesystem Support: Reads FAT32, EXT4, Btrfs, UBIFS.
- Environment Management: Handles variables like IPs or boot arguments.
- CLI: Provides an interactive command line for users.
- Bootm (Boot Multi-image):
- Load Kernel: Loads the OS kernel into RAM.
- Load Device Tree: Loads the .dtb hardware description.
- Ramdisk (Optional): Loads an initrd.
- Jump: Transfers control to the OS kernel.
U-Boot Image Tree Blob (u-boot.itb)
Why do we need it? In older 32-bit ARM systems, the bootloader was a single file (u-boot.itb). Modern 64-bit systems are fragmented; the SPL must load multiple binaries (BL31, BL33, BL32, DTB) into specific, different memory addresses. Hardcoding these addresses in C code is messy.
We use the FIT (Flattened Image Tree) format to create u-boot.itb. This is a container (like a ZIP file) with a header that tells the SPL exactly where to load each component.
Inside u-boot.itb:
| Component | Role | Destination Example |
| u-boot-nodtb.bin | Main U-Boot (BL33) | 0x00200000 (DDR) |
| bl31.bin | TF-A (BL31) | 0x00040000 (SRAM) |
| u-boot.dtb | Device Tree(Hardware Info) | 0x00200000 (Appended) |
| tee.bin (Optional) | OP-TEE (BL32) | 0x08400000 (Trust RAM) |
What is the Build Process?
- Step 1: Compile TF-A
- Download the TF-A source code.
- Compile it to generate bl31.bin.
- Step 2: Compile OP-TEE (Optional)
- Download the OP-TEE source code.
- Compile it to generate tee.bin.
- Step 3: Compile U-Boot(BL33 + SPL)
- Download the U-Boot source code.
- Configure board: make <board>_defconfig.
- Ensure .config enables FIT support:
CONFIG_SPL_FIT=y
CONFIG_SPL_LOAD_FIT=y
CONFIG_BINMAN=y
// for tee (optional)
CONFIG_TEE=y C
ONFIG_OPTEE=y - Set environment variables: export CROSS_COMPILE=aarch64-linux-gnu-
- Run the U-Boot compilation.
//the location of your bl31.elf
make BL31=path/to/bl31.elf TEE=path/to/tee.bin
OS Kernel
init, bringing the system to life.Chain of Trust
How do we ensure no tampering?
Root of Trust (RoT):
BL1 Verifies BL2:
- Read: BL1 (ROM) reads the BL2 image.
- Extract: BL1 extracts the digital signature from the image.
- Verify: BL1 uses the Hash stored in the eFuse to verify the integrity of the public key and the signature.
- Result: Match: It means BL2 was released by the original manufacturer -> Execute it. Mismatch: BL1 immediately halts (Panic) and refuses to boot.
Comments