uboot2015第一阶段---SPL

声明:

本文转载自 《 U-BOOT移植过程详解: SPL》

SPL

SPL是uboot第一阶段执行的代码. 主要负责搬移uboot第二阶段的代码到内存中运行. SPL是由固化在芯片内部的ROM引导的. 我们知道很多芯片厂商固化的ROM支持从nandflash, SDCARD等外部介质启动. 所谓启动, 就是从这些外部介质中搬移一段固定大小(4K/8K/16K等)的代码到内部RAM中运行. 这里搬移的就是SPL. 在最新版本的uboot中, 可以看到SPL也支持nandflash, SDCARD等多种启动方式. 当SPL本身被搬移到内部RAM中运行时, 它会从nandflash, SDCARD等外部介质中搬移uboot第二阶段的代码到外部内存中.

SPL的文件组成

当我们在uboot下执行make命令的时候, 它最核心的功能是执行Makefile中的all目标编译出相应的文件. 我们来看看这个all目标

all:        $(ALL-y) $(SUBDIR_EXAMPLES)  
all:        $(ALL-y) $(SUBDIR_EXAMPLES)  

all依赖于(ALLy)(SUBDIR_EXAMPLES), 这里我只关注ALL-y, 如下:

# Always append ALL so that arch config.mk's can add custom ones  
ALL-y += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map  

