u-boot SPL启动流程

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
嵌入式Linux的启动流程(带SPL)

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中的_startlowlevel_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.cboard_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
    18
    void 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.cboard_init_f(),这里面做了更多的事情,
    common/board_f.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void board_init_f(ulong boot_flags)
    {
    gd->flags = boot_flags;
    gd->have_console = 0;

    if (initcall_run_list(init_sequence_f))
    hang();

    #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
    !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64)
    /* NOTREACHED - jump_to_copy() does not return */
    hang();
    #endif
    }

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.cboard_init_r()函数。


board_init_r()函数

1、对于u-boot-spl来说,这个函数做了以下几件事:

  • 定义spl_boot_list[],该数组存放从哪个boot device去启动,比如说SPI、nand、emmc等;
  • 定义struct spl_image_info spl_image,用来标记uboot镜像存在哪个位置,启动地址是哪里,详见下面的定义;
include/spl.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct spl_image_info {
const char *name;
u8 os;
uintptr_t load_addr;
uintptr_t entry_point;
#if CONFIG_IS_ENABLED(LOAD_FIT)
void *fdt_addr;
#endif
u32 size;
u32 flags;
void *arg;
#ifdef CONFIG_DUAL_BOOTLOADER
uint64_t rbindex;
#endif
};
  • 如果定义了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.cspl_mmc.cspl_nand.c等。在这些文件中都通过SPL_LOAD_IMAGE_METHOD来定义struct spl_image_info的详细内容,并且实现load_image的实体函数,来将镜像从特定的boot device load到DDR中。

common/spl/spl_mmc.c
1
SPL_LOAD_IMAGE_METHOD("MMC1", 0, BOOT_DEVICE_MMC1, spl_mmc_load_image);

include/spl.h
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
/**
* Holds information about a way of loading an SPL image
*
* @name: User-friendly name for this method (e.g. "MMC")
* @boot_device: Boot device that this loader supports
* @load_image: Function to call to load image
*/
struct spl_image_info {
const char *name;
u8 os;
uintptr_t load_addr;
uintptr_t entry_point;
#if CONFIG_IS_ENABLED(LOAD_FIT)
void *fdt_addr;
#endif
u32 size;
u32 flags;
void *arg;
#ifdef CONFIG_DUAL_BOOTLOADER
uint64_t rbindex;
#endif

/**
* Holds information about a way of loading an SPL image
*
* @name: User-friendly name for this method (e.g. "MMC")
* @boot_device: Boot device that this loader supports
* @load_image: Function to call to load image
*/
struct spl_image_loader {
#ifdef CONFIG_SPL_LIBCOMMON_SUPPORT
const char *name;
#endif
uint boot_device;
/**
* load_image() - Load an SPL image
*
* @spl_image: place to put image information
* @bootdev: describes the boot device to load from
*/
int (*load_image)(struct spl_image_info *spl_image,
struct spl_boot_device *bootdev);
};

/* Declare an SPL image loader */
#define SPL_LOAD_IMAGE(__name) \
ll_entry_declare(struct spl_image_loader, __name, spl_image_loader)

/*
* _priority is the priority of this method, 0 meaning it will be the top
* choice for this device, 9 meaning it is the bottom choice.
* _boot_device is the BOOT_DEVICE_... value
* _method is the load_image function to call
*/
#define SPL_LOAD_IMAGE_METHOD(_name, _priority, _boot_device, _method) \
SPL_LOAD_IMAGE(_method ## _priority ## _boot_device) = { \
.name = _name, \
.boot_device = _boot_device, \
.load_image = _method, \
}
#else
#define SPL_LOAD_IMAGE_METHOD(_name, _priority, _boot_device, _method) \
SPL_LOAD_IMAGE(_method ## _priority ## _boot_device) = { \
.boot_device = _boot_device, \
.load_image = _method, \
}
#endif
  • 调用spl_load_image()函数从对应的boot device将镜像信息存放到spl_image中,
common/spl/spl.c
1
2
3
4
5
6
7
8
9
10
11
static int spl_load_image(struct spl_image_info *spl_image,
struct spl_image_loader *loader)
{
struct spl_boot_device bootdev;
int ret;
bootdev.boot_device = loader->boot_device;
bootdev.boot_device_name = NULL;

ret = loader->load_image(spl_image, &bootdev);
return ret;
}
  • 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。


参考资料

Title:u-boot SPL启动流程

Author:Victor Huang

Time:2019-07-21 / 21:07

Link:http://wowothink.com/1e031f74/

License: Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)