LV260-SPL简介
u-boot 里面,有一个 spl - common/spl 目录。我们在看 u-boot 代码的时候,很多地方都可以看到和它相关的东西,它是什么?我们来简单了解下。
一、SPL 名字怎么来的?
SPL 全称叫做:Secondary Program Loader,就是二级加载。它 是 uboot 第一阶段执行的代码。主要负责 搬移 uboot 第二阶段的代码到系统内存(System Ram,也叫片外内存)中运行。
我们知道 u-boot 是用来引导启动我们的嵌入式系统的,那为什么不直接使用 u-boot ?为何还会多出一个 u-boot spl 呢?那我们从系统启动开始说起吧。
站在芯片厂商的角度来说,硬件系统一上电,一定是要去某个地址取指令(一般是 0x00000000),然后软件便开始很欢快的运行起来了;通常来讲,SoC 厂家都会做一个 ROM 在 SoC 内部,这个 ROM 很小(主要还是因为成本),里面固化了上电启动的代码(一经固化,永不能改,是芯片生产的时候,做进去的);这部分代码呢,我们管它叫做 BootROM(或者随便叫也行)。
换句话来说,上电后,先接管系统的是 SoC 厂家的 BootROM,它要做些什么事情呢?初始化系统,CPU 的配置,关闭看门狗,初始化时钟,初始化一些外设(比如 USB Controller、MMC Controller,Nand Controller 等);我们管这个 BootROM 叫做一级启动程序,而排在后面的就叫二级启动,这就是 SPL 名字的由来。
二、为什么要有 SPL?
1. 需要先知道的事?
如果是大芯片(不是单片机),外挂了存储设备(eMMC、Nand、SDCard 等)和内存 RAM(SDRAM、DDR 等),通常情况下呢,我们要让系统跑起来,需要先烧写代码,这个烧写代码,其实是将可执行的二进制文件写到外部的存储设备上(eMMC、Nand、SD Card 等);系统上电启动的时候呢,去把他们读到内存中去执行。
前面我们说了,上电后,其实 SoC 厂家自己的 BootROM,其他可执行的程序(u-boot、Kernel)都放(烧写)到了外部存储器。那么 BootROM 的代码除了去初始化硬件环境以外,还需要去外部存储器上面,将接下来可执行的程序读到内存来执行。
既然是读到内存执行,那么这个内存可以不可以是我们板载的 DDR 呢?理论上是可以的,但是,SoC 厂家设计的 DDR 控制器呢,一般会支持很多种类型的 DDR 设备,并且会提供兼容性列表,SoC 厂家怎么可能知道用户 PCB 上到底用了哪种内存呢?所以,直接把外部可执行程序读到 DDR 显然是不太友好的,一般来说呢,SoC 都会做一个内部的小容量的 SRAM (又是成本),BootROM 将外部的可执行程序从存储器中读出来,放到 SRAM 去执行。
好了,现在我们引出了 BootROM 和内部 SRAM;那么 BootROM 从具体哪个存储器读出二进制文件呢?SoC 厂家一般会支持多种启动方式,比如从 eMMC 读取,从 SDCard 读取,从 Nand Flash 读取等等;上电的时候,需要告诉它,它需要从什么样的外设来读取后面的启动二进制文件。
一般的设计思路是,做一组 Bootstrap Pin,上电的时候呢?BootROM 去采集这几个 IO 的电平,来确认要从什么样的外部存储器来加载后续的可执行文件。比如呢,2 个 IO,2'b00 表示从 Nand 启动,2'b01 表示从 eMMC 启动,2'b10 表示从 SDCard 启动等等。
BootROM 读到这些值后,就会去初始化对应的外设,然后来读取后面要执行的代码;这些 IO 一般来说,会做成板载的拨码开关,用于调整芯片的启动方式。
这里,多说一句,读取烧写的二进制的时候呢,需要注意一些细节,比如,SoC 厂家告诉我们,我们需要先把 SDCard 初始化称为某种文件系统,然后把东西放进去才有效之类的;因为文件系统是组织文件的方式,并不是裸分区;我们按照 A 文件系统的方式放进去,然后 SoC 的 BootROM 也按照 A 文件系统的方式读出来,才能够达成一致。
2. 进入正题——SPL?
前面说了,BootROM 会根据 Bootstrap Pin 去确定从某个存储器来读可执行的二进制文件到 SRAM 并执行;理论上来说,这个二进制文件就可以是我们的 u-boot.bin 文件了;也就是 BootROM 直接加载 u-boot.bin。
理论上是这样的,但是这里有一个问题,就是 SRAM 很贵,一般来说,SoC 的片上 SRAM 都不会太大,一般 4KB、8KB、16KB...256KB 不等;但是呢,u-boot 编译出来,却很大,好几百 KB,放不下的。放不下怎么办?有两种办法:
1、放不下就放不下呗,BootROM 加载多少算多少;
2、做一个小一点的 boot 程序,先让 BootROM 加载这个小的程序,后面再由这个小 boot 去加载 u-boot;
比如,我们的 u-boot 有 300KB,SRAM 有 8KB,外部 DDR 1GB:
- 如果使用第一种方案的话,u-boot 的前面 8K 被加载进入 SRAM 执行,u-boot 被截断,我们就需要保证在 u-boot 的前 8KB 代码,把板载的 DDR 初始化好,把整个 u-boot 拷贝到 DDR,然后跳转到 DDR 执行;
- 第二种方案的话,我们做一个小的 u-boot ,这个 u-boot 就叫做 spl,它很小很小(小于 SRAM 大小),它先被 BootROM 加载到 SRAM 运行,那么这个 spl 要做什么事情呢?最主要的就是要初始化 DDR Controller,然后将真正的大 u-boot 从外部存储器读取到 DDR 中,然后跳转到大 u-boot。
如下图所示:

