U-Boot啓動過程徹底分析

1.1       U-Boot工做過程 linux

 

U-Boot啓動內核的過程能夠分爲兩個階段,兩個階段的功能以下: 數組

       1)第一階段的功能 數據結構

Ø  硬件設備初始化 ide

Ø  加載U-Boot第二階段代碼到RAM空間 函數

Ø  設置好棧 oop

Ø  跳轉到第二階段代碼入口 fetch

       2)第二階段的功能 ui

Ø  初始化本階段使用的硬件設備 this

Ø  檢測系統內存映射 spa

Ø  將內核從Flash讀取到RAM

Ø  爲內核設置啓動參數

Ø  調用內核

1.1.1             U-Boot啓動第一階段代碼分析

       第一階段對應的文件是cpu/arm920t/start.Sboard/samsung/mini2440/lowlevel_init.S

       U-Boot啓動第一階段流程以下:

 

 2.1 U-Boot啓動第一階段流程

 

       根據cpu/arm920t/u-boot.lds中指定的鏈接方式:

ENTRY(_start)

SECTIONS

{

       . = 0x00000000;

 

       . = ALIGN(4);

       .text :

       {

                     cpu/arm920t/start.o    (.text)

                board/samsung/mini2440/lowlevel_init.o (.text)

                 board/samsung/mini2440/nand_read.o (.text)

              *(.text)

       }

       … …

}

       第一個連接的是cpu/arm920t/start.o,所以u-boot.bin的入口代碼在cpu/arm920t/start.o中,其源代碼在cpu/arm920t/start.S中。下面咱們來分析cpu/arm920t/start.S的執行。

1.      硬件設備初始化

1)設置異常向量

       cpu/arm920t/start.S開頭有以下的代碼:

.globl _start

_start:    b     start_code                         /* 復位 */

       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中斷向量  */

/*  中斷向量表入口地址 */

_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

 

       .balignl 16,0xdeadbeef

 

       以上代碼設置了ARM異常向量表,各個異常向量介紹以下:

 2.1 ARM異常向量表

地址 

異常 

進入模式

描述

0x00000000 

復位

管理模式

復位電平有效時,產生復位異常,程序跳轉到復位處理程序處執行

0x00000004 

未定義指令

未定義模式

遇到不能處理的指令時,產生未定義指令異常

0x00000008

軟件中斷

管理模式

執行SWI指令產生,用於用戶模式下的程序調用特權操做指令

0x0000000c

預存指令

停止模式

處理器預取指令的地址不存在,或該地址不容許當前指令訪問,產生指令預取停止異常

0x00000010

數據操做

停止模式

處理器數據訪問指令的地址不存在,或該地址不容許當前指令訪問時,產生數據停止異常

0x00000014

未使用

未使用

未使用

0x00000018

IRQ

IRQ

外部中斷請求有效,且CPSR中的I位爲0時,產生IRQ異常

0x0000001c

FIQ

FIQ

快速中斷請求引腳有效,且CPSR中的F位爲0時,產生FIQ異常

       cpu/arm920t/start.S中還有這些異常對應的異常處理程序。當一個異常產生時,CPU根據異常號在異常向量表中找到對應的異常向量,而後執行異常向量處的跳轉指令,CPU就跳轉到對應的異常處理程序執行。

       其中復位異常向量的指令「b start_code」決定了U-Boot啓動後將自動跳轉到標號「start_code」處執行。

2CPU進入SVC模式

start_code:

       /*

        * set the cpu to SVC32 mode

        */

       mrs r0, cpsr

       bic  r0, r0, #0x1f        /*工做模式位清零 */

       orr   r0, r0, #0xd3              /*工做模式位設置爲「10011」(管理模式),並將中斷禁止位和快中斷禁止位置1 */

       msr cpsr, r0

       以上代碼將CPU的工做模式位設置爲管理模式,並將中斷禁止位和快中斷禁止位置一,從而屏蔽了IRQFIQ中斷。

3)設置控制寄存器地址

# if defined(CONFIG_S3C2400)

#  define pWTCON 0x15300000

#  define INTMSK  0x14400008

#  define CLKDIVN      0x14800014

#else      /* s3c2410s3c2440下面4個寄存器地址相同 */

#  define pWTCON 0x53000000               /* WATCHDOG控制寄存器地址 */

#  define INTMSK  0x4A000008                     /* INTMSK寄存器地址  */

#  define INTSUBMSK 0x4A00001C      /* INTSUBMSK寄存器地址 */

#  define CLKDIVN      0x4C000014                   /* CLKDIVN寄存器地址 */

# endif

       對與s3c2440開發板,以上代碼完成了WATCHDOGINTMSKINTSUBMSKCLKDIVN四個寄存器的地址的設置。各個寄存器地址參見參考文獻[4] 

4)關閉看門狗

       ldr   r0, =pWTCON

       mov       r1, #0x0

       str   r1, [r0]   /* 看門狗控制器的最低位爲0時,看門狗不輸出復位信號 */

       以上代碼向看門狗控制寄存器寫入0,關閉看門狗。不然在U-Boot啓動過程當中,CPU將不斷重啓。

5)屏蔽中斷

       /*

        * mask all IRQs by setting all bits in the INTMR - default

        */

       mov       r1, #0xffffffff     /* 某位被置1則對應的中斷被屏蔽 */

       ldr   r0, =INTMSK

       str   r1, [r0]

       INTMSK是主中斷屏蔽寄存器,每一位對應SRCPND中斷源引腳寄存器)中的一位,代表SRCPND相應位表明的中斷請求是否被CPU所處理

         根據參考文獻4INTMSK寄存器是一個32位的寄存器,每位對應一箇中斷,向其中寫入0xffffffff就將INTMSK寄存器所有位置一,從而屏蔽對應的中斷。

