4. SPL源码结构解析:SPL代码在U-Boot中的位置、关键目录与文件、编译配置系统
SPL(Secondary Program Loader)作为U-Boot的两阶段启动架构中的第一阶段引导程序,其源码组织方式与主U-Boot既有联系又有独立结构。理解SPL在U-Boot源码树中的位置、关键目录与文件、以及编译配置系统,是进行启动优化和定制化开发的基础。本章将深入解析SPL源码的物理布局与逻辑组织。
4.1 SPL在U-Boot源码树中的位置
SPL并非一个独立的代码仓库,而是作为U-Boot源码树的一部分存在。从顶层目录结构来看,SPL的代码分散在多个目录中,通过统一的编译配置系统(Kconfig/Makefile)进行整合。下表列出了SPL相关的主要目录及其职责:
| 目录路径 | 职责描述 | SPL相关性 |
|---|---|---|
arch/arm/ (及其他架构) | CPU架构相关代码,包括启动汇编、异常处理、MMU等 | SPL入口代码(如start.S)位于此 |
board/ | 板级初始化代码,包含DDR初始化、时钟配置等 | SPL板级初始化函数(如spl_board_init()) |
common/spl/ | SPL框架核心代码,启动流程控制 | 核心:spl.c, spl_atf.c, spl_fit.c |
drivers/ | 设备驱动,包括串口、MMC、NAND、SPI Flash等 | SPL使用的精简驱动(通过配置裁剪) |
include/ | 全局头文件,数据结构定义 | spl.h, spl_load.h 等关键头文件 |
lib/ | 通用库函数,如字符串操作、CRC校验、压缩解压 | SPL链接时使用的精简库 |
dts/ | 设备树源文件 | SPL使用的设备树(通常为-u-boot.dtsi) |
关键概念: SPL与主U-Boot共享同一套源码树,但通过编译宏
CONFIG_SPL_BUILD 进行区分。当编译SPL时,该宏被定义,代码中通过 #ifdef CONFIG_SPL_BUILD 条件编译来包含或排除特定代码段。
4.2 关键目录与文件详解
4.2.1 common/spl/:SPL框架核心
这是SPL最重要的目录,包含了SPL启动流程的框架代码。核心文件如下:
spl.c:SPL主控逻辑,实现board_init_f()和board_init_r()两个阶段,以及spl_phase_boot_device()等关键函数。spl_atf.c:ARM Trusted Firmware (ATF) 加载与跳转支持,实现spl_invoke_atf()函数。spl_fit.c:FIT (Flattened Image Tree) 镜像解析与加载,支持多镜像打包。spl_legacy.c:传统Legacy镜像格式(uImage)加载支持。spl_mmc.c:MMC/SD卡设备加载实现。spl_nand.c:NAND Flash设备加载实现。spl_spi.c:SPI Flash设备加载实现。spl_net.c:网络启动(TFTP)加载实现。
开发提示: 当需要添加新的启动设备支持(如QSPI、SDIO等),应在
common/spl/ 目录下创建对应的 spl_xxx.c 文件,并实现 spl_load_xxx() 函数接口。
4.2.2 arch/arm/:架构相关启动代码
以ARM架构为例,SPL的入口代码位于 arch/arm/cpu/armv7/ 或 arch/arm/cpu/armv8/ 目录下:
start.S:SPL的第一条指令入口,设置CPU模式、初始化栈指针、清零BSS段,然后跳转到board_init_f()。lowlevel_init.S:低层初始化,包括关闭看门狗、初始化PLL、配置系统时钟等。cpu.c:CPU核心初始化,如使能I/D-Cache、TLB等。
4.2.3 board/:板级初始化代码
每个板级目录(如 board/ti/am335x/)包含:
board.c:实现spl_board_init()、board_init_f()、dram_init()等板级特定函数。mux.c:引脚复用配置。clock.c:时钟树配置。
4.2.4 include/:关键头文件
spl.h:SPL核心数据结构定义,包括struct spl_image_info、struct spl_load_info等。spl_load.h:加载接口函数声明。asm/arch/cpu.h:CPU架构相关寄存器定义。
4.3 SPL源码结构SVG架构图
下图展示了SPL源码在U-Boot中的整体组织架构,以及各模块之间的调用关系:
4.4 编译配置系统
SPL的编译配置系统基于Kconfig和Makefile,与主U-Boot共享同一套配置框架,但通过独立的配置选项进行控制。
4.4.1 Kconfig配置体系
SPL相关的Kconfig选项分布在多个层级:
- 顶层Kconfig:
Kconfig文件中的config SPL选项,作为SPL功能的全局开关。 - 架构级Kconfig:
arch/arm/Kconfig中的SPL架构相关选项。 - 板级Kconfig:
board/xxx/Kconfig中的板级SPL配置。 - 功能级Kconfig:各功能目录下的Kconfig,如
common/spl/Kconfig。
下表列出了SPL编译配置中的关键Kconfig选项:
| 配置选项 | 默认值 | 功能描述 |
|---|---|---|
CONFIG_SPL | n | 全局SPL功能使能 |
CONFIG_SPL_FRAMEWORK | y (if SPL) | 启用SPL框架核心代码 |
CONFIG_SPL_LDSCRIPT | 自定义 | SPL链接脚本路径 |
CONFIG_SPL_TEXT_BASE | 0x00000000 | SPL代码段基地址 |
CONFIG_SPL_MAX_SIZE | 0x8000 | SPL最大尺寸限制(字节) |
CONFIG_SPL_BSS_START_ADDR | 自定义 | SPL BSS段起始地址 |
CONFIG_SPL_BSS_MAX_SIZE | 0x2000 | SPL BSS段最大尺寸 |
CONFIG_SPL_STACK | 自定义 | SPL栈地址 |
CONFIG_SPL_MMC_SUPPORT | n | 使能MMC/SD卡加载支持 |
CONFIG_SPL_NAND_SUPPORT | n | 使能NAND Flash加载支持 |
CONFIG_SPL_SPI_SUPPORT | n | 使能SPI Flash加载支持 |
CONFIG_SPL_NET_SUPPORT | n | 使能网络加载支持 |
CONFIG_SPL_ATF | n | 使能ATF加载支持 |
CONFIG_SPL_FIT | n | 使能FIT镜像格式支持 |
CONFIG_SPL_CRYPTO | n | 使能加密/签名验证支持 |
4.4.2 Makefile编译规则
SPL的Makefile编译规则通过条件编译实现。核心机制如下:
# 顶层 Makefile 中的 SPL 编译入口
ifeq ($(CONFIG_SPL),y)
ALL-y += spl/u-boot-spl.bin
endif
# common/spl/Makefile 中的 SPL 源文件编译规则
obj-$(CONFIG_SPL_FRAMEWORK) += spl.o
obj-$(CONFIG_SPL_MMC_SUPPORT) += spl_mmc.o
obj-$(CONFIG_SPL_NAND_SUPPORT) += spl_nand.o
obj-$(CONFIG_SPL_SPI_SUPPORT) += spl_spi.o
obj-$(CONFIG_SPL_NET_SUPPORT) += spl_net.o
obj-$(CONFIG_SPL_ATF) += spl_atf.o
obj-$(CONFIG_SPL_FIT) += spl_fit.o
obj-$(CONFIG_SPL_LEGACY) += spl_legacy.o
# 驱动目录中的 SPL 编译规则
obj-$(CONFIG_SPL_MMC_SUPPORT) += drivers/mmc/
obj-$(CONFIG_SPL_NAND_SUPPORT) += drivers/mtd/nand/
obj-$(CONFIG_SPL_SERIAL_SUPPORT) += drivers/serial/
常见陷阱: SPL的尺寸限制非常严格(通常为32KB~256KB)。在添加新的驱动或功能时,必须通过
CONFIG_SPL_MAX_SIZE 检查编译后的SPL二进制是否超出限制。超出限制会导致启动失败,且通常没有错误提示。
4.4.3 编译流程与输出产物
SPL的编译流程与主U-Boot并行执行,最终生成独立的二进制文件。编译流程如下:
- 配置阶段:
make xxx_defconfig生成.config文件,包含SPL相关配置。 - 编译阶段:
make命令同时编译SPL和主U-Boot,通过CONFIG_SPL_BUILD宏区分。 - 链接阶段:使用独立的链接脚本(
u-boot-spl.lds)生成SPL的ELF文件。 - 格式转换:将ELF转换为二进制格式(
u-boot-spl.bin),并可能添加头部信息。
编译完成后,在 spl/ 目录下生成以下关键产物:
| 文件 | 格式 | 说明 |
|---|---|---|
u-boot-spl | ELF | SPL的ELF可执行文件,用于调试 |
u-boot-spl.bin | Binary | SPL的原始二进制镜像,烧录到存储介质 |
u-boot-spl.map | Text | SPL的符号映射表,用于地址分析 |
u-boot-spl.srec | S-Record | Motorola S-Record格式,用于串口下载 |
u-boot-spl.lds | Linker Script | SPL的链接脚本,定义内存布局 |
4.5 SPL编译配置与输出产物SVG流程图
下图展示了从配置到最终产物的完整编译流程:
4.6 实际案例分析:TI AM335x平台的SPL源码结构
以TI AM335x平台为例,展示SPL源码的实际组织方式:
u-boot/
├── arch/arm/cpu/armv7/
│ ├── start.S # SPL入口 (reset vector)
│ ├── lowlevel_init.S # 低层初始化 (PLL, DDR)
│ └── cpu.c # CPU初始化
├── board/ti/am335x/
│ ├── board.c # spl_board_init(), dram_init()
│ ├── mux.c # 引脚复用配置
│ └── clock.c # 时钟配置
├── common/spl/
│ ├── spl.c # SPL主控
│ ├── spl_mmc.c # MMC启动支持
│ ├── spl_nand.c # NAND启动支持
│ └── spl_spi.c # SPI启动支持
├── drivers/
│ ├── mmc/omap_hsmmc.c # MMC控制器驱动
│ ├── serial/ns16550.c # 串口驱动
│ └── spi/omap3_spi.c # SPI驱动
├── include/
│ ├── spl.h # SPL数据结构
│ └── configs/am335x_evm.h # 板级配置头文件
└── dts/
└── am335x-evm-u-boot.dtsi # SPL设备树片段
实战要点: 在AM335x平台上,SPL的尺寸限制为
CONFIG_SPL_MAX_SIZE=0x8000(32KB),其中前2KB为ROM Code引导头。SPL运行在内部SRAM中,地址范围为0x402F0400~0x402FFFFF。理解这些物理限制对于优化SPL启动时间至关重要。
4.7 本章小结
SPL源码结构体现了嵌入式启动固件的模块化设计思想:
- 物理分布:SPL代码分散在
arch/、board/、common/spl/、drivers/等目录中,通过统一的编译框架整合。 - 逻辑分层:从架构层→板级层→SPL框架层→驱动层,层次清晰,便于移植和定制。
- 配置灵活:通过Kconfig和Makefile的条件编译机制,可以精确控制SPL的功能集和尺寸。
- 产物独立:SPL生成独立的二进制镜像,与主U-Boot解耦,便于独立烧录和调试。