OP-TEE Hello World 入门实战:从构建到 Host / TA 交互的完整解析
关键词:OP-TEE、Trusted Application(TA)、Client Application(CA/Host)、TEEC_InvokeCommand、TEE_Param、Secure World / Normal World
1. 为什么要写这篇文章
在学习 OP-TEE 的过程中,很多资料要么停留在“概念介绍”,要么直接跳到复杂的安全存储、密钥管理,中间缺少一个真正“能跑起来、能看清调用链”的实例。
示例恰好填补了这个空白:
hello_world
它足够简单(只做整数加减)却完整覆盖了 OP-TEE 的核心机制Host 与 TA 的交互路径清晰非常适合作为第一个实战级入门案例
本文基于一次完整的实际构建与运行过程,对 示例进行系统整理,目标是:
hello_world
读完本文,你应该能明确回答:OP-TEE 的功能,究竟是“怎么通过 Host 和 TA 协同实现的”。
2. OP-TEE 的基本运行模型(先建立正确认知)
在深入代码之前,必须先建立一个非常重要的认知:
2.1 OP-TEE 不是“你直接写 Secure OS”
在 OP-TEE 体系中,你能直接编写的代码只有两类:
| 世界 | 代码类型 | 作用 |
|---|---|---|
| Normal World | Client Application(CA / Host) | 发起请求 |
| Secure World | Trusted Application(TA) | 处理安全逻辑 |
OP-TEE OS 本身只是一个安全运行环境,真正的“功能”永远在 TA 里。
2.2 Host / TA 的关系是什么?
可以用一句话概括:
Host 是客户端,TA 是一个运行在 Secure World 的同步 RPC 服务。
调用关系是:
Host (Linux 用户态)
↓ TEEC_InvokeCommand
OP-TEE Driver (Kernel)
↓ SMC
OP-TEE OS
↓
TA_InvokeCommandEntryPoint
TA 执行完后,结果会原路返回到 Host。
3. hello_world 示例整体结构
示例由两部分组成:
hello_world
hello_world/
├── host/ # Normal World 客户端程序
│ └── main.c
└── ta/ # Secure World Trusted Application
├── hello_world_ta.c
├── include/
└── Makefile
这两个目录缺一不可,它们分别对应 OP-TEE 的两侧世界。
4. Host 端代码详解(Normal World)

