第1階段——uboot分析之硬件初始化start.S(4)


分析uboot第一個執行函數_start(cpu/arm920t/start.S) 

打開cpu/arm920t/start.S

1 .globl _start                      // .globl定義一個全局符號"_start",表明_start這個符號要被鏈接器用到 
2 _start:                            //_start:系統復位設置,以下共8種不同的異常處理
3 b reset                             //復位異常 0x0
4 ldr    pc, _undefined_instruction                 //未定義的指令異常 0x4
5 ldr    pc, _software_interrupt                 // 軟件中斷異常 0x8 
6 ldr    pc, _prefetch_abort                 //內存操作異常 0xc
7 ldr    pc, _data_abort                 //數據異常 0x10
8 ldr    pc, _not_used                 //未使用 0x14
9 ldr    pc, _irq                   //中斷IRQ異常 0x18
10 ldr    pc, _fiq                 //快速中斷FIQ異常 0x1c
11 
12 _undefined_instruction:    .word undefined_instruction                 //0x20
13 _software_interrupt:    .word software_interrupt                       //0x24
14 _prefetch_abort:    .word prefetch_abort             // 0x28
15 _data_abort:    .word data_abort                     //0x2c
16 _not_used:    .word not_used                        //0x30
17 _irq:    .word irq                                  //0x34
18 _fiq:    .word fiq                                  //0x38
19 
20 .balignl 16,0xdeadbeef                              //0x3c

在第1行中".globl _start":使用.globol聲明全局符號_start,在 board/100ask24x0/u-boot.lds中ENTRY(_start)這里用到
其中符號保存的地址都在頂層目錄/system.map中列出來了

system.map文件開頭部分如下:

33f80000 t $a 
33f80000 T _start                   //_start符號被鏈接在33f80000,其中33f80000是生成bin文件的運行啟始地址.
33f80020 t $d 
33f80020 t _undefined_instruction   //_undefined_instruction符號被鏈接在33f80020
...
33f80160 t undefined_instruction //_undefined_instruction指向的undefined_instruction符號被鏈接在33f80160
33f801c0 t software_interrupt
33f80220 t prefetch_abort
33f80280 t data_abort
33f802e0 t not_used
33f80340 T Launch
33f803b0 t On_Steppingstone
33f80400 t irq
...

在第2行中_start之所以有8種不同的異常處理,是在2440芯片手冊中已經規定好了的,如下圖1:

                                                            圖1

從上圖可以看出復位異常處理需要進入管理模式(0X00000000),所以start.S 中“b reset”跳轉到設置管理模式。

在linux中的異常向量地址是經過MMU(虛擬內存管理)產生的虛擬地址,比如中斷地址:
0x18映射到物理地址是0xc000 0018(映射地址由自己設定),所以linux把中斷向量放在0xc000 0018就行了。

CPU一上電設置了入口地址"ENTRY(_start)"后,就會進入"_start"全局符號中執行上面第3行跳轉到復位異常字段: "b reset".
1.那么后面的異常處理為什么用ldr不用b指令?
之所以第一句使用b reset,是因為ldr指令屬於絕對跳轉,而b屬於相對跳轉,它的地址與代碼位置無關。
因為復位異常在CPU運行前是沒有初始化SDRAM(不能使用0X30000000以上地址),

在正常工作后也可能觸發復位,這時由於CPU已經對SDRAM、MMU(虛擬內存管理)等初始化了,
此時的虛擬地址和物理地址完全不同,所以reset使用b指令相對跳轉。

2.后面的異常處理是怎么執行的?執行后異常處理又怎么退出的?
在2440芯片手冊上給出,例如當處理一個中斷IRQ異常時:
a.保存當前PC現場到寄存器R14,  
b.把當前程序狀態寄存器(CPSR)保存到備份程序狀態寄存器(SPSR)中.從異常退出的時候,就可以由SPSR來恢復CPSR。
c.根據中斷IRQ異常處理,強制將 CPSR 模式位設為中斷模式,如下圖

d.強制 PC 從相關異常向量處取下條指令。跳轉到0x18實現中斷異常處理.
退出中斷IRQ異常時:
a. 將中斷IRQ所對應的是R14_irq寄存器並放入到 PC 中,如下圖,中斷IRQ所對應的是R14_irq寄存器,執行MOVS R14_svc .

