2014.4新版uboot啓動流程分析

辛苦之作,轉載請註明出處,謝謝!


最近開始接觸uboot,現在需要將2014.4版本uboot移植到公司armv7開發板。

在網上搜索講uboot啓動過程的文章,大多都是比較老版本的uboot,於是決定將新版uboot啓動過程記錄下來,和大家共享。

  1. #  
  2. # (C) Copyright 2000-2013  
  3. # Wolfgang Denk, DENX Software Engineering, [email protected].  
  4. #  
  5. # SPDX-License-Identifier:  GPL-2.0+  
  6. #  
  7.   
  8. VERSION = 2014   
  9. PATCHLEVEL = 04  
  10. SUBLEVEL =  
  11. EXTRAVERSION =  
  12. NAME =  
到我寫這篇文章之時,這個版本的uboot是最新版本。

2014.4版本uboot啓動至命令行幾個重要函數爲:_start,_main,board_init_f,relocate_code,board_init_r

一 _start

對於任何程序,入口函數是在鏈接時決定的,uboot的入口是由鏈接腳本決定的。uboot下armv7鏈接腳本默認目錄爲arch/arm/cpu/u-boot.lds。這個可以在配置文件中與CONFIG_SYS_LDSCRIPT來指定。

入口地址也是由連接器決定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定。這個會在編譯時加在ld連接器的選項-Ttext中

uboot的配置編譯原理也非常值得學習,我想在另外寫一篇文章來記錄,這裏不詳細說了。

查看u-boot.lds

  1. <span style="font-size:14px;">OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm")  
  2. OUTPUT_ARCH(arm)  
  3. ENTRY(_start)  
  4. SECTIONS  
  5. {  
  6.     . = 0x00000000;  
  7.   
  8.     . = ALIGN(4);  
  9.     .text :  
  10.     {     
  11.         *(.__image_copy_start)  
  12.         CPUDIR/start.o (.text*)  
  13.         *(.text*)  
  14.     }     
  15.   
  16.     . = ALIGN(4);  
  17.     .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }  
  18.   
  19.     . = ALIGN(4);  
  20.     .data : {   
  21.         *(.data*)  
  22.     }     
  23. </span>  
鏈接腳本中這些宏的定義在linkage.h中,看字面意思也明白,程序的入口是在_start.,後面是text段,data段等。

_start在arch/arm/cpu/armv7/start.S中,一段一段的分析,如下:

  1. <span style="font-size:14px;">.globl _start  
  2. _start: b   reset  
  3.     ldr pc, _undefined_instruction  
  4.     ldr pc, _software_interrupt  
  5.     ldr pc, _prefetch_abort  
  6.     ldr pc, _data_abort  
  7.     ldr pc, _not_used  
  8.     ldr pc, _irq  
  9.     ldr pc, _fiq  
  10. #ifdef CONFIG_SPL_BUILD  
  11. _undefined_instruction: .word _undefined_instruction  
  12. _software_interrupt:    .word _software_interrupt  
  13. _prefetch_abort:    .word _prefetch_abort  
  14. _data_abort:        .word _data_abort  
  15. _not_used:      .word _not_used  
  16. _irq:           .word _irq  
  17. _fiq:           .word _fiq  
  18. _pad:           .word 0x12345678 /* now 16*4=64 */  
  19. #else  
  20. .globl _undefined_instruction  
  21. _undefined_instruction: .word undefined_instruction  
  22. .globl _software_interrupt  
  23. _software_interrupt:    .word software_interrupt  
  24. .globl _prefetch_abort  
  25. _prefetch_abort:    .word prefetch_abort  
  26. .globl _data_abort  
  27. _data_abort:        .word data_abort  
  28. .globl _not_used  
  29. _not_used:      .word not_used  
  30. .globl _irq  
  31. _irq:           .word irq   
  32. .globl _fiq  
  33. _fiq:           .word fiq   
  34. _pad:           .word 0x12345678 /* now 16*4=64 */</span>  
  1. <span style="font-size:14px;">#endif  /* CONFIG_SPL_BUILD */  
  2.   
  3. .global _end_vect  
  4. _end_vect:  
  5.   
  6.     .balignl 16,0xdeadbeef  
  7. </span>  
.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,方便更加高效的訪問內存。接着分析下面一段代碼

  1. <span style="font-size:14px;">#ifdef CONFIG_USE_IRQ  
  2. /* IRQ stack memory (calculated at run-time) */  
  3. .globl IRQ_STACK_START  
  4. IRQ_STACK_START:  
  5.     .word   0x0badc0de  
  6.   
  7. /* IRQ stack memory (calculated at run-time) */  
  8. .globl FIQ_STACK_START  
  9. FIQ_STACK_START:  
  10.     .word 0x0badc0de  
  11. #endif  
  12.   
  13. /* IRQ stack memory (calculated at run-time) + 8 bytes */  
  14. .globl IRQ_STACK_START_IN  
  15. IRQ_STACK_START_IN:  
  16.     .word   0x0badc0de  
  17. </span>  

如果uboot中使用中斷,這裏聲明中斷處理函數棧起始地址,這裏給出的值是0x0badc0de,是一個非法值,註釋也說明了,這個值會在運行時重新計算,我查找了一下代碼是在interrupt_init中。

  1. <span style="font-size:14px;">reset:  
  2.     bl  save_boot_params  
  3.     /* 
  4.      * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode, 
  5.      * except if in HYP mode already 
  6.      */  
  7.     mrs r0, cpsr  
  8.     and r1, r0, #0x1f       @ mask mode bits  
  9.     teq r1, #0x1a       @ test for HYP mode  
  10.     bicne   r0, r0, #0x1f       @ clear all mode bits  
  11.     orrne   r0, r0, #0x13       @ set SVC mode  
  12.     orr r0, r0, #0xc0       @ disable FIQ and IRQ  
  13.     msr cpsr,r0  
  14. </span>  
