复杂 SoC(如 TI K3、NXP i.MX8)ROM 只能载入极小第一级 loader,U-Boot 用 TPL → SPL → Proper 三级镜像逐级初始化 DRAM 与外设。common/spl/spl.c 是 SPL 核心:spl_early_init()、board_init_r()、spl_load_image() 按 spl_image_loader 注册表加载下一级,jump_to_image() 跳转到 U-Boot 或 Linux。与单级 SPL 相比,链式启动把 DDR training、PMIC、时钟树拆到 TPL,减轻 SPL 体积限制。
源码锚点
| 路径 | 关键符号 | 作用 |
|---|---|---|
common/spl/spl.c |
board_init_r() |
SPL 主逻辑 |
common/spl/spl.c |
spl_load_image() |
加载下一级镜像 |
common/spl/spl.c |
jump_to_image() |
跳转 Proper/内核 |
common/spl/spl.c |
spl_parse_image_header() |
解析 mkimage |
include/spl.h |
struct spl_image_info |
加载结果 |
common/spl/spl_fat.c |
spl_load_image_fat() |
从 FAT 加载 |
tpl/arch/... |
TPL 入口 | 第一级 C 代码 |
SPL board_init_r 骨架:
c
/* common/spl/spl.c */
void board_init_r(gd_t *dummy1, ulong dummy2)
{
u32 spl_boot_list[] = {
BOOT_DEVICE_MMC1,
BOOT_DEVICE_MMC2,
BOOT_DEVICE_UART,
...
};
if (IS_ENABLED(CONFIG_SPL_SYS_MALLOC_SIMPLE))
mem_malloc_init(SPL_MALLOC_BASE, SPL_MALLOC_SIZE);
if (IS_ENABLED(CONFIG_SPL_DM))
spl_dm_init();
if (CONFIG_IS_ENABLED(LOAD_FIT)) {
ret = spl_load_image(spl_boot_list, count, &spl_image);
} else {
ret = spl_load_image(spl_boot_list, count, &spl_image);
}
switch (spl_image.os) {
case IH_OS_U_BOOT:
jump_to_image(&spl_image);
break;
case IH_OS_LINUX:
spl_board_prepare_for_boot();
jump_to_image(&spl_image);
break;
default:
hang();
}
}
加载器查找:
c
/* common/spl/spl.c */
static struct spl_image_loader *spl_ll_find_loader(uint boot_device)
{
struct spl_image_loader *drv =
ll_entry_start(struct spl_image_loader, spl_image_loader);
const int n_ents = ll_entry_count(struct spl_image_loader,
spl_image_loader);
for (; drv != drv + n_ents; drv++) {
if (drv->boot_device == boot_device)
return drv;
}
return NULL;
}
jump_to_image:
c
/* common/spl/spl.c */
__weak void __noreturn jump_to_image(struct spl_image_info *spl_image)
{
typedef void __noreturn (*image_entry_t)(ulong, ulong, ulong);
image_entry_t entry = (image_entry_t)spl_image->entry_point;
debug("Jumping to image at %lx\n", spl_image->entry_point);
cleanup_before_linux();
entry(0, 0, 0);
}
TPL 跳 SPL(概念流程):
c
/* tpl 完成 DDR 后 */
spl_image.entry_point = CONFIG_SPL_TEXT_BASE;
memcpy((void *)load_addr, u_boot_spl, spl_size);
/* 跳转 SPL reset vector */
调用链
BootROM
└── 加载 TPL @ SRAM
└── board_init_f (TPL)
└── 初始化最小时钟 + DDR
└── spl_load_image → 加载 SPL 到 SRAM/DDR
└── jump_to_image → SPL
SPL board_init_r
├── spl_early_init / spl_board_init
├── spl_load_image(spl_boot_list)
│ ├── spl_mmc_load() / spl_fat_load() / spl_spi_load()
│ └── spl_parse_image_header → spl_image.entry_point
└── jump_to_image
└── U-Boot Proper board_init_f
或 Falcon: spl_direct_jump_linux()
重点知识
CONFIG_TPL vs CONFIG_SPL :Kconfig 独立开关;TPL 镜像更小、无完整 DM;SPL 可含 CONFIG_SPL_LIBCOMMON_SUPPORT、FAT、USB。三者 defconfig 各生成 u-boot-tpl.bin、u-boot-spl.bin、u-boot.bin。
spl_boot_list 顺序 :板级 spl_boot_device() 或 eFUSE/strap 决定优先介质;失败则尝试下一项。量产常固定 eMMC,开发加 UART/YMODEM。
SPL_FIT / SPL_LEGACY :CONFIG_SPL_LOAD_FIT 从 FIT 读 u-boot 子镜像;Legacy 裸二进制需 CONFIG_SPL_PAYLOAD 指定链接地址。
SPL size 限制 :SRAM 128KB 典型;用 CONFIG_SPL_MAX_SIZE 与 u-boot-spl.map 检查。TPL 更小,DDR 代码必须进 SPL。
SPL_OS_BOOT :SPL 直接加载内核(Falcon)跳过 Proper;与完整 U-Boot 的 booti 能力互斥配置。
Handoff 数据 :spl_handoff 或 ATF BL31 传递 DRAM 参数;K3 等平台 SPL 先起 ATF 再起 U-Boot。
Checklist
- 确认 ROM 支持的镜像格式与加载地址(Reference Manual)
- TPL/SPL/Proper 各自
TEXT_BASE不重叠 -
spl_boot_list与硬件 boot pin 一致 - FIT 中 u-boot、spl 节点 load/entry 正确
-
CONFIG_SPL_STACK足够 DDR init 调用栈 - 串口 log 区分 TPL/SPL 阶段 banner
- 测量各阶段耗时(bootstage 在 SPL 可用性)
- OTA 更新需同时升级 tpl/spl/proper 还是仅 spl+proper