第3章:BROM分析:A64内部BROM结构、BROM启动代码执行流程、BROM与SPC通信

好,咱们进入第三讲。这一章我打算把A64的BROM彻底拆开来讲。BROM,也就是Boot ROM,是芯片上电后第一个执行代码的地方。说白了,它就是一颗SoC的“灵魂种子”。你想想看,芯片刚上电,DDR还没初始化,NAND Flash也读不了,CPU凭什么能跑起来?靠的就是这块巴掌大的只读存储器。

我个人习惯把BROM比作“芯片的接生婆”。它不负责干重活,但负责把系统从“无”带到“有”。这一章,我们就来解剖这个接生婆。

3.1 A64内部BROM结构

全志A64的BROM,物理上是一块32KB的ROM。注意,是真正的只读存储器,出厂就固化好了。你改不了,也擦不掉。它的地址映射在0x00000000这个起始位置。嗯,这里要注意,很多新手会误以为0x00000000是DDR的起始地址,其实在A64上,上电后这个地址映射的是BROM。

BROM内部大致分为几个逻辑区域:

  • 启动向量表:位于BROM的最开头,包含复位向量、异常向量等。CPU上电后,PC指针直接跳到这里。
  • 硬件初始化代码:这部分代码负责最基本的CPU和总线初始化。比如设置系统时钟、关闭看门狗、初始化SPC(安全处理器)等。
  • 启动介质扫描代码:这是BROM的核心逻辑。它会按照固定的优先级顺序,去检测各个启动介质(SD卡、NAND、SPI NOR等)上是否存在有效的启动镜像。
  • SPC通信协议栈:BROM需要和SPC(Security Processor,安全处理器)进行交互,这部分代码就是实现两者之间的通信协议。
  • FEL模式代码:如果所有启动介质都失败了,BROM会进入FEL模式,等待USB下载。这是开发调试的救命稻草。

关键点:BROM的大小是固定的32KB。这意味着它的功能必须极其精简。任何多余的代码都会挤占宝贵的空间。我在做早期A64的bring-up时,就深刻体会到了这一点——BROM里每一行汇编都是精打细算的。

3.2 BROM启动代码执行流程

好,我们来看看BROM到底是怎么一步步把系统拉起来的。这个过程,我建议你把它想象成一个“闯关游戏”。

第一步:上电复位

芯片上电后,CPU从0x00000000开始执行。BROM的第一件事就是设置堆栈指针(SP),然后跳转到初始化代码。这里有个细节:A64的CPU在复位后默认是32位ARM模式,而不是64位AArch64模式。BROM会先以32位模式运行,完成一些基础初始化后,再切换到64位模式。

第二步:硬件初始化

BROM会做以下几件事:

  • 关闭看门狗定时器(WDT)——防止意外复位
  • 初始化系统时钟(PLL)——让CPU跑在合适的频率上
  • 初始化SPC——建立与安全处理器的通信通道
  • 初始化基本的GPIO和UART——方便调试输出

第三步:启动介质扫描

这是最核心的部分。BROM会按照一个固定的优先级列表去扫描启动介质。A64的默认顺序是:

优先级 启动介质 说明
1 FEL模式 如果检测到USB连接,直接进入FEL
2 SD卡(MMC0) 检测SD卡上的启动分区
3 eMMC(MMC2) 检测板载eMMC
4 SPI NOR Flash 检测SPI接口的NOR Flash
5 NAND Flash 检测NAND Flash

对于每个介质,BROM会尝试读取其前若干个扇区(通常是前1MB),然后检查是否存在有效的“启动头”(Boot Header)。这个启动头包含了魔数、校验和、镜像大小、加载地址等信息。如果校验通过,BROM就把这个镜像加载到SRAM中,然后跳转过去执行。

我的经验:我曾经遇到过一块板子,死活从SD卡启动不了。查了半天,发现是SD卡的分区表格式不对。BROM对启动介质的格式要求非常严格,比如SD卡必须是FAT32格式,且启动镜像必须放在特定的偏移位置。这个坑,我踩过,你们就别再踩了。

第四步:加载并跳转到SPL

如果BROM在某个介质上找到了有效的启动镜像,它会把镜像加载到SRAM中(通常是0x00020000这个位置),然后跳转过去。这个被加载的镜像,就是我们常说的SPL(Secondary Program Loader)。SPL的职责是初始化DDR,然后加载真正的Bootloader(如U-Boot)。

如果所有介质都扫描失败,BROM会进入FEL模式。这时候,芯片会等待USB主机通过FEL协议来下载代码。这也是我们开发调试时最常用的手段。

3.3 BROM与SPC通信

SPC,全称是Security Processor Controller,也就是安全处理器。在A64上,SPC是一个独立的微控制器,负责处理安全相关的任务,比如密钥管理、安全启动验证等。BROM和SPC之间通过一个专用的Mailbox机制进行通信。

这个Mailbox,说白了就是一块共享内存区域,加上一组中断信号。BROM和SPC各自拥有对这块内存的读写权限。通信流程大致如下:

  1. BROM发起请求:BROM将请求数据写入Mailbox的发送缓冲区,然后触发一个中断给SPC。
  2. SPC处理请求:SPC收到中断后,从Mailbox中读取请求数据,执行相应的安全操作(比如验证签名)。
  3. SPC返回结果:SPC将处理结果写入Mailbox的接收缓冲区,然后触发一个中断给BROM。
  4. BROM获取结果:BROM收到中断后,从Mailbox中读取结果,继续后续流程。

这个通信协议是半双工的,也就是说,同一时刻只能有一方在发送数据。BROM和SPC之间通过一个简单的状态机来保证数据的一致性。

注意:BROM和SPC的通信是安全启动的关键环节。如果SPC验证失败,BROM会拒绝加载后续的Bootloader。我在项目中就遇到过SPC固件版本不匹配导致启动失败的情况,折腾了两天才定位到问题。所以,如果你在定制Bootloader时涉及到安全启动,一定要确保SPC的固件和BROM的版本是兼容的。

另外,BROM和SPC之间还有一个“心跳”机制。BROM会定期向SPC发送一个“我还活着”的信号。如果SPC长时间没有收到这个信号,它会认为BROM已经崩溃,然后触发系统复位。这个机制是为了防止系统在启动过程中卡死。

嗯,这一章的内容就到这里。BROM虽然小,但五脏俱全。理解了BROM,你就理解了A64启动流程的“第一推动力”。下一章,我们会深入SPL,看看它是如何接过BROM的接力棒,把DDR初始化好,然后加载U-Boot的。