# if defined(CONFIG_S3C2440)

          ldr  r1, =0x7fff      

          ldr  r0, =INTSUBMSK

          str  r1, [r0]

# endif

       INTSUBMSK每一位對應SUBSRCPND中的一位,代表SUBSRCPND相應位表明的中斷請求是否被CPU所處理

       根據參考文獻4INTSUBMSK寄存器是一個32位的寄存器,可是隻使用了低15位。向其中寫入0x7fff就是將INTSUBMSK寄存器所有有效位(低15位)置一,從而屏蔽對應的中斷。

6)設置MPLLCON,UPLLCON, CLKDIVN

# if defined(CONFIG_S3C2440) 

#define MPLLCON   0x4C000004

#define UPLLCON   0x4C000008  

          ldr  r0, =CLKDIVN  

          mov  r1, #5

          str  r1, [r0]

 

          ldr  r0, =MPLLCON

          ldr  r1, =0x7F021 

          str  r1, [r0]

 

    ldr  r0, =UPLLCON 

          ldr  r1, =0x38022

          str  r1, [r0]

# else

       /* FCLK:HCLK:PCLK = 1:2:4 */

       /* default FCLK is 120 MHz ! */

       ldr   r0, =CLKDIVN

       mov       r1, #3

       str   r1, [r0]

#endif

       CPU上電幾毫秒後,晶振輸出穩定,FCLK=Fin(晶振頻率),CPU開始執行指令。但實際上,FCLK能夠高於Fin,爲了提升系統時鐘,須要用軟件來啓用PLL。這就須要設置CLKDIVNMPLLCONUPLLCON3個寄存器。

       CLKDIVN寄存器用於設置FCLKHCLKPCLK三者間的比例,能夠根據表2.2來設置。

 2.2 S3C2440 CLKDIVN寄存器格式

CLKDIVN

說明

初始值

HDIVN

[2:1]

00 : HCLK = FCLK/1.

01 : HCLK = FCLK/2.

10 : HCLK = FCLK/4 (當 CAMDIVN[9] = 0 時)

HCLK= FCLK/8  (當 CAMDIVN[9] = 1 時)

11 : HCLK = FCLK/3 (當 CAMDIVN[8] = 0 時)

HCLK = FCLK/6 (當 CAMDIVN[8] = 1時)

00

PDIVN

[0]

0: PCLK = HCLK/1   1: PCLK = HCLK/2

0

 

       設置CLKDIVN5,就將HDIVN設置爲二進制的10,因爲CAMDIVN[9]沒有被改變過,取默認值0,所以HCLK = FCLK/4PDIVN被設置爲1,所以PCLK= HCLK/2。所以分頻比FCLK:HCLK:PCLK = 1:4:8 

       MPLLCON寄存器用於設置FCLKFin的倍數。MPLLCON的位[19:12]稱爲MDIV,位[9:4]稱爲PDIV,位[1:0]稱爲SDIV

       對於S3C2440FCLKFin的關係以下面公式:

       MPLL(FCLK) = (2×m×Fin)/(p×)

       其中: m=MDIC+8p=PDIV+2s=SDIV

       MPLLCONUPLLCON的值能夠根據參考文獻4「PLL VALUE SELECTION TABLE」設置。該表部分摘錄以下:

 2.3 推薦PLL

輸入頻率

輸出頻率

MDIV

PDIV

SDIV

12.0000MHz

48.00 MHz

56(0x38)

2

2

12.0000MHz

405.00 MHz

127(0x7f)

2

1

       mini2440系統主頻設置爲405MHZUSB時鐘頻率設置爲48MHZ時,系統能夠穩定運行,所以設置MPLLCONUPLLCON爲:

       MPLLCON=(0x7f<<12) | (0x02<<4) | (0x01) = 0x7f021

       UPLLCON=(0x38<<12) | (0x02<<4) | (0x02) = 0x38022

7)關閉MMUcache

       接着往下看:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

       bl    cpu_init_crit

#endif

       cpu_init_crit這段代碼在U-Boot正常啓動時才須要執行,若將U-BootRAM中啓動則應該註釋掉這段代碼。

       下面分析一下cpu_init_crit到底作了什麼:

320  #ifndef CONFIG_SKIP_LOWLEVEL_INIT

321  cpu_init_crit:

322      /*

323       * 使數據cache與指令cache無效 */

324       */ 

325      mov       r0, #0

326      mcr p15, 0, r0, c7, c7, 0    /* c7寫入0將使ICacheDCache無效*/

327      mcr p15, 0, r0, c8, c7, 0    /* c8寫入0將使TLB失效 */

328 

329      /*

330       * disable MMU stuff and caches

331       */

332      mrc p15, 0, r0, c1, c0, 0    /*  讀出控制寄存器到r0  */

333      bic  r0, r0, #0x00002300   @ clear bits 13, 9:8 (--V- --RS)

334      bic  r0, r0, #0x00000087   @ clear bits 7, 2:0 (B--- -CAM)

335      orr   r0, r0, #0x00000002   @ set bit 2 (A) Align

336      orr   r0, r0, #0x00001000   @ set bit 12 (I) I-Cache

337      mcr p15, 0, r0, c1, c0, 0    /*  保存r0到控制寄存器  */

338 

