ATF入门-3BL2启动流程分析

1,225 阅读20分钟

image.png

BL2承担起了加载其他固件(bl31、bl32(optee-os)和bl33(uboot))的功能,并在secure boot中占据了重要角色,主要是对这些固件进行校验,防止被篡改。

本文直接从代码进行分析,并结合qemu运行代码调试,写了挺多,但是具体到细节里面还是差很远,并且在实际工作中还需要做一些安全的方案,希望对大家有所帮助。

1. BL2简介

image.png

Bl2的启动流程与bl1类似,主要区别是

  • bl2的初始化流程比bl1更简单,
  • 但其可能需要加载更多的镜像,如bl31、bl32和bl33。
  • 由于bl2可以运行于el3或s-el1,它们的入口函数和处理流程都有所区别。使能了RME则BL2运行在el3模式,否则BL2运行在s-el1模式。在armv8架构上,BL2一般运行在s-el1模式。为了简化分析我们选取比较常见的s-el1方式。
  • BL2进行存在于RAM,BL2加载的镜像可能存在于DDR,所以要初始化DDR

BL2跟BL1的功能这么像,为什么BL2不能被BL1替代?

  1. BL1的初衷就是ROM写死在芯片里面,用于校验外部固件用的,只要完成这个功能其他的都不重要。而要完成别的东西,那么bin文件的体积就会增大,在SoC里面的存储是非常贵的
  2. BL1里面的驱动需要越少越好,因为是驱动就涉及到适配不同厂家,ROM都写死了就不能适配了,出厂支持什么就是是什么,这是不能接受的

综上,BL1未完成的加载搬运其他固件的任务就交给BL2了。这样的设计还挺精妙的。

2. 代码分析

image.png

  • BL2的主要工作就是加载BL3x系列镜像,然后通过SMC进入BL1进而跳转到BL31运行。
  • bl2_entrypoint()是BL2的入口,前半部分主要进行一系列初始化工作,然后通过bl2_main()加载BL3x镜像到RAM中,最后通过SMC调用执行BL1中指定的smc handler将CPU执行权交给BL31。

2.1 bl2_entrypoint

bl2/aarch64/bl2_entrypoint.S中是s-el1模式对应的代码。

func bl2_entrypoint
    mov	x20, x0
    mov	x21, x1
    mov	x22, x2
    mov	x23, x3 //参数保存

    adr    x0, early_exceptions
    msr    vbar_el1, x0 //设定异常向量。

    msr    sctlr_el1, x0 //配置cache、内存对齐等属性。
    bl    inv_dcache_range //将BL2的__RW_START__和__RW_END__之间的内存刷回DDR中。
    bl plat_set_my_stack //初始化bl2运行的栈
    
    mov	x0, x20
    mov	x1, x21
    mov	x2, x22
    mov	x3, x23
    bl	bl2_setup
    bl    bl2_main //跳转到BL2主函数执行,该函数加载BL3x镜像,并通过SMC调用BL1指定SMC函数将CPU执行权交给BL31。

2.1.1 参数保存

bl1虽然定义了x0 – x7寄存器用于向bl2传递参数,但bl2实际使用的只有x0 - x3四个寄存器,因此其实际传参的数量不能超过四个。在BL2中x0 - x3四个寄存器在bl2_setup函数里面需要用。但是之前需要执行其他函数就需要先保存下,mov x20, x0 就是把x0的值放入x20。。

在armv8过程调用中,x0 – 18是caller saved寄存器,x19 – x30是callee saved寄存器。

  1. 所谓caller saved寄存器就是在子函数过程调用中,若这些寄存器的内容需要保存,则由函数调用方来保存它们,子程序可以随意使用这些寄存器,而无须在调用完成后恢复它们的值。
  2. 而callee寄存器则相反,子程序若需要使用这些寄存器,则必须要先保存它们的原始值,然后调用完成后恢复它们。

由于bl1的参数位于caller寄存器中,因此需要将其保存到callee寄存器中,以确保后面的子程序调用不会篡改这些寄存器的值。

