3. OP-TEE 启动与运行流程

OP-TEE 并不是一个孤立运行的程序,它依赖从芯片上电到富操作系统(REE)启动的完整信任链。理解它的启动与运行流程,是后续分析共享内存与性能优化的前提。本章围绕安全启动、BL32 加载、TA 与 CA 通信模型、会话建立与命令调用流程展开,帮助读者建立从"上电"到"一次 TA 命令执行完成"的全局视图。

一、安全启动与信任链

安全启动的核心思想是:每一级固件在加载下一级之前,先验证其签名或哈希,验证通过才移交执行权。这样,任何一级被篡改都会导致启动中断,从而保证最终运行在安全世界中的 OP-TEE 内核是可信的。

典型 ARMv8-A 平台的启动链如下:

  • BL1(Boot ROM):芯片内固化代码,验证 BL2 的签名,是信任根(Root of Trust)。
  • BL2(Trusted Boot Firmware):初始化 DDR 等基础硬件,加载并验证 BL31、BL32、BL33。
  • BL31(EL3 Runtime):常驻安全监控(Secure Monitor),负责 SMC 调用转发与安全世界/非安全世界切换。
  • BL32(OP-TEE OS):运行在 Secure EL1,即 TEE 内核本体。
  • BL33(U-Boot / UEFI):非安全世界引导程序,最终拉起 Linux 内核。
信任链方向:BL1 → BL2 → BL31/BL32/BL33。BL32 的可信性由 BL2 验证,而 BL32 又负责验证后续加载的 TA,形成"固件—TEE 内核—TA"三级信任传递。

二、BL32 加载与 OP-TEE 初始化

BL32 通常以 tee.bin 或 tee-raw.bin 形式存在,由 BL2 加载到安全内存区域。加载后,OP-TEE 内核依次完成:

  1. 设置异常向量表与 MMU,建立安全世界地址映射;
  2. 初始化 GIC、串口、定时器等平台驱动;
  3. 初始化线程与栈,准备处理来自非安全世界的 SMC;
  4. 加载并验证内置 TA(Early TA / Pseudo TA);
  5. 进入等待循环,通过 SMC 与 BL31 交互。

在 Linux 侧,OP-TEE 驱动通过 optee 节点与 BL32 通信,设备树中常见配置如下:

firmware {
    optee {
        compatible = "linaro,optee-tz";
        method = "smc";
    };
};

当 Linux 启动 optee 驱动时,会通过 SMC 向 BL32 发起握手,获取版本、共享内存配置等信息,随后注册字符设备 /dev/tee0 与 /dev/teepriv0,供用户态 CA 使用。

三、TA 与 CA 通信模型

OP-TEE 的通信模型由两个角色构成:

  • CA(Client Application):运行在 Linux 用户态,通过 libteec 调用 TEE 接口。
  • TA(Trusted Application):运行在 OP-TEE 安全世界,处理具体安全逻辑。

两者之间不能直接访问对方内存,所有交互都通过共享内存与 SMC 完成。CA 调用 TEEC_InvokeCommand 后,请求经 libteec → 内核 optee 驱动 → SMC → BL31 → BL32,最终由 OP-TEE 调度到目标 TA。TA 执行完成后,结果沿原路径返回。

OP-TEE 启动与 CA/TA 通信全景 启动信任链 BL1 ROM BL2 BL31 EL3 BL32 OP-TEE BL33 U-Boot 非安全世界 REE 安全世界 TEE CA 应用 libteec / TEEC_InvokeCommand Linux 内核 optee 驱动 /dev/tee0 · /dev/teepriv0 共享内存 SHM OP-TEE 内核 SMC 处理 · 会话管理 TA 实例 TA_InvokeCommandEntryPoint 安全内存 / TA 私有数据 SMC 返回结果

四、会话建立与命令调用流程

一次完整的 TA 调用通常包含"打开会话—调用命令—关闭会话"三个阶段。以 CA 侧代码为例:

TEEC_Context ctx;
TEEC_Session sess;
TEEC_Operation op;
uint32_t err;

TEEC_InitializeContext(NULL, &ctx);
TEEC_OpenSession(&ctx, &sess, &uuid,
                 TEEC_LOGIN_PUBLIC, NULL, NULL, &err);

memset(&op, 0, sizeof(op));
op.paramTypes = TEEC_PARAM_TYPES(
    TEEC_MEMREF_TEMP_INPUT, TEEC_NONE,
    TEEC_NONE, TEEC_NONE);
op.params[0].tmpref.buffer = input;
op.params[0].tmpref.size   = len;

TEEC_InvokeCommand(&sess, CMD_PROCESS, &op, &err);
TEEC_CloseSession(&sess);
TEEC_FinalizeContext(&ctx);

对应的 TA 侧入口为:

TEE_Result TA_InvokeCommandEntryPoint(
    void *sess_ctx, uint32_t cmd_id,
    uint32_t param_types, TEE_Param params[4])
{
    switch (cmd_id) {
    case CMD_PROCESS:
        return process_data(params);
    default:
        return TEE_ERROR_BAD_PARAMETERS;
    }
}

整个流程可归纳为下表:

阶段 CA 侧动作 TEE 侧动作 关键接口
初始化 初始化上下文 驱动握手,注册设备 TEEC_InitializeContext
打开会话 指定 TA UUID 发起连接 加载/查找 TA,创建会话 TEEC_OpenSession
调用命令 填充参数并触发调用 分发到 TA 命令入口 TEEC_InvokeCommand
关闭会话 释放会话资源 销毁会话上下文 TEEC_CloseSession
清理 释放上下文 驱动资源回收 TEEC_FinalizeContext

五、典型场景与常见误区

场景:一个安全支付 CA 需要向 TA 发送交易数据并获取签名结果。CA 通过 TEEC_MEMREF_TEMP_INPUT 传入明文,TA 在安全世界内完成密钥运算,返回签名。整个过程中,密钥始终不出安全世界,REE 侧只能看到密文与签名结果。

常见误区一:认为 CA 与 TA 可以直接共享指针。实际上两者地址空间隔离,必须通过共享内存或临时内存引用传递数据,OP-TEE 会在 SMC 边界做参数拷贝与校验。
常见误区二:忽略会话生命周期。频繁 OpenSession/CloseSession 会带来额外开销,在高频调用场景下应复用会话,这也是后续性能优化的重要切入点。
复盘要点:安全启动保证 BL32 可信,BL32 保证 TA 可信;CA 与 TA 之间通过 SMC + 共享内存通信;会话是资源与状态的载体,命令调用是数据流动的载体。理解这条链路,才能准确定位共享内存与性能瓶颈所在。