• 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
    ", 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
    ");  
            hang();  
        }  

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

    switch (spl_image.os) {  
        case IH_OS_U_BOOT:  
            debug("Jumping to U-Boot
    ");  
            break;  
    #ifdef CONFIG_SPL_OS_BOOT  
        case IH_OS_LINUX:  
            debug("Jumping to Linux
    ");  
            spl_board_prepare_for_linux();  
            jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR);  
    #endif  
        default:  
            debug("Unsupported OS image.. Jumping nevertheless..
    ");  
        }  
        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的芯片, 都是有办法做这个事情的.

  • 相关阅读:
    面向连接的网络应用程序--服务器端
    使用完整读写函数的网络应用程序
    套接字编程基础
    网络编程基础
    传输控制协议TCP
    UDP协议
    电子词典
    strtok()函数、fseek()函数、fwrite()函数、fread()函数的使用
    指针与数组
    软件推荐----RDO(Remote Desktop Organizer)
  • 原文地址:https://www.cnblogs.com/xxg1992/p/6636381.html
Copyright © 2020-2023  润新知