← 返回博客列表

一、什么是 Bootloader

Bootloader(引导加载程序)是嵌入式系统上电后运行的第一段代码,负责初始化最小硬件环境、加载操作系统内核并将控制权交给内核。它在硬件与 OS 之间扮演"桥梁"角色。

类比 PC 上的 BIOS/UEFI:PC 启动时 BIOS 完成硬件自检(POST)、初始化外设、读取磁盘引导扇区,然后跳转到 MBR/VBR 加载 GRUB/Windows Boot Manager。嵌入式系统没有统一的 BIOS,Bootloader 集成了 BIOS + GRUB 两层职责。

PC vs 嵌入式启动流程对比 PC 启动流程 上电 BIOS/UEFI GRUB Windows Linux 应用 BIOS 硬件初始化 + GRUB 加载内核 两层分工 嵌入式启动流程 上电 Boot ROM SPL U-Boot Linux 应用 Boot ROM + SPL + U-Boot 三段接力 BIOS+GRUB 二合一
图 1-1:PC vs 嵌入式系统启动流程对比

1.1 为什么需要 Bootloader

嵌入式系统上电后 CPU 处于初始状态:Cache 未初始化、内存控制器未配置、外设未启动,无法直接运行 OS。Bootloader 解决三大核心问题:

  1. 硬件最小初始化:配置 CPU 时钟、内存控制器(DDR)、UART、Flash 控制器,让系统能运行复杂代码
  2. 内核加载:从 Flash/SD/eMMC/网络读取 OS 内核镜像到 RAM,准备启动参数
  3. 权力移交:关闭中断、清空 Cache、跳转到内核入口,让 OS 接管系统
关键概念:Boot ROM
几乎所有 SoC(如 i.MX、STM32MP1、RK3399)都在芯片内部固化了Boot ROM(不可修改的启动代码)。上电后 CPU 首先执行 Boot ROM,它根据 boot cfg 引脚状态从 SD/NAND/NOR/QSPI/USB 加载下一段代码(通常是 SPL)。这是芯片厂商写死的,用户不可改。

二、嵌入式系统启动全流程

以 i.MX6 / RK3399 / STM32MP1 为典型,嵌入式系统从上电到 Linux 用户登录共经历五个阶段:

嵌入式 Linux 系统启动五阶段 ① Boot ROM 芯片内置 读取 boot cfg 加载下一阶段 ② SPL Second Program Loader 初始化 DDR ③ U-Boot Main 完整功能 命令行交互 加载内核 ④ Linux Kernel 设备树解析 驱动加载 挂载 rootfs ⑤ 用户态 init/systemd 服务启动 登录 阶段说明 ① Boot ROM:芯片厂商固化代码,根据 boot cfg 引脚从外部介质加载 SPL 到 SRAM ② SPL:U-Boot 第一阶段,体积小(<32KB),仅初始化 DDR,跳转到 U-Boot Main ③ U-Boot Main:完整 U-Boot,提供命令行,加载内核、设备树、initrd 到 RAM ④ Linux Kernel:内核启动,解析设备树,加载驱动,挂根文件系统 ⑤ 用户态:init/systemd 启动服务,最终用户登录 关键:①② 在 SRAM 跑(小空间),③④ 在 DDR 跑(大空间)
图 2-1:嵌入式 Linux 系统启动五阶段(Boot ROM → SPL → U-Boot → Kernel → 用户态)

2.1 Boot ROM 的工作

Boot ROM 是芯片出厂时烧录在芯片内部 ROM 中的代码,无法修改。它在 CPU 复位向量地址执行,主要工作:

2.2 为什么需要 SPL

现代 SoC 上电后 DDR 还未初始化,只有芯片内部 SRAM 可用(通常 32~128KB)。完整 U-Boot 有几百 KB,无法塞进 SRAM。所以SPL(Second Program Loader)作为"过渡阶段"出现:

常见误区
有人认为 Bootloader = U-Boot,实际上 U-Boot 只是 Bootloader 的一种实现。完整启动链是 Boot ROM → SPL → U-Boot,其中 SPL 是 U-Boot 项目的一部分(在 build 目录里叫 u-boot-spl.bin)。

三、Bootloader 的两大职责

无论使用哪种 Bootloader(U-Boot、Barebox、CoreBoot),它们都有两个核心职责:硬件初始化和内核加载。

Bootloader 的两大职责 职责一:硬件初始化 CPU 相关: • 关闭中断、设置 CPU 频率 • 关 MMU/Cache • 配置 PLL 时钟树 内存相关: • 初始化 DDR 控制器 • 配置时序参数 • 测试 RAM 容量 外设:UART、Flash、MMC、网络 职责二:加载内核 镜像定位: • 从 Flash/SD/eMMC 读取 • 支持 zImage/uImage/Image • 网络加载(TFTP/NFS) 参数准备: • 加载设备树 DTB • 准备命令行参数 • initrd/initramfs 移交:bootm 跳转内核入口
图 3-1:Bootloader 的两大职责(硬件初始化 + 内核加载)

3.1 硬件初始化的具体内容

