Part I — Boot

§ 1.1 Bootloader & U-Boot

From power-on reset to start_kernel(): BIOS/UEFI handoff, U-Boot two-stage boot, Device Tree, and kernel image formats.

1. Overview

When a CPU powers on it has no OS, no filesystem, no device drivers — just ROM firmware and a program counter pointing at a fixed physical address. The boot chain progressively initializes hardware, locates the kernel image, loads it into RAM, hands it a hardware description (Device Tree), and jumps to its entry point. On x86 servers this is BIOS/UEFI → GRUB2 → vmlinuz. On embedded ARM/RISC-V boards it is ROM → SPL → U-Boot proper → kernel. Both paths converge at the same C function: start_kernel().

2. Key Data Structures

U-Boot Global Data — gd_t

The single most important U-Boot structure. A pointer to it is kept in a dedicated CPU register (r9 on ARM32, x18 on ARM64), so every driver and command can reach it without symbol lookup or function arguments.

FieldTypePurpose
bdstruct bd_info *Board info: DRAM base/size, flash geometry
flagsunsigned longGD_FLG_RELOC set after U-Boot relocates to top of DRAM
relocaddrunsigned longAddress U-Boot copied itself to in DRAM
ram_sizephys_size_tTotal DRAM size detected during board_init_f()
env_addrunsigned longPointer to live ENV buffer (key=value\0…\0)
fdt_blobvoid *DTB used by U-Boot itself for driver model
new_fdtvoid *Modified DTB pointer passed to the Linux kernel
dm_rootstruct udevice *Root of the driver-model device tree
cur_serial_devstruct udevice *Active console UART device

Device Tree Blob (DTB) — Hardware Description

A DTB is a compiled binary tree of hardware nodes and properties. U-Boot passes its physical address to the kernel in register x1 (ARM64) or r2 (ARM32). The kernel calls unflatten_device_tree() to expand it into an in-memory device_node tree that drivers probe against.

DTB Node PathKernel Driver BoundKey Property
/cpus/cpu@0ARM core drivercompatible = arm,cortex-a53
/memoryMemory controllerreg = <base size> — physical DRAM range
/soc/serial@addr8250 / ns16550 UARTcompatible = ns16550a
/soc/ethernet@addrDWMAC / stmmaccompatible = snps,dwmac
/chosen(no driver — metadata)bootargs, initrd-start/end

Kernel Image Formats

FormatPlatformSelf-decompressingNotes
vmlinuxanyNoRaw ELF — for debugging / symbol lookup only
vmlinuz / bzImagex86YesSelf-extracting; GRUB loads at 0x100000; entry at 0x1000000
zImageARM32YesCompressed binary; decompresses to 0x80008000
ImageARM64NoUncompressed; U-Boot booti validates 64-byte header
uImageU-BootOptionalLegacy: 64-byte header with load addr, entry addr, CRC
fitImage (ITB)U-BootOptionalSigned: kernel + DTB + initrd in one blob with hash nodes

3. Core Mechanism — U-Boot Two-Stage Boot

Background: Embedded SoCs have only 192–256 KB of on-chip SRAM at power-on; the DDR controller is uninitialized so DRAM is inaccessible. The full U-Boot binary is 300–600 KB — it cannot fit. The two-stage design solves this: SPL fits in SRAM, initializes DDR, then loads U-Boot proper into DRAM.

Plan:

  1. ROM BootROM copies SPL from flash (NAND / SPI NOR / eMMC) into SRAM and jumps to spl_start().
  2. SPL initializes PLLs (clocks) and DDR PHY. DDR training can take 10–100 ms (calibrates per-lane delays using test patterns).
  3. SPL calls spl_load_image(), reads a fixed offset or partition table, and copies U-Boot proper into DRAM.
  4. SPL calls board_init_r() which tail-jumps to U-Boot proper's entry. SRAM is no longer needed.
  5. U-Boot proper runs board_init_f() (relocation), then board_init_r() (full init: USB, PCI, Ethernet…).
  6. U-Boot reads ENV from flash (or TFTP), waits bootdelay seconds, then runs bootcmd.
  7. bootcmd loads kernel + DTB into DRAM, sets bootargs, and calls booti (ARM64) or bootz (ARM32).
  8. booti validates the ARM64 Image magic, sets x0 = 0, x1 = DTB address, and branches to the kernel's _text.

