4、Bootloader裁剪与优化(下):U-Boot Falcon模式加速启动、SPL深度优化、Flash分区规划

上一讲我们把U-Boot的启动流程和基础裁剪聊透了。这一讲,咱们要动真格的了——把启动时间从秒级压到毫秒级。

你可能会问:启动快几秒,真的那么重要吗?我做过一个工业相机项目,客户要求上电后200ms内必须输出第一帧图像。普通U-Boot启动要1.2秒,根本没法用。最后就是靠Falcon模式和SPL优化,硬生生把时间压到了180ms。

好,咱们直接进入正题。

4.1 Falcon模式:跳过U-Boot完整启动

Falcon模式的核心思想很简单:SPL加载完硬件初始化后,直接跳转到内核,不再回到U-Boot完整版。

说白了,就是把U-Boot的“二次加载”阶段整个砍掉。你想想看,U-Boot完整版要解析环境变量、加载驱动、检查文件系统……这些在量产产品里其实都是多余的。

Falcon模式启动流程对比

  • 传统模式:ROM → SPL → U-Boot完整版 → 内核
  • Falcon模式:ROM → SPL → 内核(跳过U-Boot完整版)

我习惯把Falcon模式叫做“三级跳变两级跳”。省掉的那一级,通常能省下300-800ms。

4.1.1 启用Falcon模式

在U-Boot配置中打开:

# 在include/configs/your_board.h中
#define CONFIG_SPL_OS_BOOT
#define CONFIG_SYS_SPL_ARGS_ADDR  0x82000000
#define CONFIG_CMD_SPL

然后在SPL中指定内核和设备树的加载地址:

// board/your_board/spl.c
void spl_board_prepare_for_linux(void) {
    // 关闭不必要的硬件外设
    gpio_direction_output(CONFIG_LED_GPIO, 0);
    // 确保DDR已经稳定
    mdelay(1);
}

经验之谈:Falcon模式下,SPL需要自己完成所有硬件初始化。我遇到过DDR时序没配好,内核直接挂掉的情况。建议先在完整U-Boot下确认DDR稳定性,再切Falcon模式。

4.1.2 生成Falcon镜像

把内核、设备树、根文件系统打包成一个SPL能直接加载的镜像:

# 生成Falcon镜像
mkimage -A arm -O linux -T kernel -C none -a 0x80008000 -e 0x80008000 \
        -n "Falcon Kernel" -d zImage falcon_kernel.img

# 烧录到Flash指定位置
# 注意:这个位置必须和SPL中CONFIG_SYS_MMCSD_RAW_MODE_KERNEL_SECTOR一致

4.2 SPL深度优化:把每一微秒都榨出来

SPL是启动的第一段用户代码,它的效率直接决定了整个系统的启动速度。我见过很多团队,SPL代码写得跟完整U-Boot一样臃肿,白白浪费了几百毫秒。

4.2.1 裁剪SPL功能

SPL不需要的功能,一个都不要留:

# 在defconfig中关闭不必要的SPL功能
# CONFIG_SPL_NET_SUPPORT is not set
# CONFIG_SPL_USB_HOST_SUPPORT is not set
# CONFIG_SPL_FAT_WRITE is not set
# CONFIG_SPL_EXT_SUPPORT is not set
# CONFIG_SPL_YMODEM_SUPPORT is not set

# 只保留最核心的
CONFIG_SPL_MMC_SUPPORT=y
CONFIG_SPL_SERIAL_SUPPORT=y
CONFIG_SPL_DRIVERS_MISC_SUPPORT=y

为什么要这么狠?因为SPL的每一行代码,都是拿启动时间换的。我做过测试,一个没用的printk就能多花5ms。

4.2.2 优化DDR初始化

DDR初始化是SPL中最耗时的部分。很多开发板直接用U-Boot的完整DDR训练流程,那是在浪费时间。

我的做法是:

  1. 固定时序参数:量产时DDR颗粒型号固定,不需要每次都重新训练
  2. 使用预置配置:把训练好的参数直接硬编码到SPL中
  3. 减少校准次数:DDR写电平校准从3次减到1次
// 硬编码DDR时序,跳过训练
static struct dram_timing fixed_timing = {
    .tRCD   = 10,
    .tRP    = 10,
    .tRAS   = 24,
    .tWR    = 10,
    .tWTR   = 4,
    .tRFC   = 80,
};

void sdram_init(void) {
    // 直接应用固定时序,不训练
    apply_dram_timing(&fixed_timing);
    // 只做一次写电平校准
    if (dwc_ddrphy_apb_wrlevel(0, 1) != 0) {
        // 校准失败才回退到完整训练
        full_dram_training();
    }
}

注意:固定时序只适用于同一批次、同一型号的DDR颗粒。换批次或换供应商后,必须重新验证。我曾经因为没注意DDR颗粒批次变更,导致产品在高温下随机死机,排查了整整两周。