在上電或者重啓後,處理器取得第一條指令就是b reset,所以會直接跳轉到reset函數處。reset首先是跳轉到save_boot_params中,如下:

  1. <span style="font-size:14px;">/************************************************************************* 
  2.  * 
  3.  * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3) 
  4.  *  __attribute__((weak)); 
  5.  * 
  6.  * Stack pointer is not yet initialized at this moment 
  7.  * Don't save anything to stack even if compiled with -O0 
  8.  * 
  9.  *************************************************************************/  
  10. ENTRY(save_boot_params)  
  11.     bx  lr          @ back to my caller  
  12. ENDPROC(save_boot_params)  
  13.     .weak   save_boot_params  
  14. </span>  
這裏save_boot_params函數中沒做什麼直接跳回,註釋也說明了,棧沒有初始化,最好不要再函數中做操作。

這裏值得注意的是.weak關鍵字,在網上找了到的解釋,我的理解是.weak相當於聲明一個函數,如果該函數在其他地方沒有定義,則爲空函數,有定義則調用該定義的函數。

具體解釋可以看這位大神的詳解:

http://blog.csdn.net/norains/article/details/5954459

接下來reset執行7條指令,修改cpsr寄存器,設置處理器進入svc模式,並且關掉irq和fiq。

  1. <span style="font-size:14px;">/* 
  2.  * Setup vector: 
  3.  * (OMAP4 spl TEXT_BASE is not 32 byte aligned. 
  4.  * Continue to use ROM code vector only in OMAP4 spl) 
  5.  */  
  6. #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))  
  7.     /* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */  
  8.     mrc p15, 0, r0, c1, c0, 0   @ Read CP15 SCTRL Register  
  9.     bic r0, #CR_V       @ V = 0  
  10.     mcr p15, 0, r0, c1, c0, 0   @ Write CP15 SCTRL Register  
  11.   
  12.     /* Set vector address in CP15 VBAR register */  
  13.     ldr r0, =_start  
  14.     mcr p15, 0, r0, c12, c0, 0  @Set VBAR  
  15. #endif  
  16.   
  17.     /* the mask ROM code should have PLL and others stable */  
  18. #ifndef CONFIG_SKIP_LOWLEVEL_INIT  
  19.     bl  cpu_init_cp15  
  20.     bl  cpu_init_crit  
  21. #endif  
  22.   
  23.     bl  _main  
  24. </span>  

前面6條彙編指令是對協處理器cp15進行操作,設置了處理器的異常向量入口地址爲_start,但是我在網上沒有找到cp15協處理器的c12寄存器的說明,可能是armv7新添加的

協處理器cp15的說明可以看我轉的一篇文章:

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

接下來如果沒有定義宏CONFIG_SKIP_LOWLEVEL_INIT,則會分別跳轉執行cpu_init_cp15以及cpu_init_crit。

在分析這2個函數之前先總結一下上面分析的這一段_start中彙編的作用:

1 初始化異常向量表   2 設置cpu svc模式,關中斷    3 配置cp15,設置異常向量入口 

都是跟異常有關的部分。

接下來先分析cpu_init_cp15

  1. <span style="font-size:14px;">/************************************************************************* 
  2.  * 
  3.  * cpu_init_cp15 
  4.  * 
  5.  * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless 
  6.  * CONFIG_SYS_ICACHE_OFF is defined. 
  7.  * 
  8.  *************************************************************************/  
  9. ENTRY(cpu_init_cp15)  
  10.     /* 
  11.      * Invalidate L1 I/D 
  12.      */  
  13.     mov r0, #0          @ set up for MCR  
  14.     mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs  
  15.     mcr p15, 0, r0, c7, c5, 0   @ invalidate icache  
  16.     mcr p15, 0, r0, c7, c5, 6   @ invalidate BP array  
  17.     mcr     p15, 0, r0, c7, c10, 4  @ DSB  
  18.     mcr     p15, 0, r0, c7, c5, 4   @ ISB  
  19.   
  20.     /* 
  21.      * disable MMU stuff and caches 
  22.      */  
  23.     mrc p15, 0, r0, c1, c0, 0  
  24.     bic r0, r0, #0x00002000 @ clear bits 13 (--V-)  
  25.     bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)  
  26.     orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align  
  27.     orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB  
  28. #ifdef CONFIG_SYS_ICACHE_OFF  
  29.     bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache  
  30. #else  
  31.     orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache  
  32. #endif  
  33.     mcr p15, 0, r0, c1, c0, 0  
  34. </span>  
  1. <span style="font-size:14px;">#ifdef CONFIG_ARM_ERRATA_716044  
  2.     mrc p15, 0, r0, c1, c0, 0   @ read system control register  
  3.     orr r0, r0, #1 << 11    @ set bit #11  
  4.     mcr p15, 0, r0, c1, c0, 0   @ write system control register  
  5. #endif  
  6.   
  7. #if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))  
  8.     mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
  9.     orr r0, r0, #1 << 4     @ set bit #4  
  10.     mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
  11. #endif  
  12.   
  13. #ifdef CONFIG_ARM_ERRATA_743622  
  14.     mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
  15.     orr r0, r0, #1 << 6     @ set bit #6  
  16.     mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
  17. #endif  
  18.   
  19. #ifdef CONFIG_ARM_ERRATA_751472  
  20.     mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
  21.     orr r0, r0, #1 << 11    @ set bit #11  
  22.     mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
  23. #endif  
  24. #ifdef CONFIG_ARM_ERRATA_761320  
  25.     mrc p15, 0, r0, c15, c0, 1  @ read diagnostic register  
  26.     orr r0, r0, #1 << 21    @ set bit #21  
  27.     mcr p15, 0, r0, c15, c0, 1  @ write diagnostic register  
  28. #endif  
  29.   
  30.     mov pc, lr          @ back to my caller  
  31. ENDPROC(cpu_init_cp15)  
  32. </span>  
