【精读Uboot】SPL阶段的board_init_r详细分析

对于i.MX平台上的SPL来说,其不会直接跳转到Uboot,而是在SPL阶段借助BOOTROM跳转到ATF,然后再通过ATF跳转到Uboot。

board_init_f会初始化设备相关的硬件,最后进入board_init_r为镜像跳转做准备。下面是board_init_r调用的核心函数流程,接下来我们会对其中的函数进行详细分析。

c 复制代码
spl_board_init //board/freescale/imx93_evk/spl.c
board_boot_order(spl_boot_list)//spl_boot_device获取SPL启动设备 arch/arm/mach-imx/imx8/cpu.c
spl_boot_device//arch/arm/mach-imx/spl.c
    ->spl_board_boot_device//board/freescale/imx93_evk/spl.c
spl_return_to_bootrom
board_return_to_bootrom//arch/arm/mach-imx/spl_imx_romapi.c

1、spl_board_init

对于i.MX93芯片来说,spl_board_init启动ELE了引擎。这里不对ELE进行详细分析。

c 复制代码
//board/freescale/imx93_evk/spl.c
void spl_board_init(void)
{
	int ret;

	puts("Normal Boot\n");

	ret = ahab_start_rng();
	if (ret)
		printf("Fail to start RNG: %d\n", ret);
}

2、board_boot_order

spl_boot_device根据boot配置获取当前的启动设备。对于使用SCU的芯片需要特殊处理BOOT_DEVICE_SPI类型的设备。

c 复制代码
//arch/arm/mach-imx/imx8/cpu.c,适用于93
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_board_boot_device函数内容可以看出,i.MX8以及i.MX9系列芯片的SPL跳转皆是由BOOTROM辅助实现的,因为这里直接返回了BOOT_DEVICE_BOOTROM

c 复制代码
//arch/arm/mach-imx/spl.c
u32 spl_boot_device(void)
{
	enum boot_device boot_device_spl = get_boot_device();
	return spl_board_boot_device(boot_device_spl);
}

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:
		return BOOT_DEVICE_MMC1;
	case SD2_BOOT:
	case MMC2_BOOT:
		return BOOT_DEVICE_MMC2;
	default:
		return BOOT_DEVICE_NONE;
	}
#endif
}

3、spl_return_to_bootrom

由于上面返回的boot设备是BOOT_DEVICE_BOOTROM,因此这里各家定义的board_return_to_bootrom用于辅助跳转。

通过ROM API查询当前的启动设备和启动阶段,启动阶段可以分为Primary bootSecondary bootRecovery bootUSB boot,打印对应的启动阶段信息,最后使用ROM API将Uboot搬运到DDR的对应位置。

c 复制代码
int board_return_to_bootrom(struct spl_image_info *spl_image,
			    struct spl_boot_device *bootdev)
{
	volatile gd_t *pgd = gd;
	int ret;
	u32 boot, bstage;

	ret = g_rom_api->query_boot_infor(QUERY_BT_DEV, &boot,
					  ((uintptr_t)&boot) ^ QUERY_BT_DEV);
	ret |= g_rom_api->query_boot_infor(QUERY_BT_STAGE, &bstage,
					   ((uintptr_t)&bstage) ^ QUERY_BT_STAGE);
	set_gd(pgd);

	if (ret != ROM_API_OKAY) {
		puts("ROMAPI: failure at query_boot_info\n");
		return -1;
	}

	printf("Boot Stage: ");

	switch (bstage) {
	case BT_STAGE_PRIMARY:
		printf("Primary boot\n");
		break;
	case BT_STAGE_SECONDARY:
		printf("Secondary boot\n");
		break;
	case BT_STAGE_RECOVERY:
		printf("Recovery boot\n");
		break;
	case BT_STAGE_USB:
		printf("USB boot\n");
		break;
	default:
		printf("Unknow (0x%x)\n", bstage);
	}
	//USB下载模式
	if (is_boot_from_stream_device(boot))
		return spl_romapi_load_image_stream(spl_image, bootdev);

	return spl_romapi_load_image_seekable(spl_image, bootdev, boot);
}

4、spl_romapi_load_image_seekable

