bl2_entrypoint汇编→ bl2_mainC这条路径跨越两个文件、两个异常级别从 BL1 的 ERET 着陆点开始到 BL2 加载完所有镜像并跳转到下一阶段结束。第一阶段bl2/aarch64/bl2_entrypoint.S这是 BL1 el3_exit → ERET 后 CPU 的第一条指令。此时 CPU 已从 EL3 降级到 S-EL1ELR_EL3 指向第16行的 bl2_entrypoint。func bl2_entrypoint逐行分析/* 第1步保存 BL1 传来的参数 */ mov x20, x0 ← x0 bl2_ep_info 指针或用做平台特定参数 mov x21, x1 mov x22, x2 mov x23, x3BL1 通过 el3_exit 时的寄存器传递了4个参数x0–x3先暂存到被调用者保存寄存器 x20–x23 中避免后续初始化被覆盖。/* 第2步设置 S-EL1 异常向量表 */ adr x0, early_exceptions msr vbar_el1, x0 isbVBAR_EL1 指向 early_exceptions早期异常处理确保任何在 MMU 开启前发生的同步/异步异常都有处理入口。/* 第3步允许 SError 中断 */msr daifclr, #DAIF_ABT_BIT清除 PSTATE 的 Aasync abort掩码位允许 SError 被触发。/* 第4步配置 SCTLR_EL1 — 指令缓存 对齐检查 */ mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT) mrs x0, sctlr_el1 orr x0, x0, x1 #if ENABLE_BTI bic x0, x0, #(SCTLR_BT0_BIT | SCTLR_BT1_BIT) ← 兼容 PAC 分支类型 #endif bic x0, x0, #SCTLR_DSSBS_BIT ← 允许推测存储 msr sctlr_el1, x0 isb开启指令缓存I_bit、数据访问对齐检查A_bit和 SP 对齐检查SA_bit。此时不开启 MMU——MMU 由后续的 bl2_plat_arch_setup() 在 C 代码中配置。/* 第5步清除 RW 区域的脏缓存行 */ adr x0, __RW_START__ adr x1, __RW_END__ sub x1, x1, x0 bl inv_dcache_rangeBL1 可能留下了 RW 区域的脏缓存行在开启 MMU 前必须清除防止数据不一致。/* 第6步清零 .bss 段和可选的 coherent memory 段*/ adrp x0, __BSS_START__ add x0, x0, :lo12:__BSS_START__ adrp x1, __BSS_END__ add x1, x1, :lo12:__BSS_END__ sub x1, x1, x0 bl zeromem未初始化的全局/静态变量都放在 BSS必须在调用任何 C 代码前清零。/* 第7步设置 BL2 的栈指针 */ bl plat_set_my_stack调用平台函数分配并设置 SP_EL1 的栈每个 CPU 有自己的栈区域。/* 第8步恢复 BL1 参数到 x0–x3准备调用 C 函数 */ mov x0, x20 ← 恢复 bl2_ep_info 指针 mov x1, x21 mov x2, x22 mov x3, x23 /* ★ 第9步跳转到 BL2 的 C 主函数 */ bl bl2_main这是从汇编到 C 的关键跳转。bl 指令设置 LRx30 返回地址然后跳转到 bl2_main。/* 理论上永远不会执行到这里 */ no_ret plat_panic_handler如果 bl2_main 返回实际不会——它会通过 SMC 或 ERET 跳转到下一阶段触发 panic。第二阶段bl2/bl2_main.c进入 BL2 的 C 世界梳理初始化流程按行号顺序bl2_main(x0, x1, x2, x3) │ ├─ ① plat_setup_early_console() ← 第49行早期控制台初始化 │ ├─ ② bl2_early_platform_setup2(x0..x3) ← 第52行早期平台 setup │ 参数来自 BL1x0bl2_ep_info, x1..x3平台特定 │ 典型操作解析内存布局、初始化 IO 控制器 │ ├─ ③ bl2_arch_setup() ← 第55行S-EL1 架构配置 │ 见 bl2/aarch64/bl2_arch_setup.c:15-19 │ └─ write_cpacr(CPACR_EL1_FPEN(NO_TRAP)) ← 开放 FP/SIMD 寄存器访问 │ ├─ ④ bl2_plat_arch_setup() ← 第58行★ 开启 MMU │ 平台函数建立页表、启用 MMU数据缓存 │ ├─ ⑤ pauth_init_enable_el1() ← 第60-66行指针认证初始化 │ (BL2_RUNS_AT_EL3 时用 pauth_init_enable_el3) │ ├─ ⑥ crypto_mod_init() ← 第79行密码学模块初始化 │ ├─ ⑦ auth_mod_init() ← 第82行认证模块初始化 │ 为镜像签名验证做准备 │ ├─ ⑧ bl2_plat_mboot_init() ← 第85行测量启动后端初始化 │ ├─ ⑨ bl2_plat_preload_setup() ← 第94行启动源初始化 │ ├─ ⑩ ★ bl2_load_images() ★ ← 第101行核心任务 │ 见 bl2/bl2_image_load_v2.c │ └─ 循环加载 SCP_BL2 → BL31 → [BL32] → BL33 │ └─ 验证每个镜像的数字签名 │ └─ 返回 BL31 的 entry_point_info 指针 │ ├─ ⑪ bl2_plat_mboot_finish() ← 第115行测量启动结束 │ ├─ ⑫ crypto_mod_finish() ← 第117行密码模块结束 │ └─ ⑬ ★ 跳转到下一阶段两种情况不能并存 │ ├─ !BL2_RUNS_AT_EL3最常见BL2在S-EL1 │ 第147行: smc(BL1_SMC_RUN_IMAGE, next_bl_ep_info) │ │ │ ├─ SMC #0 → 同步异常 → trap 到 EL3 │ ├─ BL1 的 SMC handlerbl1_exceptions.S │ │ └─ prepare_el3_entry → bl1_smc_wrapper → el3_exit │ └─ ERET → ★ BL31 入口用 next_bl_ep_info 设置的 ELR_EL3 │ └─ BL2_RUNS_AT_EL3BL2也在EL3较少见 第164行: bl2_run_next_image(next_bl_ep_info) │ ├─ disable_mmu_icache_el3() ← 关 MMU ├─ msr elr_el3, next_bl_ep-pc ← 设目标 PC BL31 入口 ├─ msr spsr_el3, next_bl_ep-spsr ← 设目标异常级别 └─ exception_return ← ERET → ★ BL31关键转折点示意BL1 (EL3) BL2 (S-EL1) BL31 (EL3) ───────── ────────── ────────── el3_exit ──ERET──▶ bl2_entrypoint.S │ (设置 SCTLR、缓存、BSS、栈) │ bl bl2_main │ ├─ bl2_arch_setup() ← CPACR (开放 FP/SIMB) ├─ bl2_plat_arch_setup() ← 开启 MMU ├─ bl2_load_images() ← ★ 加载 BL31/BL32/BL33 │ └─ smc(BL1_SMC_RUN_IMAGE) ──SMC──▶ BL1 SMC handler │ └─ el3_exit ──ERET──▶ BL31关于 BL2 Boot Loader stage 2 的精确定位从代码可以清晰看到 BL2 的角色本质方面说明异常级别S-EL1默认或 EL3BL2_RUNS_AT_EL3核心任务验证签名并加载后续所有镜像生命周期短暂——所有代码都运行在会被后续阶段回收的内存上退出方式SMC 返回 BL1由 BL1 做 el3_exit或直接 ERET不再返回bl2_main不从 C 函数 return——要么 SMC、要么 ERET、要么 panic正是 BL1 的 ERET 在 S-EL1 世界的第一着陆点也是 BL2 整个生命周期的起点。从这里开始CPU 完成从裸汇编环境到完整 C 运行时的过渡最终完成固件加载的使命。