2.1.2 异常向量设置

early_exceptions在common/aarch64/early_exceptions.S中定义,从其定义可知,bl2捕获到异常后不会对其做实际处理,而只是打印出异常相关的信息,然后将系统设置为panic状态。

异常向量表设置完成后,bl2将使能serror和external abort异常,显然这些异常一般意味着系统出现了像未定义指令、空指针等严重错误,因此需要捕获并将将系统设置为安全状态。其代码流程如下:

msr	daifclr, #DAIF_ABT_BIT

2.1.3 设置sctlr_el1寄存器

该流程主要用于使能指令cache、对齐检查和栈对齐检查特性,其代码流程如下:

	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

2.1.4 C运行时环境准备

C运行时环境需要清空bss段内存,并为其设置运行时栈,其流程与bl1相同,可参考bl1启动流程

2.2 bl2_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();

bl2_early_platform_setup2里面可以看到BL1传入的4个参数了。后面的分析我们仍然以qemu平台为例。参数处理流程如下(plat/qemu/common/qemu_bl2_setup.c):

void bl2_early_platform_setup2(u_register_t arg0, u_register_t arg1,
			       u_register_t arg2, u_register_t arg3)
{
	meminfo_t *mem_layout = (void *)arg1;

	/* Initialize the console to provide early debug support */
	qemu_console_init();

	/* Setup the BL2 memory layout */
	bl2_tzram_layout = *mem_layout;

	plat_qemu_io_setup();
}

(1)从x1参数中获取bl2的内存layout信息

(2)初始化串口控制台

(3)设置bl2的内存layout信息

(4)初始化qemu的storage加载驱动

bl2_plat_arch_setup()中

void bl2_plat_arch_setup(void)
{
	const mmap_region_t bl_regions[] = {
		MAP_BL2_TOTAL,
		MAP_BL2_RO,
#if USE_COHERENT_MEM
		MAP_BL_COHERENT_RAM,
#endif
		{0}
	};

	setup_page_tables(bl_regions, plat_qemu_get_mmap());
#ifdef __aarch64__
	enable_mmu_el1(0);
#else
	enable_mmu_svc_mon(0);
#endif
}

qemu平台初始化主要是为bl2内存建立MMU页表,并启动MMU和dcache,其主要目的是加快后面镜像加载的速度。

2.3 bl2_main

void bl2_main(void)
{
    NOTICE("BL2: %s\n", version_string);
    NOTICE("BL2: %s\n", build_message);

    /* Perform remaining generic architectural setup in S-EL1 */
    bl2_arch_setup();
#if TRUSTED_BOARD_BOOT
    /* Initialize authentication module */
    auth_mod_init(); //初始化image验证模块
#endif /* TRUSTED_BOARD_BOOT */

    /* Load the subsequent bootloader images. */
    next_bl_ep_info = bl2_load_images(); //加载bl3x image到RAM中并返回bl31的入口地址

#ifdef AARCH32
    disable_mmu_icache_secure();
#endif /* AARCH32 */

    smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0); //发起SMC异常启动BL31镜像,交给BL1 bl1_aarch32_smc_handler处理。

bl2_arch_setup()函数,aarch64架构下使能fp和simd寄存器访问权限,其代码如下:

	 write_cpacr(CPACR_EL1_FPEN(CPACR_EL1_FP_TRAP_NONE));

2.4 bl2_load_images

image.png 一般镜像都放在fip包里面,但是这套代码如上图,直接使用了bin文件。

该函数用来加载bl3x的image到RAM中,返回一个具有image入口信息的变量。smc handle根据该变量跳转到bl31进行执行。

