要理解从 ATF/OPTEED 跳转到 OP-TEE 初始化、以及运行时异常切入 OP-TEE 的机制,首先要明确两个核心前提:
- OP-TEE OS 运行在 Secure EL1 (S-EL1) 特权级,OPTEED 运行在 EL3(Secure Monitor) 特权级。EL3 是系统最高特权级,所有安全/非安全世界的切换、异常路由都必须经过 EL3 中转。
- 从 EL3 跳转到低异常级(S-EL1)只能通过 ERET 异常返回指令实现,无法用普通函数调用/分支指令。所有“跳转”本质都是:构造目标异常级的寄存器上下文 → 设置 ELR_EL3(目标地址)和 SPSR_EL3(目标程序状态)→ 执行 ERET,由硬件自动完成特权级切换。
下面结合代码,分两部分详细拆解。
一、首次跳转:进入 OP-TEE OS 初始化的完整流程
冷启动时 OPTEED 主动发起跳转,让 OP-TEE 执行自身内核初始化,这是一次性的启动握手流程,对应代码中 opteed_init() → opteed_init_with_entry_point() → opteed_synchronous_sp_entry() 调用链。
1. 跳转前:预构造 S-EL1 启动上下文
在跳转之前,OPTEED 会先准备好 OP-TEE 启动需要的完整寄存器现场,由 opteed_init_optee_ep_state() + cm_init_my_context() 完成:
if (!optee_ep_info->pc)
return 1;
opteed_rw = optee_ep_info->args.arg0;
opteed_pageable_part = optee_ep_info->args.arg1;
opteed_mem_limit = optee_ep_info->args.arg2;
dt_addr = optee_ep_info->args.arg3;
opteed_init_optee_ep_state(optee_ep_info,
opteed_rw,
optee_ep_info->pc,
opteed_pageable_part,
opteed_mem_limit,
dt_addr,
&opteed_sp_context[linear_id]);
/*
* All OPTEED initialization done. Now register our init function with
* BL31 for deferred invocation
*/
bl31_register_bl32_init(&opteed_init);
注册opteed_init为初始化函数:
#if !OPTEE_ALLOW_SMC_LOAD
static int32_t opteed_init(void)
{
entry_point_info_t *optee_entry_point;
/*
* Get information about the OP-TEE (BL32) image. Its
* absence is a critical failure.
*/
optee_entry_point = bl31_plat_get_next_image_ep_info(SECURE);
return opteed_init_with_entry_point(optee_entry_point);
}
#endif /* !OPTEE_ALLOW_SMC_LOAD */
static int32_t
opteed_init_with_entry_point(entry_point_info_t *optee_entry_point)
{
uint32_t linear_id = plat_my_core_pos();
optee_context_t *optee_ctx = &opteed_sp_context[linear_id];
uint64_t rc;
assert(optee_entry_point);
cm_init_my_context(optee_entry_point);
/*
* Arrange for an entry into OPTEE. It will be returned via
* OPTEE_ENTRY_DONE case
*/
rc = opteed_synchronous_sp_entry(optee_ctx);
assert(rc != 0);
return rc;
}
- 从 BL2 传递的镜像信息中,取出 OP-TEE 的启动入口地址(optee_ep_info->pc,即 OP-TEE 镜像的复位入口,不是后续的向量表)、架构位数(AArch32/AArch64)、启动参数(可分页区域地址、内存上限、DTB 地址)。
- 将入口地址写入安全上下文的 ELR_EL3(异常返回地址),配置 SPSR_EL3 为 S-EL1 特权级、对应指令集、中断掩码状态。
- 将启动参数(arg0~arg3)写入安全上下文的通用寄存器 x0~x3,作为 OP-TEE 启动的入参。
这一步的作用是:当 ERET 执行时,硬件会自动把这些上下文加载到 CPU 寄存器,OP-TEE 启动后可以直接获取参数。
2. 执行跳转:同步进入 OP-TEE 的底层机制
上下文准备完成后,调用 opteed_synchronous_sp_entry() 执行实际跳转。这是 OPTEED 配套的汇编辅助函数,核心动作:
- 保存当前 EL3 的调用现场(LR、Callee 寄存器)到 EL3 栈,保证 OP-TEE 返回后能回到 BL31 的 C 代码继续执行。
- 调用 cm_set_next_eret_context(SECURE),标记下一次 ERET 切换到安全世界上下文。
- 执行 ERET 指令:
- 硬件自动从安全上下文中恢复 S-EL1 的系统寄存器、通用寄存器
- PC 跳转到 ELR_EL3 指向的 OP-TEE 启动入口
- 特权级从 EL3 降到 S-EL1,安全状态保持 Secure
此时 CPU 正式进入 OP-TEE OS 的初始化代码,开始执行内核初始化、驱动初始化、TA 框架初始化等流程。
源码分析:
uint64_t opteed_synchronous_sp_entry(optee_context_t *optee_ctx)
{
uint64_t rc;
assert(optee_ctx != NULL);
assert(optee_ctx->c_rt_ctx == 0);
/* Apply the Secure EL1 system register context and switch to it */
assert(cm_get_context(SECURE) == &optee_ctx->cpu_ctx);
cm_el1_sysregs_context_restore(SECURE);
cm_set_next_eret_context(SECURE);
rc = opteed_enter_sp(&optee_ctx->c_rt_ctx);
#if ENABLE_ASSERTIONS
optee_ctx->c_rt_ctx = 0;
#endif
return rc;
}
汇编跳转到OPTTEE:
func opteed_enter_sp
/* Make space for the registers that we're going to save */
mov x3, sp
str x3, [x0, #0]
sub sp, sp, #OPTEED_C_RT_CTX_SIZE
/* Save callee-saved registers on to the stack */
stp x19, x20, [sp, #OPTEED_C_RT_CTX_X19]
stp x21, x22, [sp, #OPTEED_C_RT_CTX_X21]
stp x23, x24, [sp, #OPTEED_C_RT_CTX_X23]
stp x25, x26, [sp, #OPTEED_C_RT_CTX_X25]
stp x27, x28, [sp, #OPTEED_C_RT_CTX_X27]
stp x29, x30, [sp, #OPTEED_C_RT_CTX_X29]
/* ---------------------------------------------
* Everything is setup now. el3_exit() will
* use the secure context to restore to the
* general purpose and EL3 system registers to
* ERET into OPTEE.
* ---------------------------------------------
*/
b el3_exit
endfunc opteed_enter_sp
3. 初始化完成:OP-TEE 主动返回握手
OP-TEE 初始化全部完成后,会主动执行一条 SMC 指令,功能号为 TEESMC_OPTEED_RETURN_ENTRY_DONE,并通过 x1 寄存器返回自身的向量表基地址。
这条 SMC 会触发 EL3 同步异常,重新回到 OPTEED 的 opteed_smc_handler,命中对应分支:
/*******************************************************************************
* Address of the entrypoint vector table in OPTEE. It is
* initialised once on the primary core after a cold boot.
******************************************************************************/
struct optee_vectors *optee_vector_table;
/*******************************************************************************
* Array to keep track of per-cpu OPTEE state
******************************************************************************/
optee_context_t opteed_sp_context[OPTEED_CORE_COUNT];
case TEESMC_OPTEED_RETURN_ENTRY_DONE:
optee_vector_table = (optee_vectors_t *) x1; // 保存OP-TEE返回的向量表
set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON);
// 注册电源管理钩子、S-EL1中断处理函数
opteed_synchronous_sp_exit(optee_ctx, x1); // 回到同步调用点
保存状态:
/*******************************************************************************
* OPTEE PM state information e.g. OPTEE is suspended, uninitialised etc
* and macros to access the state information in the per-cpu 'state' flags
******************************************************************************/
#define OPTEE_PSTATE_OFF 1
#define OPTEE_PSTATE_ON 2
#define OPTEE_PSTATE_SUSPEND 3
#define OPTEE_PSTATE_UNKNOWN 0
#define OPTEE_PSTATE_SHIFT 0
#define OPTEE_PSTATE_MASK 0x3
#define get_optee_pstate(state) ((state >> OPTEE_PSTATE_SHIFT) & \
OPTEE_PSTATE_MASK)
#define clr_optee_pstate(state) (state &= ~(OPTEE_PSTATE_MASK \
<< OPTEE_PSTATE_SHIFT))
#define set_optee_pstate(st, pst) do { \
clr_optee_pstate(st); \
st |= (pst & OPTEE_PSTATE_MASK) << \
OPTEE_PSTATE_SHIFT; \
} while (0)
/*******************************************************************************
* This function takes an OPTEE context pointer and:
* 1. Saves the S-EL1 system register context tp optee_ctx->cpu_ctx.
* 2. Restores the current C runtime state (callee saved registers) from the
* stack frame using the reference to this state saved in opteed_enter_sp().
* 3. It does not need to save any general purpose or EL3 system register state
* as the generic smc entry routine should have saved those.
******************************************************************************/
void opteed_synchronous_sp_exit(optee_context_t *optee_ctx, uint64_t ret)
{
assert(optee_ctx != NULL);
/* Save the Secure EL1 system register context */
assert(cm_get_context(SECURE) == &optee_ctx->cpu_ctx);
cm_el1_sysregs_context_save(SECURE);
assert(optee_ctx->c_rt_ctx != 0);
opteed_exit_sp(optee_ctx->c_rt_ctx, ret);
/* Should never reach here */
assert(0);
}
/*******************************************************************************
* The next four functions are used by runtime services to save and restore
* EL1 context on the 'cpu_context' structure for the specified security
* state.
******************************************************************************/
void cm_el1_sysregs_context_save(uint32_t security_state)
{
cpu_context_t *ctx;
ctx = cm_get_context(security_state);
assert(ctx != NULL);
el1_sysregs_context_save(get_el1_sysregs_ctx(ctx));
#if IMAGE_BL31
if (security_state == SECURE)
PUBLISH_EVENT(cm_exited_secure_world);
else
PUBLISH_EVENT(cm_exited_normal_world);
#endif
}
void cm_el1_sysregs_context_restore(uint32_t security_state)
{
cpu_context_t *ctx;
ctx = cm_get_context(security_state);
assert(ctx != NULL);
el1_sysregs_context_restore(get_el1_sysregs_ctx(ctx));
#if IMAGE_BL31
if (security_state == SECURE)
PUBLISH_EVENT(cm_entering_secure_world);
else
PUBLISH_EVENT(cm_entering_normal_world);
#endif
}
/* ---------------------------------------------
* This function is called 'x0' pointing to a C
* runtime context saved in opteed_enter_sp(). It
* restores the saved registers and jumps to
* that runtime with 'x0' as the new sp. This
* destroys the C runtime context that had been
* built on the stack below the saved context by
* the caller. Later the second parameter 'x1'
* is passed as return value to the caller
* ---------------------------------------------
*/
.global opteed_exit_sp
func opteed_exit_sp
/* Restore the previous stack */
mov sp, x0
/* Restore callee-saved registers on to the stack */
ldp x19, x20, [x0, #(OPTEED_C_RT_CTX_X19 - OPTEED_C_RT_CTX_SIZE)]
ldp x21, x22, [x0, #(OPTEED_C_RT_CTX_X21 - OPTEED_C_RT_CTX_SIZE)]
ldp x23, x24, [x0, #(OPTEED_C_RT_CTX_X23 - OPTEED_C_RT_CTX_SIZE)]
ldp x25, x26, [x0, #(OPTEED_C_RT_CTX_X25 - OPTEED_C_RT_CTX_SIZE)]
ldp x27, x28, [x0, #(OPTEED_C_RT_CTX_X27 - OPTEED_C_RT_CTX_SIZE)]
ldp x29, x30, [x0, #(OPTEED_C_RT_CTX_X29 - OPTEED_C_RT_CTX_SIZE)]
/* ---------------------------------------------
* This should take us back to the instruction
* after the call to the last opteed_enter_sp().
* Place the second parameter to x0 so that the
* caller will see it as a return value from the
* original entry call
* ---------------------------------------------
*/
mov x0, x1
ret
endfunc opteed_exit_sp
- OPTEED 保存向量表后,后续所有运行时切入 OP-TEE 的操作都通过这张表跳转。
- 调用 opteed_synchronous_sp_exit 恢复之前保存的 EL3 调用现场,回到 opteed_synchronous_sp_entry 的调用处,BL31 继续后续启动流程。
至此,初始化跳转完成,OP-TEE 进入就绪可服务状态。
二、运行时:异常切入 OP-TEE OS 的两条核心路径
初始化完成后,OP-TEE 不会主动运行,所有切入 OP-TEE 的操作都由异常触发:先陷入 EL3,再由 OPTEED 路由转发到 OP-TEE。主流有两条路径。
路径1:非安全世界发起 SMC 请求(同步切入)
这是最常用的业务路径:非安全世界(Linux/Android)通过 SMC 指令调用 Trusted OS 服务,触发 EL3 同步异常,最终切入 OP-TEE。
完整流程对应 opteed_smc_handler 中 is_caller_non_secure(flags) 为真的分支:
if (is_caller_non_secure(flags)) {
#if OPTEE_ALLOW_SMC_LOAD
if (opteed_allow_load && smc_fid == NSSMC_OPTEED_CALL_UID) {
/* Provide the UUID of the image loading service. */
SMC_UUID_RET(handle, optee_image_load_uuid);
}
if (smc_fid == NSSMC_OPTEED_CALL_LOAD_IMAGE) {
/*
* TODO: Consider wiping the code for SMC loading from
* memory after it has been invoked similar to what is
* done under RECLAIM_INIT, but extended to happen
* later.
*/
if (!opteed_allow_load) {
SMC_RET1(handle, -EPERM);
}
opteed_allow_load = false;
uint64_t data_size = dual32to64(x1, x2);
uint64_t data_pa = dual32to64(x3, x4);
if (!data_size || !data_pa) {
/*
* This is invoked when the OP-TEE image didn't
* load correctly in the kernel but we want to
* block off loading of it later for security
* reasons.
*/
SMC_RET1(handle, -EINVAL);
}
SMC_RET1(handle, opteed_handle_smc_load(
data_size, data_pa));
}
#endif /* OPTEE_ALLOW_SMC_LOAD */
/*
* This is a fresh request from the non-secure client.
* The parameters are in x1 and x2. Figure out which
* registers need to be preserved, save the non-secure
* state and send the request to the secure payload.
*/
assert(handle == cm_get_context(NON_SECURE));
//保存非安全世界状态上下文
cm_el1_sysregs_context_save(NON_SECURE);
/*
* We are done stashing the non-secure context. Ask the
* OP-TEE to do the work now. If we are loading vi an SMC,
* then we also need to init this CPU context if not done
* already.
*/
if (optee_vector_table == NULL) {
SMC_RET1(handle, -EINVAL);
}
if (get_optee_pstate(optee_ctx->state) ==
OPTEE_PSTATE_UNKNOWN) {
opteed_cpu_on_finish_handler(0);
}
/*
* Verify if there is a valid context to use, copy the
* operation type and parameters to the secure context
* and jump to the fast smc entry point in the secure
* payload. Entry into S-EL1 will take place upon exit
* from this function.
*/
assert(&optee_ctx->cpu_ctx == cm_get_context(SECURE));
/* Set appropriate entry for SMC.
* We expect OPTEE to manage the PSTATE.I and PSTATE.F
* flags as appropriate.
*/
//设置跳转地址,根据是否同步
if (GET_SMC_TYPE(smc_fid) == SMC_TYPE_FAST) {
cm_set_elr_el3(SECURE, (uint64_t)
&optee_vector_table->fast_smc_entry);
} else {
cm_set_elr_el3(SECURE, (uint64_t)
&optee_vector_table->yield_smc_entry);
}
//恢复sel1的上下文
cm_el1_sysregs_context_restore(SECURE);
cm_set_next_eret_context(SECURE);
write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
CTX_GPREG_X4,
read_ctx_reg(get_gpregs_ctx(handle),
CTX_GPREG_X4));
write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
CTX_GPREG_X5,
read_ctx_reg(get_gpregs_ctx(handle),
CTX_GPREG_X5));
write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
CTX_GPREG_X6,
read_ctx_reg(get_gpregs_ctx(handle),
CTX_GPREG_X6));
/* Propagate hypervisor client ID */
write_ctx_reg(get_gpregs_ctx(&optee_ctx->cpu_ctx),
CTX_GPREG_X7,
read_ctx_reg(get_gpregs_ctx(handle),
CTX_GPREG_X7));
SMC_RET4(&optee_ctx->cpu_ctx, smc_fid, x1, x2, x3);
}
/*******************************************************************************
* This function is used to program the context that's used for exception
* return. This initializes the SP_EL3 to a pointer to a 'cpu_context' set for
* the required security state
******************************************************************************/
void cm_set_next_eret_context(uint32_t security_state)
{
cpu_context_t *ctx;
ctx = cm_get_context(security_state);
assert(ctx != NULL);
cm_set_next_context(ctx);
}
/* Inline definitions */
/*******************************************************************************
* This function is used to program the context that's used for exception
* return. This initializes the SP_EL3 to a pointer to a 'cpu_context' set for
* the required security state
******************************************************************************/
static inline void cm_set_next_context(void *context)
{
#if ENABLE_ASSERTIONS
uint64_t sp_mode;
/*
* Check that this function is called with SP_EL0 as the stack
* pointer
*/
__asm__ volatile("mrs %0, SPSel\n"
: "=r" (sp_mode));
assert(sp_mode == MODE_SP_EL0);
#endif /* ENABLE_ASSERTIONS */
__asm__ volatile("msr spsel, #1\n"
"mov sp, %0\n"
"msr spsel, #0\n"
: : "r" (context));
}
- 异常捕获:非安全世界执行 SMC 指令,硬件自动陷入 EL3;EL3 异常向量表根据 SMC 的 OEN 范围,路由到 OPTEED 的 opteed_smc_handler。
- 保存非安全上下文:调用 cm_el1_sysregs_context_save(NON_SECURE),保存当前非安全世界的 EL1 系统寄存器,防止切换后被破坏。
- 选择 OP-TEE 入口:根据 SMC 类型(Fast/Yield),从向量表中选择对应入口,写入安全上下文的 ELR_EL3:
if (GET_SMC_TYPE(smc_fid) == SMC_TYPE_FAST) {
cm_set_elr_el3(SECURE, (uint64_t)&optee_vector_table->fast_smc_entry);
} else {
cm_set_elr_el3(SECURE, (uint64_t)&optee_vector_table->yield_smc_entry);
}
- 传递请求参数:将 SMC 号、请求参数(x1~x7)从非安全上下文拷贝到安全上下文的通用寄存器。
- 切入 OP-TEE:恢复 S-EL1 系统寄存器上下文,调用 cm_set_next_eret_context(SECURE),最终通过 SMC_RET4 触发 ERET,跳转到 OP-TEE 的对应 SMC 处理入口。
OP-TEE 处理完请求后,同样通过 SMC 返回 EL3,OPTEED 再把结果写回非安全上下文,切回非安全世界。
路径2:安全世界 FIQ 中断触发(异步切入)
当 CPU 运行在非安全世界时,如果发生安全组中断(Secure Group FIQ),硬件会强制陷入 EL3,由 OPTEED 路由到 OP-TEE 处理,对应代码中的 opteed_sel1_interrupt_handler。
static uint64_t opteed_sel1_interrupt_handler(uint32_t id,
uint32_t flags,
void *handle,
void *cookie)
{
uint32_t linear_id;
optee_context_t *optee_ctx;
/* Check the security state when the exception was generated */
assert(get_interrupt_src_ss(flags) == NON_SECURE);
/* Sanity check the pointer to this cpu's context */
assert(handle == cm_get_context(NON_SECURE));
/* Save the non-secure context before entering the OPTEE */
cm_el1_sysregs_context_save(NON_SECURE);
/* Get a reference to this cpu's OPTEE context */
linear_id = plat_my_core_pos();
optee_ctx = &opteed_sp_context[linear_id];
assert(&optee_ctx->cpu_ctx == cm_get_context(SECURE));
cm_set_elr_el3(SECURE, (uint64_t)&optee_vector_table->fiq_entry);
cm_el1_sysregs_context_restore(SECURE);
cm_set_next_eret_context(SECURE);
/*
* Tell the OPTEE that it has to handle an FIQ (synchronously).
* Also the instruction in normal world where the interrupt was
* generated is passed for debugging purposes. It is safe to
* retrieve this address from ELR_EL3 as the secure context will
* not take effect until el3_exit().
*/
SMC_RET1(&optee_ctx->cpu_ctx, read_elr_el3());
}
完整流程:
- 中断触发:非安全世界运行时,安全外设产生 FIQ,硬件自动切换到 EL3;EL3 中断框架根据中断类型,路由到 OPTEED 注册的 S-EL1 中断处理函数。
- 保存非安全上下文:同 SMC 路径,先保存非安全世界的寄存器现场。
- 设置中断入口:将安全上下文的 ELR_EL3 设置为向量表中的 fiq_entry:
cm_set_elr_el3(SECURE, (uint64_t)&optee_vector_table->fiq_entry);
- 切入 OP-TEE:恢复 S-EL1 上下文,执行 ERET,跳转到 OP-TEE 的 FIQ 中断处理入口,由 OP-TEE 完成中断底半部处理。
- 中断返回:OP-TEE 处理完中断后,发送 TEESMC_OPTEED_RETURN_FIQ_DONE SMC 回到 EL3,OPTEED 恢复非安全上下文,切回非安全世界继续执行。
/*
* OPTEE has finished handling a S-EL1 FIQ interrupt. Execution
* should resume in the normal world.
*/
case TEESMC_OPTEED_RETURN_FIQ_DONE:
/* Get a reference to the non-secure context */
ns_cpu_context = cm_get_context(NON_SECURE);
assert(ns_cpu_context);
/*
* Restore non-secure state. There is no need to save the
* secure system register context since OPTEE was supposed
* to preserve it during S-EL1 interrupt handling.
*/
cm_el1_sysregs_context_restore(NON_SECURE);
cm_set_next_eret_context(NON_SECURE);
SMC_RET0((uint64_t) ns_cpu_context);
三、核心机制总结
- 统一的跳转方式:无论是初始化跳转还是运行时切入,从 EL3 到 S-EL1 的跳转都通过 ERET 异常返回实现,本质是“构造上下文 → 装载目标地址到 ELR_EL3 → 触发 ERET”的标准流程。
- 约定式 ABI 解耦:OPTEED 与 OP-TEE 之间没有编译期符号链接,完全通过约定的向量表结构 + SMC 号定义 + 寄存器传参规则 实现解耦,只要 ABI 一致,不同版本的 ATF 和 OP-TEE 可以互相适配。
- EL3 作为唯一中转:非安全世界永远不能直接调用 OP-TEE,所有请求和中断都必须经过 EL3 过滤、上下文保存、路由转发,这是 TrustZone 安全隔离的核心保障。

963

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



