U-Boot啓動過程完全分析

轉自: http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html

1.1       U-Boot 工作過程

 

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

       1 )第一階段的功能

Ø  硬件設備初始化

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

Ø  設置好棧

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

       2 )第二階段的功能

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

Ø  檢測系統內存映射

Ø  將內核從 Flash 讀取到 RAM

Ø  爲內核設置啓動參數

Ø  調用內核

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

       第一階段對應的文件是 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 處。

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

       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;

 

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

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 使用 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 函數若干秒內沒有

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

發佈了1 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章