6.1  Bootloader

對(duì)于計(jì)算機(jī)系統(tǒng)來(lái)說(shuō),從開機(jī)上電到操作系統(tǒng)啟動(dòng)需要一個(gè)引導(dǎo)過程。嵌入式Linux系統(tǒng)同樣離不開引導(dǎo)程序,這個(gè)引導(dǎo)程序就叫作Bootloader。

6.1.1  Bootloader介紹

Bootloader是在操作系統(tǒng)運(yùn)行之前執(zhí)行的一段小程序。通過這段小程序,我們可以初始化硬件設(shè)備、建立內(nèi)存空間的映射表,從而建立適當(dāng)?shù)南到y(tǒng)軟硬件環(huán)境,為最終調(diào)用操作系統(tǒng)內(nèi)核做好準(zhǔn)備。

對(duì)于嵌入式系統(tǒng),Bootloader是基于特定硬件平臺(tái)來(lái)實(shí)現(xiàn)的。因此,幾乎不可能為所有的嵌入式系統(tǒng)建立一個(gè)通用的Bootloader,不同的處理器架構(gòu)都有不同的Bootloader。Bootloader不但依賴于CPU的體系結(jié)構(gòu),而且依賴于嵌入式系統(tǒng)板級(jí)設(shè)備的配置。對(duì)于2塊不同的嵌入式板而言,即使它們使用同一種處理器,要想讓運(yùn)行在一塊板子上的Bootloader程序也能運(yùn)行在另一塊板子上,一般也都需要修改Bootloader的源程序。

反過來(lái),大部分Bootloader仍然具有很多共性,某些Bootloader也能夠支持多種體系結(jié)構(gòu)的嵌入式系統(tǒng)。例如,U-Boot就同時(shí)支持PowerPC、ARMMIPSX86等體系結(jié)構(gòu),支持的板子有上百種。通常,它們都能夠自動(dòng)從存儲(chǔ)介質(zhì)上啟動(dòng),都能夠引導(dǎo)操作系統(tǒng)啟動(dòng),并且大部分都可以支持串口和以太網(wǎng)接口。

本章將對(duì)各種Bootloader總結(jié)分類,分析它們的共同特點(diǎn)。以U-Boot為例,詳細(xì)討論Bootloader的設(shè)計(jì)與實(shí)現(xiàn)。

6.1.2  Bootloader的啟動(dòng)

Linux系統(tǒng)是通過Bootloader引導(dǎo)啟動(dòng)的。一上電,就要執(zhí)行Bootloader來(lái)初始化系統(tǒng)??梢酝ㄟ^第4章的Linux啟動(dòng)過程框圖回顧一下。

系統(tǒng)加電或復(fù)位后,所有CPU都會(huì)從某個(gè)地址開始執(zhí)行,這是由處理器設(shè)計(jì)決定的。比如,X86的復(fù)位向量在高地址端,ARM處理器在復(fù)位時(shí)從地址0x00000000取第一條指令。嵌入式系統(tǒng)的開發(fā)板都要把板上ROMFlash映射到這個(gè)地址。因此,必須把Bootloader程序存儲(chǔ)在相應(yīng)的Flash位置。系統(tǒng)加電后,CPU將首先執(zhí)行它。

主機(jī)和目標(biāo)機(jī)之間一般有串口可以連接,Bootloader軟件通常會(huì)通過串口來(lái)輸入輸出。例如:輸出出錯(cuò)或者執(zhí)行結(jié)果信息到串口終端,從串口終端讀取用戶控制命令等。

Bootloader啟動(dòng)過程通常是多階段的,這樣既能提供復(fù)雜的功能,又有很好的可移植性。例如:從Flash啟動(dòng)的Bootloader多數(shù)是兩階段的啟動(dòng)過程。從后面U-Boot的內(nèi)容可以詳細(xì)分析這個(gè)特性。

大多數(shù)Bootloader都包含2種不同的操作模式:本地加載模式和遠(yuǎn)程下載模式。這2種操作模式的區(qū)別僅對(duì)于開發(fā)人員才有意義,也就是不同啟動(dòng)方式的使用。從最終用戶的角度看,Bootloader的作用就是用來(lái)加載操作系統(tǒng),而并不存在所謂的本地加載模式與遠(yuǎn)程下載模式的區(qū)別。

因?yàn)?/SPAN>Bootloader的主要功能是引導(dǎo)操作系統(tǒng)啟動(dòng),所以我們?cè)敿?xì)討論一下各種啟動(dòng)方式的特點(diǎn)。

1.網(wǎng)絡(luò)啟動(dòng)方式

這種方式開發(fā)板不需要配置較大的存儲(chǔ)介質(zhì),跟無(wú)盤工作站有點(diǎn)類似。但是使用這種啟動(dòng)方式之前,需要把Bootloader安裝到板上的EPROM或者Flash中。Bootloader通過以太網(wǎng)接口遠(yuǎn)程下載Linux內(nèi)核映像或者文件系統(tǒng)。第4章介紹的交叉開發(fā)環(huán)境就是以網(wǎng)絡(luò)啟動(dòng)方式建立的。這種方式對(duì)于嵌入式系統(tǒng)開發(fā)來(lái)說(shuō)非常重要。

使用這種方式也有前提條件,就是目標(biāo)板有串口、以太網(wǎng)接口或者其他連接方式。串口一般可以作為控制臺(tái),同時(shí)可以用來(lái)下載內(nèi)核影像和RAMDISK文件系統(tǒng)。串口通信傳輸速率過低,不適合用來(lái)掛接NFS文件系統(tǒng)。所以以太網(wǎng)接口成為通用的互連設(shè)備,一般的開發(fā)板都可以配置10M以太網(wǎng)接口。

對(duì)于PDA等手持設(shè)備來(lái)說(shuō),以太網(wǎng)的RJ-45接口顯得大了些,而USB接口,特別是USB的迷你接口,尺寸非常小。對(duì)于開發(fā)的嵌入式系統(tǒng),可以把USB接口虛擬成以太網(wǎng)接口來(lái)通訊。這種方式在開發(fā)主機(jī)和開發(fā)板兩端都需要驅(qū)動(dòng)程序。

另外,還要在服務(wù)器上配置啟動(dòng)相關(guān)網(wǎng)絡(luò)服務(wù)。Bootloader下載文件一般都使用TFTP網(wǎng)絡(luò)協(xié)議,還可以通過DHCP的方式動(dòng)態(tài)配置IP地址。

DHCP/BOOTP服務(wù)為Bootloader分配IP地址,配置網(wǎng)絡(luò)參數(shù),然后才能夠支持網(wǎng)絡(luò)傳輸功能。如果Bootloader可以直接設(shè)置網(wǎng)絡(luò)參數(shù),就可以不使用DHCP。

TFTP服務(wù)為Bootloader客戶端提供文件下載功能,把內(nèi)核映像和其他文件放在/tftpboot目錄下。這樣Bootloader可以通過簡(jiǎn)單的TFTP協(xié)議遠(yuǎn)程下載內(nèi)核映像到內(nèi)存。如圖6.1所示。

6.1  網(wǎng)絡(luò)啟動(dòng)示意圖

大部分引導(dǎo)程序都能夠支持網(wǎng)絡(luò)啟動(dòng)方式。例如:BIOSPXEPreboot Execution Environment)功能就是網(wǎng)絡(luò)啟動(dòng)方式;U-Boot也支持網(wǎng)絡(luò)啟動(dòng)功能。

2.磁盤啟動(dòng)方式

傳統(tǒng)的Linux系統(tǒng)運(yùn)行在臺(tái)式機(jī)或者服務(wù)器上,這些計(jì)算機(jī)一般都使用BIOS引導(dǎo),并且使用磁盤作為存儲(chǔ)介質(zhì)。如果進(jìn)入BIOS設(shè)置菜單,可以探測(cè)處理器、內(nèi)存、硬盤等設(shè)備,可以設(shè)置BIOS從軟盤、光盤或者某塊硬盤啟動(dòng)。也就是說(shuō),BIOS并不直接引導(dǎo)操作系統(tǒng)。那么在硬盤的主引導(dǎo)區(qū),還需要一個(gè)Bootloader。這個(gè)Bootloader可以從磁盤文件系統(tǒng)中把操作系統(tǒng)引導(dǎo)起來(lái)。

Linux傳統(tǒng)上是通過LILOLInux LOader)引導(dǎo)的,后來(lái)又出現(xiàn)了GNU的軟件GRUBGRand Unified Bootloader)。這2Bootloader廣泛應(yīng)用在X86Linux系統(tǒng)上。你的開發(fā)主機(jī)可能就使用了其中一種,熟悉它們有助于配置多種系統(tǒng)引導(dǎo)功能。

LILO軟件工程是由Werner Almesberger創(chuàng)建,專門為引導(dǎo)Linux開發(fā)的?,F(xiàn)在LILO的維護(hù)者是John Coffman,最新版本下載站點(diǎn):http://lilo.go.LILO有詳細(xì)的文檔,例如LILO套件中附帶使用手冊(cè)和參考手冊(cè)。此外,還可以在LDP的“LILO mini-HOWTO”中找到LILO的使用指南。

GRUBGNU計(jì)劃的主要bootloader。GRUB最初是由Erich BoleynGNU Mach操作系統(tǒng)撰寫的引導(dǎo)程序。后來(lái)有Gordon MatzigkeitOkuji Yoshinori接替Erich的工作,繼續(xù)維護(hù)和開發(fā)GRUB。GRUB的網(wǎng)站http://www./software/grub/上有對(duì)套件使用的說(shuō)明文件,叫作《GRUB manual》。GRUB能夠使用TFTPBOOTP或者DHCP通過網(wǎng)絡(luò)啟動(dòng),這種功能對(duì)于系統(tǒng)開發(fā)過程很有用。

