分析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-第二階段硬件初始化。