4. IVT与Boot Data结构
在 i.MX 系列处理器的启动链中,Boot ROM 是芯片上电后执行的第一段固件。它并不理解 ELF、FIT 或任何操作系统镜像格式,它只认一种非常朴素的“自举描述结构”——IVT(Image Vector Table,镜像向量表)。IVT 是 Boot ROM 与用户镜像之间的契约:Boot ROM 按固定规则找到 IVT,从 IVT 中读出“镜像在哪里、有多大、入口在哪里、要不要校验”,然后跳转执行。理解 IVT 与 Boot Data 的字段含义和偏移关系,是排查“镜像烧录成功但无法启动”这类问题的第一把钥匙。
一、IVT 在镜像中的位置与自举加载总览
i.MX6/6ULL/7D 等常见器件的 Boot ROM 支持多种启动设备(SD/eMMC、NAND、NOR、串口、USB)。以 SD 卡启动为例,Boot ROM 会读取 SD 卡的第一个扇区(LBA 0,512 字节)作为引导头,其中偏移 0x00 处存放的是 Boot Tag,偏移 0x04 处存放的是 IVT 的绝对地址(小端)。Boot ROM 据此跳转到该地址读取 IVT 结构。因此,镜像布局中必须保证 IVT 位于一个 Boot ROM 可寻址的位置,并且其地址被正确写入引导头。
IVT 本身是一个固定 32 字节的结构,包含 8 个 32 位字段。它并不直接描述整个镜像,而是通过指针把 Boot ROM 引向 boot_data、DCD、入口地址和自举数据。下图给出 IVT 与 Boot Data、DCD、入口地址之间的字段级关系与自举加载路径。
二、IVT 八个字段逐一解析
IVT 的 8 个字段都是 32 位小端存储,含义如下:
- header(0x00):4 字节复合字段。低 8 位是 Tag,固定为 0xD1;次 8 位是长度,固定为 0x20(32 字节);高 16 位是版本,i.MX6 常用 0x40,i.MX7/8 常用 0x41。Boot ROM 首先校验 Tag 和长度,不匹配则直接放弃该启动设备。
- entry(0x04):镜像入口的绝对地址。Boot ROM 完成搬运后跳转到这里。对于 U-Boot,这个地址通常是 U-Boot 的 _start 符号地址,例如 0x87800000(i.MX6ULL DDR 起始)。
- reserved(0x08):保留字段,必须写 0。
- dcd_ptr(0x0C):指向 DCD(Device Configuration Data)的地址。DCD 用于在跳转前配置 DDR 控制器、时钟、IOMUX 等。若不需要 DCD,该字段写 0。
- boot_data_ptr(0x10):指向 boot_data 结构的地址。这是 IVT 与镜像长度信息之间的桥梁。
- self(0x14):IVT 自身的绝对地址。Boot ROM 用它做一致性校验,也用于计算相对偏移。
- csf(0x18):指向 CSF(Command Sequence File)的地址,用于 HAB 安全启动签名。未启用安全启动时写 0。
- reserved2(0x1C):保留字段,写 0。
可以看到,IVT 本身不携带镜像长度,长度信息由 boot_data 提供。这种“指针分离”设计让 IVT 可以放在镜像头部,而 boot_data 可以紧随其后或放在其他位置,只要地址正确即可。
三、boot_data 偏移与字段含义
boot_data 是一个 12 字节结构,通常紧跟在 IVT 之后,但并非强制。它的三个字段是:
| 偏移 | 字段 | 含义 | 典型值示例 |
|---|---|---|---|
| 0x00 | start | 镜像起始地址(含 IVT 的绝对地址) | 0x877FF400 |
| 0x04 | length | 镜像总长度(字节),含 IVT、Boot Data、DCD、U-Boot 本体 | 0x00080000 |
| 0x08 | plugin | 插件标志,普通启动为 0 | 0x00000000 |
Boot ROM 读取 boot_data 后,会以 start 为源地址、length 为长度,把整段镜像搬运到 entry 所在的运行地址。注意:start 是镜像在启动介质中的加载地址,entry 是搬运完成后 CPU 跳转执行的地址,两者可以不同。例如从 SD 卡启动时,start 可能是 0x877FF400(DDR 中的加载地址),entry 是 0x87800000(U-Boot 正文入口),中间 0xC00 字节正是 IVT、Boot Data、DCD 等头部结构占用的空间。
四、entry 地址与自举加载流程
自举加载的完整路径可以概括为:Boot ROM 读引导头 → 定位 IVT → 校验 header → 读 boot_data 得到 start/length → 按 length 搬运镜像 → 跳转 entry。其中有两个关键判断点:
- IVT header 校验:Tag 必须为 0xD1,长度必须为 0x20。若烧录时字节序错误或地址偏移错误,Boot ROM 会认为该设备无有效镜像,转而尝试下一个启动设备。
- boot_data 一致性:start 必须等于 IVT 所在地址(或镜像实际起始地址),length 必须覆盖整个镜像。若 length 偏小,U-Boot 尾部被截断,跳转后可能在重定位阶段崩溃;若 start 错误,搬运源地址错位,entry 处代码完全不可执行。
实际工程中,IVT、Boot Data、DCD 通常由 mkimage 工具自动生成。以 i.MX6ULL 为例,使用 mkimage -T imximage 并配合 .imximage.cfg 配置文件,工具会根据配置中的 BOOT_FROM、LOAD_ADDR、ENTRY_ADDR 等参数,自动计算并填充 IVT 各字段。手工构造时,必须保证所有指针都是绝对地址,且与链接脚本中的地址一致。
hexdump -C image.imx | head -n 4 查看前 64 字节:偏移 0x00 应为 D1 00 20 40(或 41),偏移 0x04 应为 entry 地址的小端表示,偏移 0x10 应为 boot_data 地址。若这些字节不符合预期,问题多半出在镜像生成阶段,而非 Boot ROM 或硬件。
五、一个可对照的示例布局
假设某 i.MX6ULL 板卡从 SD 卡启动,U-Boot 链接地址为 0x87800000,镜像头部占用 0xC00 字节,则典型布局如下:
0x877FF400 IVT header : D1 00 20 40
0x877FF404 entry : 00 00 80 87 -> 0x87800000
0x877FF408 reserved : 00 00 00 00
0x877FF40C dcd_ptr : 00 00 00 00 (无 DCD)
0x877FF410 boot_data_ptr : 00 00 00 87 -> 0x877FF400? 需按实际填写
0x877FF414 self : 00 00 00 87 -> 0x877FF400
0x877FF418 csf : 00 00 00 00
0x877FF41C reserved2 : 00 00 00 00
0x877FF420 boot_data.start : 00 F4 FF 87 -> 0x877FF400
0x877FF424 boot_data.length: 00 00 08 00 -> 0x00080000
0x877FF428 boot_data.plugin: 00 00 00 00
0x877FF42C ... : DCD / 填充 / U-Boot 正文
0x87800000 U-Boot _start : 入口执行点
上表中 boot_data_ptr 和 self 的具体数值取决于 IVT 实际被放置在镜像中的哪个偏移。若 IVT 位于镜像起始处,则 self 等于镜像起始地址;若 IVT 前还有引导头,则 self 需要加上引导头长度。这一点在手工构造镜像时最容易出错,务必以链接脚本和实际烧录偏移为准。
掌握 IVT 与 Boot Data 的字段语义和偏移关系后,再回头看 U-Boot 的 u-boot.imx 文件,就能理解为什么它比 u-boot.bin 多出约 3KB 的头部,也能在启动异常时快速定位是头部生成错误、地址配置错误,还是搬运长度不足。这是 i.MX 启动链工程中最基础、也最值得反复核对的一环。