4.1 Host 的角色是什么?
Host 代码的职责非常明确:
初始化 OP-TEE 上下文打开一个 TA Session构造参数(TEE_Param)通过 发起请求接收 TA 的返回结果
TEEC_InvokeCommand
4.2 关键数据结构:TEEC_Operation
在 中,有一段非常关键的代码:
main.c
memset(&op, 0, sizeof(op));
op.paramTypes = TEEC_PARAM_TYPES(
TEEC_VALUE_INOUT,
TEEC_NONE,
TEEC_NONE,
TEEC_NONE
);
op.params[0].value.a = 42;
这里体现了 OP-TEE 参数传递的核心机制。
paramTypes 是什么?
OP-TEE 固定支持 4 个参数槽每个参数的类型由 指定
paramTypes
本例中:
第 0 个参数:其余 3 个参数:未使用
VALUE_INOUT
这意味着:
一个整数从 Normal World 传入 Secure World,并且允许 TA 修改后再传回。
4.3 发起调用:TEEC_InvokeCommand
printf("Invoking TA to increment %d
", op.params[0].value.a);
res = TEEC_InvokeCommand(
&sess,
TA_HELLO_WORLD_CMD_DEC_VALUE,
&op,
&err_origin
);
printf("TA incremented value to %d
", op.params[0].value.a);
这一行是 Host → Secure World 的唯一入口。
注意几个关键点:
是 命令 ID
TA_HELLO_WORLD_CMD_DEC_VALUE 中的参数会被 OP-TEE 自动拷贝这是一个同步调用,不会立即返回
op
5. TA 端代码详解(Secure World)
5.1 TA 的统一入口函数
所有来自 Host 的命令,最终都会进入:
TEE_Result TA_InvokeCommandEntryPoint(
void *sess_ctx,
uint32_t cmd_id,
uint32_t param_types,
TEE_Param params[4]
)
这是 TA 的“命令分发中心”。
5.2 cmd_id 与 Host 的对应关系
在 TA 中:
switch (cmd_id) {
case TA_HELLO_WORLD_CMD_INC_VALUE:
return inc_value(param_types, params);
case TA_HELLO_WORLD_CMD_DEC_VALUE:
return dec_value(param_types, params);
default:
return TEE_ERROR_BAD_PARAMETERS;
}
这里的 直接对应 Host 中的
cmd_id 第二个参数。
TEEC_InvokeCommand
也就是说:
Host 决定调用哪个 TA 函数,完全由 cmd_id 决定。
5.3 真正的业务逻辑:inc_value / dec_value
以 为例:
dec_value
static TEE_Result dec_value(uint32_t param_types, TEE_Param params[4])
{
uint32_t exp_param_types = TEE_PARAM_TYPES(
TEE_PARAM_TYPE_VALUE_INOUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE
);
if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;
IMSG("Got value: %u from NW", params[0].value.a);
params[0].value.a--;
IMSG("Decrease value to: %u", params[0].value.a);
return TEE_SUCCESS;
}
这里发生了三件关键事情:
校验参数类型(安全边界)读取来自 Normal World 的数据修改参数内容作为返回值
6. 参数是如何“来回传递”的?
这是很多初学者最困惑的问题。
6.1 VALUE_INOUT 的真实含义
当你写:
TEEC_VALUE_INOUT
它意味着:
OP-TEE Core 会在进入 TA 前,把值拷贝到 Secure WorldTA 修改 在 TA
params[0].value.a 时,OP-TEE Core 会把结果再拷回 Normal World
return
整个过程对开发者是透明的。
6.2 调用结束后发生了什么?
当 TA 执行:
return TEE_SUCCESS;
并不是“直接回到 Host”,而是:
返回给 OP-TEE CoreOP-TEE Core 拷贝 OUT / INOUT 参数触发 Secure → Normal World 切换Linux OP-TEE Driver 恢复用户态 返回
TEEC_InvokeCommand()
这就是为什么你能在 Host 中直接看到修改后的值。
7. 运行结果验证
在目标设备上执行 Host 程序:
./optee_example_hello_world
输出:
Invoking TA to increment 42
TA incremented value to 43
同时可以确认:
正在运行OP-TEE 驱动已初始化TA 已成功从
tee-supplicant 加载
/lib/optee_armtz
8. 这个 hello_world 示例真正教会了什么?
这个示例虽然简单,但它完整展示了:
OP-TEE 的 Host / TA 编程模型Secure World 与 Normal World 的 同步调用机制参数如何在两个世界之间安全传递cmd_id 作为“安全接口”的本质
可以说:
理解了 hello_world,就理解了 80% 的 OP-TEE 应用开发模式。
9. 关于 RPC(Remote Procedure Call)的简要说明
在理解了 这种 同步调用模型 之后,很多读者会进一步产生一个疑问:
hello_world
如果 TA 需要访问 Normal World 的资源(比如文件系统、设备、网络),该怎么办?
这就引出了 OP-TEE 中另一个非常重要、但在 中刻意没有用到的概念:RPC(Remote Procedure Call)。
hello_world
9.1 什么是 OP-TEE 中的 RPC
在 OP-TEE 语境下,RPC 并不是一个“网络概念”,而是:
Secure World 中的 TA,主动请求 Normal World 帮它完成某些事情的一种机制。
也就是说:
正常情况下:
调用方向是 Normal → Secure(Host 调用 TA)
使用 RPC 时:
调用方向会在执行过程中 反向一次:Secure → Normal
但需要强调的是:
RPC 并不会打破整体的同步模型。
从 Host 的视角来看:
TEEC_InvokeCommand() 仍然是一次同步调用
RPC 只是发生在 Secure World 内部执行过程中的一个“中断式回调”。
9.2 RPC 一般用来做什么?
TA 在以下场景中,通常会触发 RPC:
访问 REE 文件系统(普通文件、RPMB 除外)使用 tee-supplicant 提供的服务需要较大内存分配(非 Secure RAM)使用部分依赖 Normal World 的系统能力
在这些情况下:
TA → OP-TEE Core → RPC → tee-supplicant / Linux → 返回 TA
随后 TA 继续执行,最终再返回给 Host。
9.3 为什么 hello_world 没有 RPC?
这是一个非常好的设计选择。
示例的目标是:
hello_world
让初学者只关注 Host / TA 的最小闭环
不引入 tee-supplicant、文件系统等复杂因素
把注意力集中在:
cmd_id参数传递Secure / Normal 世界切换
因此,在 中:
hello_world
TA 是一个“纯计算逻辑”,不依赖 Normal World 资源,自然也就不需要 RPC。
这也是为什么它非常适合作为第一个实战示例。
10. hello_world 示例的官方参考链接
为了便于读者对照源码、跟进最新实现,这里给出 示例的官方仓库链接:
hello_world
OP-TEE 官方示例仓库(hello_world):
https://github.com/linaro-swg/optee_examples/tree/master/hello_world
强烈建议:
对照本文阅读源码自己修改 或参数类型做实验逐步体会 Host / TA 交互模型
cmd_id
11. 下一步可以做什么?
在 hello_world 之后,常见的进阶方向包括:
使用 传递 buffer(字符串、数据块)在 TA 中使用 Secure Storage设计多命令 TA(类似安全服务)理解 TA 如何通过 RPC 使用 Normal World 资源
MEMREF
这些内容,都建立在本文所讲的基础之上。
10. 总结
OP-TEE 并不神秘。
它的核心思想非常简单:
用 Secure World 的 TA,去实现你真正信任的逻辑;
用 Normal World 的 Host,去驱动和使用这些逻辑。
这个例子,正是理解这一思想的最佳起点。
hello_world
如果你正在入门 OP-TEE,强烈建议你亲手把这个示例完整跑一遍。
理解一次,胜过看十篇零散的资料。

