1. 案例1

案例1:U-Boot启动流程中的硬件初始化入口分析 学习卡片

任务目标

在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个函数,上述代码仅保留与本案例目标直接相关的代表性子集,用于说明调用顺序和阶段划分机制。完整列表请直接阅读对应源文件。

验证方法

  1. 源码路径确认:在U-Boot根目录执行以下命令,确认关键文件存在:
ls common/board_f.c common/board_r.c arch/arm/lib/crt0.S
  1. 调用链追踪:在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。

  1. 阶段职责验证:分别打开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宏查看打印日志。