Project 10: U-Boot Exploration and Customization
Master the industry-standard bootloader used in billions of embedded devices worldwide.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | ★★★☆☆ Advanced |
| Time Estimate | 1-2 weeks |
| Language | C (reading/modifying existing code), Shell scripting (U-Boot commands) |
| Prerequisites | Linux command line comfort, cross-compilation basics, Projects 1-4 recommended |
| Key Topics | Cross-compilation, device tree, TFTP network boot, custom commands, environment variables |
1. Learning Objectives
By completing this project, you will:
- Understand U-Boot architecture: Learn how the most widely-used embedded bootloader is structured, configured, and extended
- Master cross-compilation: Build software on x86 for ARM/MIPS/PowerPC targets using proper toolchains
- Work with device trees: Understand how hardware description files configure boot behavior and kernel handoff
- Implement network boot workflows: Set up TFTP-based development cycles used in professional embedded development
- Extend a production codebase: Navigate a large, mature C project and add custom functionality
- Configure boot sequences: Use environment variables and boot scripts to automate complex boot flows
2. Theoretical Foundation
2.1 Core Concepts
U-Boot (Universal Bootloader) is the dominant bootloader for non-x86 embedded systems. Understanding U-Boot means understanding how the majority of embedded Linux devices boot.
U-Boot's Role in Embedded Systems:
┌─────────────────────────────────────────────────────────────────────────┐
│ POWER ON │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ ROM BOOTLOADER (Vendor-specific) │
│ • SoC-specific first-stage loader in ROM │
│ • Minimal initialization │
│ • Loads SPL or U-Boot from storage │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SPL (Secondary Program Loader) │
│ • Tiny U-Boot subset (~30KB) │
│ • Initializes DRAM (critical - can't run from RAM without this!) │
│ • Loads full U-Boot into DRAM │
│ • Sometimes called MLO, u-boot-spl.bin │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ U-BOOT (Full) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Command Line Interface │ │
│ │ • Environment variables (bootcmd, bootargs, etc.) │ │
│ │ • Interactive shell with 100+ commands │ │
│ │ • Script execution (boot.scr) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Driver Framework │ │
│ │ • Storage: MMC/SD, NAND, NOR, USB, SATA │ │
│ │ • Network: Ethernet, USB gadget │ │
│ │ • Display: LCD, HDMI │ │
│ │ • GPIO, I2C, SPI, UART │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Boot Target Support │ │
│ │ • Linux kernel (zImage, Image, uImage) │ │
│ │ • Device tree (DTB) loading and modification │ │
│ │ • Initramfs loading │ │
│ │ • EFI payload support │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ LINUX KERNEL │
│ • Receives DTB pointer in register r2 (ARM) or x0 (ARM64) │
│ • Receives command line in bootargs │
│ • Takes over all hardware control │
└─────────────────────────────────────────────────────────────────────────┘
The Device Tree (DTB/DTS)
Device trees describe hardware that cannot be discovered at runtime. Unlike x86 systems with PCI enumeration, ARM/MIPS systems need explicit hardware descriptions.
Device Tree Concept:
┌─────────────────────────────────────────────────────────────────────────┐
│ .dts (Device Tree Source) │
│ │
│ / { │
│ model = "Raspberry Pi 3 Model B+"; │
│ compatible = "brcm,bcm2837"; │
│ │
│ memory@0 { │
│ device_type = "memory"; │
│ reg = <0x0 0x40000000>; /* 1GB at address 0 */ │
│ }; │
│ │
│ soc { │
│ uart0: serial@7e201000 { │
│ compatible = "arm,pl011"; │
│ reg = <0x7e201000 0x200>; │
│ interrupts = <2 25>; │
│ }; │
│ │
│ gpio@7e200000 { │
│ compatible = "brcm,bcm2835-gpio"; │
│ reg = <0x7e200000 0xb4>; │
│ }; │
│ }; │
│ │
│ chosen { │
│ bootargs = "console=ttyAMA0,115200"; │
│ /* U-Boot modifies this at runtime! */ │
│ }; │
│ }; │
└─────────────────────────────────────────────────────────────────────────┘
│
│ dtc (Device Tree Compiler)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ .dtb (Device Tree Blob) │
│ │
│ • Binary format, efficient to parse at boot │
│ • U-Boot loads this to memory │
│ • U-Boot MODIFIES it before passing to kernel: │
│ - Adds detected RAM size │
│ - Adds MAC addresses from EEPROM │
│ - Adds serial numbers │
│ - Modifies bootargs │
│ - Reserves memory regions │
└─────────────────────────────────────────────────────────────────────────┘
TFTP Network Boot
Network boot is essential for embedded development, eliminating SD card swapping.
TFTP Development Workflow:
Developer Machine Target Board
┌────────────────────┐ ┌────────────────────┐
│ │ │ │
│ Edit kernel code │ │ U-Boot running │
│ │ │ │ │ │
│ ▼ │ │ │ │
│ Compile kernel │ │ │ │
│ │ │ │ │ │
│ ▼ │ │ │ │
│ Copy to /srv/tftp │ │ │ │
│ │ │ Ethernet │ │ │
│ ▼ │◄──────────────► │ │ │
│ TFTP Server │ │ ▼ │
│ (tftpd-hpa) │ │ "tftp $kernel_addr│
│ │ │ │ kernel.img" │
│ │ │ │ │ │
│ │ UDP/69 │ │ │ │
│ └───────────┼──────────────────┼────────┘ │
│ │ 512-byte blocks │ Kernel loads │
│ │ with ACKs │ │ │
│ │ │ ▼ │
│ │ │ "bootz $kernel_addr│
│ │ │ - $fdt_addr" │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ Kernel boots! │
└────────────────────┘ └────────────────────┘
Time from code change to test: ~30 seconds (vs. 5+ minutes with SD card)
2.2 Why This Matters
Industry Prevalence: U-Boot runs on:
- Raspberry Pi (when using Linux)
- Almost all ARM development boards (BeagleBone, NVIDIA Jetson, i.MX, etc.)
- Consumer routers (OpenWrt devices)
- Network switches and appliances
- Automotive infotainment systems
- IoT gateways
- Industrial controllers
- Billions of devices total
Career Impact:
- Embedded Linux positions require U-Boot knowledge
- Board bring-up engineers work with U-Boot daily
- Understanding U-Boot makes kernel debugging much easier
- Network boot skills accelerate development dramatically
2.3 Historical Context
- 1999: Wolfgang Denk starts PPCBoot for PowerPC embedded systems
- 2002: Renamed to U-Boot, expanded beyond PowerPC
- 2008: Device tree support added
- 2010+: Became the de facto standard for ARM Linux
- 2015+: SPL (Secondary Program Loader) architecture matures
- 2020+: UEFI support added for ARM servers
U-Boot’s longevity comes from:
- Clean, modular architecture
- Active maintenance (1000+ commits/month)
- Board vendor contributions
- Comprehensive documentation
2.4 Common Misconceptions
Misconception 1: “U-Boot is just for Linux”
- Reality: U-Boot can boot any OS (FreeBSD, QNX, bare-metal) or even act as the final runtime for simple applications.
Misconception 2: “Device trees are optional”
- Reality: Modern ARM Linux requires device trees. Without them, the kernel doesn’t know what hardware exists.
Misconception 3: “TFTP is insecure so it’s not used in production”
- Reality: TFTP is used in production for initial provisioning and recovery. Production devices then boot from local storage.
Misconception 4: “U-Boot configuration is like Linux kernel configuration”
- Reality: Similar (Kconfig), but U-Boot has far more board-specific configuration. Wrong defconfig = doesn’t boot.
3. Project Specification
3.1 What You Will Build
A complete U-Boot development environment with:
- U-Boot built from source for Raspberry Pi 3/4 or QEMU ARM64
- Working serial console for interactive U-Boot access
- TFTP network boot capability for rapid development
- Custom boot script (boot.scr) automating boot sequence
- Custom U-Boot command demonstrating codebase extension
3.2 Functional Requirements
| Requirement | Description | Verification |
|---|---|---|
| FR1: Build | Cross-compile U-Boot for target | u-boot.bin or u-boot.elf generated without errors |
| FR2: Serial | Access U-Boot console via serial | See U-Boot banner and prompt over UART |
| FR3: Environment | Navigate and modify environment variables | printenv, setenv, saveenv work correctly |
| FR4: Network | Configure IP and reach TFTP server | ping command succeeds |
| FR5: TFTP | Download file via TFTP | tftp command downloads kernel.img |
| FR6: Boot Script | Automate boot with boot.scr | Boot script executes automatically |
| FR7: Custom Command | Add new U-Boot command | help shows your command, command executes |
3.3 Non-Functional Requirements
| Requirement | Description |
|---|---|
| NFR1 | Build completes in < 5 minutes on modern hardware |
| NFR2 | Custom command follows U-Boot coding style |
| NFR3 | Boot script handles TFTP failure gracefully (fallback) |
| NFR4 | Documentation exists for reproducing your setup |
3.4 Example Usage / Output
# U-Boot console on Pi (via serial):
U-Boot 2024.01 (Jan 01 2024)
DRAM: 1 GiB
RPI 3 Model B+ (0xa020d3)
MMC: mmc@7e202000: 0
Loading Environment from FAT... OK
Hit any key to stop autoboot: 0
=> help
...
hello - print hello message with optional arguments
...
=> printenv bootcmd
bootcmd=run mmc_boot || run net_boot
=> setenv serverip 192.168.1.100
=> setenv ipaddr 192.168.1.200
=> ping 192.168.1.100
Using smsc95xx_eth device
host 192.168.1.100 is alive
=> tftp 0x80000 kernel.img
Using smsc95xx_eth device
TFTP from server 192.168.1.100; our IP address is 192.168.1.200
Filename 'kernel.img'.
Load address: 0x80000
Loading: ##################################################
done
Bytes transferred = 5242880 (500000 hex)
=> md 0x80000 10
00080000: ea000006 e1a00000 e1a00000 e1a00000
00080010: e1a00000 e1a00000 e1a00000 e1a00000
=> bootm 0x80000
## Booting kernel from Legacy Image at 00080000 ...
Starting kernel ...
# Or with your custom command:
=> hello
Hello from my custom U-Boot command!
=> hello world
Hello from my custom U-Boot command!
Arguments: world
3.5 Real World Outcome
Upon completion, you’ll have:
- A fully functional embedded development environment
- Experience with the bootloader powering billions of devices
- Skills directly applicable to embedded Linux positions
- Understanding of network boot workflows used in production
- Ability to read and modify a large, professional C codebase
4. Solution Architecture
4.1 High-Level Design
Development Environment Architecture:
┌─────────────────────────────────────────────────────────────────────────┐
│ HOST MACHINE (x86_64) │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────────┐ │
│ │ Cross-Compile │ │ TFTP │ │ Serial │ │
│ │ Toolchain │ │ Server │ │ Terminal │ │
│ │ aarch64-linux │ │ tftpd-hpa │ │ screen/minicom │ │
│ │ -gnu-gcc │ │ /srv/tftp/ │ │ 115200 8N1 │ │
│ └───────┬───────┘ └───────┬───────┘ └───────────┬───────────────┘ │
│ │ │ │ │
│ ┌───────▼───────┐ │ │ │
│ │ U-Boot │ │ │ │
│ │ Source │ │ │ │
│ │ git clone │ │ │ │
│ └───────┬───────┘ │ │ │
│ │ │ │ │
│ ┌───────▼───────┐ │ │ │
│ │ Build Output │ │ │ │
│ │ u-boot.bin ├──────────┼──────────────────────┼────────────────┐ │
│ │ boot.scr │ │ │ │ │
│ │ kernel.img ├──────────┘ │ │ │
│ └───────────────┘ │ │ │
│ │ │ │
│ ┌─────────────────────────┘ │ │
│ │ USB-Serial │ │
│ │ Adapter │ │
└──────────────────────────┼──────────────────────────────────────────┼──┘
│ │
│ GPIO 14/15 │ SD Card
│ (UART TX/RX) │
▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ TARGET BOARD (Raspberry Pi 3/4) │
│ ┌────────────────────────────────────────────────────────────────────┐│
│ │ U-BOOT ││
│ │ ││
│ │ 1. Read config.txt, load u-boot.bin as kernel ││
│ │ 2. Initialize console on UART ││
│ │ 3. Load environment from FAT partition ││
│ │ 4. Execute bootcmd (or wait for user input) ││
│ │ 5. TFTP download kernel.img, dtb ││
│ │ 6. Boot kernel with device tree ││
│ │ ││
│ └────────────────────────────────────────────────────────────────────┘│
│ │
│ Network: Ethernet (onboard) ─────────► Router/Switch │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4.2 Key Components
| Component | Description | Location |
|---|---|---|
| U-Boot Source | Main repository | git clone https://source.denx.de/u-boot/u-boot.git |
| Board Config | Hardware-specific settings | configs/rpi_3_defconfig or configs/qemu_arm64_defconfig |
| Device Tree | Hardware description | arch/arm/dts/bcm2837-rpi-3-b.dts |
| Command Framework | CLI infrastructure | cmd/ directory |
| Environment | Boot configuration | Stored in FAT partition or flash |
| Boot Script | Automated boot sequence | boot.scr (compiled from boot.txt) |
4.3 Data Structures
U-Boot Command Structure (include/command.h):
struct cmd_tbl {
char *name; /* Command name */
int maxargs; /* Maximum number of arguments */
int repeatable; /* Can command be repeated with Enter? */
int (*cmd)(struct cmd_tbl *, int, int, char * const []);
char *usage; /* Short help message */
char *help; /* Long help message */
int (*complete)(int argc, char * const argv[],
char last_char, int maxv, char *cmdv[]);
};
/* Registration macro */
U_BOOT_CMD(
name, /* Command name */
maxargs, /* Maximum arguments */
repeatable, /* Repeat on Enter? */
command, /* Command function */
usage, /* Short help */
help /* Long help */
);
Environment Variables:
| Variable | Purpose | Example |
|---|---|---|
bootcmd |
Command executed at boot | run mmc_boot |
bootargs |
Kernel command line | console=ttyAMA0,115200 root=/dev/mmcblk0p2 |
ipaddr |
Board IP address | 192.168.1.200 |
serverip |
TFTP server IP | 192.168.1.100 |
kernel_addr_r |
Kernel load address | 0x80000 |
fdt_addr_r |
Device tree load address | 0x2600000 |
4.4 Algorithm Overview
Boot Sequence Algorithm:
1. Power On
└─► Hardware reset, CPU starts at vendor ROM
2. SPL Load (if applicable)
├─► SPL initializes DRAM
└─► SPL loads full U-Boot to DRAM
3. U-Boot Initialization
├─► board_init_f() - Early initialization (serial, basic clocks)
├─► Relocate to end of DRAM
├─► board_init_r() - Full initialization (drivers, devices)
└─► Load environment from storage
4. Autoboot Check
├─► Display "Hit any key to stop autoboot"
├─► Wait bootdelay seconds
└─► If key pressed → Interactive mode
Else → Execute bootcmd
5. bootcmd Execution (typical)
├─► Load kernel from MMC: load mmc 0:1 ${kernel_addr_r} kernel.img
├─► Load DTB: load mmc 0:1 ${fdt_addr_r} bcm2837-rpi-3-b.dtb
├─► Modify DTB: fdt addr ${fdt_addr_r}; fdt resize
├─► Set bootargs: setenv bootargs console=ttyAMA0,115200 root=/dev/mmcblk0p2
└─► Boot: bootz ${kernel_addr_r} - ${fdt_addr_r}
6. Kernel Handoff (ARM)
├─► Disable interrupts
├─► Set registers: r0=0, r1=machine_type, r2=dtb_address
├─► Disable MMU/caches (most kernels expect this)
└─► Jump to kernel entry point
5. Implementation Guide
5.1 Development Environment Setup
Ubuntu/Debian (Recommended):
# Install build dependencies
sudo apt update
sudo apt install -y \
git \
build-essential \
gcc-aarch64-linux-gnu \
device-tree-compiler \
bison \
flex \
libssl-dev \
libncurses-dev \
bc \
u-boot-tools \
tftpd-hpa \
nfs-kernel-server \
screen
# Verify toolchain
aarch64-linux-gnu-gcc --version
# Should show: aarch64-linux-gnu-gcc (Ubuntu ...) 11.x or later
# Clone U-Boot
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout v2024.01 # Use a stable release
macOS:
# Install dependencies
brew install --cask gcc-arm-embedded
brew install u-boot-tools dtc
brew tap ArmMbed/homebrew-formulae
brew install arm-none-eabi-gcc
# For cross-compilation, consider using Docker
docker run -it -v $(pwd):/work ubuntu:22.04
# Then follow Ubuntu instructions inside container
TFTP Server Setup:
# Install and configure TFTP server
sudo apt install tftpd-hpa
# Edit config
sudo vim /etc/default/tftpd-hpa
# Set: TFTP_DIRECTORY="/srv/tftp"
# Set: TFTP_OPTIONS="--secure --create"
# Create directory and set permissions
sudo mkdir -p /srv/tftp
sudo chown tftp:tftp /srv/tftp
sudo chmod 755 /srv/tftp
# Restart service
sudo systemctl restart tftpd-hpa
sudo systemctl enable tftpd-hpa
# Verify
echo "test" | sudo tee /srv/tftp/test.txt
tftp localhost -c get test.txt
cat test.txt # Should show "test"
5.2 Project Structure
u-boot-project/
├── u-boot/ # Cloned U-Boot repository
│ ├── arch/arm/dts/ # Device tree sources
│ ├── board/raspberrypi/ # Board-specific code
│ ├── cmd/ # Command implementations
│ │ ├── Makefile # Command build config
│ │ ├── hello.c # YOUR CUSTOM COMMAND
│ │ └── ...
│ ├── configs/ # Board configurations
│ │ ├── rpi_3_defconfig
│ │ ├── rpi_4_defconfig
│ │ └── qemu_arm64_defconfig
│ ├── include/ # Headers
│ │ ├── command.h # Command infrastructure
│ │ └── configs/ # Board config headers
│ └── ...
├── output/ # Build outputs
│ ├── u-boot.bin # Main U-Boot binary
│ ├── boot.scr # Compiled boot script
│ └── ...
├── sdcard/ # SD card contents
│ ├── config.txt # Pi firmware config
│ ├── u-boot.bin # Copied U-Boot
│ ├── boot.scr # Boot script
│ └── ...
├── tftp/ # TFTP server root (symlinked)
│ ├── kernel.img
│ └── dtb.dtb
└── scripts/
├── build.sh # Build automation
├── deploy.sh # Deploy to SD card
└── boot.txt # Boot script source
5.3 The Core Question You’re Answering
How do professional embedded systems manage complex boot sequences, device initialization, and flexible deployment across different hardware configurations?
This project answers the fundamental question of production bootloader architecture: How does a single bootloader binary handle diverse hardware, support network-based development workflows, provide interactive debugging capabilities, and maintain backward compatibility across product generations? U-Boot demonstrates industrial-strength bootloader design used in billions of devices.
5.4 Concepts You Must Understand First
Before building this project, verify your understanding of these prerequisite concepts:
- Cross-compilation toolchains (Chapter 2, “Embedded Linux Primer” by Christopher Hallinan)
- Self-assessment question: Can you explain why cross-compiling requires different headers and libraries than native compilation? What does
CROSS_COMPILE=specify? - Why it matters: U-Boot must be built on x86 for ARM/MIPS/PowerPC targets
- Self-assessment question: Can you explain why cross-compiling requires different headers and libraries than native compilation? What does
- Device Tree (DTB/DTS) structure (U-Boot Device Tree Documentation)
- Self-assessment question: What information does a device tree contain that cannot be discovered at runtime? How does the bootloader pass the DTB to the kernel?
- Why it matters: U-Boot modifies device trees to pass board-specific information (RAM size, MAC addresses) to Linux
- TFTP protocol and network booting (Chapter 7, “Embedded Linux Primer”)
- Self-assessment question: Why is TFTP used instead of HTTP for bootloader transfers? How does TFTP handle packet loss without TCP?
- Why it matters: Network boot eliminates SD card swapping during development
- ARM boot protocol (Chapter 6, “Building Embedded Linux Systems” by Karim Yaghmour)
- Self-assessment question: What registers must be set before jumping to a Linux kernel on ARM? Where does the bootloader place the device tree?
- Why it matters: Improper handoff causes kernel panic with no output
- U-Boot environment variables and scripting (U-Boot README and docs)
- Self-assessment question: How does
bootcmddiffer frombootargs? What doesboot.scrcontain and why is mkimage needed? - Why it matters: Environment variables control the entire boot sequence
- Self-assessment question: How does
- Makefile-based build systems (Chapter 12, “Managing Projects with GNU Make” by Robert Mecklenburg)
- Self-assessment question: What does
make defconfigdo? How does the Kconfig system track configuration options? - Why it matters: U-Boot has thousands of configuration options for different boards
- Self-assessment question: What does
5.5 Questions to Guide Your Design
As you implement this project, these questions will guide critical design decisions:
Build and Configuration
- Which board configuration (
make <board>_defconfig) matches your target hardware or emulation environment? - What cross-compilation toolchain is required (ARM64, ARM32, MIPS, PowerPC)?
- Which U-Boot features must be enabled in menuconfig (network support, USB, specific filesystems)?
- Where will U-Boot store its environment variables (SD card, flash, RAM-only)?
Boot Flow and Automation
- What should happen on power-up without user intervention (
bootcmdscript)? - How will you implement fallback boot sources (SD card -> network -> USB)?
- What kernel command-line arguments (
bootargs) does your target OS require? - How will you handle development vs production boot scenarios?
Network Boot Setup
- What TFTP server will you run and where (host machine, VM, dedicated server)?
- How will U-Boot obtain an IP address (static assignment or DHCP)?
- What memory address should you load the kernel to (avoiding conflicts)?
- How will you verify successful download before booting?
Extensibility and Customization
- What custom command would demonstrate understanding of U-Boot’s command infrastructure?
- Where in the
cmd/directory should your new command be added? - What Makefile changes are needed to include your command in the build?
- How will your command parse arguments and interact with U-Boot’s environment?
5.6 Thinking Exercise
Before writing any code, perform this analysis:
Trace the complete U-Boot boot sequence:
- Draw a flowchart showing what happens from power-on to kernel execution
- Identify all decision points (autoboot timeout, boot source selection, error handling)
- Mark where environment variables influence the flow
- Identify where the device tree is loaded and modified
Analyze a real boot.scr script:
# Download this example and decode it:
wget http://example.com/boot.scr
dd if=boot.scr bs=1 skip=64 | less # Skip mkimage header
Walk through each command and explain:
- Why is the kernel loaded to a specific address?
- What does
fdt addrandfdt resizeaccomplish? - How does the script handle load failures?
- What order must operations occur (load kernel, load DTB, load initramfs, fdt operations, boot)?
Expected insights:
- U-Boot is a complex program with its own shell, drivers, and filesystem support
- The environment acts as a configuration database
- Device tree manipulation at boot time allows one kernel binary to run on different hardware variants
- Network boot fundamentally changes the development workflow
5.7 Hints in Layers
Hint 1: Getting Started (Conceptual Direction)
U-Boot is a mature project with extensive documentation. Start by building for a well-supported board rather than custom hardware. QEMU’s virt machine or Raspberry Pi are excellent choices because they have active communities and working defconfigs.
The basic workflow is: (1) get toolchain, (2) clone U-Boot, (3) make defconfig, (4) make, (5) deploy to target, (6) connect serial console. Don’t try to customize until you have a working baseline.
Study the relationship between board-specific code in board/ directory, device trees in arch/*/dts/, and configuration in configs/. Understanding this structure is key to customization.
Hint 2: Build and Deployment (More Specific Guidance)
For Raspberry Pi 3:
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
export CROSS_COMPILE=aarch64-linux-gnu-
make rpi_3_defconfig
make -j$(nproc)
The output u-boot.bin must be renamed to kernel8.img (for Pi 3/4) on the SD card’s boot partition. Edit config.txt to add:
enable_uart=1
kernel=kernel8.img
Connect USB-to-serial adapter to GPIO 14 (TXD) and 15 (RXD), plus ground. Use 115200 baud. You should see U-Boot banner and prompt.
For QEMU ARM64:
make qemu_arm64_defconfig
make -j$(nproc)
qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -bios u-boot.bin
Hint 3: Creating Custom Commands and Scripts (Technical Details)
To add a custom command:
- Look at
cmd/directory, copy a simple command likecmd/version.c - Create
cmd/hello.c: ```c #include#include
static int do_hello(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { printf(“Hello from custom U-Boot command!\n”); if (argc > 1) printf(“Arguments: %s\n”, argv[1]); return 0; }
U_BOOT_CMD(hello, 2, 0, do_hello, “print hello message”, “[args] - optional arguments” );
3. Add `obj-y += hello.o` to `cmd/Makefile`
4. Rebuild U-Boot
For boot scripts, create `boot.txt`:
```bash
echo "Loading kernel via TFTP..."
setenv serverip 192.168.1.100
setenv ipaddr 192.168.1.200
tftp ${kernel_addr_r} kernel.img
tftp ${fdt_addr_r} dtb.dtb
bootz ${kernel_addr_r} - ${fdt_addr_r}
Compile it: mkimage -A arm64 -T script -C none -d boot.txt boot.scr
Place boot.scr on boot partition. Set environment variable: setenv bootcmd 'load mmc 0:1 ${scriptaddr} boot.scr; source ${scriptaddr}'
Hint 4: Network Boot and Debugging (Tools and Verification)
Set up a TFTP server on your development machine:
# Ubuntu/Debian
sudo apt install tftpd-hpa
sudo mkdir -p /srv/tftp
sudo cp kernel.img /srv/tftp/
sudo systemctl restart tftpd-hpa
In U-Boot console:
=> setenv ipaddr 192.168.1.200
=> setenv serverip 192.168.1.100
=> setenv netmask 255.255.255.0
=> tftp 0x80000 kernel.img
=> md 0x80000 20 # Memory dump to verify
Common issues:
- No TFTP response: Check firewall (
sudo ufw allow tftp) - Wrong load address: Conflicts with U-Boot or DTB, check memory map
- Environment not saved:
saveenvrequires CONFIG_ENV_IS_IN_* set correctly
Use bdinfo command to see board info, printenv to see all variables, fdt print to examine device tree after loading.
To verify device tree modifications, boot Linux and check /proc/device-tree/ or use dtc to decompile the DTB.
5.8 The Interview Questions They’ll Ask
If you put “U-Boot bootloader customization” on your resume, expect these questions:
- “Walk me through the complete U-Boot boot sequence from power-on to kernel handoff.”
- What they’re testing: Understanding of the boot chain phases
- Strong answer: “SPL (if used) initializes DRAM and loads main U-Boot, U-Boot initializes hardware and environment, runs
bootcmdscript, loads kernel/DTB/initramfs, modifies device tree with board info, sets up registers (r0, r1, r2 on ARM), and jumps to kernel entry point with MMU/caches off.”
- “How does U-Boot modify the device tree before passing it to Linux, and why is this necessary?”
- What they’re testing: Device tree knowledge and board bring-up experience
- Strong answer: “U-Boot calls
fdt_fixupfunctions to add/modify nodes: detected RAM size, MAC addresses read from EEPROM, serial numbers, kernel command line, and reserved memory regions. This allows one DTB to support board variants with different RAM configurations.”
- “Explain the difference between U-Boot’s environment variables in RAM versus persistent storage.”
- What they’re testing: Understanding of embedded persistence challenges
- Strong answer: “Default environment is compiled in.
saveenvwrites to persistent storage (SD, SPI flash, EEPROM). Changes in RAM are lost on reset. Environment location is configured at compile time (CONFIG_ENV_IS_IN_MMC, etc.). Some boards use redundant environments with CRC validation.”
- “How would you debug a U-Boot boot failure with no serial output?”
- What they’re testing: Debugging methodology in constrained environments
- Strong answer: “Check SPL/first-stage works by looking for any DRAM activity. Use JTAG debugger to set breakpoint at reset vector. Verify U-Boot binary loaded to correct address. Check board strapping pins. Test with known-good binary. Add GPIO toggle before serial init to verify execution.”
- “What’s the purpose of mkimage and the legacy U-Boot image format?”
- What they’re testing: Understanding of U-Boot’s image handling
- Strong answer: “mkimage wraps binaries with a 64-byte header containing CRC, load address, entry point, OS type, and compression. U-Boot validates CRC before booting. Used for kernels, ramdisks, and scripts. Newer FIT format supports multiple images and verification.”
- “How does network booting with TFTP and DHCP work in U-Boot?”
- What they’re testing: Network protocol understanding at firmware level
- Strong answer: “U-Boot uses
dhcpcommand to get IP via DHCP, server responds with IP and next-server/bootfile parameters.tftpcommand uses next-server as TFTP server, downloads file to specified memory address, verifies transfer. Then standard boot commands execute the downloaded kernel.”
5.9 Books That Will Help
| Topic | Book & Chapter |
|---|---|
| U-Boot internals and configuration | “Embedded Linux Primer” by Christopher Hallinan, Chapter 7 (U-Boot) and Chapter 16 (Device Trees) |
| Cross-compilation and toolchains | “Mastering Embedded Linux Programming” by Chris Simmonds, Chapter 2 (Learning About Toolchains) |
| ARM boot protocol and kernel handoff | “Building Embedded Linux Systems” by Karim Yaghmour, Chapter 6 (Bootloaders) |
| Device tree syntax and bindings | “Linux Kernel Development” by Robert Love, Chapter 20 (Device Model), plus devicetree.org specification |
| TFTP protocol details | “TCP/IP Illustrated, Volume 1” by W. Richard Stevens, Chapter 15 (TFTP) |
| Makefile and Kconfig build systems | “Managing Projects with GNU Make” by Robert Mecklenburg, Chapter 6 (Build Systems) |
| U-Boot scripting and environment | U-Boot official documentation (u-boot.readthedocs.io), specifically “Shell Scripts” section |
| Network boot workflows and PXE | “Embedded Linux System Design and Development” by P. Raghavan et al., Chapter 7 (Boot Strategies) |
5.10 Implementation Phases
Phase 1: Build Baseline U-Boot (Days 1-2)
# Clone and configure
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout v2024.01 # Stable release
# Build for Raspberry Pi 3
export CROSS_COMPILE=aarch64-linux-gnu-
make rpi_3_defconfig
make -j$(nproc)
# Build for QEMU (alternative)
make qemu_arm64_defconfig
make -j$(nproc)
Phase 2: Deploy and Connect (Days 2-3)
For Raspberry Pi:
# Prepare SD card boot partition
# Format first partition as FAT32, label BOOT
# Copy firmware files from Raspberry Pi OS image
# Copy U-Boot
cp u-boot.bin /media/BOOT/kernel8.img
# Create config.txt
cat > /media/BOOT/config.txt << EOF
enable_uart=1
kernel=kernel8.img
arm_64bit=1
EOF
# Connect serial adapter and open terminal
screen /dev/ttyUSB0 115200
For QEMU:
qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-m 1G \
-nographic \
-bios u-boot.bin
Phase 3: Environment and Network Boot (Days 3-5)
# In U-Boot console
=> printenv
=> setenv ipaddr 192.168.1.200
=> setenv serverip 192.168.1.100
=> setenv netmask 255.255.255.0
# Test network
=> ping 192.168.1.100
# Place test file on TFTP server
echo "Test content" | sudo tee /srv/tftp/test.txt
# Test TFTP
=> tftp 0x80000 test.txt
=> md.b 0x80000 20 # Should show "Test content"
# Save environment
=> saveenv
Phase 4: Boot Script (Days 5-6)
Create boot.txt:
echo "=== Custom Boot Script ==="
echo "Attempting network boot..."
if tftp ${kernel_addr_r} kernel.img; then
if tftp ${fdt_addr_r} bcm2837-rpi-3-b.dtb; then
echo "Network boot: kernel and DTB loaded"
bootz ${kernel_addr_r} - ${fdt_addr_r}
fi
fi
echo "Network boot failed, trying MMC..."
if load mmc 0:1 ${kernel_addr_r} kernel.img; then
if load mmc 0:1 ${fdt_addr_r} bcm2837-rpi-3-b.dtb; then
echo "MMC boot: kernel and DTB loaded"
bootz ${kernel_addr_r} - ${fdt_addr_r}
fi
fi
echo "All boot attempts failed!"
Compile and deploy:
mkimage -A arm64 -T script -C none -d boot.txt boot.scr
cp boot.scr /media/BOOT/
Phase 5: Custom Command (Days 6-7)
Create cmd/hello.c:
// SPDX-License-Identifier: GPL-2.0+
/*
* Simple hello command for demonstrating U-Boot command interface
*/
#include <common.h>
#include <command.h>
#include <env.h>
static int do_hello(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
const char *name;
if (argc > 1)
name = argv[1];
else
name = env_get("username");
if (!name)
name = "World";
printf("Hello, %s! This is a custom U-Boot command.\n", name);
printf("Build date: %s %s\n", __DATE__, __TIME__);
return 0;
}
U_BOOT_CMD(
hello, 2, 0, do_hello,
"greet the user",
"[name]\n"
" - Print a greeting message\n"
" - If name is not given, uses 'username' env variable\n"
" - Falls back to 'World' if neither is set"
);
Add to cmd/Makefile:
obj-y += hello.o
Rebuild and test:
make -j$(nproc)
# Deploy and test
=> hello
=> hello Developer
=> setenv username John
=> hello
5.11 Key Implementation Decisions
| Decision | Options | Recommendation |
|---|---|---|
| Target platform | Pi 3, Pi 4, QEMU | Start with QEMU for safety, then Pi |
| U-Boot version | Latest main, release tag | Use release tag for stability |
| Environment storage | MMC, SPI, RAM only | MMC for Pi, easier debugging |
| Network protocol | Static IP, DHCP | Static IP for predictability |
| Custom command scope | Minimal demo, Useful tool | Minimal demo first |
6. Testing Strategy
6.1 Build Verification
# Test 1: Clean build succeeds
make distclean
make rpi_3_defconfig
make -j$(nproc) 2>&1 | tee build.log
test -f u-boot.bin && echo "PASS: u-boot.bin exists" || echo "FAIL"
# Test 2: Custom command compiled
grep "do_hello" System.map && echo "PASS: hello command linked" || echo "FAIL"
# Test 3: Size check
ls -la u-boot.bin # Should be ~500KB-1MB typically
6.2 Functional Testing
# Test 4: Serial console works
# Power on board, check for U-Boot banner
# Test 5: Environment operations
=> printenv bootdelay
=> setenv test_var hello
=> printenv test_var
=> saveenv
# Power cycle
=> printenv test_var # Should still exist
# Test 6: Network connectivity
=> ping ${serverip}
# Test 7: TFTP download
=> tftp 0x80000 test.txt
=> md.b 0x80000 20
# Test 8: Custom command
=> help hello
=> hello
=> hello TestUser
6.3 Boot Script Testing
# Test 9: Script loads and runs
=> load mmc 0:1 ${scriptaddr} boot.scr
=> source ${scriptaddr}
# Test 10: Fallback works
# Remove kernel from TFTP, verify MMC fallback
7. Common Pitfalls & Debugging
Problem 1: U-Boot builds but board doesn’t boot, no serial output
Root cause: Wrong defconfig for board, incorrect UART configuration in device tree, or baud rate mismatch.
Fix: Verify you selected exact board variant (rpi_3_defconfig vs rpi_3_b_plus_defconfig). Check device tree enables correct UART node. Most boards use 115200 8N1. Some boards require specific boot mode pins set. Try known-good U-Boot binary first.
Quick test: If SPL is used, you should see SPL output before main U-Boot. No SPL output means DRAM initialization failed or wrong load address.
Problem 2: TFTP download fails with timeout or “ARP retry count exceeded”
Root cause: Network configuration mismatch (wrong subnet, firewall blocking, TFTP server not running), or U-Boot Ethernet driver not initialized.
Fix: Verify U-Boot and host are on same subnet. Check ping 192.168.1.100 from U-Boot works first. Verify TFTP server running: sudo systemctl status tftpd-hpa. Check firewall allows UDP port 69. Ensure U-Boot has CONFIG_CMD_NET=y and correct Ethernet driver enabled.
Quick test: Use Wireshark to capture traffic, look for DHCP/ARP packets from U-Boot. If no packets, Ethernet driver initialization failed.
Problem 3: Kernel panics immediately after “Starting kernel…” with no additional output
Root cause: Wrong kernel load address (overwriting U-Boot or DTB), incorrect DTB passed, or boot arguments missing console parameter.
Fix: Check bdinfo in U-Boot for memory layout. Load kernel to ${kernel_addr_r} (predefined safe address). Verify DTB passed correctly: bootz ${kernel_addr_r} - ${fdt_addr_r} (note the - for no initramfs). Add console=ttyS0,115200 to bootargs. Verify DTB matches kernel version.
Quick test: Use bootm for legacy images or bootz for zImage. For debugging, enable early printk in kernel config.
Problem 4: Custom U-Boot command not appearing in help or “Unknown command”
Root cause: Command not compiled in, Makefile not updated, or CONFIG option not enabled.
Fix: Verify cmd/Makefile has obj-y += yourcommand.o (unconditional) or obj-$(CONFIG_CMD_YOURCOMMAND) += yourcommand.o with corresponding Kconfig entry. Check U_BOOT_CMD macro syntax is exactly right (especially name string). Rebuild with make clean && make. Verify command appears in u-boot.map.
Quick test: Add a simple printf("Command registered\n"); at file scope (outside function) to verify file is compiled.
Problem 5: Environment changes not persisting after reboot
Root cause: Environment storage not configured, wrong storage location, or saveenv fails silently.
Fix: Check U-Boot config has CONFIG_ENV_IS_IN_MMC=y (or FAT, SPI flash, etc.). Verify storage location accessible: mmc dev 0 for SD card. Create environment partition if needed. Check saveenv output for errors. Some boards use redundant environments requiring two copies.
Quick test: printenv shows default environment, not saved changes. Compare environment size with CONFIG_ENV_SIZE. Use env default -a to reset, then saveenv.
8. Extensions & Challenges
After completing the basic project, try these extensions:
8.1 Intermediate Extensions
- Implement boot menu: Create a graphical text menu for selecting boot options
- Add hardware diagnostics: Create commands for testing GPIO, I2C, SPI
- Implement factory reset: Add command to restore default environment
- USB boot support: Configure U-Boot to boot from USB storage
8.2 Advanced Extensions
- Secure Boot integration: Sign U-Boot and kernels, verify signatures
- UEFI payload: Configure U-Boot as UEFI boot manager
- Falcon mode: Skip U-Boot entirely for faster boot (SPL direct to Linux)
- Custom board port: Port U-Boot to a new/unsupported board
8.3 Professional Extensions
- Production boot flow: Implement A/B boot partitions with fallback
- Network provisioning: Auto-configure devices via DHCP options
- Encrypted storage: Boot from encrypted filesystem
- Remote update: Implement OTA update mechanism
9. Real-World Connections
9.1 Industry Applications
| Domain | How U-Boot is Used |
|---|---|
| Consumer Routers | OpenWrt devices boot via U-Boot, often with TFTP recovery |
| Automotive | Infotainment systems, instrument clusters use U-Boot for reliability |
| Industrial IoT | Factory controllers, gateways boot Linux via U-Boot |
| Network Equipment | Switches, routers from major vendors use U-Boot |
| Development Boards | BeagleBone, Jetson, i.MX all ship with U-Boot |
9.2 Career Relevance
- Embedded Linux Developer: U-Boot knowledge is a baseline requirement
- Board Bring-Up Engineer: Primary daily tool for new hardware
- DevOps/SRE (Embedded): Network boot for fleet provisioning
- Security Researcher: Understanding boot chain for vulnerability assessment
9.3 Open Source Contribution
U-Boot is actively maintained and welcomes contributions:
- Bug fixes
- Board ports
- Driver improvements
- Documentation
- Testing
Contributing to U-Boot is excellent resume material.
10. Resources
10.1 Official Documentation
10.2 Tutorials and Guides
10.3 Community
11. Self-Assessment Checklist
Before considering this project complete, verify you can:
Knowledge Check
- Explain the SPL -> U-Boot -> Kernel boot chain
- Describe what device trees contain and why they’re needed
- Explain the difference between
bootcmdandbootargs - Describe how TFTP boot works at a protocol level
- Explain environment variable persistence mechanisms
Skills Check
- Build U-Boot from source for at least one target
- Navigate and modify the U-Boot codebase
- Configure and use TFTP for development workflow
- Create and use U-Boot boot scripts
- Add a new command to U-Boot
Practical Check
- Successfully boot a Linux kernel via TFTP
- Demonstrate custom command execution
- Show environment variable persistence across reboots
- Explain your boot script’s fallback logic
12. Submission / Completion Criteria
Your implementation is complete when you can demonstrate:
- Building: Clean compilation of U-Boot with your custom command
- Console: Access to U-Boot interactive console via serial
- Network: Successful TFTP transfer from your development machine
- Scripting: Automated boot via boot.scr with fallback logic
- Extension: Custom command that accepts arguments and works correctly
- Documentation: README explaining your setup and configuration
Deliverables:
- Modified U-Boot source (custom command)
- Boot script (boot.txt and boot.scr)
- Documentation of your development environment setup
- Screenshot or terminal log showing successful TFTP boot
- Screenshot showing custom command execution
Next Project: P11 - Virtual Machine Boot Process Inspector
Previous Project: P09 - Raspberry Pi Bare-Metal