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中的整体组织架构,以及各模块之间的调用关系:

SPL源码结构架构图 SPL源码结构架构图 SPL入口 (start.S) 架构相关层 (arch/arm/) lowlevel_init.S cpu.c (Cache/MMU) interrupts.c timer.c 板级相关层 (board/xxx/) board.c (DDR/时钟) mux.c (引脚复用) spl_board_init() SPL框架核心 (common/spl/) spl.c (主控) spl_atf.c spl_fit.c spl_mmc.c spl_nand.c 设备驱动层 (drivers/) mmc/ nand/ spi/ serial/ net/ 说明:SPL与主U-Boot共享源码树,通过 CONFIG_SPL_BUILD 宏进行条件编译区分

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_SPLn全局SPL功能使能
CONFIG_SPL_FRAMEWORKy (if SPL)启用SPL框架核心代码
CONFIG_SPL_LDSCRIPT自定义SPL链接脚本路径
CONFIG_SPL_TEXT_BASE0x00000000SPL代码段基地址
CONFIG_SPL_MAX_SIZE0x8000SPL最大尺寸限制(字节)
CONFIG_SPL_BSS_START_ADDR自定义SPL BSS段起始地址
CONFIG_SPL_BSS_MAX_SIZE0x2000SPL BSS段最大尺寸
CONFIG_SPL_STACK自定义SPL栈地址
CONFIG_SPL_MMC_SUPPORTn使能MMC/SD卡加载支持
CONFIG_SPL_NAND_SUPPORTn使能NAND Flash加载支持
CONFIG_SPL_SPI_SUPPORTn使能SPI Flash加载支持
CONFIG_SPL_NET_SUPPORTn使能网络加载支持
CONFIG_SPL_ATFn使能ATF加载支持
CONFIG_SPL_FITn使能FIT镜像格式支持
CONFIG_SPL_CRYPTOn使能加密/签名验证支持

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并行执行,最终生成独立的二进制文件。编译流程如下:

  1. 配置阶段:make xxx_defconfig 生成 .config 文件,包含SPL相关配置。
  2. 编译阶段:make 命令同时编译SPL和主U-Boot,通过 CONFIG_SPL_BUILD 宏区分。
  3. 链接阶段:使用独立的链接脚本(u-boot-spl.lds)生成SPL的ELF文件。
  4. 格式转换:将ELF转换为二进制格式(u-boot-spl.bin),并可能添加头部信息。

编译完成后,在 spl/ 目录下生成以下关键产物:

文件格式说明
u-boot-splELFSPL的ELF可执行文件,用于调试
u-boot-spl.binBinarySPL的原始二进制镜像,烧录到存储介质
u-boot-spl.mapTextSPL的符号映射表,用于地址分析
u-boot-spl.srecS-RecordMotorola S-Record格式,用于串口下载
u-boot-spl.ldsLinker ScriptSPL的链接脚本,定义内存布局

4.5 SPL编译配置与输出产物SVG流程图

下图展示了从配置到最终产物的完整编译流程:

SPL编译配置与输出产物流程图 SPL编译配置与输出产物流程图 make xxx_defconfig 生成 .config (含SPL配置) make 条件编译 (CONFIG_SPL_BUILD) 链接 (u-boot-spl.lds) 生成 u-boot-spl (ELF) objcopy / mkimage 格式转换与头部添加 u-boot-spl.bin 原始二进制 (烧录用) u-boot-spl.map 符号映射表 (调试用) u-boot-spl.srec S-Record (串口下载) u-boot-spl.lds 链接脚本 (内存布局) 说明:SPL编译产物位于 spl/ 目录,与主U-Boot产物 (u-boot.bin) 分离

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解耦,便于独立烧录和调试。