339      /*

340       * before relocating, we have to setup RAM timing

341       * because memory timing is board-dependend, you will

342       * find a lowlevel_init.S in your board directory.

343       */

344      mov       ip, lr

345 

346      bl    lowlevel_init

347 

348      mov       lr, ip

349      mov       pc, lr

350  #endif /* CONFIG_SKIP_LOWLEVEL_INIT */

       代碼中的c0c1c7c8都是ARM920T的協處理器CP15的寄存器。其中c7cache控制寄存器,c8TLB控制寄存器。325~327行代碼將0寫入c7c8,使CacheTLB內容無效。

       332~337行代碼關閉了MMU。這是經過修改CP15c1寄存器來實現的,先看CP15c1寄存器的格式(僅列出代碼中用到的位):

 2.3 CP15c1寄存器格式(部分)

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

.

.

V

I

.

.

R

S

B

.

.

.

.

C

A

M

       各個位的意義以下:

V :  表示異常向量表所在的位置,0:異常向量在0x000000001:異常向量在 0xFFFF0000
I :  0 
:關閉ICaches:開啓ICaches
R
S : 用來與頁表中的描述符一塊兒肯定內存的訪問權限
B :  0 
CPU爲小字節序; CPU爲大字節序
C :  0
:關閉DCaches1:開啓DCaches
A :  0
:數據訪問時不進行地址對齊檢查;1:數據訪問時進行地址對齊檢查
M :  0
:關閉MMU1:開啓MMU

       332~337行代碼將c1 M位置零,關閉了MMU

8)初始化RAM控制寄存器

       其中的lowlevel_init就完成了內存初始化的工做,因爲內存初始化是依賴於開發板的,所以lowlevel_init的代碼通常放在board下面相應的目錄中。對於mini2440lowlevel_initboard/samsung/mini2440/lowlevel_init.S中定義以下:

45  #define BWSCON   0x48000000        /* 13個存儲控制器的開始地址 */

  … …

129  _TEXT_BASE:

130      .word     TEXT_BASE

131 

132  .globl lowlevel_init

133  lowlevel_init:

134      /* memory control configuration */

135      /* make r0 relative the current location so that it */

136      /* reads SMRDATA out of FLASH rather than memory ! */

137      ldr     r0, =SMRDATA

138      ldr   r1, _TEXT_BASE

139      sub  r0, r0, r1              /* SMRDATA _TEXT_BASE就是13個寄存器的偏移地址 */

140      ldr   r1, =BWSCON   /* Bus Width Status Controller */

141      add     r2, r0, #13*4

142  0:

143      ldr     r3, [r0], #4    /*13個寄存器的值逐一賦值給對應的寄存器*/

144      str     r3, [r1], #4

145      cmp     r2, r0

146      bne     0b

147 

148      /* everything is fine now */

149      mov       pc, lr

150 

151      .ltorg

152  /* the literal pools origin */

153 

154  SMRDATA:            /*  下面是13個寄存器的值  */

155  .word  … …

156   .word  … …

 … …

       lowlevel_init初始化了13個寄存器來實現RAM時鐘的初始化。lowlevel_init函數對於U-BootNAND FlashNOR Flash啓動的狀況都是有效的。

       U-Boot.lds連接腳本有以下代碼:

       .text :

       {

                     cpu/arm920t/start.o    (.text)

                board/samsung/mini2440/lowlevel_init.o (.text)

                 board/samsung/mini2440/nand_read.o (.text)

              … …

       }

       board/samsung/mini2440/lowlevel_init.o將被連接到cpu/arm920t/start.o後面,所以board/samsung/mini2440/lowlevel_init.o也在U-Boot的前4KB的代碼中。

       U-BootNAND Flash啓動時,lowlevel_init.o將自動被讀取到CPU內部4KB的內部RAM中。所以第137~146行的代碼將從CPU內部RAM中複製寄存器的值到相應的寄存器中。

       對於U-BootNOR Flash啓動的狀況,因爲U-Boot鏈接時肯定的地址是U-Boot在內存中的地址,而此時U-Boot還在NOR Flash中,所以還須要在NOR Flash中讀取數據到RAM中。

       因爲NOR Flash的開始地址是0,而U-Boot的加載到內存的起始地址是TEXT_BASESMRDATA標號在Flash的地址就是SMRDATATEXT_BASE

       綜上所述,lowlevel_init的做用就是將SMRDATA開始的13個值複製給開始地址[BWSCON]13個寄存器,從而完成了存儲控制器的設置。

9)複製U-Boot第二階段代碼到RAM

       cpu/arm920t/start.S原來的代碼是隻支持從NOR Flash啓動的,通過修改如今U-BootNOR FlashNAND Flash上都能啓動了,實現的思路是這樣的:

 

       bl    bBootFrmNORFlash /*  判斷U-Boot是在NAND Flash仍是NOR Flash啓動  */

       cmp       r0, #0          /*  r0存放bBootFrmNORFlash函數返回值,若返回0表示NAND Flash啓動,不然表示在NOR Flash啓動  */

       beq nand_boot         /*  跳轉到NAND Flash啓動代碼  */

 

/*  NOR Flash啓動的代碼  */

       b     stack_setup         /* 跳過NAND Flash啓動的代碼 */

 

nand_boot:

/*  NAND Flash啓動的代碼  */

 

