3. 源码结构解析

好,咱们直接进入正题。这一章我打算带你把ATF的源码目录翻个底朝天,再把BL1到BL33这几个启动阶段彻底讲透。说实话,我刚接触ATF那会儿,光看目录结构就晕了半小时——文件夹套文件夹,名字还都挺像。但等你真正搞明白每个目录是干嘛的,每个BL阶段负责什么,你会发现这玩意儿设计得其实挺巧妙。

3.1 ATF源码目录结构

先说说顶层目录。你从官方仓库拉下来代码后,第一眼看到的就是这些:

bl1/
bl2/
bl2u/
bl31/
bl32/
bl33/
common/
plat/
include/
lib/
docs/
tools/

嗯,看着是不是有点眼熟?bl1到bl33这几个目录,就是咱们后面要重点讲的启动阶段。每个目录里放的是对应阶段的源码和链接脚本。我个人的习惯是,先看plat/目录,因为平台相关的代码都在这里,移植工作主要就是改这里面的东西。

再往下看:

  • common/:放的是各阶段共享的通用代码,比如一些基础的数据结构、内存管理工具。
  • lib/:库函数目录,里面又有子目录,比如lib/xlat_tables/是页表操作库,lib/psci/是电源管理接口库。这些库你移植时基本不用动,但得知道它们的存在。
  • include/:头文件大本营。我建议你重点看include/plat/,平台相关的宏定义和接口声明都在这里。
  • docs/:官方文档。说实话,有些文档写得挺简略的,但docs/porting-guide.rst这份移植指南你必须读一遍。
  • tools/:一些辅助工具,比如生成证书、打包镜像的工具。

这里我插一句。我在做第一个移植项目时,犯过一个低级错误——直接改了common/目录下的代码。结果一编译,其他平台全崩了。后来才明白,common/是共享的,你改它会影响所有平台。正确的做法是在plat/里重写或覆盖。

注意:永远不要修改 common/、lib/ 等通用目录下的代码。平台定制请全部放在 plat/<平台名>/ 下。

3.2 BL1:最底层的“硬核”启动

BL1,全称Boot Loader Stage 1。它是芯片上电后执行的第一个代码,通常放在ROM里,不可修改。你想想看,这时候DRAM还没初始化呢,代码只能在SRAM或者Cache里跑。

BL1的核心任务就三件:

  1. 初始化最基本的硬件——比如关闭中断、设置栈指针。
  2. 验证BL2镜像的签名和完整性(如果开启了安全启动)。
  3. 加载BL2到SRAM,然后跳转过去。

代码位置在bl1/目录。你打开看看,会发现代码量其实不大,因为功能太受限了。我记得有一次调试,发现BL1死活跳不到BL2,最后定位是栈指针没对齐——就少了个#define宏,折腾了我一整天。

小技巧:调试BL1时,可以用JTAG直接单步跟踪。但要注意,很多SoC的ROM区域是只读的,你没法改BL1代码,只能通过修改平台宏来调整它的行为。

3.3 BL2:承上启下的“大管家”

BL2是第二个阶段,运行在SRAM里。它比BL1自由多了,因为这时候基本的硬件环境已经搭好。BL2的主要工作是加载和验证后续阶段的镜像。

具体来说:

  • 它要读取Flash里的BL31、BL32(如果有)、BL33镜像。
  • 验证每个镜像的签名(安全启动流程)。
  • 把它们放到正确的位置(内存地址)。
  • 最后跳转到BL31。

代码在bl2/目录。你会发现里面有个bl2_main.c,这是入口函数。我建议你从这里开始读代码,逻辑很清晰。

嗯,这里要注意一点。BL2的代码大小是有限制的,因为它也跑在SRAM里。SRAM通常不大,比如256KB或512KB。所以BL2不能太臃肿。我在项目中就遇到过BL2编译后超了SRAM大小,最后不得不裁剪了一些调试日志。

3.4 BL31:安全世界的“操作系统”

BL31是ATF的核心,它实现了ARM的运行时安全固件。说白了,它就是一个跑在安全世界的小型操作系统,负责处理安全监控调用(SMC)、电源管理(PSCI)、安全中断等。