entry_point_info_t *bl2_load_images(void)
{
    bl_params_t *bl2_to_next_bl_params;
    bl_load_info_t *bl2_load_info;
    const bl_load_info_node_t *bl2_node_info;
    int plat_setup_done = 0;
    int err;

    /*
     * Get information about the images to load.
     */
    bl2_load_info = plat_get_bl_image_load_info(); //获取待加载镜像BL3x或SCP_EL2信息,然后遍历处理。
    assert(bl2_load_info);
    assert(bl2_load_info->head);
    assert(bl2_load_info->h.type == PARAM_BL_LOAD_INFO);
    assert(bl2_load_info->h.version >= VERSION_2);
    bl2_node_info = bl2_load_info->head; // bl2_node_info指向镜像第一个对象。

    while (bl2_node_info) { //循环遍历bl2_mem_params_descs成员并加载处理。
        /*
         * Perform platform setup before loading the image,
         * if indicated in the image attributes AND if NOT
         * already done before.
         */
        if (bl2_node_info->image_info->h.attr & IMAGE_ATTRIB_PLAT_SETUP) { //确定加载前是否需要进行特定平台初始化。
            if (plat_setup_done) {
                WARN("BL2: Platform setup already done!!\n");
            } else {
                INFO("BL2: Doing platform setup\n");
                bl2_platform_setup(); //bl2平台相关的设置,如security设置,timer设置以及dtb设置
                plat_setup_done = 1;
            }
        }
        
        err = bl2_plat_handle_pre_image_load(bl2_node_info->image_id); //镜像加载前平台可以执行一些其特定的流程
        if (err != 0) {
                ERROR("BL2: Failure in pre image load handling (%i)\n", err);
                plat_error_handler(err);
        }


        if (!(bl2_node_info->image_info->h.attr & IMAGE_ATTRIB_SKIP_LOADING)) { //确定是否需要跳过加载到RAM步骤;如否,则进行验证并加载。
            INFO("BL2: Loading image id %d\n", bl2_node_info->image_id);
            err = load_auth_image(bl2_node_info->image_id,
                bl2_node_info->image_info); //将镜像加载到RAM,然后进行验证。此过程跟BL1一样
            if (err) {
                ERROR("BL2: Failed to load image (%i)\n", err);
                plat_error_handler(err);
            }
        } else {
            INFO("BL2: Skip loading image id %d\n", bl2_node_info->image_id);
        }

        /* Allow platform to handle image information. */
        err = bl2_plat_handle_post_image_load(bl2_node_info->image_id); //修改特定镜像的加载信息。
        if (err) {
            ERROR("BL2: Failure in post image load handling (%i)\n", err);
            plat_error_handler(err);
        }

        /* Go to next image */
        bl2_node_info = bl2_node_info->next_load_info; //循环加载下一个镜像。
    }

    /*
     * Get information to pass to the next image.
     */
    bl2_to_next_bl_params = plat_get_next_bl_params(); //获取下一个执行镜像入口信息,属性为EXECUTABLE和EP_FIRST_EXE,也即BL31。
    assert(bl2_to_next_bl_params);
    assert(bl2_to_next_bl_params->head);
    assert(bl2_to_next_bl_params->h.type == PARAM_BL_PARAMS);
    assert(bl2_to_next_bl_params->h.version >= VERSION_2);

    /* Flush the parameters to be passed to next image */
    plat_flush_next_bl_params(); //将bl_mem_params_desc_ptr数据刷到DDR中,后面即将通过SMC跳转到BL1启动BL31,保持数据一致性。

    return bl2_to_next_bl_params->head->ep_info;
}

2.4.1 镜像信息

bl2_mem_params_descs定义了BL2需要加载镜像的信息,对于qemu平台其定义位于plat/qemu/common/qemu_bl2_mem_params_desc.c中

通过REGISTER_BL_IMAGE_DESCS()将其和bl_mem_params_desc_ptr关联,并获取需要加载镜像数目bl_mem_params_desc_num。

static bl_mem_params_node_t bl2_mem_params_descs[] = {
#ifdef SCP_BL2_BASE
	/* Fill SCP_BL2 related information if it exists */
    {
	    .image_id = SCP_BL2_IMAGE_ID,
 
	    SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY,
		    VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE),
 
	    SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY,
		    VERSION_2, image_info_t, 0),
	    .image_info.image_base = SCP_BL2_BASE,
	    .image_info.image_max_size = PLAT_CSS_MAX_SCP_BL2_SIZE,
 
	    .next_handoff_image_id = INVALID_IMAGE_ID,
    },
