当前位置:   article > 正文

Uboot分析--SPL跳转过程分析_uboot optee

uboot optee

总目录

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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

spl需要初始化定时器、atf控制器、pmic、ddr。spl_dram_init会将ddr的timming信息写入ddr控制器。最后调用spl的board_init_r函数,加载BL31验证atf固件,从emmc/sd中加载BL32(uboot),最后移交控制权给uboot。

board_init_r

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

1、board_boot_order–读取支持的启动设备

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;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

而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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2、boot_from_devices–读取uboot镜像

读取到的uboot信息放在spl_image中,spl_image中存储了bootdev为emmc,spl_load_image会从emmc中加载uboot image到内存。

3、ATF跳转

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

如果没有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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4、optee跳转入口

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果是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)
  • 1
  • 2
  • 3
  • 4

5、最终跳转到uboot启动

spl_board_prepare_for_boot();//空函数,8mp未定义,8qm定义了。
jump_to_image_no_args(&spl_image);
  • 1
  • 2
/*
 * +------------+  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");
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

这里会通过imx_hab_authenticate_image进行CFS验证,成功后image_entry;如果是FIT image,那么就会直接跳转到image_entry,不进行验证。image_entry这个函数是uboot的入口函数!!

6、总结

总结,SPL初始化串口,i2c,pmic,ddr后,开始从存储设备(emmc)加载uboot到内存中,判断是atf还是optee os。如果是atf,就会跳转到atf进行固件验证,验证成功后调用i.mx定义的jump_to_image_no_args,还需要做进一步的hab验证,验证成功后从uboot入口函数进入uboot,然后uboot再启动内核。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/130031
推荐阅读
相关标签
  

闽ICP备14008679号