1. 程式人生 > >U-boot初始化階段流程分析

U-boot初始化階段流程分析

U-boot的初始化主要分為兩個階段

第一階段:主要是SOC內部的初始化,板級的初始化比較少,所以移植的修改量比較小。此階段由組合語言編寫,程式碼主體分佈在start.S和lowlevel_init.S中。
其中start.S作為主幹,其主要流程為:
注:加粗的比較重要,和板級有點關係
1. 填充16位元組的校驗位
2. 設定異常向量表
3. 設定cpu為SVC模式
4. 禁用cach和mmu
5. 判斷啟動介質
6. 在SRAM中設定棧
7. 跳入 lowlevel_init.S
關看門狗
設定供電鎖存
判斷當前執行位置
初始化時鐘
初始化DDR


初始化串列埠(列印‘OK’)
8. 再次供電鎖存
9. 在DDR中設定棧
10. 通過引腳判斷啟動介質
11. 跳到movi.c
重定位
12. 跳入 lowlevel_init.S設定虛擬地址對映
13. 在DDR中合理的地方設定棧
14. 清bss段
15. 跳入board.c,到DDR中執行程式

第二階段:主要是板級的初始化,SOC內部的初始化比較小,移植的修改量主要在此。此階段由c語言編寫,程式碼主體分佈在board.c中

  1. 初始化全域性變數gd_t結構體
  2. 遍歷陣列執行所有的初始化函式
    cpu初始化(沒做事)
    開始板級初始化,配置網絡卡用到的GPIO、機器碼、記憶體傳參地址

    定時器的初始化
    環境變數判定
    設定波特率
    設定串列埠(沒做事)
    初始化控制檯(沒做事)
    顯示uboot的logo
    列印cpu的資訊
    確認開發板資訊
    確認DDR資訊
    列印DDR資訊
  3. 初始化堆
  4. 利用了linux的裝置驅動初始化mmc
  5. 環境變數重定位
  6. 往gd賦值ip地址
  7. 往gd賦值乙太網地址
  8. linux裝置驅動初始化
  9. 跳轉表初始化(沒做事)
  10. 控制檯的第二部分的初始化
  11. 中斷初始化(沒做事)
  12. 開發板上比較晚期的初始化(沒做事)
  13. 利用驅動初始化網絡卡
  14. 升級uboot功能(沒做事)

執行完初始化的兩個階段後就進入了uboot的命令列迴圈部分
個人感覺U-boot的初始化相對於其他部分來說,是真正和板級相關的,所以對於移植來講是比較重要的,畢竟上層的邏輯程式碼都是不用修改的…..

1.第一階段(start.S部分)

下面是簡要的程式碼流程節選分析

  • 由於在config.mk中為編譯器指定了標頭檔案搜尋路徑(即頂層目錄下的include資料夾),故標頭檔案將預設從頂層目錄下的include資料夾內搜尋
  • 這些標頭檔案其實都不是真正被包含的檔案,它們大多是在配置編譯階段產生的符號連結或者是具有符號連結功能的標頭檔案。總之,它們的功能都類似於符號連結,目的是讓uboot的原始碼更具靈活性和移植性
  • 比如第一句程式碼中的config.h,其實產生的效果是讓start.S包含了根目錄下的include/configs/x210_sd.h
#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h>   
  • 這一部分主要功能是在程式碼段最開始處放置16位元組的填充位,因為sd/nand啟動時需要16位元組的校驗頭,這個校驗頭是在編譯階段通過mkv210image.c中計算並填充的
  • 通過偽.word指令定義4個word(32位)的空間來在程式碼段最開始處佔16位元組
  • 這段空間的地址最後將位於BL1的起始地址之前,即IROM中0xD0020000至0xD0020010,而且並不作為BL1的一部分
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)  
    .word 0x2000
    .word 0x0
    .word 0x0
    .word 0x0