ALL-$(CONFIG_NAND_U_BOOT) += $(obj)u-boot-nand.bin  
ALL-$(CONFIG_ONENAND_U_BOOT) += $(obj)u-boot-onenand.bin  
ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin  
ALL-$(CONFIG_SPL_FRAMEWORK) += $(obj)u-boot.img  
ALL-$(CONFIG_TPL) += $(obj)tpl/u-boot-tpl.bin  
ALL-$(CONFIG_OF_SEPARATE) += $(obj)u-boot.dtb $(obj)u-boot-dtb.bin  
ifneq ($(CONFIG_SPL_TARGET),)  
ALL-$(CONFIG_SPL) += $(obj)$(subst ",,$(CONFIG_SPL_TARGET))  
endif  

# enable combined SPL/u-boot/dtb rules for tegra  
ifneq ($(CONFIG_TEGRA),)  
ifeq ($(CONFIG_OF_SEPARATE),y)  
ALL-y += $(obj)u-boot-dtb-tegra.bin  
else  
ALL-y += $(obj)u-boot-nodtb-tegra.bin  
endif  
endif  
# Always append ALL so that arch config.mk's can add custom ones  
ALL-y += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map  

ALL-$(CONFIG_NAND_U_BOOT) += $(obj)u-boot-nand.bin  
ALL-$(CONFIG_ONENAND_U_BOOT) += $(obj)u-boot-onenand.bin  
ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin  
ALL-$(CONFIG_SPL_FRAMEWORK) += $(obj)u-boot.img  
ALL-$(CONFIG_TPL) += $(obj)tpl/u-boot-tpl.bin  
ALL-$(CONFIG_OF_SEPARATE) += $(obj)u-boot.dtb $(obj)u-boot-dtb.bin  
ifneq ($(CONFIG_SPL_TARGET),)  
ALL-$(CONFIG_SPL) += $(obj)$(subst ",,$(CONFIG_SPL_TARGET))  
endif  

# enable combined SPL/u-boot/dtb rules for tegra  
ifneq ($(CONFIG_TEGRA),)  
ifeq ($(CONFIG_OF_SEPARATE),y)  
ALL-y += $(obj)u-boot-dtb-tegra.bin  
else  
ALL-y += $(obj)u-boot-nodtb-tegra.bin  
endif  
endif  

因为本节是讨论SPL, 所以我们只关注其中的一句ALL-(CONFIGSPL)+=(obj)spl/u-boot-spl.bin
这句话表明
必须定义CONFIG_SPL才能编译出spl的bin: 一般在”include/configs/${CONFIG_NAME}.h”中定义
SPL的bin依赖于u-boot-spl.bin
接着往下看

$(obj)spl/u-boot-spl.bin:   $(SUBDIR_TOOLS) depend  
        $(MAKE) -C spl all 
CONFIG_SPL_BUILD := y  
export CONFIG_SPL_BUILD

export CONFIG_SPL_BUILD: 在接下来的编译中, 这个变量为y. 从后面的分析中可以看到, uboot的stage1, stage2阶段的代码用的是同一个Start.S, 只不过在Start.S中用#ifdef CONFIG_SPL_BUILD这种条件编译来区分. 类似的还有其他一些文件.

SPL代码分析

u-boot-spl.lds: 它的位置在上文中我们分析了
根据u-boot-spl.lds中的规则, 我们知道CPUDIR/start.o被放在了最前面. 它所对应的文件就是arch/arm/cpu/armv7/start.S

start.S

下面我们看看start.S

.globl _start  
_start: b   reset  
    ldr pc, _undefined_instruction  
    ldr pc, _software_interrupt  
    ldr pc, _prefetch_abort  
    ldr pc, _data_abort  
    ldr pc, _not_used  
    ldr pc, _irq  
    ldr pc, _fiq  


_start是我们在lds里面指定的ENTRY(_start)
首先会跳转到reset处
ldr pc, _xxx定义的是中断向量表

#ifdef CONFIG_SPL_BUILD  
_undefined_instruction: .word _undefined_instruction  
_software_interrupt:    .word _software_interrupt  
_prefetch_abort:    .word _prefetch_abort  
_data_abort:        .word _data_abort  
_not_used:      .word _not_used  
_irq:           .word _irq  
_fiq:           .word _fiq  
_pad:           .word 0x12345678 /* now 16*4=64 */  
#else  
_undefined_instruction: .word undefined_instruction  
_software_interrupt:    .word software_interrupt  
_prefetch_abort:    .word prefetch_abort  
_data_abort:        .word data_abort  
_not_used:      .word not_used  
_irq:           .word irq  
_fiq:           .word fiq  
_pad:           .word 0x12345678 /* now 16*4=64 */  
#endif  /* CONFIG_SPL_BUILD */ 

当CONFIG了SPL_BUILD之后, 一旦发生异常中断, 就会进入死循环. 所以我们的SPL里面不允许出发异常中断
不过正常的uboot(即stage2阶段)还是可以处理异常中断的.
reset

/*  
 * the actual reset code  
 */  

reset:  
    bl  save_boot_params  
    /*  
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,  
     * except if in HYP mode already  
     */  
    mrs r0, cpsr  
    and r1, r0, #0x1f       @ mask mode bits  
    teq r1, #0x1a       @ test for HYP mode  
    bicne   r0, r0, #0x1f       @ clear all mode bits  
    orrne   r0, r0, #0x13       @ set SVC mode  
    orr r0, r0, #0xc0       @ disable FIQ and IRQ  
    msr cpsr,r0  

        /* ........ */  
    /* the mask ROM code should have PLL and others stable */  
#ifndef CONFIG_SKIP_LOWLEVEL_INIT  
    bl  cpu_init_cp15  
    bl  cpu_init_crit  
#endif  

    bl  _main  

当初次上电或者复位时, Uboot最新运行的就是这里的代码
bl save_boot_params: 如果没有重新定义save_boot_params,则使用

/*************************************************************************  
 *  
 * cpu_init_cp15  
 *  
 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless  
 * CONFIG_SYS_ICACHE_OFF is defined.  
 *  
 *************************************************************************/  
ENTRY(cpu_init_cp15)  
    /*  
     * Invalidate L1 I/D  
     */  
    mov r0, #0          @ set up for MCR  
    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs  
    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache  
    mcr p15, 0, r0, c7, c5, 6   @ invalidate BP array  
    mcr     p15, 0, r0, c7, c10, 4  @ DSB  
    mcr     p15, 0, r0, c7, c5, 4   @ ISB  

    /*  
     * disable MMU stuff and caches  
     */  
    mrc p15, 0, r0, c1, c0, 0  
    bic r0, r0, #0x00002000 @ clear bits 13 (--V-)  
    bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)  
    orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align  
    orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB  
#ifdef CONFIG_SYS_ICACHE_OFF  
    bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache  
#else  
    orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache  
#endif  
    mcr p15, 0, r0, c1, c0, 0  

#ifdef CONFIG_ARM_ERRATA_716044  
    mrc p15, 0, r0, c1, c0, 0   @ read system control register  
    orr r0, r0, #1 << 11  @ set bit #11  
    mcr p15, 0, r0, c1, c0, 0   @ write system control register  
#endif  

#ifdef CONFIG_ARM_ERRATA_742230  
    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
    orr r0, r0, #1 << 4       @ set bit #4  
    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
#endif  

#ifdef CONFIG_ARM_ERRATA_743622  
    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
    orr r0, r0, #1 << 6       @ set bit #6  
    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
#endif  

#ifdef CONFIG_ARM_ERRATA_751472  
    mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
    orr r0, r0, #1 << 11  @ set bit #11  
    mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
#endif  

    mov pc, lr          @ back to my caller  
ENDPROC(cpu_init_cp15)  

cpu_init_crit

#ifndef CONFIG_SKIP_LOWLEVEL_INIT  
/*************************************************************************  
 *  
 * CPU_init_critical registers  
 *  
 * setup important registers  
 * setup memory timing  
 *  
 *************************************************************************/  
ENTRY(cpu_init_crit)  
    /*  
     * Jump to board specific initialization...  
     * The Mask ROM will have already initialized  
     * basic memory. Go here to bump up clock rate and handle  
     * wake up conditions.  
     */  
    b   lowlevel_init       @ go setup pll,mux,memory  
ENDPROC(cpu_init_crit)  
#endif  

b lowlevel_init : 跳转到

lowlevel_init.S

lowlevel_init

#include <asm-offsets.h>  
#include <config.h>  
#include <linux/linkage.h>  

ENTRY(lowlevel_init)  
    /*  
     * Setup a temporary stack  
     */  
    ldr sp, =CONFIG_SYS_INIT_SP_ADDR  
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */  
#ifdef CONFIG_SPL_BUILD  
    ldr r9, =gdata  
#else  
    sub sp, #GD_SIZE  
    bic sp, sp, #7  
    mov r9, sp  
#endif  
    /*  
     * Save the old lr(passed in ip) and the current lr to stack  
     */  
    push    {ip, lr}  

    /*  
     * go setup pll, mux, memory  
     */  
    bl  s_init  
    pop {ip, pc}  

以前老版本的uboot, lowlevel_init一般都是在board/xxx下面的板级文件夹下面实现的. 现在直接放到CPUDIR下面了, 那它做了什么事情呢
对stack pointer赋值成CONFIG_SYS_INIT_SP_ADDR
确保sp是8字节对齐
将gdata的地址存入到r9寄存器中
跳转到 s_init: 这个s_init就需要芯片厂商或者我们自己在板级文件里面实现了. 它主要做的事情
setup pll, mux, memory
我个人感觉, 新版本的uboot在CPUDIR下实现了一个lowlevel_init.S文件, 主要目标是初始化sp, 这样s_init就可以用C语言实现了. 而以前的老版本里面, s_init里面要做的事情都是用汇编做的.

crt0.S

_main

ENTRY(_main)  

/*  
 * Set up initial C runtime environment and call board_init_f(0).  
 */  

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)  
    ldr sp, =(CONFIG_SPL_STACK)  