stack_setup:       

       /* 其餘代碼 */

 

       其中bBootFrmNORFlash函數做用是判斷U-Boot是在NAND Flash啓動仍是NOR Flash啓動,若在NOR Flash啓動則返回1,不然返回0根據ATPCS規則,函數返回值會被存放在r0寄存器中,所以調用bBootFrmNORFlash函數後根據r0的值就能夠判斷U-BootNAND Flash啓動仍是NOR Flash啓動bBootFrmNORFlash函數在board/samsung/mini2440/nand_read.c中定義以下:

int bBootFrmNORFlash(void)

{

    volatile unsigned int *pdw = (volatile unsigned int *)0;

    unsigned int dwVal;

  

    dwVal = *pdw;         /* 先記錄下原來的數據 */

    *pdw = 0x12345678;

    if (*pdw != 0x12345678)       /* 寫入失敗,說明是在NOR Flash啓動 */

    {

        return 1;     

    }

    else                                   /* 寫入成功,說明是在NAND Flash啓動 */

    {

        *pdw = dwVal;        /* 恢復原來的數據 */

        return 0;

    }

}

     不管是從NOR Flash仍是從NAND Flash啓動,地址0處爲U-Boot的第一條指令「 b    start_code」

       對於從NAND Flash啓動的狀況,其開始4KB的代碼會被自動複製到CPU內部4K內存中,所以能夠經過直接賦值的方法來修改。

       對於從NOR Flash啓動的狀況,NOR Flash的開始地址即爲0,必須經過必定的命令序列才能向NOR Flash中寫數據,因此能夠根據這點差異來分辨是從NAND Flash仍是NOR Flash啓動:向地址0寫入一個數據,而後讀出來,若是發現寫入失敗的就是NOR Flash,不然就是NAND Flash

       下面來分析NOR Flash啓動部分代碼:

208      adr  r0, _start              /* r0 <- current position of code   */

209      ldr   r1, _TEXT_BASE            /* test if we run from flash or RAM */

 

/* 判斷U-Boot是不是下載到RAM中運行,如果,則不用 再複製到RAM中了,這種狀況一般在調試U-Boot時才發生 */

210      cmp      r0, r1      /*_start等於_TEXT_BASE說明是下載到RAM中運行 */

211      beq stack_setup

212  /* 如下直到nand_boot標號前都是NOR Flash啓動的代碼 */

213      ldr   r2, _armboot_start

214      ldr   r3, _bss_start

215      sub  r2, r3, r2              /* r2 <- size of armboot            */

216      add r2, r0, r2              /* r2 <- source end address         */

217  /* 搬運U-Boot自身到RAM*/

218  copy_loop:

219      ldmia     r0!, {r3-r10} /* 從地址爲[r0]NOR Flash中讀入8個字的數據 */

220      stmia      r1!, {r3-r10} /* r3r10寄存器的數據複製給地址爲[r1]的內存 */

221      cmp       r0, r2                    /* until source end addreee [r2]    */

222      ble  copy_loop

223      b     stack_setup         /* 跳過NAND Flash啓動的代碼 */

       下面再來分析NAND Flash啓動部分代碼:

nand_boot:

    mov r1, #NAND_CTL_BASE 

    ldr r2, =( (7<<12)|(7<<8)|(7<<4)|(0<<0) )

    str r2, [r1, #oNFCONF]   /* 設置NFCONF寄存器 */

 

       /* 設置NFCONT,初始化ECC/解碼器,禁止NAND Flash片選 */

    ldr r2, =( (1<<4)|(0<<1)|(1<<0) )

    str r2, [r1, #oNFCONT] 

 

    ldr r2, =(0x6)           /* 設置NFSTAT */

str r2, [r1, #oNFSTAT]

 

       /* 復位命令,第一次使用NAND Flash前復位 */

    mov r2, #0xff           

    strb r2, [r1, #oNFCMD]

    mov r3, #0              

 

    /* 爲調用C函數nand_read_ll準備堆棧 */

    ldr sp, DW_STACK_START  

    mov fp, #0              

    /* 下面先設置r0r2,而後調用nand_read_ll函數將U-Boot讀入RAM */

    ldr r0, =TEXT_BASE      /* 目的地址:U-BootRAM的開始地址 */

    mov r1, #0x0               /* 源地址:U-BootNAND Flash中的開始地址 */

    mov r2, #0x30000          /* 複製的大小,必須比u-boot.bin文件大,而且必須是NAND Flash塊大小的整數倍,這裏設置爲0x30000192KB */

    bl  nand_read_ll                 /* 跳轉到nand_read_ll函數,開始複製U-BootRAM */

tst  r0, #0x0                     /* 檢查返回值是否正確 */

beq stack_setup

bad_nand_read:

loop2: b loop2    //infinite loop

 

.align 2

DW_STACK_START: .word STACK_BASE+STACK_SIZE-4

       其中NAND_CTL_BASEoNFCONF等在include/configs/mini2440.h中定義以下:

#define NAND_CTL_BASE  0x4E000000  // NAND Flash控制寄存器基址

 

#define STACK_BASE  0x33F00000     //base address of stack

#define STACK_SIZE  0x8000         //size of stack

 

#define oNFCONF  0x00      /* NFCONF相對於NAND_CTL_BASE偏移地址 */

#define oNFCONT  0x04      /* NFCONT相對於NAND_CTL_BASE偏移地址*/

#define oNFADDR  0x0c     /* NFADDR相對於NAND_CTL_BASE偏移地址*/

#define oNFDATA  0x10      /* NFDATA相對於NAND_CTL_BASE偏移地址*/

#define oNFCMD   0x08     /* NFCMD相對於NAND_CTL_BASE偏移地址*/

#define oNFSTAT  0x20        /* NFSTAT相對於NAND_CTL_BASE偏移地址*/

#define oNFECC   0x2c              /* NFECC相對於NAND_CTL_BASE偏移地址*/

       NAND Flash各個控制寄存器的設置在S3C2440的數據手冊有詳細說明,這裏就不介紹了。

       代碼中nand_read_ll函數的做用是在NAND Flash中搬運U-BootRAM,該函數在board/samsung/mini2440/nand_read.c中定義。

       NAND Flash根據page大小可分爲2種: 512B/page2048B/page的。這兩種NAND Flash的讀操做是不一樣的。所以就須要U-Boot識別到NAND Flash的類型,而後採用相應的讀操做,也就是說nand_read_ll函數要能自動適應兩種NAND Flash

       參考S3C2440的數據手冊能夠知道:根據NFCONF寄存器的Bit3AdvFlash (Read only))和Bit2 PageSize (Read only))能夠判斷NAND Flash的類型。Bit2Bit3NAND Flashblock類型的關係以下表所示:

 2.4 NFCONFBit3Bit2NAND Flash的關係

Bit2   Bit3

0

1

0

256 B/page

512 B/page

1

1024 B/page

2048 B/page

 

       因爲的NAND Flash只有512B/page2048 B/page這兩種,所以根據NFCONF寄存器的Bit3便可區分這兩種NAND Flash了。

       完整代碼見board/samsung/mini2440/nand_read.c中的nand_read_ll函數,這裏給出僞代碼:

int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)

{

//根據NFCONF寄存器的Bit3來區分2NAND Flash

       if( NFCONF & 0x8 )        /* Bit1,表示是2KB/pageNAND Flash */

       {

              ////////////////////////////////////

              讀取2K block NAND Flash

              ////////////////////////////////////

 

       }

       else                      /* Bit0,表示是512B/pageNAND Flash */

       {

              /////////////////////////////////////

              讀取512B block NAND Flash

              /////////////////////////////////////

 

       }

    return 0;

}

10)設置堆棧

       /*  設置堆棧 */

