3. BL1 (Boot ROM)

好,咱们正式开始拆解ATF的第一阶段——BL1。

BL1,全称是Boot Loader Stage 1。它被固化在芯片内部的ROM里,芯片一上电,CPU复位后执行的第一条指令,就来自这里。说白了,这是整个系统启动的“原点”。

我个人习惯把BL1叫做“硬启动代码”。为什么?因为它没得改。芯片出厂那一刻,BL1的内容就焊死在硅片上了。你想想看,这玩意儿要是出了bug,那可不是打个补丁就能解决的,得重新流片。所以,BL1的设计哲学就一个字:稳。

3.1 BL1 的核心职责

BL1的任务其实很纯粹,就三件事:

  • 初始化最基础的硬件:比如关掉所有中断、设置异常向量表、初始化栈指针。这时候DRAM还没影儿呢,只能用芯片内部的SRAM或者Cache作为临时RAM。
  • 验证并加载BL2:BL1从Flash(比如NOR Flash、eMMC、或者SPI Flash)里把BL2的镜像读出来,做完整性校验和签名验证。只有通过验证,才会把控制权交给BL2。
  • 跳转到BL2:一切就绪后,BL1会清理自己的痕迹,然后跳转到BL2的入口地址。

关键点:BL1是信任链的起点。它本身是“绝对可信”的,因为它固化在ROM里,物理上无法篡改。后续所有组件的可信度,都建立在BL1对它们的验证之上。

3.2 BL1 的执行环境

嗯,这里要注意。BL1运行在EL3(异常级别3),这是ARMv8架构里权限最高的异常级别。它拥有对系统所有资源的完全控制权。

但有个尴尬的地方:BL1运行的时候,DRAM还没初始化。所以它只能用芯片内部的“小池塘”——也就是SRAM或者Cache。这个空间通常很小,可能只有几十KB到几百KB。我在项目中遇到过,某款芯片的SRAM只有64KB,BL1加上BL2的头部信息,挤得满满当当,稍微多加点调试代码就爆了。

资源 说明
异常级别 EL3
内存 内部SRAM 或 设置为SRAM使用的Cache
存储介质 Boot ROM(代码本身) + 外部Flash(存放BL2镜像)
栈 在SRAM中临时分配,通常很小(几KB)

3.3 BL1 的典型执行流程

我画了一张流程图,帮你把BL1的整个生命周期串起来。你看完应该就清楚了。

BL1 典型执行流程图 1. 上电复位,CPU从ROM开始执行 2. 设置异常向量表、栈指针 3. 初始化必要硬件(UART、定时器等) 4. 从Flash读取BL2镜像到SRAM 5. 验证BL2签名(RSA/ECDSA等) 6. 清理并跳转到BL2入口

你看,流程很清晰。但有几个细节我想展开聊聊。

3.4 避坑指南:BL1 开发中的常见问题

我曾经在一个项目里,被BL1折腾得够呛。当时芯片的Boot ROM有个隐藏的勘误:在特定条件下,Cache作为RAM使用时,初始化顺序不对会导致数据污染。我们查了整整两周,最后是拿着逻辑分析仪一根一根量信号线才定位到问题。

所以,我给你总结几个实战中容易踩的坑:

  • 栈空间溢出:BL1的栈通常只有几KB。如果你在BL1里调用了复杂的函数,或者递归调用,很容易就把栈撑爆了。我建议你在调试阶段,在栈底填充一个固定模式(比如0xDEADBEEF),然后定期检查这个模式有没有被覆盖。
  • Flash读取时序:BL1要从Flash里读BL2镜像。但Flash的读取时序(比如SPI的时钟频率、模式)必须和硬件设计匹配。我见过有人把SPI的极性搞反了,结果读出来的数据全是0xFF。
  • 签名验证失败:BL1的签名验证是信任链的关键。但如果你把公钥的哈希值存错了位置,或者签名算法参数没对齐,那BL2永远也跑不起来。我曾经犯过一个低级错误:公钥哈希值的大小端搞反了,结果验证死活过不去。

警告:千万不要在BL1里做复杂操作!比如初始化DRAM、加载文件系统、或者打印冗长的调试信息。BL1的空间和时间都极其有限。它的原则是“做完该做的,立刻走人”。任何多余的操作,都可能引入不可预知的风险。

3.5 BL1 与 BL2 的接口约定

BL1跳转到BL2时,不是简单的一条跳转指令就完事了。它们之间有一套约定好的接口规范。在ATF里,这个规范叫做Boot Loader Stage 2 Image (BL2_IMAGE)。

BL1会通过寄存器向BL2传递一些关键信息:

寄存器 传递内容 说明
x0 BL2入口点 BL2镜像在内存中的起始地址
x1 平台信息结构体指针 包含内存布局、外设基地址等硬件信息
x2 BL2镜像大小 用于BL2自我校验
x3 预留(通常为0) 用于未来扩展

我个人习惯在BL1的最后,把这些寄存器的值打印出来(通过UART),方便调试时确认传递的参数是否正确。你想想看,如果x1传了一个野指针过去,BL2一上来就访问非法地址,那整个系统直接就挂了。

3.6 小结

BL1虽然代码量不大,但它是整个系统启动的基石。它固化在ROM里,不可更改,所以必须经过严格的验证。它的核心任务就是初始化最基础的硬件,验证并加载BL2,然后功成身退。

嗯,说白了,BL1就是个“开门人”。它把门打开,确认进来的人是可信的,然后就把钥匙交给下一个人。至于后面的人要做什么,它不管,也管不了。

小技巧:如果你在调试BL1,建议在UART输出里加上时间戳。BL1的执行时间通常只有几毫秒到几十毫秒。通过时间戳,你可以精确地知道每个步骤花了多少时间,这对优化启动速度非常有帮助。