BL31运行在DRAM里,所以代码可以大一些。它的主要模块包括:

模块 功能
PSCI 电源管理,比如CPU开关、挂起、唤醒
SMC处理 处理来自非安全世界的安全调用
中断管理 安全中断的注册和分发
上下文管理 保存和恢复CPU上下文,用于世界切换

代码在bl31/目录。我个人觉得BL31是ATF里最复杂的一块,因为它涉及安全世界和非安全世界的切换。你想想看,每次普通世界调用SMC指令,CPU都要切到安全世界,BL31得保存好普通世界的状态,处理完再切回去。这个过程稍有不慎,系统就崩了。

核心要点:BL31是安全世界的“内核”,它不跑Linux,也不跑RTOS,它只做一件事——安全服务。所以它的代码必须精简、可靠。

3.5 BL32:可选的“安全操作系统”

BL32是一个可选的阶段。它运行一个真正的安全操作系统,比如OP-TEE(开源安全扩展)。如果你不需要TEE(可信执行环境),可以完全跳过BL32,直接从BL31跳到BL33。

BL32的作用是提供更丰富的安全服务,比如:

  • 安全存储(密钥、证书)
  • 加密运算加速
  • 可信用户界面

代码在bl32/目录。但说实话,这个目录里通常只放一些与ATF交互的粘合代码,真正的TEE操作系统源码在另一个仓库里。ATF这边只需要知道怎么加载和跳转到BL32就行。

我曾经在一个项目里集成OP-TEE,折腾了两周才跑通。最大的坑是内存布局——BL32需要一块安全DRAM区域,这块区域不能被普通世界访问。如果你在设备树里没配好,BL32一启动就挂。

3.6 BL33:最后的“跳板”

BL33就是最终的非安全世界引导程序。在大多数平台上,它就是U-Boot或者UEFI。ATF的任务就是把控制权交给BL33,之后就不再干预了。

BL33的加载由BL2完成,跳转由BL31完成。流程是这样的:

  1. BL2把BL33镜像加载到指定内存地址。
  2. BL31完成安全世界初始化后,执行一条ERET指令,切换到非安全世界。
  3. CPU开始执行BL33代码,也就是U-Boot开始跑。

代码不在ATF仓库里,BL33是外部项目。ATF这边只需要在平台代码里定义BL33的加载地址和入口地址就行。

3.7 各阶段的关系图

说了这么多,我画张图帮你理一理。这张SVG图展示了从BL1到BL33的完整启动流程:

ATF启动流程:BL1 → BL33 BL1 ROM代码,不可修改 加载+验证 BL2 SRAM,加载后续镜像 跳转 BL31 安全运行时固件 可选 BL32 安全OS(如OP-TEE) 返回 ERET切换 BL33 U-Boot / UEFI 引导 Linux 内核 图例 BL1 - 只读ROM BL2 - SRAM BL31 - 安全固件 BL33 - 非安全引导

从这张图你能看到,整个启动过程是链式的。每个阶段只做自己分内的事,然后交给下一个阶段。这种设计的好处是职责单一、易于验证。坏处嘛,就是启动时间会稍微长一点——每个阶段都要做初始化、验证、加载。

3.8 各阶段的内存布局

最后,我列个表总结一下各阶段的内存位置和特点。这对你移植时配置内存非常有用:

阶段 运行位置 是否可修改 主要职责
BL1 ROM(或片上SRAM) 否(出厂固化) 最小硬件初始化,加载BL2
BL2 SRAM 是(平台代码) 加载并验证BL31/BL32/BL33
BL31 DRAM(安全区域) 是(平台代码) 安全监控、电源管理、SMC处理
BL32 DRAM(安全区域) 是(可选) 运行安全操作系统
BL33 DRAM(非安全区域) 是(外部项目) 引导最终操作系统

这里有个关键点你要记住:BL1和BL2跑在SRAM里,因为这时候DRAM还没初始化。BL31、BL32、BL33都跑在DRAM里,但安全和非安全区域要严格隔离。我在项目中见过最典型的错误,就是BL31和BL33的内存区域重叠了,结果BL31一跑就把BL33的代码踩了,系统直接死机。