b. 復制 SPSR 的內容返回給 CPSR 中。
c. 如果在異常進入時置位了中斷禁止標志位異常,清除中斷禁止標志位


3. 第12行中 .word:        類似於(unsigend long)      

以第12行中 _undefined_instruction: .word undefined_instruction為例
_undefined_instruction和undefined_instruction都是一個標號,
表示_undefined_instruction指向一個32位(4字節)地址,該地址用undefined_instruction符號變量代替。

用C語言表示就是:_undefined_instruction = &undefined_instruction

相當於PC從_undefined_instruction取值時,即undefined_instruction地址存到了PC中。
4.第20行中 .balignl 16,0xdeadbeef:
它的意思就是在以當前地址開始,在地址為16的倍數的指令位置的上一個指令填入為0xdeadbeef的內容。
此時當前地址剛好0x3c=60,由於ARM每個指令間隔4個字節,且64%16=0,所以在0x3c中填入0xdeadbeef。
它們的作用就是為內存做標記,插在那里,這個位置往前有特殊作用的內存,禁止訪問。
接下來繼續往下看start.o

reset: 
/* 設置CPSR程序程序狀態寄存器為管理模式   */                           
  mrs    r0,cpsr                   //MRS讀出CPSR寄存器值到R0
  bic    r0,r0,#0x1f               //將R0低5位清空
  orr    r0,r0,#0xd3               //R0與b'110 10011按位或,禁止IRQ和FIQ中斷,10011:復位需要設為管理模式(圖1)
  msr    cpsr,r0                   //MSR寫入CPSR寄存器

/* 關看門狗   */      
# define pWTCON        0x53000000          //(WitchDog Timer)看門狗定時器寄存器WTCON,設為0X0表示關閉看門狗
# define INTMOD     0X4A000004        //(Interrupt Mode)中斷模式寄存器INTMOD,相應位=0:IRQ模式,相應位=1:IRQ模式,
# define INTMSK        0x4A000008            //(Interrupt Mask)中斷屏蔽寄存器INTMSK,相應位=0:開啟中斷服務,相應位=1:關閉中斷服務
# define INTSUBMSK    0x4A00001C        //中斷次級屏蔽寄存器,相應位=0:開啟中斷服務,相應位=1:關閉中斷服務
# define CLKDIVN    0x4C000014            //時鍾分頻寄存器

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)        //宏定義CONFIG_S3C2410已定義
    ldr     r0, =pWTCON                                         //R0等於WTCON地址
    mov     r1, #0x0                                            //R1=0x0
    str     r1, [r0]                                            //關閉WTCON寄存器,pWTCON=0;
/* 關中斷   */  
    mov    r1, #0xffffffff                                         //R1=0XFFFF FFFF
    ldr    r0, =INTMSK                                             //R0等於INTMSK地址
    str    r1, [r0]                                                //*0x4A000008=0XFFFF FFFF(關閉所有中斷)
# if defined(CONFIG_S3C2410)
    ldr    r1, =0x3ff                                          //R1=0x3FF
    ldr    r0, =INTSUBMSK                                      //R0等於INTSUBMSK地址
    str    r1, [r0]                                            //*0x4A00001C=0x3FF(關閉次級所有中斷)
# endif


/* 判斷系統是從nand啟動的還是直接將程序下載到SDRAM中運行, 若系統從nand啟動,這里得到r0和r1值是不一樣的,r1=0x33f80000, 而r0=0x00000000。說明沒初始化SDRAM,ne(no equal)標識符為真,所以bl cpu_init_crit執行跳轉. */ adr r0, _start ldr r1, _TEXT_BASE cmp r0, r1 blne cpu_init_crit

CPU復位后是從這里開始執行,這里初始化了:
1.執行設置CPSR程序程序狀態寄存器為管理模式
2.關看門狗
3.屏蔽中斷
4.進入cpu_init_crit函數關閉MMU,進入lowlevel_init初始化13個BANK寄存器來初始化SDRAM

進入cpu_init_crit函數(關閉MMU):

cpu_init_crit:

mov    r0, #0
mcr    p15, 0, r0, c7, c7, 0    //關閉ICaches(指令緩存,關閉是為了降低MMU查表帶來的開銷)和DCaches(數據緩存,DCaches使用的是虛擬地址,開啟MMU之前必須關閉)
mcr    p15, 0, r0, c8, c7, 0    //使無效整個數據TLB和指令TLB(TLB就是負責將虛擬內存地址翻譯成實際的物理內存地址)

