深入解析OP-TEE的libteec核心API实现
在 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。CTEEC_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。CTEEC_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。CTEEC_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释放共享内存。