LV05-01-uboot-09-uboot中的SPL

本文主要是uboot——SPL的相关笔记。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
Win32DiskImager Win32DiskImager v1.0
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
https://elixir.bootlin.com/linux/latest/source 在线阅读linux kernel源码
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)

u-boot里面,有一个叫做 SPL 的东东,我们在看 u-boot 代码的时候,很多地方都可以看到和它相关的东西,这里了解这玩意到底是拿来干啥用的。

一、SPL名字怎么来的?

SPL 全称叫做:Secondary Program Loader,看名字,像是一个什么二级加载相关的;实质上,也就是是二级加载。

我们知道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。

如下图所示:

img

先假设,我们的代码,都已经放置到了外部存储器上,也就是绿色部分,然后会进行以下步骤:

  • 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框架?

其实在u-boot的文档中,给我们提供了一个通用的框架说明,我们打开u-boot源码目录中的doc/README.SPL文件:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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.