先假设,我们的代码,都已经放置到了外部存储器上,也就是绿色部分,然后会进行以下步骤:
- SoC 厂家要做的事情:
(1)上电后,BootROM 开始执行,初始化时钟,关闭看门狗,关 Cache,关中断等等,根据 Bootstrap Pin 来确定启动设备,初始化外设;
(2)使用外设驱动,从存储器读取 SPL;
- 用户要做的事情:
(3)SPL 被读到 SRAM 执行,此刻,控制权以及移交到我们的 SPL 了;
(4)SPL 初始化外部 DDR;
(5)SPL 使用驱动从外部存储器读取 u-boot 并放到 DDR;
(6)跳转到 DDR 中的 u-boot 执行;
(7)加载内核。
但是在实际情况中,还需注意很多问题:
(1)编译阶段的链接地址,是否需要地址无关?
(2)SPL 的代码和 u-boot 的代码是否有重合的地方?如果有,是否意味着 SPL 执行过的,跳转到 u-boot 又要在执行一次?
(3)具体情况下,需要配置哪些硬件?怎么配置?
三、SPL 编译
SPL 复用的是 uboot 里面的代码。我们可以通过一些配置选项来编译出 SPL 对应的 bin 文件。在学习 imx6ull 的时候,其实并没有怎么关注 SPL,而且 NXP 官方的源码中 mx6ull_14x14_evk_defconfig 配置文件中也没有 SPL 相关配置,menuconfig 中也找不到对应的配置,也就是说,imx6ull。但是有一个配置文件为 mx6slevk_spl_defconfig ,通过这个配置文件,我们可以编译出 SPL,并且 menuconfig 中有对应的 SPL 选项。
1. 配置 uboot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6slevk_spl_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig![]() | ![]() |
可以发现在使用 mx6slevk_spl_defconfig 配置文件的时候 SPL 对应的选项可以进行配置。
2. 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16编译信息中会有含有 spl 关键词的成果物生成:
在编译结束后会发现,源码目录下多了一个 spl 目录:

