uboot之SPL理解和主要流程梳理

SPL是UBOOT的二次程序加载器,旨在统一各种实现并允许轻松添加新实现。它提供了一个框架,允许复用大部分源码,减少了代码重复。在配置文件中启用SPL及相关支持,通过编译宏如CONFIG_SPL_BUILD。SPL的链接脚本根据平台定制,例如ARMV8的链接脚本定义了内存区域。执行流程包括start.S初始化,board_f.c和spl.c的初始化,然后通过board_boot_order从设备如Flash或UART引导U-Boot。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在uboot代码的/doc/README.spl文件有简单的介绍:

To unify all existing implementations for a secondary program loader (SPL)
and to allow simply adding of new implementations this generic SPL framework
has been created. With this framework almost all source files for a board
can be reused. No code duplication or symlinking is necessary anymore.

可见,SPL被称作:二次程序加载器。UBOOT的SPL提供了一种框架,在这个框架内能够方便的实现一种SPL。该框架能复用一个单板几乎所有的UBOO和SPL的源码

SPL模块的主要公共代码在common\spl目录。要使能SPL,要在configs/***_defconfig文件中配置相关SPL的编译宏。如下配置:

CONFIG_SPL=y
CONFIG_SPL_RAW_IMAGE_SUPPORT=y
CONFIG_SPL_LEGACY_IMAGE_SUPPORT=y
CONFIG_SPL_SYS_MALLOC_SIMPLE=y
CONFIG_SPL_CTC5236=y
CONFIG_SPL_YMODEM_SUPPORT=y

编译SPL过程中可使用CONFIG_SPL_BUILD编译宏。而对于arm的单板可用CONFIG_PRELOADER。

During the SPL build a variable named CONFIG_SPL_BUILD is exported
in the make environment and also appended to CPPFLAGS with -DCONFIG_SPL_BUILD.
Source files can therefore be compiled for SPL with different settings.
ARM-based boards have previously used the option CONFIG_PRELOADER for it.

SPL最后生成u-boot-spl, u-boot-spl.bin 和u-boot-spl.map.

SPL的链接脚本是u-boot-spl.lds

 # Linker Script
    ifdef CONFIG_SPL_LDSCRIPT
    # need to strip off double quotes
    LDSCRIPT := $(addprefix $(SRCTREE)/,$(subst ",,$(CONFIG_SPL_LDSCRIPT)))
    endif
    
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-spl.lds
    endif
    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(TOPDIR)/$(CPUDIR)/u-boot-spl.lds
    endif
    ifeq ($(wildcard $(LDSCRIPT)),)
    $(error could not find linker script)
    endif

对应以ARM V8为例,其具体的链接脚本在arch\arm\cpu\armv8如下:

MEMORY { .sram : ORIGIN = CONFIG_SPL_TEXT_BASE,
        LENGTH = CONFIG_SPL_MAX_SIZE }
MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR,
        LENGTH = CONFIG_SPL_BSS_MAX_SIZE }

OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SECTIONS
{
    .text : {
        . = ALIGN(8);
        *(.__image_copy_start)
        CPUDIR/start.o (.text*)
        *(.text*)
    } >.sram

    .rodata : {
        . = ALIGN(8);
        *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
    } >.sram

    .data : {
        . = ALIGN(8);
        *(.data*)
    } >.sram

    .u_boot_list : {
        . = ALIGN(8);
        KEEP(*(SORT(.u_boot_list*)));
    } >.sram

    .image_copy_end : {
        . = ALIGN(8);
        *(.__image_copy_end)
    } >.sram

    .end : {
        . = ALIGN(8);
        *(.__end)
    } >.sram

    _image_binary_end = .;

    .bss_start : {
        . = ALIGN(8);
        KEEP(*(.__bss_start));
    } >.sdram

    .bss : {
        *(.bss*)
         . = ALIGN(8);
    } >.sdram

    .bss_end : {
        KEEP(*(.__bss_end));
    } >.sdram

arch\arm\cpu\armv8\start.S,作为SPL的入口。

SPL的执行流程如下:

(reset) <arm\cpu\armv8\start.S中执行bllowlevel_init,

(b lowlevel_init: arch\arm\cpu\armv8\lowlevel_init.S) 中执行bllowlevel_init

(b _main) –> <arch/arm/lib/crt0.S>

(bl board_init_f <common\board_f.c>

后调用 <arch/arm/lib/spl.c> (board_init_r) 。主要调用

#ifdef CONFIG_SPL_BOARD_INIT
    spl_board_init();
    /*1 spl_board_init多数CPU会在对应的arch其自己cpu目录下进行针对其CPU的实现。
       如centec芯片 spl.c (arch\arm\cpu\armv8\ctc5236)*/
