3. OP-TEE Client概述
在 OP-TEE 的整体软件栈中,OP-TEE Client 是普通世界(Normal World,即 Linux 用户态与内核态)与安全世界(Secure World,即 OP-TEE OS 与 TA)之间的“桥梁层”。它并不直接执行可信应用(TA)的逻辑,而是负责把普通世界应用的请求,通过标准化的接口和驱动通道,安全地传递到安全世界,并把结果带回。理解 OP-TEE Client 的位置、组成与通信模型,是后续编写 CA/TA 程序、排查通信故障的基础。
一、OP-TEE Client 在系统中的位置
从系统分层看,OP-TEE 的完整链路自上而下大致为:普通世界用户态应用(CA)→ libteec → tee-supplicant → Linux 内核 TEE 驱动 → SMC/HVC 指令 → OP-TEE OS → TA。其中,libteec 与 tee-supplicant 共同构成 OP-TEE Client 的主体,它们都运行在普通世界,但职责截然不同。
libteec 是一个用户态库,通常以 libteec.so 形式提供,被 CA 直接链接调用。它封装了打开 TEE 设备、建立会话、调用 TA 命令、共享内存注册等操作,把复杂的 ioctl 细节隐藏起来。tee-supplicant 则是一个常驻后台的守护进程,它代表安全世界去访问普通世界的文件系统、RPMB、网络等资源。当 TA 需要加载动态库、读写安全存储文件或访问 RPMB 时,OP-TEE OS 会通过 RPC 请求“回到”普通世界,由 tee-supplicant 完成实际 I/O。
二、libteec 与 tee-supplicant 的职责划分
libteec 的核心职责可以概括为“会话管理 + 命令下发 + 内存共享”。它提供 TEEC_InitializeContext、TEEC_OpenSession、TEEC_InvokeCommand、TEEC_CloseSession、TEEC_FinalizeContext 等 API,并负责把参数封装成 OP-TEE 驱动可识别的结构。CA 只需关注 TA 的 UUID、命令 ID 和参数,不必关心底层 ioctl 的细节。
tee-supplicant 的职责则偏向“资源代理”。它不参与 CA 的直接调用,而是被动响应安全世界的 RPC。典型场景包括:TA 通过 TEE_LoadTa 加载动态库时,tee-supplicant 从文件系统读取 .ta 文件;TA 使用安全存储时,tee-supplicant 负责在 /data/tee/ 下读写加密文件;TA 访问 RPMB 时,tee-supplicant 通过内核 RPMB 驱动完成读写。没有 tee-supplicant,很多 TA 功能会因资源不可达而失败。
| 对比项 | libteec | tee-supplicant |
|---|---|---|
| 运行形态 | 用户态动态库,被 CA 链接 | 常驻后台守护进程 |
| 调用方向 | CA 主动调用,进入安全世界 | 安全世界 RPC 触发,回到普通世界 |
| 核心接口 | TEEC_OpenSession / TEEC_InvokeCommand | 文件、RPMB、动态库加载等 RPC 服务 |
| 典型设备 | /dev/tee0、/dev/teepriv0 | /dev/teepriv0、/dev/mmcblk*rpmb |
| 缺失后果 | CA 无法建立会话或调用命令 | TA 无法加载库、读写安全存储或 RPMB |
三、客户端与 TA 的通信模型
CA 与 TA 的通信遵循“上下文—会话—命令”三级模型。首先,CA 调用 TEEC_InitializeContext 打开 TEE 设备,获得一个上下文;然后通过 TEEC_OpenSession 指定 TA 的 UUID,与目标 TA 建立会话;最后通过 TEEC_InvokeCommand 携带命令 ID 和参数调用 TA 的具体命令。TA 侧则通过 TA_InvokeCommandEntryPoint 接收命令并返回结果。
TEEC_Context ctx;
TEEC_Session sess;
TEEC_InitializeContext(NULL, &ctx);
TEEC_UUID uuid = TA_HELLO_WORLD_UUID;
TEEC_OpenSession(&ctx, &sess, &uuid,
TEEC_LOGIN_PUBLIC, NULL, NULL, NULL);
uint32_t origin;
TEEC_Parameter param;
param.value.a = 0x1234;
param.value.b = 0x5678;
TEEC_InvokeCommand(&sess, TA_HELLO_WORLD_CMD_INC_VALUE,
¶m, &origin);
TEEC_CloseSession(&sess);
TEEC_FinalizeContext(&ctx);
参数传递有四种类型:TEEC_VALUE_INPUT、TEEC_VALUE_OUTPUT、TEEC_MEMREF_TEMP_INPUT、TEEC_MEMREF_TEMP_OUTPUT 等。小数据可直接用 value 传递,大数据则通过共享内存(memref)传递。共享内存需要注册,确保安全世界与普通世界看到的是同一块物理内存,避免拷贝带来的性能损耗与安全风险。
四、典型场景与常见误区
以一个“安全计数”TA 为例:CA 打开会话后,循环调用 TA_INC_VALUE 命令,TA 在安全世界内维护计数器并返回新值。若 CA 忘记调用 TEEC_CloseSession,会话资源不会立即释放,长期运行可能耗尽会话槽位。若 tee-supplicant 未启动,TA 在首次访问安全存储时会阻塞在 RPC 上,表现为 CA 调用超时或返回 TEEC_ERROR_COMMUNICATION。
常见误区有三类:其一,把 libteec 与 tee-supplicant 混为一谈,认为只要链接了 libteec 就能访问文件系统;其二,忽略共享内存的生命周期,在 TEEC_InvokeCommand 返回前就释放缓冲区;其三,误以为 CA 可以直接调用 TA 内部函数,实际上所有交互都必须经过命令 ID 和参数结构。排查时,可先确认 /dev/tee0 是否存在、tee-supplicant 是否运行,再检查 UUID 与命令 ID 是否匹配,最后用 strace 观察 ioctl 调用是否成功。
综上,OP-TEE Client 位于普通世界与安全世界的交界处,libteec 负责“请求下行”,tee-supplicant 负责“资源上行”,二者配合构成 CA 与 TA 之间的完整通信链路。掌握这一模型,才能在后续的 TA 开发、安全存储与 RPMB 调试中快速定位问题。