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之前的匯編的作用如下:
- 初始化異常向量表
- 設置cpu svc模式,關中斷
- 設置異常向量入口
如果沒有定義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工作:
- 前面總結過的部分,初始化異常向量表,設置svc模式,關中斷
- 初始化mmu cache tlb
- 板級初始化,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