类别 初始化内容 典型代码位置
CPU 初始化 关闭中断、关闭 MMU、清 Cache、设置 SVC 模式 start.S / cpu_init.c
时钟初始化 配置 PLL、CPU/AXI/AHB/APB 分频 clk/clock.c
DDR 初始化 配置 DDR 控制器、PHY 训练、时序参数 ddr/ddr_init.c
管脚复用 IOMUX 配置引脚功能(UART/SDC/GPIO) pinctrl.c
外设初始化 UART 调试口、Timer、Flash/SD 控制器 drivers/

3.2 内核加载的三种方式

加载方式 介质 命令 典型场景
本地加载 SD/eMMC/NAND/NOR fatload / ext4load / nand read 量产产品
网络加载 TFTP / NFS tftpboot / nfs 开发调试
RAM 加载 USB/UART 下载到 RAM loadb / loads / usbboot 烧写空板

四、主流 Bootloader 对比

嵌入式领域有多种 Bootloader,U-Boot 是最广泛使用的,但并非唯一选择。

主流 Bootloader 对比 U-Boot DENX 维护 起源:PPC 时代 支持:ARM/MIPS/PPC/x86 命令:最丰富 代码:~10MB 优势: • 生态最成熟 • 厂商支持广泛 • 文档完善 市场: 80%+ ARM 设备 事实标准 Barebox 社区维护 起源:U-Boot 分支 支持:ARM/MIPS/x86 命令:Linux 风格 代码:~5MB 优势: • 代码更干净 • 命令行友好 • 设备树原生 市场: 小众 工业设备 CoreBoot 社区维护 起源:LinuxBIOS 支持:x86/ARM/RISC-V 命令:弱 代码:~8MB 优势: • BIOS 替代 • 开源透明 • 启动快 市场: 服务器 Chromebook 其他 厂商自研 RedBoot:eCos 系 AFE Boot:海思 Little Kernel:LK Android Boot:ABL 优势: • 厂商定制 • 安卓生态 市场: 安卓手机 海思/高通
图 4-1:主流 Bootloader 对比(U-Boot / Barebox / CoreBoot / 厂商自研)
Bootloader 支持架构 典型场景 开放性
U-Boot ARM/MIPS/PPC/x86/RISC-V 嵌入式 Linux 通用 GPLv2 完全开放
Barebox ARM/MIPS/x86 工业嵌入式 GPLv2 完全开放
CoreBoot x86/ARM/RISC-V 服务器/Chromebook GPLv2 完全开放
Little Kernel (LK) ARM/ARM64 Android Boot MIT 开放
RedBoot ARM/MIPS/x86 eCos 系统遗留 GPLv2 / eCos license
选型建议
• 通用嵌入式 Linux 产品 → U-Boot(生态最成熟,厂商支持最好)
• 工业级设备、追求代码质量 → Barebox(更干净、命令行更友好)
• x86 服务器/Chromebook → CoreBoot(替代 BIOS)
• Android 设备 → Little Kernel + ABL(高通/MTK 默认)

五、U-Boot 简介:历史与地位

U-Boot(Das U-Boot,全称"the U-Boot")由德国 DENX 软件工程公司 Wolfgang Denk 主导开发,2000 年开源发布,基于 PowerPC 时代的 8xxrom 演化而来,逐渐成为 ARM/MIPS 嵌入式 Linux 的事实标准 Bootloader。

U-Boot 发展史 2000 · 8xxrom PPC 8xx 系列 CPU 内置 ROM 起源: PPC 嵌入式时代 贡献: Wolfgang Denk DENX 公司 地位: 第一版 Bootloader 2002 · U-Boot 0.1 支持 ARM 开源 GPLv2 里程碑: 脱离 PPC 限制 特性: 命令行交互 环境变量 市场: PXA/IXP 流行 2010 · Driver Model DM 驱动模型 设备树支持 里程碑: 类 Linux 框架 特性: SPL/TPL 多阶段 Kconfig 配置 市场: ARM Cortex-A 主流 2020+ · 主线 + LTS v2024.04 LTS RISC-V 支持 趋势: EFI_LOADER Android Boot Secure Boot 特点: 主线 + 厂商 BSP 地位: 事实标准
图 5-1:U-Boot 发展史(8xxrom → 开源 → DM → EFI/Secure Boot)

5.1 U-Boot 的核心特点

主线 vs BSP
U-Boot 生态分两层:主线(mainline)由社区维护,质量高但厂商支持滞后;厂商 BSP(Board Support Package)由芯片厂商基于主线 fork,加入自家驱动但常滞后。开发流程:BSP 启动 → 推动改动回流主线 → 长期靠主线维护。

六、U-Boot 架构与目录结构

U-Boot 源码采用类似 Linux 内核的目录组织,让有 Linux 经验的开发者快速上手。

U-Boot 源码目录结构 u-boot/ arch/ CPU 架构相关代码 arm/mips/ppc/x86 board/ 开发板特定代码 厂商/型号目录 drivers/ 设备驱动 mmc/net/serial/... include/ 头文件、配置文件 configs/xxx_defconfig common/ 通用功能 命令、初始化 cmd/ U-Boot 命令实现 bootm.c、nand.c... lib/ 通用库 加密、压缩、字符串 net/ + fs/ + dts/ 网络/文件系统/设备树 tftp、ext4、fdt • Makefile + Kconfig:构建与配置系统(类 Linux) • scripts/:编译脚本、镜像打包工具(mkimage) • tools/:辅助工具(envtools、dumpimage、mkenvimage) • configs/:每个开发板一个 defconfig(如 rpi_4_defconfig)
图 6-1:U-Boot 源码目录结构