stack_setup:

       ldr   r0, _TEXT_BASE            /* upper 128 KiB: relocated uboot   */

       sub  r0, r0, #CONFIG_SYS_MALLOC_LEN   /* malloc area              */

       sub  r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /*  跳過全局數據區               */

#ifdef CONFIG_USE_IRQ

       sub  r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub  sp, r0, #12           /* leave 3 words for abort-stack    */

       只要將sp指針指向一段沒有被使用的內存就完成棧的設置了。根據上面的代碼能夠知道U-Boot內存使用狀況了,以下圖所示:

 

 

2.2 U-Boot內存使用狀況

 

11)清除BSS

clear_bss:

       ldr   r0, _bss_start              /* BSS段開始地址,在u-boot.lds中指定*/

       ldr   r1, _bss_end               /* BSS段結束地址,在u-boot.lds中指定*/

       mov       r2, #0x00000000

clbss_l:str     r2, [r0]          /* bss段清零*/

       add r0, r0, #4

       cmp      r0, r1

       ble  clbss_l

       初始值爲0,無初始值的全局變量,靜態變量將自動被放在BSS段。應該將這些變量的初始值賦爲0,不然這些變量的初始值將是一個隨機的值,如有些程序直接使用這些沒有初始化的變量將引發未知的後果。

12)跳轉到第二階段代碼入口

       ldr   pc, _start_armboot

 

_start_armboot:   .word  start_armboot

       跳轉到第二階段代碼入口start_armboot處。

1.1.2             U-Boot啓動第二階段代碼分析

       start_armboot函數在lib_arm/board.c中定義,是U-Boot第二階段代碼的入口。U-Boot啓動第二階段流程以下:

 

 2.3 U-Boot第二階段執行流程

       在分析start_armboot函數前先來看看一些重要的數據結構:

1gd_t結構體

       U-Boot使用了一個結構體gd_t來存儲全局數據區的數據,這個結構體在include/asm-arm/global_data.h中定義以下:

typedef  struct     global_data {

       bd_t              *bd;

       unsigned long      flags;

       unsigned long      baudrate;

       unsigned long      have_console;      /* serial_init() was called */

       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 */

       void              **jt;              /* jump table */

} gd_t;

       U-Boot使用了一個存儲在寄存器中的指針gd來記錄全局數據區的地址:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

       DECLARE_GLOBAL_DATA_PTR定義一個gd_t全局數據結構的指針,這個指針存放在指定的寄存器r8中。這個聲明也避免編譯器把r8分配給其它的變量。任何想要訪問全局數據區的代碼,只要代碼開頭加入「DECLARE_GLOBAL_DATA_PTR」一行代碼,而後就可使用gd指針來訪問全局數據區了。

       根據U-Boot內存使用圖中能夠計算gd的值:

gd = TEXT_BASE CONFIG_SYS_MALLOC_LEN  sizeof(gd_t)

2bd_t結構體

       bd_tinclude/asm-arm.u/u-boot.h中定義以下:

typedef struct bd_info {

    int                bi_baudrate;               /* 串口通信波特率 */

    unsigned long     bi_ip_addr;          /* IP 地址*/

    struct environment_s        *bi_env;              /* 環境變量開始地址 */

    ulong            bi_arch_number;      /* 開發板的機器碼 */

    ulong            bi_boot_params;       /* 內核參數的開始地址 */

    struct                         /* RAM配置信息 */

    {

              ulong start;

              ulong size;

    }bi_dram[CONFIG_NR_DRAM_BANKS]; 

} bd_t;

       U-Boot啓動內核時要給內核傳遞參數,這時就要使用gd_tbd_t結構體中的信息來設置標記列表。

