赞
踩
NXP i.MX8M secure boot流程
Uboot链接脚本分析述
Uboot启动分析–start.S启动分析(1)
Uboot启动分析–start.S启动分析(2)
Uboot启动分析–start.S启动分析(3)
Uboot启动分析–__main分析(1)
Uboot启动分析–__main分析(2)
Uboot启动分析–启动kernel
Uboot分析–SPL跳转过程分析
Uboot中lpddr4的初始化(i.MX8M)
使用U_BOOT_CMD()自定义uboot命令
上电后,片上的ROM code会将启动的bin文件拷贝到sram中运行,sram通常只有4kb
甚至更小。所以将uboot中放到sram中运行是不现实的。SPL应运而生,SPL(Secondary Program Loader)是一个非常小的bin文件,足以加载到sram中运行。SPL在sram运行起来以后会将uboot加载到外部ram中运行。
board/freescale/imx8mp_evk/spl.c
void board_init_f(ulong dummy) { struct udevice *dev; int ret; /* Clear the BSS. */ memset(__bss_start, 0, __bss_end - __bss_start); arch_cpu_init(); board_early_init_f(); timer_init(); preloader_console_init(); ret = spl_early_init(); if (ret) { debug("spl_early_init() failed: %d\n", ret); hang(); } ret = uclass_get_device_by_name(UCLASS_CLK, "clock-controller@30380000", &dev); if (ret < 0) { printf("Failed to find clock node. Check device tree\n"); hang(); } enable_tzc380(); power_init_board(); /* DDR initialization */ spl_dram_init(); //非常重要 board_init_r(NULL, 0); }
spl需要初始化定时器、atf控制器、pmic、ddr。spl_dram_init会将ddr的timming信息写入ddr控制器。最后调用spl的board_init_r函数,加载BL31验证atf固件,从emmc/sd中加载BL32(uboot),最后移交控制权给uboot。
common/spl/spl.c
void board_init_r(gd_t *dummy1, ulong dummy2) { u32 spl_boot_list[] = { BOOT_DEVICE_NONE, BOOT_DEVICE_NONE, BOOT_DEVICE_NONE, BOOT_DEVICE_NONE, BOOT_DEVICE_NONE, }; struct spl_image_info spl_image; int ret; debug(">>" SPL_TPL_PROMPT "board_init_r()\n"); //gd->bd= &bdata; //board data来源于链接脚本中的.data段 spl_set_bd(); #if defined(CONFIG_SYS_SPL_MALLOC_START) mem_malloc_init(CONFIG_SYS_SPL_MALLOC_START, CONFIG_SYS_SPL_MALLOC_SIZE); gd->flags |= GD_FLG_FULL_MALLOC_INIT; #endif if (!(gd->flags & GD_FLG_SPL_INIT)) { //设置malloc系统 if (spl_init()) hang(); } //board/freescale/imx8mp_evk/spl.c定义 //初始化i2c、clk spl_board_init(); initr_watchdog(); if (IS_ENABLED(CONFIG_SPL_OS_BOOT) || CONFIG_IS_ENABLED(HANDOFF) || IS_ENABLED(CONFIG_SPL_ATF)) //ddr初始化扇区 //arch/arm/mach-imx/imx8m/soc.c定义 //这个函数很值得分析,其中分了两块ddr区域 //其中的sdram2 在atf中的初始化过程很重要,涉及到改ddr大小的方法。 //其中有和optee区域划分相关的代码 dram_init_banksize(); bootcount_inc(); memset(&spl_image, '\0', sizeof(spl_image)); #ifdef CONFIG_SYS_SPL_ARGS_ADDR spl_image.arg = (void *)CONFIG_SYS_SPL_ARGS_ADDR; #endif spl_image.boot_device = BOOT_DEVICE_NONE; board_boot_order(spl_boot_list); //从存储设备中读取uboot到内存 ret = boot_from_devices(&spl_image, spl_boot_list, ARRAY_SIZE(spl_boot_list)); if (ret) { if (CONFIG_IS_ENABLED(SHOW_ERRORS) && CONFIG_IS_ENABLED(LIBCOMMON_SUPPORT)) printf(SPL_TPL_PROMPT "failed to boot from all boot devices (err=%d)\n", ret); else puts(SPL_TPL_PROMPT "failed to boot from all boot devices\n"); hang(); } spl_perform_fixups(&spl_image); switch (spl_image.os) { case IH_OS_ARM_TRUSTED_FIRMWARE: //atf验证跳转 debug("Jumping to U-Boot via ARM Trusted Firmware\n"); spl_fixup_fdt(spl_image.fdt_addr); spl_invoke_atf(&spl_image); break; case IH_OS_TEE: debug("Jumping to U-Boot via OP-TEE\n"); //optee跳转 spl_board_prepare_for_optee(spl_image.fdt_addr); spl_optee_entry(NULL, NULL, spl_image.fdt_addr, (void *)spl_image.entry_point); break; debug("SPL malloc() used 0x%lx bytes (%ld KB)\n", gd->malloc_ptr, gd->malloc_ptr / 1024); bootstage_mark_name(get_bootstage_id(false), "end phase"); ret = bootstage_stash((void *)CONFIG_BOOTSTAGE_STASH_ADDR, CONFIG_BOOTSTAGE_STASH_SIZE); if (ret) debug("Failed to stash bootstage: err=%d\n", ret); spl_board_prepare_for_boot(); jump_to_image_no_args(&spl_image); }
board_boot_order被定义为一个弱函数,可以被厂商覆盖,因此我们这里会使用arch/arm/mach-imx/imx8/cpu.c中定义的board_boot_order函数。
void board_boot_order(u32 *spl_boot_list)
{
spl_boot_list[0] = spl_boot_device();
if (spl_boot_list[0] == BOOT_DEVICE_SPI) {
/* Check whether we own the flexspi0, if not, use NOR boot */
if (!sc_rm_is_resource_owned(-1, SC_R_FSPI_0))
spl_boot_list[0] = BOOT_DEVICE_NOR;
}
}
而spl_boot_device也是一个弱函数,被arch/arm/mach-imx/spl.c中的spl_boot_device所覆盖。
u32 spl_boot_device(void)
{
enum boot_device boot_device_spl = get_boot_device();
return spl_board_boot_device(boot_device_spl);
}
get_boot_device定义在arch/arm/mach-imx/imx8m/soc.c
enum boot_device get_boot_device(void) { volatile gd_t *pgd = gd; int ret; u32 boot; u16 boot_type; u8 boot_instance; enum boot_device boot_dev = SD1_BOOT; //调用rom地址对应的指针函数查询启动设备信息 //struct rom_api *g_rom_api = (struct rom_api *)0x980 ret = g_rom_api->query_boot_infor(QUERY_BT_DEV, &boot, ((uintptr_t)&boot) ^ QUERY_BT_DEV); gd = pgd; if (ret != ROM_API_OKAY) { puts("ROMAPI: failure at query_boot_info\n"); return -1; } boot_type = boot >> 16; boot_instance = (boot >> 8) & 0xff; switch (boot_type) { case BT_DEV_TYPE_SD: boot_dev = boot_instance + SD1_BOOT; break; case BT_DEV_TYPE_MMC: boot_dev = boot_instance + MMC1_BOOT; break; case BT_DEV_TYPE_NAND: boot_dev = NAND_BOOT; break; case BT_DEV_TYPE_FLEXSPINOR: boot_dev = QSPI_BOOT; break; case BT_DEV_TYPE_USB: boot_dev = USB_BOOT; break; default: break; } return boot_dev; }
rom code会读取拨码信息,来判断当前的启动状态(emmc/sd/qspi)。这里以我们拨码设置emmc启动为例,rom code会识别出当前是emmc启动,于是boot_dev 就为boot_instance + MMC1_BOOT。至于为什么要加上boot_instance,rom code不开放。。我也不知道哈哈哈。
从硬件设计可知,MMC2_BOOT对应了sd卡,返回BOOT_DEVICE_MMC1,MMC3_BOOT对应emmc,返回BOOT_DEVICE_MMC2,这就是为什么emmc启动的时候会打印boot from mmc2。
int spl_board_boot_device(enum boot_device boot_dev_spl) { #ifdef CONFIG_SPL_BOOTROM_SUPPORT return BOOT_DEVICE_BOOTROM; #else switch (boot_dev_spl) { case SD1_BOOT: case MMC1_BOOT: case SD2_BOOT: case MMC2_BOOT: return BOOT_DEVICE_MMC1; case SD3_BOOT: case MMC3_BOOT: return BOOT_DEVICE_MMC2; case QSPI_BOOT: return BOOT_DEVICE_NOR; case NAND_BOOT: return BOOT_DEVICE_NAND; case USB_BOOT: return BOOT_DEVICE_BOARD; default: return BOOT_DEVICE_NONE; } #endif }
读取到的uboot信息放在spl_image中,spl_image中存储了bootdev为emmc,spl_load_image会从emmc中加载uboot image到内存。
case IH_OS_ARM_TRUSTED_FIRMWARE:
//atf验证跳转
debug("Jumping to U-Boot via ARM Trusted Firmware\n");
//修正spl_image中的fdt信息
spl_fixup_fdt(spl_image.fdt_addr);
spl_invoke_atf(&spl_image);
break;
atf是BL31,optee是BL32,uboot是BL33。bl31_entry进入了atf空间,传入了uboot的入口函数地址(bl33_entry)和uboot image地址(spl_image->entry_point)。
void spl_invoke_atf(struct spl_image_info *spl_image) { uintptr_t bl32_entry = 0; uintptr_t bl33_entry = CONFIG_SYS_TEXT_BASE; void *blob = spl_image->fdt_addr; uintptr_t platform_param = (uintptr_t)blob; int node; //查找OP-TEE二进制(in/fit图像)加载地址或入口点(如果不同),并将其作为BL3-2入口点传递,这是可选的。 node = spl_fit_images_find(blob, IH_OS_TEE); if (node >= 0) bl32_entry = spl_fit_images_get_entry(blob, node); //找到U-Boot二进制文件(in/fit映像)加载addrees或入口点(如果不同),并将其作为BL3-3入口点传递。这需要扩展以支持Falcon模式。 node = spl_fit_images_find(blob, IH_OS_U_BOOT); if (node >= 0) bl33_entry = spl_fit_images_get_entry(blob, node); //我们还没有提供BL3-2入口,但是使用类似的逻辑,这是可能的。 //将optee入口,uboot入口参数传给atf,然后跳转到atf中, bl31_entry(spl_image->entry_point, bl32_entry, bl33_entry, platform_param); }
如果没有atf控制器,则会直接跳转到内核地址。一样通过armv8_switch_to_el2函数进入内核栈。这里值得注意的是SPL可以直接进入内核栈。
void __noreturn jump_to_image_linux(struct spl_image_info *spl_image)
{
debug("Entering kernel arg pointer: 0x%p\n", spl_image->arg);
cleanup_before_linux();
armv8_switch_to_el2((u64)spl_image->arg, 0, 0, 0,
spl_image->entry_point, ES_TO_AARCH64);
}
case IH_OS_TEE:
debug("Jumping to U-Boot via OP-TEE\n");
//optee跳转
spl_board_prepare_for_optee(spl_image.fdt_addr);
spl_optee_entry(NULL, NULL, spl_image.fdt_addr,
(void *)spl_image.entry_point);
break;
如果是optee os,那么spl_optee_entry会直接将pc指针移动到spl_image.entry_point地址。
ENTRY(spl_optee_entry)
ldr lr, =CONFIG_SYS_TEXT_BASE
mov pc, r3
ENDPROC(spl_optee_entry)
spl_board_prepare_for_boot();//空函数,8mp未定义,8qm定义了。
jump_to_image_no_args(&spl_image);
/* * +------------+ 0x0 (DDR_UIMAGE_START) - * | Header | | * +------------+ 0x40 | * | | | * | | | * | | | * | | | * | Image Data | | * . | | * . | > Stuff to be authenticated ----+ * . | | | * | | | | * | | | | * +------------+ | | * | | | | * | Fill Data | | | * | | | | * +------------+ Align to ALIGN_SIZE | | * | IVT | | | * +------------+ + IVT_SIZE - | * | | | * | CSF DATA | <---------------------------------------------------------+ * | | * +------------+ * | | * | Fill Data | * | | * +------------+ + CSF_PAD_SIZE */ __weak void __noreturn jump_to_image_no_args(struct spl_image_info *spl_image) { typedef void __noreturn (*image_entry_noargs_t)(void); uint32_t offset; image_entry_noargs_t image_entry = (image_entry_noargs_t)(unsigned long)spl_image->entry_point; debug("image entry point: 0x%lX\n", spl_image->entry_point); if (spl_image->flags & SPL_FIT_FOUND) { image_entry(); } else { /* * HAB looks for the CSF at the end of the authenticated * data therefore, we need to subtract the size of the * CSF from the actual filesize */ offset = spl_image->size - CONFIG_CSF_SIZE; if (!imx_hab_authenticate_image(spl_image->load_addr, offset + IVT_SIZE + CSF_PAD_SIZE, offset)) { image_entry(); } else { panic("spl: ERROR: image authentication fail\n"); } } }
这里会通过imx_hab_authenticate_image进行CFS验证,成功后image_entry;如果是FIT image,那么就会直接跳转到image_entry,不进行验证。image_entry这个函数是uboot的入口函数!!
总结,SPL初始化串口,i2c,pmic,ddr后,开始从存储设备(emmc)加载uboot到内存中,判断是atf还是optee os。如果是atf,就会跳转到atf进行固件验证,验证成功后调用i.mx定义的jump_to_image_no_args,还需要做进一步的hab验证,验证成功后从uboot入口函数进入uboot,然后uboot再启动内核。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。