3、接口基础:GP TEE 核心 API、OP-TEE 扩展 API、TA 与 CA 通信模型
好,咱们进入第三章。这一章是理解 OP-TEE 的基石——接口。说白了,就是搞清楚三件事:GlobalPlatform 那套标准接口长什么样、OP-TEE 自己加了哪些私货、以及 CA(客户端应用)和 TA(可信应用)到底怎么传话的。
我刚开始接触 TEE 时,最头疼的就是这些 API 名字。一堆 GP、TEE、TA、CA 缩写,文档还又长又臭。后来踩了几个坑才明白,其实核心就那么几条线。咱们今天就把它们捋清楚。
3.1 GP TEE 核心 API:标准化的“普通话”
GlobalPlatform(GP)是 TEE 领域的国际标准组织。它定义了一套 TEE 内部接口规范,目的是让不同厂商的 TEE 能跑同样的 TA。你想想看,如果每家 TEE 都搞一套私有 API,那开发者不得疯掉?
GP TEE 核心 API 主要分两大块:内部 API 和 客户端 API。
3.1.1 内部 API(TEE Internal API)
这是 TA 开发者直接打交道的接口。TA 运行在 TEE 里,通过这套 API 完成加密、存储、时间获取等操作。我习惯把它分成四类:
| 类别 | 典型函数 | 用途 |
|---|---|---|
| 基础操作 | TEE_Panic, TEE_GetProperty |
异常处理、获取系统属性 |
| 内存管理 | TEE_Malloc, TEE_Free, TEE_MemMove |
TA 内部堆内存分配与操作 |
| 加密服务 | TEE_AllocateOperation, TEE_CipherInit |
AES、RSA、哈希、HMAC 等 |
| 安全存储 | TEE_OpenPersistentObject, TEE_WriteObjectData |
将数据加密后写入 REE 文件系统 |
关键点:GP 内部 API 是 TA 的“标准库”。你写的 TA 如果只用这些 API,理论上可以移植到任何符合 GP 规范的 TEE 上。但现实嘛……后面会讲。
3.1.2 客户端 API(TEE Client API)
这是 CA 用来跟 TA 通信的接口。CA 运行在 REE(普通世界),通过它发起会话、传递数据。核心就四个函数:
TEEC_InitializeContext—— 初始化 TEE 上下文TEEC_OpenSession—— 打开与 TA 的会话TEEC_InvokeCommand—— 调用 TA 中的命令TEEC_CloseSession—— 关闭会话
嗯,这里要注意:TEEC_InvokeCommand 是核心中的核心。CA 传数据给 TA,或者 TA 返回结果给 CA,都靠它。参数通过 TEEC_Operation 结构体传递,里面可以挂内存缓冲区。
3.2 OP-TEE 扩展 API:厂商的“方言”
GP 标准虽然好,但有些功能它没覆盖到。比如硬件唯一密钥、安全时钟、底层调试接口。这时候 OP-TEE 就自己加了一套扩展 API。我个人觉得,这些扩展才是 OP-TEE 真正好用的地方。
常见的扩展 API 包括:
- TEE_GetREETime —— 获取 REE 侧时间(GP 只提供 TEE 内部时间)
- TEE_GenerateRandom —— 硬件真随机数(比 GP 的伪随机更安全)
- TEE_ReadMemory / TEE_WriteMemory —— 直接读写物理内存(慎用!)
- CFG_xxx 宏 —— 编译时配置开关,控制 TA 行为
警告:扩展 API 虽然方便,但会破坏可移植性。我曾经把一个用了大量 OP-TEE 扩展 API 的 TA 移植到另一个 TEE 上,结果改了大半个月。所以我的建议是:能用 GP 标准就别用扩展,除非你确定只跑在 OP-TEE 上。
3.3 TA 与 CA 通信模型:消息是怎么传的?
这是本章的重头戏。CA 在 REE,TA 在 TEE,两个世界物理隔离。它们怎么通信?靠的是 共享内存 和 SMC(安全监控调用)。
流程大致是这样:
- CA 调用
TEEC_OpenSession,触发一个 SMC 陷入 EL3 的 Monitor 层 - Monitor 切换安全世界,加载或唤醒对应的 TA
- CA 和 TA 通过预先分配的共享内存缓冲区交换数据
- 每次
TEEC_InvokeCommand都会走一遍 SMC 切换
下面这张图把整个通信链路画出来了,你一看就明白:
我的经验:调试通信问题时,最常遇到的坑是共享内存没对齐。GP 要求共享内存缓冲区地址必须是 4 字节对齐,否则 TEEC_RegisterSharedMemory 会返回错误。我曾经花了一下午查一个诡异的内存访问错误,最后发现是 CA 传了个未对齐的 buffer。记住:对齐!对齐!对齐!
3.4 通信数据流:参数怎么传?
CA 和 TA 之间传数据,靠的是 TEEC_Operation 结构体。这个结构体里有个 params 数组,最多 4 个参数。每个参数可以是以下类型之一:
- TEEC_VALUE_INPUT —— 传一个 32 位整数值(只进不出)
- TEEC_VALUE_OUTPUT —— 传一个 32 位整数值(只出不进)
- TEEC_VALUE_INOUT —— 传一个 32 位整数值(双向)
- TEEC_MEMREF_TEMP_INPUT —— 传一块临时内存(CA 分配,TA 只读)
- TEEC_MEMREF_TEMP_OUTPUT —— 传一块临时内存(TA 写入,CA 读取)
- TEEC_MEMREF_WHOLE —— 传一块共享内存的整个区域
说白了,小数据用 VALUE,大数据用 MEMREF。我一般习惯:小于 32 字节用 VALUE,大于 32 字节用 MEMREF。这样效率最高。
3.5 避坑指南:我踩过的几个坑
坑一:共享内存生命周期
我曾经在 CA 里用栈上变量作为共享内存 buffer,结果 TA 还没读完,CA 函数就返回了,栈被回收。TA 读到的全是垃圾数据。记住:共享内存必须在整个会话期间有效,最好用堆分配或全局变量。
坑二:参数数量限制
GP 规定一次最多传 4 个参数。如果你需要传更多数据,要么合并成一个结构体,要么分多次调用。我见过有人硬塞 5 个参数,结果 TA 端只收到前 4 个,后面那个丢了。查了半天才发现是参数超限。
坑三:扩展 API 的依赖
OP-TEE 的扩展 API 虽然好用,但不同版本之间可能有差异。比如 TEE_GetREETime 在 3.12 版本之后改了参数结构。如果你升级了 OP-TEE 版本,记得检查扩展 API 的 changelog。我上次升级时没注意,结果 TA 编译通过但运行时直接 Panic。
3.6 小结
这一章咱们把接口基础捋了一遍。GP 核心 API 是标准,OP-TEE 扩展 API 是特色,通信模型靠共享内存 + SMC。记住三点:
- 能用 GP 标准就别用扩展
- 共享内存一定要对齐
- 参数最多 4 个,别超了
这些基础打牢了,后面调试日志的时候你才能一眼看出问题出在 CA 侧还是 TA 侧,还是通信链路本身。