cpu_init_cp15函數是配置cp15協處理器相關寄存器來設置處理器的MMU,cache以及tlb。如果沒有定義CONFIG_SYS_ICACHE_OFF則會打開icache。關掉mmu以及tlb。
具體配置過程可以對照cp15寄存器來看,這裏不詳細說了

接下來看cpu_init_crit

  1. <span style="font-size:14px;">/************************************************************************* 
  2.  * 
  3.  * CPU_init_critical registers 
  4.  * 
  5.  * setup important registers 
  6.  * setup memory timing 
  7.  * 
  8.  *************************************************************************/  
  9. ENTRY(cpu_init_crit)  
  10.     /* 
  11.      * Jump to board specific initialization... 
  12.      * The Mask ROM will have already initialized 
  13.      * basic memory. Go here to bump up clock rate and handle 
  14.      * wake up conditions. 
  15.      */  
  16.     b   lowlevel_init       @ go setup pll,mux,memory  
  17. ENDPROC(cpu_init_crit)  
  18. </span>  

看註釋可以明白,cpu_init_crit調用的lowlevel_init函數是與特定開發板相關的初始化函數,在這個函數裏會做一些pll初始化,如果不是從mem啓動,則會做memory初始化,方便後續拷貝到mem中運行。

lowlevel_init函數則是需要移植來實現,做clk初始化以及ddr初始化

從cpu_init_crit返回後,_start的工作就完成了,接下來就要調用_main,總結一下_start工作:

1 前面總結過的部分,初始化異常向量表,設置svc模式,關中斷

2 配置cp15,初始化mmu cache tlb

3 板級初始化,pll memory初始化

二 _main

_main函數在arch/arm/lib/crt0.S中,mian函數的作用在註釋中有詳細的說明,我們分段來分析一下

  1. ENTRY(_main)  
  2.   
  3. /* 
  4.  * Set up initial C runtime environment and call board_init_f(0). 
  5.  */  
  6.   
  7. #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)  
  8.     ldr sp, =(CONFIG_SPL_STACK)  
  9. #else  
  10.     ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)  
  11. #endif  
  12.     bic sp, sp, #7  /* 8-byte alignment for ABI compliance */  
  13.     sub sp, sp, #GD_SIZE    /* allocate one GD above SP */  
  14.     bic sp, sp, #7  /* 8-byte alignment for ABI compliance */  
  15.     mov r9, sp      /* GD is above SP */  
  16.     mov r0, #0  
  17.     bl  board_init_f  
首先將CONFIG_SYS_INIT_SP_ADDR定義的值加載到棧指針sp中,這個宏定義在配置頭文件中指定。

8字節對齊,然後減掉GD_SIZE,這個宏定義是指的全局結構體gd的大小,是160字節在此處,這個結構體用來保存uboot一些全局信息,需要一塊單獨的內存。

最後將sp保存在r9寄存器中。因此r9寄存器中的地址就是gd結構體的首地址。

在後面所有code中如果要使用gd結構體,必須在文件中加入DECLARE_GLOBAL_DATA_PTR宏定義,定義如下:

  1. #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")  
gd結構體首地址就是r9中的值。

接着說_main上面一段代碼,接着r0賦爲0,也就是參數0爲0,調用board_init_f

三 board_init_f

移植uboot先做一個最精簡版本,很多配置選項都沒有打開,比如fb mmc等硬件都默認不打開,只配置基本的ddr serial,這樣先保證uboot能正常啓動進入命令行,然後再去添加其他。

我們這裏分析就是按最精簡版本來,這樣可以更加簡潔的說明uboot的啓動流程。

board_init_f函數主要是根據配置對全局信息結構體gd進行初始化。

gd結構體中有個別成員意義我也不是很理解,這裏我只說我理解並且在後面起到作用的成員。

  1. gd->mon_len = (ulong)&__bss_end - (ulong)_start;  
初始化mon_len,代表uboot code的大小。

  1. for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {  
  2.     if ((*init_fnc_ptr)() != 0) {  
  3.         hang ();  
  4.     }  
  5. }  
遍歷調用init_sequence所有函數,init_sequence定義如下:

  1. init_fnc_t *init_sequence[] = {  
  2.     arch_cpu_init,      /* basic arch cpu dependent setup */  
  3.     mark_bootstage,  
  4. #ifdef CONFIG_OF_CONTROL  
  5.     fdtdec_check_fdt,  
  6. #endif  
  7. #if defined(CONFIG_BOARD_EARLY_INIT_F)  
  8.     board_early_init_f,  
  9. #endif  
  10.     timer_init,     /* initialize timer */  
  11. #ifdef CONFIG_BOARD_POSTCLK_INIT  
  12.     board_postclk_init,  
  13. #endif  
  14. #ifdef CONFIG_FSL_ESDHC  
  15.     get_clocks,  
  16. #endif  
  17.     env_init,       /* initialize environment */  
  18.     init_baudrate,      /* initialze baudrate settings */  
  19.     serial_init,        /* serial communications setup */  
  20.     console_init_f,     /* stage 1 init of console */  
  21.     display_banner,     /* say that we are here */  
  22.     print_cpuinfo,      /* display cpu info (and speed) */  
  23. #if defined(CONFIG_DISPLAY_BOARDINFO)  
  24.     checkboard,     /* display board info */  
  25. #endif  
  26. #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)  
  27.     init_func_i2c,  
  28. #endif  
  29.     dram_init,      /* configure available RAM banks */  
  30.     NULL,  
  31. };  
arch_cpu_init需要實現,要先啓動uboot這裏可以先寫一個空函數。

timer_init在lib/time.c中有實現,也是空函數,但是有__WEAK關鍵字,如果自己實現,則會調用自己實現的這個函數

