一、NS-EL1 调用 SMC 时,BL31 内部的异常入口与路由
基于 runtime_exceptions 异常向量表,NS-EL1(AArch64 非安全内核态)执行 SMC 指令后,会严格按照以下路径在 BL31 内部完成异常入口与分发。
1. 异常向量入口命中
ARMv8 异常向量表按「当前异常级别 + 栈指针 + 低级别架构」分为 4 组,每组 4 个异常类型。NS-EL1 属于「低于 EL3 的 AArch64 异常级别」,触发同步异常(SMC 属于同步异常)时,固定命中:
.globl sync_exception_aarch64
.globl irq_aarch64
.globl fiq_aarch64
.globl serror_aarch64
vector_entry sync_exception_aarch64 // 0x400 ~ 0x600 区间:Lower EL using AArch64 的同步异常
2. 向量入口前置处理
进入 sync_exception_aarch64 后,依次执行 4 步前置操作:
vector_entry sync_exception_aarch64
/*
* This exception vector will be the entry point for SMCs and traps
* that are unhandled at lower ELs most commonly. SP_EL3 should point
* to a valid cpu context where the general purpose and system register
* state can be saved.
*/
save_x30 // 1. 保存 LR(x30) 到 CPU 上下文,释放 x30 给后续逻辑使用
apply_at_speculative_wa // 2. 执行推测执行漏洞硬件缓解补丁
sync_and_handle_pending_serror // 3. 同步错误,检查待处理 SError,按 FFH/KFH 模式处理或反射
unmask_async_ea // 4. 取消异步外部中止屏蔽,允许 EL3 处理异步错误
handle_sync_exception // 进入同步异常分发核心逻辑
end_vector_entry sync_exception_aarch64
3. 同步异常类别(EC)判断
在 handle_sync_exception 宏中,读取 ESR_EL3 寄存器提取异常类别 EC,按类型分流:
.macro handle_sync_exception
#if ENABLE_RUNTIME_INSTRUMENTATION
/*
* Read the timestamp value and store it in per-cpu data. The value
* will be extracted from per-cpu data by the C level SMC handler and
* saved to the PMF timestamp region.
*/
mrs x30, cntpct_el0
str x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X29]
mrs x29, tpidr_el3
str x30, [x29, #CPU_DATA_PMF_TS0_OFFSET]
ldr x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X29]
#endif
mrs x30, esr_el3
ubfx x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH // 提取 EC 字段
/* Handle SMC exceptions separately from other synchronous exceptions */
cmp x30, #EC_AARCH32_SMC
b.eq smc_handler32 // 32位SMC → smc_handler32
cmp x30, #EC_AARCH64_SMC
b.eq sync_handler64
cmp x30, #EC_AARCH64_SYS
b.eq sync_handler64 // 64位SMC → sync_handler64
cmp x30, #EC_IMP_DEF_EL3
b.eq imp_def_el3_handler
NS-EL1(AArch64)发起的 SMC,EC 值为 EC_AARCH64_SMC,因此跳转到 sync_handler64,进入 SMC 运行时服务分发逻辑。
4. SMC 运行时服务分发(核心路由)
sync_handler64 位于 sync_exception_handler 函数内,是所有 AArch64 SMC 的统一分发入口,流程如下:
sync_handler64:
/* NOTE: The code below must preserve x0-x4 */
/*
* Save general purpose and ARMv8.3-PAuth registers (if enabled).
* Also save PMCR_EL0 and set the PSTATE to a known state.
*/
bl prepare_el3_entry
#if ENABLE_PAUTH
/* Load and program APIAKey firmware key */
bl pauth_load_bl31_apiakey
#endif
/*
* Populate the parameters for the SMC handler.
* We already have x0-x4 in place. x5 will point to a cookie (not used
* now). x6 will point to the context structure (SP_EL3) and x7 will
* contain flags we need to pass to the handler.
*/
mov x5, xzr
mov x6, sp
/*
* Restore the saved C runtime stack value which will become the new
* SP_EL0 i.e. EL3 runtime stack. It was saved in the 'cpu_context'
* structure prior to the last ERET from EL3.
*/
ldr x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
/* Switch to SP_EL0 */
msr spsel, #MODE_SP_EL0
/*
* Save the SPSR_EL3 and ELR_EL3 in case there is a world
* switch during SMC handling.
* TODO: Revisit if all system registers can be saved later.
*/
mrs x16, spsr_el3
mrs x17, elr_el3
stp x16, x17, [x6, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
/* Load SCR_EL3 */
mrs x18, scr_el3
/* check for system register traps */
mrs x16, esr_el3
ubfx x17, x16, #ESR_EC_SHIFT, #ESR_EC_LENGTH
cmp x17, #EC_AARCH64_SYS
b.eq sysreg_handler64
/* Clear flag register */
mov x7, xzr
#if ENABLE_RME
/* Copy SCR_EL3.NSE bit to the flag to indicate caller's security */
ubfx x7, x18, #SCR_NSE_SHIFT, #1
/*
* Shift copied SCR_EL3.NSE bit by 5 to create space for
* SCR_EL3.NS bit. Bit 5 of the flag corresponds to
* the SCR_EL3.NSE bit.
*/
lsl x7, x7, #5
#endif /* ENABLE_RME */
/* Copy SCR_EL3.NS bit to the flag to indicate caller's security */
bfi x7, x18, #0, #1
mov sp, x12
/*
* Per SMCCC documentation, bits [23:17] must be zero for Fast
* SMCs. Other values are reserved for future use. Ensure that
* these bits are zeroes, if not report as unknown SMC.
*/
tbz x0, #FUNCID_TYPE_SHIFT, 2f /* Skip check if its a Yield Call*/
tst x0, #(FUNCID_FC_RESERVED_MASK << FUNCID_FC_RESERVED_SHIFT)
b.ne smc_unknown
/*
* Per SMCCCv1.3 a caller can set the SVE hint bit in the SMC FID
* passed through x0. Copy the SVE hint bit to flags and mask the
* bit in smc_fid passed to the standard service dispatcher.
* A service/dispatcher can retrieve the SVE hint bit state from
* flags using the appropriate helper.
*/
2:
and x16, x0, #(FUNCID_SVE_HINT_MASK << FUNCID_SVE_HINT_SHIFT)
orr x7, x7, x16
bic x0, x0, #(FUNCID_SVE_HINT_MASK << FUNCID_SVE_HINT_SHIFT)
/* Get the unique owning entity number */
ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
/* Load descriptor index from array of indices */
adrp x14, rt_svc_descs_indices
add x14, x14, :lo12:rt_svc_descs_indices
ldrb w15, [x14, x16]
/* Any index greater than 127 is invalid. Check bit 7. */
tbnz w15, 7, smc_unknown
/*
* Get the descriptor using the index
* x11 = (base + off), w15 = index
*
* handler = (base + off) + (index << log2(size))
*/
adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
lsl w10, w15, #RT_SVC_SIZE_LOG2
ldr x15, [x11, w10, uxtw]
/*
* Call the Secure Monitor Call handler and then drop directly into
* el3_exit() which will program any remaining architectural state
* prior to issuing the ERET to the desired lower EL.
*/
#if DEBUG
cbz x15, rt_svc_fw_critical_error
#endif
blr x15
b el3_exit
1.上下文保存:调用 prepare_el3_entry,将 NS 世界的通用寄存器、PAuth 寄存器完整保存到 CPU 上下文结构
2.栈切换:从 SP_EL3(异常专用栈)切换到 SP_EL0(EL3 C 语言运行时栈),为调用 C 函数做准备
2.参数封装:保留 x0~x4 的 SMC 入参,x6 指向 CPU 上下文,x7 携带安全状态、SVE 提示等标志位
4.服务索引计算:
ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH // 提取 SMC FID 的 OEN(所属服务组)
ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH // 提取调用类型:Fast / Yielding
orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH // 组合成服务索引
5.查表分发:
adrp x14, rt_svc_descs_indices
ldrb w15, [x14, x16] // 从索引表查对应服务的描述符下标
adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
lsl w10, w15, #RT_SVC_SIZE_LOG2
ldr x15, [x11, w10, uxtw] // 取出对应服务的 handler 函数指针
blr x15 // 调用对应运行时服务的处理函数
6.异常返回:服务处理完成后,调用 el3_exit 恢复上下文,通过 ERET 指令返回 NS-EL1。
二、从 BL31 路由到 OP-TEE 运行实体的链路
OP-TEE(BL32)运行在 S-EL1(安全 EL1),NS-EL1 无法直接跳转访问,必须经过 EL3 的安全监视器BL31(ATF )做「世界切换」。承担这个转发角色的,是 ATF 内置的 OP-TEE SPD(Secure Payload Dispatcher,简称 opteed)。
1. opteed 的注册:预先绑定 SMC 范围
opteed 在 BL31 启动阶段,通过 DECLARE_RT_SVC 宏向运行时服务框架注册了两个服务实例:
// 快速 SMC 服务:不可中断,用于简单控制指令
DECLARE_RT_SVC(
optee_fast,
OEN_TOS_START, OEN_TOS_END, // OEN 范围:50 ~ 63(SMCCC 规定的 Trusted OS 组)
SMC_TYPE_FAST,
opteed_setup,
opteed_smc_handler
);
// 标准 Yielding SMC 服务:可中断挂起,用于 TA 调用等复杂业务
DECLARE_RT_SVC(
optee_std,
OEN_TOS_START, OEN_TOS_END,
SMC_TYPE_YIELD,
NULL,
opteed_smc_handler
);
对应 SMCCC 规范,所有发往可信操作系统的 SMC,Function ID 的 OEN 字段(bit29:24)都属于 50~63 范围。因此 NS-EL1 发起的 OP-TEE 相关 SMC,都会被上一步的分发逻辑路由到 opteed_smc_handler 函数。
2. NS → OP-TEE 的世界切换流程
opteed_smc_handler 收到请求后,负责完成「非安全世界 → 安全世界」的上下文切换与跳转,核心步骤:
(1)确认 NS 上下文已归档
前面 prepare_el3_entry 已经把 NS-EL1 的完整寄存器状态、系统寄存器都存入了 NS 上下文结构体,opteed 只需确认上下文位置,无需重复保存。
(2)切换到 OP-TEE 安全上下文
每个 CPU 都维护两套独立的上下文:NS(非安全)和 S(安全)。opteed 从 per-CPU 的 opteed_sp_context 中,取出 OP-TEE 的 S-EL1 上下文,替换当前的上下文指针。这套上下文在 OP-TEE 初始化完成时就已保存,包含 OP-TEE 的栈指针、页表基址、异常向量基址等完整运行状态。
(3)配置返回目标与安全状态
opteed 修改上下文结构体中的关键字段,决定 el3_exit 后 CPU 的去向:
- SCR_EL3.NS = 0:标记 ERET 后进入安全世界
- ELR_EL3 = OP-TEE SMC 入口:指向 OP-TEE 内部的 SMC 向量表(如 vector_std_smc_entry / vector_fast_smc_entry)
- SPSR_EL3:配置为 S-EL1 特权级、EL1h 栈模式、开中断等运行状态
- x0~x4:原样保留 SMC 参数,透传给 OP-TEE
(4)el3_exit 完成最终跳转
opteed_smc_handler 返回后,sync_handler64 调用 el3_exit。此时上下文已替换为安全世界的快照,el3_exit 执行:
- 从上下文恢复 S-EL1 的所有通用寄存器、系统寄存器
- 配置 SCR_EL3.NS = 0 切换安全状态
- 执行 ERET 指令,CPU 从 EL3 退出,跳转到 OP-TEE 的 S-EL1 异常向量入口
补充:两类 SMC 的差异
- Fast SMC:关中断执行,不支持挂起,用于简单操作(如版本查询、状态控制),全程不涉及 OP-TEE 线程调度
- Yielding SMC:开中断执行,支持线程挂起与 RPC 交互,是业务层 TA 调用、加密服务的主路径,也是最常用的类型
三、初始化阶段的关联
BL31 启动时会通过 opteed 加载并启动 BL32(OP-TEE),OP-TEE 初始化完成后通过 TEESMC_OPTEED_RETURN_ENTRY_DONE 这个 SMC 返回 EL3,opteed 会保存 OP-TEE 的向量表基地址,后续所有 SMC 请求都固定转发到该入口。

963

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



