3. OP-TEE启动流程:安全世界启动、非安全世界启动、ATF与OP-TEE交互、SPMC初始化
大家好,我是你们的嵌入式安全讲师。今天我们来聊聊OP-TEE的启动流程。说实话,这块内容我第一次接触时也绕了不少弯路。整个启动过程涉及安全世界、非安全世界、ATF和SPMC,听起来很复杂,但拆开来看,其实就几个关键环节。
3.1 整体启动脉络
先画个图,帮大家理清思路。OP-TEE的启动不是一蹴而就的,它分阶段进行。我个人习惯把整个过程想象成一场接力赛:
嗯,这张图基本概括了启动的全貌。从BootROM开始,经过BL1、BL2,最后到ATF(BL31)。ATF负责初始化SPMC,然后分别拉起安全世界和非安全世界。说白了,ATF就是个调度员,它决定谁先跑、怎么跑。
3.2 安全世界启动
安全世界的启动,核心就是OP-TEE OS的初始化。我记得第一次看OP-TEE的启动代码时,被一堆初始化函数搞晕了。后来我总结了一个规律:OP-TEE的启动就是三板斧。
安全世界启动三板斧:
- 平台初始化:配置MMU、中断控制器、时钟等
- 核心服务初始化:内存管理、线程管理、IPC等
- 注册SMC处理函数:告诉ATF,我准备好了,可以处理安全调用
具体到代码层面,OP-TEE的入口点在core/arch/arm/kernel/entry_a64.S。这里有个关键函数_start,它负责从ATF跳转到OP-TEE后的第一件事。
/* 简化后的OP-TEE启动入口 */
_start:
/* 1. 设置栈指针 */
ldr x0, =stack_top
mov sp, x0
/* 2. 初始化BSS段 */
ldr x0, =__bss_start
ldr x1, =__bss_end
bl clear_bss
/* 3. 调用C语言初始化 */
bl main_init
这里有个坑,我当年踩过。BSS段清零必须在设置栈之后做,否则局部变量会出问题。你想想看,栈都没准备好,你调用函数,数据往哪放?
避坑指南:我曾经在移植OP-TEE到新平台时,忽略了MMU的页表配置。结果OP-TEE启动后,访问外设寄存器直接触发异常。后来排查了两天才发现,是页表属性没配成device memory。记住:外设寄存器一定要配成nGnRnE属性,否则缓存一致性会搞死你。
3.3 非安全世界启动
非安全世界的启动,说白了就是正常的Linux启动流程。但这里有个关键点:非安全世界启动前,安全世界必须已经初始化完毕。为什么?因为非安全世界启动过程中,可能会调用安全服务(比如密钥注入、安全存储初始化)。
ATF在拉起非安全世界时,会通过SMC调用通知OP-TEE:「我要启动非安全世界了,你准备好」。OP-TEE收到后,会做一些收尾工作,比如注册标准服务、打开中断等。
非安全世界启动的关键步骤:
- ATF通过SMC调用
PSCI_CPU_ON启动非安全核心 - 非安全核心执行U-Boot或内核启动代码
- 内核初始化OP-TEE驱动(
optee.ko) - 驱动通过SMC与OP-TEE建立通信通道
这里有个细节,我建议你注意。OP-TEE驱动在内核中注册时,会调用optee_probe()函数。这个函数会尝试与OP-TEE建立会话。如果OP-TEE还没准备好,驱动会返回-EPROBE_DEFER,等下次再试。嗯,这种设计挺巧妙的,避免了竞态条件。
3.4 ATF与OP-TEE交互
ATF和OP-TEE的交互,核心就是SMC(Secure Monitor Call)。SMC是一种特殊的指令,它能让非安全世界切换到安全世界,或者让安全世界切换到监控模式。
我个人习惯把SMC调用分为三类:
| SMC类型 | 功能 | 示例 |
|---|---|---|
| 标准SMC | 非安全世界调用安全服务 | TA调用、安全存储 |
| 快速SMC | 高优先级调用,不可中断 | PSCI电源管理 |
| 监控SMC | ATF内部使用 | 上下文切换 |
ATF收到SMC后,会根据SMC的function ID决定路由给谁。比如,0xBF00XXXX开头的SMC会路由给OP-TEE,而0x84000000开头的PSCI调用则由ATF自己处理。
注意:千万不要在中断上下文中调用SMC!我曾经有个项目,在中断处理函数里调用了SMC,结果系统直接死锁。原因是SMC会触发安全监控模式切换,而中断上下文不允许这种切换。记住:SMC只能在进程上下文中调用。
3.5 SPMC初始化
SPMC(Secure Partition Manager Core)是ARM FF-A(Firmware Framework for Arm Architecture)规范中的核心组件。说白了,SPMC就是安全世界的「操作系统」,它负责管理安全分区(Secure Partitions)。
SPMC的初始化在ATF中完成,具体在bl31_main()函数里。初始化流程大致如下:
/* ATF中SPMC初始化简化流程 */
void bl31_main(void)
{
/* 1. 初始化SPMC核心 */
spmc_init();
/* 2. 注册SMC处理函数 */
smc_register_handler(FID_SPMC, spmc_smc_handler);
/* 3. 启动安全分区 */
spmc_sp_start(SP_ID_OP_TEE);
/* 4. 启动非安全世界 */
smc(PSCI_CPU_ON, NS_CPU_ID);
}
这里有个关键点:SPMC初始化完成后,会启动OP-TEE作为第一个安全分区。OP-TEE启动后,会向SPMC注册自己的服务。这样,非安全世界调用安全服务时,SPMC就知道该路由给谁了。
个人经验:我在调试SPMC时,遇到过一个奇怪的问题。OP-TEE启动后,SPMC总是报错说「分区未注册」。后来发现,是OP-TEE的manifest文件配置错了。manifest文件里定义了分区的内存布局、中断配置等信息。如果这些信息不对,SPMC就无法正确管理分区。所以,移植OP-TEE时,一定要仔细检查manifest文件。
3.6 启动流程中的关键数据结构
启动过程中,有几个数据结构你必须要懂。我列个表,方便你对照:
| 数据结构 | 作用 | 所在位置 |
|---|---|---|
boot_manifest |
描述安全分区的配置信息 | ATF/SPMC |
tee_mmu_info |
OP-TEE的MMU配置 | OP-TEE核心 |
smc_handler |
SMC处理函数表 | ATF/OP-TEE |
thread_core_local |
每个核心的线程本地数据 | OP-TEE线程管理 |
嗯,这些数据结构在调试时特别有用。比如,当OP-TEE启动失败时,你可以检查tee_mmu_info中的页表配置是否正确。如果页表错了,OP-TEE访问内存就会触发异常。
3.7 总结
好了,OP-TEE的启动流程就讲到这里。总结一下核心要点:
- 启动顺序:BootROM → BL1 → BL2 → ATF → 安全世界 → 非安全世界
- ATF是调度员:它负责初始化SPMC,然后分别启动安全世界和非安全世界
- SMC是通信桥梁:非安全世界通过SMC调用安全服务
- SPMC是安全世界的管家:它管理安全分区的生命周期
说实话,启动流程这块内容,光看代码是不够的。我建议你动手跑一遍QEMU模拟器,单步跟踪启动过程。这样印象会深刻得多。我在项目中就是这么干的,效果很好。
可可精品资料库