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