一、主要执行过程如下
ATF 定制资料里,BL2 的冷启动入口链路:
bl2_entrypoint() → bl2_el3_setup → bl2_el3_early_platform_setup → s32_early_plat_init → 时钟/串口/存储初始化 → bl2_el3_plat_arch_setup → ddr_init() → bl2_main → bl2_load_images → bl2_run_next_image 。
二、上述链路的参考依据
ATF\bl2\aarch64\bl2_el3_entrypoint.S
以下是这个文件的分析:
cpp
bl2_entrypoint
│
├─ mov x20, x0
├─ mov x21, x1
├─ mov x22, x2
├─ mov x23, x3
│ ↑
│ 保存前级(BootROM/前一阶段)传入参数
│
├─ el3_entrypoint_common
│ ↑
│ 做 EL3 通用入口初始化:
│ - _init_sctlr=1
│ - _init_memory=1
│ - _init_c_runtime=1
│ - _exception_vectors=bl2_el3_exceptions
│
├─ mov x0, x20
├─ mov x1, x21
├─ mov x2, x22
├─ mov x3, x23
│ ↑
│ 恢复前级参数,传给后续 C 函数
│
├─ bl bl2_el3_setup
│ ↑
│ 做 BL2 setup
│
├─ [if ENABLE_PAUTH] bl pauth_init_enable_el3
│
├─ bl bl2_main
│ ↑
│ 进入 BL2 主流程
│
└─ no_ret plat_panic_handler
↑
正常情况下不应返回到这里
这段代码的意思可以直接概括成一句话:
它就是 BL2 的真正入口汇编。CPU 一旦跳进 bl2_entrypoint,先做 EL3 级别的通用启动初始化,再恢复 BootROM 传下来的参数,然后调用 bl2_el3_setup() 做 BL2 早期初始化,最后调用 bl2_main() 进入 BL2 主流程。
下面我按代码顺序详细拆。
1. 这是个什么文件?
这是 TF-A 里 BL2 的 AArch64 EL3 入口汇编文件 。
它不是普通业务代码,而是 "BL2 刚开始执行时跑的第一段代码"。
你前面一直问:
为什么说
bl2_entrypoint() -> bl2_el3_setup() -> bl2_main()?
这段代码本身就是直接证据,因为里面白纸黑字写了:
bl bl2_el3_setup
...
bl bl2_main
这里的 bl 是 ARM64 的 branch with link,也就是"调用函数"。
所以执行顺序不是猜的,就是:
进入 bl2_entrypoint → 调 bl2_el3_setup → 回来后调 bl2_main
2. 文件开头在干什么?
#include <platform_def.h>
#include <arch.h>
#include <asm_macros.S>
#include <common/bl_common.h>
#include <el3_common_macros.S>
这些头文件不是 C 头文件那种业务配置,而是给汇编提供:
- 平台地址/边界定义
- 架构常量
- 汇编宏
- BL 通用定义
- EL3 入口公共宏
尤其后面那个:
el3_entrypoint_common
就是从这些宏里来的。
3. 这句是什么意思?
.globl bl2_entrypoint
表示把 bl2_entrypoint 导出为全局符号。
这样链接器能认识它,别的文件也能引用它。
配合你前面贴过的链接脚本:
ENTRY(bl2_entrypoint)
可以确定:
整个 BL2 镜像的入口符号就是 bl2_entrypoint。
也就是说,BL2 一开始从这里进。
4. FIXUP_SIZE 是干什么的?
#if BL2_IN_XIP_MEM
#define FIXUP_SIZE 0
#else
#define FIXUP_SIZE ((BL2_LIMIT) - (BL2_BASE))
#endif
这段是给位置无关修正/重定位相关逻辑准备参数的。
- 如果 BL2 是 XIP(execute in place,原地执行),就不需要这类 fixup,设为 0
- 如果不是 XIP,那 fixup 范围就是整个 BL2 镜像大小:
BL2_LIMIT - BL2_BASE
这值后面传给 el3_entrypoint_common:
_pie_fixup_size=FIXUP_SIZE
说明入口公共宏会根据这个值处理 PIE/重定位相关初始化。
你现在不用把它理解太深,抓住一点就够:
这是 BL2 入口在做镜像自身运行前准备时用的参数。
5. func bl2_entrypoint 是什么意思?
func bl2_entrypoint
这是汇编宏,表示开始定义一个函数 bl2_entrypoint。
它就是 BL2 的入口函数。
6. 一进来为什么先保存 x0~x3?
/* Save arguments x0-x3 from previous Boot loader */
mov x20, x0
mov x21, x1
mov x22, x2
mov x23, x3
这几句的意思是:
- 上一级启动阶段(这里通常就是 BootROM 或前级加载器)把参数放在
x0 ~ x3 - 但后面要执行
el3_entrypoint_common,这个宏会做很多初始化,可能会改动寄存器 - 所以先把
x0~x3备份到x20~x23
为什么选 x20~x23? 因为这是临时保存,属于当前入口阶段自己管理的一组寄存器。
所以这一步的本质就是:
先把上一级传下来的参数保住,别被后面的初始化冲掉。
7. 这坨 el3_entrypoint_common 到底是什么?
el3_entrypoint_common \
_init_sctlr=1 \
_warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS \
_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU \
_init_memory=1 \
_init_c_runtime=1 \
_exception_vectors=bl2_el3_exceptions \
_pie_fixup_size=FIXUP_SIZE
这是最关键的一段之一。
它不是函数调用,而是 宏展开 。
也就是说,这里会插入一大段 EL3 通用入口代码。
它的作用可以理解成:
在正式进入 BL2 自己的 C/平台逻辑之前,先把 EL3 执行环境准备好。
这些参数分别大致表示:
_init_sctlr=1
初始化 SCTLR_EL3 相关配置。
也就是 EL3 控制寄存器的基本 setup。
可以理解成:把 CPU 的 EL3 控制状态初始化好。
_warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS
这个和 warm boot / reset address 机制有关。
意思是是否启用某种 warm boot mailbox 处理逻辑,要看 PROGRAMMABLE_RESET_ADDRESS 配置。
你现在不必深抠,只要知道:
这个入口宏同时兼顾冷启动/热启动场景。
_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU
表示是否考虑"多核冷启动"场景。
如果不是单核冷启动,就要处理 secondary CPU 相关逻辑。
_init_memory=1
初始化内存相关状态。
通常会涉及清理某些内存区域、准备运行时内存环境等。
_init_c_runtime=1
这个很重要。
表示 初始化 C runtime 环境。
什么意思?
因为你后面要调用 C 函数:
bl2_el3_setupbl2_main
而 C 函数依赖很多基础运行环境,例如:
- 栈
.bss清零.data搬运/准备- 基本 ABI 环境
所以这里先把 "C 代码能正常跑" 的基础打好。
_exception_vectors=bl2_el3_exceptions
设置异常向量表。
也就是在 EL3 下如果发生异常,CPU 应该跳到哪个向量入口处理。
这里指定的是:
BL2 自己的 EL3 异常向量表 bl2_el3_exceptions。
_pie_fixup_size=FIXUP_SIZE
前面说过,这是给位置无关修正/镜像 fixup 用的参数。
这整段总结一下
el3_entrypoint_common 的本质就是:
把 BL2 在 EL3 下运行所需的底层执行环境准备好。
所以流程上是:
- 先保存 BootROM 传入参数
- 做 EL3 通用启动初始化
- 再恢复参数
- 再进入 BL2 自己的 setup / main
8. 为什么后面又把 x20x23 放回 x0x3?
/* Restore parameters of boot rom */
mov x0, x20
mov x1, x21
mov x2, x22
mov x3, x23
因为后面要调用:
bl bl2_el3_setup
而 bl2_el3_setup 按调用约定通常就是从 x0~x3 接收参数。
也就是说:
- BootROM 传来的参数,原本就在
x0~x3 - 前面通用入口初始化可能会破坏它们
- 所以先备份
- 现在准备正式调用 BL2 setup,就再恢复回来
这说明一个很重要的事实:
bl2_el3_setup() 会继续使用前级启动器传下来的参数。
9. 这句是你最关心的直接证据
/* Perform BL2 setup */
bl bl2_el3_setup
这句没有歧义。
它的意思就是:
调用 bl2_el3_setup() 做 BL2 的 setup。
所以为什么我之前一直说:
bl2_entrypoint -> bl2_el3_setup
因为源码就在这里。
不是"可能会调",不是"按文档推测",就是:
入口汇编明确调用。
10. bl2_el3_setup 做什么?
这段代码本身没展开 bl2_el3_setup 的函数体,但从 TF-A 的结构和你前面追的链路来看,它是 BL2 早期平台初始化阶段。
通常会做类似这些事:
- 早期平台初始化
- 时钟/console/存储初始化
- 架构相关 setup
- DDR 初始化前置工作
你前面那条链: bl2_el3_setup -> bl2_el3_early_platform_setup -> s32_early_plat_init -> ... -> bl2_el3_plat_arch_setup -> ddr_init() 属于它内部再展开出来的子流程。
但注意:这些"内部细节"不是从当前这段汇编里直接看出来的,而是从 bl2_el3_setup 的实现和平台文档里得到的。
当前这段汇编直接能证明的只有:
它确实被调用了。
11. ENABLE_PAUTH 这段什么意思?
#if ENABLE_PAUTH
bl pauth_init_enable_el3
#endif
这是条件编译。
如果启用了 Pointer Authentication(指针认证,Armv8.3+ 的安全特性),那就在 EL3 里初始化并开启它。
如果没开这个配置,这段根本不会编进去。
所以主线流程里,这一步是 可选 的。
12. 这句说明什么?
/* Jump to main function. */
bl bl2_main
这句同样是直接证据。
意思是:
BL2 setup 完成后,调用 bl2_main(),进入 BL2 主流程。
所以入口汇编定义出的主链非常清楚:
bl2_entrypoint → 保存上一级参数
→ el3_entrypoint_common
→ 恢复参数
→ bl2_el3_setup()
→ (可选)pauth_init_enable_el3()
→ bl2_main()
这就是你一直在追的最核心执行顺序。
13. bl2_main 一般负责什么?
这段汇编本身没写 bl2_main 的内容,但从 TF-A 结构来说,它通常负责:
- 加载下一阶段镜像
- 组织镜像参数
- 最终跳到下一级,如 BL31 / BL33
你前面贴过 bl2_run_next_image,那就属于 bl2_main 之后的后续执行链里会用到的机制。
当前这段汇编能直接证明的是:
最终会进入 bl2_main。
14. 为什么最后是 panic?
/* Should never reach this point. */
no_ret plat_panic_handler
意思是:
正常情况下,bl2_main 不应该返回到这里。
因为 BL2 的职责通常是:
- 完成初始化
- 加载下一阶段
- 把控制权交出去
一旦成功交给下一阶段,就不会再回到这个入口函数。
所以如果真执行到了这里,说明出错了,于是调用:
plat_panic_handler
并且 no_ret 表示它不应该返回。
15. 这段代码回答了你哪些核心问题?
问题 1:BL2 是不是从这里开始?
是。
因为:
ENTRY(bl2_entrypoint)指定镜像入口- 这里定义了
func bl2_entrypoint
问题 2:为什么说 bl2_entrypoint -> bl2_el3_setup?
因为源码里直接写了 bl bl2_el3_setup。
问题 3:为什么说之后会去 bl2_main?
因为源码里直接写了 bl bl2_main。
问题 4:BootROM 是不是"执行这个函数名"?
更准确地说:
BootROM 不是认识 C/ASM 函数名,而是跳到 BL2 镜像入口地址;而这个入口地址对应的符号就是 bl2_entrypoint。
也就是说:
- BootROM 看入口地址
- 链接器把入口地址定义到
bl2_entrypoint - 所以 CPU 进入的第一段 BL2 代码就是这里
16. 你可以把它理解成下面这条执行链
最直白地翻译成流程就是:
上电后
- BootROM 运行
- BootROM 根据 IVT/镜像信息找到 BL2
- 跳到 BL2 的入口地址
- 这个入口地址对应
bl2_entrypoint
进入 bl2_entrypoint 后
- 保存 BootROM 传下来的
x0~x3 - 做 EL3 通用入口初始化
- 恢复
x0~x3 - 调
bl2_el3_setup - 如果启用了指针认证,做
pauth_init_enable_el3 - 调
bl2_main - 后续加载下一级镜像并跳转
- 正常情况下不返回
17. 如果只保留最关键的逐句解释
这段代码可以压缩成下面几句理解:
bl2_entrypoint:BL2 入口mov x20~x23, x0~x3:保存前级传参el3_entrypoint_common:建立 EL3 和 C 运行环境mov x0~x3, x20~x23:恢复前级传参bl bl2_el3_setup:做 BL2 早期初始化bl bl2_main:进入 BL2 主流程plat_panic_handler:正常不该走到这里,走到了就是异常
18. 最后给你一个"最准确、不绕"的结论
这段汇编就是 BL2 的入口代码。它的作用不是做复杂业务,而是把 CPU 从"刚跳进 BL2 的裸入口状态"带到"可以安全执行 BL2 C 代码"的状态,然后明确调用 bl2_el3_setup() 和 bl2_main()。所以你前面一直追问的 bl2_entrypoint() -> bl2_el3_setup() -> bl2_main(),在这段源码里已经被直接写死了。
如果你愿意,我下一条可以继续把这段代码按 "每条指令级别" 给你解释,尤其是:
el3_entrypoint_common宏在 TF-A 里通常展开成什么- 为什么要在 EL3 里先初始化 C runtime
bl2_main和bl2_run_next_image是怎么衔接的