3、ATF基础架构:BL1/BL2/BL31/BL32/BL33阶段划分,SMC调用与安全监控模式

ARM Trusted Firmware,圈内人都叫它ATF。这东西说白了就是ARMv8-A架构下安全世界的“地基”。我刚开始接触它的时候,也被那一堆BL给绕晕过。BL1、BL2、BL31、BL32、BL33……这到底谁先谁后?各自干什么?

今天我们就把它彻底理清楚。你想想看,一个系统从上电到进入操作系统,中间要经历多少道关卡?ATF就是负责把这些关卡的安全门都管好。

3.1 启动流程总览:五阶段接力赛

ATF的启动是一个典型的“接力棒”模型。每个BL阶段完成自己的初始化后,把控制权交给下一个阶段。我习惯把这五个阶段画成一张图,这样一看就明白。

核心概念:ATF的启动顺序是 BL1 → BL2 → BL31/BL32 → BL33。BL31和BL32是并行加载的,但BL31先运行。

ATF五阶段启动流程 BL1 ROM Bootloader BL2 Trusted Boot BL31 EL3 Runtime BL32 Secure OS BL33 UEFI / OS 说明: BL1在ROM中,不可修改。BL2在Flash中,负责验证后续镜像。 BL31是EL3的“大管家”,BL32是可选的安全操作系统,BL33就是UEFI或内核。

3.2 各阶段深度解析

3.2.1 BL1:第一级启动,ROM里的“铁钉子”

BL1是芯片出厂时固化在ROM里的代码。它不可修改,不可擦除。我个人觉得,BL1的设计哲学就是“最小可信基”。它只做两件事:初始化最基本的硬件(比如时钟、SRAM),然后加载并验证BL2。

为什么BL1要放在ROM里?因为ROM是只读的,攻击者改不了。这是整个信任链的根。我记得在某个项目中,芯片的ROM有bug,导致BL1在特定温度下读取Flash会出错。那叫一个头疼——ROM没法打补丁,最后只能靠BL2做workaround。

避坑指南:BL1的代码量通常控制在4KB-16KB以内。空间极其有限,别想着在BL1里做复杂的事情。我曾经见过有人试图在BL1里加USB驱动,结果ROM装不下,整个项目推倒重来。

3.2.2 BL2:可信启动的“守门员”

BL2是存放在Flash中的。它负责加载并验证BL31、BL32、BL33的镜像。验证方式通常是数字签名校验——用公钥验签,确保镜像没有被篡改过。

BL2运行在EL3(安全最高权限级别)。它做完验证后,会把控制权交给BL31。这里有个细节:BL2在交出控制权之前,会把自己占用的内存清理干净,防止敏感信息泄露。

阶段 存储介质 异常级别 主要职责
BL1 ROM EL3 最小硬件初始化,加载BL2
BL2 Flash EL3 加载并验证后续所有镜像
BL31 SRAM/DRAM EL3 运行时服务,SMC处理,PSCI
BL32 SRAM/DRAM S-EL1 安全操作系统(如OP-TEE)
BL33 DRAM EL2/EL1 UEFI或操作系统内核

3.2.3 BL31:EL3的“大管家”

BL31是ATF的核心。它运行在EL3,负责处理安全监控模式下的各种请求。说白了,BL31就是安全世界和普通世界之间的“交换机”。

BL31提供的主要服务包括:

  • PSCI(电源状态协调接口):CPU的休眠、唤醒、开关机都由它管。
  • SMC处理:接收来自普通世界的安全调用请求。
  • 中断路由:决定一个中断是交给安全世界处理,还是交给普通世界。

BL31启动后,会初始化中断控制器(GIC)、系统计数器等。然后它进入一个等待循环,随时准备处理SMC调用或中断。

3.2.4 BL32:安全操作系统(可选)

BL32是一个可选的安全操作系统。最常见的是OP-TEE(Open Portable Trusted Execution Environment)。它运行在S-EL1,提供安全服务,比如密钥管理、安全存储、加密运算等。

为什么需要BL32?因为BL31虽然权限高,但功能太单一。如果你需要运行复杂的可信应用(TA),就需要一个真正的操作系统来管理。BL32就是干这个的。

注意:BL32不是必须的。如果你的系统不需要安全操作系统,可以只跑BL31+BL33。但这样做的话,所有安全服务都得在BL31里实现,代码会变得很臃肿。我个人建议,只要硬件支持,就加上BL32。

3.2.5 BL33:最终启动目标

BL33就是UEFI或者操作系统内核。在UEFI+ATF的协同场景下,BL33通常是UEFI固件。UEFI运行在EL2(虚拟化扩展)或EL1,通过SMC调用向BL31请求安全服务。

3.3 SMC调用:安全世界的“门铃”

SMC(Secure Monitor Call)是普通世界进入安全世界的唯一入口。你想想看,普通世界的代码权限低,不能直接访问安全资源。那它怎么让安全世界帮忙做事?按门铃——发一个SMC异常。

SMC调用的流程是这样的:

  1. 普通世界执行 SMC #0 指令。
  2. CPU陷入EL3,BL31的SMC处理函数被调用。
  3. BL31解析SMC参数,判断是哪个服务(PSCI、TOS、自定义)。
  4. BL31执行对应的处理逻辑,或者转发给BL32。
  5. 结果通过寄存器返回给普通世界。

这里有个关键点:SMC调用是有标准规范的。ARM定义了SMC Calling Convention(SMCCC),规定了参数怎么传、返回值怎么取。我建议你直接看SMCCC文档,别自己瞎猜。

// 一个典型的SMC调用示例(UEFI侧)
// 调用PSCI_CPU_ON,唤醒CPU1
UINT64 FunctionId = 0xC4000003;  // PSCI_CPU_ON的SMC ID
UINT64 CpuId = 1;
UINT64 EntryPoint = 0x80000000;
UINT64 ContextId = 0;

// 执行SMC指令
// 在UEFI中通常通过封装好的函数调用
Status = ArmCallSmc(FunctionId, CpuId, EntryPoint, ContextId, NULL);

关键点:SMC调用的开销大约是几百个CPU周期。虽然比普通函数调用慢,但比中断快得多。在性能敏感的场景下,尽量减少SMC调用次数。我习惯的做法是批量处理——一次SMC调用完成多个操作。

3.4 安全监控模式:EL3的“上帝视角”

安全监控模式(Secure Monitor Mode)是ARMv8-A引入的概念。它对应EL3异常级别。在EL3下,代码可以访问所有系统资源——安全世界的、普通世界的,通通都能看。

这种“上帝视角”有什么用?

  • 上下文切换:当普通世界和安全世界切换时,EL3负责保存和恢复寄存器状态。
  • 中断路由:EL3可以配置GIC,决定一个中断是触发安全中断还是普通中断。
  • 系统控制:比如电源管理、复位控制等,这些操作必须在最高权限下执行。

我记得有一次调试一个中断路由问题。安全世界的定时器中断老是跑到普通世界去。查了半天,发现是GIC的配置寄存器在EL3下没设对。嗯,这种问题在EL3下调试特别麻烦——因为一旦EL3崩了,整个系统就挂了,连个日志都打不出来。

调试技巧:在BL31中加一个简单的UART输出函数。我习惯在EL3的SMC处理入口和出口各打一行日志。这样能快速定位是哪个SMC调用出了问题。别指望JTAG,很多量产板子没有JTAG接口。

3.5 各阶段的内存布局

内存怎么分?这是个大学问。ATF各阶段对内存的需求不同,而且必须互不重叠。我见过太多因为内存布局冲突导致的诡异bug。

阶段 典型内存区域 大小 说明
BL1 ROM + 内部SRAM 4KB - 16KB BL1代码在ROM中,数据在SRAM中
BL2 内部SRAM 64KB - 256KB BL2运行期间独占
BL31 内部SRAM 或 保留DRAM 64KB - 512KB 常驻内存,不能被OS覆盖
BL32 安全DRAM 4MB - 32MB 安全OS和TA使用
BL33 普通DRAM 剩余所有 UEFI或OS使用

这里有个容易踩的坑:BL31和BL32的内存必须从普通世界的视角“隐藏”起来。怎么做?通过内存控制器或系统MMU的配置,让普通世界根本看不到这些区域。否则,一个恶意程序就能直接读取安全世界的数据。

严重警告:千万不要把BL31或BL32的内存区域映射到UEFI的页表里。我曾经见过一个项目,UEFI的页表不小心包含了安全内存区域,结果一个DMA操作直接把安全OS的数据给覆盖了。系统跑着跑着就神秘重启,查了整整两周才找到原因。

3.6 小结

ATF的五阶段架构,说白了就是一条信任链。从ROM里的BL1开始,一级一级验证,一级一级传递信任。BL31是枢纽,SMC是通道,安全监控模式是保障。

理解这些基础,你才能知道UEFI和ATF到底怎么配合。下一层我们就要开始讲具体的协同启动了。