3. SPL (Secondary Program Loader) 概述:SPL在启动链中的位置、作用与基本工作原理

3.1 SPL在嵌入式启动链中的位置

在典型的嵌入式Linux启动流程中,SPL(Secondary Program Loader)位于启动链的第二阶段,紧接在第一阶段启动ROM(BootROM)之后。BootROM通常固化在SoC内部,负责从预定义的存储介质(如NAND Flash、eMMC、SD卡或SPI NOR Flash)中加载并执行SPL。SPL完成硬件初始化后,再加载并跳转到完整的U-Boot(或ATF BL2等)主引导程序。

启动链关键阶段: BootROM → SPL → U-Boot / ATF BL2 → Kernel → RootFS

SPL的出现主要为了解决BootROM容量限制与主引导程序功能膨胀之间的矛盾。BootROM通常只有几KB到几十KB的片上SRAM可用,无法承载功能完整的引导程序;而SPL作为精简的“桥梁”,在有限的资源下完成关键初始化,为后续加载完整引导程序铺平道路。

SPL在嵌入式启动链中的位置 嵌入式Linux启动链 — SPL位置 BootROM (片上ROM) SPL (SRAM中运行) U-Boot / ATF (DDR中运行) Linux Kernel (DDR中运行) SPL是BootROM与完整引导程序之间的关键桥梁,在SRAM中运行,负责初始化DDR并加载主引导程序 典型大小:16KB ~ 128KB | 运行环境:片上SRAM | 加载目标:DDR

3.2 SPL的核心作用

SPL在启动链中承担着承上启下的关键角色,其核心作用可以归纳为以下五个方面:

作用 说明 典型实现
1. 硬件最小初始化 初始化时钟、PLL、引脚复用、看门狗等基础外设 board_init_f() 阶段
2. DDR控制器初始化 配置DDR时序、频率、电压,使能内存控制器 ddr_init() / dram_init()
3. 存储介质驱动 驱动NAND、eMMC、SD、SPI Flash等存储设备 SPL DM驱动模型
4. 加载主引导程序 从存储介质读取U-Boot/ATF到DDR并校验 spl_load_image()
5. 跳转执行 设置CPU状态、栈指针,跳转到主引导程序入口 spl_jump_to_image()
设计要点: SPL必须保持极小的代码体积(通常限制在16KB~64KB),因此只能包含最必要的驱动和功能。复杂的设备树解析、文件系统支持、网络协议栈等通常留给U-Boot主程序处理。

3.3 SPL的基本工作原理

SPL的工作流程可以划分为四个主要阶段,每个阶段都有明确的目标和约束:

  1. 入口与早期初始化(start.S): 设置CPU模式、关闭中断、初始化栈指针、清零BSS段。此阶段通常用汇编语言编写,与SoC架构紧密相关。
  2. 板级初始化(board_init_f): 初始化时钟、串口、GPIO、定时器等基础外设,完成DDR控制器的初始化。此阶段仍在SRAM中运行,栈空间有限。
  3. SPL重定位(可选): 如果SPL体积较大或需要更大的栈空间,可以将自身从SRAM复制到DDR中继续执行。部分SoC跳过此步骤。
  4. 加载与跳转(spl_load_image + spl_jump_to_image): 从存储介质读取主引导程序到DDR指定地址,进行完整性校验(如CRC或哈希),然后设置跳转参数并执行。
SPL基本工作流程图 SPL基本工作流程 ① 入口与早期初始化 start.S (汇编) ② 板级初始化 board_init_f() ③ SPL重定位(可选) SRAM → DDR ④ 加载主引导程序 spl_load_image() → 跳转 整个流程在SRAM中启动,DDR初始化完成后可重定位到DDR继续执行

3.4 SPL与BootROM、U-Boot的对比

为了更清晰地理解SPL在启动链中的定位,下表从多个维度对比BootROM、SPL和U-Boot:

对比维度 BootROM SPL U-Boot
存储位置 SoC内部ROM Flash / eMMC / SD Flash / eMMC / SD
运行位置 片上SRAM 片上SRAM → DDR DDR
典型大小 4KB ~ 32KB 16KB ~ 128KB 256KB ~ 2MB
可修改性 不可修改(掩膜ROM) 可更新(通过烧录) 可更新(通过烧录)
主要功能 加载SPL到SRAM 初始化DDR,加载U-Boot 加载Kernel、DTB、RootFS
驱动复杂度 极简(仅存储介质驱动) 精简(必要外设+存储) 完整(网络、USB、文件系统等)

3.5 SPL的代码结构示例

以U-Boot SPL为例,其代码组织通常遵循以下目录结构:

u-boot/
├── arch/arm/mach-xxx/          # SoC架构相关代码
│   ├── start.S                 # SPL入口汇编
│   ├── lowlevel_init.S         # 低层初始化
│   └── spl.c                   # SPL板级初始化
├── board/vendor/board_name/    # 板级支持
│   ├── board.c                 # board_init_f() 实现
│   └── Kconfig                 # SPL配置选项
├── common/
│   ├── spl/
│   │   ├── spl.c               # SPL核心框架
│   │   ├── spl_mmc.c           # MMC/SD加载实现
│   │   ├── spl_nand.c          # NAND加载实现
│   │   └── spl_spi.c           # SPI Flash加载实现
│   └── image.c                 # 镜像解析与校验
├── drivers/
│   ├── ddr/                    # DDR控制器驱动
│   ├── serial/                 # 串口驱动
│   └── spi/                    # SPI驱动
└── include/
    ├── spl.h                   # SPL API头文件
    └── configs/board.h         # 板级配置宏
常见陷阱: SPL开发中最容易遇到的问题包括:DDR时序配置错误导致系统无法启动、SPL体积超出SRAM限制、存储介质驱动未正确初始化导致加载失败。建议在开发初期通过串口输出调试信息,逐步验证每个阶段的正确性。

3.6 SPL的启动时间优化要点

SPL作为启动链中的关键环节,其执行时间直接影响整体启动速度。以下是几个常见的优化方向:

  • 减少不必要的初始化: 只初始化启动必需的硬件,将非关键外设的初始化推迟到U-Boot或Kernel阶段。
  • 优化DDR初始化: DDR训练和校准是SPL中最耗时的操作之一,可以通过使用预训练参数或快速训练模式来缩短时间。
  • 提高存储介质读取速度: 使用DMA传输、增大读取块大小、启用缓存等策略。
  • 压缩SPL自身: 如果SPL体积接近SRAM上限,可以考虑使用压缩技术,由BootROM解压后执行。
  • 并行化加载: 在支持多通道存储介质的平台上,可以并行加载U-Boot的多个部分。
典型优化效果: 通过上述优化,SPL阶段的执行时间可以从数百毫秒降低到数十毫秒级别,为整体启动时间优化奠定基础。