除了傳統(tǒng)的Linux系統(tǒng)上的引導(dǎo)程序以外,還有其他一些引導(dǎo)程序,也可以支持磁盤引導(dǎo)啟動(dòng)。例如:LoadLin可以從DOS下啟動(dòng)Linux;還有ROLO、LinuxBIOS,U-Boot也支持這種功能。

3Flash啟動(dòng)方式

大多數(shù)嵌入式系統(tǒng)上都使用Flash存儲(chǔ)介質(zhì)。Flash有很多類型,包括NOR FlashNAND Flash和其他半導(dǎo)體盤。其中,NOR Flash(也就是線性Flash)使用最為普遍。

NOR Flash可以支持隨機(jī)訪問,所以代碼是可以直接在Flash上執(zhí)行的。Bootloader一般是存儲(chǔ)在Flash芯片上的。另外,Linux內(nèi)核映像和RAMDISK也可以存儲(chǔ)在Flash上。通常需要把Flash分區(qū)使用,每個(gè)區(qū)的大小應(yīng)該是Flash擦除塊大小的整數(shù)倍。圖6.2Bootloader和內(nèi)核映像以及文件系統(tǒng)的分區(qū)表。

6.2  Flash存儲(chǔ)示意圖

Bootloader一般放在Flash的底端或者頂端,這要根據(jù)處理器的復(fù)位向量設(shè)置。要使Bootloader的入口位于處理器上電執(zhí)行第一條指令的位置。

接下來(lái)分配參數(shù)區(qū),這里可以作為Bootloader的參數(shù)保存區(qū)域。

再下來(lái)內(nèi)核映像區(qū)。Bootloader引導(dǎo)Linux內(nèi)核,就是要從這個(gè)地方把內(nèi)核映像解壓到RAM中去,然后跳轉(zhuǎn)到內(nèi)核映像入口執(zhí)行。

然后是文件系統(tǒng)區(qū)。如果使用Ramdisk文件系統(tǒng),則需要Bootloader把它解壓到RAM中。如果使用JFFS2文件系統(tǒng),將直接掛接為根文件系統(tǒng)。這兩種文件系統(tǒng)將在第12章詳細(xì)講解。

最后還可以分出一些數(shù)據(jù)區(qū),這要根據(jù)實(shí)際需要和Flash大小來(lái)考慮了。

這些分區(qū)是開發(fā)者定義的,Bootloader一般直接讀寫對(duì)應(yīng)的偏移地址。到了Linux內(nèi)核空間,可以配置成MTD設(shè)備來(lái)訪問Flash分區(qū)。但是,有的Bootloader也支持分區(qū)的功能,例如:Redboot可以創(chuàng)建Flash分區(qū)表,并且內(nèi)核MTD驅(qū)動(dòng)可以解析出redboot的分區(qū)表。

除了NOR Flash,還有NAND Flash、Compact FlashDiskOnChip等。這些Flash具有芯片價(jià)格低,存儲(chǔ)容量大的特點(diǎn)。但是這些芯片一般通過專用控制器的I/O方式來(lái)訪問,不能隨機(jī)訪問,因此引導(dǎo)方式跟NOR Flash也不同。在這些芯片上,需要配置專用的引導(dǎo)程序。通常,這種引導(dǎo)程序起始的一段代碼就把整個(gè)引導(dǎo)程序復(fù)制到RAM中運(yùn)行,從而實(shí)現(xiàn)自舉啟動(dòng),這跟從磁盤上啟動(dòng)有些相似。

6.1.3  Bootloader的種類

嵌入式系統(tǒng)世界已經(jīng)有各種各樣的Bootloader,種類劃分也有多種方式。除了按照處理器體系結(jié)構(gòu)不同劃分以外,還有功能復(fù)雜程度的不同。

首先區(qū)分一下“Bootloader”和“Monitor”的概念。嚴(yán)格來(lái)說(shuō),“Bootloader”只是引導(dǎo)設(shè)備并且執(zhí)行主程序的固件;而“Monitor”還提供了更多的命令行接口,可以進(jìn)行調(diào)試、讀寫內(nèi)存、燒寫Flash、配置環(huán)境變量等?!?/SPAN>Monitor”在嵌入式系統(tǒng)開發(fā)過程中可以提供很好的調(diào)試功能,開發(fā)完成以后,就完全設(shè)置成了一個(gè)“Bootloader”。所以,習(xí)慣上大家把它們統(tǒng)稱為Bootloader。

6.1列出了Linux的開放源碼引導(dǎo)程序及其支持的體系結(jié)構(gòu)。表中給出了X86 ARM PowerPC體系結(jié)構(gòu)的常用引導(dǎo)程序,并且注明了每一種引導(dǎo)程序是不是“Monitor”。

6.1                                                   開放源碼的Linux 引導(dǎo)程序

Bootloader

Monitor

   

x86

ARM

PowerPC

LILO

Linux磁盤引導(dǎo)程序

GRUB

GNULILO替代程序

Loadlin

DOS引導(dǎo)Linux

ROLO

ROM引導(dǎo)Linux而不需要BIOS

Etherboot

通過以太網(wǎng)卡啟動(dòng)Linux系統(tǒng)的固件

LinuxBIOS

完全替代BUISLinux引導(dǎo)程序

BLOB

LART等硬件平臺(tái)的引導(dǎo)程序

U-boot

通用引導(dǎo)程序

RedBoot

基于eCos的引導(dǎo)程序

 

對(duì)于每種體系結(jié)構(gòu),都有一系列開放源碼Bootloader可以選用。

1X86

X86的工作站和服務(wù)器上一般使用LILOGRUB。LILOLinux發(fā)行版主流的Bootloader。不過Redhat Linux發(fā)行版已經(jīng)使用了GRUBGRUBLILO有更有好的顯示界面,使用配置也更加靈活方便。

在某些X86嵌入式單板機(jī)或者特殊設(shè)備上,會(huì)采用其他Bootloader,例如:ROLO。這些Bootloader可以取代BIOS的功能,能夠從FLASH中直接引導(dǎo)Linux啟動(dòng)?,F(xiàn)在ROLO支持的開發(fā)板已經(jīng)并入U-Boot,所以U-Boot也可以支持X86平臺(tái)。

2ARM

ARM處理器的芯片商很多,所以每種芯片的開發(fā)板都有自己的Bootloader。結(jié)果ARM bootloader也變得多種多樣。最早有為ARM720處理器的開發(fā)板的固件,又有了armboot,StrongARM平臺(tái)的blob,還有S3C2410處理器開發(fā)板上的vivi等?,F(xiàn)在armboot已經(jīng)并入了U-Boot,所以U-Boot也支持ARM/XSCALE平臺(tái)。U-Boot已經(jīng)成為ARM平臺(tái)事實(shí)上的標(biāo)準(zhǔn)Bootloader。

3PowerPC

PowerPC平臺(tái)的處理器有標(biāo)準(zhǔn)的Bootloader,就是ppcboot。PPCBOOT在合并armboot等之后,創(chuàng)建了U-Boot,成為各種體系結(jié)構(gòu)開發(fā)板的通用引導(dǎo)程序。U-Boot仍然是PowerPC平臺(tái)的主要Bootloader

4MIPS

MIPS公司開發(fā)的YAMON是標(biāo)準(zhǔn)的Bootloader,也有許多MIPS芯片商為自己的開發(fā)板寫了Bootloader?,F(xiàn)在,U-Boot也已經(jīng)支持MIPS平臺(tái)。

5SH

SH平臺(tái)的標(biāo)準(zhǔn)Bootloadersh-bootRedboot在這種平臺(tái)上也很好用。

6M68K

M68K平臺(tái)沒有標(biāo)準(zhǔn)的Bootloader。Redboot能夠支持m68k系列的系統(tǒng)。

值得說(shuō)明的是Redboot,它幾乎能夠支持所有的體系結(jié)構(gòu),包括MIPS、SH、M68K等體系結(jié)構(gòu)。Redboot是以eCos為基礎(chǔ),采用GPL許可的開源軟件工程。現(xiàn)在由core eCos的開發(fā)人員維護(hù),源碼下載網(wǎng)站是http://www./snapshots。Redboot的文檔也相當(dāng)完善,有詳細(xì)的使用手冊(cè)《RedBoot User’s Guide》。

6.2.1  U-Boot工程簡(jiǎn)介

最早,DENX軟件工程中心的Wolfgang Denk基于8xxrom的源碼創(chuàng)建了PPCBOOT工程,并且不斷添加處理器的支持。后來(lái),Sysgo Gmbhppcboot移植到ARM平臺(tái)上,創(chuàng)建了ARMboot工程。然后以ppcboot工程和armboot工程為基礎(chǔ),創(chuàng)建了U-Boot工程。

現(xiàn)在U-Boot已經(jīng)能夠支持PowerPC、ARM、X86、MIPS體系結(jié)構(gòu)的上百種開發(fā)板,已經(jīng)成為功能最多、靈活性最強(qiáng)并且開發(fā)最積極的開放源碼Bootloader。目前仍然由DENXWolfgang Denk維護(hù)。

U-Boot的源碼包可以從sourceforge網(wǎng)站下載,還可以訂閱該網(wǎng)站活躍的U-Boot Users郵件論壇,這個(gè)郵件論壇對(duì)于U-Boot的開發(fā)和使用都很有幫助。

U-Boot軟件包下載網(wǎng)站:http:///project/u-boot。

U-Boot郵件列表網(wǎng)站:http://lists./lists/listinfo/u-boot-users/

DENX相關(guān)的網(wǎng)站:http://www./re/DPLG.html。

6.2.2  U-Boot源碼結(jié)構(gòu)

從網(wǎng)站上下載得到U-Boot源碼包,例如:U-Boot-1.1.2.tar.bz2

解壓就可以得到全部U-Boot源程序。在頂層目錄下有18個(gè)子目錄,分別存放和管理不同的源程序。這些目錄中所要存放的文件有其規(guī)則,可以分為3類。

· 1類目錄與處理器體系結(jié)構(gòu)或者開發(fā)板硬件直接相關(guān);