3init_sequence數組

       U-Boot使用一個數組init_sequence來存儲對於大多數開發板都要執行的初始化函數的函數指針。init_sequence數組中有較多的編譯選項,去掉編譯選項後init_sequence數組以下所示:

typedef int (init_fnc_t) (void);

 

init_fnc_t *init_sequence[] = {

       board_init,         /*開發板相關的配置--board/samsung/mini2440/mini2440.c */

       timer_init,            /* 時鐘初始化-- cpu/arm920t/s3c24x0/timer.c */

       env_init,            /*初始化環境變量--common/env_flash.c common/env_nand.c*/

       init_baudrate,      /*初始化波特率-- lib_arm/board.c */

       serial_init,            /* 串口初始化-- drivers/serial/serial_s3c24x0.c */

       console_init_f,    /* 控制通信臺初始化階段1-- common/console.c */

       display_banner,   /*打印U-Boot版本、編譯的時間-- gedit lib_arm/board.c */

       dram_init,            /*配置可用的RAM-- board/samsung/mini2440/mini2440.c */

       display_dram_config,              /* 顯示RAM大小-- lib_arm/board.c */

       NULL,

};

       其中的board_init函數在board/samsung/mini2440/mini2440.c中定義,該函數設置了MPLLCOMUPLLCON,以及一些GPIO寄存器的值,還設置了U-Boot機器碼和內核啓動參數地址 

/* MINI2440開發板的機器碼 */

gd->bd->bi_arch_number = MACH_TYPE_MINI2440;

 

/* 內核啓動參數地址 */

gd->bd->bi_boot_params = 0x30000100;  

       其中的dram_init函數在board/samsung/mini2440/mini2440.c中定義以下:

int dram_init (void)

{

      /* 因爲mini2440只有 */

      gd->bd->bi_dram[0].start = PHYS_SDRAM_1;

      gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;

 

      return 0;

}

mini2440使用232MBSDRAM組成了64MB的內存,接在存儲控制器的BANK6,地址空間是0x30000000~0x34000000

include/configs/mini2440.hPHYS_SDRAM_1PHYS_SDRAM_1_SIZE 分別被定義爲0x300000000x0400000064M)。

       分析完上述的數據結構,下面來分析start_armboot函數:

void start_armboot (void)

{

       init_fnc_t **init_fnc_ptr;

       char *s;

       … …

       /* 計算全局數據結構的地址gd */

       gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));

       … …

       memset ((void*)gd, 0, sizeof (gd_t));

       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

       memset (gd->bd, 0, sizeof (bd_t));

       gd->flags |= GD_FLG_RELOC;

 

       monitor_flash_len = _bss_start - _armboot_start;

 

/* 逐個調用init_sequence數組中的初始化函數  */

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                     hang ();

              }

       }

 

/* armboot_start cpu/arm920t/start.S 中被初始化爲u-boot.lds鏈接腳本中的_start */

       mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,

                     CONFIG_SYS_MALLOC_LEN);

 

/* NOR Flash初始化 */

#ifndef CONFIG_SYS_NO_FLASH

       /* configure available FLASH banks */

       display_flash_config (flash_init ());

#endif /* CONFIG_SYS_NO_FLASH */

 

       … …

/* NAND Flash 初始化*/

#if defined(CONFIG_CMD_NAND)

       puts ("NAND:  ");

       nand_init();         /* go init the NAND */

#endif

       … …

       /*配置環境變量,從新定位 */

       env_relocate ();

       … …

       /* 從環境變量中獲取IP地址 */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

       stdio_init (); /* get the devices list going. */

       jumptable_init ();

       … …

       console_init_r (); /* fully init console as a device */

       … …

       /* enable exceptions */

       enable_interrupts ();

 

#ifdef CONFIG_USB_DEVICE

       usb_init_slave();

#endif

 

       /* Initialize from environment */

       if ((s = getenv ("loadaddr")) != NULL) {

              load_addr = simple_strtoul (s, NULL, 16);

       }

#if defined(CONFIG_CMD_NET)

       if ((s = getenv ("bootfile")) != NULL) {

              copy_filename (BootFile, s, sizeof (BootFile));

       }

#endif

       … …

       /* 網卡初始化 */

#if defined(CONFIG_CMD_NET)

#if defined(CONFIG_NET_MULTI)

       puts ("Net:   ");

#endif

       eth_initialize(gd->bd);

… …

#endif

 

       /* main_loop() can return to retry autoboot, if so just run it again. */

       for (;;) {

              main_loop ();

       }

       /* NOTREACHED - no way out of command loop except booting */

}

       main_loop函數在common/main.c中定義。通常狀況下,進入main_loop函數若干秒內沒有

1.1.3             U-Boot啓動Linux過程

       U-Boot使用標記列表(tagged list)的方式向Linux傳遞參數。標記的數據結構式是tag,在U-Boot源代碼目錄include/asm-arm/setup.h中定義以下:

struct tag_header {

       u32 size;       /* 表示tag數據結構的聯合u實質存放的數據的大小*/

       u32 tag;        /* 表示標記的類型 */

};

 

struct tag {

       struct tag_header hdr;

       union {

              struct tag_core           core;

              struct tag_mem32      mem;

              struct tag_videotext   videotext;

              struct tag_ramdisk     ramdisk;

              struct tag_initrd  initrd;

              struct tag_serialnr       serialnr;