#endif /* SCP_BL2_BASE */
 
#ifdef EL3_PAYLOAD_BASE
	/* Fill EL3 payload related information (BL31 is EL3 payload)*/
    {
	    .image_id = BL31_IMAGE_ID,
 
	    SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
		    VERSION_2, entry_point_info_t,
		    SECURE | EXECUTABLE | EP_FIRST_EXE),
	    .ep_info.pc = EL3_PAYLOAD_BASE,
	    .ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
		    DISABLE_ALL_EXCEPTIONS),
 
	    SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
		    VERSION_2, image_info_t,
		    IMAGE_ATTRIB_PLAT_SETUP | IMAGE_ATTRIB_SKIP_LOADING),
 
	    .next_handoff_image_id = INVALID_IMAGE_ID,
    },
 
#else /* EL3_PAYLOAD_BASE */
 
	/* Fill BL31 related information */
    {
	    .image_id = BL31_IMAGE_ID,
 
	    SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
		    VERSION_2, entry_point_info_t,
		    SECURE | EXECUTABLE | EP_FIRST_EXE),
	    .ep_info.pc = BL31_BASE,
	    .ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
		    DISABLE_ALL_EXCEPTIONS),
#if DEBUG
	    .ep_info.args.arg1 = ARM_BL31_PLAT_PARAM_VAL,
#endif
 
	    SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
		    VERSION_2, image_info_t, IMAGE_ATTRIB_PLAT_SETUP),
	    .image_info.image_base = BL31_BASE,
	    .image_info.image_max_size = BL31_LIMIT - BL31_BASE,
 
# ifdef BL32_BASE
	    .next_handoff_image_id = BL32_IMAGE_ID,
# else
	    .next_handoff_image_id = BL33_IMAGE_ID,
# endif
    },
 
# ifdef BL32_BASE
	/* Fill BL32 related information */
    {
	    .image_id = BL32_IMAGE_ID,
 
	    SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
		    VERSION_2, entry_point_info_t, SECURE | EXECUTABLE),
	    .ep_info.pc = BL32_BASE,
 
	    SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
		    VERSION_2, image_info_t, 0),
	    .image_info.image_base = BL32_BASE,
	    .image_info.image_max_size = BL32_LIMIT - BL32_BASE,
 
	    .next_handoff_image_id = BL33_IMAGE_ID,
    },
# endif /* BL32_BASE */
 
	/* Fill BL33 related information */
    {
	    .image_id = BL33_IMAGE_ID,
	    SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
		    VERSION_2, entry_point_info_t, NON_SECURE | EXECUTABLE),
# ifdef PRELOADED_BL33_BASE
	    .ep_info.pc = PRELOADED_BL33_BASE,
 
	    SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
		    VERSION_2, image_info_t, IMAGE_ATTRIB_SKIP_LOADING),
# else
	    .ep_info.pc = PLAT_ARM_NS_IMAGE_OFFSET,
 
	    SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
		    VERSION_2, image_info_t, 0),
	    .image_info.image_base = PLAT_ARM_NS_IMAGE_OFFSET,
	    .image_info.image_max_size = ARM_DRAM1_SIZE,
# endif /* PRELOADED_BL33_BASE */
 
	    .next_handoff_image_id = INVALID_IMAGE_ID,
    }
#endif /* EL3_PAYLOAD_BASE */
};

在该变量中规定了SCP_BL2, EL3_payload, bl32, bl33 image的相关信息,例如:

  • image的入口地址信息:ep_info
  • image在RAM中的基地址:image_base
  • image的基本信息:image_info
  • image的ID值:image_id

2.4.2 bl2_plat_handle_post_image_load()

该接口用于设置镜像加载相关信息,qemu平台代码如下(plat/qemu/common/qemu_bl2_setup.c):