· 2類目錄是一些通用的函數(shù)或者驅(qū)動(dòng)程序;

· 3類目錄是U-Boot的應(yīng)用程序、工具或者文檔。

6.2列出了U-Boot頂層目錄下各級(jí)目錄存放原則。

6.2                                                  U-Boot的源碼頂層目錄說(shuō)明

   

   

說(shuō)

board

平臺(tái)依賴

存放電路板相關(guān)的目錄文件,例如:RPXlite(mpc8xx)、smdk2410(arm920t)sc520_cdp(x86) 等目錄

cpu

平臺(tái)依賴

存放CPU相關(guān)的目錄文件,例如:mpc8xx、ppc4xxarm720t、arm920t xscale、i386等目錄

lib_ppc

平臺(tái)依賴

存放對(duì)PowerPC體系結(jié)構(gòu)通用的文件,主要用于實(shí)現(xiàn)PowerPC平臺(tái)通用的函數(shù)

   

   

說(shuō)

lib_arm

平臺(tái)依賴

存放對(duì)ARM體系結(jié)構(gòu)通用的文件,主要用于實(shí)現(xiàn)ARM平臺(tái)通用的函數(shù)

lib_i386

平臺(tái)依賴

存放對(duì)X86體系結(jié)構(gòu)通用的文件,主要用于實(shí)現(xiàn)X86平臺(tái)通用的函數(shù)

include

通用

頭文件和開發(fā)板配置文件,所有開發(fā)板的配置文件都在configs目錄下

common

通用

通用的多功能函數(shù)實(shí)現(xiàn)

lib_generic

通用

通用庫(kù)函數(shù)的實(shí)現(xiàn)

Net

通用

存放網(wǎng)絡(luò)的程序

Fs

通用

存放文件系統(tǒng)的程序

Post

通用

存放上電自檢程序

drivers

通用

通用的設(shè)備驅(qū)動(dòng)程序,主要有以太網(wǎng)接口的驅(qū)動(dòng)

Disk

通用

硬盤接口程序

Rtc

通用

RTC的驅(qū)動(dòng)程序

Dtt

通用

數(shù)字溫度測(cè)量器或者傳感器的驅(qū)動(dòng)

examples

應(yīng)用例程

一些獨(dú)立運(yùn)行的應(yīng)用程序的例子,例如helloworld

tools

工具

存放制作S-Record 或者 U-Boot格式的映像等工具,例如mkimage

Doc

文檔

開發(fā)使用文檔

 

U-Boot的源代碼包含對(duì)幾十種處理器、數(shù)百種開發(fā)板的支持??墒菍?duì)于特定的開發(fā)板,配置編譯過程只需要其中部分程序。這里具體以S3C2410 arm920t處理器為例,具體分析S3C2410處理器和開發(fā)板所依賴的程序,以及U-Boot的通用函數(shù)和工具。

6.2.3  U-Boot的編譯

U-Boot的源碼是通過GCCMakefile組織編譯的。頂層目錄下的Makefile首先可以設(shè)置開發(fā)板的定義,然后遞歸地調(diào)用各級(jí)子目錄下的Makefile,最后把編譯過的程序鏈接成U-Boot映像。

1.頂層目錄下的Makefile

它負(fù)責(zé)U-Boot整體配置編譯。按照配置的順序閱讀其中關(guān)鍵的幾行。

每一種開發(fā)板在Makefile都需要有板子配置的定義。例如smdk2410開發(fā)板的定義如下。

 

smdk2410_config :   unconfig

     @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

 

執(zhí)行配置U-Boot的命令make smdk2410_config,通過./mkconfig腳本生成include/config.
mk
的配置文件。文件內(nèi)容正是根據(jù)Makefile對(duì)開發(fā)板的配置生成的。

 

ARCH   = arm

CPU    = arm920t

BOARD  = smdk2410

SOC    = s3c24x0

 

上面的include/config.mk文件定義了ARCH、CPU、BOARDSOC這些變量。這樣硬件平臺(tái)依賴的目錄文件可以根據(jù)這些定義來(lái)確定。SMDK2410平臺(tái)相關(guān)目錄如下。

board/smdk2410/

cpu/arm920t/

cpu/arm920t/s3c24x0/

lib_arm/

include/asm-arm/

include/configs/smdk2410.h

再回到頂層目錄的Makefile文件開始的部分,其中下列幾行包含了這些變量的定義。

 

# load ARCH, BOARD, and CPU configuration

include include/config.mk

export       ARCH CPU BOARD VENDOR SOC

 

Makefile的編譯選項(xiàng)和規(guī)則在頂層目錄的config.mk文件中定義。各種體系結(jié)構(gòu)通用的規(guī)則直接在這個(gè)文件中定義。通過ARCH、CPUBOARD、SOC等變量為不同硬件平臺(tái)定義不同選項(xiàng)。不同體系結(jié)構(gòu)的規(guī)則分別包含在ppc_config.mk、arm_config.mk、mips_config.mk等文件中。

頂層目錄的Makefile中還要定義交叉編譯器,以及編譯U-Boot所依賴的目標(biāo)文件。

 

ifeq ($(ARCH),arm)

CROSS_COMPILE = arm-linux-          //交叉編譯器的前綴

#endif

export  CROSS_COMPILE

# U-Boot objects....order is important (i.e. start must be first)

OBJS  = cpu/$(CPU)/start.o                  //處理器相關(guān)的目標(biāo)文件

LIBS  = lib_generic/libgeneric.a            //定義依賴的目錄,每個(gè)目錄下先把目標(biāo)文件連接成*.a文件。

LIBS += board/$(BOARDDIR)/lib$(BOARD).a

LIBS += cpu/$(CPU)/lib$(CPU).a

ifdef SOC

LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a

endif

LIBS += lib_$(ARCH)/lib$(ARCH).a

 

然后還有U-Boot映像編譯的依賴關(guān)系。

 

ALL = u-boot.srec u-boot.bin System.map

all:        $(ALL)

u-boot.srec:    u-boot

            $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@

u-boot.bin: u-boot

            $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

……

u-boot:         depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)

            UNDEF_SYM='$(OBJDUMP) -x $(LIBS) \

            |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\

            $(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) \

                 --start-group $(LIBS) $(PLATFORM_LIBS) --end-group \

                 -Map u-boot.map -o u-boot

 

Makefile缺省的編譯目標(biāo)為all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srecu-boot.bin又依賴于U-BootU-Boot就是通過ld命令按照u-boot.map地址表把目標(biāo)文件組裝成u-boot。

其他Makefile內(nèi)容就不再詳細(xì)分析了,上述代碼分析應(yīng)該可以為閱讀代碼提供了一個(gè)線索。

2.開發(fā)板配置頭文件

除了編譯過程Makefile以外,還要在程序中為開發(fā)板定義配置選項(xiàng)或者參數(shù)。這個(gè)頭文件是include/configs/<board_name>.h。<board_name>用相應(yīng)的BOARD定義代替。

這個(gè)頭文件中主要定義了兩類變量。

一類是選項(xiàng),前綴是CONFIG_,用來(lái)選擇處理器、設(shè)備接口、命令、屬性等。例如:

 

#define   CONFIG_ARM920T         1

#define   CONFIG_DRIVER_CS8900  1

 

另一類是參數(shù),前綴是CFG_,用來(lái)定義總線頻率、串口波特率、Flash地址等參數(shù)。例如:

 

#define     CFG_FLASH_BASE      0x00000000

#define CFG_PROMPT          "=>"

3.編譯結(jié)果

根據(jù)對(duì)Makefile的分析,編譯分為2步。第1步配置,例如:make smdk2410_config;第2步編譯,執(zhí)行make就可以了。

編譯完成后,可以得到U-Boot各種格式的映像文件和符號(hào)表,如表6.3所示。

6.3                                                  U-Boot編譯生成的映像文件

說(shuō)   

說(shuō)   

System.map

U-Boot映像的符號(hào)表

u-boot.bin

U-Boot映像原始的二進(jìn)制格式

u-boot

U-Boot映像的ELF格式

u-boot.srec

U-Boot映像的S-Record格式

 

U-Boot3種映像格式都可以燒寫到Flash中,但需要看加載器能否識(shí)別這些格式。一般u-boot.bin最為常用,直接按照二進(jìn)制格式下載,并且按照絕對(duì)地址燒寫到Flash中就可以了。U-Bootu-boot.srec格式映像都自帶定位信息。

4U-Boot工具

tools目錄下還有些U-Boot的工具。這些工具有的也經(jīng)常用到。表6.4說(shuō)明了幾種工具的用途。

6.4                                                              U-Boot的工具

說(shuō)   

說(shuō)    

bmp_logo

制作標(biāo)記的位圖結(jié)構(gòu)體

img2srec

轉(zhuǎn)換SREC格式映像

envcrc

校驗(yàn)u-boot內(nèi)部嵌入的環(huán)境變量

mkimage

轉(zhuǎn)換U-Boot格式映像

gen_eth_addr

生成以太網(wǎng)接口MAC地址

updater

U-Boot自動(dòng)更新升級(jí)工具

 

這些工具都有源代碼,可以參考改寫其他工具。其中mkimage是很常用的一個(gè)工具,Linux內(nèi)核映像和ramdisk文件系統(tǒng)映像都可以轉(zhuǎn)換成U-Boot的格式。

6.2.4  U-Boot的移植

U-Boot能夠支持多種體系結(jié)構(gòu)的處理器,支持的開發(fā)板也越來(lái)越多。因?yàn)?/SPAN>Bootloader是完全依賴硬件平臺(tái)的,所以在新電路板上需要移植U-Boot程序。

開始移植U-Boot之前,先要熟悉硬件電路板和處理器。確認(rèn)U-Boot是否已經(jīng)支持新開發(fā)板的處理器和I/O設(shè)備。假如U-Boot已經(jīng)支持一塊非常相似的電路板,那么移植的過程將非常簡(jiǎn)單。