4.2.3 优化代码执行位置

SPL通常在SRAM中执行,但SRAM速度比DDR慢得多。我的优化策略是:

  • 关键路径代码放在SRAM开头:减少指令预取缺失
  • 大块数据操作放在DDR初始化之后:利用DDR的高带宽
  • 使用内联函数:减少函数调用开销
// 把最常用的函数内联
static inline void early_uart_putc(char c) {
    while (*(volatile uint32_t *)UART_LSR & UART_LSR_TEMT);
    *(volatile uint32_t *)UART_THR = c;
}

4.3 Flash分区规划:为快速启动铺路

Flash分区规划看似简单,但很多人一开始没想清楚,后面改起来痛不欲生。我见过一个产品,因为分区规划不合理,OTA升级时不得不重写整个Flash布局,差点把产品搞成砖。

4.3.1 典型分区布局

分区名称 起始地址 大小 说明
SPL 0x00000000 64KB 一级引导程序
U-Boot环境变量 0x00010000 64KB env存储区
U-Boot完整版 0x00020000 512KB 完整U-Boot(Falcon模式可省略)
Falcon内核 0x000A0000 4MB Falcon模式专用内核镜像
设备树 0x004A0000 128KB 设备树blob
根文件系统 0x004C0000 剩余空间 只读根文件系统
数据分区 末尾 动态 用户数据、日志等

这个布局有几个关键点:

  • SPL放在最前面:ROM默认从0地址加载
  • 环境变量单独分区:避免擦写时影响其他区域
  • Falcon内核紧跟在U-Boot后面:SPL可以直接按偏移加载

4.3.2 分区对齐与擦除块

NAND Flash和eMMC都有擦除块的概念。分区起始地址必须对齐到擦除块边界,否则会出现“写一个字节擦整个块”的悲剧。

// 以NAND Flash为例,擦除块大小128KB
#define FLASH_ERASE_SIZE  (128 * 1024)

// 分区起始地址必须对齐
#define PART_SPL_START    0x00000000
#define PART_ENV_START    ALIGN(0x00010000, FLASH_ERASE_SIZE)
#define PART_UBOOT_START  ALIGN(0x00020000, FLASH_ERASE_SIZE)
#define PART_KERNEL_START ALIGN(0x000A0000, FLASH_ERASE_SIZE)

我的习惯:在分区表里预留一个“备用分区”。万一后续需要增加功能(比如双备份系统),不用重新规划整个布局。我一般预留4MB,够用了。

4.3.3 Falcon模式下的分区优化

如果用了Falcon模式,U-Boot完整版分区可以缩小甚至去掉。但我不建议完全去掉——保留一个最小化的U-Boot用于紧急恢复。

优化后的布局:

SPL:         64KB
环境变量:     64KB
U-Boot恢复:  256KB  (最小化版本,仅用于刷机)
Falcon内核:   4MB
设备树:      128KB
根文件系统:   剩余空间

这样,SPL加载Falcon内核时,直接从偏移0x00060000读取,不需要任何文件系统解析,速度最快。

4.4 实战:把启动时间从1.2秒压到180ms

回到我开头说的那个工业相机项目。我们来看看具体怎么做的:

  1. 启用Falcon模式:跳过U-Boot完整版,省掉400ms
  2. SPL裁剪:去掉网络、USB、文件系统支持,省掉150ms
  3. DDR固定时序:跳过训练流程,省掉300ms
  4. Flash分区优化:把内核放在固定偏移,省掉文件系统解析的80ms
  5. 代码优化:内联关键函数、减少printk,省掉90ms

最终启动时间:180ms。客户很满意。

关键数据:优化前后对比

阶段 优化前 优化后 节省
ROM → SPL 50ms 50ms 0ms
SPL初始化 450ms 60ms 390ms
U-Boot完整版 400ms 0ms 400ms
加载内核 300ms 70ms 230ms
总计 1200ms 180ms 1020ms

嗯,这就是Bootloader裁剪与优化的全部内容。从Falcon模式到SPL深度优化,再到Flash分区规划,每一步都能帮你省下几百毫秒。别小看这几百毫秒,在工业控制、汽车电子这些领域,它就是产品能不能用的分水岭。

Bootloader裁剪优化流程 原始启动 (1.2s) Falcon模式 SPL深度优化 跳过U-Boot 裁剪+内联 DDR固定时序 Flash分区规划 最终启动 (180ms) 继续优化 对齐布局 集成验证 优化要点总结 • Falcon模式:跳过U-Boot完整版,直接加载内核 • SPL优化:裁剪无用功能、内联关键函数、固定DDR时序 • Flash分区:对齐擦除块、预留恢复分区、Falcon内核固定偏移 • 实测效果:1.2s → 180ms,节省87%启动时间