經過了上一篇的配置,我們已經執行make就可以編譯出一個uboot.bin,但這還不夠,首先,此時的uboot並不符合三星晶元對bootloader的格式要求,其次,此時的uboot.bin也沒有結合我們的開發板進行配置,還無法使用。而要進行這樣的個性化配置,前提條件就是對uboot開機流程和編譯系 ...
經過了上一篇的配置,我們已經執行make就可以編譯出一個uboot.bin,但這還不夠,首先,此時的uboot並不符合三星晶元對bootloader的格式要求,其次,此時的uboot.bin也沒有結合我們的開發板進行配置,還無法使用。而要進行這樣的個性化配置,前提條件就是對uboot開機流程和編譯系統有所瞭解,本文主要討論前者。uboot是一個兩階段bootloader,第一階段主要做硬體直接相關的初始化,使用彙編編寫;第二階段主要為操作系統的運行準備環境,主要用C編寫,這裡以ARM平臺為例分析其啟動流程。下麵是啟動過程中主要涉及的文件
arch/arm/cpu/armv7/start.S
board/samsung/myboard/lowlevel_init.S
arch/arm/lib/crt0.S
arch/arm/lib/board.c
arch/samsung/myboard/myboard.c
第一階段
第一階段的主要文件和任務如下
arch/arm/cpu/armv7/start.S
1. 設置CPU為SVC模式
2. 關閉中斷,MMU,Cache
board/samsung/origen/lowlevel_init.S
3. 關閉看門狗
4. 初始化記憶體,串口
5. 設置棧
6. 代碼自搬移
7. 清BSS
8. 跳轉到C入口????
start.S
39 .globl _start
40 _start: b reset
41 ldr pc, _undefined_instruction
42 ldr pc, _software_interrupt
43 ldr pc, _prefetch_abort
44 ldr pc, _data_abort
45 ldr pc, _not_used
46 ldr pc, _irq
47 ldr pc, _fiq
--40--> 異常向量表設置
126 reset:
127 bl save_boot_params
131 mrs r0, cpsr
132 bic r0, r0, #0x1f
133 orr r0, r0, #0xd3
134 msr cpsr,r0
--126-->設置CPU為SVC模式
下麵這三行代碼非常重要,是整個uboot啟動過程的交叉點
154 bl cpu_init_cp15
155 bl cpu_init_crit
158 bl _main
--154-->跳轉執行cpu_init_cp15,即初始化CP15協處理器
--155-->跳轉執行cpu_init_crit,
--158-->跳轉執行_main,即第二階段
287 ENTRY(cpu_init_cp15)
291 mov r0, #0 @ set up for MCR
292 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
293 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
294 mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
295 mcr p15, 0, r0, c7, c10, 4 @ DSB
296 mcr p15, 0, r0, c7, c5, 4 @ ISB
297
301 mrc p15, 0, r0, c1, c0, 0
302 bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
303 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
304 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
305 orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
307 bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
311 mcr p15, 0, r0, c1, c0, 0
312 mov pc, lr @ back to my caller
313 ENDPROC(cpu_init_cp15)
--291-->關閉Cache
--301-->關閉MMU
324 ENTRY(cpu_init_crit)
331 b lowlevel_init @ go setup pll,mux,memory
332 ENDPROC(cpu_init_crit)
--331-->跳轉到lowlevel_init,位於board/samsung/origen/lowlevel_init.S,進行板級相關的設置。
lowlevel_init.S
這是位於目錄的初始化文件,主要完成特定開發板的初始化工作,包括時鐘、記憶體和串口等。
82 bl system_clock_init
85 bl mem_ctrl_asm_init
87 1:
88 /* for UART */
89 bl uart_asm_init
90 bl tzpc_init
91 pop {pc}
114 system_clock_init:
329 uart_asm_init:
357 tzpc_init:
--82-->初始化系統時鐘,即跳轉到114行
--85-->初始化系統記憶體
--89-->初始化UART串口,即跳轉到329行
--90-->初始化TrustZoneProtectorController,即跳轉到357行
執行完lowlevel_init.S,依據上面那三行代碼,執行流程就該回到start.S執行156行跳轉到_main,開始執行第二階段。
第二階段
從start.S跳轉到_main ,標緻著uboot啟動過程的第二階段的開始。在第二階段,核心文件是crt0.S,但我們最關心的是其中回調板級C程式的入口位置。第二階段的流程如下:
arch/arm/lib/crt0.S
1. 初始化C運行環境,調用board_init_f()
arch/arm/lib/board.c
1. board_init_f對全局信息GD結構體進行填充
arch/arm/lib/crt0.S
1. 代碼重定位
2. 代碼自搬移
3. 執行超迴圈
arch/arm/lib/board.c
1. board_init_r()是進入定製板目錄的入口
crt0.S
進入第二階段是首要任務就是準備C語言運行的環境:
96 _main:
102 #if defined(CONFIG_NAND_SPL)
103 /* deprecated, use instead CONFIG_SPL_BUILD */
104 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
105 #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
106 ldr sp, =(CONFIG_SPL_STACK)
107 #else
108 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
109 #endif
110 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
111 sub sp, #GD_SIZE /* allocate one GD above SP */
112 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
113 mov r8, sp /* GD is above SP */
114 mov r0, #0
115 bl board_init_f
_main
--104-->初始化SP,為C語言做準備
--110-->保存128B放GD結構體來存放全局信息,
--111-->GD的地址放在r8中,
--115-->跳轉到board_init_f(),這個整個初始化過程中第一次執行的C代碼
board.c
下麵這個函數就是uboot初始化過程中執行的第一個C函數,可以看作這個文件的入口函數。函數比較長,我就不逐句分析了,這個函數主要的作用就是執行一些高等級的初始化。其中最重要的就是準備全局信息GD結構體
209 typedef int (init_fnc_t) (void);
243 init_fnc_t *init_sequence[] = {
244 arch_cpu_init, /* basic arch cpu dependent setup */
245 mark_bootstage,
246 #ifdef CONFIG_OF_CONTROL
247 fdtdec_check_fdt,
...
277 void board_init_f(ulong bootflag)
278 {
...
291 gd->mon_len = _bss_end_ofs;
292 #ifdef CONFIG_OF_EMBED
293 /* Get a pointer to the FDT */
294 gd->fdt_blob = _binary_dt_dtb_start;
295 #elif defined CONFIG_OF_SEPARATE
296 /* FDT is at end of image */
297 gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE);
298 #endif
299 /* Allow the early environment to override the fdt address */
300 gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
301 (uintptr_t)gd->fdt_blob);
302
303 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
304 if ((*init_fnc_ptr)() != 0) {
305 hang ();
306 }
307 }
...
board_init_f()
--243--> 全局的函數指針數組,每個指針都是int (*ptr)(void)型的。
--291-->mon_len 通過鏈接腳本可以知道存放的是 uboot 代碼大小;
--294-->fdt_blob 存放設備數地址;
--303--遍歷函數指針數組init_sequence中的每一個成員,就是將數組中的每一個初始化函數都執行一次,這種寫法可以借鑒
crt0.S
函數board_init_f()返回後,繼續執行crt0.S中115行之後的部分,主要的工作是執行代碼自搬移,代碼重定位等工作,執行完這些之後,我們我們找到了最感興趣的下麵這幾句
163 /* call board_init_r(gd_t *id, ulong dest_addr) */
164 mov r0, r8 /* gd_t */
165 ldr r1, [r8, #GD_RELOCADDR] /* dest_addr */
166 /* call board_init_r */
167 ldr pc, =board_init_r /* this is auto-relocated! */
--167-->跳轉到board_init_r函數執行,這次跳出去這個文件的語句就執行完畢了,不會再回來了
board.c
這也是最後一次跳轉到這個文件了,執行額函數如下
519 void board_init_r(gd_t *id, ulong dest_addr)
520 {
521 ulong malloc_start;
522 #if !defined(CONFIG_SYS_NO_FLASH)
523 ulong flash_size;
524 #endif
525
526 gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
527 bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
528
529 monitor_flash_len = _end_ofs;
530
531 /* Enable caches */
532 enable_caches();
533
534 debug("monitor flash len: %08lX\n", monitor_flash_len);
535 board_init(); /* Setup chipselects */
...
650 /* set up exceptions */
651 interrupt_init();
652 /* enable exceptions */
653 enable_interrupts();
667 eth_initialize(gd->bd);
...
701 /* main_loop() can return to retry autoboot, if so just run it again. */
702 for (;;) {
703 main_loop();
704 }
705
board_init_r()
--532-->很多緊急工作都做完了,可以打開cache了
--535-->關鍵!!!這個就是我們苦苦尋找的板級定製文件的xxx.c的入口函數!!!
--651-->中斷初始化
--653-->使能中斷
--667-->網卡初始化,函數的實現在net/eth.c,會回調板級xxx.c中的board_eth_init()
--703-->執行超迴圈,主要功能是處理環境變數,解析命令,也就是uboot中和我們交互的命令的解析工作都在這裡執行!!!
main_loop()與啟動內核
main_loop()的實現在common/main.c,它的主要功能就是迴圈檢測輸入的命令並執行,其中一個環境變數bootdelay(自啟動)的設置決定了是否啟動內核,如果延時大於等於零,並且沒有在延時過程中接收到按鍵,則引導內核。ootloader 要想啟動內核,可以直接跳到內核的第一個指令處,即內核的起始地址,這樣便可以完成內核的啟動工作了。但是要想啟動內核還需要滿足下麵的一些條件,這些條件在Linux內核文檔"/Documentation/kernel-parameters.txt"中有說明,
1、cpu 寄存器設置
* R0 = 0
* R1 = 機器類型 id
* R2 = 啟動參數在記憶體中的起始地址
2、cpu 模式
* 禁止所有中斷
* 必須為 SVC(超級用戶)模式
3、Cache、MMU
* 關閉 MMU
* 指令 Cache 可以開啟或者關閉
* 數據 Cache 必須關閉
4、設備
* DMA 設備應當停止工作
5、PC 為內核的起始地址
關於uboot的啟動分析,本文只是冰山一角的一丟丟,不過希望通過我的這一堆廢話下來,能幫助你對uboot的啟動流程有一個整體的認識,當然,如果文中有錯誤,歡迎批評指正^-^