SPL简介
1、SPL是Secondary Program Loader
,也就是第二阶段引导启动程序。
2、SPL是一套小的boot代码,主要负责初始化外部的DRAM和flash,然后引导启动u-boot。
3、SPL是在u-boot代码中,与u-boot共用一套代码,通过CONFIG_SPL_XXX
来区分。
u-boot可以引导kernel,那么谁来引导u-boot呢?可以使用Rom code(Boot rom)
引导u-boot,也可以使用SPL
引导u-boot。两者之间有啥差别呢?
1、如果Rom code
直接引导u-boot,那么Rom code
必须能够初始化外部DRAM又或者内部的SRAM足够大能够启动u-boot;
2、如果做不到,那么只能通过SPL来过渡。Rom code
从外设flash上固定的地址读取SPL到内部的SRAM上运行,SPL去初始化外部的DRAM,然后将u-boot从flash上拷贝到外部的DRAM并启动u-boot。下面讲讲u-boot SPL启动流程
启动流程介绍
1、嵌入式Linux的启动流程(带SPL)如下:
Rom code –> SPL –> u-boot –> Linux kernel –> file system –> application
2、PC的启动流程为:
BIOS -> MBR -> GRUB -> kernel
u-boot-spl流程
基本环境如下:
- u-boot版本:2018.03
- 开发板:imx8qxp mek
- u-boot配置:打开SPL
- u-boot:表示不带SPL的u-boot
- u-boot-spl:表示已配置上SPL的u-boot
u-boot-spl入口
u-boot-spl的入口同u-boot一样,也都是始于arch/arm/cpu/armv8/start.S
中的_start
→ lowlevel_init
→ _main
的调用顺序。也就是说,这个过程,spl会去执行一遍,不带spl的u-boot也会去执行一遍。中间通过CONFIG_SPL_XXX
的宏进行区分。
_main
简要说明
根据_main
的简要说明,里面会根据SPL做差分,走不同的流程。
1、对于u-boot-spl:
_main
中会设置堆栈地址CONFIG_SPL_STACK
,根据memory map,该地址属于OCRAM
。这里就可以证明SPL是运行在OCRAM
中的。_main
中调用board/freescale/imx8qxp_mek/spl.c
的board_init_f()
;board/freescale/imx8qxp_mek/spl.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void board_init_f(ulong dummy)
{
/* Clear global data */
memset((void *)gd, 0, sizeof(gd_t));
arch_cpu_init();
board_early_init_f();
timer_init();
preloader_console_init();
/* Clear the BSS. */
memset(__bss_start, 0, __bss_end - __bss_start);
board_init_r(NULL, 0);
}
2、对于u-boot来说:
_main
中会设置堆栈地址CONFIG_SYS_INIT_SP_ADDR
,根据memory map,该地址属于外部DRAM地址。这里就可以证明u-boot是运行在外部DDR上;_main
调用common/board_f.c
的board_init_f()
,这里面做了更多的事情,common/board_f.c 1
2
3
4
5
6
7
8
9
10
11
12
13
14void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64)
/* NOTREACHED - jump_to_copy() does not return */
hang();
}
board_init_f()
函数
1、对于u-boot-spl来说,在该函数中做了cpu、serial、timer、console的初始化,最终调用common/spl/spl.c
中的board_init_r()
函数。
2、对于u-boot来说,u-boot将需要在board_init_f
中初始化的内容,抽象为一系列API,使用init_sequence_f[]
数组定义。这些API由u-boot声明,由平台的开发者根据实际情况实现。在_main
的最后,会调用common/board_r.c
的board_init_r()
函数。
board_init_r()
函数
1、对于u-boot-spl来说,这个函数做了以下几件事:
- 定义
spl_boot_list[]
,该数组存放从哪个boot device去启动,比如说SPI、nand、emmc等; - 定义
struct spl_image_info spl_image
,用来标记uboot镜像存在哪个位置,启动地址是哪里,详见下面的定义;
1 | struct spl_image_info { |
- 如果定义了
CONFIG_SPL_OS_BOOT
宏,那么就去调用dram_init_banksize()
函数做DRAM的初始化。这个宏表示直接跳过u-boot去启动kernel; - 调用
mem_malloc_init()
为malloc分配空间,从CONFIG_SYS_SPL_MALLOC_START
开始,大小为CONFIG_SYS_SPL_MALLOC_SIZE
; - 调用
spl_init()
; - 调用
spl_board_init()
,该函数在board/freescale/imx8qxp_mek/spl.c
中定义,主要做了spl_dram_init()
的DDR初始化; - 调用
board_boot_order()
去获取启动设备; - 调用
boot_from_devices()
根据得到启动设备,从对应的设备中加载镜像到DDR中,使用spl_load_image()
进行load;
在common/spl/
目录下定义了好几个启动设备,包括spl_fat.c
、spl_mmc.c
、spl_nand.c
等。在这些文件中都通过SPL_LOAD_IMAGE_METHOD
来定义struct spl_image_info
的详细内容,并且实现load_image
的实体函数,来将镜像从特定的boot device load到DDR中。
1 | SPL_LOAD_IMAGE_METHOD("MMC1", 0, BOOT_DEVICE_MMC1, spl_mmc_load_image); |
1 | /** |
- 调用
spl_load_image()
函数从对应的boot device将镜像信息存放到spl_image
中,
1 | static int spl_load_image(struct spl_image_info *spl_image, |
- load完镜像后,默认会去调用
spl_board_prepare_for_boot()
和jump_to_image_no_args()
去jumping to U-Boot
。至此,u-boot-spl的流程就走完了,接下来就是走u-boot的流程。
imx8x的做法
根据[i.MX_Linux_User’s_Guide.pdf]的说明,
On i.MX 8M Quad and 8M Mini, the second program loader (SPL) is enabled in U-Boot. SPL is implemented as the firstlevel bootloader running on TCML (due to OCRAM size limitation). It is used to initialize DDR and load U-Boot, U-Boot DTB, Arm trusted firmware, and TEE OS (optional) from the boot device into the memory. After SPL completes loading the images, it jumps to the Arm trusted firmware BL31 directly. The BL31 starts the optional BL32 (TEE OS) and BL33 (u-boot) for continue booting kernel.
在i.MX8MQ 和 i.MX8MM,在u-boot中是使能SPL。SPL跑在TCML(Tightly-Coupled Memory)
内存上,SPL用于初始化DDR并从flash上加载u-boot、u-boot dtb、ATF、TEE到DDR上。SPL加载镜像完毕之后,直接跳转到ATF BL31阶段,BL31再去启动BL32(TEE)和BL33(u-boot),u-boot再去启动kernel。
注:在i.MX8QXP的CPU上,它的Rom code
在上电的时候会根据boot mode从对应的flash上特定的位置读取到flash.bin,将打包到其中的dcd cfg
的DDR配置读取出来然后初始化外部的DRAM。