#else  
    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)  
#endif  
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */  
    sub sp, #GD_SIZE    /* allocate one GD above SP */  
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */  
    mov r9, sp      /* GD is above SP */  
    mov r0, #0  
    bl  board_init_f  

重新对SP赋值, 确认sp是8字对齐
在栈顶保留一个global_data的大小, 这个global_data是uboot里面的一个全局数据, 很多地方都会用到. 俗称 gd_t
确认更新后的sp是8字对齐
r9指向global_data, 后面别的地方想用global_data时候, 可以直接从r9里面获取地址.
r0赋值0
bl board_init_f: 跳转到board_init_f. 在编译SPL时, 分析Makefile可以看出, 该函数的实现是在

board_init_f

/*  
 * In the context of SPL, board_init_f must ensure that any clocks/etc for  
 * DDR are enabled, ensure that the stack pointer is valid, clear the BSS  
 * and call board_init_f.  We provide this version by default but mark it  
 * as __weak to allow for platforms to do this in their own way if needed.  
 */  
void __weak board_init_f(ulong dummy)  
{  
    /* Clear the BSS. */  
    memset(__bss_start, 0, __bss_end - __bss_start);  

    /* Set global data pointer. */  
    gd = &gdata;  

    board_init_r(NULL, 0);  
}  