對最精簡uboot,需要做好就是ddr和serial,所以我們最關心是serial_init,console_init_f以及dram_init.

先看serial_init

  1. <span style="font-size:14px;">int serial_init(void)  
  2. {  
  3.     return get_current()->start();  
  4. }  
  5. </span>  
  1. <span style="font-size:14px;">static struct serial_device *get_current(void)  
  2. {  
  3.     struct serial_device *dev;  
  4.   
  5.     if (!(gd->flags & GD_FLG_RELOC))  
  6.         dev = default_serial_console();  
  7.     else if (!serial_current)  
  8.         dev = default_serial_console();  
  9.     else  
  10.         dev = serial_current;  
  11.   
  12.     /* We must have a console device */  
  13.     if (!dev) {  
  14. #ifdef CONFIG_SPL_BUILD  
  15.         puts("Cannot find console\n");  
  16.         hang();  
  17. #else  
  18.         panic("Cannot find console\n");  
  19. #endif  
  20.     }  
  21.   
  22.     return dev;  
  23. }  
  24. </span>  
gd->flags還沒做初始化,serial_current用來存放我們當前要使用的serial,這裏也還沒做初始化,所以最終serial_device就是default_serial_console(),這個在serial驅動中有實現,來返回一個默認的調試串口。

serial_device結構體代表了一個串口設備,其中的成員都需要在自己的serial驅動中實現。

這樣在serial_init中get_current獲取就是串口驅動中給出的默認調試串口結構體,執行start,做一些特定串口初始化。

console_init_f將gd中have_console置1,這個函數不詳細說了。

display_banner,print_cpuinfo利用現在的調試串口打印了uboot的信息。

接下來就是dram_init。

dram_init對gd->ram_size初始化,以便board_init_f後面代碼對dram空間進行規劃。

dram_init實現可以通過配置文件定義宏定義來實現,也可以通過對ddrc控制器讀獲取dram信息。

繼續分析board_init_f,剩餘代碼將會對sdram空間進行規劃!

  1. <span style="font-size:14px;">#if defined(CONFIG_SYS_MEM_TOP_HIDE)  
  2.     /* 
  3.      * Subtract specified amount of memory to hide so that it won't 
  4.      * get "touched" at all by U-Boot. By fixing up gd->ram_size 
  5.      * the Linux kernel should now get passed the now "corrected" 
  6.      * memory size and won't touch it either. This should work 
  7.      * for arch/ppc and arch/powerpc. Only Linux board ports in 
  8.      * arch/powerpc with bootwrapper support, that recalculate the 
  9.      * memory size from the SDRAM controller setup will have to 
  10.      * get fixed. 
  11.      */  
  12.     gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;  
  13. #endif  
  14.   
  15.     addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize();  
  16. </span>  
CONFIG_SYS_MEM_TOP_HIDE宏定義是將一部分內存空間隱藏,註釋說明對於ppc處理器在內核中有接口來實現使用uboot提供的值,這裏咱們不考慮。

addr的值由CONFIG_SYS_SDRAM_BASE加上ram_size。也就是到了可用sdram的頂端。

  1. <span style="font-size:14px;">#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))  
  2.     /* reserve TLB table */  
  3.     gd->arch.tlb_size = PGTABLE_SIZE;  
  4.     addr -= gd->arch.tlb_size;  
  5.   
  6.     /* round down to next 64 kB limit */  
  7.     addr &= ~(0x10000 - 1);  
  8.   
  9.     gd->arch.tlb_addr = addr;  
  10.     debug("TLB table from %08lx to %08lx\n", addr, addr + gd->arch.tlb_size);  
  11. #endif  
  12.   
  13.     /* round down to next 4 kB limit */  
  14.     addr &= ~(4096 - 1);  
  15.     debug("Top of RAM usable for U-Boot at: %08lx\n", addr);  
  16. </span>  
如果打開了icache以及dcache,則預留出PATABLE_SIZE大小的tlb空間,tlb存放首地址賦值給gd->arch.tlb_addr。

最後addr此時值就是tlb的地址,4kB對齊。

  1. <span style="font-size:14px;">#ifdef CONFIG_LCD  
  2. #ifdef CONFIG_FB_ADDR  
  3.     gd->fb_base = CONFIG_FB_ADDR;  
  4. #else  
  5.     /* reserve memory for LCD display (always full pages) */  
  6.     addr = lcd_setmem(addr);  
  7.     gd->fb_base = addr;  
  8. #endif /* CONFIG_FB_ADDR */  
  9. #endif /* CONFIG_LCD */  
  10.   
  11.     /* 
  12.      * reserve memory for U-Boot code, data & bss 
  13.      * round down to next 4 kB limit 
  14.      */  
  15.     addr -= gd->mon_len;  
  16.     addr &= ~(4096 - 1);  
  17.   
  18.     debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);  
  19. </span>  
如果需要使用frambuffer,使用配置fb首地址CONFIG_FB_ADDR或者調用lcd_setmem獲取fb大小,這裏面有板級相關函數需要實現,不過爲了先能啓動uboot,沒有打開fb選項。addr值就是fb首地址。

gd->fb_base保存fb首地址。

接着-gd->mon_len爲uboot的code留出空間,到這裏addr的值就確定,addr作爲uboot relocate的目標addr。

到這裏,可以看出uboot現在空間劃分是從頂端往下進行的。

先總結一下addr之上sdram空間的劃分:

由高到低 : top-->hide mem-->tlb space(16K)-->framebuffer space-->uboot code space-->addr

