ATF引导启动流程整理-Part3:BL2引导启动流程整理
3.2 BL2启动流程
Bl2的启动流程与bl1类似,主要区别是bl2的初始化流程比bl1更简单,但其可能需要加载更多的镜像,如bl31、bl32和bl33。BL2 主要的职责就是为后续image的加载执行相关的初始化操作,(主要是内存DDR、MMU、Nand Flash、串口以及EL3软件运行环境配置),并负责将后续固件加载到RAM中。一个显著的不同是它在Flash上,作为外置的一个Firmware,它的可信建立在BL1对它的验证上。
从前文可以明确的看到在前一阶段,BL1代码全部运行在EL3层,且只在主CPU核上运行; BL2则不一样,BL2可以运行在 EL3 也可以运行在 S-EL1层级,但同样要求主CPU核运行BL2代码。值得注意的是BL2运行在哪个异常等级和整个系统的启动流程、boot阶段设计相关,一般的在较低的异常等级上,不具备访问高等级异常高的权限。下面还是以运行在 S-EL1层的 BL2 为例。
检查 bl2.ld.S 文件可以确定,BL2 Image的入口函数是 bl2_entrypoint , 定位代码为 /atf/bl2/aarch64/bl2_entrypoint.S
func bl2_entrypoint
/*---------------------------------------------
* Save arguments x0 - x3 from BL1 for future
* use.
* ---------------------------------------------
*/
/* 保存 bl1 的传参 */
mov x20, x0
mov x21, x1
mov x22, x2
mov x23, x3
/* 设置 EL1 的异常向量表的基地址*/
adr x0, early_exceptions
msr vbar_el1, x0
isb
/* ---------------------------------------------
* Enable the SError interrupt now that the
* exception vectors have been setup.
* ---------------------------------------------
*/
msr daifclr, #DAIF_ABT_BIT
/* ---------------------------------------------
* Enable the instruction cache, stack pointer
* and data access alignment checks and disable
* speculative loads.
* ---------------------------------------------
*/
/* 配置 sctlr_el1 的相关寄存器*/
mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT)
mrs x0, sctlr_el1
orr x0, x0, x1
bic x0, x0, #SCTLR_DSSBS_BIT
msr sctlr_el1, x0
isb
/*初始化 c 原因环境,包括配置 RW 内存、清空Bss段、配置栈,和 BL1 处理一致*/
/* ---------------------------------------------
* Invalidate the RW memory used by the BL2
* image. This includes the data and NOBITS
* sections. This is done to safeguard against
* possible corruption of this memory by dirty
* cache lines in a system cache as a result of
* use by an earlier boot loader stage.
* ---------------------------------------------
*/
adr x0, __RW_START__
adr x1, __RW_END__
sub x1, x1, x0
bl inv_dcache_range
/* ---------------------------------------------
* Zero out NOBITS sections. There are 2 of them:
* - the .bss section;
* - the coherent memory section.
* ---------------------------------------------
*/
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
/* --------------------------------------------
* Allocate a stack whose memory will be marked
* as Normal-IS-WBWA when the MMU is enabled.
* There is no risk of reading stale stack
* memory after enabling the MMU as only the
* primary cpu is running at the moment.
* --------------------------------------------
*/
bl plat_set_my_stack
/* ---------------------------------------------
* Perform BL2 setup
* ---------------------------------------------
*/
/* 跳转到BL2 平台配置 */
mov x0, x20
mov x1, x21
mov x2, x22
mov x3, x23
bl bl2_setup
/* ---------------------------------------------
* Jump to main function.
* ---------------------------------------------
*/
/* bl2加载后续镜像,完成下一镜像的参数传递并实现跳转 */
bl bl2_main
/* ---------------------------------------------
* Should never reach this point.
* ---------------------------------------------
*/
no_ret plat_panic_handler
endfunc bl2_entrypoint
简单整理 BL2 的启动流程可以分为如下几部分
3.2.1 bl2基础参数初始化
(1) 保存参数
通过代码不难看出b1定义了x0 – x7寄存器用于向bl2传递参数,但bl2此处保存的只有x0 - x3四个寄存器。以及后续在 bl2_setup 前,将对应的寄存器再次恢复回去,这么做的意义在哪?
根据手册信息,bl2实际使用的起始只有x0-x3四个寄存器,因此其实际传参的数量不能超过四个。而为什么这里要存下来后续再恢复,从网站上搜索到在armv8过程调用中,x0 – 18是caller saved寄存器,x19 – x30是callee saved寄存器。caller saved寄存器就是在子函数过程调用中,若这些寄存器的内容需要保存,则由函数调用方来保存它们,子程序可以随意使用这些寄存器,而无须在调用完成后恢复它们的值。而callee寄存器则相反,子程序若需要使用这些寄存器,则必须要先保存它们的原始值,然后调用完成后恢复它们。而由于 bl1 的参数存储在 x0-x7属于 caller 寄存器中,需要将其保存到 callee 寄存器中,防止子程序的调用不会篡改此处的值,。
由于bl1的参数位于caller寄存器中,因此需要将其保存到callee寄存器中,以确保后面的子程序调用不会篡改这些寄存器的值。
mov x20, x0
mov x21, x1
mov x22, x2
mov x23, x3
(2)异常向量表设置
读取 early_exceptions 异常向量表的机制并配置到 vbar_el1 中,异常向量表设置完成后,bl2将使能serror和external abort异常,对应的即支持使用MMU。
adr x0, early_exceptions
msr vbar_el1, x0
isb
/* ---------------------------------------------
* Enable the SError interrupt now that the
* exception vectors have been setup.
* ---------------------------------------------
*/
msr daifclr, #DAIF_ABT_BIT
(3)配置 sctl_el1 寄存器
参考 bl1 阶段 sctl_el3 的配置,不难理解此处其实在使能指令cache、对齐检查和栈对齐检查特性
- SCTLR_SA_BIT:栈对齐错误检查
- SCTLR_A_BIT:对齐错误检查
- SCTLR_I_BIT:使能 cache 功能位
mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT)
mrs x0, sctlr_el1
orr x0, x0, x1 /* oor 指令逻辑与*/
bic x0, x0, #SCTLR_DSSBS_BIT
msr sctlr_el1, x0
isb
(4) 配置 C 运行环境
此处可以和bl1 中 _init_c_runtime 实现一致,实现配置代码段、清空BSS段、配置堆栈等,此处防止冗余不再展开。
可以参考:ATF引导启动流程整理-Part2:BL1引导启动流程整理 - silas12138 - 博客园 的 _init_c_runtime 内容
3.2.2 BL2阶段初始化:bl2_setup
来看一下 bl2 的初始化逻辑,可以对比 bl1_setup 来看,从结构上是一致的均指向平台
void bl2_setup(u_register_t arg0, u_register_t arg1, u_register_t arg2,
u_register_t arg3)
{
/* Perform early platform-specific setup */
/* 执行早期特定平台的配置 */
bl2_early_platform_setup2(arg0, arg1, arg2, arg3);
/* Perform late platform-specific setup */
/* 执行后期特定平台的配置 */
bl2_plat_arch_setup();
#if CTX_INCLUDE_PAUTH_REGS
/*
* Assert that the ARMv8.3-PAuth registers are present or an access
* fault will be triggered when they are being saved or restored.
*/
assert(is_armv8_3_pauth_present());
#endif /* CTX_INCLUDE_PAUTH_REGS */
}
来看一下 bl2 阶段对应的平台配置都做了哪些事情,和 bl1 阶段有什么不一样
(1)bl2_early_platform_setup2 早期平台配置
继续以 arm 平台实现为例,实现代码和梳理如下,平台完成的工作包括
- 初始化串口来提供早期的调试支持
- 配置 bl2 的内存布局信息
- 初始化 IO 层设备并注册平台IO设备
- 加载分区表 GPT
- 初始化ARM平台时间子系统的 generic timer(ARM提供的标准化的定时器框架,通用定时器)
void arm_bl2_early_platform_setup(uintptr_t fw_config,
struct meminfo *mem_layout)
{
int __maybe_unused ret;
/* 初始化控制台 */
arm_console_boot_init();
/* 配置BL2 的内存布局 */
bl2_tzram_layout = *mem_layout;
config_base = fw_config;
/* 初始化IO设备 */
plat_arm_io_setup();
/* 载入GPT分区表 */
#if ARM_GPT_SUPPORT
ret = gpt_partition_init();
if (ret != 0) {
ERROR("GPT partition initialisation failed!\n");
panic();
}
#endif /* ARM_GPT_SUPPORT */
}
void bl2_early_platform_setup2(u_register_t arg0, u_register_t arg1, u_register_t arg2, u_register_t arg3)
{
arm_bl2_early_platform_setup((uintptr_t)arg0, (meminfo_t *)arg1);
generic_delay_timer_init();
}
此处的 Generic Timer 的初始化操作还取决于运行在那个异常等级(EL),具体了解和配置可以参考下面的文档。
Learn the architecture - Generic Timer
(2)bl2_plat_arch_setup 后期平台配置
结合 bl1 的初始化不难理解,此处为bl2需要访问的地址建立MMU页表。
void bcm_bl2_plat_arch_setup(void)
{
if (!(read_sctlr_el1() & SCTLR_M_BIT)) {
const mmap_region_t bl_regions[] = {
MAP_REGION_FLAT(bl2_tzram_layout.total_base,
bl2_tzram_layout.total_size,
MT_MEMORY | MT_RW | MT_SECURE),
MAP_REGION_FLAT(BL_CODE_BASE,
BL_CODE_END - BL_CODE_BASE,
MT_CODE | MT_SECURE),
MAP_REGION_FLAT(BL_RO_DATA_BASE,
BL_RO_DATA_END - BL_RO_DATA_BASE,
MT_RO_DATA | MT_SECURE),
{0}
};
setup_page_tables(bl_regions, plat_brcm_get_mmap());
enable_mmu_el1(0);
}
}
void bl2_plat_arch_setup(void)
{
plat_bcm_bl2_plat_arch_setup(); /*无操作*/
bcm_bl2_plat_arch_setup();
}
3.2.3 bl2镜像加载:bl2_main
下面进入 bl2 的镜像加载流程,简单检查相关流程和 bl1 基本一致,此处不再细致的展开。
/* aarch64架构下使能fp和simd寄存器访问权限 */
bl2_arch_setup();
/* 和 BL1 一样初始化加密、安全模块 */
crypto_mod_init();
/* Initialize authentication module */
auth_mod_init();
/* 初始化测量启动函数 */
bl2_plat_mboot_init();
/* 初始化动态配置 */
bl2_plat_preload_setup();
/* 加载后续镜像,调用load_auth_image该接口用于实际的镜像加载流程,
* 其与bl1的镜像加载流程完全一样 */
next_bl_ep_info = bl2_load_images();
/* 删除测量启动程序 */
bl2_plat_mboot_finish();
NOTICE("BL2: Booting " NEXT_IMAGE "\n");
print_entry_point_info(next_bl_ep_info);
/* 退出 bl2 前将串口中的数据全部刷新掉 */
console_flush();
/* 运行下一个分区前的准备 */
bl2_run_next_image(next_bl_ep_info);
3.2.4 总结
最重要的两步都在这个bl2_main函数中完成:初始化硬件和找到BL31。
bl2_plat_preload_setup()中会初始化一堆硬件,包括读取RCW初始化Serdes等(网口),初始化DDR的代码:dram_init(),这里的DDR 初始化代码是个Binary,不含源码,这里对DDR4的初始化仅仅设置timing寄存器和Size,而没有内存的Training过程。BL2在初始化硬件后,开始寻找BL3的几个镜像:BL31,BL32和BL33。它先找到BL31,并验签它,最后转入BL31。
BL2位于SRAM中,运行在Secure EL1主要工作有:
- 架构初始化:EL1/EL0使能。
- 平台初始化:控制台(串口)初始化、存储设备初始化、MMU、相关设备安全配、
- SCP_BL2:系统控制核镜像加载,单独核处理系统功耗、时钟、复位等控制。
- 加载BL31镜像:BL2关闭MMU并关cache;将控制权交给BL31。BL2将控制权交给BL1;BL1关闭MMU并关cache;BL1将控制权交给BL31。(EL3>S.EL1>EL3)
- 加载BL32镜像:BL32运行在安全世界,BL2依赖BL31将控制权交给BL32。SPSR通过Secure-EL1 Payload Dispatcher进行初始化。(EL3>S.EL1>S.EL0>EL3)
- 加载BL33镜像:BL2依赖BL31将控制权交给BL33。(EL3>N.EL1)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体