Example — booting an ARM64 board from eMMC:

StepU-Boot commandWhat happens
1mmc dev 0 1Select eMMC device 0, boot partition 1
2fatload mmc 0:1 0x80200000 ImageCopy 22 MB kernel binary into DRAM at 0x80200000
3fatload mmc 0:1 0x80000000 rk3568.dtbCopy 16 KB DTB into DRAM at 0x80000000
4setenv bootargs 'console=ttyS2,115200 root=/dev/mmcblk0p5 rw'Store cmdline in ENV; booti copies it to /chosen/bootargs
5booti 0x80200000 - 0x80000000Validate header magic 0x644d5241; set x1=0x80000000; branch

After step 5, the CPU is in arch/arm64/kernel/head.S, MMU off, cache off, running in EL2 or EL1 depending on the firmware. The kernel will enable the MMU inside __enable_mmu (≈ 20 instructions later) and then call start_kernel().

4. Minimal C Demo

A DTB is a binary tree stored in a single contiguous blob called the Flattened Device Tree (FDT). Both U-Boot and the Linux kernel use libfdt to parse it. The demo below simulates parsing the 40-byte FDT header — the same validation logic in fdt_check_header().

FDT Header Parser — C Demo
stdin (optional)

U-Boot stores all persistent settings as a flat key=value\0key=value\0...\0\0 buffer in flash (with a leading CRC32). The demo below implements the same linear scan used by env_get().

U-Boot ENV — key=value store — C Demo
stdin (optional)

5. Kernel & U-Boot Source Pointers

File / FunctionWhat it does
arch/arm64/kernel/head.S :: _textARM64 kernel entry — first instruction; MMU off, sets up page tables
arch/x86/boot/header.S :: _startx86 real-mode entry — BIOS jumps here; starts 16-bit setup code
arch/x86/boot/decompress.c :: decompress_kernel()Self-decompression of bzImage; calls KASLR address selection
drivers/of/fdt.c :: unflatten_device_tree()Expand flat DTB blob into kernel device_node linked list
drivers/of/fdt.c :: early_init_dt_scan()Parse /memory and /chosen nodes before MM init
u-boot: common/spl/spl.c :: spl_load_image()SPL main loop: find U-Boot proper image and jump
u-boot: common/board_f.c :: board_init_f()First-stage init list: clock, DRAM, relocation
u-boot: cmd/booti.c :: do_booti()ARM64 boot: validate Image magic, set x0/x1, branch
u-boot: lib/fdtdec.c :: fdtdec_get_addr()Read 'reg' property from DTB node
u-boot: env/common.c :: env_get()Scan ENV buffer for key; return value pointer

6. Interview Prep

#QuestionConcise Answer
Q1Why does U-Boot need two stages (SPL + proper)?At power-on only ≈192 KB of on-chip SRAM is available; DDR is not yet initialized. SPL fits in SRAM, initializes the DDR controller, then loads the 300–600 KB U-Boot proper into DRAM.
Q2What is a Device Tree and why does Linux need it?A hardware-description blob (nodes + properties). Embedded SoCs have no standard bus enumeration (unlike PCI), so the bootloader passes the DTB to the kernel so it can instantiate drivers without hard-coding board layout — one kernel binary boots thousands of boards.
Q3Difference between zImage and uImage?zImage is a self-decompressing ARM32 kernel image with no extra header. uImage wraps any image with a 64-byte U-Boot header that includes load address, entry address, OS type, compression type, and a CRC32 — so U-Boot can validate and place it correctly.
Q4How does U-Boot pass the DTB to the Linux kernel on ARM64?booti sets register x0 = 0 (primary CPU, per ABI) and x1 = physical address of the DTB blob, then branches directly to the kernel's _text entry point. The kernel reads x1 immediately in head.S.
Q5What happens if the DTB is missing or corrupt?unflatten_device_tree() panics with 'No DTB found' before the console is even up, so the system is silent. You need a JTAG or UART at 115200 to see early prints. On x86 a bad e820 memory map causes boot_params failures with similar silence.