接下來要確定addr_sp的值。

  1. <span style="font-size:14px;">#ifndef CONFIG_SPL_BUILD  
  2.     /* 
  3.      * reserve memory for malloc() arena 
  4.      */  
  5.     addr_sp = addr - TOTAL_MALLOC_LEN;  
  6.     debug("Reserving %dk for malloc() at: %08lx\n",  
  7.             TOTAL_MALLOC_LEN >> 10, addr_sp);  
  8.     /* 
  9.      * (permanently) allocate a Board Info struct 
  10.      * and a permanent copy of the "global" data 
  11.      */  
  12.     addr_sp -= sizeof (bd_t);  
  13.     bd = (bd_t *) addr_sp;  
  14.     gd->bd = bd;  
  15.     debug("Reserving %zu Bytes for Board Info at: %08lx\n",  
  16.             sizeof (bd_t), addr_sp);  
  17. </span>  

  1. <span style="font-size:14px;">#ifdef CONFIG_MACH_TYPE  
  2.     gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */  
  3. #endif  
  4.       
  5.     addr_sp -= sizeof (gd_t);  
  6.     id = (gd_t *) addr_sp;  
  7.     debug("Reserving %zu Bytes for Global Data at: %08lx\n",  
  8.             sizeof (gd_t), addr_sp);</span>  

  1. <span style="font-size:14px;">#ifndef CONFIG_ARM64  
  2.     /* setup stackpointer for exeptions */  
  3.     gd->irq_sp = addr_sp;  
  4. #ifdef CONFIG_USE_IRQ  
  5.     addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);  
  6.     debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",  
  7.         CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);  
  8. #endif  
  9.     /* leave 3 words for abort-stack    */  
  10.     addr_sp -= 12;  
  11.   
  12.     /* 8-byte alignment for ABI compliance */  
  13.     addr_sp &= ~0x07;  
  14. #else   /* CONFIG_ARM64 */  
  15.     /* 16-byte alignment for ABI compliance */  
  16.     addr_sp &= ~0x0f;  
  17. #endif  /* CONFIG_ARM64 */</span>  

首先預留malloc len,這裏我定義的是0x400000.

註釋中說明,爲bd,gd做一個永久的copy。

留出了全局信息bd_t結構體的空間,首地址存在gd->bd。

留出gd_t結構體的空間。首地址存在id中。
將此地址保存在gd->irq_sp中作爲異常棧指針。uboot中我們沒有用到中斷。

最後留出12字節,for abort stack,這個沒看懂。

到這裏addr_sp值確定,總結一下addr_sp之上空間分配。

由高到低 : addr-->malloc len(0x400000)-->bd len-->gd len-->12 byte-->addr_sp(棧往下增長,addr_sp之下空間作爲棧空間)

  1. <span style="font-size:14px;">    gd->bd->bi_baudrate = gd->baudrate;  
  2.     /* Ram ist board specific, so move it to board code ... */  
  3.     dram_init_banksize();  
  4.     display_dram_config();  /* and display it */  
  5.   
  6.     gd->relocaddr = addr;  
  7.     gd->start_addr_sp = addr_sp;  
  8.     gd->reloc_off = addr - (ulong)&_start;  
  9.     debug("relocation Offset is: %08lx\n", gd->reloc_off);  
  10.     if (new_fdt) {  
  11.         memcpy(new_fdt, gd->fdt_blob, fdt_size);  
  12.         gd->fdt_blob = new_fdt;  
  13.     }  
  14.     memcpy(id, (void *)gd, sizeof(gd_t));  
  15. </span>  
給bd->bi_baudrate賦值gd->baudrate,gd->baudrate是在前面baudrate_init中初始化。

dram_init_banksize()是需要實現的板級函數。根據板上ddrc獲取ddr的bank信息。填充在gd->bd->bi_dram[CONFIG_NR_DRAM_BANKS]。

gd->relocaaddr爲目標addr,gd->start_addr_sp爲目標addr_sp,gd->reloc_off爲目標addr和現在實際code起始地址的偏移。reloc_off非常重要,會作爲後面relocate_code函數的參數,來實現code的拷貝。

最後將gd結構體的數據拷貝到新的地址id上。

至此,board_init_f結束,回到_main

四 _main

board_init_f結束後,代碼如下:

  1. <span style="font-size:14px;">#if ! defined(CONFIG_SPL_BUILD)  
  2.   
  3. /* 
  4.  * Set up intermediate environment (new sp and gd) and call 
  5.  * relocate_code(addr_moni). Trick here is that we'll return 
  6.  * 'here' but relocated. 
  7.  */  
  8.   
  9.     ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */  
  10.     bic sp, sp, #7  /* 8-byte alignment for ABI compliance */  
  11.     ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */  
  12.     sub r9, r9, #GD_SIZE        /* new GD is below bd */  
  13.   
  14.     adr lr, here  
  15.     ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */  
  16.     add lr, lr, r0  
  17.     ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */  
  18.     b   relocate_code  
  19. here:  
  20.   
  21. </span>  
這段彙編很有意思,前4條彙編實現了新gd結構體的更新。

首先更新sp,並且將sp 8字節對齊,方便後面函數開闢棧能對齊,

然後獲取gd->bd地址到r9中,需要注意,在board_init_f中gd->bd已經更新爲新分配的bd了,下一條彙編將r9減掉bd的size,這樣就獲取到了board_init_f中新分配的gd了!

後面彙編則是爲relocate_code做準備,首先加載here地址,然後加上新地址偏移量給lr,則是code relocate後的新here了,relocate_code返回條轉到lr,則是新位置的here!

最後在r0中保存code的新地址,跳轉到relocate_code

五 relocate_code

relocate_code函數在arch/arm/lib/relocate.S中,這個函數實現了將uboot code拷貝到relocaddr。具體實現有空再詳細說明。

到這裏需要總結一下,經過上面的分析可以看出,

新版uboot在sdram空間分配上,是自頂向下,

不管uboot是從哪裏啓動,spiflash,nandflash,sram等跑到這裏code都會被從新定位到sdram上部的一個位置,繼續運行。

我找了一個2010.6版本的uboot大體看了一下啓動代碼,是通過判斷_start和TEXT_BASE(鏈接地址)是否相等來確定是否需要relocate。如果uboot是從sdram啓動則不需要relocate。

