(04)ATF代码导读之BL1

本文详细介绍了QEMU环境中BL1的启动流程,包括bl1_entrypoint、el3_entrypoint_common、bl1_setup、bl1_main等关键步骤,涉及内存初始化、平台设置、镜像加载和安全启动验证等环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码导读

关于平台相关的代码和函数均以qemu实现解读。

BL1

BL1是启动的第一阶段,复位的入口函数为bl1_entrypoint,这可以从bl1.ld.S链接脚本中ENTRY标号定义看出,即ENTRY(bl1_entrypoint)。其主要执行BL1初始化,平台初始化,加载下一阶段镜像,以及跳转到下一阶段执行,大致的函数执行流程如下。

bl1_entrypoint
el3_entrypoint_common
bl1_setup
bl1_main
el3_exit

el3_entrypoint_common

el3_entrypoint_common是定义的一个宏,它是EL3等级下执行的入口通用函数,其实现位于el3_common_macros.S,主要完成C语言运行环境的搭建,异常向量表的注册,bl1镜像文件的复制,CPU安全运行环境的设定。

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=bl1_exceptions		\
    _pie_fixup_size=0
  • _init_sctlr:初始化EL3异常等级下的系统控制器,包括设置系统小端,禁止可写内存的执行权限,启用栈对齐检查,对齐错误检查等。
  • _warm_boot_mailbox:冷热启动处理,如果plat_get_my_entrypoint函数返回非0地址,执行热启动直接跳转到该地址,否则执行冷启动。
  • _secondary_cold_boot:判断冷启动当前CPU是primary还是secondary,若为primary cpu,执行冷启动流程,否则为secondary cpu,执行平台定义的secondary cpu启动设置。
  • _init_memory:内存初始化,调用platform_mem_init函数。
  • _init_c_runtime:C运行环境初始化,将ROM中的数据段重定位到RAM和清零BSS段。
  • _exception_vectors:设置异常向量表到EL3的向量表基地址寄存器中,bl1的向量表位于bl1_exceptions.S中,可以看出只支持SMC异常的处理smc_handler64,其他都会panic。
  • _pie_fixup_size:地址无关可执行文件(PIE),即加载地址和链接地址不一样,程序也可以寻址执行。

bl1_setup

bl1_setup是BL1建立初始化,包括两个函数:bl1_early_platform_setup()bl1_plat_arch_setup()

  1. bl1_early_platform_setup

    void bl1_early_platform_setup(void)
    {
    	/* Initialize the console to provide early debug support */
    	qemu_console_init();
    
    	/* Allow BL1 to see the whole Trusted RAM */
    	bl1_tzram_layout.total_base = BL_RAM_BASE;
    	bl1_tzram_layout.total_size = BL_RAM_SIZE;
    }
    
    • qemu_console_init():初始化调试打印控制台
    • bl1_tzram_layout:设置BL1的安全SRAM地址范围,这里允许BL1访问整个RAM空间
  2. bl1_plat_arch_setup

    void bl1_plat_arch_setup(void)
    {
    	QEMU_CONFIGURE_BL1_MMU(bl1_tzram_layout.total_base,
    				bl1_tzram_layout.total_size,
    				BL_CODE_BASE, BL1_CODE_END,
    				BL1_RO_DATA_BASE, BL1_RO_DATA_END,
    				BL_COHERENT_RAM_BASE, BL_COHERENT_RAM_END);
    }
    

    该函数主要是建立MMU页表,并且使能dcache。QEMU_CONFIGURE_BL1_MMU会调用到DEFINE_CONFIGURE_MMU_EL(el1),该宏的展开如下。

    #define DEFINE_CONFIGURE_MMU_EL(_el)					\
    	void qemu_configure_mmu_##_el(unsigned long total_base,	\
    				   unsigned long total_size,		\
    				   unsigned long code_start,		\
    				   unsigned long code_limit,		\
    				   unsigned long ro_start,		\
    				   unsigned long ro_limit,		\
    				   unsigned long coh_start,		\
    				   unsigned long coh_limit)		\
    	{								\
    		mmap_add_region(total_base, total_base,			\
    				total_size,				\
    				MT_MEMORY | MT_RW | MT_SECURE);		\
    		mmap_add_region(code_start, code_start,			\
    				code_limit - code_start,		\
    				MT_CODE | MT_SECURE);			\
    		mmap_add_region(ro_start, ro_start,			\
    				ro_limit - ro_start,			\
    				MT_RO_DATA | MT_SECURE);		\
    		mmap_add_region(coh_start, coh_start,			\
    				coh_limit - coh_start,			\
    				MT_DEVICE | MT_RW | MT_SECURE);		\
    		mmap_add(plat_qemu_mmap);				\
    		init_xlat_tables();					\
    									\
    		enable_mmu_##_el(0);					\
    	}
    
    • mmap_add_region/mmap_add:将虚拟地址与物理地址的映射关系添加到内存映射表中
    • init_xlat_tables:初始化页表和转换表,使得MMU能够正确地将虚拟地址转换为物理地址
    • enable_mmu_##_el(0):启用MMU,并将虚拟地址转换为物理地址,从而实现对虚拟内存的访问

