3. ATF源码结构解析:从仓库到启动的完整旅程
说实话,ATF(ARM Trusted Firmware)的源码结构,我第一次看的时候也有点懵。几十个目录,一堆缩写,BL1、BL2、BL31……到底谁先跑?谁负责什么?
别急,今天我就带你把这层窗户纸捅破。我个人习惯把ATF的源码结构分成三块来看:仓库布局、启动阶段、编译系统。搞懂这三块,你就能在源码里自由穿行了。
3.1 ATF仓库结构:别被目录吓到
ATF的代码托管在 trustedfirmware-a 仓库里。你 git clone 下来后,会看到这样的顶层目录:
.
├── bl1/ # 安全世界的第一个启动阶段
├── bl2/ # 安全世界的第二个启动阶段
├── bl31/ # 安全世界的运行时阶段(EL3)
├── bl32/ # 安全世界的TEE阶段(可选)
├── bl33/ # 非安全世界的启动阶段(通常是U-Boot/EDK2)
├── common/ # 各阶段共享的公共代码
├── drivers/ # 外设驱动(GIC、UART、SPI等)
├── include/ # 头文件
├── lib/ # 核心库(如C库、CPU操作库)
├── plat/ # 平台相关代码(板级支持包)
├── services/ # 运行时服务(如PSCI、SDEI)
├── tools/ # 辅助工具(如证书生成、FIP打包)
├── Makefile # 顶层构建文件
└── docs/ # 文档
嗯,这里要注意:plat/ 目录是你最需要关注的。因为不同芯片厂商的移植工作,90% 都在这里完成。我在项目中遇到过,有人把 plat/ 里的代码改错了,结果BL1跑完直接死机——连个日志都看不到。
bl*/ 是启动流程,plat/ 是硬件适配,lib/ 是通用逻辑。改代码前,先搞清楚你在改哪一层。
3.2 BL1/BL2/BL31/BL32/BL33:五阶段启动,各司其职
ATF的启动过程,说白了就是一场接力赛。每个阶段跑完,把接力棒交给下一个。我画了一张图,帮你直观理解:
每个阶段的具体职责,我用表格给你总结一下:
| 阶段 | 运行位置 | 异常等级 | 核心职责 | 典型大小 |
|---|---|---|---|---|
| BL1 | 片上ROM | EL3 | 初始化最小硬件、加载BL2到SRAM | ~32KB |
| BL2 | SRAM | S-EL1 | 验证并加载BL31/BL32/BL33镜像 | ~64KB |
| BL31 | DRAM | EL3 | 提供运行时服务(PSCI、SMC处理) | ~128KB |
| BL32 | DRAM | S-EL1 | 运行TEE OS(如OP-TEE) | ~1MB+ |
| BL33 | DRAM | EL2/EL1 | 启动非安全OS(U-Boot、Linux) | ~2MB+ |
3.3 各阶段详解:从ROM到OS
BL1——一切从这里开始
BL1是上电后第一个执行的代码,固化在芯片的ROM里。它的任务很简单:初始化CPU、设置异常向量表、配置少量外设(比如UART),然后从Flash里加载BL2到SRAM。
你想想看,BL1只有32KB的空间,能做的事情非常有限。我见过有些芯片厂商为了省成本,BL1连UART都不初始化——结果板子变砖了都不知道原因。
BL2——可信启动的守门员
BL2运行在SRAM里,它的核心工作是验证。验证BL31、BL32、BL33的签名和哈希,确保它们没有被篡改。这就是所谓的「信任链」——BL1信任BL2,BL2信任后面的所有镜像。
为什么需要BL2?因为BL1太小了,做不了复杂的验证。BL2有64KB的空间,可以跑完整的证书链验证。我在项目中遇到过,BL2验证失败导致启动卡住——后来发现是证书过期了。嗯,证书管理也是个坑。
BL31——EL3的大管家
BL31是ATF里最复杂的阶段。它运行在EL3,负责处理安全监控调用(SMC)、电源管理(PSCI)、系统复位等。说白了,它就是安全世界和非安全世界之间的「看门人」。
BL31一旦跑起来,就不会退出。它会一直驻留在内存里,等待来自BL32或BL33的SMC请求。我个人习惯把BL31叫做「EL3的常驻服务进程」。
BL32——TEE OS的舞台
BL32是可选的。如果你不需要TEE(可信执行环境),可以跳过它。但如果你要做安全支付、DRM、指纹认证,那BL32就是核心。
BL32通常运行OP-TEE或Trusty这样的TEE OS。它和BL31通过SMC通信,和BL33通过共享内存交换数据。我曾经调试过一个bug,BL32发给BL33的数据总是乱码——最后发现是缓存一致性问题。你想想看,两个世界共享内存,缓存不刷新的后果就是数据错乱。
BL33——非安全世界的入口
BL33就是U-Boot、EDK2或者直接是Linux内核。它运行在非安全世界,负责启动你的主操作系统。
到这里,ATF的使命就完成了。它把控制权交给了BL33,然后自己退居幕后,只通过SMC提供服务。
3.4 编译系统与构建流程
ATF使用Makefile构建系统。说实话,它的Makefile写得挺优雅的,但第一次看的人可能会晕。核心命令就一个:
make CROSS_COMPILE=aarch64-linux-gnu- PLAT=qemu DEBUG=1 bl31
这条命令的意思是:用aarch64交叉编译器,编译qemu平台,开启调试模式,只生成BL31镜像。
构建流程大致如下:
- 解析Makefile:加载平台配置、工具链、编译选项
- 编译源码:按阶段编译bl1/、bl2/、bl31/等目录下的C和汇编文件
- 链接:每个阶段生成独立的ELF文件
- 生成镜像:用工具把ELF转换成二进制镜像(.bin)
- 打包FIP:用
fiptool把多个镜像打包成一个FIP文件
PLAT=,结果ATF用默认的 fvp 平台编译,烧到我的开发板上直接不跑。记住:PLAT 必须和你板子匹配,否则就是白忙活。
常用的编译目标有:
| 目标 | 说明 |
|---|---|
all | 编译所有阶段(BL1+BL2+BL31) |
bl1 | 只编译BL1 |
bl31 | 只编译BL31 |
fip | 生成FIP镜像包 |
clean | 清理编译产物 |
编译完成后,你会在 build/<platform>/release/ 或 build/<platform>/debug/ 目录下看到生成的镜像文件。我个人习惯先编译debug版本,因为调试信息能帮我快速定位问题。