6.1 三层代码分层

层级 目录 作用
架构层 arch/arm/cpu、arch/arm/lib CPU 启动汇编、MMU、Cache、中断
开发板层 board/<vendor>/<board>/ 板级初始化、DDR 配置、外设 pinmux
通用层 common/、drivers/、lib/ 通用命令、设备驱动、库函数

6.2 Kconfig 配置系统

U-Boot 借鉴 Linux 的 Kconfig + Kbuild 系统,每个开发板对应一个 configs/<board>_defconfig 文件:

# configs/rpi_4_defconfig(树莓派 4B 默认配置)
CONFIG_ARM=y
CONFIG_TARGET_RPI_4=y
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv8"
CONFIG_USE_ARCH=64
CONFIG_ARCH_BCM283X=y
CONFIG_NR_DRAM_BANKS=1
CONFIG_ENV_SIZE=0x4000
CONFIG_ENV_OFFSET=0x3c0000

# 启用 SPL(可选)
CONFIG_SPL=y
CONFIG_SPL_STACK_R=y

# 启用 Driver Model
CONFIG_DM=y
CONFIG_DM_GPIO=y
CONFIG_DM_MMC=y
CONFIG_DM_SERIAL=y

# 启用网络
CONFIG_CMD_NET=y
CONFIG_CMD_DHCP=y
CONFIG_CMD_TFTPPUT=y

# 启用文件系统
CONFIG_CMD_FAT=y
CONFIG_CMD_EXT4=y
CONFIG_CMD_FS_GENERIC=y

# 启用 bootm / bootz
CONFIG_CMD_BOOTM=y
CONFIG_CMD_BOOTZ=y
CONFIG_FIT=y                 # FIT 镜像支持

七、SPL 与多阶段启动

SPL(Second Program Loader)是 U-Boot 的"精简版",专门为 SRAM 启动场景设计。本节详解 SPL/TPL 的多阶段启动机制。

U-Boot 多阶段启动流程 Boot ROM 芯片内置 无 RAM 假设 从介质读 SPL 放入 SRAM SPL 在 SRAM 运行 < 32KB 初始化 DDR 加载 U-Boot Main U-Boot Main 在 DDR 运行 完整功能 命令行 加载内核 Linux Kernel 在 DDR 运行 解析设备树 挂 rootfs 启动 init SPL 与 U-Boot Main 的对比 • SPL:只编译最少驱动(UART、DDR、MMC 之一),无命令行,无网络,体积小(<32KB) • U-Boot Main:完整驱动、命令行、网络、文件系统,体积大(200KB~1MB) • TPL(Third Program Loader):DDR 训练需要更早阶段时引入,比 SPL 更早 关键:SPL 和 Main 共享源码,通过 CONFIG_SPL 宏区分编译 → 一次 make 生成 u-boot-spl.bin + u-boot.bin 两个文件
图 7-1:U-Boot 多阶段启动流程(Boot ROM → SPL → U-Boot → Linux)

7.1 何时需要 SPL

场景 需要 SPL? 原因
SRAM < 32KB,DDR 未初始化 必须 U-Boot 太大,放不进 SRAM
Boot ROM 直接初始化 DDR 不需要 Boot ROM 已准备好 RAM,直接加载 U-Boot
DDR 需软件训练(DDR4/LPDDR4) 需要 TPL 训练代码需运行在 SRAM,比 SPL 更早
x86 平台 不需要 BIOS/UEFI 已初始化内存
SPL 编译产物
make 后在 build 目录生成:
• u-boot-spl.bin:SPL 镜像(精简版,送进 SRAM)
• u-boot.bin:U-Boot Main 镜像(完整版,运行在 DDR)
• u-boot.img:加了 mkimage 头部的 U-Boot Main(Boot ROM 通过头部识别)
• u-boot.itb:FIT 格式镜像(包含 SPL + Main + 多个 DTB)

八、U-Boot 启动流程详解

U-Boot 启动过程从汇编入口到 main 循环,经历多个阶段。下图以 ARM 平台为例展示完整流程:

U-Boot 启动流程(ARM 平台) _start (arch/arm/cpu) reset 汇编入口 cpu_init_cp15 关 MMU/Cache、设置异常向量 board_init_f 早期初始化(ROM 中) relocate_code 自搬运到 RAM 高地址 board_init_r 后期初始化(RAM 中) initcall_loop 调用 init 序列(类 Linux) env_relocate 加载环境变量 main_loop 命令行 / 自动启动 run_bootcmd 执行 bootcmd 环境变量 bootm 加载内核镜像 boot_jump_linux 跳转内核入口 关键概念:relocate_code U-Boot 启动时先在 ROM/低地址 RAM 运行,初始化 DDR 后将自己"搬运"到 RAM 高地址 → 让出低地址给 Linux 内核(内核通常加载到 0x80008000 等低地址) → 搬运后重定位 GOT 表、修改链接地址,保证代码在新区址继续运行
图 8-1:U-Boot 启动流程详解(ARM 平台)

8.1 启动关键函数

