Back to Technology

Embedded Systems Series Part 6: U-Boot Bootloader Mastery

January 25, 2026 Wasil Zafar 55 min read

Master U-Boot bootloader—boot process architecture, environment configuration, device tree basics, and bootloader customization.

Table of Contents

  1. Introduction to Bootloaders
  2. Boot Process Architecture
  3. U-Boot Architecture
  4. Environment Configuration
  5. Device Tree Basics
  6. U-Boot Commands
  7. Boot Methods (TFTP, NFS, MMC)
  8. Bootloader Customization
  9. Debugging U-Boot
  10. Conclusion & Next Steps

Introduction to Bootloaders

Series Navigation: This is Part 6 of the 12-part Embedded Systems Series. Review Part 5: Embedded Linux Fundamentals first.

A 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 SPL U-Boot Kernel
  1. ROM Bootloader (BL0): Burned into chip, loads SPL from boot device
  2. SPL (Secondary Program Loader): Initializes DRAM, loads full U-Boot
  3. U-Boot (BL2): Full bootloader—loads kernel, DTB, passes control
  4. Linux Kernel: Decompresses, initializes drivers, mounts root filesystem
  5. 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)

TFTP NFS
# 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)

MMC
# 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.

Key Takeaways:
  • 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.

Next Steps

Technology