              struct tag_revision      revision;

              struct tag_videolfb     videolfb;

              struct tag_cmdline     cmdline;

 

              /*

               * Acorn specific

               */

              struct tag_acorn  acorn;

              /*

               * DC21285 specific

               */

              struct tag_memclk      memclk;

       } u;

};

       U-Boot使用命令bootm來啓動已經加載到內存中的內核。而bootm命令實際上調用的是do_bootm函數。對於Linux內核,do_bootm函數會調用do_bootm_linux函數來設置標記列表和啓動內核。do_bootm_linux函數在lib_arm/bootm.c 中定義以下:

59   int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)

60   {

61       bd_t       *bd = gd->bd;

62       char       *s;

63       int   machid = bd->bi_arch_number;

64       void       (*theKernel)(int zero, int arch, uint params);

65  

66   #ifdef CONFIG_CMDLINE_TAG

67       char *commandline = getenv ("bootargs");   /* U-Boot環境變量bootargs */

68   #endif

       … …

73       theKernel = (void (*)(int, int, uint))images->ep; /* 獲取內核入口地址 */

       … …

86   #if defined (CONFIG_SETUP_MEMORY_TAGS) || \

87       defined (CONFIG_CMDLINE_TAG) || \

88       defined (CONFIG_INITRD_TAG) || \

89       defined (CONFIG_SERIAL_TAG) || \

90       defined (CONFIG_REVISION_TAG) || \

91       defined (CONFIG_LCD) || \

92       defined (CONFIG_VFD)

93       setup_start_tag (bd);                                     /* 設置ATAG_CORE標誌 */

       … …

100  #ifdef CONFIG_SETUP_MEMORY_TAGS

101      setup_memory_tags (bd);                             /* 設置內存標記 */

102  #endif

103  #ifdef CONFIG_CMDLINE_TAG

104      setup_commandline_tag (bd, commandline);      /* 設置命令行標記 */

105  #endif

       … …

113      setup_end_tag (bd);                               /* 設置ATAG_NONE標誌 */          

114  #endif

115 

116      /* we assume that the kernel is in place */

117      printf ("\nStarting kernel ...\n\n");

       … …

126      cleanup_before_linux ();          /* 啓動內核前對CPU做最後的設置 */

127 

128      theKernel (0, machid, bd->bi_boot_params);      /* 調用內核 */

129      /* does not return */

130 

131      return 1;

132  }

       其中的setup_start_tagsetup_memory_tagssetup_end_tag函數在lib_arm/bootm.c中定義以下:

       1setup_start_tag函數

static void setup_start_tag (bd_t *bd)

{

       params = (struct tag *) bd->bi_boot_params;  /* 內核的參數的開始地址 */

 

       params->hdr.tag = ATAG_CORE;

       params->hdr.size = tag_size (tag_core);

 

       params->u.core.flags = 0;

       params->u.core.pagesize = 0;

       params->u.core.rootdev = 0;

 

       params = tag_next (params);

}

       標記列表必須以ATAG_CORE開始,setup_start_tag函數在內核的參數的開始地址設置了一個ATAG_CORE標記。

       2setup_memory_tags函數

static void setup_memory_tags (bd_t *bd)

{

       int i;

/*設置一個內存標記 */

       for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {   

              params->hdr.tag = ATAG_MEM;

              params->hdr.size = tag_size (tag_mem32);

 

              params->u.mem.start = bd->bi_dram[i].start;

              params->u.mem.size = bd->bi_dram[i].size;

 

              params = tag_next (params);

       }

}

       setup_memory_tags函數設置了一個ATAG_MEM標記,該標記包含內存起始地址,內存大小這兩個參數。

       3setup_end_tag函數

static void setup_end_tag (bd_t *bd)

{

       params->hdr.tag = ATAG_NONE;

       params->hdr.size = 0;

}

       標記列表必須以標記ATAG_NONE結束,setup_end_tag函數設置了一個ATAG_NONE標記,表示標記列表的結束。

       U-Boot設置好標記列表後就要調用內核了。但調用內核前,CPU必須知足下面的條件:

(1)    CPU寄存器的設置

Ø  r0=0

Ø  r1=機器碼

Ø  r2=內核參數標記列表在RAM中的起始地址

(2)    CPU工做模式

Ø  禁止IRQFIQ中斷

Ø  CPUSVC模式

(3)    使數據Cache與指令Cache失效

       do_bootm_linux中調用的cleanup_before_linux函數完成了禁止中斷和使Cache失效的功能。cleanup_before_linux函數在cpu/arm920t/cpu.中定義:

int cleanup_before_linux (void)

{

       /*

        * this function is called just before we call linux

        * it prepares the processor for linux

        *

        * we turn off caches etc ...

        */

 

       disable_interrupts ();         /* 禁止FIQ/IRQ中斷 */

 

       /* turn off I/D-cache */

       icache_disable();               /* 使指令Cache失效 */

       dcache_disable();              /* 使數據Cache失效 */

       /* flush I/D-cache */

       cache_flush();                    /* 刷新Cache */

 

       return 0;

}

       因爲U-Boot啓動以來就一直工做在SVC模式,所以CPU的工做模式就無需設置了。

do_bootm_linux中:

64       void       (*theKernel)(int zero, int arch, uint params);

… …

73       theKernel = (void (*)(int, int, uint))images->ep;

… …