新版uboot在這方面還是有較大變動。

這樣變動我考慮好處可能有二,一是不用考慮啓動方式,all relocate code。二是不用考慮uboot鏈接地址,因爲都要重新relocate。

uboot sdram空間規劃圖:


六 _main

從relocate_code回到_main中,接下來是main最後一段代碼,如下:

  1. <span style="font-size:14px;">/* Set up final (full) environment */  
  2.   
  3.     bl  c_runtime_cpu_setup /* we still call old routine here */  
  4.   
  5.     ldr r0, =__bss_start    /* this is auto-relocated! */  
  6.     ldr r1, =__bss_end      /* this is auto-relocated! */  
  7.   
  8.     mov r2, #0x00000000     /* prepare zero to clear BSS */  
  9.   
  10. clbss_l:cmp r0, r1          /* while not at end of BSS */  
  11.     strlo   r2, [r0]        /* clear 32-bit BSS word */  
  12.     addlo   r0, r0, #4      /* move to next */  
  13.     blo clbss_l  
  14.   
  15.     bl coloured_LED_init  
  16.     bl red_led_on  
  17.   
  18.     /* call board_init_r(gd_t *id, ulong dest_addr) */  
  19.     mov     r0, r9                  /* gd_t */  
  20.     ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */  
  21.     /* call board_init_r */  
  22.     ldr pc, =board_init_r   /* this is auto-relocated! */  
  23.   
  24.     /* we should not return here. */  
  25.   
  26. </span>  
首先跳轉到c_runtime_cpu_setup,如下:

  1. <span style="font-size:14px;">ENTRY(c_runtime_cpu_setup)  
  2. /* 
  3.  * If I-cache is enabled invalidate it 
  4.  */  
  5. #ifndef CONFIG_SYS_ICACHE_OFF  
  6.     mcr p15, 0, r0, c7, c5, 0   @ invalidate icache  
  7.     mcr     p15, 0, r0, c7, c10, 4  @ DSB   
  8.     mcr     p15, 0, r0, c7, c5, 4   @ ISB   
  9. #endif  
  10. /* 
  11.  * Move vector table 
  12.  */  
  13.     /* Set vector address in CP15 VBAR register */  
  14.     ldr     r0, =_start  
  15.     mcr     p15, 0, r0, c12, c0, 0  @Set VBAR  
  16.   
  17.     bx  lr    
  18.   
  19. ENDPROC(c_runtime_cpu_setup)  
  20. </span>  
如果icache是enable,則無效掉icache,保證從sdram中更新指令到cache中。

接着更新異常向量表首地址,因爲code被relocate,所以異常向量表也被relocate。

從c_runtime_cpu_setup返回,下面一段彙編是將bss段清空。

接下來分別調用了coloured_LED_init以及red_led_on,很多開發板都會有led指示燈,這裏可以實現上電指示燈亮,有調試作用。

最後r0賦值gd指針,r1賦值relocaddr,進入最後的board_init_r !

七 board_init_r

參數1是新gd指針,參數2是relocate addr,也就是新code地址

  1. <span style="font-size:14px;">    gd->flags |= GD_FLG_RELOC;  /* tell others: relocation done */  
  2.     bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");  
  3.   
  4.     monitor_flash_len = (ulong)&__rel_dyn_end - (ulong)_start;  
  5.   
  6.     /* Enable caches */  
  7.     enable_caches();  
  8.   
  9.     debug("monitor flash len: %08lX\n", monitor_flash_len);  
  10.     board_init();   /* Setup chipselects */  
  11. </span>  

置位gd->flags,標誌已經relocate。monitor_flash_len這個變量作用沒看懂。使能cache,最後board_init是需要實現的板級支持函數。做開發板的基本初始化。

  1. <span style="font-size:14px;">#ifdef CONFIG_CLOCKS  
  2.     set_cpu_clk_info(); /* Setup clock information */  
  3. #endif  
  4.   
  5.     serial_initialize();  
  6.   
  7.     debug("Now running in RAM - U-Boot at: %08lx\n", dest_addr);  
  8. </span>  
如果打開CONFIG_CLOCKS,set_cpu_clk_info也是需要實現的板級支持函數。

重點來說一些serial_initialize,對於最精簡能正常啓動的uboot,serial和ddr是必須正常工作的。

實現在drivers/serial/serial.c中,如下:

  1. <span style="font-size:14px;">void serial_initialize(void)  
  2. {  
  3.     mpc8xx_serial_initialize();  
  4.     ns16550_serial_initialize();  
  5.     pxa_serial_initialize();  
  6.     s3c24xx_serial_initialize();  
  7.     s5p_serial_initialize();  
  8.     mpc512x_serial_initialize();。。。。  
  9.    mxs_auart_initialize();  
  10.     arc_serial_initialize();  
  11.     vc0718_serial_initialize();  
  12.   
  13.     serial_assign(default_serial_console()->name);  
  14. }</span>  
所有串口驅動都會實現一個xxxx_serial_initialize函數,並且添加到serial_initialize中,

xxxx_serial_initialize函數中是將所有需要的串口(用結構體struct serial_device表示,其中實現了基本的收 發 配置)調用serial_register註冊,serial_register如下:

  1. <span style="font-size:14px;">void serial_register(struct serial_device *dev)  
  2. {  
  3. #ifdef CONFIG_NEEDS_MANUAL_RELOC  
  4.     if (dev->start)  
  5.         dev->start += gd->reloc_off;  
  6.     if (dev->stop)  
  7.         dev->stop += gd->reloc_off;  
  8.     if (dev->setbrg)  
  9.         dev->setbrg += gd->reloc_off;  
  10.     if (dev->getc)  
  11.         dev->getc += gd->reloc_off;  
  12.     if (dev->tstc)  
  13.         dev->tstc += gd->reloc_off;  
  14.     if (dev->putc)  
  15.         dev->putc += gd->reloc_off;  
  16.     if (dev->puts)  
  17.         dev->puts += gd->reloc_off;  
  18. #endif  
  19.       
  20.     dev->next = serial_devices;  
  21.     serial_devices = dev;  
  22. }</span>  
