3. OP-TEE 驱动基础:驱动模型、设备树、TA 与 CA 通信机制

在 OP-TEE 安全世界中,驱动是连接硬件外设与安全服务的桥梁。与普通 Linux 内核驱动不同,OP-TEE 驱动运行在 TrustZone 的安全侧,直接操作安全外设(如密钥存储器、安全时钟、TUI 显示控制器),并通过 安全中断 与普通世界协同。本章从驱动模型、设备树绑定、以及 TA/CA 通信三个维度,构建 OP-TEE 驱动的完整知识框架。

3.1 OP-TEE 驱动模型

OP-TEE 内核(core/)采用分层驱动架构,所有驱动均继承自 struct tee_driver,并通过 driver_init() 宏注册。驱动模型分为三层:

层次 路径 职责 示例
核心层 core/kernel/driver.c 驱动注册、匹配、生命周期管理 tee_driver_register()
总线层 core/drivers/ 外设总线抽象(PL011、SPI、I2C) pl011_uart_driver
设备层 core/arch/arm/plat-*/ 平台特定设备实例化与中断绑定 stm32mp1_uart_init()
关键概念: OP-TEE 驱动不依赖 Linux 设备模型,而是使用自己的 dt_driver 机制,直接从设备树(FDT)解析并匹配驱动。这意味着驱动开发者必须同时掌握 OP-TEE 驱动框架和设备树绑定规则。

驱动注册流程如下:

  1. 设备树解析器扫描 /firmware/optee 或 /secure 节点。
  2. 匹配 compatible 属性到 dt_driver 表。
  3. 调用驱动 .probe() 函数,传入 dt_driver_phandle 参数。
  4. 驱动申请 I/O 内存、注册中断处理函数、初始化硬件。
  5. 驱动通过 tee_driver_register() 加入内核驱动链表。
OP-TEE 驱动注册与匹配流程 设备树 FDT dt_driver 匹配表 probe() 回调 中断注册 I/O 内存映射 硬件初始化 tee_driver_register() 驱动注册完成后,通过 secure monitor call (SMC) 向 Linux 内核暴露服务接口

3.2 设备树绑定与安全外设描述

OP-TEE 使用扁平设备树(FDT)描述安全外设。设备树节点必须放置在 /secure 或 /firmware/optee 路径下,并包含 optee,secure-device 属性以标识安全属性。典型的安全 UART 节点如下:

/ {
  secure {
    compatible = "optee,secure-bus";
    uart0: serial@ff010000 {
      compatible = "arm,pl011", "optee,pl011";
      reg = <0x0 0xff010000 0x0 0x1000>;
      interrupts = <0 33 4>;
      optee,secure-device;
      clocks = <&uart_clk>;
      status = "okay";
    };
  };
};
调试提示: 使用 devicetree-validate 工具检查节点格式。OP-TEE 启动时若未找到匹配驱动,会在 core/drivers/dt_driver.c 中打印 D/DT: no driver found for ... 日志,这是排查设备树绑定错误的第一个检查点。

设备树属性与驱动匹配规则:

  • compatible:驱动匹配主键,支持多级兼容(如 "arm,pl011" 回退到 "optee,pl011")。
  • reg:物理地址与大小,驱动通过 dt_map_dev() 映射到安全世界虚拟地址空间。
  • interrupts:安全中断号,必须使用 GIC_SPI 类型(中断号 > 32),且中断目标设置为安全组(Group 0/1)。
  • optee,secure-device:标记该设备仅由 OP-TEE 访问,Linux 端驱动应跳过此节点。
安全设备树节点与驱动匹配关系 /secure/serial@ff010000 compatible = "arm,pl011" reg = <0xff010000 0x1000> interrupts = <0 33 4> dt_driver 表 .compatible = "arm,pl011" .probe = pl011_probe .flags = DT_DRIVER_SECURE pl011 驱动实例 uart_base = 0xff010000 irq_num = 33 匹配成功后,驱动获得设备树中所有资源信息,无需额外平台代码

3.3 TA 与 CA 通信机制