#endif
  • 這段程式碼是設定異常向量表
  • .globl是把_start這個標號全域性化,是編譯器的操作,並不是彙編指令
  • 標號_start代表uboot的第一句程式碼b reset的地址值,即$(TEXT_BASE)指定的重定位後DDR內的虛擬地址即0xc3e00000,但事實上在重定位前,這句程式碼的實際地址是cpu規定的BL1的起始地址0xD0020010(sram內)
  • 從b reset開始其實是在設定異常向量表,這句程式碼所處的位置是與異常向量表基地址偏移量為的0的地方。當復位異常發生時(開機也是復位異常),cpu會自動跳轉入異常表基址偏移量為0處執行復位異常程式
  • 其實uboot並沒有很細緻的處理各種異常,因為uboot比較簡單,作為載入程式並不是OS,所以沒有必要
.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                                    
    ldr pc, _fiq
  • 開機後程序正式從此開始
  • msr cpsr_c, #0xd3 功能是將IRQ和FIQ禁能,同時把cpu設為SVC模式。具體操作是將0xd3寫入cpsr的controlbits,msr是程式狀態暫存器讀寫指令,cpsr_c是指令集中的關鍵字,表示cpsr的controlbits
reset:                                              
    /*
     * set the cpu to SVC32 mode and IRQ & FIQ disable
     */
    @;mrs   r0,cpsr                                 /*前四句被註釋掉了*/
    @;bic   r0,r0,#0x1f
    @;orr   r0,r0,#0xd3
    @;msr   cpsr,r0
    msr cpsr_c, #0xd3       @ I & F disable, Mode: 0x13 - SVC
  • 這段開始是和cpu有關初始化相關程式碼,和板級沒有關係,故一般不會去改動
  • bl是帶返回的跳轉,此處跳轉去執行禁能L2 cache 相關的程式碼,然後返回。後面又重新整理L1 cache的和Dcache,再後面禁能了MMU功能。
  • 為什麼要禁能cache和mmu?Icache與Dcache用來加快cpu與記憶體之間資料與指令的傳輸速率,但在上電之初,記憶體初始化比cpu慢一拍,當cpu初始化了,但記憶體還沒準備好就讀記憶體,那麼會造成指令取址異常,所以在上電之初要關cache;至於mmu,純粹是初始化階段用不到,就把它關了