static int qemu_bl2_handle_post_image_load(unsigned int image_id)
{
	int err = 0;
	bl_mem_params_node_t *bl_mem_params = get_bl_mem_params_node(image_id);
	…
	switch (image_id) {
	case BL32_IMAGE_ID:
        …                                                                              (a)
	case BL33_IMAGE_ID:
#if ARM_LINUX_KERNEL_AS_BL33
		bl_mem_params->ep_info.args.arg0 =
			(u_register_t)ARM_PRELOADED_DTB_BASE;
		bl_mem_params->ep_info.args.arg1 = 0U;
		bl_mem_params->ep_info.args.arg2 = 0U;
		bl_mem_params->ep_info.args.arg3 = 0U;                                   (b)
#else
		bl_mem_params->ep_info.args.arg0 = 0xffff & read_mpidr();                (c)
#endif
		bl_mem_params->ep_info.spsr = qemu_get_spsr_for_bl33_entry();            (d)
		break;
	default:
		break;
	}

	return err;
}
  1. bl32用于加载trust os,在启动流程中不是必须的,此处暂时不讨论
  2. 若由bl2直接启动linux,则设置linux的启动参数。我们知道armv8架构的linux启动参数都是通过dtb传递的,因 此这里将dtb地址设置为其启动参数
  3. 对于其它类型的bl33(如uboot),则将当前处理器的affinity信息作为其启动参数
  4. 设置bl33的spsr

2.4.3 smc到BL31

bl2可能会加载bl31、bl32、bl33镜像,因此其需要将这些被加载镜像的信息传给下一阶段。Bl2通过链表方式来组织这些参数,其中每一级镜像是链表的一个节点,其具体结构如下图所示:

bl2若运行在S-EL1下,则镜像加载完成并准备好参数后,需要通过smc异常再次进入bl1,由bl1的smc处理函数来执行实际的镜像切换流程。在执行smc命令之前,我们需要为其设置好参数,上面的bl_params->head->ep_info会设置为smc的调用参数,同时bl_params还需要被设置为第一级镜像的arg0参数,即在启动第一级镜像(如bl31)时,通过其x0寄存器传给它的也是bl_params指针,从而使bl31可以继续启动其后的镜像。

  由于bl_params位于sram内存中,而bl2开启了dcache,因此在跳转到smc之前,需要将这部分数据从cache刷到sram中。最后我们就可以调用下面的smc指令返回bl1le :

smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0);

2.5 smc_handler64

smc处理流程如下:

vector_entry SynchronousExceptionA64                
smc_handler64    

其中smc_handler64会判断bl2传入的命令,若命令为BL1_SMC_RUN_IMAGE,则从x1寄存器中获取下一阶段镜像的ep_info,执行上下文切换的准备,并最终跳转到下一阶段镜像的入口执行。其代码流程如下:

func smc_handler64
	mov	x30, #BL1_SMC_RUN_IMAGE
	cmp	x30, x0                                                               
	b.ne	smc_handler1mrs	x30, scr_el3
	tst	x30, #SCR_NS_BIT2b.ne	unexpected_sync_exception

	ldr	x30, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
	msr	spsel, #MODE_SP_EL0
	mov	sp, x303mov	x20, x14mov	x0, x20
	bl	bl1_print_next_bl_ep_info5ldp	x0, x1, [x20, #ENTRY_POINT_INFO_PC_OFFSET]                         
	msr	elr_el3, x0
	msr	spsr_el3, x16ubfx	x0, x1, #MODE_EL_SHIFT, #2
	cmp	x0, #MODE_EL3                                                         (7b.ne	unexpected_sync_exceptionmov	x0, x20
	bl	bl1_plat_prepare_exit8ldp	x6, x7, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x30)]
	ldp	x4, x5, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x20)]
	ldp	x2, x3, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x10)]
	ldp	x0, x1, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x0)]9exception_return10endfunc smc_handler64

(1)判断通过x0寄存器传入的smc命令是否为BL1_SMC_RUN_IMAGE,若不是则执行smc_handler,否则继续执行下面的镜像跳转流程

