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 是怎么衔接的
相关推荐
淡淡烟雨淡淡愁2 小时前
安装libreoffice
linux
蜀道山老天师3 小时前
云原生监控入门:监控基础概念 + SLI/SLO/SLA 详解 + Prometheus 从零安装配置
linux·运维·云原生·prometheus
AIDF20263 小时前
linux 服务器网络问题排查
linux·服务器·网络
楼兰公子3 小时前
br_opi5_plus_defconfig 附带uboot
linux·运维·服务器
mzhan0174 小时前
Linux: signal: SIGALRM; alarm: ITIMER_REAL
linux·运维·服务器
mzhan0174 小时前
Linux: compare的直观性
java·linux·服务器
原来是猿4 小时前
TCP Server 业务扩展实战:从 Echo 到远程命令执行与词典翻译
linux·运维·服务器
剑神一笑5 小时前
Linux awk 命令:文本处理的瑞士军刀
linux·运维·chrome
用户2367829801686 小时前
Linux df 命令深度解析:从磁盘空间监控到 inode 耗尽排查
linux