1. 案例1
任务目标
在U-Boot源码中定位启动流程的硬件初始化入口,梳理从复位向量到board_init_f、board_init_r的调用链,明确两个阶段各自的初始化职责与边界。
环境与依赖
- U-Boot源码版本:v2023.04(或其他近期版本,路径结构基本一致)
- 目标板:以
qemu-arm或任意ARM开发板配置为例(无需实际硬件) - 工具:
grep、cscope或vim+ctags用于源码检索
启动流程关键入口定位
U-Boot的启动分为两个阶段,由board_init_f和board_init_r两个函数分别承载。第一阶段(f = first)在链接地址为ROM/SRAM的早期环境中执行,完成CPU/板级最基础的初始化和DRAM relocation准备;第二阶段(r = relocated)在代码重定位到DRAM后执行,完成完整的外设初始化和命令行环境搭建。
调用链核心路径如下(以ARM为例):
_start (arch/arm/lib/vectors.S)
→ reset (arch/arm/cpu/armv7/start.S)
→ _main (arch/arm/lib/crt0.S)
→ board_init_f (common/board_f.c)
→ relocate_code (arch/arm/lib/relocate.S)
→ board_init_r (common/board_r.c)
其中_main是C语言环境建立后的统一入口,board_init_f返回后不直接调用board_init_r,而是通过relocate_code将U-Boot自身拷贝到DRAM,再跳转到新地址的board_init_r。这是两个阶段切换的关键机制。
核心代码:初始化函数调用顺序
以下代码摘自common/board_f.c和common/board_r.c,展示两个阶段初始化序列的注册方式(实际为数组表驱动,按顺序执行):
// common/board_f.c — 第一阶段:重定位前
static int initf_dm(void) { /* 驱动模型早期初始化 */ return 0; }
static int initf_malloc(void) { /* malloc池预留 */ return 0; }
static int setup_machine(void) { /* 设置机器类型 */ return 0; }
static int reserve_mmu(void) { /* 预留MMU页表空间 */ return 0; }
static int setup_dram(void) { /* 探测DRAM大小 */ return 0; }
static init_fnc_t init_sequence_f[] = {
setup_machine,
initf_malloc,
initf_dm,
setup_dram,
reserve_mmu,
NULL, // 哨兵
};
void board_init_f(ulong boot_flags)
{
gd = (struct global_data *)&_global_data;
// 依次执行 init_sequence_f[] 中的每个函数
for (init_fnc_t *fn = init_sequence_f; *fn; ++fn) {
if ((*fn)()) // 返回非0表示出错
hang();
}
// 最后调用 relocate_code 跳转到第二阶段
relocate_code(gd->relocaddr, 0, (ulong)&_start);
}
// common/board_r.c — 第二阶段:重定位后
static int initr_dm(void) { /* 驱动模型完整初始化 */ return 0; }
static int initr_malloc(void) { /* 启用完整malloc */ return 0; }
static int initr_mmc(void) { /* MMC控制器初始化 */ return 0; }
static int initr_net(void) { /* 网络设备初始化 */ return 0; }
static int initr_jumptable(void) { /* 跳转表 */ return 0; }
static int run_main_loop(void) { /* 进入命令行主循环 */ return 0; }
static init_fnc_t init_sequence_r[] = {
initr_jumptable,
initr_dm,
initr_malloc,
initr_mmc,
initr_net,
run_main_loop,
NULL,
};
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
gd = new_gd;
// 依次执行 init_sequence_r[] 中的每个函数
for (init_fnc_t *fn = init_sequence_r; *fn; ++fn) {
if ((*fn)())
hang();
}
// 正常不会返回
}
实际源码中init_sequence_f[]包含约30~40个函数,init_sequence_r[]包含约40~50个函数,上述代码仅保留与本案例目标直接相关的代表性子集,用于说明调用顺序和阶段划分机制。完整列表请直接阅读对应源文件。
验证方法
- 源码路径确认:在U-Boot根目录执行以下命令,确认关键文件存在:
ls common/board_f.c common/board_r.c arch/arm/lib/crt0.S
- 调用链追踪:在
arch/arm/lib/crt0.S中搜索bl board_init_f和bl board_init_r,确认跳转顺序:
grep -n "board_init_f\|board_init_r" arch/arm/lib/crt0.S
预期输出:crt0.S中先调用board_init_f,随后调用relocate_code,最后调用board_init_r。
- 阶段职责验证:分别打开
common/board_f.c和common/board_r.c,对比init_sequence_f[]与init_sequence_r[]中的函数名,确认:
- 第一阶段包含
setup_dram、reserve_mmu等与内存布局相关的初始化; - 第二阶段包含
initr_mmc、initr_net等外设驱动初始化; - 第一阶段不包含任何外设驱动初始化,第二阶段不包含DRAM探测。
通过条件:以上三条验证全部符合,且能口头说明board_init_f→relocate_code→board_init_r的跳转原因(代码重定位需要DRAM可用,而DRAM大小探测必须在重定位前完成)。
常见问题
- 为什么board_init_f不直接调用board_init_r?——因为此时U-Boot仍在SRAM/ROM中运行,需要先将自己拷贝到DRAM(relocate),否则无法访问大容量内存。
- 两个阶段的gd(global_data)有何区别?——第一阶段gd位于SRAM中固定地址;重定位后gd被重新指向DRAM中的新副本,保证后续代码访问的是同一份全局数据。
- 如何确认某块板卡实际执行了哪些初始化?——在
board_init_f入口处设置断点,单步执行并观察init_sequence_f[]中每个函数是否被调用,或打开DEBUG宏查看打印日志。