i.MX6ULL 启动镜像为什么需要 IVT 和 DCD:从 BootROM 到go命令
学习 i.MX6ULL 裸机和 U-Boot 时,我遇到了两个看起来互相打架的现象:
BootROM 启动 U-Boot:需要 u-boot.imx,里面有 IVT、DCD、Boot Data U-Boot 命令行运行裸机程序: tftp 0x87800000 printf.bin go 0x87800000为什么前者手续齐全,后者拿一个纯.bin就能出发?
因为负责加载程序的人变了,系统所处的阶段也变了。BootROM 是上电后第一次接手现场的人,DDR 还没准备好,它需要完整的“地址说明、初始化清单和镜像大小”;U-Boot 已经把屋子收拾好了,go只需要知道从哪扇门进去。
问题索引
| 你想弄清什么 | 去哪里看 |
|---|---|
| i.MX6ULL 上电后谁先运行 | BootROM 启动主线 |
| IVT 每个字段大概做什么 | IVT 是镜像导航表 |
| DDR 为什么由 DCD 初始化 | DCD 解决先有鸡还是先有蛋 |
| Boot Data 与 CSF 是什么 | 镜像大小和安全启动 |
为什么u-boot.imx不是纯代码 | BootROM 认识的完整镜像 |
为什么printf.bin不需要 IVT/DCD | U-Boot go 是另一条路径 |
go会不会解析 ELF 或重定位 | go 到底做了什么 |
| 加载地址和链接地址为什么要一致 | 三个地址别打架 |
| IVT 是否就是 ARM 中断向量表 | 两个 Vector Table 不是一家 |
| 面对新镜像怎么判断要不要头 | 五问判断模板 |
1. 上电后第一位接手的是 BootROM
i.MX6ULL 内部固化了一段 BootROM。芯片复位后,不是 U-Boot 立刻从天而降,而是 BootROM 先工作:
芯片复位 -> BootROM -> 判断启动介质 -> 查找合法 i.MX 启动镜像 -> 解析 IVT -> 执行 DCD -> DDR 可用 -> 根据 Boot Data 搬运镜像 -> 跳转到 IVT.entry -> U-Boot 开始运行BootROM 面对的环境很原始:
- 外部 DDR 还不能直接使用。
- 不存在文件系统和完整驱动框架。
- 只能按芯片约定识别启动介质与镜像格式。
- 早期通常依赖片内 ROM 和少量片内 RAM。
因此它不能把任意.bin随手往 DDR 一扔。它需要一份符合 i.MX 启动规范的镜像说明。
2. IVT 是给 BootROM 看的镜像导航表
IVT = Image Vector TableIVT 可以理解为启动镜像里的导航页:
IVT |- header |- entry |- dcd |- boot_data |- self `- csf| 字段 | BootROM 用它做什么 |
|---|---|
header | 确认这是合法 IVT,读取版本和长度 |
entry | 初始化和搬运后,从哪个地址开始执行 |
dcd | DCD 配置数据位于哪里 |
boot_data | 镜像起始地址、大小和插件标志 |
self | IVT 自身运行地址,用于解析相关指针 |
csf | HAB 安全启动相关数据位置 |
最重要的理解不是背字段顺序,而是知道 BootROM 通过 IVT 回答:
镜像各部分在哪里? 硬件初始化数据在哪里? 需要搬多少内容? 最后从哪里执行?3. DCD 解决“先有 DDR 还是先跑 U-Boot”的问题
DCD 全称:
Device Configuration Data它是一组给 BootROM 解释执行的寄存器配置命令,最典型用途是初始化 DDR:
BootROM 读取 DCD -> 配置 DDR IOMUX 与电气属性 -> 配置 DDR 控制器时序、宽度、刷新和校准 -> DDR 能可靠读写 -> BootROM 才能把较大的 U-Boot 搬进去这里有一个启动阶段的经典矛盾:
U-Boot 想运行,需要 DDR DDR 想可用,需要先配置控制器 配置 DDR 的代码又不能先放进尚不可用的 DDR 运行DCD 的价值就在这里:BootROM 在外部 DDR 尚不可用时,就能解释这些配置并写寄存器。
所以 DCD 不等于一段普通 C 初始化函数。它是 BootROM 认识的数据格式和命令序列。
DCD 不是永远正确的“DDR 配方”
更换 DDR 型号、容量、位宽、走线或时钟后,参考板 DCD 不一定还能直接使用。典型症状可能是:
完全无法启动 偶发启动 大内存访问出错 升温或降温后不稳定 U-Boot 运行一段距离后随机死机这时需要结合原理图、DDR 参数、NXP 工具和压力测试校准,而不是只确认“镜像里有 DCD”就宣布 DDR 已经毕业。
4. Boot Data 与 CSF 分别补充什么信息
Boot Data:镜像从哪开始、有多大
Boot Data 常描述:
- 镜像加载或运行起始地址。
- 镜像总大小。
- 是否为特殊 plugin 镜像。
可以把三者分工记成:
IVT:目录和指针 DCD:早期硬件怎么配 Boot Data:镜像搬哪里、搬多少CSF:安全启动相关信息
CSF 与 NXP HAB 安全启动有关,用于镜像签名、证书链、完整性和身份验证。
普通未启用安全启动的开发板,CSF 可能为空;正式开启 Secure Boot 后,它就不再是可有可无的装饰。
一句话区分:
DCD 解决“硬件能不能工作” CSF 解决“这份镜像能不能信”5.u-boot.imx为什么不是一份纯二进制
普通 U-Boot 编译产物中的纯代码,还不一定是 BootROM 能直接启动的最终镜像。
i.MX6ULL 构建会把:
U-Boot 程序 + IVT + DCD + Boot Data + 必要镜像头 + 可选 CSF -> u-boot.imx因此u-boot.imx同时服务两个目标:
- 让 BootROM 知道怎么准备硬件、搬运和跳转。
- 携带最终要运行的 U-Boot 代码与数据。
这也是为什么不能简单把任意u-boot.bin改名为u-boot.imx。文件后缀不是魔法贴纸,BootROM 看的是里面的结构。
6. 已经进入 U-Boot 后,go走的是另一条路径
在 U-Boot 命令行执行:
tftp 0x87800000 printf.bin go 0x87800000此时早期工作已经完成:
BootROM 已运行 DCD 已执行 DDR 已初始化 U-Boot 已搬入 DDR 并开始工作 串口和网络已可用 TFTP 能把文件放到指定地址所以现在不需要再让 BootROM 解析 IVT,也不需要再次通过 DCD 初始化 DDR。
流程只是:
TFTP 把 printf.bin 放入 DDR -> go 把控制权交给指定地址 -> CPU 从该地址开始取指BootROM 像第一次接站的人,需要地址、行李清单和开门步骤;U-Boot 已经把客人带进屋了,go只负责说“从这扇门进去”。
7.go没有你想象得那么全能
可以把go addr粗略理解为调用或跳转到一个地址。不同 U-Boot 版本和架构实现细节可能不同,但它不会替你完成:
- 解析 IVT/DCD。
- 初始化 DDR。
- 像 Linux
execve()一样完整加载 ELF。 - 自动重定位裸机程序。
- 加载设备树并准备 Linux 启动参数。
- 保证 Cache、MMU、中断和外设状态正好符合你的程序预期。
因此纯裸机程序至少要满足:
- 入口位于
go跳转的位置。 - 加载地址与链接假设一致,或程序具备位置无关/重定位能力。
- 不覆盖正在运行的 U-Boot、栈、malloc、环境和其他保留区。
- 自己正确处理栈、BSS、异常向量和硬件状态。
- 知道是否会返回 U-Boot;很多裸机程序并不适合返回。
go很直接,直接到有时近乎冷酷:地址给错,它不会在旁边提醒“您似乎想去另一个函数”。
8. 加载地址、链接地址和入口地址别打架
这三个概念经常混在一起:
| 地址 | 含义 |
|---|---|
| 加载地址 | 文件被放进内存的位置 |
| 链接地址 | 链接器安排代码、数据和符号时假定的运行地址 |
| 入口地址 | CPU 最终开始执行的位置 |
如果程序按0x87800000链接:
tftp 0x87800000 printf.bin go 0x87800000三者保持一致最简单。
如果改成:
tftp 0x88000000 printf.bin go 0x88000000但程序仍按0x87800000链接,绝对地址、全局变量、跳转表或位置相关访问可能出错,导致串口无输出、Data Abort、Undefined Instruction 或直接跑飞。
当然,位置无关代码、显式重定位或仅使用相对寻址的简单程序可能例外。关键不是“bin 永远不能换地址”,而是程序是否为换地址做好了准备。
怎么检查 ELF 的链接与入口
生成.bin前保留 ELF,使用:
arm-none-eabi-readelf-hprintf.elf arm-none-eabi-readelf-lprintf.elf arm-none-eabi-nm-nprintf.elf|headarm-none-eabi-objdump-hprintf.elf重点确认入口地址、段地址、链接脚本和下载地址。
9. IVT 和异常向量表只是名字有点像
| 名称 | 服务对象 | 作用 |
|---|---|---|
| Image Vector Table | i.MX BootROM | 描述启动镜像、入口、DCD、Boot Data、CSF |
| Exception Vector Table | ARM CPU 运行时 | 处理 reset、Undefined、SVC、Abort、IRQ、FIQ |
IVT 出现在启动镜像格式中;异常向量表属于程序运行后的 CPU 异常机制。两者都叫 Vector Table,不代表可以互相代班。
10. 已经在 DDR 运行时,不要随意重新初始化 DDR
理论上程序可以重写 DDR 控制器寄存器,但如果代码本身正在 DDR 中执行:
CPU 正从 DDR 取指 -> 程序修改 DDR 时钟或控制器 -> DDR 暂时不可用或时序改变 -> 下一条指令读取失败 -> 系统崩溃DDR 初始化通常要在程序被搬入 DDR 之前完成。若必须重新训练或切换配置,需要在片内 RAM 中运行专门代码,并严格控制 Cache、栈和访问路径,不是普通go示例里顺手改几行寄存器的工作。
11. 以后遇到启动镜像,先问这五个问题
1. 谁在加载程序? BootROM、U-Boot、Linux,还是调试器? 2. 加载时 DDR 是否已经可用? 若不可用,谁负责早期初始化? 3. 加载器认识什么格式? i.MX 启动镜像、ELF、FIT、uImage,还是纯 bin? 4. 程序按什么地址链接,又被放到哪里? 加载地址、链接地址、入口地址是否一致? 5. 跳转前的运行环境满足程序假设吗? 栈、BSS、Cache、MMU、中断、时钟和外设状态如何?按这五问走,通常就能判断:
| 场景 | 是否需要 IVT/DCD |
|---|---|
| BootROM 从 SD/eMMC 启动 U-Boot | 需要合法 i.MX 启动镜像 |
制作u-boot.imx | 需要相关启动结构 |
| BootROM/SDP 直接启动自定义裸机程序 | 通常需要符合 ROM 识别格式 |
| U-Boot 中 TFTP 下载 zImage | 不需要 IVT/DCD |
U-Boot 中go运行纯 bin | 不需要再次添加 |
| Linux 启动普通用户程序 | 不需要 |
总结
两条路径放在一起,就不容易混:
复位后 BootROM 直接启动 -> 环境未准备 -> 需要 IVT、DCD、Boot Data 等镜像信息 已经进入 U-Boot 后执行 go -> DDR 和基础环境已准备 -> 下载与链接地址匹配的程序,直接跳转IVT 告诉 BootROM“镜像各部分在哪里、最后从哪执行”,DCD 告诉 BootROM“DDR 和必要硬件怎么初始化”,Boot Data 告诉它“镜像多大、搬到哪里”。
真正值得记住的不是哪个文件后缀要加头,而是:当前是谁在加载程序,它手里已有怎样的运行环境。