移植U-Boot工作就是添加開發(fā)板硬件相關(guān)的文件、配置選項(xiàng),然后配置編譯。

開始移植之前,需要先分析一下U-Boot已經(jīng)支持的開發(fā)板,比較出硬件配置最接近的開發(fā)板。選擇的原則是,首先處理器相同,其次處理器體系結(jié)構(gòu)相同,然后是以太網(wǎng)接口等外圍接口。還要驗(yàn)證一下這個(gè)參考開發(fā)板的U-Boot,至少能夠配置編譯通過。

S3C2410處理器的開發(fā)板為例,U-Boot-1.1.2版本已經(jīng)支持SMDK2410開發(fā)板。我們可以基于SMDK2410移植,那么先把SMDK2410編譯通過。

我們以S3C2410開發(fā)板fs2410為例說(shuō)明。移植的過程參考SMDK2410開發(fā)板,SMDK2410U-Boot-1.1.2中已經(jīng)支持。

移植U-Boot的基本步驟如下。

1)在頂層Makefile中為開發(fā)板添加新的配置選項(xiàng),使用已有的配置項(xiàng)目為例。

 

smdk2410_config   :       unconfig

         @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

 

參考上面2行,添加下面2行。

 

fs2410_config   :       unconfig

     @./mkconfig $(@:_config=) arm arm920t fs2410 NULL s3c24x0

 

2)創(chuàng)建一個(gè)新目錄存放開發(fā)板相關(guān)的代碼,并且添加文件。

board/fs2410/config.mk

board/fs2410/flash.c

board/fs2410/fs2410.c

board/fs2410/Makefile

board/fs2410/memsetup.S

board/fs2410/u-boot.lds

3)為開發(fā)板添加新的配置文件

可以先復(fù)制參考開發(fā)板的配置文件,再修改。例如:

$cp include/configs/smdk2410.h  include/configs/fs2410.h

如果是為一顆新的CPU移植,還要?jiǎng)?chuàng)建一個(gè)新的目錄存放CPU相關(guān)的代碼。

4)配置開發(fā)板

$ make fs2410_config

5)編譯U-Boot

執(zhí)行make命令,編譯成功可以得到U-Boot映像。有些錯(cuò)誤是跟配置選項(xiàng)是有關(guān)系的,通常打開某些功能選項(xiàng)會(huì)帶來(lái)一些錯(cuò)誤,一開始可以盡量跟參考板配置相同。

6)添加驅(qū)動(dòng)或者功能選項(xiàng)

在能夠編譯通過的基礎(chǔ)上,還要實(shí)現(xiàn)U-Boot的以太網(wǎng)接口、Flash擦寫等功能。

對(duì)于FS2410開發(fā)板的以太網(wǎng)驅(qū)動(dòng)和smdk2410完全相同,所以可以直接使用。CS8900驅(qū)動(dòng)程序文件如下。

drivers/cs8900.c

drivers/cs8900.h

對(duì)于Flash的選擇就麻煩多了,Flash芯片價(jià)格或者采購(gòu)方面的因素都有影響。多數(shù)開發(fā)板大小、型號(hào)不都相同。所以還需要移植Flash的驅(qū)動(dòng)。每種開發(fā)板目錄下一般都有flash.c這個(gè)文件,需要根據(jù)具體的Flash類型修改。例如:

board/fs2410/flash.c

7)調(diào)試U-Boot源代碼,直到U-Boot在開發(fā)板上能夠正常啟動(dòng)。

調(diào)試的過程可能是很艱難的,需要借助工具,并且有些問題可能困擾很長(zhǎng)時(shí)間。

6.2.5 添加U-Boot命令

U-Boot的命令為用戶提供了交互功能,并且已經(jīng)實(shí)現(xiàn)了幾十個(gè)常用的命令。如果開發(fā)板需要很特殊的操作,可以添加新的U-Boot命令。

U-Boot的每一個(gè)命令都是通過U_Boot_CMD宏定義的。這個(gè)宏在include/command.h頭文件中定義,每一個(gè)命令定義一個(gè)cmd_tbl_t結(jié)構(gòu)體。

 

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

 

這樣每一個(gè)U-Boot命令有一個(gè)結(jié)構(gòu)體來(lái)描述。結(jié)構(gòu)體包含的成員變量:命令名稱、最大參數(shù)個(gè)數(shù)、重復(fù)數(shù)、命令執(zhí)行函數(shù)、用法、幫助。

從控制臺(tái)輸入的命令是由common/command.c中的程序解釋執(zhí)行的。find_cmd()負(fù)責(zé)匹配輸入的命令,從列表中找出對(duì)應(yīng)的命令結(jié)構(gòu)體。

基于U-Boot命令的基本框架,來(lái)分析一下簡(jiǎn)單的icache操作命令,就可以知道添加新命令的方法。

1)定義CACHE命令。在include/cmd_confdefs.h中定義了所有U-Boot命令的標(biāo)志位。

 

#define CFG_CMD_CACHE       0x00000010ULL   /* icache, dcache       */

 

如果有更多的命令,也要在這里添加定義。

2)實(shí)現(xiàn)CACHE命令的操作函數(shù)。下面是common/cmd_cache.c文件中icache命令部分的代碼。

 

#if (CONFIG_COMMANDS & CFG_CMD_CACHE)

static int on_off (const char *s)

{       //這個(gè)函數(shù)解析參數(shù),判斷是打開cache,還是關(guān)閉cache

        if (strcmp(s, "on") == 0) {  //參數(shù)為“on

               return (1);

        } else if (strcmp(s, "off") == 0) {  //參數(shù)為“off

               return (0);

    }

    return (-1);

}

 

int do_icache ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{     //對(duì)指令cache的操作函數(shù)

      switch (argc) {

      case 2:               /* 參數(shù)個(gè)數(shù)為1,則執(zhí)行打開或者關(guān)閉指令cache操作 */

             switch (on_off(argv[1])) {

             case 0:     icache_disable();        //打開指令cache

                   break;

             case 1:     icache_enable ();        //關(guān)閉指令cache

                   break;

             }

            /* FALL TROUGH */

      case 1:           /* 參數(shù)個(gè)數(shù)為0,則獲取指令cache狀態(tài)*/ 

            printf ("Instruction Cache is %s\n",

                    icache_status() ? "ON" : "OFF");

            return 0;

      default:  //其他缺省情況下,打印命令使用說(shuō)明

            printf ("Usage:\n%s\n", cmdtp->usage);

            return 1;

      }

      return 0;

}

……

U_Boot_CMD( //通過宏定義命令

    icache,   2,   1,     do_icache,  //命令為icache,命令執(zhí)行函數(shù)為do_icache()

    "icache  - enable or disable instruction cache\n",   //幫助信息

    "[on, off]\n"

    "    - enable or disable instruction cache\n"

);

……

#endif

 

U-Boot的命令都是通過結(jié)構(gòu)體__U_Boot_cmd_##name來(lái)描述的。根據(jù)U_Boot_CMDinclude/command.h中的兩行定義可以明白。

 

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

 

還有,不要忘了在common/Makefile中添加編譯的目標(biāo)文件。

3)打開CONFIG_COMMANDS選項(xiàng)的命令標(biāo)志位。這個(gè)程序文件開頭有#if語(yǔ)句需要預(yù)處理是否包含這個(gè)命令函數(shù)。CONFIG_COMMANDS選項(xiàng)在開發(fā)板的配置文件中定義。例如:SMDK2410平臺(tái)在include/configs/smdk2410.h中有如下定義。

 

/***********************************************************

 * Command definition

 ***********************************************************/

#define CONFIG_COMMANDS \

                 (CONFIG_CMD_DFL  | \

                 CFG_CMD_CACHE     | \

                 CFG_CMD_REGINFO    | \

                 CFG_CMD_DATE      | \

                 CFG_CMD_ELF)

 

按照這3步,就可以添加新的U-Boot命令。

6.3  U-Boot的調(diào)試

新移植的U-Boot不能正常工作,這時(shí)就需要調(diào)試了。調(diào)試U-Boot離不開工具,只有理解U-Boot啟動(dòng)過程,才能正確地調(diào)試U-Boot源碼。

6.3.1 硬件調(diào)試器

硬件電路板制作完成以后,這時(shí)上面還沒有任何程序,就叫作裸板。首要的工作是把程序或者固件加載到裸板上,這就要通過硬件工具來(lái)完成。習(xí)慣上,這種硬件工具叫作仿真器。

仿真器可以通過處理器的JTAG等接口控制板子,直接把程序下載到目標(biāo)板內(nèi)存,或者進(jìn)行Flash編程。如果板上的Flash是可以拔插的,就可以通過專用的Flash燒寫器來(lái)完成。在第4章介紹過目標(biāo)板跟主機(jī)之間的連接,其中JTAG等接口就是專門用來(lái)連接仿真器的。

仿真器還有一個(gè)重要的功能就是在線調(diào)試程序,這對(duì)于調(diào)試Bootloader和硬件測(cè)試程序很有用。

從最簡(jiǎn)單的JTAG電纜,到ICE仿真器,再到可以調(diào)試Linux內(nèi)核的仿真器。

復(fù)雜的仿真器可以支持與計(jì)算機(jī)間的以太網(wǎng)或者USB接口通信。

對(duì)于U-Boot的調(diào)試,可以采用BDI2000。BDI2000完全可以反匯編地跟蹤Flash中的程序,也可以進(jìn)行源碼級(jí)的調(diào)試。

使用BDI2000調(diào)試U-boot的方法如下。

1)配置BDI2000和目標(biāo)板初始化程序,連接目標(biāo)板。

2)添加U-Boot的調(diào)試編譯選項(xiàng),重新編譯。

U-Boot的程序代碼是位置相關(guān)的,調(diào)試的時(shí)候盡量在內(nèi)存中調(diào)試,可以修改連接定位地址TEXT_BASETEXT_BASEboard/<board_name>/config.mk中定義。

