Lazy loaded image
Words 0Read Time 1 min
Invalid Date
notion image
 

Uboot 启动阶段对比

对比项
第一阶段 (board_init_f)
第二阶段 (board_init_r)
执行时机
Uboot启动最初阶段
代码重定位完成后
代码位置
DDR低地址执行(0x87800000),栈/GD借用OCRAM(0x0091FF00) OCRAM 仅 128KB,放不下完整 U-Boot 镜像(~512KB+),所以代码不在 OCRAM 中执行
DDR高地址执行(重定位后)
主要目标
硬件基础初始化、确定内存布局、准备代码重定位
完成板级功能初始化、设备驱动初始化、准备进入主循环
核心任务
·初始化基本硬件(时钟、DRAM等)·计算各区域地址·保留内存区域·准备重定位参数
·设备模型初始化·存储设备初始化(emmc、nand)·网络初始化·控制台初始化·环境变量加载
关键函数
init_sequence_f[] 函数数组
init_sequence_r[] 函数数组
是否可用malloc
仅early malloc可用(受限)
完整的malloc机制可用
设备驱动
仅初始化必要的基础驱动
初始化完整的设备驱动模型和外设驱动
结束标志
完成代码重定位,跳转到board_init_r
进入主循环(main_loop),等待用户命令或自动启动

Boot ROM:U-Boot 之前的隐藏阶段

疑问:U-Boot 第一阶段 eMMC/NAND 驱动尚未初始化,镜像如何加载到 DDR?
答案:这项工作由 IMX6ULL 片内固化的 Boot ROM(~96KB,不可修改)在 U-Boot 之前完成。
Boot ROM 上电后从片内 ROM 地址空间直接取指令执行,完整流程:
  1. 读取 BOOT_MODE 引脚 + eFuse 配置 → 确定启动介质(eMMC/SD/NAND)
  1. 最小化初始化对应存储控制器(uSDHC 用于 eMMC/SD,GPMI 用于 NAND)— 仅满足读取数据的最低要求
  1. 从存储介质固定偏移读取 IVT(Image Vector Table)+ Boot Data + DCD(Device Configuration Data)
  1. 执行 DCD — DCD 包含 DDR 控制器的寄存器配置序列,Boot ROM 按序写入,完成 DDR 硬件初始化
  1. 将 U-Boot 镜像从存储介质拷贝到 DDR 的 0x87800000
  1. 跳转到 U-Boot 入口点 _start
⚠️
两层"初始化"的区别
  • Boot ROM 阶段:硬件级最小初始化,只能从固定偏移读取固定大小数据,目的仅是加载引导镜像
  • U-Boot 第二阶段 initr_mmc:完整的块设备驱动初始化(DM 驱动模型、速率协商、分区表解析),支持 mmc read/write、FAT/ext4 文件系统等
同理,board_init_f 中的 dram_init 并非真正初始化 DDR 硬件(Boot ROM 通过 DCD 已完成),而是探测 DDR 大小并记录到 gd->ram_size
U-Boot 镜像中包含 DCD 段(定义在 imximage.cfg),它不是给 U-Boot 自己用的,而是给 Boot ROM 用来配置 DDR 的寄存器写入序列。
 
第二阶段主要完成板级初始化、emmc初始化、控制台初始化、中断初始化及网络初始化等,流程图如下所示:
notion image

board_init_r

核心工作是遍历执行函数指针数组init_sequence_r[]所指向的每一个函数。
  1. initr_reloc - 标记重定位成功,malloc初始化;
  1. initr_caches - 首先检查 I-cache 的使能状态,如果未使能 I-cache 则将其使能,接着使能 D-cache;
  1. initr_reloc_global_data - 设置monitor_flash_len及gd成员env_addr,将EFI runtime重新定位到gd->relocadd;
  1. initr_malloc - 在第一阶段 reserve_malloc 已预留的 16MB 地址空间上,初始化 malloc 管理器(空闲链表、对齐边界、管理元数据等),使 malloc()/free() 可正常调用。第一阶段仅"圈地"(预留地址),此处才真正"建仓库"(初始化管理结构);
  1. log_init - 分配log驱动空间,设置gd->flags中的log就绪标志;
  1. initr_bootstage - 标记引导阶段;
  1. initr_console_record - 空;
  1. bootstage_relocate - 完整的bootstage实现,重定位当前的bootstage记录;
  1. initr_dm - 初始化设备驱动模型,驱动模型绑定->ofdata_to_platdata(可选)->probe
    1. 初始化一个树形的驱动模型结构,在内存中也就形成了一个至少深度为2的树形结构(假设有子节点),其中gd->dm_root 保存着根节点的信息。
      probe流程,先调用一下drv->ofdata_to_platdata(dev), 再去执行 probe。
  1. board_init - 设置启动参数的地址
  1. initr_serial- 完成串口相关初始化,但函数并未初始化实际硬件,而是向u-boot注册一下struct serial_device类型的串口设备,并将终端使用串口设备指针分配给serial_current;