对应的 spl 成果物就在这个目录下。
3. i.mx6ull 的不使用 SPL?
去搜索 NXP 官方的 evk 评估板对应的 mx6ull_14x14_evk_defconfig 配置文件就会发现,它并没任何关于 SPL 的配置,它用的是 i.mx6ull 这款芯片,内部 SRAM 是 128KB,但是我们通过这个默认配置文件编译出来的 u-boot.bin 都 600 多 MB,没有 SPL 的情况下,怎么运行的 uboot?
这个我们要翻一下参考手册《i.MX 6ULL Applications Processor Reference Manual》Chapter 8 System Boot 的 8.1 Overview 部分,有一段是这样写的:
The Device Configuration Data (DCD) feature allows the boot ROM code to obtain the SOC configuration data from an external program image residing on the boot device. As an example, the DCD can be used to program the DDR controller for optimal settings, improving the boot performance. The DCD is restricted to the memory areas and peripheral addresses that are considered essential for the boot purposes (see Write data command).翻译一下就是:
设备配置数据(DCD)特性允许引导 ROM 代码从驻留在引导设备上的外部程序映像获取 SOC 配置数据。例如,DCD 可用于对 DDR 控制器进行编程以获得最佳设置,从而提高启动性能。DCD 仅限于内存区域和外设地址,这些被认为是引导目的所必需的(请参阅写数据命令)。DCD 是包含在程序映像(ROM 外部)中的配置信息,ROM 对其进行解释以配置各种片上外设。
NXP 提供的 uboot 经过编译最终烧写进存储介质中的是 uboot.imx 文件,这个 imx 后缀的文件不同于传统的比如 S3C2440 最终烧写的 uboot.bin 文件。imx 文件是在 bin 文件的基础上加上了一个头部,IMX6ULL 芯片内部自带的 BOOTROM 程序会根据 BOOT_MODE 对应 GPIO 的高低电平选择对应的启动介质,从中读取这个头部信息,然后对头部信息进行解析,头部中最重要的是一个叫 DCD 表的东西。DCD 表中包含了时钟寄存器的地址和寄存器的值,引脚复用寄存器地址和寄存器的值,DDR 控制器的寄存器地址和寄存器的值。imx6ull 内部的 BOOTROM 程序会根据 DCD 表的内容打开时钟,初始化外部 DDR。因此 NXP 提供的 uboot 代码的汇编阶段没有初始化时钟和初始化 DDR 的相关汇编代码!这也是 NXP 的 uboot 和传统的三星提供的 uboot 的重大区别。
imx6ull 的 BOOTROM 程序会根据解析出来的链接起始地址在一开始就把整个 uboot 源码读取到 DDR 中去,也就是说 Uboot 的第一行代码就运行在 DDR 中,这是不同于三星的传统 Uboot 的,传统 Uboot 的第一句代码是运行在片内 SRAM 上的。
Tips:由于上述的区别,uboot 的重定位过程也就不同了,imx6ull 的重定位过程是把 uboot 整体从 DDR 的起始地址给挪到 DDR 的后端地址上去,给 Linux 内核腾位置。而三星 Uboot 中重定位是从 Flash 中把 Uboot 加载到 DDR 中去,这也算是一个不同之处。
四、SPL 框架?
其实在 u-boot 的文档中,给我们提供了一个通用的框架说明,我们打开 u-boot 源码目录中的 README.SPL - doc/README.SPL 文件:
Generic SPL framework
=====================
Overview
--------
To unify all existing implementations for a secondary program loader (SPL)
and to allow simply adding of new implementations this generic SPL framework
has been created. With this framework almost all source files for a board
can be reused. No code duplication or symlinking is necessary anymore.
How it works
------------
The object files for SPL are built separately and placed in the "spl" directory.
The final binaries which are generated are u-boot-spl, u-boot-spl.bin and
u-boot-spl.map.
A config option named CONFIG_SPL_BUILD is enabled by Kconfig for SPL.
Source files can therefore be compiled for SPL with different settings.
For example:
ifeq ($(CONFIG_SPL_BUILD),y)
obj-y += board_spl.o
else
obj-y += board.o
endif
obj-$(CONFIG_SPL_BUILD) += foo.o
#ifdef CONFIG_SPL_BUILD
foo();
#endif
The building of SPL images can be enabled by CONFIG_SPL option in Kconfig.
Because SPL images normally have a different text base, one has to be
configured by defining CONFIG_SPL_TEXT_BASE. The linker script has to be
defined with CONFIG_SPL_LDSCRIPT.
To support generic U-Boot libraries and drivers in the SPL binary one can
optionally define CONFIG_SPL_XXX_SUPPORT. Currently following options
are supported:
CONFIG_SPL_LIBCOMMON_SUPPORT (common/libcommon.o)
CONFIG_SPL_LIBDISK_SUPPORT (disk/libdisk.o)
CONFIG_SPL_I2C_SUPPORT (drivers/i2c/libi2c.o)
CONFIG_SPL_GPIO_SUPPORT (drivers/gpio/libgpio.o)
CONFIG_SPL_MMC_SUPPORT (drivers/mmc/libmmc.o)
CONFIG_SPL_SERIAL_SUPPORT (drivers/serial/libserial.o)
CONFIG_SPL_SPI_FLASH_SUPPORT (drivers/mtd/spi/libspi_flash.o)
CONFIG_SPL_SPI_SUPPORT (drivers/spi/libspi.o)
CONFIG_SPL_FAT_SUPPORT (fs/fat/libfat.o)
CONFIG_SPL_EXT_SUPPORT
CONFIG_SPL_LIBGENERIC_SUPPORT (lib/libgeneric.o)
CONFIG_SPL_POWER_SUPPORT (drivers/power/libpower.o)
CONFIG_SPL_NAND_SUPPORT (drivers/mtd/nand/libnand.o)
CONFIG_SPL_DRIVERS_MISC_SUPPORT (drivers/misc)
CONFIG_SPL_DMA_SUPPORT (drivers/dma/libdma.o)
CONFIG_SPL_POST_MEM_SUPPORT (post/drivers/memory.o)
CONFIG_SPL_NAND_LOAD (drivers/mtd/nand/nand_spl_load.o)
CONFIG_SPL_SPI_LOAD (drivers/mtd/spi/spi_spl_load.o)
CONFIG_SPL_RAM_DEVICE (common/spl/spl.c)
CONFIG_SPL_WATCHDOG_SUPPORT (drivers/watchdog/libwatchdog.o)
Debugging
---------
When building SPL with DEBUG set you may also need to set CONFIG_PANIC_HANG
as in most cases do_reset is not defined within SPL.
Estimating stack usage
----------------------
With gcc 4.6 (and later) and the use of GNU cflow it is possible to estimate
stack usage at various points in run sequence of SPL. The -fstack-usage option
to gcc will produce '.su' files (such as arch/arm/cpu/armv7/syslib.su) that
will give stack usage information and cflow can construct program flow.
Must have gcc 4.6 or later, which supports -fstack-usage
1) Build normally
2) Perform the following shell command to generate a list of C files used in
SPL:
$ find spl -name '*.su' | sed -e 's:^spl/::' -e 's:[.]su$:.c:' > used-spl.list
3) Execute cflow:
$ cflow --main=board_init_r `cat used-spl.list` 2>&1 | $PAGER
cflow will spit out a number of warnings as it does not parse
the config files and picks functions based on #ifdef. Parsing the '.i'
files instead introduces another set of headaches. These warnings are
not usually important to understanding the flow, however.参考资料:
U-boot 中 SPL 功能和源码流程分析 - 虚生 - 博客园
什么是 u-boot 的“SPL” - cockpunctual - 博客园
ubuntu - what is the use of SPL (secondary program loader) - Stack Overflow
What is the difference between a Bootrom vs bootloader on ARM systems - Stack Overflow
IMX6ULL 是否使用了 SPL——这篇分析可以参考,但是实际对于官方 i.mx6ull evk 板来说,这篇博客中提到的两种方式都没有用到。

