U-BOOT全線移植分析系列之二
――U-boot基礎
Sailor_forever [email protected]轉載請註明
http://blog.csdn.net/sailor_8318/archive/2008/08/04/2768049.aspx
【摘要】本節介紹了U-boot的基本概念。首先介紹了U-boot源代碼的目錄結構,並給出了一個實例。接着簡單介紹了U-boot支持的基本功能、常見命令和環境變量。最後詳細分析了U-boot啓動的兩個階段,重點介紹了加載拷貝代碼至RAM中的過程。
【關鍵詞】bootloader,U-boot,環境變量, stage1,位置無關,代碼搬移
二 U-boot基礎
現在爲Linux開放源代碼Bootloader有很多,blob、 redboot及U-BOOT等,其中U-BOOT是目前用來開發嵌入式系統引導代碼使用最爲廣泛的Bootloader。它支持POWERPC、ARM、MIPS和 X86等處理器,支持嵌入式操作系統有Linux、Vxworks及NetBSD等。
2.1 U-boot源代碼目錄結構
|-- board 平臺依賴,存放電路板相關的目錄文件
|-- common 通用多功能函數的實現
|-- cpu 平臺依賴,存放cpu相關的目錄文件
|-- disk 通用。硬盤接口程序
|-- doc 文檔
|-- drivers 通用的設備驅動程序,如以太網接口驅動
|-- dtt
|-- examples 應用例子
|-- fs 通用存放文件系統的程序
|-- include 頭文件和開發板配置文件,所有開發板配置文件放在其configs裏
|-- lib_arm 平臺依賴,存放arm架構通用文件
|-- lib_generic 通用的庫函數
|-- lib_i386 平臺依賴,存放x86架構通用文件
|-- lib_m68k 平臺依賴
|-- lib_microblaze 平臺依賴
|-- lib_mips 平臺依賴
|-- lib_nios 平臺依賴
|-- lib_ppc平臺依賴,存放ppc架構通用文件
|-- net 存放網絡的程序
|-- post 存放上電自檢程序
|-- rtc rtc的驅動程序
`-- tools 工具
詳細實例:
² board:開發板相關的源碼,不同的板子對應一個子目錄,內部放着主板相關代碼。 Board/at91rm9200dk/at91rm9200.c, config.mk, Makefile, flash.c ,u-boot.lds等都和具體開發板的硬件和地址分配有關。
² common:與體系結構無關的代碼文件,實現了u-boot所有命令,其中內置了一個shell腳本解釋器(hush.c, a prototype Bourne shell grammar parser), busybox中也使用了它。
² cpu:與cpu相關代碼文件,其中的所有子目錄都是以u-boot所支持的cpu命名。
cpu/at91rm9200/at45.c, at91rm9200_ether.c, cpu.c, interrupts.c serial.c, start.S, config.mk, Makefile等。其中:
cpu.c負責初始化CPU、設置指令Cache和數據Cache等;
interrupt.c負責設置系統的各種中斷和異常,比如快速中斷、開關中斷、時鐘中斷、軟件中斷、預取中止和未定義指令等;
start.S負責u-boot啓動時執行的第一個文件,它主要是設置系統堆棧和工作方式,爲跳轉到C程序入口點做準備;
at91rm9200_ether.c和serial.c很重要,這是系統能夠下載資源的前提。
² disk:設備分區處理代碼。
² doc:u-boot相關文檔。
² drivers:u-boot所支持的設備驅動代碼, 網卡、支持CFI的Flash、串口和USB總線等。
² fs: u-boot所支持文件系統訪問存取代碼, 如jffs2。
² include:u-boot head文件,主要是與各種硬件平臺相關的頭文件,如include/asm-arm/arch-at91rm9200/AT91RM9200.h(硬件寄存器名稱及地址的定義), hardware.h (內存及flash地址以及IO物理地址和虛擬地址的定義),include/asm-arm/proc-armv(與具體的CPU無關,無需移植)。
² net:與網絡有關的代碼,BOOTP協議、TFTP協議、RARP協議代碼實現. 無需移植。
² lib_arm:與arm體系相關的代碼。
² tools:編譯後會生成mkimage工具,用來對生成的raw bin文件加入u-boot特定的image_header.
2.2 U-Boot支持的主要功能
主要功能如下:
² 系統引導,支持NFS掛載、RAMDISK(壓縮或非壓縮)形式的根文件系統;
² 支持NFS掛載、從FLASH中引導壓縮或非壓縮系統內核;
² 基本輔助功能,強大的操作系統接口功能;可靈活設置、傳遞多個關鍵參數給操作系統,適合系統在不同開發階段的調試要求與產品發佈,尤對Linux支持最爲強勁;
² 支持目標板環境參數多種存儲方式,如FLASH、NVRAM、EEPROM;
² CRC32校驗,可校驗FLASH中內核、RAMDISK鏡像文件是否完好;
² 設備驅動,串口、SDRAM、FLASH、以太網、LCD、NVRAM、EEPROM、鍵盤、USB、PCMCIA、PCI、RTC等驅動支持;
² 上電自檢功能 SDRAM、FLASH大小自動檢測;SDRAM故障檢測;CPU型號;
² 特殊功能,XIP內核引導。
2.3 U-boot命令介紹及環境變量
² ?得到所有命令列表
² Help:help usb, 列出USB功能的使用說明
² ping:注意只能開發板PING別的機器(AT91RM9200不支持,需要進行配置)
² setenv: 設置環境變量
Ø setenv serverip 192.168.0.1
Ø setenv ipaddr 192.168.0.56
Ø setenv bootcmd ‘tftp 32000000 vmlinux; kgo 32000000’
² saveenv:保存環境變量。在設置好環境變量以後,保存變量值
² tftp:tftp 32000000 vmlinux, 把server(IP=環境變量中設置的serverip)中/tftpboot/下的vmlinux通過TFTP讀入到物理內存32000000處
² bootp- 通過網絡用 BootP/TFTP 協議來啓動映象
² tftpboot- 通過網絡用 TFTP 協議、設置服務器和客戶機的 IP 地址進行映象文件傳送
² kgo: 起動沒有壓縮的linux內核,kgo 32000000(AT91RM9200不支持)
² bootm:起動UBOOT TOOLS製作的壓縮LINUX內核, bootm 3200000
² protect: 對FLASH進行寫保護或取消寫保護,protect on 1:0-3(就是對第一塊FLASH的0-3扇區進行保護),protect off 1:0-3取消寫保護
² erase:刪除FLASH的扇區, erase 1:0-2(就是對每一塊FLASH的0-2扇區進行刪除)
² cp: 在內存中複製內容, cp 32000000 0 40000(把內存中0x32000000開始的0x40000字節複製到0x0處)
² mw: 對RAM中的內容寫操作, mw 32000000 ff 10000(把內存0x32000000開始的0x10000字節設爲0xFF)
² md: 修改RAM中的內容, md 32000000(內存的起始地址)
² flinfo: 列出flash的信息
² loadb: 準備用KERMIT協議接收來自kermit或超級終端傳送的文件。
² nfs: nfs 32000000 192.168.0.12:aa.txt,把192.168.0.12(LINUX 的NFS文件系統)中的NFS文件系統中的aa.txt 讀入內存0x32000000處。
最常用的幾個命令如下:
² go- 在地址 'addr' 處開始程序執行
² run- 運行一個環境變量所定義的命令
² bootm- 從內存中進行運行經過mkimage加工的程序映象
² loadb- 通過串口線(kermit mode) 來裝載二進制文件
² printenv- 打印環境變量
² setenv- 設置環境變量
² saveenv保存環境變量到內存
² tftp-通過網絡下載文件
² protect,erase,flash讀寫
下面是 U-BOOT 中的簡單環境變量
² baudrate波特率
² bootdelay boot 延遲
² bootcmd Boot 命令
² bootargs Boot 參數,傳遞給內核
² bootfile 默認下載啓動的內核映象
² ipaddr 客戶機 IP 地址
² serverip 服務器地址
² loadaddr 裝載地址
² ethaddr 網卡 MAC 地址
2.4 U-Boot的啓動流程分析
和大多數的Bootloader一樣,U-BOOT的啓動分爲兩個階段兩個部分,依賴於CPU體系結構的代碼主要放在stage1,且用匯編來實現,而stage2則通常用C語言來實現,這樣可以實現複雜的功能,而且具有更好的可讀性和可移植性。
下面分別分析一下這兩個階段的啓動流程:
第一階段:基本的硬件初始化,爲第二階段程序運行建立環境(cpu/ at91rm9200/start.s文件的代碼部分):
××××××××××××××××××××××××××××
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) // 程序的入口在/cpu/××××/start.s中定義
SECTIONS
{
. = 0x00000000; // 程序鏈接的地址
. = ALIGN(4);
.text :
{
cpu/at91rm9200/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
armboot_end_data = .; //代碼段結束地址
. = ALIGN(4);
.bss : { *(.bss) }
armboot_end = .; //整個U-boot印象的結束地址
}
××××××××××××××××××××××××××××
在此需要定義程序入口,由於一個可執行的Image必須要有一個入口點,並且只能有一個全局入口,通常這個入口就在ROM (flash)的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改連接器腳本u-boot.lds來完成,該階段需要依次完成的工作一般包括:
² CPU自身的初始化,它包括:CPU運行模式的設置(管理模式)、設置異常的入口地址和異常處理函數、運行時鐘頻率的設置等工作。
² 初始化GPIO和內存控制器。
² 爲拷貝Stage2準備RAM空間。
² 進行自拷貝,將U-BOOT的Stage2拷貝到RAM中。
² 設置好堆棧。
² 跳轉到Stage2的入口,從而轉到RAM中執行,該工作是調用指令ldr pc, start armboot來完成的。
××××××××××××××××××××××××××
從1.1.2開始,u-boot有初始化SDRAM並拷貝自己到SDRAM運行的代碼,而之前的版本就沒有這個功能(詳細查看下代碼??的確如此,因此對於以前的版本TEXT_BASE沒有起作用?實際測試下??)。board/ at91rm9200dk中config.mk文件(TEXT_BASE = 0x21f00000,32M RAM的設置,爲第二階段程序在RAM中的運行地址)用於設置程序編譯連接的起始地址,在程序中要特別注意與地址相關指令的使用。
Board/at91rm9200dk/config.mk
TEXT_BASE = 0x21f00000(u-boot將被載入SDRAM的高端部分)
注意,對於不同的系統RAM大小可能不一樣,要根據實際情況調整。
在/config.mk中
ifdef BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
endif
CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS) /
-D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE)
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
export TEXT_BASE PLATFORM_CPPFLAGS PLATFORM_RELFLAGS CPPFLAGS CFLAGS AFLAGS
對於U-Boot 1.1.2以前的版本,並沒有自拷貝的部分,若flash中首地址存放的是非壓縮的u-boot.bin的,則啓動部分是一直運行在flash中的;但對於AT91RM9200來說,他通常有三個文件loader.bin, boot.bin, u-boot.bin,flash中首先運行的是boot.bin其將壓縮的u-boot.bin.gz解壓拷貝到TEXT_BASE處運行,此時已經在RAM中了,因此也無需實現自拷貝了。
對於U-Boot 1.1.2以後的版本,若像AT91RM9200有boot.bin這樣的過渡程序,則將u-boot.bin.gz解壓到RAM中,此時運行地址和鏈接地址相同,無需拷貝;若沒有,則u-boot.bin將在flash中執行初始化部分,然後將自身拷貝到RAM中執行。
// 比較運行地址和鏈接地址,如果當前已經在RAM中運行了,則無需拷貝到RAM中
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
當程序在Flash中運行時,執行程序跳轉時必須要使用相對跳轉指令,而不能使用絕對地址的跳轉(即直接對PC操作)。如果使用絕對地址,那麼,程序的取指是相對於當前PC位置向前或者向後的32MB空間內,而不會跳入SDRAM中。
×××××××××××××××××××××××××××××
在上述操作運行完成後,就進入到/lib_arm/board.c 中的start_armboot()函數運行,並建立起了一個基本的環境,此時的物理內存空間的分佈就變成了如圖所示的情況。
轉入boatloader stage2的系統內存佈局
第二階段:運行U-BOOT的主體部分
該階段以程序跳轉到lib_arm/board.c中的start_armboot函數爲標誌,該函數同時也是C語言的開始函數,是整個啓動代碼的主體函數,同時還是整個U-BOOT的主體函數,該函數主要完成以下工作:
² 調用一系列的初始化函數,初始化本階段使用到的硬件,如:
×××××××××××××××××××
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
#if defined(CONFIG_VCMA9)
checkboard,
#endif
NULL,
};
×××××××××××××××××××××
env_init:設置環境變量,初始化環境;
init_baudrate:設置串口的波特率;
serial_init:設置串口的工作方式;
dram_init:設置SDRAM的起始地址和大小;
² 檢查存儲器分配和使用情況:獲取flash的bank分區情況、是否擦除、是否上鎖等信息爲以後flash相關命令使用;初始化系統內存分配函數,供後面的代碼使用malloc等函數,U-BOOT沒有使用其他現成的庫所有函數的實現均在文件中),如果系統有液晶等顯示設備,一併在此分配顯示內存。
² 打印內存,flash、環境變量設置等信息。
² 等待幾秒時間,如果有鍵盤輸入,則進入命令模式,接收用戶輸入的命令並解釋執行(如啓動操作系統,更新flash內容)。
² 如果在給定的時間內沒有用戶輸入或者在執行命令操作時收到了用戶要求啓動內核的命令(boot),則將把操作系統內核和根文件系統映像文件從flash中拷貝到RAM中相應位置,並在設置內核啓動參數後跳轉到內核映像的首地址處執行。
×××××××××××××××××××××××××××××
U-BOOT調用 Linux 內核的方法是直接跳轉到內核的第一條指令處,也即直接跳轉到MEM_START+0x8000地址處。在跳轉時,要滿足下列條件:
a) CPU寄存器的設置:R0=0;R1=機器類型 ID,本系統的機器類型ID=193。R2=啓動參數標記列表在RAM中的起始基地址;
b) CPU模式:必須禁止中斷(IRQs和FIQs);CPU必須工作在SVC模式;
c) Cache和MMU的設置:MMU 必須關閉;指令Cache可以打開也可以關閉;數據Cache必須關閉。
系統採用下列代碼來進入內核函數:
void (*theKernel)(int zero, int arch);
theKernel = (void (*)(int, int))ntohl(hdr->ih_ep);
theKernel(0, bd->bi_arch_number);其中,hdr是image_header_t類型的結構體,hdr->ih_ep爲entry point,指向內核的第一條指令地址,即Linux操作系統下的/kernel/arch/arm/boot/compressed/head.S彙編程序。theKernel()函數調用應該不會返回,如果該調用返回,則說明出錯。
×××××××××××××××××××××××××××××