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 驱动框架和设备树绑定规则。
驱动注册流程如下:
- 设备树解析器扫描
/firmware/optee或/secure节点。 - 匹配
compatible属性到dt_driver表。 - 调用驱动
.probe()函数,传入dt_driver_phandle参数。 - 驱动申请 I/O 内存、注册中断处理函数、初始化硬件。
- 驱动通过
tee_driver_register()加入内核驱动链表。
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 端驱动应跳过此节点。
3.3 TA 与 CA 通信机制
CA(Client Application,运行在 Linux 用户空间)与 TA(Trusted Application,运行在 OP-TEE 用户空间)通过 GP TEE 客户端 API 进行通信。通信路径涉及四个关键组件:
- libteec:CA 侧库,封装
TEEC_OpenSession()、TEEC_InvokeCommand()等标准接口。 - OP-TEE Linux 驱动(
drivers/tee/optee/):处理 SMC 调用,将 CA 请求转发到安全世界。 - OP-TEE 内核(
core/):解析请求,调度到对应 TA 会话。 - 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):预先注册的共享内存,零拷贝传递,适合大块数据。
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 通信。