linux ATF BL2执行过程

一、主要执行过程如下

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_setup
  • bl2_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 下运行所需的底层执行环境准备好。

所以流程上是:

  1. 先保存 BootROM 传入参数
  2. 做 EL3 通用启动初始化
  3. 再恢复参数
  4. 再进入 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. 你可以把它理解成下面这条执行链

最直白地翻译成流程就是:

上电后

  1. BootROM 运行
  2. BootROM 根据 IVT/镜像信息找到 BL2
  3. 跳到 BL2 的入口地址
  4. 这个入口地址对应 bl2_entrypoint

进入 bl2_entrypoint

  1. 保存 BootROM 传下来的 x0~x3
  2. 做 EL3 通用入口初始化
  3. 恢复 x0~x3
  4. bl2_el3_setup
  5. 如果启用了指针认证,做 pauth_init_enable_el3
  6. bl2_main
  7. 后续加载下一级镜像并跳转
  8. 正常情况下不返回

17. 如果只保留最关键的逐句解释

这段代码可以压缩成下面几句理解:

  • bl2_entrypointBL2 入口
  • 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_mainbl2_run_next_image 是怎么衔接的
相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言