代码导读
关于平台相关的代码和函数均以qemu实现解读。
BL2
BL2是启动的第二阶段,同理复位的入口函数为bl2_entrypoint
。BL2的加载流程同BL1类似,只是需要加载更多的镜像,如BL31、BL32或者BL33等,大致的函数执行流程如下。
env_init
基础环境初始化。
-
保存BL1传递过来的x0-x3参数,BL1通过x0-x1寄存器向BL2传递参数,当前BL1只通过x1传递过来BL2可用的安全内存区域。
/*--------------------------------------------- * Save arguments x0 - x3 from BL1 for future * use. * --------------------------------------------- */ mov x20, x0 mov x21, x1 mov x22, x2 mov x23, x3
# 使用gdb调试看下寄存器值 (gdb) info reg x0 x1 x2 x3 x0 0x0 0 x1 0xe001000 234885120 x2 0x0 0 x3 0x0 0
-
设定异常向量:将EL1异常向量表基地址设置到VBAR寄存器,向量表定义位于
early_exceptions.S
,可以看出BL2捕获异常后不会做处理,只是打印异常相关的信息,然后直接进入panic。/* --------------------------------------------- * Set the exception vector to something sane. * --------------------------------------------- */ adr x0, early_exceptions msr vbar_el1, x0 isb
-
使能SError中断,用来屏蔽系统错误
/* --------------------------------------------- * Enable the SError interrupt now that the * exception vectors have been setup. * --------------------------------------------- */ msr daifclr, #DAIF_ABT_BIT
-
EL1等级下,使能ICACHE和栈对齐、数据对齐检查,关闭预取
/* --------------------------------------------- * Enable the instruction cache, stack pointer * and data access alignment checks and disable * speculative loads. * --------------------------------------------- */ 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运行环境初始化,将ROM中的数据段重定位到RAM和清零BSS段
/* --------------------------------------------- * 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
plat_set_my_stack
分配bl2运行时栈
func plat_set_my_stack
get_up_stack platform_coherent_stacks, PLATFORM_STACK_SIZE
mov sp, x0
ret
endfunc plat_set_my_stack
bl2_setup
bl2_setup是BL2建立初始化,包括两个函数:bl2_early_platform_setup2()
和bl2_plat_arch_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类似,该函数会初始化串口、设置BL2内存区域和qemu的存储驱动初始化
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(); }
-
bl2_plat_arch_setup
同BL1类似,该函数会配置MMU,启动DCACHe。
void bl2_plat_arch_setup(void) { QEMU_CONFIGURE_BL2_MMU(bl2_tzram_layout.total_base, bl2_tzram_layout.total_size, BL_CODE_BASE, BL_CODE_END, BL_RO_DATA_BASE, BL_RO_DATA_END, BL_COHERENT_RAM_BASE, BL_COHERENT_RAM_END); }
bl2_main
bl2_main主要完成了bl2阶段的主要操作, 包括对下一个阶段镜像文件的解析、 获取入口地址和镜像文件大小等信息, 然后对镜像文件进行验签和加载操作。 将bl31加载到内存中后会触发安全监控模式调用(smc) 将CPU权限转交给bl31。
void bl2_main(void)
{
entry_point_info_t *next_bl_ep_info;
NOTICE("BL2: %s\n", version_string);
NOTICE("BL2: %s\n", build_message);
/* Perform remaining generic architectural setup in S-EL1 */
bl2_arch_setup();
crypto_mod_init();
/* Initialize authentication module */
auth_mod_init();
/* Initialize the Measured Boot backend */
bl2_plat_mboot_init();
/* Initialize boot source */
bl2_plat_preload_setup();
/* Load the subsequent bootloader images. */
next_bl_ep_info = bl2_load_images();
/* Teardown the Measured Boot backend */
bl2_plat_mboot_finish();
console_flush();
/*
* Run next BL image via an SMC to BL1. Information on how to pass
* control to the BL32 (if present) and BL33 software images will
* be passed to next BL image as an argument.
*/
smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0);
}
-
bl2_arch_setup:开启FP/SIMD寄存器访问权限。
void bl2_arch_setup(void) { /* Give access to FP/SIMD registers */ write_cpacr(CPACR_EL1_FPEN(CPACR_EL1_FP_TRAP_NONE)); }
-
crypto_mod_init:初始化安全启动需要密码库,与BL1相同。
-
auth_mod_init:认证模块初始化,与BL1相同。
-
bl1_plat_mboot_init:measured boot初始化,与BL1相同。
-
bl2_plat_preload_setup:qemu为空函数
-
bl2_load_images:加载bl3x镜像,并返回bl31的入口地址。
struct entry_point_info *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(); assert(bl2_load_info != NULL); assert(bl2_load_info->head != NULL); 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; while (bl2_node_info != NULL) { /* * 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) != 0U) { if (plat_setup_done != 0) { WARN("BL2: Platform setup already done!!\n"); } else { INFO("BL2: Doing platform setup\n"); bl2_platform_setup(); 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) == 0U) { INFO("BL2: Loading image id %u\n", bl2_node_info->image_id); err = load_auth_image(bl2_node_info->image_id, bl2_node_info->image_info); if (err != 0) { ERROR("BL2: Failed to load image id %u (%i)\n", bl2_node_info->image_id, err); plat_error_handler(err); } } else { INFO("BL2: Skip loading image id %u\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 != 0) { 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(); assert(bl2_to_next_bl_params != NULL); assert(bl2_to_next_bl_params->head != NULL); assert(bl2_to_next_bl_params->h.type == PARAM_BL_PARAMS); assert(bl2_to_next_bl_params->h.version >= VERSION_2); assert(bl2_to_next_bl_params->head->ep_info != NULL); /* Populate arg0 for the next BL image if not already provided */ if (bl2_to_next_bl_params->head->ep_info->args.arg0 == (u_register_t)0) bl2_to_next_bl_params->head->ep_info->args.arg0 = (u_register_t)bl2_to_next_bl_params; /* Flush the parameters to be passed to next image */ plat_flush_next_bl_params(); return bl2_to_next_bl_params->head->ep_info; }
-
plat_get_bl_image_load_info:获取加载所有镜像的信息,这里会调用到
get_bl_load_info_from_mem_params_desc
,该函数会创建待加载镜像的链表。bl_load_info_t *get_bl_load_info_from_mem_params_desc(void) { unsigned int index = 0; /* If there is no image to start with, return NULL */ if (bl_mem_params_desc_num == 0U) return NULL; /* Assign initial data structures */ bl_load_info_node_t *bl_node_info = &bl_mem_params_desc_ptr[index].load_node_mem; bl_load_info.head = bl_node_info; SET_PARAM_HEAD(&bl_load_info, PARAM_BL_LOAD_INFO, VERSION_2, 0U); /* Go through the image descriptor array and create the list */ 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; /* Link next image if present */ if ((index + 1U) < bl_mem_params_desc_num) { /* Get the memory and link the next node */ bl_node_info->next_load_info = &bl_mem_params_desc_ptr[index + 1U].load_node_mem; bl_node_info = bl_node_info->next_load_info; } } return &bl_load_info; }
待加载镜像的描述信息是由
bl_mem_params_desc_ptr
提供,其是REGISTER_BL_IMAGE_DESCS(bl2_mem_params_descs)
注册到系统中的。镜像文件描述信息是由bl_mem_params_node_t结构体定义的。/* Following structure is used to store BL ep/image info. */ typedef struct bl_mem_params_node { unsigned int image_id; image_info_t image_info; entry_point_info_t ep_info; unsigned int next_handoff_image_id; bl_load_info_node_t load_node_mem; bl_params_node_t params_node_mem; } bl_mem_params_node_t;
- image_id:镜像ID
- image_info:镜像信息
- ep_info:跳转信息
- next_handoff_image_id:交接的下一个镜像ID
- load_node_mem:镜像加载流程节点
- params_node_mem:镜像执行流程节点
以qemu平台,只加载bl31和bl33的镜像描述信息如下。其中删除PRELOADED_BL33_BASE宏,其表示如果平台已经预加载BL33,就直接跳转过去执行,而不是依赖TF-A去加载。可以看出BL2先加载BL31镜像,然后再加载BL33镜像。
static bl_mem_params_node_t bl2_mem_params_descs[] = { /* 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), 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, .next_handoff_image_id = BL33_IMAGE_ID, }, /* 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), .ep_info.pc = NS_IMAGE_OFFSET, SET_STATIC_PARAM_HEAD(image_info, PARAM_EP, VERSION_2, image_info_t, 0), .image_info.image_base = NS_IMAGE_OFFSET, .image_info.image_max_size = NS_IMAGE_MAX_SIZE, .next_handoff_image_id = INVALID_IMAGE_ID, } }; REGISTER_BL_IMAGE_DESCS(bl2_mem_params_descs)
-
进入循环遍历节点加载镜像,首先判断加载镜像之前是否需要执行平台初始化,即bl2_platform_setup,包括security_setup和update_dt两个函数,security_setup可以设置TrustZone地址空间控制或者其他外设,qemu平台为空;update_dt是更新设备树。
static void security_setup(void) { /* * This is where a TrustZone address space controller and other * security related peripherals, would be configured. */ } static void update_dt(void) { int ret; void *fdt = (void *)(uintptr_t)ARM_PRELOADED_DTB_BASE; ret = fdt_open_into(fdt, fdt, PLAT_QEMU_DT_MAX_SIZE); if (ret < 0) { ERROR("Invalid Device Tree at %p: error %d\n", fdt, ret); return; } if (dt_add_psci_node(fdt)) { ERROR("Failed to add PSCI Device Tree node\n"); return; } if (dt_add_psci_cpu_enable_methods(fdt)) { ERROR("Failed to add PSCI cpu enable methods in Device Tree\n"); return; } ret = fdt_pack(fdt); if (ret < 0) ERROR("Failed to pack Device Tree at %p: error %d\n", fdt, ret); } void bl2_platform_setup(void) { security_setup(); update_dt(); /* TODO Initialize timer */ }
-
bl2_plat_handle_pre_image_load:镜像加载前可以进行一些预处理操作。
-
load_auth_image:加载并认证镜像,与BL1的加载流程完全一致。
-
bl2_plat_handle_post_image_load:平台镜像加载后处理,对于qemu平台会调用qemu_bl2_handle_post_image_load。
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); assert(bl_mem_params); switch (image_id) { case BL32_IMAGE_ID: /* ... */ break; case BL33_IMAGE_ID: #if ARM_LINUX_KERNEL_AS_BL33 /* * According to the file ``Documentation/arm64/booting.txt`` of * the Linux kernel tree, Linux expects the physical address of * the device tree blob (DTB) in x0, while x1-x3 are reserved * for future use and must be 0. */ 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; #else /* BL33 expects to receive the primary CPU MPID (through r0) */ bl_mem_params->ep_info.args.arg0 = 0xffff & read_mpidr(); #endif bl_mem_params->ep_info.spsr = qemu_get_spsr_for_bl33_entry(); break; default: /* Do nothing in default case */ break; } return err; }
-
对于BL32,是加载Trust OS,我们这里不进行加载,也就不会处理
-
对于BL33,如果是直接将Linux作为BL33启动,那么通过dtb地址传递Linux需要的启动参数,否则就讲当前处理器affinity信息(如处理器ID)作为启动参数传递给arg0。最后设置跳转bl33需要的spsr寄存器,指明进入非安全世界的异常等级、栈指针SP_ELx和关闭所有异常。
static uint32_t qemu_get_spsr_for_bl33_entry(void) { uint32_t spsr; unsigned int mode; /* Figure out what mode we enter the non-secure world in */ mode = (el_implemented(2) != EL_IMPL_NONE) ? MODE_EL2 : MODE_EL1; /* * TODO: Consider the possibility of specifying the SPSR in * the FIP ToC and allowing the platform to have a say as * well. */ spsr = SPSR_64(mode, MODE_SP_ELX, DISABLE_ALL_EXCEPTIONS); return spsr; }
-
-
当加载完所有镜像后,调用plat_get_next_bl_params获取传递给下一个镜像的信息。对于qemu平台,会调用get_next_bl_params_from_mem_params_desc,该函数会创建待执行镜像的链表。首先寻找第一个需要执行的镜像,在bl2_mem_params_descs中定义的是bl31,放入next_bl_params链表头,然后可执行镜像的参数节点添加到参数列表中。
bl_params_t *get_next_bl_params_from_mem_params_desc(void) { unsigned int count; unsigned int img_id = 0U; unsigned int link_index = 0U; bl_params_node_t *bl_current_exec_node = NULL; bl_params_node_t *bl_last_exec_node = NULL; bl_mem_params_node_t *desc_ptr; /* If there is no image to start with, return NULL */ if (bl_mem_params_desc_num == 0U) return NULL; /* Get the list HEAD */ for (count = 0U; count < bl_mem_params_desc_num; count++) { desc_ptr = &bl_mem_params_desc_ptr[count]; if ((EP_GET_EXE(desc_ptr->ep_info.h.attr) == EXECUTABLE) && (EP_GET_FIRST_EXE(desc_ptr->ep_info.h.attr) == EP_FIRST_EXE)) { next_bl_params.head = &desc_ptr->params_node_mem; link_index = count; break; } } /* Make sure we have a HEAD node */ assert(next_bl_params.head != NULL); /* Populate the HEAD information */ SET_PARAM_HEAD(&next_bl_params, PARAM_BL_PARAMS, VERSION_2, 0U); /* * Go through the image descriptor array and create the list. * This bounded loop is to make sure that we are not looping forever. */ for (count = 0U; count < bl_mem_params_desc_num; count++) { desc_ptr = &bl_mem_params_desc_ptr[link_index]; /* Make sure the image is executable */ assert(EP_GET_EXE(desc_ptr->ep_info.h.attr) == EXECUTABLE); /* Get the memory for current node */ bl_current_exec_node = &desc_ptr->params_node_mem; /* Populate the image information */ bl_current_exec_node->image_id = desc_ptr->image_id; bl_current_exec_node->image_info = &desc_ptr->image_info; bl_current_exec_node->ep_info = &desc_ptr->ep_info; if (bl_last_exec_node != NULL) { /* Assert if loop detected */ assert(bl_last_exec_node->next_params_info == NULL); /* Link the previous node to the current one */ bl_last_exec_node->next_params_info = bl_current_exec_node; } /* Update the last node */ bl_last_exec_node = bl_current_exec_node; /* If no next hand-off image then break out */ img_id = desc_ptr->next_handoff_image_id; if (img_id == INVALID_IMAGE_ID) break; /* Get the index for the next hand-off image */ link_index = get_bl_params_node_index(img_id); assert((link_index > 0U) && (link_index < bl_mem_params_desc_num)); } /* Invalid image is expected to terminate the loop */ assert(img_id == INVALID_IMAGE_ID); return &next_bl_params; }
-
plat_flush_next_bl_params:刷新参数给下一个镜像,由于BL2开启了dcache,在跳转之前,需要将参数数据从cache刷新到SRAM中。qemu平台调用flush_bl_params_desc,最终会调用到flush_bl_params_desc_args。
void flush_bl_params_desc_args(bl_mem_params_node_t *mem_params_desc_ptr, unsigned int mem_params_desc_num, bl_params_t *next_bl_params_ptr) { assert(mem_params_desc_ptr != NULL); assert(mem_params_desc_num != 0U); assert(next_bl_params_ptr != NULL); flush_dcache_range((uintptr_t)mem_params_desc_ptr, sizeof(*mem_params_desc_ptr) * mem_params_desc_num); flush_dcache_range((uintptr_t)next_bl_params_ptr, sizeof(*next_bl_params_ptr)); }
-
-
bl2_plat_mboot_finish:卸载mesured boot驱动
-
console_flush:出BL2前,刷新串口中的所有数据
-
通过SMC跳转到下一镜像,由于BL2运行在S-EL1下,需要通过SMC异常再次进入BL1,由BL1的SMC处理函数来执行实际的镜像切换流程。这里触发了一个类型为BL1_SMC_RUN_IMAGE的安全监控模式调用,第二个参数为下一阶段的入口跳转信息。
smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0);
通过SMC调用会进入到bl1_exceptions.S中smc_handler64,如下。
func smc_handler64 /* ---------------------------------------------- * Detect if this is a RUN_IMAGE or other SMC. * ---------------------------------------------- */ mov x30, #BL1_SMC_RUN_IMAGE cmp x30, x0 b.ne smc_handler /* ------------------------------------------------ * Make sure only Secure world reaches here. * ------------------------------------------------ */ mrs x30, scr_el3 tst x30, #SCR_NS_BIT b.ne unexpected_sync_exception /* ---------------------------------------------- * Handling RUN_IMAGE SMC. First switch back to * SP_EL0 for the C runtime stack. * ---------------------------------------------- */ ldr x30, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP] msr spsel, #MODE_SP_EL0 mov sp, x30 /* --------------------------------------------------------------------- * Pass EL3 control to next BL image. * Here it expects X1 with the address of a entry_point_info_t * structure describing the next BL image entrypoint. * --------------------------------------------------------------------- */ mov x20, x1 mov x0, x20 bl bl1_print_next_bl_ep_info ldp x0, x1, [x20, #ENTRY_POINT_INFO_PC_OFFSET] msr elr_el3, x0 msr spsr_el3, x1 ubfx x0, x1, #MODE_EL_SHIFT, #2 cmp x0, #MODE_EL3 b.ne unexpected_sync_exception bl disable_mmu_icache_el3 tlbi alle3 dsb ish /* ERET implies ISB, so it is not needed here */ mov x0, x20 bl bl1_plat_prepare_exit ldp 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)] exception_return endfunc smc_handler64
- 首先判断是不是BL1_SMC_RUN_IMAGE,若是继续镜像切换,否则认为是普通类型的异常,进入smc_handler
- 接着判断是否是安全世界执行到此,如果不是则认为不合法,产生异常
- 然后恢复el3_exit流程中保存的运行时栈的值,恢复到SP_EL0中
- bl1_print_next_bl_ep_info:打印bl3x参数信息
- 从next_bl_ep_info参数中获取设置ELR_EL3和SPSR_EL3寄存器
- 判断下一镜像是否是EL3,如果不是则报异常
- bl1_plat_prepare_exit:退出BL1前的操作,默认为空操作
- 设置x0-x7跳转参数,这里是next_bl_ep_info的arg0出栈传递给x0,arg1即bl_params指针出栈传递给x1
- 最后通过ERET跳转到下一阶段镜像