TEE-OS学习轨迹第十七篇: 深入解析NS-EL1调用SMC的BL31异常处理流程

一、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 执行:
  1. 从上下文恢复 S-EL1 的所有通用寄存器、系统寄存器
  2. 配置 SCR_EL3.NS = 0 切换安全状态
  3. 执行 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 请求都固定转发到该入口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值