mini2440的uboot分析

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×142s'> )
       其中: 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)關閉MMUcache
       接着往下看:
#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使用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函數若干秒內沒有
       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 *p_w_picpaths)
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))p_w_picpaths->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))p_w_picpaths->ep;
… …
128     theKernel (0, machid, bd->bi_boot_params);
       第73行代碼將內核的入口地址“p_w_picpaths->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,從而完成了命令的執行。
 
   作者:heaad
    http://www.cnblogs.com/heaad/ 
  郵箱:[email protected]
    本文摘選自作者所寫的一篇文章。轉載請註明,水平有限,歡迎拍磚。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章