CA(Client Application,运行在 Linux 用户空间)与 TA(Trusted Application,运行在 OP-TEE 用户空间)通过 GP TEE 客户端 API 进行通信。通信路径涉及四个关键组件:

  1. libteec:CA 侧库,封装 TEEC_OpenSession()、TEEC_InvokeCommand() 等标准接口。
  2. OP-TEE Linux 驱动(drivers/tee/optee/):处理 SMC 调用,将 CA 请求转发到安全世界。
  3. OP-TEE 内核(core/):解析请求,调度到对应 TA 会话。
  4. TA 入口:TA 通过 TA_CreateEntryPoint() 和 TA_InvokeCommandEntryPoint() 处理命令。
步骤 CA 侧 Linux 内核 OP-TEE 内核 TA 侧
1 TEEC_InitializeContext() 打开 /dev/tee0 — —
2 TEEC_OpenSession() SMC OPTEE_SMC_OPEN_SESSION 分配会话 ID TA_CreateEntryPoint()
3 TEEC_InvokeCommand() SMC OPTEE_SMC_INVOKE 参数校验与内存映射 TA_InvokeCommandEntryPoint()
4 TEEC_CloseSession() SMC OPTEE_SMC_CLOSE_SESSION 释放会话资源 TA_DestroyEntryPoint()
安全边界注意: CA 与 TA 之间的共享内存必须通过 TEEC_AllocateSharedMemory() 或 TEEC_RegisterSharedMemory() 注册。OP-TEE 内核会检查内存缓冲区是否在预先注册的共享内存池中,防止 CA 传递非法地址导致安全世界内存破坏。

参数传递支持三种模式:

  • 值传递(Value):适用于小数据(≤ 4 字节),直接嵌入 SMC 参数寄存器。
  • 临时内存引用(Temporary Memory Reference):CA 分配缓冲区,OP-TEE 内核在安全世界创建影子副本。
  • 持久内存引用(Persistent Memory Reference):预先注册的共享内存,零拷贝传递,适合大块数据。
CA 与 TA 通信完整调用链 CA (用户空间) libteec Linux tee 驱动 OP-TEE 内核 TA (安全用户空间) 实线:调用路径(CA → TA) 虚线:返回路径(TA → CA) TEEC_OpenSession → SMC → 会话创建 → TA_CreateEntryPoint TEEC_InvokeCommand → SMC → 参数映射 → TA_InvokeCommandEntryPoint TEEC_CloseSession → SMC → 会话销毁 → TA_DestroyEntryPoint

3.4 驱动与 TA 的协同模式

在实际调试中,驱动与 TA 的交互通常遵循两种模式:

模式 描述 适用场景 中断处理
同步调用 CA 调用 TA,TA 直接操作驱动寄存器 密钥生成、随机数读取 TA 轮询状态寄存器
异步通知 驱动中断触发,TA 通过 tee_notify() 唤醒 CA 安全触摸屏输入、安全传感器 中断处理函数设置事件标志
调试切入点: 在 core/arch/arm/kernel/thread.c 中设置断点,观察 SMC 调用号(OPTEE_SMC_OPEN_SESSION = 0x1000,OPTEE_SMC_INVOKE = 0x1001)。这是验证 CA→TA 通信是否到达安全世界的第一道关卡。

驱动与 TA 共享数据时,必须使用 安全世界共享内存(Secure Shared Memory)。OP-TEE 内核提供 tee_shm_alloc() 和 tee_shm_free() 接口,驱动通过 struct tee_shm 描述符将物理内存注册到 TA 的地址空间。CA 侧则通过 TEEC_RegisterSharedMemory() 将用户空间缓冲区映射到同一物理内存区域。

/* 驱动侧分配共享内存示例 */
struct tee_shm *shm;
res = tee_shm_alloc(tee_ctx, &shm, 4096, TEE_SHM_MAPPED);
if (res != TEE_SUCCESS) {
  EMSG("Failed to allocate secure shared memory");
  return res;
}
/* 将物理地址传递给 TA */
param[0].memref.buffer = shm->paddr;
param[0].memref.size = 4096;
性能优化: 对于高频中断驱动的数据传递(如安全音频流),建议使用 环形缓冲区(ring buffer)放在共享内存中,驱动中断处理函数只更新写指针,TA 轮询读指针,避免每次中断都触发 SMC 通信。