U-boot在開發(fā)板上移植過程詳解(1)---bootloader架構(gòu)分析本例中采用的同樣是前邊一貫的實(shí)驗(yàn)板,這里就不對板子資源做進(jìn)一步介紹了。
我們知道,bootloader是系統(tǒng)上電后最初加載運(yùn)行的代碼。它提供了處理器上電復(fù)位后最開始需要執(zhí)行的初始化代碼。在PC機(jī)上引導(dǎo)程序一般由BIOS開始執(zhí)行,然后讀取硬盤中位于MBR(Main Boot Record,主引導(dǎo)記錄)中的Bootloader(例如LILO或GRUB),并進(jìn)一步引導(dǎo)操作系統(tǒng)的啟動。然而在嵌入式系統(tǒng)中通常沒有像BIOS那樣的固件程序,因此整個系統(tǒng)的加載啟動就完全由bootloader來完成。它主要的功能是加載與引導(dǎo)內(nèi)核映像。
一個嵌入式的存儲設(shè)備通過通常包括四個分區(qū),第一分區(qū)存放的當(dāng)然是u-boot,第二個分區(qū)存放著u-boot要傳給系統(tǒng)內(nèi)核的參數(shù),第三個分區(qū)是系統(tǒng)內(nèi)核(kernel),第四個分區(qū)則是根文件系統(tǒng)。如下圖所示:
圖一 固態(tài)存儲設(shè)備的典型空間分配結(jié)構(gòu)
第一部分:Bootloader啟動模式
Bootloader的啟動過程可以是單階段的,也可以是多階段的。多階段的bootloader比單階段的bootloader提供更為復(fù)雜的功能。以及更好的移植性,比如U-bot。
第一階段:
Bootloader執(zhí)行最基本的硬件初始化操作。如關(guān)閉中斷,關(guān)閉看門狗以避免處理器被復(fù)位,以及關(guān)閉MMU功能,關(guān)閉處理器緩存(數(shù)據(jù)緩存一定要關(guān)閉,指令緩存可以打開),設(shè)置系統(tǒng)時鐘,初始化內(nèi)存等。這一階段代碼通常由匯編代碼編寫,為了運(yùn)行下一階段的C語言程序還必須設(shè)置好堆棧。如果是從NAND Flash啟動,則必須通過NAND Flash控制器將bootloader代碼復(fù)制到內(nèi)存。
第二階段:
這一階段一般用C語言編寫,大致分為一下幾步:
1)初始化各種硬件設(shè)備,比如設(shè)置處理器正常工作的時鐘頻率,初始化串口等。
2)檢測系統(tǒng)內(nèi)存,主要是確定系統(tǒng)內(nèi)存容量以及其地址空間信息。
3)將內(nèi)核映像文件加載到內(nèi)存。
4)準(zhǔn)備內(nèi)核引導(dǎo)參數(shù)。
5)跳轉(zhuǎn)到內(nèi)核的第一條指令處,開始執(zhí)行內(nèi)核初始化代碼,控制權(quán)轉(zhuǎn)移到內(nèi)核代碼,bootload的使命結(jié)束。
第二部分:Bootloader的操作模式
一般的bootload而都包含兩種不同的操作模式:啟動加載模式和下載模式
啟動加載模式:這種模式也稱自主模式,即bootloader從目標(biāo)機(jī)上的某個固體存儲設(shè)備上將操作系統(tǒng)加載到內(nèi)存中運(yùn)行,這個過程沒有用戶的介入。這種模式是正
常的工作模式,最終的產(chǎn)品發(fā)布時必須工作在這種模式下。
下載模式:在這種模式下,目標(biāo)機(jī)上的bootloader將通過串口,網(wǎng)絡(luò)連接或者其他通信手段從主機(jī)下載文件,比如下載內(nèi)核映像或根文件系統(tǒng)映像等。從主機(jī)下載的文件通常被保存在目標(biāo)機(jī)的內(nèi)存中,然后再寫入到目標(biāo)機(jī)上的Flash等固態(tài)存儲設(shè)備中。這種工作模式通常在第一次安裝內(nèi)核與跟文件系統(tǒng)時使用?;蛘咴谙到y(tǒng)更新時使用。進(jìn)行嵌入式系統(tǒng)調(diào)試時一般也讓bootloader工作在這一模式下。
第三部分:Arm bootloader的特點(diǎn)
要實(shí)現(xiàn)一個通用的bootloader是一件不可能的事情,但是還是可以根據(jù)Arm的體系結(jié)構(gòu),從理論上總結(jié)出一些Arm平臺上bootloader的共性,這些共性只能局限于bootloader的基本功能。
對于一個運(yùn)行于Arm平臺的系統(tǒng)來說,bootloader作為引導(dǎo)與加載內(nèi)核映像的工具需要提供一下幾個功能:
1)bootloader必須能夠初始化內(nèi)存。
2)雖然系統(tǒng)的啟動并不一定依賴串口,但一般來說bootloader應(yīng)該初始化至少一個串口,通過它與主機(jī)進(jìn)行通信,以便進(jìn)行開發(fā),調(diào)試和維護(hù)工作。
3)這是linux內(nèi)核所要求的,如果不給出內(nèi)核參數(shù),則內(nèi)核就會使用其默認(rèn)參數(shù)。
4)一般來說,內(nèi)核映像必須在內(nèi)存運(yùn)行,所以必須從其他非易失存儲介質(zhì)上復(fù)制到內(nèi)存。
5)讓執(zhí)行流程跳轉(zhuǎn)到內(nèi)核映像的入口。
啟動內(nèi)核時,系統(tǒng)必須處于指定的狀態(tài),包括處理器模式,MMU和緩存的設(shè)置,寄存器的設(shè)置等方面。
處理器模式)處理器應(yīng)處于SVC模式,在這種特權(quán)模式下,內(nèi)核才能執(zhí)行所有的指令。中斷必須關(guān)閉。在異常向量表尚未初始化的情況下,如果發(fā)生中斷,將導(dǎo)致系統(tǒng)崩潰。一般來說,bootloader本身也沒有必要支持中斷的實(shí)現(xiàn),這屬于內(nèi)核的管理范圍。
MMU和緩存設(shè)置)MMU必須關(guān)閉。啟動MMU進(jìn)入保護(hù)模式是內(nèi)核的工作。而bootloader本身工作在實(shí)模式下,所有對地址的操作使用的都是物理地址,不存在虛擬地址。數(shù)據(jù)緩存必須關(guān)閉,bootloader的主要功能是裝載內(nèi)核映像,映像數(shù)據(jù)必須真實(shí)寫回內(nèi)存中,不能僅放在處理器的緩存中,所以數(shù)據(jù)緩存必須關(guān)閉。指令緩存可以打開,一般情況下,推薦將指令緩存也關(guān)閉。
寄存器設(shè)置)寄存器R0的值應(yīng)為0,R1的值表示機(jī)器類型,R2的值則是引導(dǎo)參數(shù)列表在內(nèi)存中的起始地址。這三個寄存器是在最后啟動內(nèi)核時需要設(shè)置的。
第四部分:U-boot源碼分析
在實(shí)際使用中,U-boot被固話在CPU的上電/復(fù)位啟動地址處(通常在非易失存儲器中)。每當(dāng)嵌入式設(shè)備上電/復(fù)位時,CPU總是從啟動地址(U-boot)處啟動。U-bo
ot啟動后,首先初始化各種硬件設(shè)備,如CPU,緩存,存儲器,MMU,總線控制器,各種I/O接口等,然后從遠(yuǎn)程主機(jī)或者本地非易失存儲設(shè)備中裝載可執(zhí)行文件或操作系統(tǒng)
,為整個嵌入式系統(tǒng)準(zhǔn)備運(yùn)行環(huán)境。要使用U-boot,最初必須使用某種硬件支持的方式將U-boot映像寫入非易失存儲器中。比如我這里板子上沒有任何的bootloader
可以使用JTAG接口將U-boot映像寫入Flash的開始處。
U-boot采用了一種高度模塊化的編程方式,不同功能類別的代碼分別放在不同的目錄中,幾個U-boot常用到的目錄分析如下所示:
1 board)這個目錄中存放了所有U-boot支持的目標(biāo)板的子目錄。在這個目錄中一般是針對特定目標(biāo)板的初始化和操作代碼。
2 cpu)這個目錄中存放了U-boot支持的所有CPU類型。
3 common)這個目錄中存放獨(dú)立于處理器體系架構(gòu)的通用代碼,包括U-boot的一些公共命令的實(shí)現(xiàn)。一般來說,其中以cmd_*.c命令的文件就是相對命令的實(shí)
現(xiàn)。比如cmd_bootm.c就是對命令bootm的實(shí)現(xiàn)。
4 drivers)這個目錄中存放的是各種外設(shè)接口的驅(qū)動程序。
5 fs)這個目錄中存放了U-boot支持的文件系統(tǒng)。
6 include)這個目錄是存放各種CPU及目標(biāo)板的頭文件和配置文件的公共目錄,其中的configs目錄存放了各種目標(biāo)板的配置頭文件。針對不同的板子,里邊的配置
文件要根據(jù)實(shí)際情況進(jìn)行修改。
7 lib_XXX)這個目錄存放XXX體系架構(gòu)的處理器的相關(guān)支持。
8 net)這個目錄用于存放與網(wǎng)絡(luò)功能相關(guān)的文件。
下節(jié),就要開始對U-boot源碼中的關(guān)鍵功能的實(shí)現(xiàn)進(jìn)行分析,主要是從一下幾個方面:
1)使用匯編語言編寫的第一階段代碼
2)第二階段代碼命令的實(shí)現(xiàn)
3)第二階段操作系統(tǒng)引導(dǎo)機(jī)制的實(shí)現(xiàn)
U-boot在開發(fā)板上移植過程詳解(2)---U-boot實(shí)現(xiàn)源碼分析(第一階段)前邊,我們說了,一般的bootloader都分為兩個階段。我在講U-boot實(shí)現(xiàn)源碼分析時,也是按照這連個階段來分析,如果對這兩個階段不清楚,請看前邊的博客。好了,開始今天的主題:U-boot在開發(fā)板上移植過程詳解(2)---U-boot實(shí)現(xiàn)源碼分析(start.S分析)
第一階段:
1)一些基本的硬件初始化工作
u-boot對應(yīng)的第一階段代碼放在cpu/arm920t/start.S文件中,入口代碼如下:
.globl _start ;global聲明一個符號可被其它文件引用,相當(dāng)于聲明了一個全局變量,.globl與.global相同
_start: b reset ;b是不帶返回的跳轉(zhuǎn)(bl是帶返回的跳轉(zhuǎn)),意思是無條件直接跳轉(zhuǎn)到reset標(biāo)號出執(zhí)行程序
ldr pc, _undefined_instruction ;ldr相當(dāng)于mov操作
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
;.word偽操作用于分配一段字內(nèi)存單元(分配的單元都是字對齊的),并用偽操作中的expr初始化。
_undefined_instruction: .word undefined_instruction ;就是在當(dāng)前地址,即_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
這部分就是異常向量表。當(dāng)系統(tǒng)上電或復(fù)位后,將執(zhí)行第一條指令,即跳轉(zhuǎn)到標(biāo)簽為reset的代碼處執(zhí)行,具體如下:
reset: ;設(shè)置CPU為SVC32管理模式
mrs r0,cpsr ;mrs將狀態(tài)寄存器cpsr(current program status register)的內(nèi)容傳送至通用寄存器
bic r0,r0,#0x1f ;r0和0x1f(00011111)的反碼進(jìn)行位與,是把 r0后面5位清零
orr r0,r0,#0xd3 ;r0和0xd3(11010011)進(jìn)行位或,最后得到r0=11010011,目的是設(shè)置r0的后5位為10011,讓ARM進(jìn)入SVC特權(quán)模式
msr cpsr,r0
#if defined(CONFIG_S3C2400) ;關(guān)閉看門狗
# define pWTCON 0x15300000 ;看門狗寄存器
# define INTMSK 0x14400008 ;中斷屏蔽寄存器
# define CLKDIVN 0x14800014 ;時鐘分頻寄存器
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008
# define INTSUBMSK 0x4A00001C ;次級中斷屏蔽寄存器
# define CLKDIVN 0x4C000014 ;時鐘分頻寄存器
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
mov r1, #0xffffffff ;屏蔽所有中斷
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
ldr r0, =CLKDIVN ;設(shè)置時鐘
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit ;跳轉(zhuǎn)并把轉(zhuǎn)移后面緊接的一條指令地址保存到鏈接寄存器LR(R14)中,以此來完成子程序的調(diào)用
#endif
上面的代碼將CPU設(shè)為管理模式,關(guān)閉看門狗,屏蔽中斷并設(shè)置中斷,最后調(diào)用cpu_init_crit函數(shù)進(jìn)行cpu的初始化,代碼如下:
cpu_init_crit: ;清除指令和數(shù)據(jù)緩存
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 ;mcr{條件} 協(xié)處理器編碼, 協(xié)處理器操作碼1, 目的寄存器, 源寄存器1, 源寄存器2, 協(xié)處理器操作碼2
mcr p15, 0, r0, c8, c7, 0 ;mcr指令用于將ARM處理器寄存器的數(shù)據(jù)傳送到協(xié)處理器寄存器中,若協(xié)處理器不能成功完成操作,則
;產(chǎn)生未定義指令異常。其中協(xié)處理器操作碼1和協(xié)處理器操作碼2為協(xié)處理器將要執(zhí)行的操作,目的寄存器
;為ARM處理器的寄存器,源寄存器1和源寄存器2均為協(xié)處理器的寄存器。
mrc p15, 0, r0, c1, c0, 0 ;mrc 協(xié)處理器寄存器到ARM處理器寄存器的數(shù)據(jù)傳送指令
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
mov ip, lr ;設(shè)置SDRAM控制器,與具體的目標(biāo)板相關(guān)
bl lowlevel_init
mov lr, ip
mov pc, lr
在這個函數(shù)中做了一下工作:清除指令與數(shù)據(jù)緩存,禁用MMU與數(shù)據(jù)指令緩存,最后調(diào)用lowlevel_init函數(shù)設(shè)置SDRAM控制器。該函數(shù)的實(shí)現(xiàn)與具體的目標(biāo)板有關(guān)的。
2)準(zhǔn)備RAM空間
所謂準(zhǔn)備RAM空間,就是初始化內(nèi)存芯片,使它可用。 在board/smdk2410/lowlevel.init.S就是這個作用,要注意這時的代碼,數(shù)據(jù)都保存在NOR Flash上,內(nèi)存中還沒有,所以讀取數(shù)據(jù)時要變換地址,如下:
_TEXT_BASE:
.word TEXT_BASE
.globl lowlevel_init
lowlevel_init:
;現(xiàn)在起三行進(jìn)行地址變化,因?yàn)檫@時候內(nèi)存中還沒有數(shù)據(jù),不能使用連接程序時確定的地址來讀取數(shù)據(jù)
ldr r0, =SMRDATA ;SMBRDATA表示這13個寄存器的值存放在開始地址(連接地址),值為0x33F8XXXX,處于內(nèi)存中
ldr r1, _TEXT_BASE ;獲得代碼段的起始地址(_TEXT_BASE=0X33F80000)
sub r0, r0, r1 ;將r0和r1相減,這就是13個寄存器值在Nor Flash上存放的開始地址
ldr r1, =BWSCON ;Bus Width Status Controller
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
mov pc, lr
.ltorg
SMRDATA: ;13個寄存器的值
.word … …
.word … …
這里做完以后,就要將整個U-boot的代碼都復(fù)制到SDRAM中,這些又都在start.S中實(shí)現(xiàn),如下:
relocate: ;將u-boot復(fù)制到RAM中
adr r0, _start ;r0:當(dāng)前代碼的開始地址
ldr r1, _TEXT_BASE ;r1:代碼段的連接地址
cmp r0, r1 ;測試現(xiàn)在是在Flash中還是在RAM中
beq stack_setup ;如果已經(jīng)在RAM中(這通常是調(diào)試時直接下載到RAM中),則不需要復(fù)制
ldr r2, _armboot_start ;_armboot_start在前邊已經(jīng)定義,是第一條指令的運(yùn)行地址
ldr r3, _bss_start ;在連接腳本u-boot.lds中定義,是代碼的結(jié)束地址
sub r2, r3, r2 ;r2=代碼段的長度
add r2, r0, r2 ;r2=NOR Flash上代碼段的結(jié)束地址
copy_loop:
ldmia r0!, {r3-r10} ;從地址[r0]處獲得數(shù)據(jù)
stmia r1!, {r3-r10} ;復(fù)制到地址[r1]處
cmp r0, r2 ;復(fù)制是否復(fù)制完畢
ble copy_loop ;沒復(fù)制完,則繼續(xù)
接下來,就要設(shè)置棧,棧的設(shè)置靈活性很大,只要讓sp寄存器指向一段沒有使用的內(nèi)存即可。
stack_setup:
ldr r0, _TEXT_BASE ;_TEXT_BASE為代碼段的開始地址,值為0x33F80000
sub r0, r0, #CFG_MALLOC_LEN ;代碼段下面,留出一段內(nèi)存以實(shí)現(xiàn)malloc
sub r0, r0, #CFG_GBL_DATA_SIZE ;再留出一段內(nèi)存,存一些全局參數(shù)
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) ;IRQ,FIQ模式的棧
#endif
sub sp, r0, #12 ;最后,留下12字節(jié)的內(nèi)存給abort異常,往下的內(nèi)存就都是棧了
3)跳轉(zhuǎn)到第二階段代碼的C入口點(diǎn)
在跳轉(zhuǎn)之前,還要清除BSS段(初始值為0,無初始值的全局變量,靜態(tài)變量放在BSS段),代碼如下:
clear_bss:
ldr r0, _bss_start ;BSS段的開始地址,它的值在連接腳本U-boot.lds中確定
ldr r1, _bss_end ;BSS段的結(jié)束地址,它的值也在連接腳本u-boot.lds確定
mov r2, #0x00000000
clbss_l:str r2, [r0] ;往BSS段中寫入0值
add r0, r0, #4
cmp r0, r1
ble clbss_l
現(xiàn)在,c函數(shù)的運(yùn)行環(huán)境已經(jīng)完全準(zhǔn)備好了,通過如下命令直接跳轉(zhuǎn)(這之后,程序才在內(nèi)存中執(zhí)行),它將調(diào)用lib_arm/board.c中的start_armboot函數(shù)(這是一個C語言函數(shù)),這是第二階段的入口點(diǎn):
ldr pc, _start_armboot
_start_armboot: .word start_armboot
在第二階段代碼中,將進(jìn)行更多的初始化工作,如對各種設(shè)備和接口的初始化,串口終端的初始化等。如果沒有設(shè)置自動運(yùn)行,則最終將進(jìn)入一個循環(huán),在循環(huán)內(nèi)讀取用戶輸入的命令并執(zhí)行,這些會在下一節(jié)詳細(xì)介紹。
U-boot在開發(fā)板上移植過程詳解(3)---U-boot實(shí)現(xiàn)源碼分析(第二階段)U-boot的第二階段和bootloader所完成的功能基本上是一致的,只是順序上有點(diǎn)差別。另外,u-boot在啟動內(nèi)核之前可以讓用戶決定是否進(jìn)入下載模式,即進(jìn)入u-boot的控制界面。
第二階段是從lib_arm/board.c中的start_armboot函數(shù)開始的。移植u-boot的主要工作在于對硬件的初始化,驅(qū)動。這里就重點(diǎn)按照硬件的操作上。
(1)初始化本階段要用到的硬件設(shè)備
這里最重要的是設(shè)置系統(tǒng)時鐘,初始化串口,只要這兩個設(shè)置好了,就可以從串口看到打印信息。board_init函數(shù)設(shè)置MPLL、改變系統(tǒng)時鐘,它是開發(fā)板相關(guān)的函數(shù),在board/samsung/smdk2440/smdk2440.c中實(shí)現(xiàn)。值得注意的是board_init函數(shù)還保存了機(jī)器類型ID,這將在調(diào)用內(nèi)核的時候傳遞給內(nèi)核。
串口的初始化函數(shù)主要是serial_init,它設(shè)置UART控制器,是CPU的相關(guān)函數(shù),在cpu/arm920t/s3c2440/serial.c中實(shí)現(xiàn)。
(2)檢測系統(tǒng)內(nèi)存映射
對于特定的開發(fā)板,器內(nèi)存的分布是明確的,所以可以直接設(shè)置。board/smdk2410/smdk2410.c中的dram_init函數(shù)指定了本開發(fā)板的內(nèi)存起始地址為0x300
00000,大小為0x40000000.代碼如下:
int dram_init(void){ //這兩個值都定義在include/configs/smdk2440.h中 gd->bd->bi_dram[0] . start = PHYS_SDRAM_1; //即0x30000000 gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; //即0x04000000 return 0;}
這些設(shè)置的參數(shù),將在后面向內(nèi)核傳遞參數(shù)時用到。
(3)U-boot命令實(shí)現(xiàn)
我們已經(jīng)知道,即使是內(nèi)核的啟動,也是通過U-boot命令來實(shí)現(xiàn)的。u-boot中的每個命令都通過U-BOOT-CMD宏(在include/command.h)來定義,格式如下:
U_BOOT_CMD(name, maxargs, repeatable, command, “usage”, "help”)
各項(xiàng)參數(shù)說明如下:
name:命令的名字,注意,它不是一個字符串(不要用雙引號括起來)
maxargs:最大的參數(shù)個數(shù)
repeatable:命令是否可重復(fù),可重復(fù)是指運(yùn)行一個命令后,下次敲回車即可再次運(yùn)行
command:對應(yīng)的函數(shù)指針,類型為(*cmd)(struct cmd_tbl_s *, int, int, char *[])
usage:簡短的使用說明,這是個字符串
下面以bootm命令來說明,它有如下定義:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"string1”,
"string2"
);
利用U_BOOT_CMD的宏展開后的命令如下
cmd_tbl_t __u_boot_cmd_boot __attribute__ ((unused, section(".u_boot_cmd"))) = { "bootm",
CFG_MAXARGS, 1, do_bootm, "string1", “string2”};
對于每個使用U_BOOT_CMD宏來定義的命令,其實(shí)都是在".u_boot_cmd"段中定義一個cmd_tbl_t結(jié)構(gòu),如下:
struct cmd_tbl_s { char *name; //命名名稱 int maxargs; //最大參數(shù)個數(shù) int repeatable; //是否允許自動重復(fù) int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); //實(shí)現(xiàn)函數(shù) char *usage; //幫助信息(短) char *help; //幫助信息(長)};typedef struct cmd_tbl_s cmd_tbl_t;
在u-boot的鏈接腳本board/smdk2410/u-boot.lds中有如下定義:
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
在程序中就是根據(jù)命令的名字在內(nèi)存段__u_boot_cmd_start~__u_boot_cmd_end找到它的cmd_tbl_t結(jié)構(gòu),然后調(diào)用它的函數(shù)(請參考common/comm
and.c中的find_cmd函數(shù))。
(4)引導(dǎo)內(nèi)核的實(shí)現(xiàn)
U-boot也是通過標(biāo)記列表向內(nèi)核傳遞參數(shù)的。ARM Linux內(nèi)核對bootloader的引導(dǎo)功能有一定要求,在執(zhí)行內(nèi)核代碼前必須設(shè)置下列條件:
& 對CPU寄存器的設(shè)置為R0=0, R1=機(jī)器類型ID,R2=引導(dǎo)參數(shù)列表的地址
& 必須禁止中斷(IRQ與FIQ)
& CPU必須處于SVC模式
& MMU必須關(guān)閉
& 數(shù)據(jù)緩存必須關(guān)閉
現(xiàn)在linux雖然支持兩種格式的引導(dǎo)參數(shù),這里主要介紹最常用的新的方式---即上面所說的標(biāo)記列表的,這種方式靈活,且對參數(shù)的描述更細(xì)致。
標(biāo)簽列表的每個標(biāo)簽由標(biāo)簽頭和標(biāo)簽體組成。標(biāo)簽頭說明這個標(biāo)簽的大小(單位是整數(shù)不是字節(jié))以及這個標(biāo)簽的類型。類型是由內(nèi)核定義好的一個數(shù)字。標(biāo)簽頭用一個結(jié)構(gòu)體struct tag_header表示,如下:
struct tag_header{ u32 size; u32 tag;};
在標(biāo)簽頭之后,根據(jù)標(biāo)簽的類型,所需的標(biāo)簽體也是不同的。標(biāo)簽列表的結(jié)束由一個特殊的標(biāo)簽類型ATAG_NONE標(biāo)志,它沒有標(biāo)簽體。
比較重要的兩個標(biāo)簽類型是ATAG_MEM(設(shè)置內(nèi)存信息)和ATAG_CMDLINE(用來傳遞命令行參數(shù),即U-boot的bootargs變量的內(nèi)容),這里列出來,需要的請大家查看google。下面給出一些小細(xì)節(jié):
&u-boot源碼中給出了一些設(shè)置標(biāo)簽列表的源代碼,放在文件lib_arm/armlinux.c中,方法是先定義一個全局變量static struct tag *params,其中這個結(jié)構(gòu)體的類型是一個將所有標(biāo)簽類型組合在一起的結(jié)構(gòu)體,如下所示:
struct tag { struct tag_header hdr; union { struct tag_corecore; struct tag_mem32mem; struct tag_videotextvideotext; struct tag_ramdiskramdisk; struct tag_initrdinitrd; struct tag_serialnrserialnr; struct tag_revisionrevision; struct tag_videolfbvideolfb; struct tag_cmdlinecmdline; struct tag_acornacorn; struct tag_memclkmemclk; } u; };
所有標(biāo)簽的頭格式都是相同的,只是標(biāo)簽體不同,因此用聯(lián)合體的方式將它們組合在一起。下面就是設(shè)置起始標(biāo)簽的函數(shù)代碼:
static void setup_start_tag (bd_t *bd){ params = (struct tag *) bd->bi_boot_params; params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size (tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next (params);}
在這個函數(shù)中,首先將變量params的值設(shè)為標(biāo)簽列表的開始地址,然后逐個設(shè)置標(biāo)簽中的成員,最后params變量的值將指向下一個標(biāo)簽應(yīng)該設(shè)置的地址。其中,tag_size是一個宏,用來得到標(biāo)簽的大小。最后,用于設(shè)置標(biāo)簽列表結(jié)束的函數(shù)如下:
static void setup_end_tag (bd_t *bd){ params->hdr.tag = ATAG_NONE; params->hdr.size = 0;}
對于ARM架構(gòu)的CPU,都是通過lib_arm/armlinux.c中的do_bootm_linxu函數(shù)來啟動內(nèi)核的,方法如下:
首先,獲得內(nèi)核映像的入口地址:
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
這樣theKernel就指向內(nèi)核存放的地址(對于ARM架構(gòu)的CPU,通常是0x30008000),這里的hdr指向內(nèi)核U-boot映像頭部數(shù)據(jù)的指針,而hdr->ih_ep就是內(nèi)核的入口地址,最后用下述代碼調(diào)用內(nèi)核:
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
這里的bd->bi_arch_number就是前面board_init函數(shù)設(shè)置的機(jī)器類型ID, bd->bi_boot_params就是標(biāo)記列表的開始地址。根據(jù)ATPCS調(diào)用約定,上述函數(shù)的三個函數(shù)分別放在寄存器R0,R1,R2中,這樣就實(shí)現(xiàn)了內(nèi)核要求的入口條件。
講到這里,有關(guān)的U-boot的關(guān)鍵源碼分析分析部分就完成了,下次開始就來U-boot移植的實(shí)踐操作篇。
U-boot在開發(fā)板上移植過程詳解(4)---U-boot移植操作實(shí)踐(u-boot框架實(shí)現(xiàn))經(jīng)過前面三節(jié)對bootloader的講解及其典型實(shí)現(xiàn)u-boot的講解,相信大家對bootloader有了很深的了解(當(dāng)然,通過講解,我也有更深的了解了)。那么今天開始,就要開始 U-boot移植操作實(shí)踐 部分的講解了。
我們知道bootloader是分為兩部分的,具體到u-boot中,這兩部分實(shí)現(xiàn)分別在:stage1代碼通常放在cpu/xxxx/start.S文件中,stage2代碼通常放在lib_xxxx/bo
ard.c文件中.具體的講解,我這里就不細(xì)講了,若有不懂,自己到前邊的博客里去翻吧。在開始之前,有必要介紹一下相應(yīng)的軟件版本:
主 機(jī):Fedora 9
編譯器:arm-linux-gcc-4.4.3
u-boot:u-boot-2009.08.tar.bz2
好了,現(xiàn)在開始真正的移植操作:
1)建立自己的開發(fā)板項(xiàng)目并測試編譯
因2440和2410的資源差不多,主頻和外設(shè)有點(diǎn)差別,所以我們就在board/samsung/下建立自己開發(fā)板的項(xiàng)目,取名叫smdk2440
#tar -jxvf u-boot-2009.08.tar.bz2
#cd u-boot-2009.08/board/samsung/
#mkdir smdk2440 //創(chuàng)建smdk2440文件夾
#cp -rf smdk2410/* smdk2440/ //將2410下所有的代碼復(fù)制到2440下
#cd smdk2440 //進(jìn)入smdk2440目錄
#mv smdk2410.c smdk2440.c //將smdk2440下的smdk2410.c改名為smdk2440.c
#cd http://www.cnblogs.com/../ //回到u-boot根目錄
#cp include/configs/smdk2410.h include/configs/smdk2440.h //建立2440頭文件
這里這樣做,隱含了一個事實(shí)就是,2440和2410的資源差不多,所以就以2410項(xiàng)目的代碼作為模板,以后根據(jù)需要再修改。這個做完了修改剛才創(chuàng)建的smdk2440下的Makefile文件,找到COBJS := smdk2410.o flash.o 將smdk2410.o改為smdk2440.o(原因,我們都知道是不) 。下面修改u-boot跟目錄下的Makefile文件,找到smdk2410_config的地方,在它下面按照smdk2410_config的格式建立smdk2440_config的編譯選項(xiàng),另外還要指定交叉編譯器。如下所示:
CROSS_COMPILE ?= arm-linux- //指定交叉編譯器為arm-linux-gcc
smdk2410_config : unconfig //2410編譯選項(xiàng)格式
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0
smdk2440_config : unconfig //2440編譯選項(xiàng)格式
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2440 samsung s3c24x0
說明:arm :CPU的架構(gòu)(ARCH)
arm920t:CPU的類型
smdk2440 :對應(yīng)在board目錄下建立新的開發(fā)板項(xiàng)目的目錄
samsung:新開發(fā)板項(xiàng)目目錄的上級目錄,如直接在board下建立新的開發(fā)板項(xiàng)目的目錄,則這里就為NULL
s3c24x0:CPU型號
保存退出,在終端下運(yùn)行:
#make my2440_config //如果出現(xiàn)Configuring for my2440 board...則表示設(shè)置正確
#make //編譯后在根目錄下會出現(xiàn)u-boot.bin文件,則u-boot移植的第一步就算完成了
說明:其實(shí)經(jīng)過上面的make時,是會發(fā)生錯誤的,解決方法如下:
問題一:board.c:127: error: inline function 'coloured_LED_init' cannot be declared weak
board.c:129: error: inline function 'red_LED_on' cannot be declared weak
board.c:131: error: inline function 'red_LED_off' cannot be declared weak
board.c:133: error: inline function 'green_LED_on' cannot be declared weak
board.c:135: error: inline function 'green_LED_off' cannot be declared weak
board.c:137: error: inline function 'yellow_LED_on' cannot be declared weak
board.c:139: error: inline function 'yellow_LED_off' cannot be declared weak
board.c:141: error: inline function 'blue_LED_on' cannot be declared weak
board.c:143: error: inline function 'blue_LED_off' cannot be declared weak
make[1]: *** [board.o] 錯誤 1
make[1]: Leaving directory `/root/workspace/u-boot-2009.08/lib_arm'
make: *** [lib_arm/libarm.a] 錯誤 2
解決方法:
打開lib_arm/board.c,定位到127行開始,將其注釋掉:
void inline __coloured_LED_init (void) {}
//void inline coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
這里注釋掉了'coloured_LED_init' 的部分,自己做時對照注釋掉后面幾個帶__attribute__的部分即可
問題二:cpu/arm920t/start.o: In function `start_code':
/root/workspace/u-boot-2009.08/cpu/arm920t/start.S:117: undefined reference to `coloured_LED_init'
/root/workspace/u-boot-2009.08/cpu/arm920t/start.S:118: undefined reference to `red_LED_on'
make: *** [u-boot] 錯誤 1
解決方法:
打開cpu/arm920t/start.S,搜索“coloured_LED_init”定位到117行,找到如下代碼:
bl coloured_LED_init
bl red_LED_on
這兩行是AT91RM9200DK開發(fā)板的LED初始化,注釋掉即可。
經(jīng)過上面的修改,make clean/make就可以在u-boot的根目錄下看到u-boot.bin文件,則u-boot移植的第一步就算完成了。但是,這里u-boot對自己的smdk2440開發(fā)板還沒有任何用處,只是搭建了一個smdk2440開發(fā)板u-boot的框架,要使其功能實(shí)現(xiàn),還要根據(jù)smdk2440開發(fā)板的具體資源情況來對u-boot源碼進(jìn)行修改.
2)根據(jù)u-boot啟動步驟來分析或者修改添加u-boot源碼,使之適合自己的開發(fā)板
一般在嵌入式系統(tǒng)軟件開發(fā)中,在所有源碼文件編譯完成之后,鏈接器要讀取一個鏈接分配文件,在該文件中定義了程序的入口點(diǎn),代碼段、數(shù)據(jù)段等分配情況等。那么我們的s3c2440開發(fā)板u-boot的這個鏈接文件就是cpu/arm920t/u-boot.lds,打開該文件部分代碼如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm) //定義生成文件的目標(biāo)平臺是arm
ENTRY(_start) //定義程序的入口點(diǎn)是_start
從這里我們知道程序的入口點(diǎn)是_start,那么我們定位到u-boot第一個要運(yùn)行的程序cpu/arm920t/start.S,查找到_start的位置如下:
.globl _start
_start: b start_code
從這里知道,程序要從start_code處開始執(zhí)行,由此可以看到,start_code處才是u-boot啟動代碼的真正開始處。以上就是u-boot的stage1入口的過程。知道了程序的入口處,也就是stage1的入口地址,下面就開始針對我們s3c2440的板子來修改u-boot,讓其為我們的硬件初始化做準(zhǔn)備,當(dāng)然啦,這就是下面幾集的內(nèi)容了。后面繼續(xù)分解。