函数 位置 职责
_start arch/arm/cpu/armv8/start.S 汇编入口,设置异常向量、SVC 模式
cpu_init_cp15 arch/arm/cpu/armv8/cpu.c 关闭 MMU、清 Cache、关闭中断
board_init_f common/board_f.c 早期初始化(在 SRAM 中运行):GPIO、UART、DDR 准备
relocate_code arch/arm/lib/relocate.c 自搬运 U-Boot 到 RAM 高地址
board_init_r common/board_r.c 后期初始化(在 RAM 中):DM、env、网络、MMC
main_loop common/main.c 命令行循环,等待用户输入或自动 boot
启动延迟与自动 boot
U-Boot 默认启动后会等待 2~3 秒(由 bootdelay 环境变量控制),期间按任意键进入命令行;否则自动执行 bootcmd 环境变量指定的启动命令。这是为了兼顾"量产自动启动"和"开发期调试"两种场景。

九、U-Boot 命令体系与环境变量

U-Boot 命令行是开发期最重要的交互界面,提供类似 Linux shell 的命令体系。每条命令由 U_BOOT_CMD 宏注册。

U-Boot 自动启动流程与核心环境变量 U-Boot main_loop bootdelay 倒计时 2 秒内按任意键打断 run bootcmd 执行 bootcmd 环境变量 bootcmd 命令组合 fatload mmc 0:1 0x80080000 Image booti 0x80080000 - 0x88000000 bootm/bootz/booti 加载内核镜像 跳转内核入口 Linux Kernel 启动 核心环境变量 • bootdelay:倒计时秒数 • bootcmd:自动启动命令 内核参数 • bootargs:传给内核的命令行 • console=tty0,115200 root=/dev/mmcblk0p2 网络配置 • ipaddr / serverip • ethaddr(MAC)
图 9-1:U-Boot 自动启动流程与核心环境变量体系

9.1 环境变量

U-Boot 通过环境变量持久化启动配置,类似 Linux 的 /etc/fstab。常见变量如下:

变量名 含义 典型值
bootdelay 启动延迟(秒) 2(按任意键进入命令行)
bootcmd 自动启动命令 run boot_sd
bootargs 传给内核的参数 console=ttyS0,115200 root=/dev/mmcblk0p2
bootfile 网络加载文件名 zImage
serverip TFTP 服务器 IP 192.168.1.100
ipaddr 本机 IP 192.168.1.10
ethaddr MAC 地址 00:1c:42:00:00:01

9.2 常用命令一览

命令 用途 示例
printenv 打印环境变量 printenv bootcmd
setenv 设置环境变量 setenv bootdelay 5
saveenv 保存到 Flash saveenv
fatload 从 FAT 分区加载 fatload mmc 0:1 0x80008000 zImage
ext4load 从 ext4 分区加载 ext4load mmc 0:2 0x88000000 /boot/dtb
tftpboot TFTP 网络加载 tftpboot 0x80008000 zImage
bootm 启动 uImage bootm 0x80008000 - 0x88000000
bootz 启动 zImage bootz 0x80008000 - 0x88000000
booti 启动 ARM64 Image booti 0x80080000 - 0x88000000
md 显示内存 md.b 0x80008000 0x40
mw 写内存 mw.l 0x80008000 0xdeadbeef 0x10
go 跳转执行 go 0x80008000
reset 软复位 reset
bcb Android Boot Control bcb boot_slot a

9.3 启动命令组合

# 典型 SD 卡启动命令组合
# 1. 加载内核到 0x80008000
U-Boot> fatload mmc 0:1 0x80008000 zImage
# 2. 加载设备树到 0x88000000
U-Boot> fatload mmc 0:1 0x88000000 bcm2837-rpi-3-b.dtb
# 3. 设置内核命令行
U-Boot> setenv bootargs console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootwait
# 4. 启动
U-Boot> bootz 0x80008000 - 0x88000000

# 一键启动组合(保存到环境变量)
U-Boot> setenv boot_sd "fatload mmc 0:1 0x80008000 zImage; \
                          fatload mmc 0:1 0x88000000 rpi-3-b.dtb; \
                          bootz 0x80008000 - 0x88000000"
U-Boot> setenv bootcmd "run boot_sd"
U-Boot> saveenv

# 网络启动(开发期常用)
U-Boot> setenv serverip 192.168.1.100
U-Boot> setenv ipaddr 192.168.1.10
U-Boot> tftpboot 0x80008000 zImage
U-Boot> tftpboot 0x88000000 rpi-3-b.dtb
U-Boot> bootz 0x80008000 - 0x88000000
开发期调试技巧
• printenv 查看当前配置,saveenv 持久化
• run <cmd> 执行环境变量中的命令组合
• editenv <name> 在线编辑长命令
• env default -a 恢复默认配置(救命稻草)

十、设备树:DTB 与 FDT

设备树(Device Tree,DT)是从 PowerPC 时代引入的硬件描述语言,Linux 内核通过它知道板卡有哪些外设、寄存器地址、中断号。U-Boot 在启动时将 DTB(DT Blob 二进制)传给内核。

