Uboot 分析(5)


下面結合移植uboot到 s3c2440來分析如何改寫相關的uboot源碼(上節已經給出uboot源碼,可以參考) 

 

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

     看一下uboot.lds文件,在board/smdk2410目錄下面,uboot.lds是告訴編譯器這些段改怎么划分,GUN編譯過的段,最基本的三個段是RO,RW,ZI,RO表示只讀,對應於具體的指代碼段,RW是數據段,ZI是歸零段,就是全局變量的那段。Uboot代碼這么多,如何保證start.s會第一個執行,編譯在最開始呢?就是通過uboot.lds鏈接文件進行

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; //起始地址

. = ALIGN(4); //4字節對齊
.text : //test指代碼段,上面3行標識是不占用任何空間的
{
cpu/arm920t/start.o (.text) //這里把start.o放在第一位就表示把start.s編
譯時放到最開始,這就是為什么把uboot燒到起始地址上它肯定運行的是start.s
*(.text)
}

. = ALIGN(4); //前面的 “.” 代表當前值,是計算一個當前的值,是計算上
面占用的整個空間,再加一個單元就表示它現在的位置
.rodata : { *(.rodata) }

. = ALIGN(4);
.data : { *(.data) }

. = ALIGN(4);
.got : { *(.got) }

. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;

. = ALIGN(4);
__bss_start = .; //bss表示歸零段
.bss : { *(.bss) }
_end = .;
}
       第一個鏈接的是cpu/arm920t/start.o,因此u-boot.bin的入口代碼在cpu/arm920t/start.o中,其源代碼在cpu/arm920t/start.S中。下面我們來分析cpu/arm920t/start.S的執行。

1.      硬件設備初始化

(1)設置異常向量

          下面代碼是系統啟動后U-boot上電后運行的第一段代碼,它是什么意思?

          u-boot對應的第一階段代碼放在cpu/arm920t/start.S文件中,入口代碼如下:.

globl _startglobal                                    /*聲明一個符號可被其它文件引用,相當於聲明了一個全局變量,.globl與.global相同*/

_start:    b     start_code                    /* 復位 */b是不帶返回的跳轉(bl是帶返回的跳轉),意思是無條件直接跳轉到start_code標號出執行程序

       ldr   pc, _undefined_instruction      /* 未定義指令向量 l---dr相當於mov操作*/

       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  /*就是在當前地址,即_undefined_instruction 處存放 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 

word偽操作用於分配一段字內存單元(分配的單元都是字對齊的),並用偽操作中的expr初始化

 

       .balignl 16,0xdeadbeef

       他們是系統定義的異常,一上電程序跳轉到_start異常處執行相應的匯編指令,下面定義出的都是不同的異常,比如軟件發生軟中斷時,CPU就會去執行軟中斷的指令,這些異常中斷在CPU中地址是從0開始,每個異常占4個字節

       ldr pc, _undefined_instruction表示把_undefined_instruction存放的數值存放到pc指針上

                   _undefined_instruction: .word undefined_instruction表示未定義的這個異常是由.word來定義的,它表示定義一個字,一個32位的數

.  word后面的數:表示把該標識的編譯地址寫入當前地址,標識是不占用任何指令的。把標識存放的數值copy到指針pc上面,那么標識上存放的值是什么?

是由.word undefined_instruction來指定的,pc就代表你運行代碼的地址,她就實現了CPU要做一次跳轉時的工作

       以上代碼設置了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 restart”決定了U-Boot啟動后將自動跳轉到標號“restart”處執行。

(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的工作模式位設置為管理模式,即設置相應的CPSR程序狀態字,並將中斷禁止位和快中斷禁止位置一,從而屏蔽了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)關閉看門狗

       ldr   r0, =pWTCON   /*將pwtcon寄存器地址賦給R0*/

       mov       r1, #0x0      /*r1的內容為0*/

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

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

為什么要關看門狗?

         就是防止,不同得兩個以上得CPU,進行喂狗的時間間隔問題:說白了就是你運行的代碼如果超出喂狗時間,而你不關狗,就會導致,你代碼還沒運行完又得去喂狗,就這樣反復得重啟CPU,那你代碼永遠也運行不完,所以,得先關看門狗得原因,就是這樣。

