ATF源码结构分析:BL1/BL2/BL31/BL32/BL33各阶段作用

ATF(ARM Trusted Firmware)的源码结构,说白了就是一套分级启动的骨架。我刚接触ATF时,面对一堆BL开头的目录也懵过——BL1、BL2、BL31……这些到底谁先跑?谁管谁?

今天咱们就把这五个阶段拆开揉碎,讲清楚每个BL的职责、关键文件,以及Makefile是怎么把它们串起来的。

1. 整体启动流程:一张图看懂

先上我手绘的SVG框架图,把BL1到BL33的调用关系画清楚。你盯着这张图看三分钟,后面讲细节就不会迷路。

ATF 分级启动流程(BL1 → BL33) BL1 (AP Boot ROM) 加载BL2镜像 BL2 (Trusted Boot Firmware) 加载BL31/32/33镜像 BL31 (EL3 Runtime) BL32 (Secure OS) BL33 (UEFI / Uboot) 关键说明: • BL1 是片上ROM,不可修改;BL2 是可信启动固件,加载后续镜像 • BL31 提供EL3运行时服务(PSCI、SMC处理);BL32 是可选的安全OS • BL33 是非安全侧启动加载器(UEFI/Uboot),最终引导OS • 箭头方向表示镜像加载与跳转顺序,BL31 负责协调BL32/BL33

2. 各BL阶段详解

2.1 BL1 — 启动的起点

BL1是固化在芯片ROM里的代码,上电后CPU第一条指令就落在它身上。它做的事情很纯粹:初始化最基本的硬件(比如串口、DRAM控制器),然后从Flash里找到BL2镜像,加载到SRAM,最后跳过去。

关键目录与文件:

  • bl1/bl1_main.c — BL1主逻辑,包括异常向量表、平台初始化
  • bl1/bl1_entrypoint.S — 汇编入口,设置栈指针、跳转到C代码
  • plat/<平台>/bl1_plat_setup.c — 平台相关的初始化(比如时钟、电源)
我的经验:BL1调试最痛苦——它跑在ROM里,没法打断点。我习惯在串口初始化后立刻打印一个字符,确认代码活着。另外,BL1的镜像大小通常被硬件限制在几十KB,加功能时一定要算好空间。

2.2 BL2 — 可信启动的管家

BL2是可信启动链的核心。它负责验证并加载BL31、BL32、BL33的镜像。说白了,它就是个安全加载器。BL2运行在EL3,但很快会把控制权交给BL31。

关键目录与文件:

  • bl2/bl2_main.c — BL2主流程,调用平台接口加载镜像
  • bl2/bl2_image_load.c — 镜像加载逻辑,支持FIP包解析
  • plat/<平台>/bl2_plat_setup.c — 平台内存布局、存储设备初始化
避坑指南:我曾经在BL2里加了一个调试打印,结果导致镜像校验失败——因为打印函数修改了某个全局变量,破坏了校验上下文。记住:BL2里不要做任何“有副作用”的调试操作。

2.3 BL31 — EL3的运行时守护者

BL31是ATF里最复杂的组件。它运行在EL3,提供运行时服务:PSCI(电源管理)、SMC(安全监控调用)处理、中断路由等。BL31一旦启动,就常驻内存,直到系统关机。

关键目录与文件:

  • bl31/bl31_main.c — BL31初始化入口,注册服务
  • bl31/runtime_svc.c — 运行时服务框架,处理SMC请求
  • services/std_svc/psci/ — PSCI实现,CPU开关、挂起/恢复
  • plat/<平台>/bl31_plat_setup.c — 平台中断控制器、GIC初始化
注意:BL31的栈空间非常有限(通常只有4KB)。我见过同事在BL31里递归调用PSCI,直接栈溢出导致系统挂死。写BL31代码时,务必控制栈使用。

2.4 BL32 — 安全世界的可选组件

BL32是可选的安全OS,比如OP-TEE、Trusty。它运行在S-EL1,提供安全服务(密钥管理、安全存储)。如果不需要安全OS,BL32可以跳过,BL31直接加载BL33。

关键目录与文件:

  • bl32/bl32_main.c — BL32入口,初始化安全OS
  • plat/<平台>/bl32_plat_setup.c — 安全内存、安全外设配置

2.5 BL33 — 非安全世界的启动加载器

BL33就是UEFI、Uboot或任何非安全侧启动加载器。它运行在EL2或EL1,负责加载操作系统。ATF对BL33的干预很少——跳过去后,BL33就独立运行了。

关键目录与文件:

  • bl33/bl33_main.c — BL33入口,设置EL2/EL1上下文
  • plat/<平台>/bl33_plat_setup.c — 非安全内存、设备树传递

3. 关键目录与文件说明

ATF源码的目录结构很清晰,我整理了一张表,方便你对照查找:

目录/文件 作用 所属阶段
bl1/ BL1源码,包括入口、主逻辑、平台适配 BL1
bl2/ BL2源码,镜像加载、校验、FIP解析 BL2
bl31/ BL31源码,运行时服务、PSCI、SMC处理 BL31
bl32/ BL32源码,安全OS入口 BL32
bl33/ BL33入口,非安全侧启动加载器适配 BL33
plat/ 平台相关代码,每个平台一个子目录 所有阶段
include/ 公共头文件,架构定义、接口声明 所有阶段
Makefile 顶层构建文件,控制整个编译流程 构建系统

4. Makefile构建系统解析

ATF的Makefile体系,说实话,刚看时有点绕。它不像普通项目一个Makefile搞定,而是分层嵌套的。顶层Makefile调用各BL的Makefile,最终生成多个独立的镜像文件。

核心构建流程:

  1. 顶层Makefile解析配置(平台、调试等级、安全OS选择)
  2. 根据配置生成build/<平台>/目录
  3. 依次编译BL1、BL2、BL31、BL32、BL33
  4. 调用fiptool打包成FIP镜像

常用构建命令示例:

# 编译QEMU平台,debug模式
make PLAT=qemu DEBUG=1 all

# 编译FVP平台,带OP-TEE
make PLAT=fvp SPD=opteed all

# 只编译BL31
make PLAT=qemu BL31=1

# 清理
make PLAT=qemu clean
关键变量说明:
  • PLAT — 目标平台,比如qemu、fvp、zynqmp
  • DEBUG — 0为release,1为debug(带符号表)
  • SPD — 安全调度器,选择BL32(如opteed、trusty)
  • LOG_LEVEL — 日志等级,40为INFO,50为VERBOSE
我曾经踩过的坑:有一次我改了BL2的代码,但编译时只跑了make PLAT=qemu,结果发现改动没生效——因为Makefile检测到BL2的.o文件没变化,跳过了重新编译。后来我养成习惯:改完代码先make clean再全量编译,或者用make PLAT=qemu BL2=1强制重编BL2。

5. 小结

ATF的源码结构,说白了就是五个BL + 一个Makefile。BL1是ROM里的硬汉,BL2是安全管家,BL31是EL3的常驻守护者,BL32是可选的秘密花园,BL33是通往OS的大门。Makefile则像一根线,把它们串成完整的启动链。

我个人建议:刚开始学ATF时,先盯着BL31看——它最复杂,也最核心。把PSCI和SMC处理搞明白,其他BL自然就通了。嗯,今天就到这儿,下回咱们聊BL31的PSCI实现细节。