欢迎光临
我们一直在努力

`u-boot.imx` 要镜像头,`printf.bin` 为什么可以直接 `go`?

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

字段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 -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、中断、时钟和外设状态如何?

    按这五问走,通常就能判断:

    场景是否需要 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 告诉它“镜像多大、搬到哪里”。

    真正值得记住的不是哪个文件后缀要加头,而是:当前是谁在加载程序,它手里已有怎样的运行环境。

    赞(0)
    未经允许不得转载:171主机测评 » `u-boot.imx` 要镜像头,`printf.bin` 为什么可以直接 `go`?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址