1. 程式人生 > >U-Boot啟動過程完全分析

U-Boot啟動過程完全分析

U-Boot啟動核心的過程可以分為兩個階段,兩個階段的功能如下:

       (1)第一階段的功能

Ø  硬體裝置初始化

Ø  載入U-Boot第二階段程式碼到RAM空間

Ø  設定好棧

Ø  跳轉到第二階段程式碼入口

       (2)第二階段的功能

Ø  初始化本階段使用的硬體裝置

Ø  檢測系統記憶體對映

Ø  將核心從Flash讀取到RAM中

Ø  為核心設定啟動引數

Ø  呼叫核心

       第一階段對應的檔案是cpu/arm920t/start.S和board/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”處執行。

(2)CPU進入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的工作模式位設定為管理模式,並將中斷禁止位和快中斷禁止位置一,從而遮蔽了IRQ和FIQ中斷。

(3)設定控制暫存器地址

# if defined(CONFIG_S3C2400)

#  define pWTCON 0x15300000

#  define INTMSK  0x14400008

#  define CLKDIVN      0x14800014

#else      /* s3c2410與s3c2440下面4個暫存器地址相同 */

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

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

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

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

# endif

       對與s3c2440開發板,以上程式碼完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四個暫存器的地址的設定。各個暫存器地址參見參考文獻[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所處理。

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

# if defined(CONFIG_S3C2440)

          ldr  r1, =0x7fff      

          ldr  r0, =INTSUBMSK

          str  r1, [r0]

# endif

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

       根據參考文獻4,INTSUBMSK暫存器是一個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。這就需要設定CLKDIVN,MPLLCON,UPLLCON這3個暫存器。

       CLKDIVN暫存器用於設定FCLK,HCLK,PCLK三者間的比例,可以根據表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

       設定CLKDIVN為5,就將HDIVN設定為二進位制的10,由於CAMDIVN[9]沒有被改變過,取預設值0,因此HCLK = FCLK/4。PDIVN被設定為1,因此PCLK= HCLK/2。因此分頻比FCLK:HCLK:PCLK = 1:4:8 。

       MPLLCON暫存器用於設定FCLK與Fin的倍數。MPLLCON的位[19:12]稱為MDIV,位[9:4]稱為PDIV,位[1:0]稱為SDIV。

       對於S3C2440,FCLK與Fin的關係如下面公式:

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

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

       MPLLCON與UPLLCON的值可以根據參考文獻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系統主頻設定為405MHZ,USB時鐘頻率設定為48MHZ時,系統可以穩定執行,因此設定MPLLCON與UPLLCON為:

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

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

(7)關閉MMU,cache

       接著往下看:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

       bl    cpu_init_crit

#endif

       cpu_init_crit這段程式碼在U-Boot正常啟動時才需要執行,若將U-Boot從RAM中啟動則應該註釋掉這段程式碼。

       下面分析一下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將使ICache與DCache無效*/

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

       程式碼中的c0,c1,c7,c8都是ARM920T的協處理器CP15的暫存器。其中c7是cache控制暫存器,c8是TLB控制暫存器。325~327行程式碼將0寫入c7、c8,使Cache,TLB內容無效。

       第332~337行程式碼關閉了MMU。這是通過修改CP15的c1暫存器來實現的,先看CP15的c1暫存器的格式(僅列出程式碼中用到的位):

表 2.3 CP15的c1暫存器格式(部分)

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:異常向量在0x00000000;1:異常向量在 0xFFFF0000
I :  0 :關閉ICaches;1 :開啟ICaches
R、S : 用來與頁表中的描述符一起確定記憶體的訪問許可權
B :  0 :CPU為小位元組序;1 : CPU為大位元組序
C :  0:關閉DCaches;1:開啟DCaches
A :  0:資料訪問時不進行地址對齊檢查;1:資料訪問時進行地址對齊檢查
M :  0:關閉MMU;1:開啟MMU

       332~337行程式碼將c1的 M位置零,關閉了MMU。

(8)初始化RAM控制暫存器

       其中的lowlevel_init就完成了記憶體初始化的工作,由於記憶體初始化是依賴於開發板的,因此lowlevel_init的程式碼一般放在board下面相應的目錄中。對於mini2440,lowlevel_init在board/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-Boot從NAND Flash或NOR 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-Boot在NAND Flash啟動時,lowlevel_init.o將自動被讀取到CPU內部4KB的內部RAM中。因此第137~146行的程式碼將從CPU內部RAM中複製暫存器的值到相應的暫存器中。

       對於U-Boot在NOR Flash啟動的情況,由於U-Boot連線時確定的地址是U-Boot在記憶體中的地址,而此時U-Boot還在NOR Flash中,因此還需要在NOR Flash中讀取資料到RAM中。

       由於NOR Flash的開始地址是0,而U-Boot的載入到記憶體的起始地址是TEXT_BASE,SMRDATA標號在Flash的地址就是SMRDATA-TEXT_BASE。

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

(9)複製U-Boot第二階段程式碼到RAM

       cpu/arm920t/start.S原來的程式碼是隻支援從NOR Flash啟動的,經過修改現在U-Boot在NOR Flash和NAND 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-Boot在NAND 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} /* 將r3至r10暫存器的資料複製給地址為[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              

    /* 下面先設定r0至r2,然後呼叫nand_read_ll函式將U-Boot讀入RAM */

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

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

    mov r2, #0x30000          /* 複製的大小,必須比u-boot.bin檔案大,並且必須是NAND Flash塊大小的整數倍,這裡設定為0x30000(192KB) */

    bl  nand_read_ll                 /* 跳轉到nand_read_ll函式,開始複製U-Boot到RAM */

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_BASE,oNFCONF等在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-Boot到RAM,該函式在board/samsung/mini2440/nand_read.c中定義。

       NAND Flash根據page大小可分為2種: 512B/page和2048B/page的。這兩種NAND Flash的讀操作是不同的。因此就需要U-Boot識別到NAND Flash的型別,然後採用相應的讀操作,也就是說nand_read_ll函式要能自動適應兩種NAND Flash。

       參考S3C2440的資料手冊可以知道:根據NFCONF暫存器的Bit3(AdvFlash (Read only))和Bit2 (PageSize (Read only))可以判斷NAND Flash的型別。Bit2、Bit3與NAND Flash的block型別的關係如下表所示:

表 2.4 NFCONF的Bit3、Bit2與NAND Flash的關係

Bit2   Bit3

0

1

0

256 B/page

512 B/page

1

1024 B/page

2048 B/page

       由於的NAND Flash只有512B/page和2048 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來區分2種NAND Flash

       if( NFCONF & 0x8 )        /* Bit是1,表示是2KB/page的NAND Flash */

       {

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

              讀取2K block 的NAND Flash

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

       }

       else                      /* Bit是0,表示是512B/page的NAND 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處。

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

 

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

       在分析start_armboot函式前先來看看一些重要的資料結構:

(1)gd_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)

(2)bd_t結構體

       bd_t在include/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_t,bd_t結構體中的資訊來設定標記列表。

(3)init_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中定義,該函式設定了MPLLCOM,UPLLCON,以及一些GPIO暫存器的值,還設定了U-Boot機器碼和核心啟動引數地址 :

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

gd->bd->bi_arch_number = MACH_TYPE_MINI2440;

/* 核心啟動引數地址 */

       其中的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使用2片32MB的SDRAM組成了64MB的記憶體,接在儲存控制器的BANK6,地址空間是0x30000000~0x34000000。

在include/configs/mini2440.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE 分別被定義為0x30000000和0x04000000(64M)。

       分析完上述的資料結構,下面來分析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函式若干秒內沒有

       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_tag,setup_memory_tags,setup_end_tag函式在lib_arm/bootm.c中定義如下:

       (1)setup_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標記。

       (2)setup_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標記,該標記包含記憶體起始地址,記憶體大小這兩個引數。

       (3)setup_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工作模式

Ø  禁止IRQ與FIQ中斷

Ø  CPU為SVC模式

(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~r3這4個暫存器來傳遞引數。因此第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命令檢視某個命令的幫助資訊時將顯示usage和help欄位的內容,否則就只顯示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_t在include/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_Section在include/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命令了

(5)menu命令執行的過程

       在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,從而完成了命令的執行。