(2)判断scr_el3的secure位是否为0。该值表示EL0 – EL2等级的secure状态,因此实际指的是smc跳转之前的执行状态。所以该值为0就表示smc跳转前bl2执行在secure状态,否则表示bl2执行在non secure状态。在atf架构中,为了系统的安全性bl2必须要运行在secure状态(因为通常在bl2需要执行一些secure相关的设置,如tzasc,tzma,tzpc等)

(3)从sp_el3栈中获取先前el3_exit流程中保存的运行时栈的值,将其恢复回sp_el0,并将栈指针切换回sp_el0

(4)获取smc指令通过x1寄存器传入的next_bl_ep_info参数

(5)打印参数信息

(6)从next_bl_ep_info参数中获取bl1的入口地址和spsr寄存器值,分别将其设置到elr_el3和spsr_el3,为跳转到下一阶段镜像做准备

(7)判断下一阶段镜像是否运行于el3,若不是则出错

(8)平台相关的跳转前自定义流程,该函数默认什么都不做

(9)通过x0 – x7设置跳转参数,在bl2中只向arg0设置了bl_params指针这一个参数,因此bl2传给bl31的参数为描述镜像信息的bl_params指针

(10)通过eret跳转到下一阶段镜像入口函数处执行

3. 实战调试

image.png

BL31的运行地址在0xe0a0000。那么就是BL2把BL31.bin加载到了这个地址。

image.png

id 3对应的就是BL31,bl2_load_images()函数会循环加载bin文件固件,从这里看起

3.1 问题1:image的列表放在哪里?

bl2_load_info = plat_get_bl_image_load_info(); 会获取列表 get_bl_load_info_from_mem_params_desc

	for (; index < bl_mem_params_desc_num; index++) {

		/* Populate the image information */
		bl_node_info->image_id = bl_mem_params_desc_ptr[index].image_id;
		bl_node_info->image_info = &bl_mem_params_desc_ptr[index].image_info;

bl_mem_params_desc_ptr变量的定义地方:

#define REGISTER_BL_IMAGE_DESCS(_img_desc)				\
	bl_mem_params_node_t *bl_mem_params_desc_ptr = &_img_desc[0];	\
	unsigned int bl_mem_params_desc_num = ARRAY_SIZE(_img_desc);
	
REGISTER_BL_IMAGE_DESCS(bl2_mem_params_descs)

bl2_mem_params_descs中就是列表了:

static bl_mem_params_node_t bl2_mem_params_descs[] = {
#ifdef EL3_PAYLOAD_BASE
ddd
...
#else
fff
	{ .image_id = BL31_IMAGE_ID,

	  SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP, VERSION_2,
				entry_point_info_t,
				SECURE | EXECUTABLE | EP_FIRST_EXE),
	  .ep_info.pc = BL31_BASE,
	  .ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
				  DISABLE_ALL_EXCEPTIONS),
# if DEBUG
	  .ep_info.args.arg1 = QEMU_BL31_PLAT_PARAM_VAL,
# endif
	  SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t,
				IMAGE_ATTRIB_PLAT_SETUP),
	  .image_info.image_base = BL31_BASE,
	  .image_info.image_max_size = BL31_LIMIT - BL31_BASE,

# ifdef QEMU_LOAD_BL32
	  .next_handoff_image_id = BL32_IMAGE_ID,
# else
	  .next_handoff_image_id = BL33_IMAGE_ID,
# endif
	},

EL3_PAYLOAD_BASE这个宏有没有定义,有一个好办法就是代码里面加乱码进行编译。 例如上面我们加了ddd和fff,进行编译:

image.png

报错了fff,则证明这个宏没定义。奇巧淫技,哈哈。

.ep_info.pc = BL31_BASE, BL31_BASE就是0xe0a0000,其是代码里面写死的,内存进行了统一的规划。