另外,如果有復(fù)位向量也需要先從鏈接腳本中去掉。鏈接腳本是board/<board_name>/
u-boot.lds

添加調(diào)試選項(xiàng),在config.mk文件中查找,DBGFLAGS,加上-g選項(xiàng)。然后重新編譯U-Boot。

3)下載U-Boot到目標(biāo)板內(nèi)存。

通過BDI2000的下載命令LOAD,把程序加載到目標(biāo)板內(nèi)存中。然后跳轉(zhuǎn)到U-Boot入口。

4)啟動(dòng)GDB調(diào)試。

啟動(dòng)GDB調(diào)試,這里是交叉調(diào)試的GDBGDBBDI2000建立鏈接,然后就可以設(shè)置斷點(diǎn)執(zhí)行了。

 

$ arm-linux-gdb u-boot

(gdb)target remote 192.168.1.100:2001

(gdb)stepi

(gdb)b start_armboot

(gdb)c

6.3.2 軟件跟蹤

假如U-Boot沒有任何串口打印信息,手頭又沒有硬件調(diào)試工具,那樣怎么知道U-Boot執(zhí)行到什么地方了呢?可以通過開發(fā)板上的LED指示燈判斷。

開發(fā)板上最好設(shè)計(jì)安裝八段數(shù)碼管等LED,可以用來(lái)顯示數(shù)字或者數(shù)字位。

U-Boot可以定義函數(shù)show_boot_progress (int status),用來(lái)指示當(dāng)前啟動(dòng)進(jìn)度。在include/common.h頭文件中聲明這個(gè)函數(shù)。

 

#ifdef CONFIG_SHOW_BOOT_PROGRESS

void    show_boot_progress (int status);

#endif

 

CONFIG_SHOW_BOOT_PROGRESS是需要定義的。這個(gè)在板子配置的頭文件中定義。CSB226開發(fā)板對(duì)這項(xiàng)功能有完整實(shí)現(xiàn),可以參考。在頭文件include/configs/csb226.h中,有下列一行。

 

#define CONFIG_SHOW_BOOT_PROGRESS       1

 

函數(shù)show_boot_progress (int status)的實(shí)現(xiàn)跟開發(fā)板關(guān)系密切,所以一般在board目錄下的文件中實(shí)現(xiàn)??匆幌?/SPAN>CSB226board/csb226/csb226.c中的實(shí)現(xiàn)函數(shù)。

 

/** 設(shè)置CSB226板的01、2三個(gè)指示燈的開關(guān)狀態(tài)

 * csb226_set_led: - switch LEDs on or off

 * @param led:   LED to switch (0,1,2)

 * @param state: switch on (1) or off (0)

 */

void csb226_set_led(int led, int state)

{

      switch(led) {

             case 0: if (state==1) {

                              GPCR0 |= CSB226_USER_LED0;

                    } else if (state==0) {

                            GPSR0 |= CSB226_USER_LED0;

                    }

                    break;

             case 1: if (state==1) {

                              GPCR0 |= CSB226_USER_LED1;

                    } else if (state==0) {

                              GPSR0 |= CSB226_USER_LED1;

                    }

                    break;

             case 2: if (state==1) {

                              GPCR0 |= CSB226_USER_LED2;

                  } else if (state==0) {

                          GPSR0 |= CSB226_USER_LED2;

                  }

                  break;

      }

      return;

}

/** 顯示啟動(dòng)進(jìn)度函數(shù),在比較重要的階段,設(shè)置三個(gè)燈為亮的狀態(tài)(1, 5, 15*/

void show_boot_progress (int status)

{

      switch(status) {

            case  1: csb226_set_led(0,1); break;

            case  5: csb226_set_led(1,1); break;

            case 15: csb226_set_led(2,1); break;

      }

      return;

}

 

這樣,在U-Boot啟動(dòng)過程中就可以通過show_boot_progresss指示執(zhí)行進(jìn)度。比如hang()函數(shù)是系統(tǒng)出錯(cuò)時(shí)調(diào)用的函數(shù),這里需要根據(jù)特定的開發(fā)板給定顯示的參數(shù)值。

 

void hang (void)

{

      puts ("### ERROR ### Please RESET the board ###\n");

#ifdef CONFIG_SHOW_BOOT_PROGRESS

      show_boot_progress(-30);

#endif

      for (;;);

6.3.3  U-Boot啟動(dòng)過程

盡管有了調(diào)試跟蹤手段,甚至也可以通過串口打印信息了,但是不一定能夠判斷出錯(cuò)原因。如果能夠充分理解代碼的啟動(dòng)流程,那么對(duì)準(zhǔn)確地解決和分析問題很有幫助。

開發(fā)板上電后,執(zhí)行U-Boot的第一條指令,然后順序執(zhí)行U-Boot啟動(dòng)函數(shù)。函數(shù)調(diào)用順序如圖6.3所示。

看一下board/smsk2410/u-boot.lds這個(gè)鏈接腳本,可以知道目標(biāo)程序的各部分鏈接順序。第一個(gè)要鏈接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于這個(gè)程序中。下面詳細(xì)分析一下程序跳轉(zhuǎn)和函數(shù)的調(diào)用關(guān)系以及函數(shù)實(shí)現(xiàn)。

1cpu/arm920t/start.S

這個(gè)匯編程序是U-Boot的入口程序,開頭就是復(fù)位向量的代碼。

6.3  U-Boot啟動(dòng)代碼流程圖

 

_start: b       reset        //復(fù)位向量

       ldr   pc, _undefined_instruction

       ldr   pc, _software_interrupt

       ldr   pc, _prefetch_abort

       ldr   pc, _data_abort

       ldr   pc, _not_used

       ldr   pc, _irq      //中斷向量

       ldr   pc, _fiq      //中斷向量

 /* the actual reset code  */

reset:          //復(fù)位啟動(dòng)子程序

       /* 設(shè)置CPUSVC32模式 */

       mrs   r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr   cpsr,r0

/* 關(guān)閉看門狗 */

 

/* 這些初始化代碼在系統(tǒng)重起的時(shí)候執(zhí)行,運(yùn)行時(shí)熱復(fù)位從RAM中啟動(dòng)不執(zhí)行 */

#ifdef CONFIG_INIT_CRITICAL

       bl    cpu_init_crit

#endif

 

relocate:                       /* U-Boot重新定位到RAM */

       adr   r0, _start          /* r0是代碼的當(dāng)前位置 */

       ldr   r1, _TEXT_BASE      /* 測(cè)試判斷是從Flash啟動(dòng),還是RAM */

       cmp     r0, r1          /* 比較r0r1,調(diào)試的時(shí)候不要執(zhí)行重定位 */

       beq     stack_setup    /* 如果r0等于r1,跳過重定位代碼 */

       /* 準(zhǔn)備重新定位代碼 */

       ldr   r2, _armboot_start

       ldr   r3, _bss_start

       sub   r2, r3, r2          /* r2 得到armboot的大小   */

       add   r2, r0, r2          /* r2 得到要復(fù)制代碼的末尾地址 */

copy_loop: /* 重新定位代碼 */

       ldmia r0!, {r3-r10}   /*從源地址[r0]復(fù)制 */

       stmia r1!, {r3-r10}   /* 復(fù)制到目的地址[r1] */

       cmp   r0, r2          /* 復(fù)制數(shù)據(jù)塊直到源數(shù)據(jù)末尾地址[r2] */

       ble   copy_loop

 

       /* 初始化堆棧等    */

stack_setup:

       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */

       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是內(nèi)存分配空間 */

       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo結(jié)構(gòu)體地址空間  */

#ifdef CONFIG_USE_IRQ

       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub   sp, r0, #12     /* abort-stack預(yù)留3個(gè)字 */

clear_bss:

       ldr   r0, _bss_start      /* 找到bss段起始地址 */

       ldr   r1, _bss_end        /*  bss段末尾地址   */

       mov   r2, #0x00000000     /* 清零 */

clbss_l:str r2, [r0]        /* bss段地址空間清零循環(huán)...  */

       add   r0, r0, #4

       cmp   r0, r1

       bne   clbss_l

       /* 跳轉(zhuǎn)到start_armboot函數(shù)入口,_start_armboot字保存函數(shù)入口指針 */

       ldr   pc, _start_armboot

_start_armboot: .word start_armboot     //start_armboot函數(shù)在lib_arm/board.c中實(shí)現(xiàn)

/* 關(guān)鍵的初始化子程序 */

cpu_init_crit:

……  //初始化CACHE,關(guān)閉MMU等操作指令

       /* 初始化RAM時(shí)鐘。

       * 因?yàn)閮?nèi)存時(shí)鐘是依賴開發(fā)板硬件的,所以在board的相應(yīng)目錄下可以找到memsetup.S文件。

       */

       mov   ip, lr

       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中實(shí)現(xiàn)

       mov   lr, ip

       mov   pc, lr

 

2lib_arm/board.c

start_armbootU-Boot執(zhí)行的第一個(gè)C語(yǔ)言函數(shù),完成系統(tǒng)初始化工作,進(jìn)入主循環(huán),處理用戶輸入的命令。

 

 

void start_armboot (void)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong size;

       init_fnc_t **init_fnc_ptr;

       char *s;

       /* Pointer is writable since we allocated a register for it */

       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

       /* compiler optimization barrier needed for GCC >= 3.4 */

       __asm__ __volatile__("": : :"memory");

       memset ((void*)gd, 0, sizeof (gd_t));

       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

       memset (gd->bd, 0, sizeof (bd_t));

       monitor_flash_len = _bss_start - _armboot_start;

       /* 順序執(zhí)行init_sequence數(shù)組中的初始化函數(shù) */

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                      hang ();

              }

       }

       /*配置可用的Flash */

       size = flash_init ();

       display_flash_config (size);

       /* _armboot_start u-boot.lds鏈接腳本中定義 */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

       /* 配置環(huán)境變量,重新定位 */

       env_relocate ();

       /* 從環(huán)境變量中獲取IP地址 */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

       /* 以太網(wǎng)接口MAC 地址 */

       ……

       devices_init ();      /* 獲取列表中的設(shè)備 */

       jumptable_init ();

       console_init_r ();    /* 完整地初始化控制臺(tái)設(shè)備 */

       enable_interrupts (); /* 使能例外處理 */

       /* 通過環(huán)境變量初始化 */

       if ((s = getenv ("loadaddr")) != NULL) {

               load_addr = simple_strtoul (s, NULL, 16);

       }

       /* main_loop()總是試圖自動(dòng)啟動(dòng),循環(huán)不斷執(zhí)行 */

       for (;;) {

               main_loop ();      /* 主循環(huán)函數(shù)處理執(zhí)行用戶命令 -- common/main.c */

       }

       /* NOTREACHED - no way out of command loop except booting */

}

 