bl1_main

bl1_main函数主要完成bl2镜像的加载及其运行环境的配置,如果启用安全启动,还需要对bl2镜像进行验签操作。

void bl1_main(void)
{
	unsigned int image_id;

	/* Announce our arrival */
	NOTICE(FIRMWARE_WELCOME_STR);
	NOTICE("BL1: %s\n", version_string);
	NOTICE("BL1: %s\n", build_message);

	INFO("BL1: RAM %p - %p\n", (void *)BL1_RAM_BASE, (void *)BL1_RAM_LIMIT);

	print_errata_status();

	/* Perform remaining generic architectural setup from EL3 */
	bl1_arch_setup();

	crypto_mod_init();

	/* Initialize authentication module */
	auth_mod_init();

	/* Initialize the measured boot */
	bl1_plat_mboot_init();

	/* Perform platform setup in BL1. */
	bl1_platform_setup();

	/* Get the image id of next image to load and run. */
	image_id = bl1_plat_get_next_image_id();

	/*
	 * We currently interpret any image id other than
	 * BL2_IMAGE_ID as the start of firmware update.
	 */
	if (image_id == BL2_IMAGE_ID)
		bl1_load_bl2();
	else
		NOTICE("BL1-FWU: *******FWU Process Started*******\n");

	/* Teardown the measured boot driver */
	bl1_plat_mboot_finish();

	bl1_prepare_next_image(image_id);

	console_flush();
}
  1. 打印欢迎词,版本信息和编译时间。

  2. bl1_arch_setup:执行EL3等级的通用架构初始化,实际调用write_scr_el3(read_scr_el3() | SCR_RW_BIT);将下一异常等级设置为AArch64。

  3. crypto_mod_init:初始化安全启动需要密码库,这里使用的mbedtls。

  4. auth_mod_init:认证模块初始化,这里调用img_parser_init初始化镜像解析模块,镜像完整性检查是通过X509v3证书来实现,其会存储镜像哈希和公钥。

  5. bl1_plat_mboot_init:measured boot初始化,其是对各个软件组件的度量和验证。

  6. bl1_platform_setup:平台初始化,这里是调用plat_qemu_io_setup,主要是qemu存储驱动初始化,为后面加载镜像做准备。ATF抽象出一组IO接口,定义标准的打开、关闭、读取和写入等操作,关于存储抽象层,后面单独来讲。这里qemu的IO初始化注册了fip设备和memmap设备,所有的BLx镜像均来自FIP固件,而FIP又是来自memmap设备,对于真实的芯片是fip固件是存储在如nand、sd等介质中。另外这里qemu还注册了半主机设备,即可以通过读文件的方式加载镜像。

    void plat_qemu_io_setup(void)
    {
    	int io_result;
    
    	io_result = register_io_dev_fip(&fip_dev_con);
    	assert(io_result == 0);
    
    	io_result = register_io_dev_memmap(&memmap_dev_con);
    	assert(io_result == 0);
    
    	/* Open connections to devices and cache the handles */
    	io_result = io_dev_open(fip_dev_con, (uintptr_t)NULL,
    				&fip_dev_handle);
    	assert(io_result == 0);
    
    	io_result = io_dev_open(memmap_dev_con, (uintptr_t)NULL,
    				&memmap_dev_handle);
    	assert(io_result == 0);
    
    	/* Register the additional IO devices on this platform */
    	io_result = register_io_dev_sh(&sh_dev_con);
    	assert(io_result == 0);
    
    	/* Open connections to devices and cache the handles */
    	io_result = io_dev_open(sh_dev_con, (uintptr_t)NULL, &sh_dev_handle);
    	assert(io_result == 0);
    
    	/* Ignore improbable errors in release builds */
    	(void)io_result;
    }
    
  7. bl1_plat_get_next_image_id:获取下一阶段的镜像ID,这里ID是BL2_IMAGE_ID。

  8. bl1_load_bl2:加载BL2镜像,包括加载镜像和验证镜像。

    static void bl1_load_bl2(void)
    {
    	image_desc_t *desc;
    	image_info_t *info;
    	int err;
    
    	/* Get the image descriptor */
    	desc = bl1_plat_get_image_desc(BL2_IMAGE_ID);
    	assert(desc != NULL);
    
    	/* Get the image info */
    	info = &desc->image_info;
    	INFO("BL1: Loading BL2\n");
    
    	err = bl1_plat_handle_pre_image_load(BL2_IMAGE_ID);
    	if (err != 0) {
    		ERROR("Failure in pre image load handling of BL2 (%d)\n", err);
    		plat_error_handler(err);
    	}
    
    	err = load_auth_image(BL2_IMAGE_ID, info);
    	if (err != 0) {
    		ERROR("Failed to load BL2 firmware.\n");
    		plat_error_handler(err);
    	}
    
    	/* Allow platform to handle image information. */
    	err = bl1_plat_handle_post_image_load(BL2_IMAGE_ID);
    	if (err != 0) {
    		ERROR("Failure in post image load handling of BL2 (%d)\n", err);
    		plat_error_handler(err);
    	}
    
    	NOTICE("BL1: Booting BL2\n");
    }
    
    • bl1_plat_get_image_desc:获取BL2镜像描述信息,主要定义BL2镜像地址、镜像最大大小、镜像是安全状态并且需要执行,具体如下

      #define BL2_IMAGE_DESC {				\
      	.image_id = BL2_IMAGE_ID,			\
      	SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,	\
      		VERSION_2, image_info_t, 0),		\
      	.image_info.image_base = BL2_BASE,		\
      	.image_info.image_max_size = BL2_LIMIT - BL2_BASE,\
      	SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,	\
      		VERSION_2, entry_point_info_t, SECURE | EXECUTABLE),\
      	.ep_info.pc = BL2_BASE,				\
      }
      // 展开如下
      image_desc_t bl2_img_desc = {
          .image_id = BL2_IMAGE_ID,							// 镜像ID
          .image_info.h.type = PARAM_EP,						// 镜像信息类型
          .image_info.h.version = VERSION_2,					// 镜像信息版本
          .image_info.h.size = sizeof(image_info_t),			// 镜像信息大小
          .image_info.h.attr = 0,								// 镜像属性
          .image_info.image_base = BL2_BASE,					// 镜像地址
          .image_info.image_max_size = BL2_LIMIT - BL2_BASE,	// 镜像大小
          .ep_info.h.type = PARAM_EP,							// 跳转信息类型
          .ep_info.h.version = VERSION_2,						// 跳转信息版本
          .ep_info.h.size = sizeof(entry_point_info_t),		// 跳转信息大小
          .ep_info.h.attr = SECURE | EXECUTABLE,   			// 跳转信息属性:镜像是安全状态并且可执行
      }
      
    • bl1_plat_handle_pre_image_load:加载之前的镜像预处理,qemu未处理

    • load_auth_image:加载并认证镜像,主要是调用load_auth_image_internal这个函数,对于安全启动会递归加载并认证镜像,否则直接加载镜像。

      static int load_auth_image_internal(unsigned int image_id,
      				    image_info_t *image_data)
      {
      #if TRUSTED_BOARD_BOOT
      	if (dyn_is_auth_disabled() == 0) {
      		return load_auth_image_recursive(image_id, image_data, 0);
      	}
      #endif
      
      	return load_image(image_id, image_data);
      }
      
    • load_image:普通启动加载镜像。首先获取镜像源,然后通过一些列IO操作完成对BL2镜像的加载。

      static int load_image(unsigned int image_id, image_info_t *image_data)
      {
      	uintptr_t dev_handle;
      	uintptr_t image_handle;
      	uintptr_t image_spec;
      	uintptr_t image_base;
      	size_t image_size;
      	size_t bytes_read;
      	int io_result;
      
      	assert(image_data != NULL);
      	assert(image_data->h.version >= VERSION_2);
      
      	image_base = image_data->image_base;
      
      	/* Obtain a reference to the image by querying the platform layer */
      	io_result = plat_get_image_source(image_id, &dev_handle, &image_spec);
      	if (io_result != 0) {
      		WARN("Failed to obtain reference to image id=%u (%i)\n",
      			image_id, io_result);
      		return io_result;
      	}
      
      	/* Attempt to access the image */
      	io_result = io_open(dev_handle, image_spec, &image_handle);
      	if (io_result != 0) {
      		WARN("Failed to access image id=%u (%i)\n",
      			image_id, io_result);
      		return io_result;
      	}
      
      	INFO("Loading image id=%u at address 0x%lx\n", image_id, image_base);
      
      	/* Find the size of the image */
      	io_result = io_size(image_handle, &image_size);
      	if ((io_result != 0) || (image_size == 0U)) {
      		WARN("Failed to determine the size of the image id=%u (%i)\n",
      			image_id, io_result);
      		goto exit;
      	}
      
      	/* Check that the image size to load is within limit */
      	if (image_size > image_data->image_max_size) {
      		WARN("Image id=%u size out of bounds\n", image_id);
      		io_result = -EFBIG;
      		goto exit;
      	}
      
      	/*
      	 * image_data->image_max_size is a uint32_t so image_size will always
      	 * fit in image_data->image_size.
      	 */
      	image_data->image_size = (uint32_t)image_size;
      
      	/* We have enough space so load the image now */
      	/* TODO: Consider whether to try to recover/retry a partially successful read */
      	io_result = io_read(image_handle, image_base, image_size, &bytes_read);
      	if ((io_result != 0) || (bytes_read < image_size)) {
      		WARN("Failed to load image id=%u (%i)\n", image_id, io_result);
      		goto exit;
      	}
      
      	INFO("Image id=%u loaded: 0x%lx - 0x%lx\n", image_id, image_base,
      	     (uintptr_t)(image_base + image_size));
      
      exit:
      	(void)io_close(image_handle);
      	/* Ignore improbable/unrecoverable error in 'close' */
      
      	/* TODO: Consider maintaining open device connection from this bootloader stage */
      	(void)io_dev_close(dev_handle);
      	/* Ignore improbable/unrecoverable error in 'dev_close' */
      
      	return io_result;
      }
      
    • load_auth_image_recursive:安全启动加载镜像,这里首先会获取BL2镜像的父镜像,这里即可信启动证书镜像,加载完证书镜像并对证书进行签名认证,然后才加载BL2镜像,最后对BL2镜像进行认证。关于认证方式及信任链再后续再单独来讲。

      static int load_auth_image_recursive(unsigned int image_id,
      				    image_info_t *image_data,
      				    int is_parent_image)
      {
      	int rc;
      	unsigned int parent_id;
      
      	/* Use recursion to authenticate parent images */
      	rc = auth_mod_get_parent_id(image_id, &parent_id);
      	if (rc == 0) {
      		rc = load_auth_image_recursive(parent_id, image_data, 1);
      		if (rc != 0) {
      			return rc;
      		}
      	}
      
      	/* Load the image */
      	rc = load_image(image_id, image_data);
      	if (rc != 0) {
      		return rc;
      	}
      
      	/* Authenticate it */
      	rc = auth_mod_verify_img(image_id,
      				 (void *)image_data->image_base,
      				 image_data->image_size);
      	if (rc != 0) {
      		/* Authentication error, zero memory and flush it right away. */
      		zero_normalmem((void *)image_data->image_base,
      			       image_data->image_size);
      		flush_dcache_range(image_data->image_base,
      				   image_data->image_size);
      		return -EAUTH;
      	}
      
      	return 0;
      }
      
    • bl1_plat_handle_post_image_load:镜像平台后处理:获取BL2镜像描述符,获取跳转入口信息,计算BL2的可用内存,其等于总的内存减去BL1已使用的sram内存,最后将BL2的内存信息通过arg1参数传递给BL2。

      int bl1_plat_handle_post_image_load(unsigned int image_id)
      {
      	meminfo_t *bl2_secram_layout;
      	meminfo_t *bl1_secram_layout;
      	image_desc_t *image_desc;
      	entry_point_info_t *ep_info;
      
      	if (image_id != BL2_IMAGE_ID)
      		return 0;
      
      	/* Get the image descriptor */
      	image_desc = bl1_plat_get_image_desc(BL2_IMAGE_ID);
      	assert(image_desc != NULL);
      
      	/* Get the entry point info */
      	ep_info = &image_desc->ep_info;
      
      	/* Find out how much free trusted ram remains after BL1 load */
      	bl1_secram_layout = bl1_plat_sec_mem_layout();
      
      	/*
      	 * Create a new layout of memory for BL2 as seen by BL1 i.e.
      	 * tell it the amount of total and free memory available.
      	 * This layout is created at the first free address visible
      	 * to BL2. BL2 will read the memory layout before using its
      	 * memory for other purposes.
      	 */
      	bl2_secram_layout = (meminfo_t *) bl1_secram_layout->total_base;
      
      	bl1_calc_bl2_mem_layout(bl1_secram_layout, bl2_secram_layout);
      
      	ep_info->args.arg1 = (uintptr_t)bl2_secram_layout;
      
      	VERBOSE("BL1: BL2 memory layout address = %p\n",
      		(void *) bl2_secram_layout);
      	return 0;
      }
      
  9. bl1_plat_mboot_finish:卸载mesured boot驱动

  10. bl1_prepare_next_image:下一阶段镜像启动准备,主要是准备安全世界和普通世界镜像的上下文,普通世界如果支持EL2,则镜像跳转到EL2,否则跳转到EL1

    void bl1_prepare_next_image(unsigned int image_id)
    {
    	/*
    	 * Following array will be used for context management.
    	 * There are 2 instances, for the Secure and Non-Secure contexts.
    	 */
    	static cpu_context_t bl1_cpu_context[2];
    
    	unsigned int security_state, mode = MODE_EL1;
    	image_desc_t *desc;
    	entry_point_info_t *next_bl_ep;
    
    	/* Get the image descriptor. */
    	desc = bl1_plat_get_image_desc(image_id);
    	assert(desc != NULL);
    
    	/* Get the entry point info. */
    	next_bl_ep = &desc->ep_info;
    
    	/* Get the image security state. */
    	security_state = GET_SECURITY_STATE(next_bl_ep->h.attr);
    
    	/* Setup the Secure/Non-Secure context if not done already. */
    	if (cm_get_context(security_state) == NULL)
    		cm_set_context(&bl1_cpu_context[security_state], security_state);
    
    	/* Prepare the SPSR for the next BL image. */
    	if ((security_state != SECURE) && (el_implemented(2) != EL_IMPL_NONE)) {
    		mode = MODE_EL2;
    	}
    
    	next_bl_ep->spsr = (uint32_t)SPSR_64((uint64_t) mode,
    		(uint64_t)MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS);
    
    	/* Allow platform to make change */
    	bl1_plat_set_ep_info(image_id, next_bl_ep);
    
    	/* Prepare the context for the next BL image. */
    	cm_init_my_context(next_bl_ep);
    	cm_prepare_el3_exit(security_state);
    
    	/* Indicate that image is in execution state. */
    	desc->state = IMAGE_STATE_EXECUTED;
    
    	print_entry_point_info(next_bl_ep);
    }
    
    • 定义上下文管理的数组,bl1_cpu_context[0]为安全上下文,bl1_cpu_context[1]为非安全上下文
    • 获取BL2的入口信息和安全状态,这里BL2镜像是安全状态
    • 查看安全或者非安全上下文是否准备好,如果没有准备好就分配
    • 准备SPSR寄存器,SPSR是程序保存状态寄存器,用于保存程序在异常处理过程中的状态。这里先判断是否支持EL2,若支持,则下一异常等级为EL2,否则下一等级为Secure_EL1。然后设置SPSR的异常等级、栈指针SP_ELx和关闭所有DAIF异常(关闭本地中断)
    • 初始化待切换异常等级上下文,这里设置SPSR,PC,SCR寄存器的值
    • 设置异常返回ERET的上下文,进行特权级的上下文切换
    • 最后标记镜像进入执行状态
  11. console_flush:退出BL1前,刷新串口中的所有数据

