第二阶段启动:重定位、设备树加载与环境变量初始化
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 附近是正常的。
重定位的典型流程
- 计算目标地址:根据 DRAM 大小和 U-Boot 镜像尺寸,算出最终运行地址
- 复制镜像:把整个 U-Boot 从临时地址复制到目标地址
- 修复符号表:更新全局变量和函数指针的地址
- 跳转执行:在目标地址重新开始执行
我曾经遇到过一个坑:某款芯片的 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 第二阶段启动的整体流程
把上面三个步骤串起来,就是第二阶段启动的完整流程。我画了一张图,方便你理解:
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 还是进了命令行。这种算轻度问题,系统能跑,但环境变量设置不生效。
排查步骤:
- 先确认环境变量存储位置:看
CONFIG_ENV_IS_IN_MMC是否定义 - 检查分区偏移:对比
CONFIG_ENV_OFFSET和实际分区表 - 手动读写测试:在 U-Boot 命令行用
mmc read读取环境变量分区,再用md查看数据 - 擦除重写:用
env erase擦除后重新保存
如果擦除重写后还是报 CRC 错误,那多半是驱动问题。我曾经遇到过 MMC 驱动在写操作时没等 busy 信号结束就读回数据,导致读到的 CRC 是旧的。
第二阶段启动,说白了就是 U-Boot 从「能跑」到「跑好」的过程。重定位保证代码在正确的位置执行,设备树让 U-Boot 认识硬件,环境变量让用户可以配置行为。这三个环节都过了,U-Boot 才算真正站稳了脚跟。