mrc    p15, 0, r0, c1, c0, 0
bic    r0, r0, #0x00002300    @ clear bits 13, 9:8 (--V- --RS) //bit8:系統不保護,bit9:ROM不保護,bit13:設置正常異常模式0x0~0x1c,即異常模式基地址為0X0
bic    r0, r0, #0x00000087    @ clear bits 7, 2:0 (B--- -CAM) //bit0~2:禁止MMU,禁止地址對齊檢查,禁止數據Cache.bit7:設為小端模式
orr    r0, r0, #0x00000002    @ set bit 2 (A) Align //bit2:開啟數據Cache
orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache //bit12:開啟指令Cache
mcr    p15, 0, r0, c1, c0, 0
/*
mcr/mrc:
Caches:是一種高速緩存存儲器,用於保存CPU頻繁使用的數據。在使用Cache技術的處理器上,當一條指令要訪問內存的數據時,
首先查詢cache緩存中是否有數據以及數據是否過期,如果數據未過期則從cache讀出數據。處理器會定期回寫cache中的數據到內存。
根據程序的局部性原理,使用cache后可以大大加快處理器訪問內存數據的速度。
其中DCaches和ICaches分別用來存放數據和執行這些數據的指令
TLB:就是負責將虛擬內存地址翻譯成實際的物理內存地址,TLB中存放了一些頁表文件,文件中記錄了虛擬地址和物理地址的映射關系。 當應用程序訪問一個虛擬地址的時候,會從TLB中查詢出對應的物理地址,然后訪問物理地址。TLB通常是一個分層結構, 使用與Cache類似的原理。處理器使用一定的算法把最常用的頁表放在最先訪問的層次。 這里禁用MMU,是方便后面直接使用物理地址來設置控制寄存器
*/ mov ip, lr //臨時保存當前子程序返回地址,因為接下來執行bl會覆蓋當前返回地址. bl lowlevel_init //跳轉到lowlevel_init(位於u-boot-1.1.6/board/100ask24x0/lowlevel_init.S) mov lr, ip //恢復當前返回地址 mov pc, lr //退出

進入lowlevel_init函數 (初始化各個bank和SDRAM)

 

lowlevel_init:
ldr r0, =SMRDATA //將SMRDATA的首地址(0x33F806C8)存到r0中 
ldr    r1, _TEXT_BASE //r1等於_TEXT_BASE內容,也就是TEXT_BASE(0x33F80000)
sub    r0, r0, r1 //將0x33F806C8與0x33F80000相減,得到現在13個寄存器值在NOR Flash上存放的開始地址
ldr    r1, =BWSCON //將BWSCON寄存器地址值存到r1中 (第一個存儲器寄存器首地址)
add r2, r0, #13*4 //每個寄存器4字節,r2=r0+13*4=NOR Flash上13個寄存器值最后一個地址
0: 
ldr r3, [r0], #4 //將r0的內容存到r3的內容中(r3等於SMRDATA里面值), 同時r0地址+=4;
str r3, [r1], #4 //將r3的內容存到r1所指的地址中(向寄存器地址里寫入r3值),同時r1地址+=4;
cmp r2, r0 // 判斷r2和r0
bne 0b //不等則跳轉到第6行繼續執行

mov pc, lr //跳回到返回地址中繼續執行

SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) 
//設置每個BWSCON,注意BANK0由硬件連線決定了
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
//設置BANKCON0~BANKCON5 
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
//設置BANKCON6~BANKCON7
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
//設置REFRESH,在S3C2440中11~17位是保留的,也即(Tchr<<16)無意義
.word 0xb1 //設置BANKSIZE,對於容量可以設置大寫,多出來的空內存會被自動檢測出來
.word 0x30 //設置MRSRB6
.word 0x30 //設置MRSRB7

 

返回到start.S繼續往下看

stack_setup: //設置棧,方便調用C函數 
ldr    r0, _TEXT_BASE    //代碼段的初始地址:r0=0x33f80000
sub    r0, r0, #CFG_MALLOC_LEN      //留出一段內存以實現malloc:r0=0x33f50000
sub    r0, r0, #CFG_GBL_DATA_SIZE  //再留出一段存一些全局參數的變量:r0=0x33F4FF80