下面我们将分析ROM API是如何将Uboot搬运到指定位置的。

  1. 通过query_boot_infor查询IVT的偏移量、pagesize和image_offset。

  2. 获取header的位置

    c 复制代码
    header = (struct image_header *)(CONFIG_SPL_IMX_ROMAPI_LOADADDR);//0x48000000 内存地址
  3. 获取Uboot在MMC介质中的偏移量,将其存储在offset(0x41400)中。

    c 复制代码
    offset = spl_romapi_get_uboot_base(image_offset, rom_bt_dev);
    
    ulong spl_romapi_get_uboot_base(u32 image_offset, u32 rom_bt_dev)
    {
    	ulong end;
    
    	image_offset = spl_arch_boot_image_offset(image_offset, rom_bt_dev);
    
    	end = get_imageset_end((void *)(ulong)image_offset, ROM_API_DEV);
    	end = ROUND(end, SZ_1K);
    
    	printf("Load image from 0x%lx by ROM_API\n", end);
    
    	return end;
    }
  4. 使用download_image函数从MMC中的0x41400处下载header信息 到DDR中的0x48000000处,后续需要对header里的信息进行判断(image_get_magic(header) == FDT_MAGIC)。这个header由mkimage_imx8.c写入。

    c 复制代码
    g_rom_api->download_image((u8 *)header, offset, size,
    					((uintptr_t)header) ^ offset ^ size);
  5. 设置其他固件的信息,对于93/8ulp来说调用的是spl_load_imx_container函数,其余芯片为spl_load_simple_fit函数。spl_load_simple_fit这个函数会解析itb文件,获取里面的配置信息,填充spl_image_infospl_load_info中的信息,加载ATF做好跳转之前的准备。

5、spl_load_simple_fit

在进入之前,设置了load.readspl_romapi_read_seekable,然后spl_simple_fit_read会调用传入的read函数和上一节而最后读取到内存的header读取整个fit固件。这个是后续读取固件的核心函数。

c 复制代码
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) && image_get_magic(header) == FDT_MAGIC) {
		struct spl_load_info load;

		memset(&load, 0, sizeof(load));
		load.bl_len = pagesize;
		load.read = spl_romapi_read_seekable;
		load.priv = &pagesize;
		return spl_load_simple_fit(spl_image, &load, offset / pagesize, header);
}

spl_load_simple_fit_fix_load使用ROM API读取itb到内存中,然后解析/configurations节点,其中的default配置名称和images的偏移量。

5.1、解析uboot

解析默认config(config-1)下面的firmware所指向的名称,这里解析出"uboot-1",然后返回出这个uboot节点在itb文件中的偏移量。一个config只有一个firmware,可以是uboot也可以是kernel,其余均为external数据。

spl_load_fit_image根据解析出的加载地址,将u-boot-nodtb.bin放到加载地址处。然后填充spl_image_info中的load_addr等信息。

c 复制代码
#define FIT_FIRMWARE_PROP	"firmware"
node = spl_fit_get_image_node(&ctx, FIT_FIRMWARE_PROP, 0);

ret = spl_load_fit_image(info, sector, &ctx, node, spl_image);

5.2、解析fdt

spl_fit_append_fdt首先也是解析出its中关于设备树的相关信息,然后使用fdt_overlay_apply_verbose->fdt_overlay_apply将its中需要overlay的部分覆盖进原始dtb中。

c 复制代码
if (os_takes_devicetree(spl_image->os)) {
		ret = spl_fit_append_fdt(spl_image, info, sector, &ctx);
}

overlay dtb格式1:

c 复制代码
/dts-v1/;
/plugin/;

 / {
        fragment@0 {
            target-path = "/";
            __overlay__ {
                /*在此添加要插入的节点*/
                .......
            };
        };

        fragment@1 {
            target = <&XXXXX>;
            __overlay__ {
                /*在此添加要插入的节点*/
                .......
            };
        };
    .......
 };

overlay dtb格式2:

c 复制代码
/dts-v1/;
/plugin/;

&{/} {
    /*此处在根节点"/"下,添加要插入的节点或者属性*/
};

&XXXXX {
    /*此处在节点"XXXXX"下,添加要插入的节点或者属性*/
};

5.3、解析loadables

和解析加载uboot类似,先找到its节点中的信息,然后根据这些信息将atf和tee放到指定位置。

c 复制代码
for (; ; index++) {
		uint8_t os_type = IH_OS_INVALID;

		node = spl_fit_get_image_node(&ctx, "loadables", index);

		image_info.load_addr = 0;
		ret = spl_load_fit_image(info, sector, &ctx, node, &image_info);

		/* Record our loadables into the FDT */
		if (spl_image->fdt_addr)
			spl_fit_record_loadable(&ctx, index,
						spl_image->fdt_addr,
						&image_info);
	}

