对于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 boot
,Secondary boot
,Recovery boot
和USB 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搬运到指定位置的。
-
通过
query_boot_infor
查询IVT的偏移量、pagesize和image_offset。 -
获取header的位置
cheader = (struct image_header *)(CONFIG_SPL_IMX_ROMAPI_LOADADDR);//0x48000000 内存地址
-
获取Uboot在MMC介质中的偏移量,将其存储在offset(0x41400)中。
coffset = 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; }
-
使用
download_image
函数从MMC中的0x41400
处下载header信息 到DDR中的0x48000000
处,后续需要对header里的信息进行判断(image_get_magic(header) == FDT_MAGIC
)。这个header由mkimage_imx8.c
写入。cg_rom_api->download_image((u8 *)header, offset, size, ((uintptr_t)header) ^ offset ^ size);
-
设置其他固件的信息,对于93/8ulp来说调用的是
spl_load_imx_container
函数,其余芯片为spl_load_simple_fit
函数。spl_load_simple_fit
这个函数会解析itb文件,获取里面的配置信息,填充spl_image_info
和spl_load_info
中的信息,加载ATF做好跳转之前的准备。
5、spl_load_simple_fit
在进入之前,设置了load.read
为spl_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