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 Table
IVT 可以理解为启动镜像里的导航页:
IVT
|- header
|- entry
|- dcd
|- boot_data
|- self
`- csf
| 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 同时服务两个目标:
这也是为什么不能简单把任意 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 很直接,直接到有时近乎冷酷:地址给错,它不会在旁边提醒“您似乎想去另一个函数”。
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 -h printf.elf
arm-none-eabi-readelf -l printf.elf
arm-none-eabi-nm -n printf.elf | head
arm-none-eabi-objdump -h printf.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、中断、时钟和外设状态如何?
按这五问走,通常就能判断:
| 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 告诉它“镜像多大、搬到哪里”。
真正值得记住的不是哪个文件后缀要加头,而是:当前是谁在加载程序,它手里已有怎样的运行环境。






