4. U-Boot启动流程与移植

4.1 U-Boot 源码结构概览

U-Boot 是嵌入式系统中最重要的二级引导加载程序。在 ARM 架构下,尤其是车载 SoC(如 NXP i.MX8、TI Jacinto、Renesas R-Car)中,U-Boot 承担了硬件初始化、DDR 训练、设备树传递、ATF 加载等关键任务。理解其源码结构是移植的第一步。

目录/文件 功能说明
arch/arm/ ARM 体系结构相关代码,包括 CPU 初始化、异常向量、MMU、cache 等
board/ 板级特定代码,每个板子一个子目录,包含 board_init、DDR 配置、GPIO 初始化
configs/ 板级默认配置文件(*_defconfig),通过 make menuconfig 生成
include/configs/ 板级头文件配置(.h),定义宏如 CONFIG_SYS_TEXT_BASE
dts/ 设备树源文件(.dts / .dtsi),U-Boot 使用与 Linux 相同的设备树
drivers/ 驱动框架,包括串口、MMC、NAND、网络、SPI、I2C 等
common/ 通用命令、启动逻辑、HUSH shell 解析器
lib/ 库函数,如 tftp、lzma、rsa、fdt 解析
tools/ 主机端工具,如 mkimage、fdtgrep
移植要点: 车载平台移植通常只需要修改 board/、configs/、include/configs/ 和 dts/ 四个目录。核心 SoC 代码(arch/arm/)一般由芯片厂商提供,不建议修改。

4.2 SPL 与 ATF 启动流程

现代 ARMv8 车载 SoC 采用 SPL → ATF (BL31) → U-Boot → Linux 的多阶段启动模型。SPL(Secondary Program Loader)是 U-Boot 的微型第一阶段,负责最基础的初始化,然后加载 ATF 和完整的 U-Boot。

启动阶段划分:
  1. ROM Code:SoC 内部 BootROM,从 SD/eMMC/NAND/SPI-NOR 加载 SPL 到 SRAM。
  2. SPL (BL2):初始化 PLL、时钟、DDR 控制器,将 ATF (BL31) 和 U-Boot (BL33) 加载到 DDR。
  3. ATF (BL31):ARM Trusted Firmware,提供安全监控模式调用(SMC)、PSCI 电源管理。
  4. U-Boot (BL33):完整 U-Boot,加载 Linux 内核和设备树。
SPL + ATF 多阶段启动流程 SPL → ATF → U-Boot 启动流程 BootROM 加载 SPL 到 SRAM SPL (BL2) DDR 初始化 ATF (BL31) PSCI / SMC U-Boot (BL33) 加载内核 SPL 加载清单: ① ATF 镜像 (bl31.bin) → DDR 地址 0x80000000 ② U-Boot 镜像 (u-boot.bin) → DDR 地址 0x82000000 关键约束: SPL 必须在 SRAM 内运行(通常 ≤ 256KB),因此必须裁剪驱动、使用 CONFIG_SPL_BUILD 条件编译
常见移植陷阱: SPL 阶段的 DDR 时序参数必须与硬件 PCB 走线延迟匹配。车载平台常使用 DDR 训练固件(如 i.MX8 的 DDR PHY Firmware),该固件需在 SPL 中提前加载到 DDR 控制器内部 SRAM。若训练失败,系统会在 SPL 阶段死机,无任何串口输出。

4.3 板级配置(board/)移植方法

U-Boot 的板级代码位于 board/// 目录。移植一个新板子通常需要创建以下文件:

文件 必须实现的关键函数 / 内容
Kconfig 定义 TARGET_ 选项,关联 SoC 和板级配置
MAINTAINERS 维护者信息
Makefile 编译目标,通常包含 obj-y += board.o
board.c board_init_f()、board_init_r()、dram_init()、board_early_init_f()
imx8mp_evk.c (示例) GPIO 初始化、PMIC 配置、DDR 尺寸检测
board_init_f() 执行顺序(SPL 阶段):
  1. 调用 board_early_init_f():设置系统时钟、IOMUX、调试串口。
  2. 调用 dram_init():返回 DDR 大小(单位:字节),U-Boot 据此设置堆栈和堆。
  3. 调用 init_sequence_f[]:包括 timer、serial、GPIO、MMC 等驱动初始化。
  4. 调用 board_init_r():进入完整 U-Boot 阶段。

