armhub.dev
Cortex-A · 12 pages
Cortex-A

Boot flow & EL levels

From silicon reset to Linux init — the multi-stage boot chain on ARMv8-A, and what runs in each Exception Level.

3 min read · 571 wordsupdated · May 12, 2026

ARMv8-A boots through a chain of binaries, each signed and verified by the previous. The chain isn't optional — even unsecured platforms typically run TF-A as BL31. Understanding the stages is the difference between "it just boots" and being able to diagnose a boot hang.

The canonical chain

              ┌──────┐
   silicon ──▶│ ROM  │   BL1 — vendor's mask ROM, runs at EL3
              └──┬───┘   Loads BL2 from boot media, verifies signature.
                 ▼
              ┌──────┐
              │ BL1  │   (Reset vector → trampoline → BL2 entry)
              └──┬───┘
                 ▼
              ┌──────┐
              │ BL2  │   Loads BL31 (and optionally BL32, BL33),
              └──┬───┘   sets up DRAM, hands control to BL31.
                 ▼
              ┌──────┐
              │ BL31 │   ── EL3 secure monitor (TF-A runtime).
              └─┬─┬──┘   Provides PSCI, runs forever in the background.
                │ │
                │ └────▶ BL32 (e.g. OP-TEE OS at EL1-S)
                ▼
              ┌──────┐
              │ BL33 │   ── EL2/EL1 normal-world bootloader (U-Boot / UEFI / coreboot).
              └──┬───┘   Loads the OS image and jumps into it.
                 ▼
              ┌──────┐
              │ OS   │   ── EL1 (or EL2 if KVM) — Linux, Android, BSD, etc.
              └──────┘

What lives where

BL1 — vendor mask ROM (EL3)

Built into silicon, cannot be updated. It runs immediately after reset, configures the minimum hardware needed to load the next stage (often just the boot SPI/eMMC), and verifies a signature against a public key fused into OTP.

BL2 — trusted boot firmware (EL3, then EL1-S)

Runs out of internal SRAM (DRAM isn't trained yet). Trains DDR, brings up the rest of the boot media, loads BL31/BL32/BL33 into their target addresses, and validates signatures. Then drops to BL31. Vendor-specific — Rockchip's ddr-init, NXP's PBL, NVIDIA's MB1/MB2 etc. all live here.

BL31 — secure monitor (EL3 runtime)

Trusted Firmware-A is the reference implementation. Once started, it never exits — it sits at EL3 for the rest of system uptime, servicing:

  • PSCI calls — CPU_ON, CPU_OFF, SYSTEM_OFF, SYSTEM_RESET, the standard ARM power-state coordination interface.
  • SMC dispatching — routes SMC calls from EL1 to either the platform (PSCI), the secure partition (BL32/OP-TEE), or vendor SiP services.
  • GICv3 redistributor setup, CPU interface saving on power-down, errata workarounds.

BL32 — trusted OS (EL1-S, optional)

Typically OP-TEE. Hosts trusted applications that handle DRM keys, biometric templates, secure storage. Loaded by BL2, started by BL31, runs in parallel with Linux.

BL33 — non-secure bootloader (EL2 or EL1)

The bootloader your firmware engineer talks to:

  • U-Boot — historically dominant on embedded Linux. Configurable, scriptable, supports network boot, USB boot, FAT/ext4.
  • UEFI (TianoCore EDK2) — server-class ARM (AWS Graviton, Ampere) and Windows on ARM.
  • coreboot — increasingly used on Chromebooks and homelab boxes.

BL33 loads the kernel (.Image on Linux), the device tree blob, and the initramfs, then jumps to the kernel entry with a specific register convention (DTB pointer in x0).

What lives at each EL

┌──────────────────────────────────────────────────────────┐
│ EL0:  user space — apps, daemons                          │
├──────────────────────────────────────────────────────────┤
│ EL1:  kernel — Linux, Android, BSD                        │
├──────────────────────────────────────────────────────────┤
│ EL2:  hypervisor — KVM (when virtualization is on), Xen  │
├──────────────────────────────────────────────────────────┤
│ EL3:  secure monitor — TF-A (BL31)                       │
└──────────────────────────────────────────────────────────┘
 
         ─────────────── orthogonal ──────────────
 
┌──────────────────────────────────────────────────────────┐
│ Secure world:                                             │
│   EL1-S: trusted OS (OP-TEE)                              │
│   EL0-S: trusted applets (TAs)                            │
└──────────────────────────────────────────────────────────┘

EL3 is always non-secure-blind — code at EL3 is the only code that can see both worlds. Transitioning from non-secure to secure (or vice versa) goes through an SMC to EL3 which dispatches to the right side.

Exception level transitions on ARMv8-A
SVC #immERETHVC #immERETSMC #immERETSMCEL0 (apps)EL1 (kernel)EL2 (hypervisor)EL3 (secure monitor)
SVC traps user space into the kernel, HVC traps the kernel into the hypervisor, and SMC always traps to EL3. ERET returns to the caller's EL using the saved SPSR/ELR pair.

PSCI — Power State Coordination

PSCI is the standardised ARM API. Every modern ARMv8-A OS calls PSCI through SMC (or HVC on virtualised guests) to manage power. The function IDs:

FunctionWhat it does
CPU_ONPower on a secondary core; pass it an entry point
CPU_OFFPower off the calling core
CPU_SUSPENDEnter a CPU-local idle state
SYSTEM_OFFFull system poweroff
SYSTEM_RESETReboot
AFFINITY_INFOQuery whether a CPU is on

The kernel's arm,psci-0.2 device-tree binding describes how to invoke it. Linux's arch/arm64/kernel/psci.c is what makes a shutdown -h now actually power off the board.

Debugging a boot failure

A boot failure tells you which stage hung by where the UART output stops:

  • No output at all → BL1 didn't even start. Boot media misconfiguration, fuse problem, bad signature on BL2.
  • Vendor banner but no TF-A banner → BL2 loaded BL31 but BL31 panicked (often missing DDR config).
  • TF-A banner but no U-Boot prompt → BL33 didn't load or jumped to the wrong address.
  • U-Boot prompt but no kernel boot → device tree / kernel image mismatch, missing initramfs.
NOTICE:  Booting Trusted Firmware
NOTICE:  BL2: v2.10.0(release):v2.10.0
INFO:    BL2: Doing platform setup
INFO:    DDR training succeeded
INFO:    BL31: Initializing GICv3
INFO:    BL31: Initializing PSCI
INFO:    Entering BL33
NOTICE:  
U-Boot 2024.04 ...

Each NOTICE and INFO is a checkpoint you can correlate against TF-A source.

See also