如果firmware属性中未定义entry值,那么将第一个loadables的entry作为跳转入口(spl_image->entry_point)。从its中我们可以知道,第一个loadables就是atf。

6、跳转至ATF

直接跳转进spl_image->entry_point所定义的地址,也就是进入ATF中。

c 复制代码
arch/arm/mach-imx/spl.c
/*
 * +------------+  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:its表

c 复制代码
/dts-v1/;

/ {
	description = "Configuration to load ATF before U-Boot";
	#address-cells = <1>;

	images {
		uboot-1 {
			description = "U-Boot (64-bit)";
			data = /incbin/("u-boot-nodtb.bin");
			type = "standalone";
			arch = "arm64";
			compression = "none";
			load = <0x40200000>;
		};
		fdt-1 {
			description = "evk";
			data = /incbin/("evk.dtb");
			type = "flat_dt";
			compression = "none";
		};
		atf-1 {
			description = "ARM Trusted Firmware";
			data = /incbin/("bl31.bin");
			type = "firmware";
			arch = "arm64";
			compression = "none";
			load = <0x00970000>;
			entry = <0x00970000>;
		};
		tee-1 {
			description = "TEE firmware";
			data = /incbin/("tee.bin");
			type = "firmware";
			arch = "arm64";
			compression = "none";
			load = <0x56000000>;
			entry = <0x56000000>;
		};
	};
	configurations {
		default = "config-1";

		config-1 {
			description = "evk";
			firmware = "uboot-1";
			loadables = "atf-1", "tee-1";
			fdt = "fdt-1";
		};
	};
};

附录2:启动log

i.MX93

shell 复制代码
U-Boot SPL 2022.04-lf_v2022.04+g1734965341 (Jun 30 2023 - 10:23:49 +0000)
SOC: 0xa0009300
LC: 0x40010
M33 prepare ok
>>SPL: board_init_r()
spl_init
Normal Boot
Trying to boot from BOOTROM
Boot Stage: Primary boot
image offset 0x0, pagesize 0x200, ivt offset 0x0 
Load image from 0x41400 by ROM_API 
Unsupported OS image.. Jumping nevertheless..
image entry point: 0x204e0000

i.MX8MP

shell 复制代码
U-Boot SPL 2023.04-lf_v2023.04+gaf7d004eaf (Aug 14 2023 - 03:48:45 +0000)
DDRINFO: start DRAM init
DDRINFO: DRAM rate 4000MTS
DDRINFO:ddrphy calibration done
DDRINFO: ddrmix config done
>>SPL: board_init_r()
spl_init
SEC0:  RNG instantiated
Normal Boot
Trying to boot from BOOTROM
Boot Stage: Primary boot
image offset 0x0, pagesize 0x200, ivt offset 0x0
Jumping to U-Boot...
image entry point: 0x970000
相关推荐
gopher951116 小时前
linux驱动开发-中断子系统
linux·运维·驱动开发
gopher95112 天前
linux驱动开发-设备树
linux·驱动开发
三菱-Liu2 天前
三菱变频器以模拟量电流进行频率设定(电流输入)
驱动开发·单片机·嵌入式硬件·硬件工程·制造
三菱-Liu3 天前
三菱FX5U CPU 内置以太网功能
网络·驱动开发·硬件工程·制造·mr
让开,我要吃人了3 天前
OpenHarmony鸿蒙( Beta5.0)摄像头实践开发详解
驱动开发·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony
OH五星上将3 天前
如何更换OpenHarmony SDK API 10
驱动开发·嵌入式硬件·sdk·harmonyos·openharmony·鸿蒙开发
OH五星上将5 天前
OpenHarmony(鸿蒙南向开发)——标准系统移植指南(二)Linux内核
linux·驱动开发·嵌入式硬件·移动开发·harmonyos·鸿蒙开发·鸿蒙内核
芊言芊语5 天前
蓝牙驱动开发详解
驱动开发
让开,我要吃人了5 天前
OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解
驱动开发·嵌入式硬件·华为·移动开发·harmonyos·鸿蒙·openharmony
OH五星上将6 天前
OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(二)
驱动开发·移动开发·harmonyos·内存管理·openharmony·鸿蒙内核·鸿蒙移植