在 OP-TEE 体系中,libteec是运行在 REE 用户态的客户端库。它完整实现了 GlobalPlatform (GP) 标准的 TEE Client API。libteec 的核心文件是 src/tee_client_api.c。它的本质工作非常纯粹:将上层面向对象的 GP API 转换为对 Linux 内核 TEE 驱动(/dev/tee0)的 ioctl 级联调用。
以下梳理其最核心的三个 API 的代码实现骨架、关键数据结构及参数序列化逻辑。
一、 TEEC_InitializeContext:初始化上下文与建立通道
该函数负责打开 TEE 驱动的字符设备文件,并获取当前 TEE 的基本版本信息与软硬件能力(Capabilities)。C
TEEC_Result TEEC_InitializeContext(const char *name, TEEC_Context *ctx)
{
char devname[PATH_MAX] = { 0 };
int fd = 0;
size_t n = 0;
if (!ctx)
return TEEC_ERROR_BAD_PARAMETERS;
for (n = 0; n < TEEC_MAX_DEV_SEQ; n++) {
uint32_t gen_caps = 0;
snprintf(devname, sizeof(devname), "/dev/tee%zu", n);
fd = teec_open_dev(devname, name, &gen_caps);
if (fd >= 0) {
ctx->imp.fd = fd;
ctx->imp.reg_mem = gen_caps & TEE_GEN_CAP_REG_MEM;
ctx->imp.memref_null = gen_caps & TEE_GEN_CAP_MEMREF_NULL;
return TEEC_SUCCESS;
}
}
return TEEC_ERROR_ITEM_NOT_FOUND;
}
二、 TEEC_OpenSession:开启与指定 TA 的安全会话
在调用具体的 TA 业务前,需要通过 UUID 寻址来拉起目标 TA。此时,libteec 会把 GP 的 TEEC_Operation 结构参数打包转换为内核识别的 tee_ioctl_param。C
TEEC_Result TEEC_OpenSession(TEEC_Context *ctx, TEEC_Session *session,
const TEEC_UUID *destination,
uint32_t connection_method, const void *connection_data,
TEEC_Operation *operation, uint32_t *ret_origin)
{
struct tee_ioctl_open_session_arg *arg = NULL;
struct tee_ioctl_param *params = NULL;
TEEC_Result res = TEEC_ERROR_GENERIC;
uint32_t eorig = 0;
int rc = 0;
const size_t arg_size = sizeof(struct tee_ioctl_open_session_arg) +
TEEC_CONFIG_PAYLOAD_REF_COUNT *
sizeof(struct tee_ioctl_param);
union {
struct tee_ioctl_open_session_arg arg;
uint8_t data[arg_size];
} buf;
struct tee_ioctl_buf_data buf_data;
TEEC_SharedMemory shm[TEEC_CONFIG_PAYLOAD_REF_COUNT];
memset(&buf, 0, sizeof(buf));
memset(&shm, 0, sizeof(shm));
memset(&buf_data, 0, sizeof(buf_data));
if (!ctx || !session) {
eorig = TEEC_ORIGIN_API;
res = TEEC_ERROR_BAD_PARAMETERS;
goto out;
}
buf_data.buf_ptr = (uintptr_t)&buf;
buf_data.buf_len = sizeof(buf);
arg = &buf.arg;
arg->num_params = TEEC_CONFIG_PAYLOAD_REF_COUNT;
params = (struct tee_ioctl_param *)(arg + 1);
uuid_to_octets(arg->uuid, destination);
setup_client_data(arg, connection_method, connection_data);
res = teec_pre_process_operation(ctx, operation, params, shm);
if (res != TEEC_SUCCESS) {
eorig = TEEC_ORIGIN_API;
goto out_free_temp_refs;
}
rc = ioctl(ctx->imp.fd, TEE_IOC_OPEN_SESSION, &buf_data);
if (rc) {
EMSG("TEE_IOC_OPEN_SESSION failed");
eorig = TEEC_ORIGIN_COMMS;
res = ioctl_errno_to_res(errno);
goto out_free_temp_refs;
}
res = arg->ret;
eorig = arg->ret_origin;
if (res == TEEC_SUCCESS) {
session->imp.ctx = ctx;
session->imp.session_id = arg->session;
}
teec_post_process_operation(operation, params, shm);
out_free_temp_refs:
teec_free_temp_refs(operation, shm);
out:
if (ret_origin)
*ret_origin = eorig;
return res;
}
三、 TEEC_InvokeCommand:核心业务指令的下发与数据交互
这是日常业务中最频繁调用的 API。由于它专注于执行指令,不需要像 OpenSession 那样搬运 UUID,所以它的逻辑更轻量,直奔 TEE_IOC_INVOKE。C
TEEC_Result TEEC_InvokeCommand(TEEC_Session *session, uint32_t cmd_id,
TEEC_Operation *operation, uint32_t *error_origin)
{
struct tee_ioctl_invoke_arg *arg = NULL;
struct tee_ioctl_param *params = NULL;
TEEC_Result res = TEEC_ERROR_GENERIC;
uint32_t eorig = 0;
int rc = 0;
/* 1. 装填 Session ID 与要执行的命令号 */
const size_t arg_size = sizeof(struct tee_ioctl_invoke_arg) +
TEEC_CONFIG_PAYLOAD_REF_COUNT *
sizeof(struct tee_ioctl_param);
union {
struct tee_ioctl_invoke_arg arg;
uint8_t data[arg_size];
} buf;
struct tee_ioctl_buf_data buf_data;
TEEC_SharedMemory shm[TEEC_CONFIG_PAYLOAD_REF_COUNT];
memset(&buf, 0, sizeof(buf));
memset(&buf_data, 0, sizeof(buf_data));
memset(&shm, 0, sizeof(shm));
if (!session) {
eorig = TEEC_ORIGIN_API;
res = TEEC_ERROR_BAD_PARAMETERS;
goto out;
}
buf_data.buf_ptr = (uintptr_t)&buf;
buf_data.buf_len = sizeof(buf);
arg = &buf.arg;
arg->num_params = TEEC_CONFIG_PAYLOAD_REF_COUNT;
params = (struct tee_ioctl_param *)(arg + 1);
arg->session = session->imp.session_id;
arg->func = cmd_id;
if (operation) {
teec_mutex_lock(&teec_mutex);
operation->imp.session = session;
teec_mutex_unlock(&teec_mutex);
}
/* 2. 序列化参数 (TEEC_Operation -> tee_ioctl_param) */
res = teec_pre_process_operation(session->imp.ctx, operation, params, shm);
if (res != TEEC_SUCCESS) {
eorig = TEEC_ORIGIN_API;
goto out_free_temp_refs;
}
/* 3. 发送关键 ioctl 陷落至内核,阻塞等待 Secure 世界(TA)处理完毕返回 */
rc = ioctl(session->imp.ctx->imp.fd, TEE_IOC_INVOKE, &buf_data);
if (rc) {
EMSG("TEE_IOC_INVOKE failed");
eorig = TEEC_ORIGIN_COMMS;
res = ioctl_errno_to_res(errno);
goto out_free_temp_refs;
}
res = arg->ret;
eorig = arg->ret_origin;
teec_post_process_operation(operation, params, shm);
/* 4. 反序列化与收尾:将 TA 修改后的 Value 或 Memref 刷新回用户态 CA 内存空间 */
out_free_temp_refs:
teec_free_temp_refs(operation, shm);
out:
if (error_origin)
*error_origin = eorig;
return res;
}
四、 核心桥梁:参数的预处理与内存共享机制
深入细读 libteec,最复杂的逻辑其实隐藏在 teec_pre_process_operation 内部。GP 标准规定每次调用最多传递 4 个参数(TEEC_Parameter params[4]),类型可以是 VALUE(纯数值)或 MEMREF(内存引用)。
static TEEC_Result teec_pre_process_operation(TEEC_Context *ctx,
TEEC_Operation *operation,
struct tee_ioctl_param *params,
TEEC_SharedMemory *shms)
{
TEEC_Result res = TEEC_ERROR_GENERIC;
size_t n = 0;
memset(shms, 0, sizeof(TEEC_SharedMemory) *
TEEC_CONFIG_PAYLOAD_REF_COUNT);
for (n = 0; n < TEEC_CONFIG_PAYLOAD_REF_COUNT; n++)
shms[n].imp.id = -1;
if (!operation) {
memset(params, 0, sizeof(struct tee_ioctl_param) *
TEEC_CONFIG_PAYLOAD_REF_COUNT);
return TEEC_SUCCESS;
}
for (n = 0; n < TEEC_CONFIG_PAYLOAD_REF_COUNT; n++) {
uint32_t param_type = 0;
param_type = TEEC_PARAM_TYPE_GET(operation->paramTypes, n);
switch (param_type) {
case TEEC_NONE:
params[n].attr = param_type;
break;
case TEEC_VALUE_INPUT:
case TEEC_VALUE_OUTPUT:
case TEEC_VALUE_INOUT:
params[n].attr = param_type;
params[n].a = operation->params[n].value.a;
params[n].b = operation->params[n].value.b;
break;
case TEEC_MEMREF_TEMP_INPUT:
case TEEC_MEMREF_TEMP_OUTPUT:
case TEEC_MEMREF_TEMP_INOUT:
res = teec_pre_process_tmpref(ctx, param_type,
&operation->params[n].tmpref, params + n,
shms + n);
if (res != TEEC_SUCCESS)
return res;
break;
case TEEC_MEMREF_WHOLE:
res = teec_pre_process_whole(
&operation->params[n].memref,
params + n);
if (res != TEEC_SUCCESS)
return res;
break;
case TEEC_MEMREF_PARTIAL_INPUT:
case TEEC_MEMREF_PARTIAL_OUTPUT:
case TEEC_MEMREF_PARTIAL_INOUT:
res = teec_pre_process_partial(param_type,
&operation->params[n].memref, params + n);
if (res != TEEC_SUCCESS)
return res;
break;
default:
return TEEC_ERROR_BAD_PARAMETERS;
}
}
return TEEC_SUCCESS;
}
在 libteec 中,它们会映射到内核驱动的 struct tee_ioctl_param,底层转换逻辑如下:
1. 值的映射 (TEEC_VALUE_INPUT / OUTPUT / INOUT)
最直观,不涉及复杂的内存映射,直接做拷贝。
- params[n].a = operation->params[n].value.a;
- params[n].b = operation->params[n].value.b;
2. 内存引用的映射 (TEEC_MEMREF_TEMP_* / TEEC_REGISTERED_MEM_*)
这是性能与安全的焦点。如果 CA 传入的是一块普通的 Buffer 内存(TEEC_MEMREF_TEMP_INPUT),由于非安全世界用户态的虚拟地址(VA)安全世界根本无法直接访问,libteec 会执行以下高开销操作:
- 申请共享内存:通过 ioctl(fd, TEE_IOC_SHM_ALLOC, &shm_alloc_arg) 向内核 TEE 驱动申请一段专用的、连通非安全与安全世界的物理连续/非连续共享内存(Shared Memory)。
- 内容拷贝:如果涉及输入(Input),使用 memcpy 将用户态临时 Buffer 的内容拷贝到这段共享内存中。
- 记录标识:将分配到的 shm_num(共享内存 ID)和 offset 填入 tee_ioctl_param.c 和 tee_ioctl_param.a 中。
- 回写与销毁:在 teec_post_process_operation 中,如果是 Output,再把数据从共享内存拷回给 CA 的临时 Buffer,并调用 TEE_IOC_SHM_FREE 释放共享内存。

1113

被折叠的 条评论
为什么被折叠?