包括系统程序状态寄存器(SPSR),这些寄存器用于在异常进入和退出时保存和恢复处理器的状态。

  1. .ep_info.spsr: 这似乎是一个结构体(可能是某种异常处理或上下文切换相关的结构体)的成员,名为spsr,该结构体可能被命名为ep_info。这个成员用于存储SPSR的值。
  2. SPSR_64(...) : 这似乎是一个宏或内联函数,用于构建64位的SPSR值。ARMv8是64位架构,所以这里使用64来指定我们正在处理的是64位版本的SPSR。
  3. MODE_EL3: 这表示处理器应该被设置到异常级别3(EL3)。在ARMv8中,EL3通常用于最高级别的安全监视器(Secure Monitor)或安全世界(Secure World)中的信任根(Root of Trust)。
  4. MODE_SP_ELX: 这通常用于指定栈指针(SP)的EL级别。ELX是一个占位符,表示它可以是任何EL级别(EL0、EL1、EL2或EL3)。但是,由于我们已经指定了MODE_EL3,所以这里的SP_ELX很可能意味着栈指针也应该是EL3级别的。
  5. DISABLE_ALL_EXCEPTIONS: 这表示应该禁用所有异常。在处理器状态寄存器中,有一些位用于控制哪些异常是启用的,哪些是被禁用的。通过设置这些位为适当的值,我们可以确保在处理器处于特定模式时不会响应某些异常。

3.2 问题2 加载bin文件前做什么?

SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t,
                        IMAGE_ATTRIB_PLAT_SETUP),

IMAGE_ATTRIB_PLAT_SETUP定义了这个就可以在加载bin文件前进行一些初始化操作,例如安全设置等。

        if (bl2_node_info->image_info->h.attr & IMAGE_ATTRIB_PLAT_SETUP) { //确定加载前是否需要进行特定平台初始化。
            if (plat_setup_done) {
                WARN("BL2: Platform setup already done!!\n");
            } else {
                INFO("BL2: Doing platform setup\n");
                bl2_platform_setup(); //bl2平台相关的设置,如security设置,timer设置以及dtb设置

由于这个是对每个image都进行了一遍,最好是根据image id进行区别处理。

3.3 问题2:固件bin文件在哪里?

image.png

例如bl31.bin就在上图我们编译出来的地方,这个代码目前不使用fip包,直接加载了bl31.bin

load_image-》plat_get_image_source(image_id, &dev_handle, &image_spec); 用于获取固件的信息,

  • image_spec就是我们找到的存放地址
  • dev_handle就是存放介质驱动提供的ops操作函数,里面有打开读写等操作,这算是一个POSIX接口

plat/qemu/common/qemu_io_storage.c中定义了plat_get_image_source

*policy = get_io_policy(image_id);

	[BL31_IMAGE_ID] = {
		&fip_dev_handle,
		(uintptr_t)&bl31_uuid_spec,
		open_fip
	},

open_fip进行校验会失败。关于fip包这里不多说明,大体就是所有的bin文件打包成一个fip.bin然后进行安全校验。

	result = policy->check(policy->image_spec);
	if (result == 0) {
		*image_spec = policy->image_spec;
		*dev_handle = *(policy->dev_handle);
	} else {
		INFO("Trying alternative IO\n");
		result = get_alt_image_source(image_id, dev_handle, image_spec);
	}

get_alt_image_source寻找替代的bin文件

get_io_file_spec(image_id);--》sh_file_spec[image_id];

	[BL31_IMAGE_ID] = {
		.path = BL31_IMAGE_NAME,
		.mode = FOPEN_MODE_RB
	},

#define BL31_IMAGE_NAME "bl31.bin"

#参考: icyshuai.blog.csdn.net/article/det… zhuanlan.zhihu.com/p/520048433 www.cnblogs.com/arnoldlu/p/…

后记:

单纯的看代码学习概念估计非常的枯燥,如果追求速度反而是走马观花,所以研究这些代码是需要一些时间进行调试的。在实际的工作中,可能BL2这一个固件就是一个人在大厂里面打的一个螺丝,是需要全投入一直干的,那可能光BL2就干一两年,才能完全的透彻。总之:纸上得来终觉浅,唯有实践出真知。