一、编译期:内置TA的静态集成与宏展开
内置TA不是独立ELF文件,而是以内核模块的形式和OP-TEE内核共同编译、链接、签名。整个集成过程完全由你给出的分散数组(Scattered Array)宏驱动,开发者只需要一行pseudo_ta_register即可完成全部编译期配置。
1.1 核心注册宏的逐层展开
以RTC内置TA注册代码为例,我们从开发者编写的原始调用开始,逐步还原为编译器最终看到的真实C代码。
原始调用
pseudo_ta_register(
.uuid = PTA_RTC_UUID,
.name = PTA_NAME,
.flags = PTA_DEFAULT_FLAGS | TA_FLAG_CONCURRENT | TA_FLAG_DEVICE_ENUM,
.open_session_entry_point = open_session,
.invoke_command_entry_point = invoke_command
);
第1步:替换 pseudo_ta_register
该宏是对分散数组通用宏的封装,固定数组名为pseudo_tas,元素类型为struct pseudo_ta_head,__VA_ARGS__原样接收所有初始化参数:
SCATTERED_ARRAY_DEFINE_PG_ITEM(pseudo_tas, struct pseudo_ta_head) =
{
.uuid = PTA_RTC_UUID,
.name = PTA_NAME,
.flags = PTA_DEFAULT_FLAGS | TA_FLAG_CONCURRENT | TA_FLAG_DEVICE_ENUM,
.open_session_entry_point = open_session,
.invoke_command_entry_point = invoke_command
};
第2步:替换 SCATTERED_ARRAY_DEFINE_PG_ITEM
传入数组名、排序序号、编译器内置计数器__COUNTER__、元素类型四个参数。__COUNTER__每调用一次自动+1,保证生成的变量名全局唯一,假设本次值为0:
__SCT_ARRAY_DEF_PG_ITEM1(pseudo_tas, 0, 0, struct pseudo_ta_head) =
{ /* 初始化参数同上 */ };
第3步:中转宏 __SCT_ARRAY_DEF_PG_ITEM1
纯中转层,保证__COUNTER__在上一层先完成求值,避免嵌套宏展开顺序问题,参数透传:
__SCT_ARRAY_DEF_PG_ITEM1(pseudo_tas, 0, 0, struct pseudo_ta_head) =
{ /* 初始化参数同上 */ };
第4步:__SCT_ARRAY_DEF_PG_ITEM2 拼接名称
这是核心层,通过##令牌粘贴生成唯一变量名,通过#字符串化生成链接段名:
- 变量名:__scattered_array_ ## id ## _ ## array_name → __scattered_array_0_pseudo_tas
- 段名:".scattered_array_" #array_name "_1_" #order → ".scattered_array_pseudo_tas_1_0"
替换后:
__SCT_ARRAY_DEF_PG_ITEM3(
struct pseudo_ta_head,
__scattered_array_0_pseudo_tas,
".scattered_array_pseudo_tas_1_0"
) = { /* 初始化参数同上 */ };
第5步:最内层生成最终代码
__SCT_ARRAY_DEF_PG_ITEM3 完成最终的静态变量定义,附带编译器保活属性与段属性:
static const struct pseudo_ta_head __scattered_array_0_pseudo_tas __used
__section(".scattered_array_pseudo_tas_1_0")
= {
.uuid = PTA_RTC_UUID,
.name = PTA_NAME,
.flags = PTA_DEFAULT_FLAGS | TA_FLAG_CONCURRENT | TA_FLAG_DEVICE_ENUM,
.open_session_entry_point = open_session,
.invoke_command_entry_point = invoke_command
};
展开结果逐行释义
- static const:文件内可见,只读不可修改,最终放入只读数据段,运行时无法篡改。
- __used:编译器层面保活,即使没有代码显式引用,也不会被当成死代码优化删除。
- __section(...):强制将该结构体放到指定链接段中,所有内置TA都会被放到同名前缀的段内。
1.2 链接期:分散数组合并
链接器根据链接脚本规则,将所有源文件中.scattered_array_pseudo_tas_*段的内容合并为一段连续内存,形成struct pseudo_ta_head类型的结构体数组,并自动生成首尾符号:
- __start_scattered_array_pseudo_tas:数组起始地址
- __end_scattered_array_pseudo_tas:数组结束地址
代码中的SCATTERED_ARRAY_BEGIN / SCATTERED_ARRAY_END宏就是对这两个符号的封装,用于遍历整个数组。
1.3 合规校验函数的自动注册
你给出的verify_pseudo_tas_conformance通过service_init(verify_pseudo_tas_conformance)注册,复用同一套分散数组机制:
- service_init宏展开后,生成一个struct initcall结构体,里面保存了函数指针verify_pseudo_tas_conformance。
- 该结构体被放到.scattered_array_service_initcall_2_*段中。
- 链接期所有service阶段的初始化函数合并为service_initcall分散数组,启动时批量执行。
二、启动期:内置TA的注册与合规校验
内置TA的注册与校验完全复用OP-TEE通用的「分级初始化 + 分散数组遍历」框架,全程对应你给出的initcall.h分级机制。
2.1 OP-TEE分级初始化框架
OP-TEE将冷启动流程划分为多个串行执行的初始化阶段,每个阶段对应一个分散数组;启动时按顺序调用对应call_xxx()函数,遍历数组中所有注册的回调函数执行。
|
初始化阶段
|
触发函数
|
对应注册宏
|
核心作用
|
|
预初始化
|
call_preinitcalls()
|
preinit_early
/
preinit
/
preinit_late
|
最早期硬件与核心机制准备
|
|
早期初始化
|
call_early_initcalls()
|
early_init
/
early_init_late
|
内存、中断、加密等核心子系统初始化
|
|
服务初始化
|
call_service_initcalls()
|
service_init_crypto
/
service_init
/
service_init_late
|
业务服务注册、合规性校验
|
|
驱动初始化
|
call_driver_initcalls()
|
driver_init
/
driver_init_late
|
外设驱动初始化与资源释放
|
|
最终收尾
|
call_finalcalls()
|
boot_final
|
启动收尾,准备首次切回非安全世界
|
initial call定义:
#ifndef __INITCALL_H
#define __INITCALL_H
#include <scattered_array.h>
#include <tee_api_types.h>
#include <trace.h>
struct initcall {
TEE_Result (*func)(void);
#if TRACE_LEVEL >= TRACE_DEBUG
int level;
const char *func_name;
#endif
};
#if TRACE_LEVEL >= TRACE_DEBUG
#define __define_initcall(type, lvl, fn) \
SCATTERED_ARRAY_DEFINE_PG_ITEM_ORDERED(type ## call, lvl, \
struct initcall) = \
{ .func = (fn), .level = (lvl), .func_name = #fn, }
#else
#define __define_initcall(type, lvl, fn) \
SCATTERED_ARRAY_DEFINE_PG_ITEM_ORDERED(type ## call, lvl, \
struct initcall) = \
{ .func = (fn), }
#endif
#define preinitcall_begin \
SCATTERED_ARRAY_BEGIN(preinitcall, struct initcall)
#define preinitcall_end SCATTERED_ARRAY_END(preinitcall, struct initcall)
#define early_initcall_begin \
SCATTERED_ARRAY_BEGIN(early_initcall, struct initcall)
#define early_initcall_end \
SCATTERED_ARRAY_END(early_initcall, struct initcall)
#define service_initcall_begin \
SCATTERED_ARRAY_BEGIN(service_initcall, struct initcall)
#define service_initcall_end \
SCATTERED_ARRAY_END(service_initcall, struct initcall)
#define driver_initcall_begin \
SCATTERED_ARRAY_BEGIN(driver_initcall, struct initcall)
#define driver_initcall_end \
SCATTERED_ARRAY_END(driver_initcall, struct initcall)
#define finalcall_begin SCATTERED_ARRAY_BEGIN(finalcall, struct initcall)
#define finalcall_end SCATTERED_ARRAY_END(finalcall, struct initcall)
/*
* The preinit_*(), *_init() and boot_final() macros are used to register
* callback functions to be called at different stages during
* initialization.
*
* Functions registered with preinit_*() are always called before functions
* registered with *_init().
*
* Functions registered with boot_final() are called before exiting to
* normal world the first time.
*
* Without virtualization this happens in the order of the defines below.
*
* However, with virtualization things are a bit different. boot_final()
* functions are called first before exiting to normal world the first
* time. Functions registered with boot_final() can only operate on the
* nexus. preinit_*() functions are called early before the first yielding
* call into the partition, in the newly created partition. *_init()
* functions are called at the first yielding call.
*
* +-------------------------------+-----------------------------------+
* | Without virtualization | With virtualization |
* +-------------------------------+-----------------------------------+
* | At the end of boot_init_primary_late() just before the print: |
* | "Primary CPU switching to normal world boot" |
* +-------------------------------+-----------------------------------+
* | 1. call_preinitcalls() | In the nexus, final calls |
* | 2. call_initcalls() +-----------------------------------+
* | 3. call_finalcalls() | 1. nex_*init*() / boot_final() |
* +-------------------------------+-----------------------------------+
* | "Primary CPU switching to normal world boot" is printed |
* +-------------------------------+-----------------------------------+
* | A guest is created and |
* | virt_guest_created() is called. |
* | After the partition has been |
* | created and activated. |
* +-----------------------------------+
* | 2. call_preinitcalls() |
* +-----------------------------------+
* | When the partition is receiving |
* | the first yielding call |
* | virt_on_stdcall() is called. |
* +-----------------------------------+
* | 3. call_initcalls() |
* +-----------------------------------+
*/
#define preinit_early(fn) __define_initcall(preinit, 1, fn)
#define preinit(fn) __define_initcall(preinit, 2, fn)
#define preinit_late(fn) __define_initcall(preinit, 3, fn)
#define early_init(fn) __define_initcall(early_init, 1, fn)
#define early_init_late(fn) __define_initcall(early_init, 2, fn)
#define service_init_crypto(fn) __define_initcall(service_init, 1, fn)
#define service_init(fn) __define_initcall(service_init, 2, fn)
#define service_init_late(fn) __define_initcall(service_init, 3, fn)
#define driver_init(fn) __define_initcall(driver_init, 1, fn)
#define driver_init_late(fn) __define_initcall(driver_init, 2, fn)
#define release_init_resource(fn) __define_initcall(driver_init, 3, fn)
入口定义:
static TEE_Result verify_pseudo_tas_conformance(void)
{
const struct pseudo_ta_head *start =
SCATTERED_ARRAY_BEGIN(pseudo_tas, struct pseudo_ta_head);
const struct pseudo_ta_head *end =
SCATTERED_ARRAY_END(pseudo_tas, struct pseudo_ta_head);
const struct pseudo_ta_head *pta;
for (pta = start; pta < end; pta++) {
const struct pseudo_ta_head *pta2;
/* PTAs must all have a specific UUID */
for (pta2 = pta + 1; pta2 < end; pta2++) {
if (!memcmp(&pta->uuid, &pta2->uuid, sizeof(TEE_UUID)))
goto err;
}
if (!pta->name ||
(pta->flags & PTA_MANDATORY_FLAGS) != PTA_MANDATORY_FLAGS ||
pta->flags & ~PTA_ALLOWED_FLAGS ||
!pta->invoke_command_entry_point)
goto err;
}
return TEE_SUCCESS;
err:
DMSG("pseudo TA error at %p", (void *)pta);
panic("PTA");
}
service_init(verify_pseudo_tas_conformance);
对应冷启动调用栈:
_start (entry_a64.S 汇编入口)
↓
boot_init_primary_late / boot_init_primary_final
↓
call_service_initcalls() ← 内置TA合规校验在此阶段执行
↓
后续初始化与首次切出
2.2 内置TA的两步启动流程
第一步:TA子系统遍历注册
在早期初始化阶段,TA管理子系统初始化时,直接遍历pseudo_tas分散数组,将所有内置TA录入全局UUID调度哈希表:
// 基于分散数组范式的注册逻辑,与校验复用同一张数组
const struct pseudo_ta_head *pta;
SCATTERED_ARRAY_FOREACH(pta, pseudo_tas, struct pseudo_ta_head) {
tee_ta_register_pseudo_ta(pta); // 插入全局UUID哈希表
}
说明:代码中未包含注册函数本体,但基于分散数组的统一设计范式,注册逻辑与后续校验逻辑复用同一张数组,保证注册与校验的对象完全一致。
第二步:service阶段强制合规校验
启动进入服务初始化阶段时,call_service_initcalls()遍历service_initcall数组,自动执行verify_pseudo_tas_conformance,完成启动前的安全校验:
- UUID唯一性校验:双重遍历所有内置TA,两两比对UUID,禁止重复UUID,防止TA冲突与恶意注入。
- 字段合法性校验:检查每个TA的name非空、必填标志位正确、无非法标志位、invoke_command_entry_point处理函数非空。
- 失败即终止:任意一项校验不通过,直接触发panic终止启动,避免异常TA进入运行态带来安全风险。
校验通过后,所有内置TA正式进入待命状态,全程常驻安全内存,系统任意时刻都可通过UUID调用。
三、运行期:NS-EL0应用调用内置TA全链路
非安全世界的用户态应用(Android App / HAL层程序,即NS-EL0)调用内置TA,是一条完整的四级特权级上升链路,全程对上层透明,严格对应之前分析的SMC、向量表、Fast SMC入口机制。
3.1 整体调用全景
NS-EL0 用户应用(Android App / HAL 服务)
↓ 标准 GlobalPlatform TEEC API
libteec 客户端库
↓ ioctl 系统调用
NS-EL1 Linux OP-TEE 字符设备驱动
↓ smc #0 触发同步异常
EL3 ATF OPTEED 安全监控器
↓ eret 切入安全世界
S-EL1 OP-TEE Fast SMC 入口
↓ UUID 哈希表匹配调度
内置 TA 内核态函数执行
3.2 逐段拆解执行细节
阶段1:非安全用户态发起调用(NS-EL0 → NS-EL1)
非安全侧使用标准TEE Client API调用,完全感知不到TA是内置还是动态加载:
TEEC_Context ctx;
TEEC_Session sess;
TEEC_Operation op;
uint32_t origin;
// 1. 初始化上下文,打开/dev/tee设备
TEEC_InitializeContext(NULL, &ctx);
// 2. 打开会话,通过UUID匹配目标TA
TEEC_OpenSession(&ctx, &sess, &pta_rtc_uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, &origin);
// 3. 填充参数,调用具体命令
memset(&op, 0, sizeof(op));
op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_OUTPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
TEEC_InvokeCommand(&sess, PTA_RTC_CMD_GET_TIME, &op, &origin);
底层执行流:
- libteec完成参数格式校验、类型转换,通过ioctl陷入Linux内核,调用OP-TEE字符设备驱动。
- Linux驱动完成参数合法性校验,大数据场景下分配共享内存并完成映射,按OP-TEE SMC ABI构造参数。
- 驱动执行smc #0指令,硬件触发同步异常,特权级从NS-EL1上升到EL3。
阶段2:EL3 OPTEED转发(EL3 → S-EL1)
- EL3异常向量表根据SMC的OEN(归属实体号),路由到opteed_smc_handler处理函数。
- 保存非安全世界完整的系统寄存器与通用寄存器上下文,防止被安全世界破坏。
- 内置TA调用通常走Fast SMC路径(原子短耗时、不可抢占),从全局保存的optee_vector_table中取出fast_smc_entry地址,写入ELR_EL3。
- 恢复安全世界系统寄存器,执行eret指令,特权级降至S-EL1,PC跳转到OP-TEE的vector_fast_smc_entry。
阶段3:S-EL1内核分发与执行
对应thread_optee_smc_a64.S中的快速调用入口:
LOCAL_FUNC vector_fast_smc_entry , : , .identity_map
readjust_pc // ASLR地址重定位,修正PC到运行时虚拟地址
sub sp, sp, #THREAD_SMC_ARGS_SIZE
store_xregs sp, THREAD_SMC_ARGS_X0, 0, 7 // x0~x7入栈保存
mov x0, sp
bl thread_handle_fast_smc // 进入C语言分发逻辑
...
C层分发执行逻辑:
- thread_handle_fast_smc解析SMC功能号,识别为TA调用请求,提取UUID、命令ID、参数类型与值。
- 查询全局UUID哈希表,命中对应的内置TA实例。
- 首次调用时执行TA注册的open_session_entry_point,创建会话上下文。
- 直接调用invoke_command_entry_point处理函数,执行业务逻辑。
核心运行特性:
- 全程在当前异常栈执行,不创建新线程、不切换地址空间、屏蔽外部中断,原子执行。
- 运行在S-EL1内核特权级,可直接访问硬件加密引擎、efuse、安全寄存器等所有内核资源。
四、结果返回链路
返回是调用的逆过程,分为小参数寄存器返回和大参数共享内存返回两种模式。
4.1 完整返回流程
- 安全侧收尾:TA函数执行完成,返回TEE_Result状态码,输出参数回填到栈上的SMC参数结构体;回到汇编入口后,将返回值加载到x1~x4寄存器,x0固定为TEESMC_OPTEED_RETURN_CALL_DONE,执行smc #0重新陷入EL3。
- EL3转发回非安全世界:OPTEED命中返回分支,保存安全世界上下文,恢复非安全世界的寄存器上下文,将返回值写入非安全侧对应寄存器,执行eret回到NS-EL1 Linux驱动。
- 非安全侧收尾:驱动读取返回状态,若使用了共享内存则完成数据同步与权限校验,最终ioctl返回用户态;libteec解析结果与参数,交付给上层应用。
4.2 两种传参模式
|
模式
|
适用场景
|
实现方式
|
|
寄存器传参
|
小整数、状态码、短指针
|
调用时x1~x7带入参,返回时x1~x4带结果,零拷贝、延迟极低
|
|
共享内存传参
|
加密数据、大结构体、文件内容
|
非安全侧分配双向映射的共享内存,寄存器仅传递物理地址与长度;安全侧校验地址合法性后直接访问
|
安全约束:共享内存会被标记为非安全属性,安全侧会执行严格的边界检查、地址合法性校验,防止非安全侧通过伪造指针越界访问安全内存。
五、核心设计总结
- 完全解耦的模块化设计:新增内置TA仅需新建源文件+一行注册宏,无需修改内核核心代码,自动被编译、注册、校验,扩展性与可审计性极强。
- 统一的分散数组范式:PTA注册、初始化函数、驱动注册全复用同一套分散数组框架,代码极简、逻辑一致,最小化可信计算基(TCB)。
- 启动期强制安全校验:通过service阶段的合规校验,从机制上保证所有内置TA的UUID唯一、字段合法,从启动源头阻断异常TA。
- 极致性能与高权限:内置TA运行在内核态,零加载开销、零地址空间切换开销,适合高频基础服务、硬件抽象、根密钥管理等高安全、高性能需求场景。

609

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