就是將你的serial_dev加到全局鏈表serial_devices中。

可以想象,如果你有4個串口,則再你的串口驅動中分別定義4個serial device,並實現對應的收發配置,然後serial_register註冊者4個串口。
回到serial-initialize,最後調用serial_assign,default_serial_console我們之前說過,就是你在串口驅動給出一個默認調試串口,serial_assign如下:
  1. <span style="font-size:14px;">int serial_assign(const char *name)  
  2. {  
  3.     struct serial_device *s;  
  4.   
  5.     for (s = serial_devices; s; s = s->next) {  
  6.         if (strcmp(s->name, name))  
  7.             continue;  
  8.         serial_current = s;  
  9.         return 0;  
  10.     }  
  11.   
  12.     return -EINVAL;  
  13. }</span>  
serial_assign就是從serial_devices鏈表中找到指定的默認調試串口,條件就是串口的name,最後serial_current就是當前的默認串口了。

總結一下,serial_initialize工作是將所有serial驅動中所有串口註冊到serial_devices鏈表中,然後找到指定的默認串口。
  1. <span style="font-size:14px;">  /* The Malloc area is immediately below the monitor copy in DRAM */  
  2.     malloc_start = dest_addr - TOTAL_MALLOC_LEN;  
  3.     mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);</span>  

根據咱們之前board_init_f中的分析,relocate addr之下的部分就是malloc的預留空間了。這裏獲取malloc首地址malloc_start.
  1. <span style="font-size:14px;">void mem_malloc_init(ulong start, ulong size)  
  2. {     
  3.     mem_malloc_start = start;  
  4.     mem_malloc_end = start + size;  
  5.     mem_malloc_brk = start;  
  6.   
  7.     memset((void *)mem_malloc_start, 0, size);  
  8.   
  9.     malloc_bin_reloc();  
  10. }</span>  
mem_malloc_init中就是對malloc預留的空間初始化,起始地址,結束地址,清空。咱們已經relocate,malloc_bin_reloc中無操作了。

board_init_r接下來的代碼是做一些外設的初始化,比如mmc flash eth,環境變量的設置,還有中斷的使能等,這裏需要說一下是關於串口的2個函數,stdio_init和console_init_r.
看stdio_init代碼,我們只定義了serial,會調到serial_stdio_init,如下:
  1. <span style="font-size:14px;">void serial_stdio_init(void)  
  2. {  
  3.     struct stdio_dev dev;  
  4.     struct serial_device *s = serial_devices;  
  5.       
  6.     while (s) {  
  7.         memset(&dev, 0, sizeof(dev));  
  8.       
  9.         strcpy(dev.name, s->name);  
  10.         dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;  
  11.       
  12.         dev.start = s->start;  
  13.         dev.stop = s->stop;  
  14.         dev.putc = s->putc;  
  15.         dev.puts = s->puts;  
  16.         dev.getc = s->getc;  
  17.         dev.tstc = s->tstc;  
  18.   
  19.         stdio_register(&dev);  
  20.   
  21.         s = s->next;  
  22.     }  
  23. }</span>  
將serial_devices鏈表上所有serial device同樣初始化一個stdio_dev,flag爲output input,調用stdio-register,將stdio_dev添加到全局devs鏈表中。

可以想象,serial_stdio_init是在drivers/serial/serial.c中實現,uboot在這裏是利用的內核分層思想,drivers/serial下是特定serial驅動,分別調用serial_register註冊到serial_devices中,這可以說是通用的serial驅動層,

通用serial層調用serial-stdio-init將所有serial註冊到stdio device中,這就是通用的stdio層。

看來分層思想還是非常重要的!

board_init_r中調用完stdio_init後又調用了console_init_r,如下
  1. <span style="font-size:14px;">int console_init_r(void)  
  2. {  
  3.     struct stdio_dev *inputdev = NULL, *outputdev = NULL;  
  4.     int i;  
  5.     struct list_head *list = stdio_get_list();  
  6.     struct list_head *pos;  
  7.     struct stdio_dev *dev;  
  8.   
  9.     /* Scan devices looking for input and output devices */  
  10.     list_for_each(pos, list) {  
  11.         dev = list_entry(pos, struct stdio_dev, list);  
  12.   
  13.         if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {  
  14.             inputdev = dev;  
  15.         }  
  16.         if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {  
  17.             outputdev = dev;  
  18.         }  
  19.         if(inputdev && outputdev)  
  20.             break;  
  21.     }  
  22.   
  23.    if (outputdev != NULL) {  
  24.         console_setfile(stdout, outputdev);  
  25.         console_setfile(stderr, outputdev);  
  26.     }  
  27.   
  28.     /* Initializes input console */  
  29.     if (inputdev != NULL) {  
  30.         console_setfile(stdin, inputdev);  
  31.     }  
  32.   
  33. #ifndef CONFIG_SYS_CONSOLE_INFO_QUIET  
  34.     stdio_print_current_devices();  
  35. #endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */  
  36.   
  37.     /* Setting environment variables */  
  38.     for (i = 0; i < 3; i++) {  
  39.         setenv(stdio_names[i], stdio_devices[i]->name);  
  40.     }  
  41.   
  42.     gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */  
  43.   
  44.     return 0;  
  45. }</span>  
console_init_r前半部分很清楚了,從devs.list鏈表中查找flag爲output或者input的dev,如果只有serial之前註冊了stdio_dev,則outputdev inputdev都是咱們註冊的第一個serial。

