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

Generic Timer.pdf

(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)
posted @   silas12138  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
点击右上角即可分享
微信分享提示