4.4 设备树(dts)在 U-Boot 中的使用

U-Boot 从 2014 年开始全面采用设备树(Device Tree)来描述硬件。与 Linux 不同的是,U-Boot 的设备树需要额外包含 U-Boot 特有属性,用于控制引导阶段的硬件配置。

4.4.1 设备树文件组织

dts/
├── arch/arm64/
│   ├── fsl-imx8mp.dtsi          # SoC 级 dtsi,包含所有外设节点
│   ├── imx8mp-evk.dts           # 板级 dts,包含 GPIO、PMIC、网络等
│   └── imx8mp-evk-u-boot.dtsi   # U-Boot 特有属性(bootph-all、u-boot,dm-spl)
关键属性说明:
  • bootph-all:表示该节点在 SPL、U-Boot、Linux 三个阶段都可用。
  • bootph-pre-ram:表示该节点在 SPL 阶段(DDR 初始化前)可用。
  • u-boot,dm-spl:旧版属性,已被 bootph-* 替代,但兼容。
  • u-boot,off-boot:在 U-Boot 阶段禁用该节点。

4.4.2 设备树在启动流程中的传递

U-Boot 在启动 Linux 时,会将设备树在内存中的地址通过 bootm 或 booti 命令传递给内核。车载平台通常使用 booti 命令(ARM64 专用)。

设备树在 U-Boot 中的使用流程 设备树在 U-Boot 中的使用流程 编译 DTS → DTB dtc -I dts -O dtb 打包到 FIT 镜像 mkimage -f fit.its U-Boot 加载 DTB load mmc 0:1 $fdt_addr booti 传递 DTB 车载平台设备树使用要点: ① 使用 fdt_addr 环境变量指定 DTB 加载地址(如 0x83000000) ② 使用 fdt_high 环境变量控制 DTB 是否被重定位(建议设为 0xffffffff 禁止重定位) ③ 使用 bootargs 环境变量传递内核命令行参数,如 console=ttymxc1,115200 root=/dev/mmcblk1p2

4.5 移植实战:从零添加一个新板子

以下步骤以 NXP i.MX8MP 平台为例,展示如何添加一个名为 myboard 的新板子:

  1. 创建板级目录:mkdir board/mycompany/myboard/
  2. 复制参考板文件:从 board/freescale/imx8mp_evk/ 复制 Kconfig、MAINTAINERS、Makefile、imx8mp_evk.c,并重命名。
  3. 修改 Kconfig:添加 TARGET_MYBOARD 选项,选择正确的 SoC。
  4. 创建设备树:在 dts/arch/arm64/ 下创建 myboard.dts 和 myboard-u-boot.dtsi。
  5. 创建默认配置:cp configs/imx8mp_evk_defconfig configs/myboard_defconfig,修改 CONFIG_TARGET_MYBOARD=y。
  6. 编译测试:make myboard_defconfig && make -j8,检查 u-boot.bin 和 u-boot.dtb 是否生成。
移植避坑指南:
  • SPL 阶段不要使用 malloc 动态分配大内存,SRAM 通常只有 128KB~256KB。
  • DDR 初始化代码必须放在 SPL 中,且不能依赖全局变量(BSS 段可能未初始化)。
  • 设备树中的 clock-frequency 属性必须与硬件晶振匹配,否则串口波特率会出错。
  • 车载平台常使用 CONFIG_BOOTDELAY=0 实现快速启动,但调试阶段建议设为 3 秒。

4.6 本章小结

知识点 核心要点
U-Boot 源码结构 arch/、board/、configs/、dts/ 是移植核心目录
SPL + ATF 启动 SPL 在 SRAM 运行,初始化 DDR 后加载 ATF 和 U-Boot
板级配置 board_init_f() 实现早期初始化,dram_init() 返回内存大小
设备树使用 U-Boot 使用 bootph-* 属性控制节点可用性,通过 booti 传递 DTB
移植步骤 创建 board 目录 → 修改 Kconfig → 添加 dts → 创建 defconfig → 编译验证