TEE-TA学习轨迹第二篇:pseudo内置TA的注册和启动

一、编译期:内置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)注册,复用同一套分散数组机制:
  1. service_init宏展开后,生成一个struct initcall结构体,里面保存了函数指针verify_pseudo_tas_conformance。
  2. 该结构体被放到.scattered_array_service_initcall_2_*段中。
  3. 链接期所有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,完成启动前的安全校验:
  1. UUID唯一性校验:双重遍历所有内置TA,两两比对UUID,禁止重复UUID,防止TA冲突与恶意注入。
  2. 字段合法性校验:检查每个TA的name非空、必填标志位正确、无非法标志位、invoke_command_entry_point处理函数非空。
  3. 失败即终止:任意一项校验不通过,直接触发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);

底层执行流:
  1. libteec完成参数格式校验、类型转换,通过ioctl陷入Linux内核,调用OP-TEE字符设备驱动。
  2. Linux驱动完成参数合法性校验,大数据场景下分配共享内存并完成映射,按OP-TEE SMC ABI构造参数。
  3. 驱动执行smc #0指令,硬件触发同步异常,特权级从NS-EL1上升到EL3。
阶段2:EL3 OPTEED转发(EL3 → S-EL1)
  1. EL3异常向量表根据SMC的OEN(归属实体号),路由到opteed_smc_handler处理函数。
  2. 保存非安全世界完整的系统寄存器与通用寄存器上下文,防止被安全世界破坏。
  3. 内置TA调用通常走Fast SMC路径(原子短耗时、不可抢占),从全局保存的optee_vector_table中取出fast_smc_entry地址,写入ELR_EL3。
  4. 恢复安全世界系统寄存器,执行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层分发执行逻辑:
  1. thread_handle_fast_smc解析SMC功能号,识别为TA调用请求,提取UUID、命令ID、参数类型与值。
  2. 查询全局UUID哈希表,命中对应的内置TA实例。
  3. 首次调用时执行TA注册的open_session_entry_point,创建会话上下文。
  4. 直接调用invoke_command_entry_point处理函数,执行业务逻辑。
核心运行特性
  • 全程在当前异常栈执行,不创建新线程、不切换地址空间、屏蔽外部中断,原子执行。
  • 运行在S-EL1内核特权级,可直接访问硬件加密引擎、efuse、安全寄存器等所有内核资源。

四、结果返回链路

返回是调用的逆过程,分为小参数寄存器返回和大参数共享内存返回两种模式。

4.1 完整返回流程

  1. 安全侧收尾:TA函数执行完成,返回TEE_Result状态码,输出参数回填到栈上的SMC参数结构体;回到汇编入口后,将返回值加载到x1~x4寄存器,x0固定为TEESMC_OPTEED_RETURN_CALL_DONE,执行smc #0重新陷入EL3。
  2. EL3转发回非安全世界:OPTEED命中返回分支,保存安全世界上下文,恢复非安全世界的寄存器上下文,将返回值写入非安全侧对应寄存器,执行eret回到NS-EL1 Linux驱动。
  3. 非安全侧收尾:驱动读取返回状态,若使用了共享内存则完成数据同步与权限校验,最终ioctl返回用户态;libteec解析结果与参数,交付给上层应用。

4.2 两种传参模式

模式
适用场景
实现方式
寄存器传参
小整数、状态码、短指针
调用时x1~x7带入参,返回时x1~x4带结果,零拷贝、延迟极低
共享内存传参
加密数据、大结构体、文件内容
非安全侧分配双向映射的共享内存,寄存器仅传递物理地址与长度;安全侧校验地址合法性后直接访问
安全约束:共享内存会被标记为非安全属性,安全侧会执行严格的边界检查、地址合法性校验,防止非安全侧通过伪造指针越界访问安全内存。

五、核心设计总结

  1. 完全解耦的模块化设计:新增内置TA仅需新建源文件+一行注册宏,无需修改内核核心代码,自动被编译、注册、校验,扩展性与可审计性极强。
  2. 统一的分散数组范式:PTA注册、初始化函数、驱动注册全复用同一套分散数组框架,代码极简、逻辑一致,最小化可信计算基(TCB)。
  3. 启动期强制安全校验:通过service阶段的合规校验,从机制上保证所有内置TA的UUID唯一、字段合法,从启动源头阻断异常TA。
  4. 极致性能与高权限:内置TA运行在内核态,零加载开销、零地址空间切换开销,适合高频基础服务、硬件抽象、根密钥管理等高安全、高性能需求场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值