Introduction to Bootloaders
Embedded Systems Mastery
Fundamentals & Architecture
Microcontrollers, memory, interruptsSTM32 & ARM Cortex-M Development
ARM architecture, peripherals, HALRTOS Fundamentals (FreeRTOS/Zephyr)
Task management, scheduling, synchronizationCommunication Protocols Deep Dive
UART, SPI, I2C, CAN, USBEmbedded Linux Fundamentals
Linux kernel, userspace, filesystemU-Boot Bootloader Mastery
Boot process, configuration, customizationLinux Device Drivers
Character, block, network driversLinux Kernel Customization
Kernel configuration, modules, debuggingAndroid System Architecture
Android layers, services, frameworkAndroid HAL & Native Development
HAL interfaces, NDK, JNIAndroid BSP & Kernel
BSP development, kernel integrationDebugging & Optimization
JTAG, GDB, profiling, optimizationA bootloader is the first software that runs when a system powers on. It initializes hardware (RAM, clocks, peripherals), loads the operating system kernel into memory, and transfers control to it. U-Boot (Das U-Boot) is the most widely used bootloader in embedded Linux systems.
Why Bootloaders Matter
- Hardware initialization: Configure clocks, memory controllers, GPIOs
- Flexibility: Choose which OS to boot, from where (flash, SD, network)
- Recovery: Provide a fallback if the OS fails to boot
- Development: Load kernels over network (TFTP), debug early boot
- Security: Verify kernel signatures (secure boot)
Boot Process Architecture
ARM systems typically use a multi-stage boot process:
ARM Boot Stages
- ROM Bootloader (BL0): Burned into chip, loads SPL from boot device
- SPL (Secondary Program Loader): Initializes DRAM, loads full U-Boot
- U-Boot (BL2): Full bootloader—loads kernel, DTB, passes control
- Linux Kernel: Decompresses, initializes drivers, mounts root filesystem
- Init (PID 1): First userspace process, starts services
Power On
¦
?
+-----------------+
¦ ROM Bootloader ¦ ? Fixed in silicon, reads boot pins
¦ (on-chip) ¦ ? Loads SPL from SD/eMMC/SPI/UART
+-----------------+
?
+-----------------+
¦ U-Boot SPL ¦ ? Runs from internal SRAM (~128KB)
¦ (MLO/SPL.bin) ¦ ? Initializes DRAM controller
+-----------------+
?
+-----------------+
¦ U-Boot ¦ ? Full bootloader in DRAM
¦ (u-boot.bin) ¦ ? Environment, commands, boot scripts
+-----------------+
?
+-----------------+
¦ Linux Kernel ¦ ? zImage/Image + DTB loaded to DRAM
¦ + Device Tree ¦ ? U-Boot jumps to kernel entry point
+-----------------+
?
+-----------------+
¦ Root Filesystem ¦ ? Kernel mounts rootfs, runs init
¦ (rootfs) ¦
+-----------------+
U-Boot Architecture
# Clone U-Boot source
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
# U-Boot source structure
u-boot/
+-- arch/ # Architecture-specific (arm, x86, riscv)
+-- board/ # Board-specific code (vendor/board/)
+-- cmd/ # U-Boot commands (boot, mmc, tftp)
+-- common/ # Common boot code
+-- configs/ # Default configurations (*_defconfig)
+-- drivers/ # Device drivers
+-- dts/ # Device tree sources
+-- include/ # Headers
+-- lib/ # Libraries (string, crc, etc.)
+-- scripts/ # Build scripts, Kconfig
# List available board configs
ls configs/ | grep -i beagle
# am335x_evm_defconfig (BeagleBone)
# am57xx_evm_defconfig (BeagleBoard-X15)
Building U-Boot
# Set cross-compiler
export CROSS_COMPILE=arm-linux-gnueabihf-
# Configure for BeagleBone Black
make am335x_evm_defconfig
# Optional: customize
make menuconfig
# Build
make -j$(nproc)
# Output files
ls -la MLO u-boot.img
# MLO ? SPL (Secondary Program Loader)
# u-boot.img ? Full U-Boot binary
Environment Configuration
U-Boot stores configuration in environment variables—persisted in flash/eMMC or set at runtime.
# At U-Boot prompt (connected via serial)
# Print all environment variables
=> printenv
# Key variables
bootcmd=run mmc_boot # Command executed on autoboot
bootargs=console=ttyO0,115200n8 root=/dev/mmcblk0p2 rootwait
bootdelay=3 # Seconds before autoboot
ipaddr=192.168.1.100 # Board's IP address
serverip=192.168.1.1 # TFTP server IP
loadaddr=0x82000000 # Where to load kernel
fdtaddr=0x88000000 # Where to load device tree
# Set a variable
=> setenv bootdelay 5
# Save to persistent storage
=> saveenv
# Create boot script
=> setenv mmc_boot 'mmc dev 0; fatload mmc 0 ${loadaddr} zImage; fatload mmc 0 ${fdtaddr} am335x-boneblack.dtb; bootz ${loadaddr} - ${fdtaddr}'
Device Tree Basics
The Device Tree (DT) describes hardware to the kernel—no hardcoded board info in kernel source. U-Boot passes the DTB (Device Tree Blob) to the kernel.
// Example device tree snippet (arch/arm/boot/dts/am335x-boneblack.dts)
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am33xx";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB DRAM */
};
leds {
compatible = "gpio-leds";
led0 {
label = "beaglebone:green:heartbeat";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
};
&uart0 {
status = "okay";
};
&i2c0 {
status = "okay";
clock-frequency = <400000>;
eeprom: eeprom@50 {
compatible = "atmel,24c256";
reg = <0x50>;
};
};
# Compile device tree
dtc -I dts -O dtb -o am335x-boneblack.dtb am335x-boneblack.dts
# Decompile (inspect binary DTB)
dtc -I dtb -O dts -o output.dts am335x-boneblack.dtb
# In kernel build
make ARCH=arm dtbs
U-Boot Commands
# Help system
=> help # List all commands
=> help boot # Help on specific command
# Memory commands
=> md 0x80000000 100 # Memory display (hex dump)
=> mw 0x80000000 0xDEADBEEF # Memory write
=> cp 0x80000000 0x81000000 1000 # Copy memory
# Storage commands
=> mmc list # List MMC devices
=> mmc dev 0 # Select MMC device 0
=> mmc info # Show MMC info
=> fatls mmc 0 # List files on FAT partition
=> fatload mmc 0 0x82000000 zImage # Load file to RAM
# Network commands
=> dhcp # Get IP via DHCP
=> ping 192.168.1.1 # Test connectivity
=> tftp 0x82000000 zImage # Download file via TFTP
# Boot commands
=> bootm 0x82000000 # Boot uImage format
=> bootz 0x82000000 - 0x88000000 # Boot zImage with DTB
=> boot # Execute bootcmd
Boot Methods (TFTP, NFS, MMC)
Network Boot (Development)
# On host: Setup TFTP server
sudo apt install tftpd-hpa
sudo cp zImage am335x-boneblack.dtb /srv/tftp/
# On U-Boot: Configure network boot
=> setenv ipaddr 192.168.1.100
=> setenv serverip 192.168.1.1
=> setenv netboot 'tftp ${loadaddr} zImage; tftp ${fdtaddr} am335x-boneblack.dtb; setenv bootargs console=ttyO0,115200n8 root=/dev/nfs nfsroot=${serverip}:/srv/nfs/rootfs,v3 ip=dhcp; bootz ${loadaddr} - ${fdtaddr}'
=> run netboot
SD Card Boot (Production)
# SD card layout (typical)
# Partition 1: FAT32 (boot) - MLO, u-boot.img, zImage, *.dtb
# Partition 2: ext4 (rootfs) - Linux root filesystem
=> setenv mmcboot 'mmc dev 0; fatload mmc 0:1 ${loadaddr} zImage; fatload mmc 0:1 ${fdtaddr} am335x-boneblack.dtb; setenv bootargs console=ttyO0,115200n8 root=/dev/mmcblk0p2 rootwait; bootz ${loadaddr} - ${fdtaddr}'
=> setenv bootcmd 'run mmcboot'
=> saveenv
Bootloader Customization
# Create custom board support
# 1. Copy existing similar board
cp -r board/ti/am335x board/mycompany/myboard
cp configs/am335x_evm_defconfig configs/myboard_defconfig
# 2. Edit board configuration
# board/mycompany/myboard/board.c - Board init code
# include/configs/myboard.h - Board config header
# 3. Create defconfig
# Edit configs/myboard_defconfig
CONFIG_ARM=y
CONFIG_TARGET_MYBOARD=y
CONFIG_DEFAULT_DEVICE_TREE="myboard"
CONFIG_BOOTDELAY=3
CONFIG_BOOTCOMMAND="run mmcboot"
# 4. Build
make myboard_defconfig
make -j$(nproc)
Debugging U-Boot
# Enable debug output in U-Boot config
=> setenv bootargs ... debug earlyprintk
# JTAG debugging (requires hardware debugger)
# OpenOCD + GDB for low-level debugging
# Serial console is your best friend
# Always connect serial before powering on
# Typical settings: 115200 baud, 8N1
# Common issues:
# - No output: Check serial connection, baud rate
# - Stuck at SPL: DRAM init failed, check timings
# - "Wrong Image Format": Use correct boot command (bootm vs bootz)
# - "Bad Linux ARM zImage magic": Kernel corrupted or wrong load address
Conclusion & What's Next
You've mastered U-Boot—the critical bridge between hardware power-on and Linux kernel execution. Understanding the boot process, environment variables, and device trees is essential for embedded Linux development.
- Boot stages: ROM ? SPL ? U-Boot ? Kernel ? Init
- SPL initializes DRAM, loads full U-Boot
- Environment variables control boot behavior
- Device Tree describes hardware to kernel
- Network boot (TFTP/NFS) accelerates development
In Part 7, we'll dive into Linux Device Drivers—writing kernel code to interface with custom hardware.