设备树工作流程 DTS 源文件 .dts 文本格式 人类可读 包含节点 + 属性 board.dts + .dtsi dtc 编译 DTB 二进制 .dtb 文件 紧凑二进制 节点 + 属性表 内核/U-Boot 解析 U-Boot 加载 读 DTB 到 RAM 可选 fdt 修改 设置 bootargs 传给内核 Linux 内核解析 of_flat_dt 匹配驱动 创建 platform device 设备树层级结构示例 / { # 根节点 cpus { ... }; # CPU 子节点 memory@80000000 { device_type = "memory"; reg = <0x80000000 0x20000000>; }; uart@0x02020000 { compatible = "arm,pl011"; reg = <...>; interrupts = <...>; };
图 10-1:设备树工作流程(DTS → DTB → U-Boot → Linux)

10.1 设备树源码示例

/dts-v1/;

/ {
    # 根节点
    model = "Raspberry Pi 4 Model B";
    compatible = "raspberrypi,4-model-b", "brcm,bcm2711";

    # CPU 节点
    cpus {
        cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a72";
            reg = <0>;
        };
    };

    # 内存节点
    memory@0 {
        device_type = "memory";
        reg = <0 0x80000000>;  # 起始地址 + 大小
    };

    # UART 节点
    uart@7e215040 {
        compatible = "arm,pl011", "arm,primecell";
        reg = <0x7e215040 0x40>;
        interrupts = <0 121 4>;
        clock-names = "uartclk", "apb_pclk";
        status = "okay";
    };

    # SD 控制器节点
    sdhci@7e340000 {
        compatible = "brcm,bcm2711-emmc2";
        reg = <0x7e340000 0x100>;
        interrupts = <0 126 4>;
        bus-width = <8>;
        status = "okay";
    };
};

10.2 U-Boot 中的 fdt 命令

U-Boot 提供 fdt 命令在启动前修改设备树(如动态修改 bootargs、调整内存大小):

# U-Boot 命令行修改设备树
U-Boot> fdt addr 0x88000000            # 设置 DTB 地址
U-Boot> fdt print /memory              # 打印 memory 节点
U-Boot> fdt set /memory reg <0 0x40000000>  # 改内存大小为 1GB
U-Boot> fdt set /chosen bootargs "console=ttyAMA0,115200"
U-Boot> fdt print /chosen
DTB Overlay(FDT Overlay)
对于"模块化板卡"(如 BeagleBone Cape、Raspberry Pi HAT),U-Boot 支持在运行时叠加 DTBO(Overlay 文件),动态扩展基础 DTB。这在工业模块化设计中非常有用。

十一、Driver Model 驱动框架

U-Boot 在 2014 年引入 Driver Model(DM),借鉴 Linux 的 device/driver/bus 三层架构,让驱动代码模块化、可复用。在此之前,U-Boot 驱动是"散装"的,每个驱动都自己写初始化逻辑。

U-Boot Driver Model 三层架构 uclass 类别抽象 代表: • UCLASS_MMC • UCLASS_ETH • UCLASS_SERIAL • UCLASS_I2C 类比: Linux class 提供统一接口 udevice 设备实例 代表: • mmc@7e340000 • eth@7e900000 • serial@7e201000 • i2c@7e208000 类比: Linux device 设备树节点实例化 driver 驱动实现 代表: • bcm2835_sdhci • smsc95xx • pl011_serial • bcm2835_i2c 类比: Linux driver 具体硬件操作
图 11-1:U-Boot Driver Model 三层架构(uclass / udevice / driver)

11.1 DM 驱动示例

// drivers/serial/serial_pl011.c —— ARM PL011 UART 驱动
#include <dm/device.h>
#include <serial.h>
#include "pl011.h"

// 1. 实现 uclass 接口(serial_ops)
static int pl011_setbrg(struct udevice *dev, int baudrate) {
    struct pl011_uart *plat = dev_get_plat(dev);
    unsigned int divisor = UART_PL011_CLOCK / (16 * baudrate);
    writel(divisor & 0xFFFF, plat->base + UART_PL011_LDIV);
    return 0;
}

static int pl011_getc(struct udevice *dev) {
    struct pl011_uart *plat = dev_get_plat(dev);
    return readl(plat->base + UART_PL011_DR);
}

static const struct dm_serial_ops pl011_serial_ops = {
    .putc = pl011_putc,
    .getc = pl011_getc,
    .setbrg = pl011_setbrg,
};

// 2. probe 函数(设备绑定后调用)
static int pl011_probe(struct udevice *dev) {
    struct pl011_uart *plat = dev_get_plat(dev);
    plat->base = dev_read_addr(dev);
    writel(0x70, plat->base + UART_PL011_CR);  // 使能 UART
    return 0;
}

// 3. 设备匹配(compatible 字符串)
static const udevice_id pl011_ids[] = {
    { .compatible = "arm,pl011" },
    { .compatible = "arm,primecell" },
    { }
};

// 4. 注册到 DM 框架
U_BOOT_DRIVER(pl011_serial) = {
    .name = "pl011_serial",
    .id = UCLASS_SERIAL,
    .of_match = pl011_ids,
    .probe = pl011_probe,
    .ops = &pl011_serial_ops,
    .plat_auto = sizeof(struct pl011_uart),
};
DM 的价值
• 代码复用:同一驱动可服务多个 udevice(如多个 UART)
• 解耦:上层 uclass 接口稳定,下层 driver 可替换
• 设备树绑定:通过 compatible 字符串自动匹配驱动
• 测试友好:uclass 提供测试桩,便于单元测试

