3、ATF源码结构:ATF仓库结构、BL1/BL2/BL31/BL32/BL33概念、平台移植框架、构建系统

好,咱们进入正题。这一章我打算把ATF的源码结构彻底讲透。很多初学者一打开ATF仓库就懵了——文件夹一大堆,不知道从哪看起。我当年第一次接触时也差不多,对着docs/和plat/发了半天呆。

说白了,ATF的源码结构并不复杂。它遵循一套清晰的启动流程,每个阶段对应一个BL(Boot Loader)组件。你只要搞懂这几个BL是干嘛的,整个仓库的脉络就清楚了。

3.1 ATF仓库结构概览

先看看ATF仓库的顶层目录。我习惯用tree -L 1扫一眼,大概长这样:

.
├── bl1/          # 第一级Boot Loader
├── bl2/          # 第二级Boot Loader
├── bl31/         # EL3 Runtime Firmware
├── bl32/         # TEE相关(可选)
├── bl33/         # 非安全世界的Boot Loader(如U-Boot)
├── common/       # 通用代码
├── plat/         # 平台移植代码
├── drivers/      # 驱动
├── include/      # 头文件
├── lib/          # 库函数
├── make_helpers/ # 构建辅助脚本
├── docs/         # 文档
└── tools/        # 工具

嗯,这里要注意:bl32/目录并不是每个平台都有。它只在需要TEE(Trusted Execution Environment)支持时才存在。我在项目中遇到过,有些客户只做安全启动,不需要TEE,那bl32/就直接删掉了。

3.2 BL1/BL2/BL31/BL32/BL33概念

这几个BL是ATF启动流程的核心。我画了一张图,帮你理清它们的关系:

ATF启动流程与BL组件关系 BL1 EL3, ROM 加载 BL2 EL3, SRAM 加载 BL31 EL3, Runtime 可选 BL32 (TEE) S-EL1, TEE OS 加载 BL33 EL2/EL1, OS 安全世界 (Secure World) 非安全世界 (Normal World)

这张图我画了好几次才满意。它清晰地展示了启动顺序:BL1 → BL2 → BL31 → (BL32) → BL33。每个阶段都有明确的职责和运行特权级。

3.2.1 BL1 - 第一级Boot Loader

BL1是上电后第一个执行的代码。它通常存放在ROM里,不可修改。它的任务很简单:

  • 初始化最基本的硬件(比如时钟、内存控制器)
  • 验证BL2的签名(安全启动)
  • 加载BL2到SRAM并跳转

我在项目中遇到过一个问题:BL1的ROM空间非常有限,通常只有几十KB。有一次我试图在BL1里加一个完整的UART驱动,结果编译后超了ROM大小。后来只能精简到最简轮询模式,才勉强塞进去。

注意:BL1是只读的,一旦芯片流片就无法修改。所以BL1的代码必须经过严格测试,否则就是硬件改版,成本极高。

3.2.2 BL2 - 第二级Boot Loader

BL2运行在SRAM里,比BL1灵活得多。它的主要工作是:

  • 加载并验证后续的BL31、BL32、BL33镜像
  • 配置内存控制器(DDR初始化)
  • 传递平台参数给后续阶段

你想想看,BL2其实就像一个「搬运工」。它把各个BL镜像从存储设备(比如eMMC、NAND Flash)搬到DDR里,然后跳转过去。我建议你在调试时,可以在BL2里加一些打印,确认每个镜像的加载地址是否正确。

3.2.3 BL31 - EL3 Runtime Firmware

BL31是ATF的核心。它运行在EL3,提供运行时服务:

  • 处理SMC(Secure Monitor Call)异常
  • 管理安全世界和非安全世界的切换
  • 提供PSCI(电源管理接口)

说白了,BL31就是整个系统的「看门人」。所有从非安全世界到安全世界的调用,都要经过BL31。我记得有一次调试一个TEE相关的bug,发现SMC调用总是返回错误。最后定位到是BL31里没有注册对应的服务ID。嗯,这种问题排查起来很费时间。

3.2.4 BL32 - TEE(可选)

BL32是TEE OS的镜像,比如OP-TEE、Trusty等。它运行在S-EL1,提供安全服务。不是所有平台都需要BL32——如果你的系统不需要TEE,可以跳过这个阶段。

我个人习惯在项目初期先不集成BL32,等BL31和BL33调通后再加。这样能减少调试的复杂度。

3.2.5 BL33 - 非安全世界的Boot Loader

BL33就是非安全世界的启动引导程序,通常是U-Boot或UEFI。它运行在EL2(虚拟化)或EL1(非虚拟化)。ATF把控制权交给BL33后,就进入非安全世界了。

3.3 平台移植框架

ATF的移植性做得很好。你只需要关注plat/目录下的代码。每个平台都有一个独立的文件夹,比如:

plat/
├── arm/          # ARM官方开发板
│   ├── juno/
│   ├── fvp/
│   └── ...
├── hisilicon/    # 海思平台
├── mediatek/     # 联发科平台
├── nvidia/       # 英伟达平台
├── qemu/         # QEMU模拟器
└── ...

移植一个新平台,你需要实现以下接口:

接口 说明 所在文件
plat_get_my_entrypoint() 获取当前CPU的入口地址 plat/common/plat_psci_common.c
plat_my_core_pos() 获取当前CPU的ID plat/common/plat_psci_common.c
plat_get_syscnt_freq() 获取系统计数器频率 plat/common/plat_psci_common.c
plat_get_console() 获取控制台驱动 plat/common/plat_console.c
platform_setup() 平台初始化 plat/common/plat_setup.c

我曾经移植ATF到一个国产芯片上,最头疼的就是时钟配置。芯片手册写得不清楚,我反复试了好几种配置才让系统计数器跑起来。后来我总结了一个经验:先拿QEMU验证逻辑,再上真硬件。

3.4 构建系统

ATF使用Makefile构建。它的构建系统设计得很巧妙,支持多平台、多配置。我常用的构建命令是:

make PLAT=qemu DEBUG=1 bl31

这条命令会编译QEMU平台的BL31镜像,并开启调试模式。常用的构建变量有:

  • PLAT:目标平台,比如qemu、juno、fvp
  • DEBUG:是否开启调试(0或1)
  • LOG_LEVEL:日志级别,从0(无日志)到50(最详细)
  • BL32:指定BL32镜像路径(如果使用TEE)
  • BL33:指定BL33镜像路径(如U-Boot)
小技巧:调试时建议设置LOG_LEVEL=40,能看到详细的启动流程。但正式发布时记得改回LOG_LEVEL=20,否则日志太多会影响性能。

构建系统还支持make_helpers/目录下的辅助脚本。比如checkpatch.pl可以检查代码风格是否符合ATF规范。我每次提交代码前都会跑一遍,省得review时被怼。

嗯,这一章的内容就到这里。ATF的源码结构其实不复杂,关键是要理解每个BL的职责和它们之间的协作关系。下一章我们会深入BL1的启动细节,看看芯片上电后到底发生了什么。

核心要点回顾:

  • ATF仓库按BL组件划分目录,bl1/到bl33/对应启动阶段
  • 启动顺序:BL1 → BL2 → BL31 → (BL32) → BL33
  • 平台移植主要修改plat/目录下的代码
  • 构建系统通过make PLAT=<platform>指定目标
可可精品资料库