關狗---詳細的原因:

      關閉看門狗,關閉中斷,所謂的喂狗是每隔一段時間給某個寄存器置位而已,在實際中會專門啟動一個線程或進程會專門喂狗,當上層軟件出現故障時就會停止喂狗,

      停止喂狗之后,cpu會自動復位,一般都在外部專門有一個看門狗,做一個外部的電路,不在cpu內部使用看門狗,cpu內部的看門狗是復位的cpu

       當開發板很復雜時,有好幾個cpu時,就不能完全讓板子復位,但我們通常都讓整個板子復位。看門狗每隔短時間就會喂狗,問題是在兩次喂狗之間的時間間隔內,運行的代碼的時間是否夠用,兩次喂狗之間的代碼是否在兩次喂狗的時間延遲之內,如果在延遲之外的話,代碼還沒改完就又進行喂狗,代碼永遠也改不完

(5)屏蔽中斷

       /*

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

        */

       mov       r1, #0xffffffff     /*屏蔽所有中斷, 某位被置1則對應的中斷被屏蔽 */ /*寄存器中的值*/

       ldr   r0, =INTMSK       /*將管理中斷的寄存器地址賦給ro*/

       str   r1, [r0]                  /*將全r1的值賦給ro地址中的內容*/

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

             INTMSK寄存器是一個32位的寄存器,每位對應一個中斷,向其中寫入0xffffffff就將INTMSK寄存器全部位置一,從而屏蔽對應的中斷。

# if defined(CONFIG_S3C2440)

          ldr  r1, =0x7fff                  

         ldr  r0, =INTSUBMSK  

         str  r1, [r0]            

 # endif

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

              INTSUBMSK寄存器是一個32位的寄存器,但是只使用了低15位。向其中寫入0x7fff就是將INTSUBMSK寄存器全部有效位(低15位)置一,從而屏蔽對應的中斷。

屏蔽所有中斷,為什么要關中斷?

    中斷處理中ldr pc是將代碼的編譯地址放在了指針上,而這段時間還沒有搬移代碼,所以編譯地址上面沒有這個代碼,如果進行跳轉就會跳轉到空指針上面

