4、ATF基础框架:BL1/BL2/BL31/BL32/BL33层级结构、PSCI接口、安全监控模式调用
好,咱们今天聊聊ATF的骨架。说白了,ATF就是ARM Trusted Firmware,它是整个安全世界的基石。我刚开始接触ATF时,第一反应是——这玩意儿层级怎么这么多?BL1、BL2、BL31……光记名字就够呛。但后来我发现,搞懂这个层级结构,你就抓住了ATF的命门。
4.1 五级Boot流程:从BL1到BL33
ATF把启动过程分成了五个阶段。每个阶段都有自己的使命。我习惯把这五个阶段比作接力赛——每一棒都有明确的任务,交接不好就会出问题。
| 阶段 | 名称 | 主要职责 | 运行权限 |
|---|---|---|---|
| BL1 | AP Trusted ROM | 最初始的启动代码,验证BL2 | EL3 |
| BL2 | Trusted Boot Firmware | 加载并验证后续镜像 | EL3 |
| BL31 | EL3 Runtime Firmware | 运行时服务,PSCI,中断管理 | EL3 |
| BL32 | Secure OS | 安全操作系统(如OP-TEE) | S-EL1 |
| BL33 | Non-secure Firmware | UEFI或uboot,最终启动OS | EL2/EL1 |
你看这个表格,BL1和BL2都是EL3权限,说白了就是最高权限。BL31也是EL3,但它是个常驻的运行时服务。BL32跑在安全世界,BL33跑在非安全世界。这个分界线很重要——安全和非安全,是ARMv8架构的核心思想。
核心要点:BL1是ROM里的代码,不可修改。BL2是Flash里的,可以更新。BL31是运行时,负责安全监控模式调用(SMC)。BL32是可选的,如果你不需要TEE,可以跳过。BL33就是你的UEFI或uboot。
4.2 BL1:一切从这里开始
BL1是上电后CPU执行的第一段代码。它通常存放在ROM里,所以叫"Trusted ROM"。我遇到过一个问题——有次客户说他们的板子起不来,我一看,BL1的签名验证没过。嗯,这里要注意:BL1的代码量非常小,因为它要保证绝对可靠。
BL1的主要工作:
- 初始化最基本的硬件(比如串口、时钟)
- 从Flash中加载BL2镜像
- 验证BL2的签名(如果使能了安全启动)
- 跳转到BL2
说白了,BL1就是个"启动加载器"的加载器。它自己不能太大,因为ROM空间有限。我记得有一次,我们为了把BL1压缩到64KB以内,砍掉了不少调试功能。
4.3 BL2:信任链的传递者
BL2是信任链的关键一环。它负责加载并验证后续所有镜像。我个人习惯把BL2看作"安全世界的管家"——它知道所有镜像的位置、大小、签名信息。
BL2做的事情:
- 解析FIP(Firmware Image Package)包
- 加载BL31、BL32(如果有)、BL33
- 验证每个镜像的签名
- 配置内存布局,为BL31做准备
- 跳转到BL31
小技巧:调试时可以在BL2里加打印,看看每个镜像的加载地址是否正确。我曾经因为FIP包里的地址偏移算错了,导致BL31加载到错误的位置,系统直接挂掉。打印一下加载地址,问题就暴露了。
4.4 BL31:EL3运行时服务
BL31是ATF的核心。它常驻在EL3,提供运行时服务。最重要的就是PSCI(Power State Coordination Interface)。你想想看,操作系统要关机、休眠、唤醒CPU,都得通过PSCI。而PSCI的实现就在BL31里。
BL31还负责安全监控模式调用(SMC)。当非安全世界(比如Linux)想调用安全服务时,就执行SMC指令,陷入EL3,由BL31处理。
// 一个典型的SMC调用流程
// 非安全世界发起SMC
smc #0
// 硬件自动陷入EL3
// BL31的异常向量表捕获到SMC
// 根据调用号分发到对应的服务
// PSCI调用示例:CPU_ON
uint64_t psci_cpu_on(uint64_t target_cpu, uint64_t entry_point, uint64_t context_id) {
// 1. 检查目标CPU是否合法
// 2. 设置CPU的启动地址
// 3. 发送唤醒信号
// 4. 返回成功/失败
return PSCI_E_SUCCESS;
}
这段代码看着简单,但实际实现要考虑很多。比如多核同步、缓存一致性、电源状态机。我调试PSCI时最头疼的就是CPU挂起后唤醒不回来——查了半天,发现是GIC配置没对。
4.5 BL32:安全操作系统
BL32是可选的。如果你需要可信执行环境(TEE),比如OP-TEE,那就需要BL32。它运行在安全世界的S-EL1,有自己的内存和中断。
BL32和BL31的交互通过SMC。BL31收到SMC后,如果发现是发给BL32的,就转发过去。这个转发过程叫"安全监控模式调用路由"。
注意:BL32的内存是安全内存,非安全世界无法访问。配置内存保护时一定要小心。我曾经把安全内存和非安全内存的地址搞混了,结果安全OS一启动就访问违例。嗯,从那以后我每次都会仔细检查内存地址映射表。
4.6 BL33:非安全世界的入口
BL33就是你的UEFI或uboot。它运行在非安全世界,权限较低(EL2或EL1)。BL33通过SMC调用BL31的服务,比如获取电源管理信息、设置安全配置。
我见过很多移植问题出在BL33和BL31的接口上。比如UEFI调用PSCI时,参数传递错了,导致系统无法关机。说白了,就是协议没对齐。
4.7 PSCI接口详解
PSCI是ARM定义的电源管理接口标准。它通过SMC调用实现。常用的PSCI函数:
- PSCI_VERSION:获取版本号
- CPU_ON:唤醒一个CPU
- CPU_OFF:关闭一个CPU
- CPU_SUSPEND:挂起一个CPU
- SYSTEM_OFF:关机
- SYSTEM_RESET:重启
每个PSCI函数都有固定的调用号和参数格式。BL31收到SMC后,根据调用号找到对应的处理函数。这个分发机制在BL31的SMC处理框架里实现。
4.8 安全监控模式调用(SMC)
SMC是ARMv8架构里最重要的指令之一。它让非安全世界可以安全地调用安全服务。调用流程是这样的:
- 非安全世界执行SMC指令
- 硬件自动陷入EL3
- BL31的异常向量表捕获到SMC
- BL31解析调用号,分发到对应的服务
- 服务处理完成后,通过ERET指令返回
这个过程中,BL31扮演了"安全网关"的角色。它检查调用是否合法,防止非安全世界滥用安全服务。
避坑指南:我曾经在移植时发现SMC调用总是返回错误。查了半天,原来是BL31的异常向量表配置错了。SMC的异常向量应该指向BL31的SMC处理入口,但我指向了普通中断处理。嗯,这种低级错误,犯过一次就不会再犯了。
4.9 层级结构图
下面我用一张SVG图来展示ATF的层级结构和调用关系。这张图我画了很多次,每次讲课时都会拿出来用。
这张图展示了三个世界:非安全世界、安全世界、EL3监控模式。箭头表示SMC调用和返回的路径。你想想看,所有跨世界的调用都必须经过EL3,这就是安全监控模式的核心思想。
4.10 移植要点总结
最后,我总结几个移植时容易踩的坑:
- BL1的ROM大小限制:ROM空间有限,别加太多调试代码
- BL2的FIP包解析:地址偏移算错会导致加载失败
- BL31的异常向量表:SMC向量必须正确配置
- PSCI的CPU状态机:挂起和唤醒的时序要严格对齐
- 安全内存保护:非安全世界不能访问安全内存
嗯,ATF的基础框架就这些。搞懂了BL1到BL33的层级关系,理解了PSCI和SMC的调用机制,你就能在移植时少走很多弯路。我当年第一次移植ATF时,光调试BL31就花了两周。现在回头看,其实就是对层级结构和调用流程理解不够深。希望今天的分享能帮你省下这两周的时间。
个人建议:刚开始移植时,先不要开安全启动。把BL1、BL2、BL31、BL33跑通再说。安全验证可以后面再加。我曾经一上来就开安全启动,结果签名验证失败,连错误日志都看不到。嗯,先跑通再加固,这个顺序很重要。
可可精品资料库