🔄
与第一阶段 serial_init 的差异
  • 第一阶段 serial_initinit_sequence_f[]):在 OCRAM 栈 + early malloc 环境下,直接操作 UART 硬件寄存器(波特率分频器、数据位/停止位/校验位、FIFO 使能等),让物理串口尽早可用。配合 console_init_f 建立极简的 pre-console(直接调用 serial_putc,无 stdio 抽象层),用于输出 banner 和调试信息。
  • 第二阶段 initr_serialinit_sequence_r[]):DM(驱动模型)已就绪、完整 malloc 可用,不再触碰硬件寄存器,而是在驱动模型框架下注册 struct serial_device,将设备挂入 stdio 子系统,并把 serial_current 指针指向当前串口设备。为后续 console_init_r 建立完整的 stdin/stdout/stderr 路由做准备。
  • 本质:同一个串口的两次"初始化"语义不同 — 第一次是"让硬件能发字节",第二次是"让软件框架能管理这个设备"。硬件只初始化一次,第二阶段复用第一阶段已配好的寄存器状态。
  1. initr_announce - 打印调试信息的代码,表示RAM正在运行,且u-boot已经完成了重定位;
  1. initr_nand- 初始化nand;
  1. initr_mmc - 初始化emmc;
  1. initr_env - 检查早期在u-boot中的环境变量是否OK,OK就加载,否则使用默认的环境变量;
  1. initr_secondary_cpu - 空;
  1. stdio_add_devices - 添加标准输入输出设备;
  1. initr_jumptable- 函数调用了jumptable_init函数以初始化跳转表,为跳转表分配内存空间,它是基于动态分配的内存空间;
  1. console_init_r - 获取”stdin”、”stdout”及”stderr”(标准输入、标准输出及标准错误)对应的设备名字,设置串口的引脚复用功能,并打印当前的标准输入、输出、错误对应的设备名字,设置环境变量;
  1. interrupt_init- 中断初始化,为中断设置栈;
  1. initr_enable_interrupts - 使能中断异常;
  1. initr_ethaddr - 从环境变量中获得并初始化网络ethaddr地址,并存到gd->bd->bi_enetaddr;
  1. board_late_init - 复位看门狗;
  1. initr_net- 初始化网卡;
  1. run_main_loop

    第二阶段总结

    U-Boot 第二阶段从 board_init_r 入口开始,到 main_loop 进入命令行或自动引导内核结束。核心目标是在完整的 C 运行时环境下初始化所有板级外设和软件框架

    执行流程总览

    init_sequence_r 按子系统分类

    子系统
    关键函数
    核心职责
    内存 & 重定位
    initr_reloc initr_caches initr_malloc
    标记重定位完成、使能 Cache、初始化 16MB malloc 堆
    设备驱动模型
    initr_dm
    扫描设备树、绑定驱动、probe 设备(含 pinctrl/clk 配置)
    存储设备
    initr_nand initr_mmc
    完整块设备驱动(DM 框架 + 速率协商 + 分区表)
    环境变量
    initr_env
    从存储加载 env 或使用默认值
    控制台 & 中断
    initr_serial console_init_r interrupt_init
    注册串口到 stdio、建立 stdin/stdout/stderr、使能中断
    网络
    initr_ethaddr initr_net
    MAC 地址初始化、网卡驱动注册
    主循环
    run_main_loop
    倒计时 → 自动引导内核 / 进入命令行

    第一阶段 vs 第二阶段核心差异

    维度
    第一阶段 (board_init_f)
    第二阶段 (board_init_r)
    执行位置
    DDR 低地址 0x87800000,栈在 OCRAM
    DDR 高地址 0x9FE70000,栈在 DDR
    malloc
    early malloc(1KB,OCRAM)
    完整 malloc(16MB,DDR)
    驱动模型
    pre-reloc DM(受限扫描)
    完整 DM(全设备树扫描 + probe)
    串口
    serial_init:直接操作 UART 硬件寄存器
    initr_serial:注册设备到 stdio 框架
    核心目标
    DDR 探测、内存规划、准备重定位
    全外设初始化、环境加载、进入主循环
    💡
    设计哲学:第一阶段用最小资源(OCRAM 128KB)完成"能跑起来"的基本条件;第二阶段在充裕的 DDR 环境下完成"跑得好"的完整初始化。两阶段通过 gd(全局数据结构)传递状态,通过重定位实现地址空间切换。
    ➡️
    下一步main_loopautoboot_command 最终调用 bootz 命令,加载 zImage 到 0x80800000、设备树到指定地址,通过 kernel_entry(0, machid, fdt_addr) 将控制权移交给 Linux 内核。详见
    上一篇
    Data Structure and Algorithm
    下一篇
    用面试拷问嵌入式技术栈

    Comments
    Loading...