3. OP-TEE启动流程

大家好,我是你们的嵌入式安全讲师。今天我们来聊聊OP-TEE的启动流程。说实话,这块内容我第一次接触时也绕了不少弯路。整个启动过程涉及安全世界、非安全世界、ATF和SPMC,听起来很复杂,但拆开来看,其实就几个关键环节。

3.1 整体启动脉络

先画个图,帮大家理清思路。OP-TEE的启动不是一蹴而就的,它分阶段进行。我个人习惯把整个过程想象成一场接力赛:

OP-TEE启动流程总览 BootROM 硬件初始化 BL1 (BootROM) 加载BL2 BL2 (SCP_BL2) 加载BL31/BL32/BL33 ATF BL31 SPMC初始化 安全世界 (Secure World) OP-TEE OS初始化 → 注册SMC处理 → 等待调用 非安全世界 (Normal World) U-Boot/内核启动 → CA应用运行 → SMC调用 SMC调用交互

嗯,这张图基本概括了启动的全貌。从BootROM开始,经过BL1、BL2,最后到ATF(BL31)。ATF负责初始化SPMC,然后分别拉起安全世界和非安全世界。说白了,ATF就是个调度员,它决定谁先跑、怎么跑。

3.2 安全世界启动

安全世界的启动,核心就是OP-TEE OS的初始化。我记得第一次看OP-TEE的启动代码时,被一堆初始化函数搞晕了。后来我总结了一个规律:OP-TEE的启动就是三板斧。

安全世界启动三板斧:

  1. 平台初始化:配置MMU、中断控制器、时钟等
  2. 核心服务初始化:内存管理、线程管理、IPC等
  3. 注册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模拟器,单步跟踪启动过程。这样印象会深刻得多。我在项目中就是这么干的,效果很好。

可可精品资料库