#endif
    /*board_boot_order common/spl/spl.c有弱引用定义。数CPU会在对应的arch其自己cpu目录下进行针对其CPU的实现。如arch\arm\cpu\armv8\ctc5236下有自己的实现*/
    board_boot_order(spl_boot_list);
    if (boot_from_devices(&spl_image, spl_boot_list,
                  ARRAY_SIZE(spl_boot_list))) {
        puts("SPL: failed to boot from all boot devices\n");
        hang();
    }

boot_from_devices会去查找注册的boot接口。以ctc5236为例,会根据注册的flash和UART去引导uboot。

UART的引导入口在 spl_ymodem_ctc5236.c (common\spl)。主要代码如下:

static int ymodem_normal_boot_flow(struct spl_image_info *spl_image)
{
    info.mode = xyzModem_ymodem;
    res = xyzModem_stream_open(&info, &err);
    NONZ_GOTO_END(res, end_stream);
    
    /* skip spl image */
    uboot_offset = CONFIG_SPL_PAD_TO;
    while(uboot_offset>0)
    {
        res = xyzModem_stream_read(buf, BUF_SIZE, &err);
        NOGT_GOTO_END(res, 0, end_stream);
        uboot_offset -= BUF_SIZE;
    }
    
    /* get 64 bytes uboot image header and (BUF_SIZE-64) bytes data */
    res = xyzModem_stream_read(buf, BUF_SIZE, &err);
    NOGT_GOTO_END(res, 0, end_stream);

    err = ctc5236_spl_parse_ih(spl_image, hdr);
    NEGA_GOTO_END(err, end_stream);
    ……
}

(jump_to_image_no_args去启动u-boot) 到此SPL的生命周期结束。

### U-Boot SPL 启动过程详解 #### 定义与作用 U-Boot SPL (Secondary Program Loader) 是一种小型引导加载程序,在嵌入式系统的启动过程中起着至关重要的角色。由于 SRAM 存储器容量有限,通常仅有 48 KiB 可供使用[^3]。因此,SPL 被设计成能够快速初始化基本硬件资源,并将主要的 U-Boot 映像从外部存储介质(如 MMC/SD 卡)复制到 RAM 中。 #### 启动流程概述 SPL 的启动流程遵循 U-Boot 整体架构的设计原则,但在功能上有所简化针对性优化: 1. **初始环境准备** - 上电复位后,CPU 开始执行位于 ROM 或者内部 SRAM 中最基础的指令集。 - 这些指令负责设置堆栈指针、清除 BSS 段等内容,为后续操作提供稳定的运行平台[^1]。 2. **低级硬件初始化** - 对 CPU 寄存器进行必要的配置调整,确保其工作状态适合进一步的操作需求。 - 初始化时钟源与时序控制器,使系统频率达到预期水平;同时激活电源管理单元以支持更多外设供电。 3. **内存子系统建立** - 如果目标设备具备 SDRAM 控制器,则需在此阶段完成相应的参数设定以及训练序列,从而让 DRAM 成功进入可读写模式。 - 配置好之后即可利用大容量随机访问存储作为临时缓冲区来装载完整的 U-Boot 影像文件。 4. **加载主引导镜像** - 使用 SPI Flash, eMMC 等接口读取预先烧录好的 U-Boot 主体部分数据流。 - 将获取的数据按照指定地址映射至之前预备完毕的工作区内存位置处。 5. **跳转执行全量版 U-Boot** - 设置 PC(program counter)寄存器指向新加载的 U-Boot 入口点偏移地址。 - 正式移交控制权给更高级别的固件组件继续处理剩余的任务直至最终启动 Linux 内核或其他操作系统实例[^4]。 ```c // 示例代码片段展示如何切换到新的 U-Boot 实例 void __attribute__((noreturn)) jump_to_uboot(unsigned long addr) { typedef void (*image_entry_point)(void); ((image_entry_point)addr)(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值