3init_sequence[]

init_sequence[]數(shù)組保存著基本的初始化函數(shù)指針。這些函數(shù)名稱和實(shí)現(xiàn)的程序文件在下列注釋中。

 

init_fnc_t *init_sequence[] = {

       cpu_init,             /* 基本的處理器相關(guān)配置 -- cpu/arm920t/cpu.c */

       board_init,           /* 基本的板級(jí)相關(guān)配置 -- board/smdk2410/smdk2410.c */

       interrupt_init,       /* 初始化例外處理 -- cpu/arm920t/s3c24x0/interrupt.c */

       env_init,             /* 初始化環(huán)境變量 -- common/cmd_flash.c */

       init_baudrate,        /* 初始化波特率設(shè)置 -- lib_arm/board.c */

       serial_init,          /* 串口通訊設(shè)置 -- cpu/arm920t/s3c24x0/serial.c */

       console_init_f,       /* 控制臺(tái)初始化階段1 -- common/console.c */

       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */

       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */

       display_dram_config,  /* 顯示RAM的配置大小 -- lib_arm/board.c */

       NULL,

};

6.3.4  U-Boot與內(nèi)核的關(guān)系

U-Boot作為Bootloader,具備多種引導(dǎo)內(nèi)核啟動(dòng)的方式。常用的gobootm命令可以直接引導(dǎo)內(nèi)核映像啟動(dòng)。U-Boot與內(nèi)核的關(guān)系主要是內(nèi)核啟動(dòng)過程中參數(shù)的傳遞。

1go命令的實(shí)現(xiàn)

 

/* common/cmd_boot.c  */

int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong addr, rc;

       int     rcode = 0;

       if (argc < 2) {

              printf ("Usage:\n%s\n", cmdtp->usage);

              return 1;

       }

       addr = simple_strtoul(argv[1], NULL, 16);

       printf ("## Starting application at 0x%08lX ...\n", addr);

       /*

        * pass address parameter as argv[0] (aka command name),

        * and all remaining args

        */

       rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);

       if (rc != 0) rcode = 1;

 

       printf ("## Application terminated, rc = 0x%lX\n", rc);

       return rcode;

}

 

go命令調(diào)用do_go()函數(shù),跳轉(zhuǎn)到某個(gè)地址執(zhí)行的。如果在這個(gè)地址準(zhǔn)備好了自引導(dǎo)的內(nèi)核映像,就可以啟動(dòng)了。盡管go命令可以帶變參,實(shí)際使用時(shí)一般不用來(lái)傳遞參數(shù)。

2bootm命令的實(shí)現(xiàn)

 

/* common/cmd_bootm.c */

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong iflag;

       ulong addr;

       ulong data, len, checksum;

       ulong  *len_ptr;

       uint  unc_len = 0x400000;

       int   i, verify;

       char  *name, *s;

       int   (*appl)(int, char *[]);

       image_header_t *hdr = &header;

 

       s = getenv ("verify");

       verify = (s && (*s == 'n')) ? 0 : 1;

       if (argc < 2) {

              addr = load_addr;

       } else {

              addr = simple_strtoul(argv[1], NULL, 16);

       }

       SHOW_BOOT_PROGRESS (1);

       printf ("## Booting image at %08lx ...\n", addr);

       /* Copy header so we can blank CRC field for re-calculation */

       memmove (&header, (char *)addr, sizeof(image_header_t));

       if (ntohl(hdr->ih_magic) != IH_MAGIC)

       {

              puts ("Bad Magic Number\n");

              SHOW_BOOT_PROGRESS (-1);

              return 1;

       }

       SHOW_BOOT_PROGRESS (2);

       data = (ulong)&header;

       len  = sizeof(image_header_t);

 

       checksum = ntohl(hdr->ih_hcrc);

       hdr->ih_hcrc = 0;

 

       if(crc32 (0, (char *)data, len) != checksum) {

              puts ("Bad Header Checksum\n");

              SHOW_BOOT_PROGRESS (-2);

              return 1;

       }

       SHOW_BOOT_PROGRESS (3);

       /* for multi-file images we need the data part, too */

       print_image_hdr ((image_header_t *)addr);

       data = addr + sizeof(image_header_t);

       len  = ntohl(hdr->ih_size);

       if(verify) {

              puts ("   Verifying Checksum ... ");

              if(crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {

                     printf ("Bad Data CRC\n");

                     SHOW_BOOT_PROGRESS (-3);

                     return 1;

              }

              puts ("OK\n");

       }

       SHOW_BOOT_PROGRESS (4);

       len_ptr = (ulong *)data;

……

       switch (hdr->ih_os) {

       default:                /* handled by (original) Linux case */

       case IH_OS_LINUX:

             do_bootm_linux  (cmdtp, flag, argc, argv,

                         addr, len_ptr, verify);

             break;

       ……

}

 

bootm命令調(diào)用do_bootm函數(shù)。這個(gè)函數(shù)專門用來(lái)引導(dǎo)各種操作系統(tǒng)映像,可以支持引導(dǎo)LinuxvxWorks、QNX等操作系統(tǒng)。引導(dǎo)Linux的時(shí)候,調(diào)用do_bootm_linux()函數(shù)。

3do_bootm_linux函數(shù)的實(shí)現(xiàn)

 

/* lib_arm/armlinux.c */

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong len = 0, checksum;

       ulong initrd_start, initrd_end;

       ulong data;

       void (*theKernel)(int zero, int arch, uint params);

       image_header_t *hdr = &header;

       bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG

       char *commandline = getenv ("bootargs");

#endif

       theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

       /* Check if there is an initrd image */

       if(argc >= 3) {

              SHOW_BOOT_PROGRESS (9);

              addr = simple_strtoul (argv[2], NULL, 16);

              printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

              /* Copy header so we can blank CRC field for re-calculation */

              memcpy (&header, (char *) addr, sizeof (image_header_t));

              if (ntohl (hdr->ih_magic) != IH_MAGIC) {

                      printf ("Bad Magic Number\n");

                      SHOW_BOOT_PROGRESS (-10);

                      do_reset (cmdtp, flag, argc, argv);

              }

              data = (ulong) & header;

              len = sizeof (image_header_t);

              checksum = ntohl (hdr->ih_hcrc);

              hdr->ih_hcrc = 0;

              if(crc32 (0, (char *) data, len) != checksum) {

                     printf ("Bad Header Checksum\n");

                     SHOW_BOOT_PROGRESS (-11);

                     do_reset (cmdtp, flag, argc, argv);

              }

              SHOW_BOOT_PROGRESS (10);

              print_image_hdr (hdr);

              data = addr + sizeof (image_header_t);

              len = ntohl (hdr->ih_size);

              if(verify) {

                     ulong csum = 0;

                     printf ("   Verifying Checksum ... ");

                     csum = crc32 (0, (char *) data, len);

                     if (csum != ntohl (hdr->ih_dcrc)) {

                            printf ("Bad Data CRC\n");

                            SHOW_BOOT_PROGRESS (-12);

                            do_reset (cmdtp, flag, argc, argv);

                     }

                     printf ("OK\n");

              }

              SHOW_BOOT_PROGRESS (11);

              if ((hdr->ih_os != IH_OS_LINUX) ||

                     (hdr->ih_arch != IH_CPU_ARM) ||

                     (hdr->ih_type != IH_TYPE_RAMDISK)) {

                     printf ("No Linux ARM Ramdisk Image\n");

                     SHOW_BOOT_PROGRESS (-13);

                     do_reset (cmdtp, flag, argc, argv);

              }

              /* Now check if we have a multifile image */

       } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {

               ulong tail = ntohl (len_ptr[0]) % 4;

               int i;

               SHOW_BOOT_PROGRESS (13);

               /* skip kernel length and terminator */

               data = (ulong) (&len_ptr[2]);

               /* skip any additional image length fields */

               for (i = 1; len_ptr[i]; ++i)

                       data += 4;

              /* add kernel length, and align */

              data += ntohl (len_ptr[0]);

              if (tail) {

                       data += 4 - tail;

              }

              len = ntohl (len_ptr[1]);

       } else {

               /* no initrd image */

              SHOW_BOOT_PROGRESS (14);

              len = data = 0;

       }

       if (data) {

               initrd_start = data;

               initrd_end = initrd_start + len;

       } else {

               initrd_start = 0;

               initrd_end = 0;

       }

       SHOW_BOOT_PROGRESS (15);

       debug ("## Transferring control to Linux (at address %08lx) ...\n",

               (ulong) theKernel);

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \

      defined (CONFIG_CMDLINE_TAG) || \

      defined (CONFIG_INITRD_TAG) || \

      defined (CONFIG_SERIAL_TAG) || \

      defined (CONFIG_REVISION_TAG) || \

      defined (CONFIG_LCD) || \

      defined (CONFIG_VFD)

      setup_start_tag (bd);

#ifdef CONFIG_SERIAL_TAG

      setup_serial_tag (&params);

#endif

#ifdef CONFIG_REVISION_TAG

      setup_revision_tag (&params);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

      setup_memory_tags (bd);

#endif

#ifdef CONFIG_CMDLINE_TAG

      setup_commandline_tag (bd, commandline);

#endif

#ifdef CONFIG_INITRD_TAG

      if (initrd_start && initrd_end)

               setup_initrd_tag (bd, initrd_start, initrd_end);

#endif

      setup_end_tag (bd);

#endif

      /* we assume that the kernel is in place */

      printf ("\nStarting kernel ...\n\n");

      cleanup_before_linux ();

 

      theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

}

 

