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。
启动阶段划分:
- ROM Code:SoC 内部 BootROM,从 SD/eMMC/NAND/SPI-NOR 加载 SPL 到 SRAM。
- SPL (BL2):初始化 PLL、时钟、DDR 控制器,将 ATF (BL31) 和 U-Boot (BL33) 加载到 DDR。
- ATF (BL31):ARM Trusted Firmware,提供安全监控模式调用(SMC)、PSCI 电源管理。
- U-Boot (BL33):完整 U-Boot,加载 Linux 内核和设备树。
常见移植陷阱: 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 阶段):
- 调用
board_early_init_f():设置系统时钟、IOMUX、调试串口。 - 调用
dram_init():返回 DDR 大小(单位:字节),U-Boot 据此设置堆栈和堆。 - 调用
init_sequence_f[]:包括 timer、serial、GPIO、MMC 等驱动初始化。 - 调用
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 专用)。
4.5 移植实战:从零添加一个新板子
以下步骤以 NXP i.MX8MP 平台为例,展示如何添加一个名为 myboard 的新板子:
- 创建板级目录:
mkdir board/mycompany/myboard/ - 复制参考板文件:从
board/freescale/imx8mp_evk/复制Kconfig、MAINTAINERS、Makefile、imx8mp_evk.c,并重命名。 - 修改 Kconfig:添加
TARGET_MYBOARD选项,选择正确的 SoC。 - 创建设备树:在
dts/arch/arm64/下创建myboard.dts和myboard-u-boot.dtsi。 - 创建默认配置:
cp configs/imx8mp_evk_defconfig configs/myboard_defconfig,修改CONFIG_TARGET_MYBOARD=y。 - 编译测试:
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 → 编译验证 |