
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 地址空间直接取指令执行,完整流程:
- 读取
BOOT_MODE引脚 + eFuse 配置 → 确定启动介质(eMMC/SD/NAND)
- 最小化初始化对应存储控制器(uSDHC 用于 eMMC/SD,GPMI 用于 NAND)— 仅满足读取数据的最低要求
- 从存储介质固定偏移读取 IVT(Image Vector Table)+ Boot Data + DCD(Device Configuration Data)
- 执行 DCD — DCD 包含 DDR 控制器的寄存器配置序列,Boot ROM 按序写入,完成 DDR 硬件初始化
- 将 U-Boot 镜像从存储介质拷贝到 DDR 的
0x87800000
- 跳转到 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初始化、控制台初始化、中断初始化及网络初始化等,流程图如下所示:

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