之後調用console_setfile,如下:
  1. <span style="font-size:14px;">static int console_setfile(int file, struct stdio_dev * dev)  
  2. {  
  3.     int error = 0;  
  4.   
  5.     if (dev == NULL)  
  6.         return -1;  
  7.   
  8.     switch (file) {  
  9.     case stdin:  
  10.     case stdout:  
  11.     case stderr:  
  12.         /* Start new device */  
  13.         if (dev->start) {  
  14.             error = dev->start();  
  15.             /* If it's not started dont use it */  
  16.             if (error < 0)  
  17.                 break;  
  18.         }  
  19.   
  20.         /* Assign the new device (leaving the existing one started) */  
  21.         stdio_devices[file] = dev;  
  22.   
  23.         /* 
  24.          * Update monitor functions 
  25.          * (to use the console stuff by other applications) 
  26.          */  
  27.         switch (file) {  
  28.         case stdin:  
  29.             gd->jt[XF_getc] = dev->getc;  
  30.             gd->jt[XF_tstc] = dev->tstc;  
  31.             break;  
  32.         case stdout:  
  33.             gd->jt[XF_putc] = dev->putc;  
  34.             gd->jt[XF_puts] = dev->puts;  
  35.             gd->jt[XF_printf] = printf;  
  36.             break;  
  37.         }  
  38.   
  39.        break;  
  40.   
  41.     default:        /* Invalid file ID */  
  42.         error = -1;  
  43.     }  
  44.     return error;  
  45. }</span>  
首先運行設備的start,就是特定serial實現的start函數。然後將stdio_device放到stdio_devices全局數組中,這個數組3個成員,stdout,stderr,stdin。最後還會在gd中設一下操作函數。

在console_init_r中最後會改變gd中flag狀態,爲GD_FLG_DEVINIT。表示設備初始化完成。

board_init_r進行完板級初始化後最後進入死循環,打印命令行,等待命令輸入和解析。到這裏uboot的啓動過程就全部結束了!

上面用很大篇幅自下往上解釋uboot下serial到console的架構,那來看一下實際使用時由printf到最後serial輸出這個自上到下的流程吧。

首先來看printf,實現在common/console.c中如下:
  1. <span style="font-size:14px;">int printf(const char *fmt, ...)  
  2. {  
  3.     va_list args;  
  4.     uint i;  
  5.     char printbuffer[CONFIG_SYS_PBSIZE];  
  6.           
  7. #if !defined(CONFIG_SANDBOX) && !defined(CONFIG_PRE_CONSOLE_BUFFER)  
  8.     if (!gd->have_console)  
  9.         return 0;  
  10. #endif    
  11.       
  12.     va_start(args, fmt);  
  13.       
  14.     /* For this to work, printbuffer must be larger than 
  15.      * anything we ever want to print. 
  16.      */   
  17.     i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);  
  18.     va_end(args);  
  19.       
  20.     /* Print the string */  
  21.     puts(printbuffer);  
  22.     return i;  
  23. }</span>  
字符串的拼接跟一般printf實現一樣,最後調用puts,puts實現也在console.c中,如下:
  1. <span style="font-size:14px;">void puts(const char *s)  
  2. {  
  3. #ifdef CONFIG_SANDBOX  
  4.     if (!gd) {  
  5.         os_puts(s);  
  6.         return;  
  7.     }  
  8. #endif  
  9.   
  10. #ifdef CONFIG_SILENT_CONSOLE  
  11.     if (gd->flags & GD_FLG_SILENT)  
  12.         return;  
  13. #endif  
  14.   
  15. #ifdef CONFIG_DISABLE_CONSOLE  
  16.     if (gd->flags & GD_FLG_DISABLE_CONSOLE)  
  17.         return;  
  18. #endif  
  19.   
  20.     if (!gd->have_console)  
  21.         return pre_console_puts(s);  
  22.   
  23.     if (gd->flags & GD_FLG_DEVINIT) {  
  24.         /* Send to the standard output */  
  25.         fputs(stdout, s);  
  26.     } else {  
  27.         /* Send directly to the handler */  
  28.         serial_puts(s);  
  29.     }  
  30. }</span>  

gd->have_console在board_init_f的console_init_f中置位,flag的GD_FLG_DEVINIT則是在剛纔board_init_r中console_init_r最後置位。

如果GD_FLG_DEVINIT沒有置位,表明console沒有註冊,是在board_init_f之後,board_init_r執行完成之前,這時調用serial_puts,如下:

  1. <span style="font-size:14px;">void serial_puts(const char *s)  
  2. {  
  3.     get_current()->puts(s);  
  4. }       </span>  

直接調到serial.c中的函數,完全符合board_init_f中serial_init的配置,僅僅找到一個默認串口來使用,其他串口暫且不管。
如果GD_FLG_DEVINIT置位,表明console註冊完成。調用fputs,如下:

  1. <span style="font-size:14px;">void fputs(int file, const char *s)  
  2. {     
  3.     if (file < MAX_FILES)  
  4.         console_puts(file, s);  
  5. }     
  6.   
  7. static inline void console_puts(int file, const char *s)  
  8. {  
  9.     stdio_devices[file]->puts(s);  
  10. }</span>  
fputs調console_puts從全局stdio_devices中找到對應stdout對應的成員stdio_device,調用puts,最終也是會調用到特定serial的puts函數。


分析後總結一下:


可以看出,對於serial,uboot實現了一個2級初始化:


stage 1,僅初始化default console serial,printf到puts後會直接調用特定串口的puts函數,實現打印


stage 2,將所有serial註冊爲stdio_device,並挑出指定調試串口作爲stdio_devices的stdout stdin stderr。printf到puts後再到全局stdio_devices中找到對應stdio_device,調用stdio-device的puts,最終調用特定serial的puts,實現打印。


區分這2個stage,是利用gd的flag,GD_FLG_DEVINIT。


原作者:http://blog.csdn.net/skyflying2012/article/details/25804209

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章