學習目標: U-boot屬於兩個階段的Bootloader,第一階段的文件為cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平臺相關的,後者是開發板相關的。U-boot的第一階段主要的任務是一些系統的初始化工作,從大的方面可以分為以下幾個部 ...
學習目標:
- 對start.S中每一行代碼,都有基本瞭解
- 通過對start.S文件分析,對ARM920T架構的CPU的啟動過程,有更清楚理解
U-boot屬於兩個階段的Bootloader,第一階段的文件為cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平臺相關的,後者是開發板相關的。U-boot的第一階段主要的任務是一些系統的初始化工作,從大的方面可以分為以下幾個部分:
①設置CPU的模式
②關閉看門狗
③關閉中斷
④關閉MMU、設置RAM時序
⑤代碼重定位、設置堆棧SP指針
⑥清除BSS段
⑦異常中斷處理
下麵對start.S文件做出詳細分析,研究每一部分內容如何實現。
1 設置CPU的模式
110 reset: 111 /* 112 * set the cpu to SVC32 mode 113 */ 114 mrs r0,cpsr 115 bic r0,r0,#0x1f 116 orr r0,r0,#0xd3 117 msr cpsr,r0
cpsr為當前狀態寄存器,它的格式如下所示:
cpsr是32位寄存器,寄存器[31:28]位為狀態標誌位,[27:8]位保留未被使用,[7:0]位為控制位。控制位中的第7位和第6位是用來設置是否使能中斷請求和快速中斷請求,第5位是設置CPU操作狀態,當設置為1處理器執行在Thumb狀態,為0時執行在ARM狀態,第0~4位一起用來決定CPU的工作模式,模式位具體說明如下圖所示:
由上圖可知我們把M[4:0]設置為0b10011時,CPU工作在管理模式下,下麵來分析代碼如何實現。
第114行,將當前程式狀態寄存器內容,複製到r0寄存器內
第115行,將r0寄存器內低5位清零
第116行,將r0寄存器內容與立即數0xd3進行或運算,並把運算結果保存到r0寄存器,CPSR位域和含義如下表所示:
CPSR位域 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
位域含義 | I | F | T | M4 | M3 | M2 | M1 | M0 |
0xd3 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
即:
bit[7]=1->設置I位為1->關閉中斷IQR,bit[6]=1->設置F位為1->關閉FIQ中斷
bit[4:0]=0b10011->設置CPU位SVC管理模式
第117行,將r0寄存器內容,複製到cpsr寄存器中
2 關看門狗
119 /* turn off the watchdog */ 120 #if defined(CONFIG_S3C2400) 121 # define pWTCON 0x15300000 122 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */ 123 # define CLKDIVN 0x14800014 /* clock divisor register */ 124 #elif defined(CONFIG_S3C2410) 125 # define pWTCON 0x53000000 126 # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */ 127 # define INTSUBMSK 0x4A00001C 128 # define CLKDIVN 0x4C000014 /* clock divisor register */ 129 #endif 130 131 #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) 132 ldr r0, =pWTCON 133 mov r1, #0x0 134 str r1, [r0]
分析代碼之前,先來介紹如何查看CONFIG_S3C2400、CONFIG_S3C2410巨集是否定義。在start.S文件開頭部分,可以看到如下圖所示的頭文件的引用
打開config.h文件,可以看到config.h內容如下
所以#include <config.h>可以替換為#include <configs/smdk2410.h>,查看CONFIG_S3C2400、CONFIG_S3C2410巨集是否定義,在config/smdk2410.h文件中直接搜索即可。
(config.h文件是根據配置U-boot命令自動生成,關於U-boot配置命令介紹,可以參考這篇文章:https://www.cnblogs.com/053179hu/p/9266553.html)
下麵來分析代碼:
第120行,使用#if語句進行判斷是否定義了CONFIG_S3C2400這個巨集,若定義了這個巨集,則第121~123處語句有效
第124行,若120行#if語句為假,執行#elif處代碼,判斷是否定義CONFIG_S3C2410這個巨集,則125~128處語句有效
第131行,使用#if語句判斷是否定義CONFIG_S3C2400、CONFIG_S3C2410巨集的任何一個,若定義則執行下麵代碼
這裡我們引用的是smdk2410.h頭文件,在該頭文件中CONFIG_S3C2410巨集被定義,編譯時#elif下語句被編譯。125~128行是對硬體外設寄存器地址巨集定義,以pWTCON為例,編譯器進行預處理時遇到pWTCON即把它換為0x53000000,進行巨集定義的目的,是使編寫代碼更加方便。
第132行,將WTCON寄存器地址,複製到r0寄存器中
第133行,將立即數0移入寄存器r1中
第134行,將r1內容0,寫入到r0地址中,即WTCON寄存器地址
從硬體手冊可以看出當WTCON寄存器第0bit內容為0,即關閉定時器0複位功能,與上面代碼相吻合。
3 關閉中斷
139 mov r1, #0xffffffff 140 ldr r0, =INTMSK 141 str r1, [r0] 142 # if defined(CONFIG_S3C2410) 143 ldr r1, =0x3ff 144 ldr r0, =INTSUBMSK 145 str r1, [r0] 146 # endif 147 148 /* FCLK:HCLK:PCLK = 1:2:4 */ 149 /* default FCLK is 120 MHz ! */ 150 ldr r0, =CLKDIVN 151 mov r1, #3 152 str r1, [r0] 153 #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
第139~145行,向INMSK和INTSUBMSK寄存器相應為位寫1,屏蔽中斷源
第148~152,設置時鐘分頻繫數
4 關閉MMU、設置RAM時序
159 #ifndef CONFIG_SKIP_LOWLEVEL_INIT 160 bl cpu_init_crit 161 #endif
bl是跳轉指令,除了包含b指令的單純的跳轉功能,在跳轉之前,還把r15寄存器=PC=CPU地址,賦值給r14=lr,然後跳轉到對應位置,等要做的事情執行完畢後,再用mov pc, lr使得cpu再跳轉回來,所以整個邏輯就是調用子程式的意思。
上面的代碼意思很清晰,就是當沒有定義CONFIG_SKIP_LOWLEVEL_INIT的時候,就跳轉到cpu_init_crit的位置,頭文件中未定義CONFIG_SKIP_LOWLEVEL_INIT,CPU將跳轉到cpu_init_crit處執行程式,cpu_init_crit入口地址處代碼如下:
240 #ifndef CONFIG_SKIP_LOWLEVEL_INIT 241 cpu_init_crit: 242 /* 243 * flush v4 I/D caches 244 */ 245 mov r0, #0 246 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ 247 mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ 248 249 /* 250 * disable MMU stuff and caches 251 */ 252 mrc p15, 0, r0, c1, c0, 0 253 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) 254 bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) 255 orr r0, r0, #0x00000002 @ set bit 2 (A) Align 256 orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache 257 mcr p15, 0, r0, c1, c0, 0 258 259 /* 260 * before relocating, we have to setup RAM timing 261 * because memory timing is board-dependend, you will 262 * find a lowlevel_init.S in your board directory. 263 */ 264 mov ip, lr 265 bl lowlevel_init 266 mov lr, ip 267 mov pc, lr 268 #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
MCR 指令用於將ARM 處理器寄存器中的數據傳送到協處理器寄存器中,格式為:
MCR 協處理器編號,協處理器操作碼1,源寄存器,目的寄存器1,目的寄存器2,協處理器操作碼2。
其中協處理器操作碼1 和協處理器操作碼2 為協處理器將要執行的操作,
源寄存器為ARM 處理器的寄存器,目的寄存器1 和目的寄存器2 均為協處理器的寄存器。
第242~246行,使I/D cache失效: 協處理寄存器操作,將r0中的數據寫入到協處理器p15的c7中,c7對應cp15的cache控制寄存器
第247行,使TLB操作寄存器失效:將r0數據送到cp15的c8、c7中。C8對應TLB操作寄存器
第252行,將c1、c0的值寫入到r0中
第257行,將設置好的r0值寫入到協處理器p15的c1、c0中,關閉MMU
第264行,將lr寄存器內容保存到ip寄存器中,用於子程式調用返回
第265行,跳轉到lowlevel_init入口地址執行,lowlevel_init在lowlevel_init.S文件中,代碼如下:
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 140 ldr r1, =BWSCON /* Bus Width Status Controller */ 141 add r2, r0, #13*4 142 0: 143 ldr r3, [r0], #4 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: 155 .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) 156 .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) 157 .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) 158 .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) 159 .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) 160 .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) 161 .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) 162 .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) 163 .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) 164 .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) 165 .word 0x32 166 .word 0x30 167 .word 0x30
此處代碼實現記憶體控制器初始化,為後面代碼重定位做準備。
第137行,將保存設置記憶體控制器的參數的初始地址(連接地址),保存到r0寄存器中。
第138行,將程式連接的入口地址存入到r1寄存器中
第139行,r0=r0-r1,獲取保存設置記憶體控制器的參數的初始地址(此處代碼未進行定位,代碼處於載入地址中,獲取的是在存儲器存放的物理地址)
第140行,將記憶體控制器的第一個寄存器地址存到r1寄存器中
第141行,獲取保存設置記憶體控制器的參數的結束地址地址(此處代碼未進行定位,代碼處於載入地址中,獲取的是在存儲器存放的物理地址)
第142~146行,將標號SMRDATA地址處存放的參數,寫入到相應寄存器中,設置記憶體控制器工作方式
第149行,程式調用返回,返回調用節點
第266~267,程式調用返回,返回調用節點(PC寄存器內容為bl cpu_init_crit指令地址+4)
5 代碼重定位、設置堆棧SP指針
163 #ifndef CONFIG_SKIP_RELOCATE_UBOOT 164 relocate: /* relocate U-Boot to RAM */ 165 adr r0, _start /* r0 <- current position of code */ 166 ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ 167 cmp r0, r1 /* don't reloc during debug */ 168 beq stack_setup 169 170 ldr r2, _armboot_start 171 ldr r3, _bss_start 172 sub r2, r3, r2 /* r2 <- size of armboot */ 173 add r2, r0, r2 /* r2 <- source end address */ 174 175 copy_loop: 176 ldmia r0!, {r3-r10} /* copy from source address [r0] */ 177 stmia r1!, {r3-r10} /* copy to target address [r1] */ 178 cmp r0, r2 /* until source end addreee [r2] */ 179 ble copy_loop 180 #endif /* CONFIG_SKIP_RELOCATE_UBOOT */ 181 182 /* Set up the stack */ 183 stack_setup: 184 ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ 185 sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ 186 sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */ 187 #ifdef CONFIG_USE_IRQ 188 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) 189 #endif 190 sub sp, r0, #12 /* leave 3 words for abort-stack */
第165行,獲取當前代碼存放起始地址,存入r0寄存器
第166行,獲取代碼連接的初始地址,存入r1寄存器
第167~168行,比較代碼當前存放初始地址和設置連接地址是否相等,如果代碼當前存放地址等於連接地址,則跳轉stack_setup入口地址,設置堆棧;否則,執行重定位操作
第170~173行,獲取U-boot代碼長度,併進長度值存入r2寄存器中
第175~179行,迴圈操作,將代碼從載入地址,複製到連接地址。
ldmia r0!, {r3-r10} 從源地址[r0]讀取4個位元組到寄存器(低地址存入低編號寄存器,高地址存入高編號寄存器),每讀一次就更新一次r0地址 ,r0=r0+4
存放形式 [r0]-->r3 r0=r0+4;[r0]-->r4 r0=r0+4;........................[r0]-->r10 r0=r0+4
stmia r1!, {r3-r10} 拷貝寄存器r3-r10的值保存到 [r1]指明的地址(低地址存入低編號寄存器,高地址存入高編號寄存器),每寫一個位元組,r1=r1+4,
存放形式 r3-->[r1] r1=r1+4;r4-->[r1] r1=r1+4;........................r10-->[r1] r1=r1+4
第183~190行,設置堆棧,CFG_MALLOC_LEN 、CFG_GBL_DATA_SIZE、CONFIG_STACKSIZE_IRQ、CONFIG_STACKSIZE_FIQ等巨集在smdk2410.h中有定義,記憶體使用如下圖所示:
6 清除bss段
85 .globl _bss_start 86 _bss_start: 87 .word __bss_start 88 89 .globl _bss_end 90 _bss_end: 91 .word _end
192 clear_bss: 193 ldr r0, _bss_start /* find start of bss segment */ 194 ldr r1, _bss_end /* stop here */ 195 mov r2, #0x00000000 /* clear */ 196 197 clbss_l:str r2, [r0] /* clear loop... */ 198 add r0, r0, #4 199 cmp r0, r1 200 ble clbss_l
第85~91行,_bss_start,_bss_end為標號地址中存放bss段起始地址和結束地址,_bss_start和_end在連接腳本中定義\u-boot-1.1.6\board\smdk2410\u-boot.lds,程式連接時動態確定。
第193~194行,把bss段起始地址存入r0寄存器中,結束地址存放到r1寄存器中。
第197~200行,迴圈操作,將bss段中的記憶體清零
7 跳轉到u-boot第二階段入口
223 ldr pc, _start_armboot 224 225 _start_armboot: .word start_armboot
初始化外設完成之後,程式跳轉到u-boot第二階段入口函數start_armboot。ldr pc,_start_armboot為絕對跳轉命令,pc值等於_start_armboot的連接地址,程式跳到SDRAM中執行,再辭之前程式都是在flash中運行的,絕對跳轉必須在初始SDRAM,執行代碼重定位之後才能進行。
8 異常中斷處理
ARM920T架構CPU異常向量表如下圖所示:
當CPU發生異常時,程式計數器跳到相應異常向量表地址處讀取指令。由上圖很容易看出,不同異常入口地址之間只有4個位元組,在這裡肯定不能存放異常處理函數。因此我們在中斷向量地址處存放相應跳轉指令,當發生異常時CPU跳到相應異常地址,讀取跳轉指令,跳轉到相應異常處理函數處執行異常處理。start.S代碼處理如下:
41 .globl _start 42 _start: b reset 43 ldr pc, _undefined_instruction 44 ldr pc, _software_interrupt 45 ldr pc, _prefetch_abort 46 ldr pc, _data_abort 47 ldr pc, _not_used 48 ldr pc, _irq 49 ldr pc, _fiq 50 51 _undefined_instruction: .word undefined_instruction 52 _software_interrupt: .word software_interrupt 53 _prefetch_abort: .word prefetch_abort 54 _data_abort: .word data_abort 55 _not_used: .word not_used 56 _irq: .word irq 57 _fiq: .word fiq 58 59 .balignl 16,0xdeadbeef
globl是個關鍵字,意思很簡單,就是相當亍C語言中的extern,聲明此變數,並且告訴鏈接器此變數是全局的,外部可以訪問。
第41行,是上電或者複位後執行第一題指令,通過b命令跳轉到reset地址處進行一系列初始化操作,reset地址標號後的代碼已經在上面分析了。
第43~57行,以_undefined_instruction為例,就是,此處分配了一個word=32bit=4位元組的地址空間,裡面存放的值是undefined_instruction。
而此處_undefined_instruction也就是該地址空間的地址了。用C語言來表達就是:
_undefined_instruction = &undefined_instruction
或 *_undefined_instruction = undefined_instruction
在後面的代碼,我們可以看到,undefined_instruction也是一個標號,即一個地址值,對應著就是在發生“未定義指令”的時候,系統所要去執行的代碼。(其他幾個對應的“軟體中斷”,“預取指錯誤”,“數據錯誤”,“未定義”,“(普通)中斷”,“快速中斷”,也是同樣的做法,跳轉到對應的位置執行對應的代碼。)
第59行,意思就是,接下來的代碼,都要16位元組對齊,不足之處,用0xdeadbeef填充。
總結:uboot第一階段
1、完成了硬體設備初始化操作
2、為載入Bootloader的第二階段準備好RAM空間
3、實現代碼重定位
4、設置好棧,並跳轉到第二階段入口函數