zoukankan      html  css  js  c++  java
  • uboot学习之uboot-spl的程序流程分析

    uboot-spl的程序流程主要包含下面的几个函数:

    _start->reset->save_boot_params->cpu_init_crit->lowlevel_init->_main->board_init_f

    在armv7架构的uboot-spl,主要需要做如下事情

      • 关闭中断,svc模式
      • 禁用MMU、TLB
      • 芯片级、板级的一些初始化操作 
        • IO初始化
        • 时钟
        • 内存
        • 选项,串口初始化
        • 选项,nand flash初始化
        • 其他额外的操作
      • 加载BL2,跳转到BL2

    下面详细分析一下它的过程。(注意,本人是以uboot2011的源码版本来分析的。)

    一、首先,通过uboot-spl编译脚本/u-boot/arch/arm/cpu/armv7/u-boot-spl.lds,程序如下:

    ...//前面省略
    OUTPUT_ARCH(arm)
    ENTRY(_start)
    SECTIONS
    {
        .text      :
        {
        __start = .;
          arch/arm/cpu/armv7/start.o    (.text)
          *(.text*)
        } >.sram
    
        . = ALIGN(4);
        .rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram
    
        . = ALIGN(4);
        .data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
        . = ALIGN(4);
        __image_copy_end = .;
        _end = .;
    
        .bss :
        {
            . = ALIGN(4);
            __bss_start = .;
            *(.bss*)
            . = ALIGN(4);
            __bss_end__ = .;
        } >.sdram
    }

    对于任何程序,入口函数是在链接时决定的,uboot的入口是由链接脚本决定的。uboot下armv7链接脚本默认目录为arch/arm/cpu/u-boot.lds。这个可以在配置文件中与CONFIG_SYS_LDSCRIPT来指定。

    入口地址也是由连接器决定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定。这个会在编译时加在ld连接器的选项-Ttext中。

    链接脚本中这些宏的定义在linkage.h中,看字面意思也明白,程序的入口是在_start.,后面是text段,data段等。

    所以uboot-spl的代码入口函数是_start。

    二、_start的定义在/u-boot/arch/arm/cpu/armv7/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                                /*  irq中断向量  */
        ldr   pc, _fiq                              /*  fiq中断向量  *
    #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 */
    
    .global _end_vect
    _end_vect:
    
        .balignl 16,0xdeadbeef          

    .global声明_start为全局符号,_start就会被连接器链接到,也就是链接脚本中的入口地址了。
    以上代码是设置arm的异常向量表,arm异常向量表如下:

     

    地址 

    异常 

    进入模式

    描述

    0x00000000 

    复位

    管理模式

    复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行

    0x00000004 

    未定义指令

    未定义模式

    遇到不能处理的指令时,产生未定义指令异常

    0x00000008

    软件中断

    管理模式

    执行SWI指令产生,用于用户模式下的程序调用特权操作指令

    0x0000000c

    预存指令

    中止模式

    处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常

    0x00000010

    数据操作

    中止模式

    处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常

    0x00000014

    未使用

    未使用

    未使用

    0x00000018

    IRQ

    IRQ

    外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常

    0x0000001c

    FIQ

    FIQ

    快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常


    8种异常分别占用4个字节,因此每种异常入口处都填写一条跳转指令,直接跳转到相应的异常处理函数中,reset异常是直接跳转到reset函数,其他7种异常是用ldr将处理函数入口地址加载到pc中。
    后面汇编是定义了7种异常的入口函数,这里没有定义CONFIG_SPL_BUILD,所以走后面一个。
    接下来定义的_end_vect中用.balignl来指定接下来的代码要16字节对齐,空缺的用0xdeadbeef,方便更加高效的访问内存。

    后面,开始声明中断起始地址:

    #ifdef CONFIG_USE_IRQ
    /* IRQ stack memory (calculated at run-time) */
    .globl IRQ_STACK_START
    IRQ_STACK_START:
        .word    0x0badc0de
    
    /* IRQ stack memory (calculated at run-time) */
    .globl FIQ_STACK_START
    FIQ_STACK_START:
        .word 0x0badc0de
    #endif
    
    /* IRQ stack memory (calculated at run-time) + 8 bytes */
    .globl IRQ_STACK_START_IN
    IRQ_STACK_START_IN:
        .word    0x0badc0de

    这里声明中断处理函数栈起始地址,给出的值是0x0badc0de,是一个非法值,注释也说明了,这个值会在运行时重新计算,代码是在interrupt_init中。

    /*
     * the actual reset code
     */
    
    reset:
        bl    save_boot_params
        /*
         * set the cpu to SVC32 mode
         */
    @    mov r0, #0
    @    cmp r0, #0
    @    beq reset
    here:
        mrs    r0, cpsr
        bic    r0, r0, #0x1f  /*工作模式位清零 */ 
        orr    r0, r0, #0xd3   /*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */
        msr    cpsr,r0
    /*以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。*/
    
    #if defined(CONFIG_ARM_A7)
    @set SMP bit
        mrc     p15, 0, r0, c1, c0, 1
        orr        r0, r0, #(1<<6)
        mcr        p15, 0, r0, c1, c0, 1
    #endif
    @#if defined(CONFIG_ARCH_SUN9IW1P1)
    @    ldr     r0, =0x008000e0
    @    ldr     r1, =0x16aa0001
    @    str     r1, [r0]
    @#endif

    在上电或者重启后,处理器取得第一条指令就是b reset,所以会直接跳转到reset函数处,而reset首先是跳转到save_boot_params中。

    三、save_boot_params的定义如下:

    void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)__attribute__((weak, alias("save_boot_params_default")));

    save_boot_params函数在/u-boot/arch/arm/cpu/armv7/cpu.c中定义,该函数什么都没做。
    这句话的意思:__attribute__((weak))将本模块的save_boot_params转成弱符号类型,如果该函数在其他地方没有定义,则为空函数。
    由于它没有定义,所以这是个空函数。

    接着,后面运行这段程序:

    #if defined(CONFIG_OMAP34XX)
        /* Copy vectors to mask ROM indirect addr */
        adr    r0, _start        @ r0 <- current position of code
        add    r0, r0, #4        @ skip reset vector
        mov    r2, #64            @ r2 <- size to copy
        add    r2, r0, r2        @ r2 <- source end address
        mov    r1, #SRAM_OFFSET0    @ build vect addr
        mov    r3, #SRAM_OFFSET1
        add    r1, r1, r3
        mov    r3, #SRAM_OFFSET2
        add    r1, r1, r3
    next:
        ldmia    r0!, {r3 - r10}        @ copy from source address [r0]
        stmia    r1!, {r3 - r10}        @ copy to   target address [r1]
        cmp    r0, r2            @ until source end address [r2]
        bne    next            @ loop until equal */
    #if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
        /* No need to copy/exec the clock code - DPLL adjust already done
         * in NAND/oneNAND Boot.
         */
        bl    cpy_clk_code        @ put dpll adjust code behind vectors
    #endif /* NAND Boot */
    #endif /* CONFIG_OMAP34XX */
        /* the mask ROM code should have PLL and others stable */
    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
        bl    cpu_init_crit
    #endif

    这里需要注意,ARM默认的异常向量表入口在0x0地址,uboot的运行介质(norflash nandflash sram等)映射地址可能不在0x0起始的地址,所以需要修改异常向量表入口。
    与网上的不同的是,这里没有对协处理器cp15的操作。
    在cpy_clk_code之前的汇编的作用如下:

    1. 初始化异常向量表
    2. 设置cpu svc模式,关中断
    3. 设置异常向量入口

    如果没有定义CONFIG_SYS_NAND_BOOT和CONFIG_SYS_ONENAND_BOOT,则进入cpy_clk_code。
    四、下面直接看cpu_init_crit。

    cpu_init_crit:
        /*
         * Invalidate L1 I/D
         */
        ...//省略详细的内容
    
        /*
         * disable MMU stuff and caches
         */
        ...//省略详细的内容
    
        /*
         * 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.
         */
        mov    ip, lr            @ persevere link reg across call
        bl    lowlevel_init        @ go setup pll,mux,memory
        mov    lr, ip            @ restore link
        mov    pc, lr            @ back to my caller
    #endif

    在禁止了L1 I/D、MMU之后,调用了lowlevel_init。他在/u-boot/arch/arm/cpu/armv7/lowlevel_init.S中有定义
    五、lowlevel_init函数

    它是与特定开发板相关的初始化函数,在这个函数里会做一些pll初始化,如果不是从mem启动,则会做memory初始化,方便后续拷贝到mem中运行。
    lowlevel_init函数则是需要移植来实现,做clk初始化以及ddr初始化
    从cpu_init_crit返回后,_start的工作就完成了,接下来就要调用_main,总结一下_start工作:

    1. 前面总结过的部分,初始化异常向量表,设置svc模式,关中断
    2. 初始化mmu cache tlb
    3. 板级初始化,pll memory初始化

    六、初始化栈指针sp为调用board_init_f做准备,这个过程一般是在_main中实现的。

    call_board_init_f:
        ldr    sp, =(CONFIG_SYS_INIT_SP_ADDR)
        bic    sp, sp, #7 /* 8-byte alignment for ABI compliance */
        ldr    r0,=0x00000000
    
        @bl  boot_standby_relocat
    
        bl    board_init_f

    首先进行堆栈的设置,然后就跳转到board_init_f函数,其中传递给该函数的参数为0。

    然后调用/u-boot/arch/arm/lib/board.c
    此时已经进入C语言的代码,spl就结束了。

    参考:

    http://blog.csdn.net/xiaohai1232/article/details/60775435#t0

    http://blog.csdn.net/skyflying2012/article/details/25804209

    http://blog.csdn.net/ooonebook/article/details/52957395

    https://wenku.baidu.com/view/254d6bc3d15abe23482f4dec.html

    https://wenku.baidu.com/view/eb73e1edb8f67c1cfad6b89b.html

    上一篇:http://www.cnblogs.com/yeqluofwupheng/p/7341989.html 

    下一篇:http://www.cnblogs.com/yeqluofwupheng/p/7355248.html

  • 相关阅读:
    00 学习资源整理
    07 MySQL的应用层调整,查询缓存设置,内存管理设置,并发参数的设置常识
    06 SQL语句编写优化
    05 Java的ReentrantLock与线程的顺序控制
    05 索引的使用常识(如何编写SQL语句避免索引失效)
    04 MYSQ的SQL优化需要了解的工具explain,profile,trace
    04 JAVA中park/unpark的原理以及JAVA在API层面线程状态总结
    03 MYSQL的体系结构以及存储引擎的基本知识
    02 链表编程题
    01 栈与队列
  • 原文地址:https://www.cnblogs.com/yeqluofwupheng/p/7347925.html
Copyright © 2011-2022 走看看