#ifdef CONFIG_USE_IRQ
sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) //中斷與快中斷的棧:r0=0x33F4DF7C
#endif
sub    sp, r0, #12                 //留出12字節內存給abort異常 設置棧頂sp=r0-12;

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl clock_init                  //進入clock_init函數
#endif 

這里初始化了:

5.設置棧

6.進入clock_init函數設置時鍾

進入clock_init函數

 

void clock_init(void)
{
S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000; //定義一個S3C24X0_CLOCK_POWER型結構體指針,clk_power->LOCKTIME=0x4C000000

if (isS3C2410) //isS3C2410為0,執行else
{... ...}
else
{
/* FCLK:HCLK:PCLK = 1:4:8 */
clk_power->CLKDIVN = S3C2440_CLKDIV; //S3C2440_CLKDIV=0X05

/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */ 
"orr r1, r1, #0xc0000000\n" //使其從快總線模式改變為異步總線模式,在2440手冊上看到
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */ 
:::"r1" //:::"r1" 向GCC聲明:我對r1作了改動
);

/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF; //PLL 鎖定時間計數寄存器

/* configure UPLL */
clk_power->UPLLCON = S3C2440_UPLL_48MHZ; //UCLK=48Mhz

/* some delay between MPLL and UPLL */
delay (4000); //等待UCLK時鍾波形穩定

/* configure MPLL */
clk_power->MPLLCON = S3C2440_MPLL_400MHZ; //FCLK=400Mhz

/* some delay between MPLL and UPLL */ 
delay (8000); //等待FCLK時鍾波形穩定
}
}

 

返回到start.S繼續往下看

relocate:    /* 拷貝u-boot到SDRAM */
adr    r0, _start    //r0:當前代碼開始地址
ldr    r1, _TEXT_BASE    //r1:代碼段連接地址(0X3FF8 0000)
cmp r0, r1 //測試現在在FLASH中還是RAM中
beq clear_bss //若_start==_TEXT_BASE,表示已經進行代碼從Flash拷貝SDRAM了(通常是調試時直接下載到RAM中)

ldr    r2, _armboot_start //r2等於_armboot_start里的內容,也就是_start
ldr    r3, _bss_start //r3等於_bss_start里的內容,(在連接腳本u-boot.lds中定義,是代碼段的結束地址)
sub    r2, r3, r2    //r2等於代碼段長度

bl CopyCode2Ram    // r0: source, r1: dest, r2: size 將從NOR FLASH上代碼段(r0~r0+r2)拷貝到sdram地址(r3)0x3ff80000代碼段地址上




clear_bss:
ldr    r0, _bss_start    //r0=__bss_start
ldr    r1, _bss_end    //r0等於_bss_end里的內容,也就是_end(在u-boot.lds里定義,是存bss的結束地址)
mov r2, #0x00000000    //r2=0;用來清bss所有段

clbss_l:
str    r2, [r0]    //*r0=0;
add    r0, r0, #4 //r0+=4;
cmp    r0, r1 
ble    clbss_l //小於等於一直執行clbss_l


ldr    pc, _start_armboot //pc等於_start_armboot里的內容,也就是跳轉到start_armboot函數
_start_armboot:    .word start_armboot *(_start_armboot)=start_armboot

這里初始化了:

7.重定位(代碼從Flash拷貝至SDRAM中)
8.清bss段(未初始的全局/靜態變量)
9.跳轉到start_armboot函數(位於u-boot-1.1.6/lib_arm/borad.c,用來實現第2階段硬件相關的初始化)

本章小結:
uboot-第一階段硬件初始化主要實現了:
1.執行設置CPSR程序程序狀態寄存器為管理模式
2.關看門狗
3.屏蔽中斷
4.關閉MMU,初始化SDRAM
5.設置棧
6.時鍾設置
7.重定位(代碼從Flash拷貝至SDRAM中)
8.清bss段(未初始的全局/靜態變量)
9.跳轉到start_armboot函數(位於u-boot-1.1.6/lib_arm/borad.c,用來實現第2階段硬件相關的初始化)
接下來開始分析uboot-第二階段硬件初始化。


免責聲明!

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



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