cpu_init_crit:
    一大堆無用的程式碼,就不貼了
    bl  disable_l2cache

    bl  set_l2cache_auxctrl_cycle

    bl  enable_l2cache

    mov r0, #0                  @ set up for MCR
    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs
    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache

    mrc p15, 0, r0, c1, c0, 0
    bic r0, r0, #0x00002000     @ clear bits 13 (--V-)
    bic r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
    orr r0, r0, #0x00000002     @ set bit 1 (--A-) Align
    orr r0, r0, #0x00000800     @ set bit 12 (Z---) BTB
    mcr     p15, 0, r0, c1, c0, 0

  • 這裡比較重要,是啟動介質的判斷。最開始從巨集內讀取啟動介質,然後把讀到的資料經過處理存放在r2暫存器中
  • 後面都是通過比較r2的值來判斷啟動介質,最後判斷得到當前的啟動介質是SD/MMC然後把巨集#BOOT_MMCSD寫入暫存器r3中,巨集的值為0x03
  • 再將啟動介質資訊再從暫存器r3中寫入INF_REG3_OFFSET暫存器
    /* Read booting information */      
    ldr r0, =PRO_ID_BASE        
    ldr r1, [r0,#OMR_OFFSET]
    bic r2, r1, #0xffffffc1

        中間一大堆就不貼了

    /* NAND BOOT */     
    cmp r2, #0x0        @ 512B 4-cycle
    moveq   r3, #BOOT_NAND

    cmp r2, #0x2        @ 2KB 5-cycle
    moveq   r3, #BOOT_NAND

    cmp r2, #0x4        @ 4KB 5-cycle   8-bit ECC
    moveq   r3, #BOOT_NAND
    ...
    /* SD/MMC BOOT */   
    cmp     r2, #0xc    
    moveq   r3, #BOOT_MMCSD 

    ldr r0, =INF_REG_BASE
    str r3, [r0, #INF_REG3_OFFSET]                  
  • 第一次設定棧,由於不設定棧的話無法使用巢狀bl跳轉指令,即雙層函式呼叫,因為只有一個LR暫存器,而後面的lowlevel_init就有雙層跳轉,故這裡開始設定SRAM中用的棧。這裡棧設定的地址並沒有按照s5pv210的推薦地址,不過也無關痛癢
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
    sub sp, sp, #12 /* set stack */
    mov fp, #0

2.第一階段(跳入lowlevel_init部分)

  • 本段功能是判斷復位的型別,復位分為好多種。如冷上電,休眠喚醒等
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
    ldr r1, [r0]
    bic r1, r1, #0xfff6ffff
    cmp r1, #0x10000
    beq wakeup_reset_pre
    cmp r1, #0x80000
    beq wakeup_reset_from_didle
  • 關看門狗
/* Disable Watchdog */                          
    ldr r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */
    mov r1, #0
    str r1, [r0]
  • 設定開發板的供電按鍵鎖存功能,注意是哪個gpio
/* PS_HOLD pin(GPH0_0) set to high */   
    ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
    ldr r1, [r0]
    orr r1, r1, #0x300  
    orr r1, r1, #0x1    
    str r1, [r0]
  • 本段的功能是檢測當前程式碼的執行位置。判斷是在SRAM中還是DDR中,即cpu是冷啟動還是休眠喚醒復位,從而來決定是否要跳過後面的時鐘、DDR的程式碼
  • 一開始把一個值賦給r0暫存器,這個值其實相當於mask掩碼,後面馬上會用到它
  • bic指令的功能是:將pc中的某些位清零(r0中為1的位清零),剩下一些特殊的bit位賦值給r1,其實有點類似PLC中ladderlogic的maskmove功能。相當於c中的r1=pc & ~(ff000fff)
  • 將_TEXT_BASE即uboot連結初始地址賦給r2。同理,將r2中的某些位清零(r0中為1的位清零),然後去比較r1和r2的值
  • 比較r1和r2,即比較的是連結初始地址和當前地址它們的特定位,這些特定位決定了當前程式碼的執行位置,如果當前地址和連結初始地址的特定位相同,那麼說明當前處於DDR,就調到標號1處執行後面的程式碼(即跳過時鐘、DDR初始化程式碼)
    ldr r0, =0xff000fff 
    bic r1, pc, r0      /* r0 <- current base addr of code */
    ldr r2, _TEXT_BASE  
    bic r2, r2, r0  
    cmp     r1, r2                
    beq     1f      
  • 跳轉到初始化時鐘的函式,執行然後返回
  • 跳轉到初始化memory(DDR)的函式,執行然後返回,其中,DMC0,DMC1等設定很重要,直接和板子上記憶體的大小和分佈有關,需要注意一下
  • 用bl指令跳轉到初始化串列埠的函式,執行然後返回,其中,列印了一個O,這個O可以作為除錯的幫助
  • 如果定義了CONFIG_ONENAND,則初始化onenand
  • 如果定義了CONFIG_NAND,則初始化nand
    bl system_clock_init    
/* Memory initialize */
    bl mem_ctrl_asm_init
1:
    /* for UART */
    bl uart_asm_init                            
    bl tzpc_init                                    /*這個不用去管它*/

#if defined(CONFIG_ONENAND)
    bl onenandcon_init                              
#endif
#if defined(CONFIG_NAND)
    /* simple init for NAND */
    bl nand_asm_init    
#endif
  • 在返回start.S前列印了’K’,與之前的’O’組成OK,這是uboot的第一條列印資訊,可以用來判斷lowlevel_init是否正常執行
  • 把之前儲存在棧中的lr值彈出到pc中,來返回到start.S
/* Print 'K' */
    ldr r0, =ELFIN_UART_CONSOLE_BASE                
    ldr r1, =0x4b4b4b4b                             
    str r1, [r0, #UTXH_OFFSET]
    pop {pc}                                        

3.第一階段(跳回start.S)

  • 又做了一遍供電按鍵鎖存設定……已經在lowlevel_init中做過了,這裡又重複了一遍
    ldr r0, =0xE010E81C  /* PS_HOLD_CONTROL register */
    ldr r1, =0x00005301  /* PS_HOLD output high */  
    str r1, [r0]
  • 為了即將執行的c程式做準備,這裡開始第二次設定棧,設置於剛在lowlevel_init中初始化的DDR中
  • 這裡將棧設定在_TEXT_PHY_BASE,即uboot的連結的真正實體地址。由於棧是滿減棧,所以緊挨著uboot放置也不會衝突
    /* get ready to call C functions */     
    ldr sp, _TEXT_PHY_BASE  /* setup temp stack pointer */
    sub sp, sp, #12         
    mov fp, #0          /* no previous frame, so fp=0 */
  • 再次檢測當前程式碼的執行位置。判斷是在SRAM中還是DDR中。原理和lowlevel_init中一模一樣
  • 如果當前地址和連結初始地址的特定位相同,那麼說明當前處於DDR,就跳過重定位程式碼
    ldr r0, =0xff000fff     
    bic r1, pc, r0      /* r0 <- current base addr of code */
    ldr r2, _TEXT_BASE      /* r1 <- original base addr in ram */
    bic r2, r2, r0      /* r0 <- current base addr of code */
    cmp     r1, r2                  /* compare r0, r1 */
    beq     after_copy      /* r0 == r1 then skip flash copy   */
  • 這一段區別於之前那段無用的啟動介質判斷,本段是通過引腳來判斷啟動介質的
  • 將一個值放入r0,該值其實是一個暫存器的地址,該暫存器裡的值可以判斷BL1是從SD/MMC哪個通道啟動的,將暫存器的值放入r1,這個暫存器的資訊在irom applicationnote裡有記載
  • 將0xEB200000這個值放入r2,該值的表示是2號方式啟動,並將暫存器的值和r2(0xEB200000)進行對比
  • 如果相同則說明BL1是2號方式啟動,跳轉入標號mmcsd_boot處執行mmc和sd重定位相關程式碼
    /* If BL1 was copied from SD/MMC CH2 */
    ldr r0, =0xD0037488                             
    ldr r1, [r0]                                
    ldr r2, =0xEB200000                             
    cmp r1, r2                                      
    beq     mmcsd_boot                              
  • 跳轉到mmc和sd的重定位函式,這個函式是c語言寫的,在根目錄下cpu/s5pc11x/movi.c,執行完後再返回
  • 然後跳轉到後面after_copy標號處,去執行重定位完了之後需要做的一些設定
    bl      movi_bl2_copy                           
    b       after_copy                              

4.第一階段之重定位(movi.c)

雖然movinand是mmc的一種,但是個人認為三星把檔名字取成movi.c還是不太妥當…

  • 定義一個型別,型別的名字是copy_sd_mmc_to_mem,根據分析,首先這個copy_sd_mmc_to_mem是一個指標;其次,這個指標指向的是一個函式,故copy_sd_mmc_to_mem是一個指向函式的指標型別;其中,函式的返回值為u32,引數為(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init)
  • 可知這個函式就是IROM中固化用來複制SD/mmc中的內容至任意地址的函式,u32型別的返回值代表了函式執行成功與否;引數channel代表sd/mmc啟動通道號(0-4);start_block是起始塊地址;block_size是複製的塊的個數;trg是複製操作的目的地址,一般是DDR內的地址;init一般給0,不用多管
typedef u32(*copy_sd_mmc_to_mem)                
(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
  • 首先讀取位於0xD0037488的暫存器值,再一次檢查啟動介質(啟動方式)……
  • 定義一個變數copy_bl2,其型別是指向某種特定型別函式的指標型別,即開頭定義的copy_sd_mmc_to_mem型別
  • 首先將數字sh0xD0037F98強制型別轉換成指向一個u32資料(固化在IROM中的一個函式的首地址)的指標
  • 簡而言之,就是說在cpu的0xD0037F98處(IROM內),存著一個函式的首地址,現在把這個首地址賦給一個指標,我們就能用這個指向函式的指標來呼叫這個函數了
void movi_bl2_copy(void)
{
    ulong ch;
#if defined(CONFIG_EVT1)                            
    ch = *(volatile u32 *)(0xD0037488);             
    copy_sd_mmc_to_mem copy_bl2 =                   
        (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
  • 判斷,如果通道是通道0啟動,則使用固化在cpu內部IROM中的重定位到連線在通道0上的啟動介質
  • 這些引數都是巨集,具體的定義和計算都在根目錄下/include/movi.h中,MOVI_BL2_POS是燒錄uboot時的扇區,MOVI_BL2_BLKCNT是uboot佔的扇區數,CFG_PHY_UBOOT_BASE為連結的實體地址

    u32 ret;
    if (ch == 0xEB000000) {                         
        ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
            CFG_PHY_UBOOT_BASE, 0);                 

5.第一階段(再次跳回start.S)

  • 這裡開始進行和虛擬地址對映相關的設定
  • TTB是TranslationTableBase即轉換表的基地址,轉換表是MMU將虛擬地址翻譯為實體地址的憑據,建立整個虛擬地址對映的關鍵就是建立轉換表,此表存放在記憶體中,工作時不需要軟體干涉
  • 只要將轉換表ttb的基地址放入cp15的c2暫存器,mmu就能自動使用虛擬地址對映,_mmu_table_base定義在start.S其值為標號mmu_table
  • 然後把轉換表ttb的基地址放入cp15的c2暫存器
  • 最後通過設定cp15的c1暫存器來開啟mmu,以實現虛擬地址對映和記憶體訪問許可權管理等功能
#if defined(CONFIG_ENABLE_MMU)                      
enable_mmu:                                         
    /* enable domain access */
    ldr r5, =0x0000ffff
    mcr p15, 0, r5, c3, c0, 0       @load domain access register 
    /* Set the TTB register */                      
    ldr r0, _mmu_table_base                         

    ldr r1, =CFG_PHY_UBOOT_BASE
    ldr r2, =0xfff00000
    bic r0, r0, r2
    orr r1, r0, r1
    mcr p15, 0, r1, c2, c0, 0               

    /* Enable the MMU */
mmu_on:
    mrc p15, 0, r0, c1, c0, 0
    orr r0, r0, #1
    mcr p15, 0, r0, c1, c0, 0           

#endif
    詳細的建表步驟(在lowlevel_init中)
    #ifdef CONFIG_MCP_SINGLE                    /*這裡開始設定虛擬對映轉換表,這裡使用的是段式對映模式,即以段(1MB)為單位進行對映*/
                                                /*因此表中的一個單元只能管1MB,整個4G記憶體需要建立4096個表單元,所以後面採用了迴圈的方法來建立*/
                                            /*查表的方法是:例如虛擬地址的高12位是x,則去查第x個表單元,該表單元的高12位就是實體地址的高12位,其實這一部分只要知道一些大概的原理即可,不必深究,等用到了再去看*/
    /* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b           /*.macro指令是彙編中巨集定義的意思,此帶參巨集將FL_SECTION_ENTRY base,ap,d,c,b定義成一個word大小的特定值*/
    .word (\base << 20) | (\ap << 10) | \       /*這個特定值就是轉換表的填充量,其中,引數base是映射出來的段地址的基地址,從第20位開始。*/
          (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)/*20位的大小正好是1MB,由此可知轉換表中未保留前20位,映射出來的地址之間間隔為1MB*/
                                                /*故這裡採用的是段式對映;繼續來分析引數,ap是訪問控制位,從第10位開始。d、c、b都是一些許可權位*/
.endm                                           /*.endm,即結束巨集定義*/
.section .mmudata, "a"
    .align 14
    // the following alignment creates the mmu table at address 0x4000.
    .globl mmu_table
mmu_table:
    .set __base,0                               /*設定變數__base值為0*/
    // Access for iRAM
    .rept 0x100                                 /*.rept 0x100相當於for迴圈,一共迴圈0x100次,所以這一塊程式碼建立了0x100(256)個轉換表單元*/
    FL_SECTION_ENTRY __base,3,0,0,0             /*利用剛才定義的帶參巨集建立轉換表的內容,變數__base和3,0,0,0作為引數*/
    .set __base,__base+1                        /*相當於base=base+1*/
    .endr                                       /*.endr對應.rept,表示迴圈體的結束*/

    // Not Allowed
    .rept 0x200 - 0x100                         /*把.rept重設為0x100,這裡寫成0x200 - 0x100是為了好理解,表示這裡要填充第0x200到0x100的表單元*/
    .word 0x00000000                            /*把全0填充進了這256個轉換表單元.....*/
    .endr

    .set __base,0x200                           /*這裡開始填充第0x600 - 0x200的表單元,原理和前0x100個一模一樣*/
    // should be accessed
    .rept 0x600 - 0x200
    FL_SECTION_ENTRY __base,3,0,1,1
    .set __base,__base+1
    .endr

    .rept 0x800 - 0x600                         /*把0x800 - 0x600的表單元填充全0,即禁止使用這些記憶體空間*/
    .word 0x00000000
    .endr

    .set __base,0x800                           /*後面的填充方法都和前面一樣*/
    // should be accessed
    .rept 0xb00 - 0x800
    FL_SECTION_ENTRY __base,3,0,0,0
    .set __base,__base+1
    .endr

由此可見整張轉換表的設定

輸入虛擬地址 輸出的實體地址 長度
0-10000000 0-10000000 256MB
20000000-60000000 20000000-60000000 1GB
60000000-80000000 0 512MB
80000000-b0000000 80000000-b0000000 768MB
b0000000-c0000000 b0000000-c0000000 256MB
c0000000-d0000000 30000000-40000000 256MB
d-完 d-完 768MB

此表僅僅將c0000000開頭的256MB對映到了DMC0的30000000開頭的256MB實體記憶體上去了
其他的虛擬地址空間根本沒動,還是原樣對映的。所以uboot的連結地址(c3e開頭的)其實是33e開頭的地址

  • 第三次設定棧,仍然設在DDR中。雖然之前已經在DDR中設過一次,但是是緊挨著uboot存放的,這個位置不合理
  • 所以本次將棧設定uboot連結地址上方2MB處,這個位置合理、緊湊、安全
stack_setup:                                        
#if defined(CONFIG_MEMORY_UPPER_CODE)               
    ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
  • 清bss段,_bss_start和_bss_end是連結指令碼中定義的
  • 利用迴圈清零
clear_bss:                                          
    ldr r0, _bss_start      /* find start of bss segment        */
    ldr r1, _bss_end        /* stop here                        */
    mov     r2, #0x00000000     /* clear     */     
clbss_l:                                            
    str r2, [r0]        /* clear loop...  */        
    add r0, r0, #4                                  
    cmp r0, r1
    ble clbss_l                                     
  • 從SRAM中遠跳轉到DDR中函式start_armboot處,start_armboot定義在根目錄下/lib_arm/board.c中
  • 至此,uboot第一階段結束,進入第二階段,至DDR處執行
ldr pc, _start_armboot                                                                      

6.第二階段

  • 首先第一句定義一個返回值為int,引數為void的函式型別,命名為init_fnc_t
  • init_sequence是一個數組,元素是指向init_fnc_t型別函式的指標,這些函式都是用來初始化各個功能的函式,先將這個陣列初始化
typedef int (init_fnc_t) (void);

init_fnc_t *init_sequence[] = {     
    cpu_init,       /* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
    reloc_init,     /* Set the relocation done flag, must
                   do this AFTER cpu_init(), but as soon
                   as possible */
#endif
    board_init,     /* basic board dependent setup */
    interrupt_init,     /* set up exceptions */
    env_init,       /* initialize environment */
    init_baudrate,      /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */
    display_banner,     /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,      /* display cpu info (and speed) */
    ...
  • init_fnc_t是本檔案開始定義的一個函式型別typedef int (init_fnc_t) (void),且init_fnc_ptr是一個二重指標。這種情況一般是建立了一個函式指標陣列,不難想象,init_fnc_ptr指向了陣列的首地址,陣列中每個元素都是指向函式的指標
  • 在後面,我們將用這個指標來遍歷陣列,從而執行各個初始化函式
void start_armboot (void)
{
    init_fnc_t **init_fnc_ptr;
  • 本段功能是初始化全域性變數結構體,gd的含義是globledata的意思,gd_t是儲存全集變數的結構體
  • gd是這麼被定義的:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm (“r8”),故gd是指向gd_t結構體的指標,register 表示儘量讓cpu放在暫存器中,以提高其讀寫速度 asm (“r8”)是指定放在暫存器的r8中
  • 最後將gd_t這個結構體的地址賦給gd這個指標,以後就用指標gd來訪問全域性變量了
ulong gd_base;
    gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
    gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif                  
    gd = (gd_t*)gd_base;
  • 這是全域性變數結構體的型別宣告,在global_data.h中
  • 結構體中的bd指向的是一個和gd_t相仿的結構體——bd_t,裡面存放的都是和硬體有關的全域性變數

typedef struct  global_data {
    bd_t        *bd;//bd是一個指向bd_t型別連結串列的指標,bd_t連結串列的內容是和板級有關的全域性變數
    unsigned long   flags;//某個標誌位
    unsigned long   baudrate;//串列埠波特率
    unsigned long   have_console;   /* serial_init() was called */ //標誌位,表示是否使用了控制檯的
    unsigned long   reloc_off;  /* Relocation Offset *///重定位有關的偏移量
    unsigned long   env_addr;   /* Address  of Environment struct *///環境變數結構體的偏移量
    unsigned long   env_valid;  /* Checksum of Environment valid? *///標誌位,表示是否能使用記憶體中的環境變數
    unsigned long   fb_base;    /* base address of frame buffer *///幀快取基地址,和顯示有關
#ifdef CONFIG_VFD
    unsigned char   vfd_type;   /* display type */
#endif
#if 0
    unsigned long   cpu_clk;    /* CPU clock in Hz!     */
    unsigned long   bus_clk;
    phys_size_t ram_size;   /* RAM size */
    unsigned long   reset_status;   /* reset status register at boot */
#endif
    void        **jt;       /* jump table *///跳轉表,基本沒用的
} gd_t;
  • 這段把gd_t結構體和bd_t結構體全部清零初始化
  • 並且分配了空間,把gd下面的一段空間分配給結構體bd_t(板級有關的全域性變數),同時把此結構體地址賦給bd這個指標。由於sizeof的返回值是以位元組為單位的純數字,所以gd也要強制型別轉換成指向位元組大小空間的指標
    memset ((void*)gd, 0, sizeof (gd_t));
    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    memset (gd->bd, 0, sizeof (bd_t));
  • 其實這整個for迴圈是為了執行所有的初始化函式。上面這行利用函式指標執行遍歷到的函式,並判斷返回值是否為0(函式執行發生錯誤),如果返回值為0,則執行掛起程式,列印失敗資訊,然後進入死迴圈….
  • 由416行定義可知,init_sequence是一個數組,元素是指向init_fnc_t型別函式的指標。現在將其首地址賦給指標init_fnc_ptr。*init_fnc_ptr的值是陣列元素(函式首地址),第一句的判斷其實可以理解為*init_fnc_ptr!=NULL,由於這個陣列定義的末尾元素就是NULL,所以*init_fnc_ptr的值可以判斷這個陣列是否已經遍歷到了末尾
  • cpu_init,第一個函式是cpu_init,但cpu初始化之前都已經全部結束了,所以該函式為空…
  • board_init,開始板級初始化,配置網絡卡用到的GPIO、機器碼、記憶體傳參地址
  • interrupt_init,這個函式名字起得不太好,其實是定時器的初始化,和中斷無關
  • env_init,環境變數的初始化,由於環境變數還沒從啟動介質中取到DDR中,故此處的初始化只是對DDR中的環境變數進行一個簡單的判定,真正的初始化在start_armboot裡面
  • init_baudrate,單純波特率,並不設定串列埠
  • serial_init,設定串列埠,這個函式什麼都沒做,因為在彙編階段串列埠已經被初始化過了
  • console_init_f,初始化控制檯,名字中的_f表示這是第一階段的初始化,由於第二階段的初始化之前需要夾雜一些前提程式碼,故將在start_armboot執行
  • display_banner,用來通過串列埠控制檯顯示uboot的logo
  • print_cpuinfo,列印cpu的資訊
  • checkboard, 確認開發板資訊
  • dram_init,dram(DDR)的初始化,由於硬體層面已經在之前搞定了,這函式只是通過巨集來獲取DDR相關資訊
  • display_dram_config,列印上面一個函式獲得的DDR配置資訊
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 
        if ((*init_fnc_ptr)() != 0) {                                       
            hang ();
        }           
    }
  • 配置堆記憶體,設定堆區的起始地址,和gd_base的設定方法相同
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
    mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else
    mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif
  • 定義了CONFIG_GENERIC_MMC,開始配置mmc
  • 列印SD/MMC相關的資訊程式碼,具體容量的列印在 mmc_initialize函式中
  • 呼叫mmc初始化函式,它先用cpu級別的初始化函式做個引子,再利用了linux的裝置驅動來正式初始化
#if defined(CONFIG_X210)

    #if defined(CONFIG_GENERIC_MMC)
        puts ("SD/MMC:  ");
        mmc_exist = mmc_initialize(gd->bd);
        if (mmc_exist != 0)
        {
            puts ("0 MB\n");
  • 這裡開始是環境變數的重定位,將環境變數從啟動介質中讀到DDR內,利用了驅動來讀,環境變數的位置是通過原始分割槽資訊表中讀到的
    env_relocate ();
  • 從重定位之後的環境變數中獲取ip地址,放到bd中的全域性變數內,以供使用
  • ip地址是由4個0-255的陣列成,它恰好能放到一個32位的unsigned long中,這也是getenv_IPaddr的返回格式,故恰好可以放在變數bi_ip_addr內
/* IP Address */
    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
  • 用getenv_r獲取乙太網地址,並放到tmp內,i是返回值
  • 若返回值大於0,則表示成功,把tmp賦給s。若不大於0則失敗,賦為NULL,由於乙太網地址比較差,故要存在陣列中。用迴圈放進去
  • 最後放到bd中的全域性變數內,以供使用
/* MAC Address */                           //網絡卡的MAC(乙太網)地址
    {
        int i;
        ulong reg;
        char *s, *e;
        char tmp[64];

        i = getenv_r ("ethaddr", tmp, sizeof (tmp));
        s = (i > 0) ? tmp : NULL;

        for (reg = 0; reg < 6; ++reg) {
            gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
然後是一些雜七雜八的函式
devices_init ();    /* get the devices list going. *///裝置初始化,這裡的裝置指的是裝置驅動,從linux中移植而來

#ifdef CONFIG_CMC_PU2
    load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */

    jumptable_init ();//跳轉表初始化,其實沒用到...
#if !defined(CONFIG_SMDK6442)
    console_init_r ();  /* fully init console as a device *///控制檯的第二部分的初始化,有實質性的功能
#endif

#if defined(CONFIG_MISC_INIT_R)
    /* miscellaneous platform dependent initialisations */
    misc_init_r ();
#endif

    /* enable exceptions */
    enable_interrupts ();//中斷初始化,其實這是個空函式,u-boot一般用不到中斷
    board_late_init ();//開發板上比較晚期的初始化,實際是空函式


    eth_initialize(gd->bd);//利用驅動初始化網絡卡
    extern void update_all(void);//這裡開始做升級uboot相關的內容....沒用到...

  • 至此,U-boot初始化階段結束,開始進入控制檯主迴圈