十二、烧写方式:SD/NAND/NOR/网络

U-Boot 镜像如何烧写到目标板?常见方式有 4 类:

U-Boot 烧写方式对比 SD/eMMC 烧写 dd / SD 卡读卡器 命令: dd if=u-boot.bin of=/dev/sdX seek=8 场景: 开发期 / 树莓派 优点: 简单、可热插拔 缺点: 需要取下 SD NAND 烧写 U-Boot 自身 nand 命令 命令: nand erase.part nand write 0x80008000 场景: 量产产品 优点: 板内升级、可靠 缺点: 需先有 U-Boot NOR/QSPI 烧写 sf 命令 命令: sf probe sf erase 0 +$SIZE sf write 0x80008000 0 $SIZE 场景: 工业设备、小容量 优点: XIP 可在片执行 网络烧写 TFTP 加载到 RAM 命令: tftpboot 0x80008000 u-boot.bin nand write 0x80008000 场景: 开发期 / OTA 升级 优点: 远程升级、便捷
图 12-1:U-Boot 烧写方式对比(SD / NAND / NOR / 网络)
烧写位置很重要!
每种 SoC 都有固定的 Boot ROM 加载偏移(offset),错了就启动不起来:
• i.MX6/7 SD 卡:offset 0x400(1KB) 开始放 SPL
• 树莓派 SD 卡:FAT 分区根目录放 bootcode.bin/start.elf/kernel8.img
• Rockchip SD 卡:offset 0x40(64 个扇区)放 idbloader(SPL)
• i.MX NAND:按 mtd 分区表,u-boot 在 mtd1

12.1 SD 卡烧写完整命令

# 在 Linux 主机烧写 U-Boot 到 SD 卡
sudo dd if=u-boot-spl.bin of=/dev/sdX seek=2 bs=512  # SPL 从第 2 扇区开始
sudo dd if=u-boot.bin of=/dev/sdX seek=64 bs=512  # U-Boot Main 从第 64 扇区开始
sync

# 树莓派用单独的脚本
sudo dd if=u-boot.bin of=/dev/sdX seek=8  # 树莓派从扇区 8 开始
sync

# 使用 u-boot 自身的 ums 命令把 SD 卡当 U 盘
U-Boot> ums 0 mmc 0  # 把 SD 卡 (mmc 0) 通过 USB 暴露给主机

十三、引导内核:bootm / bootz / booti

U-Boot 引导 Linux 内核有三种命令,对应不同内核镜像格式:

U-Boot 引导内核:三种命令对比与 FIT 镜像流程 bootm(uImage) • 传统 ARM 32 位 • 需要 mkimage 打包头部 • uImage = header + kernel 参数: bootm <kernel> <ramdisk> <fdt> bootz(zImage) • ARM 32 位压缩内核 • 不需要 mkimage 头 • 自动解压到 0x80008000 参数: bootz <kernel> <ramdisk> <fdt> booti(Image) • ARM64 / RISC-V • 原始未压缩格式 • 直接跳转到 entry 参数: booti <kernel> <ramdisk> <fdt> FIT 镜像(Flattened Image Tree)——现代首选 image.itb FIT 镜像文件 kernel zImage / Image fdt board.dtb ramdisk initramfs configurations 多配置选择 FIT 优势: ① 一个文件包含多内核 + 多 DTB + 多 initramfs ② 支持签名验证(Secure Boot) ③ 自动选择匹配的 configuration bootm
图 13-1:U-Boot 引导内核三种命令对比与 FIT 镜像结构
命令 镜像格式 典型用途
bootm uImage(mkimage 打包) 传统 ARM 32 位
bootz zImage(gzip 压缩) ARM 32 位压缩内核
booti Image(未压缩原始格式) ARM64 / RISC-V

13.1 bootm 命令详解

# bootm 命令格式
bootm <kernel_addr> <ramdisk_addr> <fdt_addr>

# 实际使用示例
U-Boot> fatload mmc 0:1 0x80008000 uImage
U-Boot> fatload mmc 0:1 0x83000000 initramfs.cpio.gz
U-Boot> fatload mmc 0:1 0x88000000 board.dtb
U-Boot> bootm 0x80008000 0x83000000 0x88000000

# 无 initramfs 时用 - 占位
U-Boot> bootm 0x80008000 - 0x88000000

13.2 FIT 镜像(Flattened Image Tree)

对于多内核 + 多 DTB + 多 initramfs 的场景,U-Boot 引入了 FIT 镜像——一个 itb 文件包含多个组件,类似设备树的层级结构。

// image.its —— FIT 镜像描述文件(类似 DTS)
/dts-v1/;

/ {
    description = "Multi image for board X";
    #images = "kernel", "fdt";

    kernel {
        description = "Linux kernel";
        data = /incbin/("zImage");
        type = "kernel";
        arch = "arm";
        os = "linux";
        compression = "gzip";
        load = <0x80008000>;
        entry = <0x80008000>;
    };

    fdt {
        description = "Board device tree";
        data = /incbin/("board.dtb");
        type = "flat_dt";
        arch = "arm";
        compression = "none";
    };

    configurations {
        default = "conf";
        conf {
            description = "Default configuration";
            kernel = "kernel";
            fdt = "fdt";
        };
    };
};