(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

       當tq2440系統主頻設置為405MHZ,USB時鍾頻率設置為48MHZ時,系統可以穩定運行,因此設置MPLLCON與UPLLCON為:

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

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

默認頻率為      FCLK:HCLK:PCLK = 1:2:4,默認 FCLK 的值為 120 MHz,該值為 S3C2410 手冊的推薦值。

設置時鍾分頻,為什么要設置時鍾?

起始可以不設,系統能不能跑起來和頻率沒有任何關系,頻率的設置是要讓外圍的設備能承受所設置的頻率,如果頻率過高則會導致cpu操作外圍設備失敗

說白了:設置頻率,就為了CPU能去操作外圍設備

(7)關閉MMU,cache  ------(也就是做bank的設置)

       接着往下看:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

       bl    cpu_init_crit  /* ;跳轉並把轉移后面緊接的一條指令地址保存到鏈接寄存器LR(R14)中,以此來完成子程序的調用*/

#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。

為什么要關閉catch和MMU呢?catch和MMU是做什么用的?

    MMU是Memory Management Unit的縮寫,中文名是內存管理單元,它是中央處理器(CPU)中用來管理虛擬存儲器、物理存儲器的控制線路

    同時也負責虛擬地址映射為物理地址,以及提供硬件機制的內存訪問授權      

概述:

一,關catch

       catch和MMU是通過CP15管理的,剛上電的時候,CPU還不能管理他們

       上電的時候MMU必須關閉,指令catch可關閉,可不關閉,但數據catch一定要關閉

       否則可能導致剛開始的代碼里面,去取數據的時候,從catch里面取,而這時候RAM中數據還沒有catch過來,導致數據預取異常

二:關MMU

      因為MMU是;把虛擬地址轉化為物理地址得作用

      而目的是設置控制寄存器,而控制寄存器本來就是實地址(物理地址),再使能MMU,不就是多此一舉了嗎?

詳細分析---

      Catch是cpu內部的一個2級緩存,它的作用是將常用的數據和指令放在cpu內部,MMU是用來把虛實地址轉換為物理地址用的

      我們的目的:是設置控制的寄存器,寄存器都是實地址(物理地址),如果既要開啟MMU又要做虛實地址轉換的話,中間還多一步,多此一舉了嘛?

  
      先要把實地址轉換成虛地址,然后再做設置,但對uboot而言就是起到一個簡單的初始化的作用和引導操作系統,如果開啟MMU的話,很麻煩,也沒必要,所以關閉MMU.
    

       說到catch就必須提到一個關鍵字Volatile,以后在設置寄存器時會經常遇到,他的本質是告訴編譯器不要對我的代碼進行優化,作用是讓編寫者感覺不倒變量的變化情況(也就是說,讓它執行速度加快吧)

       優化的過程:是將常用的代碼取出來放到catch中,它沒有從實際的物理地址去取,它直接從cpu的緩存中去取,但常用的代碼就是為了感覺一些常用變量的變化

        優化原因:如果正在取數據的時候發生跳變,那么就感覺不到變量的變化了,所以在這種情況下要用Volatile關鍵字告訴編譯器不要做優化,每次從實際的物理地址中去取指令,這就是為什么關閉catch關閉MMU

         但在C語言中是不會關閉catch和MMU的,會打開,如果編寫者要感覺外界變化,或變化太快,從catch中取數據會有誤差,就加一個關鍵字Volatile。

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

                     bl lowlevel_init下來初始化各個bank,把各個bank設置必須搞清楚,對以后移植復雜的uboot有很大幫助
                     設置完畢后拷貝uboot代碼到4k空間,拷貝完畢后執行內存中的uboot代碼

      其中的lowlevel_init就完成了內存初始化的工作,由於內存初始化是依賴於開發板的,因此lowlevel_init的代碼一般放在board下面相應的目錄中。lowlevel_init在board/smdk2410/lowlevel_init.S中定義如下:

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

  … …

129  _TEXT_BASE:

130      .word     TEXT_BASE       0x33F80000, board/config.mk中這段話表示,用戶告訴編譯器編譯地址的起始地址

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個寄存器,從而完成了存儲控制器的設置。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 問題一:如果換一塊開發板有可能改哪些東西?
                   首先,cpu的運行模式,如果需要對cpu進行設置那就設置,管看門狗,關中斷不用改,時鍾有可能要改,如果能正常使用則不用改,關閉catch和MMU不用改,設置bank有可能要改。最后一步拷貝時看地址會不會變,如果變化也要改,執行內存中代碼,地址有可能要改。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
問題二:Nor Flash和Nand Flash本質區別:

                  就在於是否進行代碼拷貝,也就是下面代碼所表述:無論是Nor Flash還是Nand Flash,核心思想就是將uboot代碼搬運到內存中去運行,但是沒有拷貝bss后面這段代碼,只拷貝bss前面的代碼,bss代碼是放置全局變量的。Bss段代碼是為了清零,拷貝過去再清零重復操作

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(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   /*flash中armboot_start的起始地址*/

214      ldr   r3, _bss_start         /*uboot_bss的起始地址*/

215      sub  r2, r3, r2              /* r2 <- size of armboot  uboot實際程序代碼的大小   */

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,否則這些變量的初始值將是一個隨機的值,若有些程序直接使用這些沒有初始化的變量將引起未知的后果。

 

轉載自:http://blog.csdn.net/hare_lee/article/details/6916325

 

 

 

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個寄存器,從而完成了存儲控制器的設置。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 問題一:如果換一塊開發板有可能改哪些東西?
                   首先,cpu的運行模式,如果需要對cpu進行設置那就設置,管看門狗,關中斷不用改,時鍾有可能要改,如果能正常使用則不用改,關閉catch和MMU不用改,設置bank有可能要改。最后一步拷貝時看地址會不會變,如果變化也要改,執行內存中代碼,地址有可能要改。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
問題二:Nor Flash和Nand Flash本質區別:

                  就在於是否進行代碼拷貝,也就是下面代碼所表述:無論是Nor Flash還是Nand Flash,核心思想就是將uboot代碼搬運到內存中去運行,但是沒有拷貝bss后面這段代碼,只拷貝bss前面的代碼,bss代碼是放置全局變量的。Bss段代碼是為了清零,拷貝過去再清零重復操作

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(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   /*flash中armboot_start的起始地址*/

214      ldr   r3, _bss_start         /*uboot_bss的起始地址*/

215      sub  r2, r3, r2              /* r2 <- size of armboot  uboot實際程序代碼的大小   */

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啟動的代碼 */


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM