4、ATF构建与集成:ARM Trusted Firmware的编译、配置与集成到Buildroot

好,咱们进入第四章。这一章要啃的,是整个启动链里最核心的一块——ARM Trusted Firmware,简称ATF。说白了,它就是负责把CPU从最原始的裸机状态,一步步带到能跑操作系统的那段“引导代码”。在ARMv8架构里,它运行在EL3异常级别,是整个系统的信任根。

我记得刚接触ATF那会儿,看着那一堆编译选项和平台宏,头都大了。后来在项目里踩过几次坑,才慢慢摸清楚它的脾气。今天我就把ATF的编译、配置,以及怎么把它优雅地塞进Buildroot这套流程,给你掰扯清楚。

4.1 ATF在启动链中的位置与作用

先看一张图,你就明白ATF在整个启动流程里到底站在哪。

ARM Trusted Firmware 在启动链中的位置 Boot ROM BL1 (AP_BL1) BL2 (AP_BL2) BL31 (EL3) BL32 (TEE) BL33 (U-Boot) 说明: • BL1/BL2/BL31 由 ATF 源码编译生成,BL32 可选(OP-TEE),BL33 通常是 U-Boot。 • 整个流程从 Boot ROM 开始,逐级验证并加载下一级镜像,形成信任链。

你看,ATF主要产出三个镜像:BL1、BL2、BL31。BL1是第一个被CPU执行的代码,它初始化最基本的硬件,然后加载BL2。BL2负责加载BL31和后面的BL32、BL33。BL31运行在EL3,提供运行时服务,比如PSCI(电源管理)和SMC(安全监控调用)。

核心要点:ATF不只是一个引导加载器,它更是一个安全固件框架。它定义了从EL3到EL0的完整异常级别切换规范。你想想看,没有它,操作系统根本没法安全地管理电源和休眠。

4.2 ATF源码结构与编译配置

ATF的源码托管在 https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git。它的目录结构很清晰,我挑几个关键目录说说:

  • plat/:平台相关代码。每个SoC厂商在这里放自己的板级支持包。比如 plat/rockchip、plat/allwinner。
  • bl1/、bl2/、bl31/:对应各个启动阶段的源码。
  • drivers/:驱动代码,比如GIC、UART、定时器。
  • include/:头文件,定义了各种宏和接口。
  • tools/:辅助工具,比如 fiptool 用来打包镜像。

编译ATF,本质上就是调用 make,传入一堆参数。我个人习惯写一个shell脚本,把参数都列清楚,免得每次敲错。

# 以 Rockchip RK3588 为例
make CROSS_COMPILE=aarch64-linux-gnu- \
     PLAT=rk3588 \
     DEBUG=0 \
     LOG_LEVEL=40 \
     BL33=../u-boot/u-boot.bin \
     all fip

这里每个参数都有讲究:

参数 说明 我的建议
CROSS_COMPILE 交叉编译工具链前缀 必须与Buildroot工具链一致
PLAT 目标平台名称 在 plat/ 目录下找对应名字
DEBUG 0=release, 1=debug 调试阶段用1,量产用0
LOG_LEVEL 日志等级,0~50 40够用,50太啰嗦
BL33 非安全侧镜像路径 通常指向U-Boot的二进制
all fip 编译目标,生成FIP包 FIP包含BL2、BL31、BL32、BL33

小技巧:如果你只想编译BL31,可以只执行 make bl31。这在调试阶段能省不少时间。我曾经在调PSCI时,每次只改BL31的代码,编译加烧写只要10秒,效率高很多。

4.3 将ATF集成到Buildroot

Buildroot里对ATF的支持已经相当成熟了。它提供了一个专门的包叫 arm-trusted-firmware。你只需要在 make menuconfig 里配置一下就行。

具体步骤:

  1. 进入配置菜单:make menuconfig
  2. 找到Bootloaders:Bootloaders --->
  3. 选中ATF:[*] arm-trusted-firmware
  4. 配置平台和选项:进入子菜单,填写平台名、BL33路径等。

配置界面大概长这样:

Bootloaders
  ─────────────────────────────────────────
  [*] arm-trusted-firmware
        Platform name (rk3588)  --->
        Custom repository version (v2.9)  --->
        [*] Build FIP image
        (../output/images/u-boot.bin) BL33 image path
        (--enable-pie) Additional ATF build options

这里有个坑,我必须要提醒你:BL33路径是相对于Buildroot输出目录的。如果你把U-Boot编译在外部,路径写错了,ATF编译会报错,说找不到BL33。我曾经因为这个折腾了一下午,最后发现是路径少了个 ../。

注意:ATF的版本要和你的SoC匹配。比如RK3588需要ATF v2.9及以上。如果版本不对,编译可能通过,但运行时BL2加载BL31会卡死。我建议你直接去SoC厂商的官方仓库拉ATF源码,别用上游主线,除非你很确定兼容性。

4.4 自定义ATF配置与调试

很多时候,默认配置不够用。比如你要调整BL2的加载地址,或者开启某个调试功能。这时候就需要修改ATF的配置文件了。

ATF的配置主要分布在两个地方:

  • 平台头文件:比如 plat/rockchip/rk3588/include/platform_def.h,定义了内存布局、外设基地址等。
  • Makefile:在 plat/rockchip/rk3588/platform.mk 里,可以添加编译选项。

举个例子,我想在BL31里开启串口调试输出:

# 在 platform.mk 中添加
BL31_SOURCES += drivers/ti/uart/uart_16550.c
$(eval $(call add_define,PLAT_LOG_LEVEL_ASSERT))

然后重新编译:

make PLAT=rk3588 DEBUG=1 LOG_LEVEL=50 bl31

这样BL31启动时就会打印详细的调试信息。嗯,这里要注意,LOG_LEVEL=50会输出大量日志,可能会影响启动速度,但调试时很有用。

4.5 验证ATF集成是否成功

编译完成后,你会得到几个关键文件:

文件 路径(在Buildroot输出目录) 说明
bl1.bin build/arm-trusted-firmware-xxx/build/rk3588/release/ BL1镜像,通常烧写到Boot ROM区域
fip.bin images/ FIP包,包含BL2、BL31、BL33
bl31.bin 同上 单独的BL31镜像,调试用

怎么验证它跑起来了?最简单的方法:接上串口,看启动日志。如果看到类似下面的输出,说明ATF工作正常:

NOTICE:  BL1: v2.9(release):v2.9-113-g1234567
NOTICE:  BL1: Built : 14:30:00, Jan 15 2025
INFO:    BL1: Booting BL2
INFO:    BL2: v2.9(release):v2.9-113-g1234567
INFO:    BL2: Built : 14:30:02, Jan 15 2025
INFO:    BL2: Loading BL31
INFO:    BL2: Loading BL33
NOTICE:  BL31: v2.9(release):v2.9-113-g1234567
NOTICE:  BL31: Built : 14:30:04, Jan 15 2025

如果卡在某个阶段,比如没有打印 Booting BL2,那多半是BL1加载BL2时出了问题。检查一下FIP包里的BL2地址是否正确,或者串口波特率是否匹配。

总结一下:ATF的集成,说白了就是三步:选对平台、配好参数、编译打包。但每一步都有细节,尤其是BL33路径和版本匹配。我建议你在Buildroot里第一次集成时,先用官方默认配置跑通,再慢慢加自己的修改。这样出了问题,也知道是哪里改坏了。