4、硬件初始化与固件加载:ROM代码、BootROM、FIP镜像的生成与加载机制

芯片上电那一刻,到底发生了什么?

很多人觉得这是黑魔法。其实说白了,就是一段固化在芯片里的代码开始执行。这段代码我们叫它ROM Code,或者BootROM。我做了这么多年固件,每次看到芯片第一次跑起来,心里还是有点小激动。

4.1 ROM Code:芯片的第一口呼吸

ROM Code是芯片出厂前就烧死的。你改不了,也删不掉。它的任务很简单——把外部存储里的下一级固件搬进RAM,然后跳过去执行。

核心职责:

  • 初始化最基础的硬件(时钟、电源、内存控制器)
  • 从预定义的启动介质读取BootROM或BL1
  • 验证固件签名(如果开启了安全启动)
  • 跳转到下一级固件入口

我记得有一次调试一块新板子,死活起不来。逻辑分析仪抓了半天,发现ROM Code在读取SPI Flash时时序不对。嗯,硬件工程师把SPI的极性画反了。这种坑,你光看代码是看不出来的。

4.2 BootROM:从ROM到RAM的接力棒

BootROM通常指芯片内部ROM里那段启动程序。但不同厂商的叫法不一样——有人叫它BL1,有人叫它Primary Bootloader。

我个人习惯把BootROM理解为"硬件初始化最小集"。它不会初始化所有外设,只做够用的那部分。比如:

  • 配置CPU异常向量表
  • 设置栈指针
  • 初始化DRAM控制器(有些芯片这一步在ROM Code里就做了)
  • 从启动设备加载FIP或BL2

避坑指南:

我曾经在一个项目里遇到BootROM把DRAM初始化参数写死了,结果换了不同批次的内存颗粒后,板子频繁死机。后来我们不得不改PCB布局来匹配BootROM的时序要求。所以,选芯片时一定要问清楚:BootROM的DRAM初始化是固定的还是可配置的?

4.3 FIP镜像:ATF的打包艺术

FIP(Firmware Image Package)是ARM Trusted Firmware定义的一种镜像打包格式。说白了,就是把BL1、BL2、BL31、BL32、BL33这些固件碎片塞进一个文件里。

为什么要这么做?你想想看,如果每个固件单独烧录,烧录工具要读好几次Flash,既慢又容易出错。FIP一次打包,一次烧录,省心多了。

FIP的典型结构

组件 描述 加载位置
BL1 BootROM加载的第一级固件 内部SRAM或OCRAM
BL2 平台初始化、加载后续镜像 DRAM(由BL1初始化)
BL31 EL3运行时固件(SMC处理、PSCI) DRAM中保留区域
BL32 可信操作系统(如OP-TEE) 安全DRAM
BL33 非安全固件(通常是UEFI或uboot) 非安全DRAM

生成FIP镜像的命令

ATF提供了fiptool来打包。我一般这么用:

# 生成FIP镜像
fiptool create \
  --tb-fw bl2.bin \
  --soc-fw bl31.bin \
  --tos-fw bl32.bin \
  --nt-fw bl33.bin \
  fip.bin

# 查看FIP内容
fiptool info fip.bin

注意:

FIP里的镜像顺序不重要,fiptool会根据UUID自动识别。但每个镜像的加载地址必须在编译时指定好,否则BL2加载时会跑飞。我曾经见过有人把BL31的加载地址写成了BL32的,结果EL3固件直接覆盖了TEE的内存——调试了整整两天才找到原因。

4.4 加载机制:从Flash到RAM的旅程

整个加载过程可以画成一张流程图。我习惯用SVG来画这种启动流,清晰直观。

ROM Code到FIP加载流程图 ROM Code → BootROM → FIP 加载流程 芯片上电 / Reset ROM Code:初始化时钟/电源 从Flash加载BootROM/BL1 BootROM:初始化DRAM 加载FIP镜像到DRAM 解析FIP → 跳转到BL2 💡 说明: FIP内部包含BL2、BL31、BL32、BL33等多个镜像。BootROM只负责加载FIP头部和BL2, 后续镜像由BL2根据FIP表项逐一加载到指定地址。

4.5 实际项目中的加载策略

不同芯片的加载策略差别很大。我列几个常见的:

  • 全加载模式:BootROM把整个FIP拷进DRAM,然后BL2在DRAM里解析。优点是快,缺点是DRAM要够大。
  • 按需加载模式:BootROM只加载BL2,BL2再去Flash里读BL31、BL32等。省内存,但Flash读取次数多。
  • 安全加载模式:每级固件加载后都要验签,验不过就死循环。我见过有些车规芯片,验签失败直接锁死芯片——得用专用工具才能解锁。

个人经验:

我在做一款AI加速卡时,BootROM只有64KB的SRAM可用。FIP里的BL2有80KB,根本放不下。后来我们用了"压缩+解压"的方案——BL1在ROM里,负责解压BL2。虽然启动慢了200ms,但总算把问题解决了。有时候,工程就是取舍的艺术。

4.6 常见问题与调试手段

问题现象 可能原因 排查方法
芯片上电后无任何输出 ROM Code未正确执行,或时钟未起振 用示波器测晶振、检查复位引脚电平
BootROM加载FIP失败 FIP烧录地址错误,或Flash驱动不匹配 用fiptool dump检查FIP内容,核对烧录偏移
BL2跳转后死机 加载地址冲突,或BL2镜像损坏 在BL2入口加GPIO翻转,确认是否到达
安全验签失败 公钥哈希不匹配,或签名算法不一致 检查OTP中存储的哈希值,确认签名工具版本

嗯,调试启动问题,我最常用的工具就是逻辑分析仪和JTAG。有时候加个GPIO翻转,比看日志管用多了。毕竟BootROM阶段,串口都还没初始化呢。

重要提醒:

千万不要在生产板上频繁擦写BootROM所在的Flash区域。有些Flash的寿命只有1万次,擦写多了就废了。我见过有人调试时反复烧录FIP,结果Flash挂了,板子直接变砖。后来我们改成了RAM调试——先用BootROM加载一个最小系统到RAM,然后在RAM里调试,最后才烧Flash。

好了,这一章的内容就到这里。硬件初始化和固件加载是整个启动链的基石,理解透了,后面UEFI和ATF的协同工作才能看得明白。