el3_exit

该函数位于context.S文件,执行实际的异常等级切换l流程。

func el3_exit
	/* ----------------------------------------------------------
	 * Save the current SP_EL0 i.e. the EL3 runtime stack which
	 * will be used for handling the next SMC.
	 * Then switch to SP_EL3.
	 * ----------------------------------------------------------
	 */
	mov	x17, sp
	msr	spsel, #MODE_SP_ELX
	str	x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]

	/* ----------------------------------------------------------
	 * Restore SPSR_EL3, ELR_EL3 and SCR_EL3 prior to ERET
	 * ----------------------------------------------------------
	 */
	ldr	x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
	ldp	x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
	msr	scr_el3, x18
	msr	spsr_el3, x16
	msr	elr_el3, x17

	restore_ptw_el1_sys_regs

	/* ----------------------------------------------------------
	 * Restore general purpose (including x30), PMCR_EL0 and
	 * ARMv8.3-PAuth registers.
	 * Exit EL3 via ERET to a lower exception level.
 	 * ----------------------------------------------------------
 	 */
	bl	restore_gp_pmcr_pauth_regs
	ldr	x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]

	exception_return

endfunc el3_exit
  • 将当前SP_EL0(EL3的运行栈)保存到X17寄存器中
  • 将SP_EL0的值保存到EL3的上下文中
  • 从EL3上下文恢复之前保存的SCR_EL3、SPSR_EL3和ELR_EL3
  • 设置SCR_EL3、SPSR_EL3和ELR_EL3寄存器
  • 恢复GP寄存器,包括x30
  • 执行ERET指令,跳转到BL2的入口地址

欢迎关注“七点八客”微信公众号。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值