EL3 确实要退出了,但并不是真正意义上面的退出,而是通过 ERET 指令降级到更低的异常级别(EL2 / EL1 / EL0)去执行下一个镜像。
函数名里的 "exit" 指的是 "退出 EL3 异常级别" 跳转到低权限级,而不是 EL3 固件关机或结束。
一、el3_exit 的完整执行流程
el3_exit 入口 │ ├─ ① 断言:当前必须在 SP_EL0 模式 (spsel == 0) │ ├─ ② 保存运行时栈指针 │ mov x17, sp ← 当前 SP_EL0 (运行时栈) │ msr spsel, #MODE_SP_ELX ← 切回 SP_EL3 │ str x17, [sp, ...CTX_RUNTIME_SP] ← 存回 context,下次 SMC 进入时可恢复 │ ├─ ③ 恢复 Per-World 上下文寄存器 │ ├─ get_per_world_context → 根据 SCR_EL3.NS 位获取当前安全状态的上下文 │ ├─ CPTR_EL3 ← 浮点/SIMD/PAuth 追踪控制 │ ├─ MPAM3_EL3 ← 内存分区和监控 (可选) │ └─ CVE-2018-3639 缓解 ← SSB 漏洞缓解状态恢复 │ ├─ ④ 同步错误 (BL31 only) │ synchronize_errors ← 确保之前的错误被同步 │ ├─ ⑤ 恢复关键 EL3 系统寄存器 ← ★ 决定"去哪、什么权限"的核心步骤 │ ldp x16, x17, [sp, CTX_SPSR_EL3] ← x16=SPSR_EL3, x17=ELR_EL3 │ ldr x18, [sp, CTX_SCR_EL3] ← SCR_EL3 (安全/非安全状态) │ ldr x19, [sp, CTX_MDCR_EL3] ← 调试控制 │ msr spsr_el3, x16 ← ★ 设置目标 PSTATE (决定目标 EL、SP、字节序等) │ msr elr_el3, x17 ← ★ 设置返回地址 (下一个镜像的入口点) │ msr scr_el3, x18 ← ★ 设置安全状态 (Secure/Non-Secure/Realm) │ msr mdcr_el3, x19 │ ├─ ⑥ restore_ptw_el1_sys_regs ← ERRATA_SPECULATIVE_AT 修复 │ ├─ ⑦ 恢复通用寄存器和 PAuth 密钥 │ bl restore_el3_runtime_regs ← 从 context 中恢复 x0-x29, SP_EL0, PAuth keys │ ldr x30, [sp, ...LR] ← 恢复 LR (返回地址寄存器) │ ├─ ⑧ 清除嵌套异常标志 (BL31 only) │ str xzr, [sp, CTX_NESTED_EA_FLAG] │ └─ ⑨ exception_return (ERET) ★ 关键指令 └── 原子操作: ├─ PC = ELR_EL3 ← 跳转到下一个镜像入口 ├─ PSTATE = SPSR_EL3 ← 降级到目标异常级别 └─ 安全状态 = SCR_EL3 ← 进入 Secure/Non-Secure二、ERET 到底做了什么?
exception_return 是 AArch64 的 ERET 指令,它的效果是不可分割的原子操作:
操作 | 说明 |
PC ← ELR_EL3 | 从ELR_EL3加载目标地址,即下一阶段 BL 的入口点 |
PSTATE ← SPSR_EL3 | 加载目标 PSTATE,决定 EL、SP 选择、字节序、A32/A64 模式 等 |
安全状态切换 | 由 SCR_EL3.NS /SCR_EL3.NSE 决定 (Secure/Non-Secure/Realm) |
三、结合BL1 场景——完整链路回顾
回顾 bl1_entrypoint.S
bl bl1_main ← C 初始化 ; ← bl1_main 返回后执行这里 #if BL2_RUNS_AT_EL3 b bl1_run_bl2_in_el3 ← 直接跳转 (不调用 el3_exit) #else b el3_exit ← 通过 ERET 切换到 EL2/EL1 #endif两个分支的区别:
分支 A:BL2_RUNS_AT_EL3(BL2 在 EL3 运行)
bl1_run_bl2_in_el3: ← bl1_entrypoint.S disable_mmu_icache_el3 ← 关 MMU 和 ICache (BL2 要重建页表) tlbi alle3 ← 刷 TLB ldp x0, x1, [x20, PC_OFFSET] ← ELR_EL3 = BL2 入口 msr elr_el3, x0 msr spsr_el3, x1 ← SPSR_EL3 = EL3 模式 ldp x0-x7, [x20, ARGS] ← 加载 BL2 参数 exception_return ← ERET → 但 SPSR 设的是 EL3 级别这种情况下 EL3 并没有真正降级——SPSR_EL3 的目标异常级别就是 EL3,所以 ERET 后 CPU 仍在 EL3,只是从 BL1 的代码跳到了 BL2 的入口。
分支 B:!BL2_RUNS_AT_EL3(BL2 在 Secure-EL1 或 EL2 运行)
b el3_exit ← 调用 el3_exitbl1_prepare_next_image() 已经把 BL2 的入口地址填入了 context 结构的 ELR_EL3,目标异常级别(EL2 或 S-EL1)填入了 SPSR_EL3。
el3_exit 执行 ERET → CPU 真正退出 EL3,降级到 EL2/S-EL1 运行 BL2。
四、"EL3 要是否退出"——两种情况对比
场景 | 调用路径 | SPSR_EL3 的目标 EL | ERET 后 CPU 级别 | BL2 所在位置 |
BL1 → BL2 (EL3模式) | bl1_run_bl2_in_el3 | EL3 | 仍在 EL3 | Trusted SRAM,EL3 |
BL1 → BL2 (非EL3模式) | el3_exit | EL2 或 S-EL1 | 降级到 EL2/EL1 | Secure RAM,非 EL3 |
BL31 → 内核 (正常启动) | el3_exit | Non-Secure EL2/EL1 | 降级到 NS-EL2/EL1 | 内核空间 |
所以:
el3_exit 这个名字描述的是机制(从 EL3 异常级别退出到更低级),但 SPSR_EL3 的值决定了它在实际使用中是"降级"还是"同级跳转"
在 bl1_run_bl2_in_el3 中,虽然也用了 exception_return,但因为 SPSR_EL3 指向 EL3,所以效果是 在同一 EL 内跳转,并非真正"退出"
五、el3_exit 的两个关键设计意图
设计点 | 原因 |
保存 SP_EL0 → context | 当前 EL3 的运行时栈指针保存到 context,下次通过 SMC 进入 EL3 时可以 prepare_el3_entry恢复,形成完整的进入/退出闭环 |
恢复 CPTR_EL3 / SCR_EL3 / PAuth 等 | 不同安全世界(Secure/Non-Secure/Realm)可能有不同的寄存器设置,每次切换时必须恢复对应世界的配置,否则安全隔离会被破坏 |
总结:el3_exit 是一个"世界切换器"——它从 context 中恢复目标世界的全部寄存器状态,然后通过 ERET 原子地完成异常级别降级和安全状态切换,让下一个镜像感觉自己一直在运行,从未被中断过。