do_bootm_linux()函數(shù)是專門引導(dǎo)Linux映像的函數(shù),它還可以處理ramdisk文件系統(tǒng)的映像。這里引導(dǎo)的內(nèi)核映像和ramdisk映像,必須是U-Boot格式的。U-Boot格式的映像可以通過mkimage工具來(lái)轉(zhuǎn)換,其中包含了U-Boot可以識(shí)別的符號(hào)。

6.4  使用U-Boot

U-Boot是“Monitor”。除了Bootloader的系統(tǒng)引導(dǎo)功能,它還有用戶命令接口,提供了一些復(fù)雜的調(diào)試、讀寫內(nèi)存、燒寫Flash、配置環(huán)境變量等功能。掌握U-Boot的使用,將極大地方便嵌入式系統(tǒng)的開發(fā)。

6.4.1 燒寫U-BootFlash

新開發(fā)的電路板沒有任何程序可以執(zhí)行,也就不能啟動(dòng),需要先將U-Boot燒寫到Flash中。

如果主板上的EPROM或者Flash能夠取下來(lái),就可以通過編程器燒寫。例如:計(jì)算機(jī)BIOS就存儲(chǔ)在一塊256KBFlash上,通過插座與主板連接。

但是多數(shù)嵌入式單板使用貼片的Flash,不能取下來(lái)燒寫。這種情況可以通過處理器的調(diào)試接口,直接對(duì)板上的Flash編程。

處理器調(diào)試接口是為處理器芯片設(shè)計(jì)的標(biāo)準(zhǔn)調(diào)試接口,包含BDM、JTAGEJTAG 3種接口標(biāo)準(zhǔn)。JTAG接口在第4章已經(jīng)介紹過;BDMBackground Debug Mode)主要應(yīng)用在PowerPC8xx系列處理器上;EJTAG主要應(yīng)用在MIPS處理器上。這3種硬件接口標(biāo)準(zhǔn)定義有所不同,但是功能基本相同,下面都統(tǒng)稱為JTAG接口。

JTAG接口需要專用的硬件工具來(lái)連接。無(wú)論從功能、性能角度,還是從價(jià)格角度,這些工具都有很大差異。關(guān)于這些工具的選擇,將在第6.4.1節(jié)詳細(xì)介紹。

最簡(jiǎn)單方式就是通過JTAG電纜,轉(zhuǎn)接到計(jì)算機(jī)并口連接。這需要在主機(jī)端開發(fā)燒寫程序,還需要有并口設(shè)備驅(qū)動(dòng)程序。開發(fā)板上電或者復(fù)位的時(shí)候,燒寫程序探測(cè)到處理器并且開始通信,然后把Bootloader下載并燒寫到Flash中。這種方式速率很慢,可是價(jià)格非常便宜。一般來(lái)說(shuō),平均每秒鐘可以燒寫100200個(gè)字節(jié)。

燒寫完成后,復(fù)位實(shí)驗(yàn)板,串口終端應(yīng)該顯示U-Boot的啟動(dòng)信息。

6.4.2  U-Boot的常用命令

U-Boot上電啟動(dòng)后,敲任意鍵可以退出自動(dòng)啟動(dòng)狀態(tài),進(jìn)入命令行。

 

U-Boot 1.1.2 (Apr 26 2005 - 12:27:13)

U-Boot code: 11080000 -> 1109614C  BSS: -> 1109A91C

RAM Configuration:

Bank #0: 10000000 32 MB

Micron StrataFlash MT28F128J3 device initialized

Flash: 32 MB

In:    serial

Out:   serial

Err:   serial

Hit any key to stop autoboot:  0

U-Boot>

 

在命令行提示符下,可以輸入U-Boot的命令并執(zhí)行。U-Boot可以支持幾十個(gè)常用命令,通過這些命令,可以對(duì)開發(fā)板進(jìn)行調(diào)試,可以引導(dǎo)Linux內(nèi)核,還可以擦寫Flash完成系統(tǒng)部署等功能。掌握這些命令的使用,才能夠順利地進(jìn)行嵌入式系統(tǒng)的開發(fā)。

輸入help命令,可以得到當(dāng)前U-Boot的所有命令列表。每一條命令后面是簡(jiǎn)單的命令說(shuō)明。

 

=> help

       - alias for 'help'

autoscr - run script from memory

base    - print or set address offset

bdinfo  - print Board Info structure

boot    - boot default, i.e., run 'bootcmd'

bootd   - boot default, i.e., run 'bootcmd'

bootm   - boot application image from memory

bootp   - boot image via network using BootP/TFTP protocol

cmp     - memory compare

coninfo  - print console devices and information

cp      - memory copy

crc32   - checksum calculation

dhcp    - invoke DHCP client to obtain IP/boot params

echo    - echo args to console

erase   - erase FLASH memory

flinfo  - print FLASH memory information

go      - start application at address 'addr'

help    - print online help

iminfo  - print header information for application image

imls    - list all images found in flash

itest    - return true/false on integer compare

loadb   - load binary file over serial line (kermit mode)

loads   - load S-Record file over serial line

loop   - infinite loop on address range

md    - memory display

mm    - memory modify (auto-incrementing)

mtest   - simple RAM test

mw      - memory write (fill)

nfs     - boot image via network using NFS protocol

nm      - memory modify (constant address)

printenv - print environment variables

protect - enable or disable FLASH write protection

rarpboot - boot image via network using RARP/TFTP protocol

reset   - Perform RESET of the CPU

run     - run commands in an environment variable

saveenv - save environment variables to persistent storage

setenv  - set environment variables

sleep   - delay execution for some time

tftpboot - boot image via network using TFTP protocol

version - print monitor version

=>

 

U-Boot還提供了更加詳細(xì)的命令幫助,通過help命令還可以查看每個(gè)命令的參數(shù)說(shuō)明。由于開發(fā)過程的需要,有必要先把U-Boot命令的用法弄清楚。接下來(lái),根據(jù)每一條命令的幫助信息,解釋一下這些命令的功能和參數(shù)。

 

=> help bootm

bootm [addr [arg ...]]

    - boot application image stored in memory

          passing arguments 'arg ...'; when booting a Linux kernel,

          'arg' can be the address of an initrd image

 

bootm命令可以引導(dǎo)啟動(dòng)存儲(chǔ)在內(nèi)存中的程序映像。這些內(nèi)存包括RAM和可以永久保存的Flash

1個(gè)參數(shù)addr是程序映像的地址,這個(gè)程序映像必須轉(zhuǎn)換成U-Boot的格式。

2個(gè)參數(shù)對(duì)于引導(dǎo)Linux內(nèi)核有用,通常作為U-Boot格式的RAMDISK映像存儲(chǔ)地址;也可以是傳遞給Linux內(nèi)核的參數(shù)(缺省情況下傳遞bootargs環(huán)境變量給內(nèi)核)。

 

=> help bootp

bootp [loadAddress] [bootfilename]

bootp命令通過bootp請(qǐng)求,要求DHCP服務(wù)器分配IP地址,然后通過TFTP協(xié)議下載指定的文件到內(nèi)存。

1個(gè)參數(shù)是下載文件存放的內(nèi)存地址。

2個(gè)參數(shù)是要下載的文件名稱,這個(gè)文件應(yīng)該在開發(fā)主機(jī)上準(zhǔn)備好。

 

=> help cmp

cmp [.b, .w, .l] addr1 addr2 count

     - compare memory

 

cmp命令可以比較2塊內(nèi)存中的內(nèi)容。.b以字節(jié)為單位;.w以字為單位;.l以長(zhǎng)字為單位。注意:cmp.b中間不能保留空格,需要連續(xù)敲入命令。

1個(gè)參數(shù)addr1是第一塊內(nèi)存的起始地址。

2個(gè)參數(shù)addr2是第二塊內(nèi)存的起始地址。

3個(gè)參數(shù)count是要比較的數(shù)目,單位按照字節(jié)、字或者長(zhǎng)字。

 

=> help cp

cp [.b, .w, .l] source target count

       - copy memory

 

cp命令可以在內(nèi)存中復(fù)制數(shù)據(jù)塊,包括對(duì)Flash的讀寫操作。

1個(gè)參數(shù)source是要復(fù)制的數(shù)據(jù)塊起始地址。

2個(gè)參數(shù)target是數(shù)據(jù)塊要復(fù)制到的地址。這個(gè)地址如果在Flash中,那么會(huì)直接調(diào)用寫Flash的函數(shù)操作。所以U-BootFlash就使用這個(gè)命令,當(dāng)然需要先把對(duì)應(yīng)Flash區(qū)域擦干凈。

3個(gè)參數(shù)count是要復(fù)制的數(shù)目,根據(jù)cp.b cp.w cp.l分別以字節(jié)、字、長(zhǎng)字為單位。

 

=> help crc32

crc32 address count [addr]

     - compute CRC32 checksum [save at addr]  

 

crc32命令可以計(jì)算存儲(chǔ)數(shù)據(jù)的校驗(yàn)和。

1個(gè)參數(shù)address是需要校驗(yàn)的數(shù)據(jù)起始地址。

2個(gè)參數(shù)count是要校驗(yàn)的數(shù)據(jù)字節(jié)數(shù)。

3個(gè)參數(shù)addr用來(lái)指定保存結(jié)果的地址。

 

=> help echo

echo [args..]

      - echo args to console; \c suppresses newline

 

echo命令回顯參數(shù)。

 

=> help erase

erase start end

      - erase FLASH from addr 'start' to addr 'end'