# 编译命令
mkimage -f image.its image.itb

# U-Boot 中启动
U-Boot> fatload mmc 0:1 0x80008000 image.itb
U-Boot> bootm 0x80008000  # 自动选择 default configuration
U-Boot> bootm 0x80008000#conf  # 指定配置
bootz 与 booti 的简化
bootz/booti 不需要 mkimage 打包头部,直接用原始 zImage/Image 即可。U-Boot 内部自动检测镜像 magic,跳到入口。
现代开发优先用 FIT,因为它支持多镜像、签名验证(Secure Boot)。

十四、实战:编译 U-Boot for Raspberry Pi

本节以树莓派 4B 为例,演示完整的 U-Boot 编译流程。树莓派是学习 U-Boot 的最佳平台——无需 JTAG、SD 卡可启动、社区文档丰富。

U-Boot 编译-烧写-验证全流程 环境准备 安装交叉编译工具链 拉取 U-Boot 源码 配置 defconfig make rpi_4_defconfig menuconfig 可选 编译 make -j$(nproc) 产物 u-boot.bin 烧写 SD 卡 dd if=u-boot.bin of=/dev/sdX seek=8 启动 验证 树莓派启动介质布局 SD 卡 FAT 分区 offset 8 (扇区) u-boot.bin config.txt kernel=u-boot.bin 告诉 RPi 固件跳转到 U-Boot kernel + dtb Image / bcm2711 dtb U-Boot 从 FAT 加载 串口验证输出(115200) U-Boot 2024.04 — Hit any key to stop autoboot: 2 → U-Boot> 确认 bootcmd / bootargs / kernel 加载正确
图 14-1:U-Boot 编译-烧写-验证全流程(以树莓派 4B 为例)

14.1 环境准备

# Ubuntu 主机安装依赖
sudo apt install build-essential gcc-aarch64-linux-gnu \
     bison flex libssl-dev bc python3 python3-pyelftools

# 拉取 U-Boot 源码(主线 v2024.04 LTS)
git clone https://github.com/u-boot/u-boot.git
cd u-boot
git checkout v2024.04

14.2 配置与编译

# 1. 配置树莓派 4B(64 位)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rpi_4_defconfig

# 2. 可选:菜单配置(启用/禁用特性)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

# 3. 编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

# 产物
ls -lh u-boot.bin
ls -lh u-boot-nodtb.bin  # 不含 DTB 的版本(树莓派专用)

14.3 烧写到 SD 卡

# 假设 SD 卡为 /dev/sdX(请按实际替换)
sudo dd if=u-boot.bin of=/dev/sdX seek=8 bs=512 conv=fsync
sync

# 在 SD 卡第一个分区(FAT32)放入内核与设备树
sudo mount /dev/sdX1 /mnt
sudo cp zImage /mnt/
sudo cp bcm2711-rpi-4-b.dtb /mnt/
# config.txt 告诉树莓派固件跳到 U-Boot
echo "kernel=u-boot.bin" | sudo tee /mnt/config.txt
sudo umount /mnt

14.4 启动验证

将 SD 卡插入树莓派 4B,连接 UART-USB 转串口到 PC,波特率 115200,上电后应看到:

U-Boot 2024.04 (Sep 09 2026 - 12:34:56 +0800)

DRAM:  4 GiB
RPI 4 Model B (0xb03114)
Core:  105 devices, 18 uclasses, devicetree: separate
MMC:   mmcnr@7e202000: 0, emmc2@7e340000: 1
Loading Environment from FAT... OK
In:    serial,usbkbd
Out:   vidconsole,serial
Err:   vidconsole,serial
Net:   eth0: ethernet@7e580000

Hit any key to stop autoboot:  2   # 倒计时
Hit any key to stop autoboot:  1
Hit any key to stop autoboot:  0

U-Boot>                            # 进入命令行

14.5 配置自动启动

# 在 U-Boot 命令行配置
U-Boot> setenv bootargs "console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootwait"
U-Boot> setenv bootcmd "fatload mmc 0:1 0x80080000 Image; \
              fatload mmc 0:1 0x88000000 bcm2711-rpi-4-b.dtb; \
              booti 0x80080000 - 0x88000000"
U-Boot> saveenv
U-Boot> reset
调试小贴士
• 用 dmesg 在内核启动后看是否识别到 U-Boot 传参
• 用 md.b 0x80080000 0x100 看内核镜像头部
• printenv 查看当前环境变量,saveenv 持久化
• 用 dm tree 查看所有 udevice(DM 调试利器)
• 用 bdinfo 看 U-Boot 自身信息(编译时间、DDR 大小)

十五、调试技巧与常见问题

U-Boot 启动故障排查决策树 上电无任何输出? 是 UART 接线对吗? 是 3.3V 电源足吗? 是 检查 Boot ROM 配置 boot cfg 引脚 / 介质 否 U-Boot 能输出? 但内核启不来 是 bootargs 对吗? 是 内核/DTB 加载对? md 命令查内存 是 检查内核 .config 否 UART 接线错误 TX→RX / RX→TX / GND→GND 否 电源不足 换 5V 2A 以上电源 调试工具链速查 • UART 串口(115200)→ 必备,调试期主输出 • JTAG/OpenOCD → SPL 阶段死机时 • QEMU → 无需硬件快速验证 • Wireshark → 抓 TFTP 网络包 • md/mw → 内存读写 • dm tree → DM 设备树
图 15-1:U-Boot 启动故障排查决策树与调试工具链

