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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM