代码导读
关于平台相关的代码和函数均以qemu实现解读。
BL1
BL1是启动的第一阶段,复位的入口函数为bl1_entrypoint
,这可以从bl1.ld.S
链接脚本中ENTRY标号定义看出,即ENTRY(bl1_entrypoint)
。其主要执行BL1初始化,平台初始化,加载下一阶段镜像,以及跳转到下一阶段执行,大致的函数执行流程如下。
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()
。
-
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空间
-
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();
}
-
打印欢迎词,版本信息和编译时间。
-
bl1_arch_setup:执行EL3等级的通用架构初始化,实际调用
write_scr_el3(read_scr_el3() | SCR_RW_BIT);
将下一异常等级设置为AArch64。 -
crypto_mod_init:初始化安全启动需要密码库,这里使用的mbedtls。
-
auth_mod_init:认证模块初始化,这里调用
img_parser_init
初始化镜像解析模块,镜像完整性检查是通过X509v3证书来实现,其会存储镜像哈希和公钥。 -
bl1_plat_mboot_init:measured boot初始化,其是对各个软件组件的度量和验证。
-
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; }
-
bl1_plat_get_next_image_id:获取下一阶段的镜像ID,这里ID是BL2_IMAGE_ID。
-
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; }
-
-
bl1_plat_mboot_finish:卸载mesured boot驱动
-
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的上下文,进行特权级的上下文切换
- 最后标记镜像进入执行状态
-
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的入口地址