天天看點

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)

         U-boot完全分析(基于FL2440u-boot移植)

1.1 U-boot 工作過程

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

  (1)第一階段的功能

     Ø  硬體裝置初始化

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

     Ø  設定好棧

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

  (2)第二階段的功能

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

     Ø  檢測系統記憶體映射

     Ø  将核心從Flash讀取到RAM中

     Ø  為核心設定啟動參數

     Ø  調用核心

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

       第一階段對應的檔案是cpu/arm920t/start.S和board/samsung/ok2440/lowlevel_init.S。

       U-Boot啟動第一階段流程如下:

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)
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                     /* 複位 (SVC模式)*/
       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異常向量表,各個異常向量介紹如下:

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)

在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           /*msr mrs都是程式狀态字通路指令(ARM中有一個程式狀态寄存器(cpsr),他用來控制處理器的工作模式,設定中斷的總開關)*/
       bic  r0, r0, #0x1f             /*工作模式位清零 */
       orr  r0, r0, #0xd3              /*工作模式位設定為“10011”(管理模式),并将中斷禁止位和快中斷禁止位置1 */
       msr cpsr, r0
           

 以上代碼将CPU的工作模式位置設為SVC(管理模式),并将中斷禁止位和快中斷禁止位置1,進而屏蔽了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寄存器位址 */


//這個是S3C2440有的哦
#  define LOCKTIME 0x4C000000                    //PLL Lock Time Counter
#  define MPLLCON  0x4C000004                    //MPLL Control
#  define UPLLCON  0x4C000008                    //UPLLCON Control
#  define CAMDIVN  0x4C000018                    //Camera Clock divider Control,這個确實在bootloader中沒有用


# endif
           

  對于S3c2410開發闆,以上代碼完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四個寄存器的位址的設定,然後設定S3C2440額外的寄存器

LOCKTIME MPLLCON UPLLCON CAMDIV

(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所處理

S3C2440A的中斷控制器中有60個中斷源

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)

INTMSK 寄存器是一個32位的寄存器,每一位對應一個中斷,向其中寫入0xffffffff就将INTMSK寄存器全部置1,進而屏蔽所有中斷

子中斷源

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)

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

32位的寄存器,但是隻使用了低15位。向其中寫入0x7fff就是将INTSUBMSK寄存器的全部有效位(低15)置1,進而屏蔽了子中斷

# if defined(CONFIG_S3C2440)
          ldr  r1, =0x7fff      
          ldr  r0, =INTSUBMSK
          str  r1, [r0]
# endif
           

(6)設定MPLLCON,UPLLCON,CLKDIV,LOCKTIME

/* During the lock time, the clock is not supplied to the internal blocks in the S3C2440A,
     U_LTIME=0xFFFF(300us),UPLL lock time count value for UCLK.
     M_LTIME=0xFFFF(300us) (MPLL lock time count value for FCLK,HCLK and PCLK) 
     */




        ldr     r0, =LOCKTIME   


        mov     r1,#0xffffff
        str     r1,[r0]
        ldr     r0, =CAMDIVN
        mov     r1,#0
        str     r1,[r0]




        /* FCLK:HCLK:PCLK = 1:4:8 */
        /* default FCLK is 12 MHz ! */
        ldr     r0,  =CLKDIVN
        mov     r1,  #5
        str     r1, [ r0]


        mrc p15,0,r1,c1,c0,0


        orr r1,r1,#0xc0000000


        mcr p15,0,r1,c1,c0,0


        ldr     r0,  =UPLLCON


        ldr     r1,  =0x00038022


        str     r1,  [r0]


        /*arm920t is the 5 flow instruction*/
               /* nop
                nop
                nop
                nop
                nop
                nop
                nop
                nop*/


        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
           

   CPU上電幾毫秒後,晶震輸出穩定,FCLK=Fin(晶陣頻率12M),CPU開始執行指令,但實際上,FCLK以高于Fin,為了提高系統時鐘,需要用軟體來啟動

PLL,這就需要設定CLKDIV,MPLLCON,UPLLCON這3個寄存器

CLKDIVN寄存器用于設定FCLK,HCLK,PCLK三者間的比例,可以根據表2.2來設定。

表 2.2 S3C2440 的CLKDIVN寄存器格式

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)

 設定CLKDIVN為5,就将HDIVN設定為二進制的10,由于

ldr     r0, =CAMDIVN
        mov     r1,#0
        str     r1,[r0]
           

是以分頻比FCLK:HCLK:PCLK = 1:4:8

對于接下來的代碼

mrc p15,0,r1,c1,c0,0


        orr r1,r1,#0xc0000000


        mcr p15,0,r1,c1,c0,0
           

這是摘自S3C2440A的datasheet

2. If HDIVN is not 0, the CPU bus mode has to be changed from the fast bus mode to the asynchronous bus
mode using following instructions(S3C2440 does not support synchronous bus mode).
MMU_SetAsyncBusMode
mrc p15,0,r0,c1,c0,0
orr r0,r0,#R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
If HDIVN is not 0 and the CPU bus mode is the fast bus mode, the CPU will operate by the HCLK. This
feature can be used to change the CPU frequency as a half or more without affecting the HCLK and
PCLK.
           

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

對于S3C2440,FCLK與Fin的關系如下面的公式


       MPLL(FCLK) = (2×m×Fin)/(p×)
       其中: m=MDIV+8,p=PDIV+2,s=SDIV
           

MPLLCON與UPLLCON的值可以根據參考文獻4中“PLL VALUE SELECTION TABLE”設定。該表部分摘錄如下:

  推薦PLL值

輸入頻率 輸入頻率 MDIV PDIV SDIV
12.0000MHz 48.00MHz 56(0x38) 2 2
12.0000MHz 405.00MHz 127(0x7f) 2 1

  當OK2440系統的主頻設定為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寄存器的格式(僅列出代碼中用到的位):

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
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下面的相應的目錄中

對于OK2440,lowlevel_init在board/samsung/ok2440/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/ok2440/lowlevel_init.o (.text)
                board/samsung/ok2440/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,NFCONF等在include/configs/ok2440.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 1
256B/page 512B/page
1 1024B/page 2048B/page

由于的NAND Flash隻有512B/page和2048 B/page這兩種,是以根據NFCONF寄存器的Bit3即可區分這兩種NAND Flash了。

   完整代碼見board/samsung/ok2440/nand_read.c中的nand_read_II函數,這裡給出了僞代碼:

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記憶體的使用情況,如下圖所示:

u-boot完全分析         U-boot完全分析(基于FL2440u-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啟動第二階段流程如下:

u-boot完全分析         U-boot完全分析(基于FL2440u-boot移植)

(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,進而完成了指令的執行。

繼續閱讀