erase N:SF[-SL]

      - erase sectors SF-SL in FLASH bank # N

erase bank N

      - erase FLASH bank # N

erase all

      - erase all FLASH banks

 

erase命令可以擦Flash

參數(shù)必須指定Flash擦除的范圍。

按照起始地址和結(jié)束地址,start必須是擦除塊的起始地址;end必須是擦除末尾塊的結(jié)束地址。這種方式最常用。舉例說(shuō)明:擦除0x20000 – 0x3ffff區(qū)域命令為erase 20000 3ffff。

按照組和扇區(qū),N表示Flash的組號(hào),SF表示擦除起始扇區(qū)號(hào),SL表示擦除結(jié)束扇區(qū)號(hào)。另外,還可以擦除整個(gè)組,擦除組號(hào)為N的整個(gè)Flash組。擦除全部Flash只要給出一個(gè)all的參數(shù)即可。

 

=> help flinfo

flinfo

       - print information for all FLASH memory banks

flinfo N

       - print information for FLASH memory bank # N

 

flinfo命令打印全部Flash組的信息,也可以只打印其中某個(gè)組。一般嵌入式系統(tǒng)的Flash只有一個(gè)組。

 

=> help go

go addr [arg ...]

      - start application at address 'addr'

        passing 'arg' as arguments

 

go命令可以執(zhí)行應(yīng)用程序。

1個(gè)參數(shù)是要執(zhí)行程序的入口地址。

2個(gè)可選參數(shù)是傳遞給程序的參數(shù),可以不用。

 

=> help iminfo

iminfo addr [addr ...]

      - print header information for application image starting at

         address 'addr' in memory; this includes verification of the

         image contents (magic number, header and payload checksums)

 

iminfo可以打印程序映像的開頭信息,包含了映像內(nèi)容的校驗(yàn)(序列號(hào)、頭和校驗(yàn)和)。

1個(gè)參數(shù)指定映像的起始地址。

可選的參數(shù)是指定更多的映像地址。

 

=> help loadb

loadb [ off ] [ baud ]

     - load binary file over serial line with offset 'off' and baudrate 'baud'

 

loadb命令可以通過串口線下載二進(jìn)制格式文件。

 

=> help loads

loads [ off ]

    - load S-Record file over serial line with offset 'off'

 

loads命令可以通過串口線下載S-Record格式文件。

 

=> help mw

mw [.b, .w, .l] address value [count]

     - write memory

 

mw命令可以按照字節(jié)、字、長(zhǎng)字寫內(nèi)存,.b .w .l的用法與cp命令相同。

1個(gè)參數(shù)address是要寫的內(nèi)存地址。

2個(gè)參數(shù)value是要寫的值。

3個(gè)可選參數(shù)count是要寫單位值的數(shù)目。

 

=> help nfs

nfs [loadAddress] [host ip addr:bootfilename]

 

nfs命令可以使用NFS網(wǎng)絡(luò)協(xié)議通過網(wǎng)絡(luò)啟動(dòng)映像。

 

=> help nm

nm [.b, .w, .l] address

     - memory modify, read and keep address

 

nm命令可以修改內(nèi)存,可以按照字節(jié)、字、長(zhǎng)字操作。

參數(shù)address是要讀出并且修改的內(nèi)存地址。

 

=> help printenv

printenv

      - print values of all environment variables

printenv name ...

      - print value of environment variable 'name'

 

printenv命令打印環(huán)境變量。

可以打印全部環(huán)境變量,也可以只打印參數(shù)中列出的環(huán)境變量。

 

=> help protect

protect on  start end

      - protect Flash from addr 'start' to addr 'end'

protect on  N:SF[-SL]

      - protect sectors SF-SL in Flash bank # N

protect on  bank N

      - protect Flash bank # N

protect on  all

      - protect all Flash banks

protect off start end

      - make Flash from addr 'start' to addr 'end' writable

protect off N:SF[-SL]

     - make sectors SF-SL writable in Flash bank # N

protect off bank N

     - make Flash bank # N writable

protect off all

     - make all Flash banks writable

 

protect命令是對(duì)Flash寫保護(hù)的操作,可以使能和解除寫保護(hù)。

1個(gè)參數(shù)on代表使能寫保護(hù);off代表解除寫保護(hù)。

23參數(shù)是指定Flash寫保護(hù)操作范圍,跟擦除的方式相同。

 

=> help rarpboot

rarpboot [loadAddress] [bootfilename]

 

rarboot命令可以使用TFTP協(xié)議通過網(wǎng)絡(luò)啟動(dòng)映像。也就是把指定的文件下載到指定地址,然后執(zhí)行。

1個(gè)參數(shù)是映像文件下載到的內(nèi)存地址。

2個(gè)參數(shù)是要下載執(zhí)行的映像文件。

 

=> help run

run var [...]

      - run the commands in the environment variable(s) 'var'

 

run命令可以執(zhí)行環(huán)境變量中的命令,后面參數(shù)可以跟幾個(gè)環(huán)境變量名。

 

=> help setenv

setenv name value ...

      - set environment variable 'name' to 'value ...'

setenv name

      - delete environment variable 'name'

 

setenv命令可以設(shè)置環(huán)境變量。

1個(gè)參數(shù)是環(huán)境變量的名稱。

2個(gè)參數(shù)是要設(shè)置的值,如果沒有第2個(gè)參數(shù),表示刪除這個(gè)環(huán)境變量。

 

 

=> help sleep

sleep N

      - delay execution for N seconds (N is _decimal_ !!!)

 

sleep命令可以延遲N秒鐘執(zhí)行,N為十進(jìn)制數(shù)。

 

=> help tftpboot

tftpboot [loadAddress] [bootfilename]

 

tftpboot命令可以使用TFTP協(xié)議通過網(wǎng)絡(luò)下載文件。按照二進(jìn)制文件格式下載。另外使用這個(gè)命令,必須配置好相關(guān)的環(huán)境變量。例如serveripipaddr

1個(gè)參數(shù)loadAddress是下載到的內(nèi)存地址。

2個(gè)參數(shù)是要下載的文件名稱,必須放在TFTP服務(wù)器相應(yīng)的目錄下。

這些U-Boot命令為嵌入式系統(tǒng)提供了豐富的開發(fā)和調(diào)試功能。在Linux內(nèi)核啟動(dòng)和調(diào)試過程中,都可以用到U-Boot的命令。但是一般情況下,不需要使用全部命令。比如已經(jīng)支持以太網(wǎng)接口,可以通過tftpboot命令來(lái)下載文件,那么還有必要使用串口下載的loadb嗎?反過來(lái),如果開發(fā)板需要特殊的調(diào)試功能,也可以添加新的命令。

在建立交叉開發(fā)環(huán)境和調(diào)試Linux內(nèi)核等章節(jié)時(shí),在ARM平臺(tái)上移植了U-Boot,并且提供了具體U-Boot的操作步驟。

6.4.3  U-Boot的環(huán)境變量

有點(diǎn)類似Shell,U-Boot也使用環(huán)境變量??梢酝ㄟ^printenv命令查看環(huán)境變量的設(shè)置。

 

U-Boot> printenv

bootdelay=3

baudrate=115200

netmask=255.255.0.0

ethaddr=12:34:56:78:90:ab

bootfile=uImage

bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M

bootcmd=tftp 0x30008000 zImage;go 0x30008000

serverip=192.168.1.1

ipaddr=192.168.1.100

stdin=serial

stdout=serial

stderr=serial

 

Environment size: 337/131068 bytes

U-Boot>

 

6.5是常用環(huán)境變量的含義解釋。通過printenv命令可以打印出這些變量的值。

6.5                                                  U-Boot環(huán)境變量的解釋說(shuō)明

環(huán)

說(shuō)

bootdelay

定義執(zhí)行自動(dòng)啟動(dòng)的等候秒數(shù)

baudrate

定義串口控制臺(tái)的波特率

netmask

定義以太網(wǎng)接口的掩碼

ethaddr

定義以太網(wǎng)接口的MAC地址

bootfile

定義缺省的下載文件

bootargs

定義傳遞給Linux內(nèi)核的命令行參數(shù)

bootcmd

定義自動(dòng)啟動(dòng)時(shí)執(zhí)行的幾條命令

serverip

定義tftp服務(wù)器端的IP地址

ipaddr

定義本地的IP地址

stdin

定義標(biāo)準(zhǔn)輸入設(shè)備,一般是串口

stdout

定義標(biāo)準(zhǔn)輸出設(shè)備,一般是串口

stderr

定義標(biāo)準(zhǔn)出錯(cuò)信息輸出設(shè)備,一般是串口

 

U-Boot的環(huán)境變量都可以有缺省值,也可以修改并且保存在參數(shù)區(qū)。U-Boot的參數(shù)區(qū)一般有EEPROMFlash兩種設(shè)備。

環(huán)境變量的設(shè)置命令為setenv,在6.2.2節(jié)有命令的解釋。

舉例說(shuō)明環(huán)境變量的使用。

 

=>setenv serverip  192.168.1.1

=>setenv ipaddr  192.168.1.100

=>setenv rootpath  "/usr/local/arm/3.3.2/rootfs"

=>setenv bootargs  "root=/dev/nfs rw nfsroot=\$(serverip):\$(rootpath) ip=
\$(ipaddr)
"

=>setenv kernel_addr 30000000

=>setenv nfscmd  "tftp \$(kernel_addr) uImage; bootm \$(kernel_addr) "

=>run nfscmd

 

上面定義的環(huán)境變量有serverip ipaddr rootpath bootargs kernel_addr。環(huán)境變量bootargs中還使用了環(huán)境變量,bootargs定義命令行參數(shù),通過bootm命令傳遞給內(nèi)核。環(huán)境變量nfscmd中也使用了環(huán)境變量,功能是把uImage下載到指定的地址并且引導(dǎo)起來(lái)??梢酝ㄟ^run命令執(zhí)行nfscmd腳本。