15.1 调试工具链

工具 用途 典型场景
UART 串口 查看 U-Boot 启动日志 必备,调试期主输出
JTAG (OpenOCD) 断点、单步、内存查看 SPL 阶段死机时
QEMU 虚拟机模拟启动 无需硬件快速验证
Wireshark 抓 TFTP/网络包 网络加载失败
hexdump + strings 分析 u-boot.bin 镜像检查

15.2 常见故障排查

故障现象 可能原因 排查方法
上电无任何输出 UART 接线错/波特率错/电源不足 用示波器看 UART TX;确认 3.3V
启动到 DDR 初始化挂死 DDR 时序参数错/电压错 用 JTAG 单步;查 DDR 配置
SPL 卡住不再继续 SPL 大小超限/链接地址错 看 u-boot-spl.bin 是否 < 32KB
U-Boot 提示 ROM 加载失败 烧写位置错/介质未识别 读 SoC 手册确认 boot cfg 与 offset
找不到 SD 卡 未启用 DM_MMC/pinmux 错 用 mmc dev 测试;查 defconfig
网络不通 ethaddr 未设/IP 配置错 setenv ethaddr;ping serverip
内核启动后无输出 bootargs console 错 确认 console=ttyXXX 与 UART 一致
bootm 报 "Bad Magic" 加载的不是 uImage 镜像 用 bootz/booti 或重新 mkimage
设备树不生效 DTB 没加载到 fdt_addr 确认 bootm 第三个参数
调试忠告
① UART 串口是调试主输出,没有串口等于"盲飞"
② SPL 卡住常见原因是 DDR 配置错误——用 JTAG 看 DDR PHY 训练日志
③ bootcmd 改错导致启不来?按任意键进 U-Boot 命令行,env default -a 恢复默认
④ 烧错位置是 Boot ROM 不识别 SD 卡的常见原因——查 SoC datasheet 的 boot cfg

十六、总结

U-Boot 是嵌入式 Linux 启动的"第一段代码"——从 Boot ROM 接棒,到交给内核,承担了硬件初始化、镜像加载、参数传递三大职责。它如同桥梁,连接了芯片固化的 Boot ROM 与可扩展的 Linux 内核。

U-Boot 知识图谱回顾 架构层 • arch / board • Kconfig 配置 • arch/<cpu>/cpu 关键词: defconfig 三层分层 分类: 架构/板级/通用 启动层 • Boot ROM → SPL • U-Boot Main • relocate_code 关键词: board_init_f/r main_loop 分类: 5 阶段启动 能力层 • 命令行 • 设备树 • DM 驱动 关键词: bootm/bootz/booti 环境变量 分类: 4 类烧写方式 应用层 • 树莓派/i.MX • RK3399/STM32MP • Android ABL 工具: dtc / mkimage QEMU 地位: 80% ARM 设备
图 16-1:U-Boot 知识图谱(四层架构回顾)

核心要点速记

  1. Bootloader 是嵌入式系统上电第一段代码,承担硬件初始化与内核加载
  2. 启动五阶段:Boot ROM → SPL → U-Boot Main → Linux Kernel → 用户态
  3. SPL 是 U-Boot 的精简版,运行在 SRAM,仅初始化 DDR,加载 U-Boot Main 到 RAM
  4. 三大职责:硬件初始化(CPU/DDR/外设)+ 镜像加载(SD/NAND/网络)+ 权力移交(bootm)
  5. 主流 Bootloader:U-Boot(80% 嵌入式)、Barebox、CoreBoot、Little Kernel
  6. U-Boot 架构:arch(架构层)+ board(板级层)+ common/drivers/lib(通用层)
  7. Kconfig 配置:每个开发板一个 defconfig 文件
  8. 环境变量:bootcmd/bootargs/bootdelay 持久化在 Flash
  9. 设备树:DTS → DTB,描述硬件拓扑,U-Boot 可在线修改后传给内核
  10. Driver Model:uclass(类别)+ udevice(设备)+ driver(驱动)三层架构
  11. 引导命令:bootm(uImage)/ bootz(zImage)/ booti(ARM64 Image)
  12. FIT 镜像:多组件打包,支持多内核多 DTB + 签名验证
  13. 烧写四法:SD(dd)/ NAND(nand 命令)/ NOR(sf 命令)/ 网络(tftpboot)
  14. 烧写位置:每个 SoC 有固定的 Boot ROM offset,错了启动不起来
  15. 定位:U-Boot 是"嵌入式 Linux 启动的桥梁"——从硬件接棒,到内核交棒

嵌入式工业通信与系统启动系列至此涵盖 串口/RS-485/CAN/EtherCAT/Modbus/PROFINET + Bootloader/U-Boot 七大主题。前六篇覆盖"运行期通信",本篇覆盖"启动期引导"——两者结合,构成了嵌入式 Linux 从上电到运行的全栈知识。

BootloaderU-BootSPLBoot ROM设备树DTBFDTDriver ModeluclassudevicebootmbootzbootiFIT image环境变量Kconfigdefconfigrelocate_code树莓派Qt/C++