第二阶段启动:重定位、设备树加载与环境变量初始化

U-Boot proper 启动,说白了就是从 SPL 手里接过接力棒,完成真正的初始化工作。我刚开始接触这块时,总觉得第二阶段就是跑个流程,后来被一个重定位 bug 折磨了两天,才明白这里面的门道有多深。

这一阶段的核心任务有三个:代码重定位、设备树加载、环境变量初始化。它们环环相扣,任何一个出问题,系统都起不来。

4.1 重定位:把自己搬到该待的地方

U-Boot proper 一开始运行在 SPL 指定的临时地址上。这个地址可能是 SRAM,也可能是 DDR 的低端区域。但最终,U-Boot 需要把自己搬到 DDR 的高端地址去。为什么?

说白了,是为了给内核腾出低端内存空间。你想想看,内核通常要加载到物理地址的低端区域,如果 U-Boot 赖在那里不走,内核就没法加载。

重定位的关键日志

U-Boot 2024.01 (Jan 15 2024 - 10:30:00 +0800)
DRAM:  512 MiB
Relocation Offset is: 1ef7e000
Relocating to 3ff7e000, new gd at 3f7e7e00, sp at 3f7e7df0

看到 Relocation Offset 和 Relocating to 这两行,就说明重定位开始了。我个人习惯先看偏移量是否合理——对于 512MB 内存,偏移量在 0x1ef7e000 附近是正常的。

重定位的典型流程

  1. 计算目标地址:根据 DRAM 大小和 U-Boot 镜像尺寸,算出最终运行地址
  2. 复制镜像:把整个 U-Boot 从临时地址复制到目标地址
  3. 修复符号表:更新全局变量和函数指针的地址
  4. 跳转执行:在目标地址重新开始执行

我曾经遇到过一个坑:某款芯片的 DDR 控制器初始化时序有问题,导致低端内存正常、高端内存读写不稳定。重定位后 U-Boot 直接跑飞,日志停在 "Relocating to ..." 后面就没了。排查了两天才发现是 DDR 训练参数不对。

4.2 设备树加载:硬件信息的传递者

设备树(Device Tree)是现代 U-Boot 的核心数据结构。它描述了板级硬件信息——CPU 型号、外设地址、时钟频率、引脚复用等等。U-Boot proper 启动时,必须找到并解析设备树。

设备树通常存放在三个地方:

存储位置 加载方式 典型场景
U-Boot 镜像内嵌 直接链接到 .dtb 段 开发板、小容量 Flash
FIT 镜像 从 FIT 结构中提取 安全启动、多配置
存储设备 从分区读取 量产产品、OTA 升级

我建议:调试阶段优先用内嵌设备树,少一层加载逻辑就少一个出错点。等设备树加载稳定了,再切到分区读取方式。

设备树加载的日志特征

Loading Device Tree to 3f7e7000, end 3f7ee000 ... OK
libfdt: fdt_check_header(): FDT_ERR_BADMAGIC

第一行说明设备树加载成功。第二行是错误——FDT_ERR_BADMAGIC 表示设备树的魔数不对。说白了,就是加载到的数据根本不是合法的设备树。

为什么会这样?我遇到过两种情况:

  • 分区偏移配错了:U-Boot 读错了 Flash 分区,拿到的是垃圾数据
  • 设备树没烧录:生产时漏烧了 .dtb 文件

4.3 环境变量初始化:U-Boot 的配置中心

环境变量是 U-Boot 的运行时配置。它决定了启动参数、网络配置、启动命令等等。第二阶段启动时,U-Boot 会尝试从存储设备加载环境变量。

环境变量的存储位置由 CONFIG_ENV_IS_IN_* 系列宏决定:

宏定义 存储介质 特点
CONFIG_ENV_IS_IN_MMC eMMC/SD 卡 容量大、可扩展
CONFIG_ENV_IS_IN_SPI_FLASH SPI NOR Flash 启动快、可靠性高
CONFIG_ENV_IS_IN_NAND NAND Flash 容量大、需 ECC
CONFIG_ENV_IS_IN_FAT FAT 文件系统 方便升级、通用

环境变量加载的典型日志

Loading Environment from MMC... OK
Loading Environment from MMC... *** Warning - bad CRC, using default environment

第一行是正常情况。第二行是警告——CRC 校验失败,U-Boot 会使用默认环境变量。嗯,这里要注意:CRC 失败不等于环境变量完全损坏。有时候只是某个字节被写错了,但整个环境变量区还能用。

我曾经在生产线上遇到过:一批板子启动时总是报 "bad CRC",但功能正常。后来发现是 Flash 驱动在写环境变量时多写了 4 个字节,导致 CRC 计算范围不对。修复驱动后问题消失。

4.4 第二阶段启动的整体流程

把上面三个步骤串起来,就是第二阶段启动的完整流程。我画了一张图,方便你理解:

U-Boot Proper 第二阶段启动流程 U-Boot Proper 第二阶段启动流程 SPL 跳转到 proper 初始化串口、时钟、DDR 代码重定位到 DDR 高端 加载并解析设备树 加载环境变量(MMC/SPI/NAND) 进入命令行主循环 任何一步失败,U-Boot 都会打印错误信息并尝试恢复或进入紧急模式

4.5 常见问题与排查思路

第二阶段启动的问题,日志里通常有明确线索。我整理了几个高频问题:

问题现象 可能原因 排查方向
重定位后无输出 DDR 高端地址不稳定 检查 DDR 训练参数、时序配置
FDT_ERR_BADMAGIC 设备树加载地址错误或数据损坏 确认分区偏移、校验烧录数据
bad CRC 环境变量 存储介质坏块或驱动 bug 检查 Flash 驱动、尝试擦除重写
卡在 "Starting kernel ..." 设备树传递给内核的参数错误 检查 chosen 节点、内存节点配置

我的调试习惯:在 board_init_f() 和 board_init_r() 里加几个 GPIO 翻转点。用示波器看 GPIO 波形,比看日志更直观。特别是重定位后串口驱动还没重新初始化时,日志根本看不到。

4.6 实战:从日志定位第二阶段问题

假设你看到这样的日志:

U-Boot 2024.01
DRAM:  512 MiB
Relocation Offset is: 1ef7e000
Relocating to 3ff7e000, new gd at 3f7e7e00, sp at 3f7e7df0
Loading Device Tree to 3f7e7000, end 3f7ee000 ... OK
Loading Environment from MMC... *** Warning - bad CRC, using default environment

U-Boot >

嗯,这里要注意:设备树加载成功,环境变量 CRC 失败但用了默认值,U-Boot 还是进了命令行。这种算轻度问题,系统能跑,但环境变量设置不生效。

排查步骤:

  1. 先确认环境变量存储位置:看 CONFIG_ENV_IS_IN_MMC 是否定义
  2. 检查分区偏移:对比 CONFIG_ENV_OFFSET 和实际分区表
  3. 手动读写测试:在 U-Boot 命令行用 mmc read 读取环境变量分区,再用 md 查看数据
  4. 擦除重写:用 env erase 擦除后重新保存

如果擦除重写后还是报 CRC 错误,那多半是驱动问题。我曾经遇到过 MMC 驱动在写操作时没等 busy 信号结束就读回数据,导致读到的 CRC 是旧的。

第二阶段启动,说白了就是 U-Boot 从「能跑」到「跑好」的过程。重定位保证代码在正确的位置执行,设备树让 U-Boot 认识硬件,环境变量让用户可以配置行为。这三个环节都过了,U-Boot 才算真正站稳了脚跟。