__weak: 表明该函数可以被重新定义
对BSS段进行清零操作
gd = &gdata;
gd的定义在DECLARE_GLOBAL_DATA_PTR

board_init_r

#ifdef CONFIG_SYS_SPL_MALLOC_START  
    mem_malloc_init(CONFIG_SYS_SPL_MALLOC_START,  
            CONFIG_SYS_SPL_MALLOC_SIZE);  
#endif  

如果定义了:CONFIG_SYS_SPL_MALLOC_START, 则进行memory的malloc池初始化. 以后调用malloc就在这个池子里面分配内存

#ifndef CONFIG_PPC  
    /*  
     * timer_init() does not exist on PPC systems. The timer is initialized  
     * and enabled (decrementer) in interrupt_init() here.  
     */  
    timer_init();  
#endif

如果没有定义:CONFIG_PPC, 则进行timer的初始化.

#ifdef CONFIG_SPL_BOARD_INIT  
    spl_board_init();  
#endif 

SPL阶段, 如果还需要做什么初始化动作, 可以放在这里. 具体的实现可以在BOARDDIR下面.

boot_device = spl_boot_device();  
    debug("boot device - %d\n", boot_device);  

必须实现spl_boot_device, 返回是从哪个外部设备启动的(NAND/SDCARD/NOR…). 可以厂商或者自己在BOARDDIR下面实现

  switch (boot_device) {  
#ifdef CONFIG_SPL_RAM_DEVICE  
    case BOOT_DEVICE_RAM:  
        spl_ram_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_MMC_SUPPORT  
    case BOOT_DEVICE_MMC1:  
    case BOOT_DEVICE_MMC2:  
    case BOOT_DEVICE_MMC2_2:  
        spl_mmc_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_NAND_SUPPORT  
    case BOOT_DEVICE_NAND:  
        spl_nand_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_ONENAND_SUPPORT  
    case BOOT_DEVICE_ONENAND:  
        spl_onenand_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_NOR_SUPPORT  
    case BOOT_DEVICE_NOR:  
        spl_nor_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_YMODEM_SUPPORT  
    case BOOT_DEVICE_UART:  
        spl_ymodem_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_SPI_SUPPORT  
    case BOOT_DEVICE_SPI:  
        spl_spi_load_image();  
        break;  
#endif  
#ifdef CONFIG_SPL_ETH_SUPPORT  
    case BOOT_DEVICE_CPGMAC:  
#ifdef CONFIG_SPL_ETH_DEVICE  
        spl_net_load_image(CONFIG_SPL_ETH_DEVICE);  
#else  
        spl_net_load_image(NULL);  
#endif  
        break;  
#endif  
#ifdef CONFIG_SPL_USBETH_SUPPORT  
    case BOOT_DEVICE_USBETH:  
        spl_net_load_image("usb_ether");  
        break;  
#endif  
    default:  
        debug("SPL: Un-supported Boot Device\n");  
        hang();  
    }  

将image从具体的外部设备中load到ram中. 这里暂时先不分析具体的load过程.

switch (spl_image.os) {  
    case IH_OS_U_BOOT:  
        debug("Jumping to U-Boot\n");  
        break;  
#ifdef CONFIG_SPL_OS_BOOT  
    case IH_OS_LINUX:  
        debug("Jumping to Linux\n");  
        spl_board_prepare_for_linux();  
        jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR);  
#endif  
    default:  
        debug("Unsupported OS image.. Jumping nevertheless..\n");  
    }  
    jump_to_image_no_args(&spl_image);  

判断image的类型
如果是u-boot,则直接break, 去运行u-boot
如果是Linux,则启动Linux
至此,SPL结束它的生命,控制权交于u-boot或Linux

在接下来的一篇中, 我们会分析当控制权交给u-boot之后, uboot的运行流程

总结

SPL移植注意点
s_init: C语言实现此函数, 必须的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成以下功能
设置CPU的PLL, GPIO管脚复用, memory等
spl_board_init: C语言实现, 可选的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成的功能自己决定
spl_boot_device: C语言实现, 必须的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成的功能
返回是从什么设备启动的(NAND/SDCARD/Nor …). 像Atmel, Samsung的芯片, 都是有办法做这个事情的.

转载于:https://www.cnblogs.com/xxg1992/p/6636381.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值