基于ARM核的Bootloader代碼的分析與設計
一 . 引言:
對于 PC 機,其開機后的初始化處理器配置、硬件初始化等操作是由 BIOS ( Basic Input /Output System )完成的,但對于嵌入式系統(tǒng)來說,出于經(jīng)濟性、價格方面的考慮一般不配置 BIOS ,因此我們必須自行編寫完成這些工作的程序,這就是所需要的開機程序。而在嵌入式系統(tǒng)中,通常并沒有像 BIOS 那樣的固件程序,啟動時用于完成初始化操作的這段代碼被稱為 Bootloader 程序,因此整個系統(tǒng)的加載啟動任務就完全由 Bootloader 來完成。簡單地說,通過這段程序,可以初始化硬件設備、建立內(nèi)存空間的映射圖(有的 CPU 沒有內(nèi)存映射功能如 S3C44B0 ),從而將系統(tǒng)的軟硬件環(huán)境設定在一個合適的狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核、運行用戶應用程序準備好正確的環(huán)境。 Bootloader 依賴于實際的硬件和應用環(huán)境,因此要為嵌入式系統(tǒng)建立一個通用、標準的 Bootloader 是非常困難的。 Bootloader 也依賴于具體的嵌入式板級設備的配置,這也就是說,對于兩塊不同的嵌入式主板而言,即使它們是基于同一 CPU 而構建,要想讓運行在一塊板子上的 Bootloader 程序也能運行在另一塊板子上,通常都需要修改 Bootloader 的源程序。
二 . 啟動流程
系統(tǒng)加電復位后,幾乎所有的 CPU 都從由復位地址上取指令。比如,基于 ARM7TDMI內(nèi)核的CPU 在復位時通常都從地址 0x00000000 處取它的第一條指令。而以微處理器為核心的嵌入式系統(tǒng)通常都有某種類型的固態(tài)存儲設備(比如 EEPROM 、 FLASH 等)被映射到這個預先設置好的地址上。因此在系統(tǒng)加電復位后,處理器將首先執(zhí)行存放在復位地址處的程序。通過集成開發(fā)環(huán)境可以將 Bootloader 定位在復位地址開始的存儲空間內(nèi),因此 Bootloader 是系統(tǒng)加電后、操作系統(tǒng)內(nèi)核或用戶應用程序運行之前,首先必須運行的一段程序代碼。對于嵌入式系統(tǒng)來說,有的使用操作系統(tǒng),也有的不使用操作系統(tǒng),比如功能簡單僅包括應用程序的系統(tǒng),但在系統(tǒng)啟動時都必須執(zhí)行Bootloader ,為系統(tǒng)運行準備好軟硬件運行環(huán)境。
系統(tǒng)的啟動通常有兩種方式,一種是可以直接從Flash啟動,另一種是可以將壓縮的內(nèi)存映像文件從 Flash (為節(jié)省Flash 資源、提高速度)中復制、解壓到RAM ,再從RAM 啟動。當電源打開時,一般的系統(tǒng)會去執(zhí)行ROM(應用較多的是Flash)里面的啟動代碼。這些代碼是用匯編語言編寫的,其主要作用在于初始化CPU 和板上的必備硬件如內(nèi)存、中斷控制器等。有時候用戶還必須根據(jù)自己板子的硬件資源情況做適當?shù)恼{(diào)整與修改。
系統(tǒng)啟動代碼完成基本軟硬件環(huán)境初始化后,對于有操作系統(tǒng)的情況下,啟動操作系統(tǒng)、啟動內(nèi)存管理、任務調(diào)度、加載驅動程序等,最后執(zhí)行應用程序或等待用戶命令;對于沒有操作系統(tǒng)的系統(tǒng)直接執(zhí)行應用程序或等待用戶命令。
啟動代碼是用來初始化電路以及用來為高級語言寫的軟件做好運行前準備的一小段匯編語言,在商業(yè)實時操作系統(tǒng)中,啟動代碼部分一般被稱為板級支持包,英文縮寫為 BSP 。它的主要功能就是:電路初始化和為高級語言編寫的軟件運行做準備。系統(tǒng)啟動流程如圖 1 所示,主要的過程如下:
1. 啟動代碼的第一步是設置中斷和異常向量。
2. 完成系統(tǒng)啟動所必須的最小配置,某些處理器芯片包含一個或幾個全局寄存器,這些寄存器必須在系統(tǒng)啟動的最初進行配置。
3. 設置看門狗,用戶設計的部分外圍電路如果必須在系統(tǒng)啟動時初始化,就可以放在這一步。
4. 配置系統(tǒng)所使用的存儲器,包括 Flash , SRAM 和 DRAM 等,并為他們分配地址空間。如果系統(tǒng)使用了 DRAM 或其它外設,就需要設置相關的寄存器,以確定其刷新頻率,數(shù)據(jù)總線寬度等信息,初始化存儲器系統(tǒng)。有些芯片可通過寄存器編程初始化存儲器系統(tǒng),而對于較復雜系統(tǒng)通常集成有 MMU 來管理內(nèi)存空間。
5. 為處理器的每個工作模式設置棧指針, ARM 處理器有多種工作模式,每種工作模式都需要設置單獨的??臻g。
6. 變量初始化,這里的變量指的是在軟件中定義的已經(jīng)賦好初值的全局變量,啟動過程中需要將這部分變量從只讀區(qū)域,也就是 Flash 拷貝到讀寫區(qū)域中,因為這部分變量的值在軟件運行時有可能重新賦值。還有一種變量不需要處理,就是已經(jīng)賦好初值的靜態(tài)全局變量,這部分變量在軟件運行過程中不會改變,因此可以直接固化在只讀的 Flash 或 EEPROM 中。
7. 數(shù)據(jù)區(qū)準備,對于軟件中所有未賦初值的全局變量,啟動過程中需要將這部分變量所在區(qū)域全部清零。
8. 最后一步是調(diào)用高級語言入口函數(shù),比如 main 函數(shù)等。
三 . 程序分析
下面根據(jù)實際經(jīng)過測試的代碼詳細講述系統(tǒng)的啟動過程。
.text /* 將此操作符開始的代碼編譯到代碼段或代碼段子段中 */
/* 集成開發(fā)環(huán)境( IDE )可以通過鏈接腳本文件將下面的語句定位在零起始地址,系統(tǒng)上電后 CPU 從此處開始執(zhí)行 */
ENTRY:
b ResetHandler /* 跳至 ResetHandler ,此句被定位在零起始地址 */
/* 除用戶模式外的其他 6 種模式稱為特權模式。特權操作模式主要處理異常和監(jiān)控調(diào)用(有時稱為軟件中斷),它們可以自由的訪問系統(tǒng)資源和改變模式。特權模式中除系統(tǒng)模式以外的 5 種模式又稱為異常模式,下面的代碼用于出現(xiàn)異常時 CPU 就會根據(jù)以下的語句自動跳轉到對應的異常處理程序處 */
b HandlerUndef /* handlerUndef */
b HandlerSWI /* SWI interrupt handler */
b HandlerPabort /* handlerPAbort */
b HandlerDabort /* handlerDAbort */
b . /* handlerReserved */
b HandlerIRQ
b HandlerFIQ
...
...
ResetHandler: /* 上電后跳轉到此處開始執(zhí)行 */
Ldr r0,=WTCON /* 禁止看門狗 */
ldr r1,=0x0
str r1,[r0]
ldr r0,=INTMSK /* 屏蔽所有中斷請求 */
ldr r1,=0x07ffffff
str r1,[r0]
/* 設置時鐘控制寄存器 */
ldr r0,=LOCKTIME[!--empirenews.page--]
ldr r1,=0xfff
str r1,[r0]
.if PLLONSTART
ldr r0,=PLLCON /* 設置 PLL */
ldr r1,=((M_DIV<<12)+(P_DIV<<4)+S_DIV) /*Fin=8MHz,Fout=64MHz*/
str r1,[r0]
.endif
ldr r0,=CLKCON
ldr r1,=0x7ff8 /* 所有單元時鐘允許 */
str r1,[r0]
/* 為 BDMA 設置復位值 */
ldr r0,=BDIDES0
ldr r1,=0x40000000 /* BDIDESn 復位值應為 0x40000000 */
str r1,[r0]
ldr r0,=BDIDES1
ldr r1,=0x40000000 /* BDIDESn 復位值應為 0x40000000 */
str r1,[r0]
/* 設置存儲器控制寄存器,存儲器的配置數(shù)據(jù)都存儲在 SMRDATA 為起始地址的數(shù)據(jù)表中,下面的代碼可以一次將預先配置好的初始化數(shù)據(jù)存入與存儲器控制器相關的 13 個寄存器,這些寄存器則是以 0x01c80000 為起始地址的 13 個連續(xù)的 32 位寄存器 */
ldr r0,=SMRDATA
ldmia r0,{r1-r13}
ldr r0,=0x01c80000 /* BWSCON 存儲控制寄存器地址 */
stmia r0,{r1-r13}
/* 初始化堆棧 */
/* CPU 復位后是處于管理模式下的,所以首先要初始化管理模式下的堆棧寄存器 */
ldr sp, =SVCStack
/* 由于處理器的每種運行模式都要有自己獨立的物理堆棧寄存器 R13 ,在用戶應用程序的初始化部分,一般都要初始化每種模式下的 R13 ,使其指向該運行模式的棧空間,這樣,當程序的運行進入異常模式時,可以將需要保護的寄存器放入 R13 所指向的堆棧,而當程序從異常模式返回時,則從對應的堆棧中恢復,采用這種方式可以保證異常發(fā)生后程序的正常執(zhí)行 */
bl InitStacks /* 跳轉至其它堆棧初始化程序并返回 */
/* 設置 IRQ 中斷處理 */
/*44B0 有兩種中斷模式:一種是沒有中斷向量表;一種是使用了中斷向量表,使用中斷向量表只能是 IRQ 方式。當使用中斷向量表的時候,中斷發(fā)生時由 S3C44B0 的中斷控制器根據(jù)中斷向量表,利用硬件方式自動跳轉到相應的中斷處理服務程序所在的位置;不使用中斷向量表時按下面的代碼,利用軟件方式跳轉而進行中斷處理,因為 S3C44B0 有 30 個中斷源,所以需要程序判斷以確定調(diào)用那個中斷服務程序 */
ldr r0,=HandleIRQ /* 如果在 0x18 和 0x1c 地址處無 “subs pc,lr,#4”*/
ldr r1,=IsrIRQ /* 為了中斷正常返回這些語句是必須的 */
str r1,[r0]
/* 拷貝讀寫區(qū)域數(shù)據(jù) / 數(shù)據(jù)區(qū)準備,將系統(tǒng)需要讀寫的數(shù)據(jù)和變量從 ROM 拷貝到 RAM 里。 Image_RO_Limit 、 Image_RW_Base 、 Image_ZI_Base 等這些符號還會在另外的鏈接腳本文件中出現(xiàn),這些符號是用來定位程序各個段的參考信息。集成開發(fā)環(huán)境在編譯鏈接的時候會根據(jù)我們編寫的程序,把它們轉換成用來對各個段定位的地址信息 */
LDR r0, =Image_RO_Limit /* 取只讀數(shù)據(jù)區(qū)域地址指針 */
LDR r1, =Image_RW_Base /* 準備執(zhí)行拷貝操作 */
LDR r3, =Image_ZI_Base
CMP r0, r1 /* 檢查是否相同 */
BEQ F1 /* 相同則跳過拷貝操作 */
F0:
CMP r1, r3 /* 執(zhí)行拷貝操作 */
LDRCC r2, [r0], #4
STRCC r2, [r1], #4
BCC F0
F1:
LDR r1, =Image_ZI_Base /* 零數(shù)據(jù)準備區(qū)起始地址 */
MOV r2, #0
F2:
CMP r3, r1 /* 執(zhí)行數(shù)據(jù)區(qū)清零 */
STRCC r2, [r3], #4
BCC F2
MRS r0, CPSR
BIC r0, r0, #NOINT /* 中斷請求允許 */
MSR CPSR_cxsf, r0
/* 跳轉到 C 入口程序 */
BL Main
B .
四 . 總結:
啟動過程中的初始化程序就是初始化 CPU 內(nèi)部各個關鍵的寄存器、配置外圍硬件電路相關寄存器、建立中斷向量表等,然后跳轉到一般由高級語言編寫的主函數(shù)的應用程序代碼去執(zhí)行,這樣就可以利用高級語言來編寫完成系統(tǒng)設計所要求的各種功能。初始化的過程對大多數(shù)初學者來說,比較難理解的是中斷的處理和一些少見的操作符號,這些符號多是一些宏定義或系統(tǒng)用于在內(nèi)存空間中對各個段的定位標識符號。掌握了 S3C44B0的啟動代碼之后,對系統(tǒng)功能程序設計會起到很大的幫助,是進行下一步程序設計的基礎。