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

📅 2026/6/24 2:14:01
TEE-OS学习轨迹第十七篇: 深入解析NS-EL1调用SMC的BL31异常处理流程
一、NS-EL1 调用 SMC 时BL31 内部的异常入口与路由基于 runtime_exceptions 异常向量表NS-EL1AArch64 非安全内核态执行 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_aarch643. 同步异常类别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_handlerNS-EL1AArch64发起的 SMCEC 值为 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 callers 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 callers 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_exit1.上下文保存调用 prepare_el3_entry将 NS 世界的通用寄存器、PAuth 寄存器完整保存到 CPU 上下文结构2.栈切换从 SP_EL3异常专用栈切换到 SP_EL0EL3 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-TEEBL32运行在S-EL1安全 EL1NS-EL1 无法直接跳转访问必须经过 EL3 的安全监视器BL31(ATF )做「世界切换」。承担这个转发角色的是 ATF 内置的OP-TEE SPDSecure 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 ~ 63SMCCC 规定的 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 规范所有发往可信操作系统的 SMCFunction 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_entrySPSR_EL3配置为 S-EL1 特权级、EL1h 栈模式、开中断等运行状态x0~x4原样保留 SMC 参数透传给 OP-TEE4el3_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 加载并启动 BL32OP-TEEOP-TEE 初始化完成后通过 TEESMC_OPTEED_RETURN_ENTRY_DONE 这个 SMC 返回 EL3opteed 会保存 OP-TEE 的向量表基地址后续所有 SMC 请求都固定转发到该入口。