Boot Sequence for embedded system

The Boot Sequence of Embedded Systems

Understanding the boot process is fundamental for embedded system development. It provides the knowledge needed to troubleshoot boot failures and use tools like JTAG to load images effectively.

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 0x00000000 or 0xFFFF0000.

What BootROM Does:

  1. 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.

  2. 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.

  3. 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:

  1. Initialize DDR SDRAM: This is its critical mission. It configures the complex timing and voltage settings required to "wake up" the external memory.

  2. Load the Main Program: It reads the full U-Boot binary (u-boot.bin) from storage into the now-active DDR.

  3. 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

For MIPS system SPL will jump to BL3- bootloader(like U-Boot). 
For modern ARM system, there are BL31, BL32 and BL33, and SPL will jump to BL31 (responsible for security)->BL32(responsible for encryption)->BL33(responsible for booting the OS) 

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-TEE 

BL33 (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: 

ComponentRoleDestination Example
u-boot-nodtb.binMain U-Boot (BL33)0x00200000 (DDR)
bl31.binTF-A (BL31)0x00040000 (SRAM)
u-boot.dtbDevice Tree(Hardware Info)0x00200000 (Appended)
tee.bin (Optional)OP-TEE (BL32)0x08400000 (Trust RAM)

What is the Build Process? 

To build a modern ARMv8 system image from scratch: 
  • Step 1: Compile TF-A 
    1. Download the TF-A source code. 
    2. Compile it to generate bl31.bin. 
  • Step 2: Compile OP-TEE (Optional
    1. Download the OP-TEE source code. 
    2. Compile it to generate tee.bin. 
  • Step 3: Compile U-Boot(BL33 + SPL)
    1. Download the U-Boot source code. 
    2. Configure board: make <board>_defconfig.
    3. 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
    4. Set environment variables: export CROSS_COMPILE=aarch64-linux-gnu-
    5. Run the U-Boot compilation. 
      //the location of your bl31.elf
      make 
      BL31=path/to/bl31.elf TEE=path/to/tee.bin
U-Boot's build tools (Binman/mkimage) will automatically package SPL, u-boot.bin, bl31, and tee into the final image.

    OS Kernel

    Finally, the CPU jumps to the kernel entry point. The kernel initializes drivers, mounts the root filesystem, and starts the first user-space process -init, bringing the system to life.

    Chain of Trust

    How do we ensure no tampering? 

    Verification Process: This involves the Chain of Trust (CoT) and the monitoring role of BL31 (EL3 Monitor). During the process where BL1 loads BL2, and BL2 loads BL3, every handover must undergo strict verification. It is like a relay race: the next runner must confirm that the baton passed by the previous runner is authentic and not a "bomb" (malicious code). This mechanism is called the Chain of Trust (CoT). 

    Root of Trust (RoT): 

    The source of everything. When the chip is manufactured, the Hash of the OEM's public key is burned into the chip's internal eFuse (Electronic Fuse). eFuses are One-Time Programmable (OTP). Once blown (burned), they cannot be changed. This provides a physical guarantee that the source is immutable. 

    BL1 Verifies BL2: 

    1. Read: BL1 (ROM) reads the BL2 image. 
    2. Extract: BL1 extracts the digital signature from the image. 
    3. Verify: BL1 uses the Hash stored in the eFuse to verify the integrity of the public key and the signature. 
    4. Result: Match: It means BL2 was released by the original manufacturer -> Execute it. Mismatch: BL1 immediately halts (Panic) and refuses to boot. 

    BL2 Verifies BL3x: 

    Once BL2 is up and running, it uses the same mechanism to verify BL31, BL32, and BL33 (U-Boot). By linking these stages one by one, until the Linux Kernel starts, we can be confident that the entire system is clean.

    Reference:



      Comments