128      theKernel (0, machid, bd->bi_boot_params);

       73行代碼將內核的入口地址「images->ep」強制類型轉換爲函數指針。根據ATPCS規則,函數的參數個數不超過4個時,使用r0~r34個寄存器來傳遞參數。所以第128行的函數調用則會將0放入r0,機器碼machid放入r1,內核參數地址bd->bi_boot_params放入r2,從而完成了寄存器的設置,最後轉到內核的入口地址。

       到這裏,U-Boot的工做就結束了,系統跳轉到Linux內核代碼執行。

1.1.4             U-Boot添加命令的方法及U-Boot命令執行過程

       下面以添加menu命令(啓動菜單)爲例講解U-Boot添加命令的方法。

(1)    創建common/cmd_menu.c

       習慣上通用命令源代碼放在common目錄下,與開發板專有命令源代碼則放在board/<board_dir>目錄下,而且習慣以「cmd_<命令名>.c」爲文件名。

(2)    定義「menu」命令

       cmd_menu.c中使用以下的代碼定義「menu」命令:

_BOOT_CMD(

       menu,    3,    0,    do_menu,

       "menu - display a menu, to select the items to do something\n",

       " - display a menu, to select the items to do something"

);

       其中U_BOOT_CMD命令格式以下:

U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)

       各個參數的意義以下:

name:命令名,非字符串,但在U_BOOT_CMD中用「#」符號轉化爲字符串

maxargs:命令的最大參數個數

rep:是否自動重複(按Enter鍵是否會重複執行)

cmd:該命令對應的響應函數

usage:簡短的使用說明(字符串)

help:較詳細的使用說明(字符串)

       在內存中保存命令的help字段會佔用必定的內存,經過配置U-Boot能夠選擇是否保存help字段。若在include/configs/mini2440.h中定義了CONFIG_SYS_LONGHELP宏,則在U-Boot中使用help命令查看某個命令的幫助信息時將顯示usagehelp字段的內容,不然就只顯示usage字段的內容。

       U_BOOT_CMD宏在include/command.h中定義:

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

       ##」與「#」都是預編譯操做符,「##」有字符串鏈接的功能,「#」表示後面緊接着的是一個字符串。

       其中的cmd_tbl_tinclude/command.h中定義以下:

struct cmd_tbl_s {

       char              *name;          /* 命令名 */

       int          maxargs;       /* 最大參數個數 */

       int          repeatable;    /* 是否自動重複 */

       int          (*cmd)(struct cmd_tbl_s *, int, int, char *[]);  /*  響應函數 */

       char              *usage;         /* 簡短的幫助信息 */

#ifdef    CONFIG_SYS_LONGHELP

       char              *help;           /*  較詳細的幫助信息 */

#endif

#ifdef CONFIG_AUTO_COMPLETE

       /* 自動補全參數 */

       int          (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);

#endif

};

typedef struct cmd_tbl_s  cmd_tbl_t;

       一個cmd_tbl_t結構體變量包含了調用一條命令的所須要的信息。

       其中Struct_Sectioninclude/command.h中定義以下:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

       凡是帶有__attribute__ ((unused,section (".u_boot_cmd"))屬性聲明的變量都將被存放在".u_boot_cmd"段中,而且即便該變量沒有在代碼中顯式的使用編譯器也不產生警告信息。

       U-Boot鏈接腳本u-boot.lds中定義了".u_boot_cmd"段:

       . = .;

       __u_boot_cmd_start = .;          /* __u_boot_cmd_start指定爲當前地址 */

       .u_boot_cmd : { *(.u_boot_cmd) }

       __u_boot_cmd_end = .;           /*  __u_boot_cmd_end指定爲當前地址  */

       這代表帶有「.u_boot_cmd」聲明的函數或變量將存儲在「u_boot_cmd」段。這樣只要將U-Boot全部命令對應的cmd_tbl_t變量加上「.u_boot_cmd」聲明,編譯器就會自動將其放在「u_boot_cmd」段,查找cmd_tbl_t變量時只要在__u_boot_cmd_start__u_boot_cmd_end之間查找就能夠了。

       所以「menu」命令的定義通過宏展開後以下:

cmd_tbl_t __u_boot_cmd_menu __attribute__ ((unused,section (".u_boot_cmd"))) = {menu, 3, 0, do_menu, "menu - display a menu, to select the items to do something\n", " - display a menu, to select the items to do something"}

       實質上就是用U_BOOT_CMD宏定義的信息構造了一個cmd_tbl_t類型的結構體。編譯器將該結構體放在「u_boot_cmd」段,執行命令時就能夠在「u_boot_cmd」段查找到對應的cmd_tbl_t類型結構體。

(3)    實現命令的函數

       cmd_menu.c中添加「menu」命令的響應函數的實現。具體的實現代碼略:

int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       /* 實現代碼略 */

}

(4)    common/cmd_menu.c編譯進u-boot.bin

       common/Makefile中加入以下代碼:

COBJS-$(CONFIG_BOOT_MENU) += cmd_menu.o

       include/configs/mini2440.h加入如代碼:

#define CONFIG_BOOT_MENU 1

       從新編譯下載U-Boot就可使用menu命令了

5menu命令執行的過程

       U-Boot中輸入「menu」命令執行時,U-Boot接收輸入的字符串「menu」,傳遞給run_command函數。run_command函數調用common/command.c中實現的find_cmd函數在__u_boot_cmd_start__u_boot_cmd_end間查找命令,並返回menu命令的cmd_tbl_t結構。而後run_command函數使用返回的cmd_tbl_t結構中的函數指針調用menu命令的響應函數do_menu,從而完成了命令的執行。

相關文章
相關標籤/搜索