逐行分析u-boot(轉)

逐行分析u-boot(轉)

 

15.1 Bootloader簡介
[編輯] 15.1.1 Bootloader的概念
1. Bootloader的引入
從前面的硬件實驗可以知道,系統上電之後,需要一段程序來進行初始化:關閉WATCHDOG、改變系統時鐘、初始化存儲控制器、將更多的代碼複製到內存中等等。如果它能將操作系統內核(無論從本地,比如Flash;還是從遠端,比如通過網絡)複製到內存中運行,就稱這段程序爲Bootloader。

簡單地說,Bootloader就是這麼一小段程序,它在系統上電時開始執行,初始化硬件設備、準備好軟件環境,最後調用操作系統內核。

可以增強Bootloader的功能,比如增加網絡功能、從PC上通過串口或網絡下載文件、燒寫文件、將Flash上壓縮的文件解壓後再運行等── 這就是一個功能更爲強大的Bootloader,也稱爲Monitor。實際上,在最終產品中用戶並不需要這些功能,它們只是爲了方便開發。

Bootloader的實現嚴重依賴於具體硬件,在嵌入式系統中硬件配置千差萬別,即使是相同的CPU,它的外設(比如Flash)也可能不同,所以不可能有一個Bootloader支持所有的CPU、所有的電路板。即使是支持CPU架構比較多的U-Boot,也不是一拿來就可以使用的(除非裏面的配置剛好與你的板子相同),需要進行一些移植。

2. Bootloader的啓動方式
CPU上電後,會從某個地址開始執行。比如MIPS結構的CPU會從0xBFC00000取第一條指令,而ARM結構的CPU則從地址0x0000000開始。嵌入式單板中,需要把存儲器件ROM或Flash等映射到這個地址,Bootloader就存放在這個地址開始處,這樣一上電就可以執行。

在開發時,通常需要使用各種命令操作Bootloader,一般通過串口來連接PC和開發板,可以在串口上輸入各種命令、觀察運行結果等。這也只是對開發人員纔有意義,用戶使用產品時是不用接串口來控制Bootloader的。從這個觀點來看,Bootloader可以分爲兩種操作模式 (Operation Mode):

(1)啓動加載(Boot loading)模式。

上電後,Bootloader從板子上的某個固態存儲設備上將操作系統加載到RAM中運行,整個過程並沒有用戶的介入。產品發佈時,Bootloader工作在這種模式下。

(2)下載(Downloading)模式。

在這種模式下,開發人員可以使用各種命令,通過串口連接或網絡連接等通信手段從主機(Host)下載文件(比如內核映像、文件系統映像),將它們直接放在內存運行或是燒入Flash類固態存儲設備中。

板子與主機間傳輸文件時,可以使用串口的xmodem/ymodem/zmodem協議,它們使用簡單,只是速度比較慢;還可以使用網絡通過tftp、nfs協議來傳輸,這時,主機上要開啓tftp、nfs服務;還有其他方法,比如USB等。

像Blob或U-Boot等這樣功能強大的Bootloader通常同時支持這兩種工作模式,而且允許用戶在這兩種工作模式之間進行切換。比如,U -Boot在啓動時處於正常的啓動加載模式,但是它會延時若干秒(這可以設置)等待終端用戶按下任意鍵而將U-Boot切換到下載模式。如果在指定時間內沒有用戶按鍵,則U-Boot繼續啓動Linux內核。

[編輯] 15.1.2 Bootloader的結構和啓動過程
1. 概述
在移植之前先了解Bootloader的一些通用概念,對理解它的代碼會有所幫助。

在一個嵌入式Linux系統中,從軟件的角度通常可以分爲4個層次:

(1)引導加載程序,包括固化在固件(firmware)中的 boot 代碼(可選)和Bootloader兩大部分。

有些CPU在運行Bootloader之前先運行一段固化的程序(固件,firmware),比如x86結構的CPU就是先運行BIOS中的固件,然後才運行硬盤第一個分區(MBR)中的Bootloader。

在大多嵌入式系統中並沒有固件,Bootloader是上電後執行的第一個程序。

(2)Linux內核。

特定於嵌入式板子的定製內核以及內核的啓動參數。內核的啓動參數可以是內核默認的,或是由Bootloader傳遞給它的。

(3)文件系統。

包括根文件系統和建立於Flash內存設備之上的文件系統。裏面包含了Linux系統能夠運行所必需的應用程序、庫等,比如可以給用戶提供操作Linux的控制界面的shell程序,動態連接的程序運行時需要的glibc或uClibc庫,等等。

(4)用戶應用程序。

特定於用戶的應用程序,它們也存儲在文件系統中。有時在用戶應用程序和內核層之間可能還會包括一個嵌入式圖形用戶界面。常用的嵌入式 GUI 有:Qtopia 和 MiniGUI 等。

顯然,在嵌入系統的固態存儲設備上有相應的分區來存儲它們,圖15.1是一個典型的分區結構。

[[Image:]]
圖15.1 嵌入式Linux系統中的典型分區結構

“Boot parameters”分區中存放一些可設置的參數,比如IP地址、串口波特率、要傳遞給內核的命令行參數等。正常啓動過程中,Bootloader首先運行,然後它將內核複製到內存中(也有些內核可以在固態存儲設備上直接運行),並且在內存某個固定的地址設置好要傳遞給內核的參數,最後運行內核。內核啓動之後,它會掛接(mount)根文件系統(“Root filesystem”),啓動文件系統中的應用程序。

 


2. Bootloader的兩個階段
Bootloader的啓動過程啓動過程可以分爲單階段(Single Stage)、多階段(Multi-Stage)兩種。通常多階段的Bootloader能提供更爲複雜的功能,以及更好的可移植性。從固態存儲設備上啓動的Bootloader大多都是 2 階段的啓動過程。這從前面的硬件實驗可以很好地理解這點:第一階段使用匯編來實現,它完成一些依賴於 CPU 體系結構的初始化,並調用第二階段的代碼。第二階段則通常使用C語言來實現,這樣可以實現更復雜的功能,而且代碼會有更好的可讀性和可移植性。

一般而言,這兩個階段完成的功能可以如下分類,但這不是絕對的:

(1)Bootloader第一階段的功能。


硬件設備初始化。
爲加載Bootloader的第二階段代碼準備RAM空間。
拷貝Bootloader的第二階段代碼到 RAM 空間中。
設置好棧。
跳轉到第二階段代碼的C入口點。
在第一階段進行的硬件初始化一般包括:關閉WATCHDOG、關中斷、設置CPU的速度和時鐘頻率、RAM初始化等。這些並不都是必需的,比如S3C2410/S3C2440的開發板所使用的U-Boot中,就將CPU的速度和時鐘頻率的設置放在第二階段。

甚至,將第二階段的代碼複製到RAM空間中也不是必需的,對於NOR Flash等存儲設備,完全可以在上面直接執行代碼,只不過這相比在RAM中執行效率大爲降低。

(2)Bootloader第二階段的功能。


初始化本階段要使用到的硬件設備。
檢測系統內存映射(memory map)。
將內核映像和根文件系統映像從Flash上讀到RAM空間中。
爲內核設置啓動參數。
調用內核。
爲了方便開發,至少要初始化一個串口以便程序員與Bootloader進行交互。

所謂檢測內存映射,就是確定板上使用了多少內存,它們的地址空間是什麼。由於嵌入式開發中,Bootloader多是針對某類板子進行編寫,所以可以根據板子的情況直接設置,不需要考慮可以適用於各類情況的複雜算法。

Flash上的內核映像有可能是經過壓縮的,在讀到RAM之後,還需要進行解壓。當然,對於有自解壓功能的內核,不需要Bootloader來解壓。

將根文件系統映像複製到RAM中,這不是必需的。這取決於是什麼類型的根文件系統,以及內核訪問它的方法。

爲內核設置啓動參數將在下一小節介紹。

將內核存放在適當的位置後,直接跳到到它的入口點即可調用內核。調用內核之前,下列條件要滿足:

(1)CPU 寄存器的設置。


R0=0
R1=機器類型ID;對於ARM結構的CPU,其機器類型ID可以參見 linux/arch/arm/tools/mach-types。
R2=啓動參數標記列表在 RAM 中起始基地址
(2)CPU工作模式。


必須禁止中斷(IRQs和FIQs)
CPU 必須 SVC 模式
(3)Cache 和 MMU 的設置。


MMU 必須關閉
指令 Cache 可以打開也可以關閉
數據 Cache 必須關閉
如果用C語言,可以像下列示例代碼一樣來調用內核:

void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE;

……

theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);

 

3. Bootloader與內核的交互
Bootloader與內核的交互是單向的,Bootloader將各類參數傳給內核。由於它們不能同時運行,傳遞辦法只有一個:Bootloader將參數放在某個約定的地方之後,再啓動內核,內核啓動後從這個地方獲得參數。

除了約定好參數存放的地址外,還要規定參數的結構。Linux 2.4.x 以後的內核都期望以標記列表(tagged list)的形式來傳遞啓動參數。標記,就是一種數據結構;標記列表,就是挨着存放的多個標記。標記列表以標記ATAG_CORE 開始,以標記ATAG_NONE 結束。標記的數據結構爲tag,它由一個tag_header結構和一個聯合(union)組成。tag_header結構表示標記的類型及長度,比如是表示內存還是表示命令行參數等。對於不同類型的標記使用不同的聯合(union),比如表示內存時使用tag_mem32,表示命令行時使用 tag_cmdline。數據結構tag和tag_header定義在Linux內核源碼的include/asm/setup.h頭文件中:

struct tag_header {

u32 size;

u32 tag;

};

<br>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;

<br>/*

 * Acorn specific

 */

struct tag_acornacorn;

<br>/*

 * DC21285 specific

 */

struct tag_memclkmemclk;

} u;

};

下面以設置內存標記、命令行標記爲例說明參數的傳遞:

(1)設置標記 ATAG_CORE。

標記列表以標記 ATAG_CORE開始,假設Bootloader與內核約定的參數存放地址爲0x30000100,則可以以如下代碼設置標記 ATAG_CORE:

params = (struct tag *) 0x30000100;

<br>params->hdr.tag = ATAG_CORE;

params->hdr.size = tag_size (tag_core);

<br>params->u.core.flags = 0;

params->u.core.pagesize = 0;

params->u.core.rootdev = 0;

<br>params = tag_next (params); 其中,tag_next定義如下,它指向當前標記的末尾:

#define tag_next(t)((struct tag *)((u32 *)(t) + (t)->hdr.size))


(2)設置內存標記。

假設開發板使用的內存起始地址爲0x30000000,大小爲0x4000000,則內存標記可以如下設置:

params->hdr.tag = ATAG_MEM;

params->hdr.size = tag_size (tag_mem32);


params->u.mem.start = 0x30000000;

params->u.mem.size = 0x4000000;


params = tag_next (params);


(3)設置命令行標記。

命令行就是一個字符串,它被用來控制內核的一些行爲。比如"root=/dev/mtdblock2 init="/linuxrc" console="ttySAC0""表示根文件系統在MTD2分區上,系統啓動後執行的第一個程序爲/linuxrc,控制檯爲ttySAC0(即第一個串口)。

命令行可以在Bootloader中通過命令設置好,然後如下構造標記傳給內核:

char *p = "root=/dev/mtdblock2 init="/linuxrc" console="ttySAC0"";

params->hdr.tag = ATAG_CMDLINE;

params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;


strcpy (params->u.cmdline.cmdline, p);


params = tag_next (params);


(4)設置標記ATAG_NONE。

標記列表以標記ATAG_NONE結束,如下設置:

params->hdr.tag = ATAG_NONE;

params->hdr.size = 0;

 


[編輯] 15.1.3 常用Bootloader介紹
現在Bootloader種類繁多,比如x86上有LILO、GRUB等。對於ARM架構的CPU,有U-Boot、Vivi等。它們各有特點,下面列出Linux的開放源代碼的Bootloader及其支持的體系架構,如表15.1所示。

表15.1 開放源碼的Linux引導程序

 


Bootloader  Monitor  描述  X86  ARM  PowerPC 
LILO  否  Linux磁盤引導程序  是  否  否 
GRUB  否  GNU的LILO替代程序  是  否  否 
Loadlin  否  從DOS引導Linux  是  否  否 
ROLO  否  從ROM引導Linux而不需要BIOS  是  否  否 
Etherboot  否  通過以太網卡啓動Linux系統的固件  是  否  否 
LinuxBIOS  否  完全替代BUIS的Linux引導程序  是  否  否 
BLOB  是  LART等硬件平臺的引導程序  否  是  否 
U-Boot  是  通用引導程序  是  是  是 
RedBoot  是  基於eCos的引導程序  是  是  是 
Vivi  是  Mizi公司針對SAMSUNG的ARM CPU設計的引導程序  否  是  否 

對於本書使用的S3C2410/S3C2440開發板,U-Boot和Vivi是兩個好選擇。Vivi是Mizi公司針對SAMSUNG的ARM架構CPU專門設計的,基本上可以直接使用,命令簡單方便。不過其初始版本只支持串口下載,速度較慢。在網上出現了各種改進版本:支持網絡功能、USB功能、燒寫YAFFS文件系統映像等。U-Boot則支持大多CPU,可以燒寫EXT2、JFFS2文件系統映像,支持串口下載、網絡下載,並提供了大量的命令。相對於Vivi,它的使用更復雜,但是可以用來更方便地調試程序。

[編輯] 15.2 U-Boot分析與移植
[編輯] 15.2.1 U-Boot工程簡介
U-Boot,全稱爲 Universal Boot Loader,即通用Bootloader,是遵循GPL條款的開放源代碼項目。其前身是由德國DENX軟件工程中心的Wolfgang Denk基於8xxROM的源碼創建的PPCBOOT工程。後來整理代碼結構使得非常容易增加其他類型的開發板、其他架構的CPU(原來只支持 PowerPC);增加更多的功能,比如啓動Linux、下載S-Record格式的文件、通過網絡啓動、通過 PCMCIA/CompactFLash/ATA disk/SCSI等方式啓動。增加ARM架構CPU及其他更多CPU的支持後,改名爲U-Boot。

它的名字“通用”有兩層含義:可以引導多種操作系統、支持多種架構的CPU。它支持如下操作系統:Linux、NetBSD、 VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持如下架構的CPU:PowerPC、MIPS、x86、ARM、NIOS、 XScale等。

U-Boot有如下特性:

開放源碼;
支持多種嵌入式操作系統內核,如Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS;
支持多個處理器系列,如PowerPC、ARM、x86、MIPS、XScale;
較高的可靠性和穩定性;
高度靈活的功能設置,適合U-Boot調試、操作系統不同引導要求、產品發佈等;
豐富的設備驅動源碼,如串口、以太網、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、鍵盤等;
較爲豐富的開發調試文檔與強大的網絡技術支持;
支持NFS掛載、RAMDISK(壓縮或非壓縮)形式的根文件系統
支持NFS掛載、從FLASH中引導壓縮或非壓縮系統內核;
可靈活設置、傳遞多個關鍵參數給操作系統,適合系統在不同開發階段的調試要求與產品發佈,尤對Linux支持最爲強勁;
支持目標板環境變量多種存儲方式,如FLASH、NVRAM、EEPROM;
CRC32校驗,可校驗FLASH中內核、RAMDISK鏡像文件是否完好;
上電自檢功能:SDRAM、FLASH大小自動檢測;SDRAM故障檢測;CPU型號;
特殊功能:XIP內核引導;
可以從http://sourceforge.net/projects/u-boot獲得U-Boot的最新版本,如果使用過程中碰到問題或是發現Bug,可以通過郵件列表網站http://lists.sourceforge.net/lists/listinfo/u-boot-users/獲得幫助。

最新的更新代碼地址http://www.denx.de/wiki/U-Boot/WebHome

 


[編輯] 15.2.2 U-Boot源碼結構
本書在u-boot-1.1.6的基礎上進行分析和移植,從sourceforge網站下載u-boot-1.1.6.tar.bz2後解壓即得到全部源碼。U-Boot源碼目錄結構比較簡單、獨立,目錄結構也比較淺,很容易全部掌握。

u-boot-1.1.6根目錄下共有26個子目錄,可以分爲4類:

(1)平臺相關的或開發板相關的。

(2)通用的函數。

(3)通用的設備驅動程序。

(4)U-Boot工具、示例程序、文檔。

先將這26個目錄的功能與作用如表15.2所示。

表15.2 U-Boot頂層目錄說明

 


目錄  特性  解釋說明 
board  開發板相關  對應不同配置的電路板(即使CPU相同),比如smdk2410、sbc2410x 
cpu  平臺相關  對應不同的CPU,比如arm920t、arm925t、i386等;在它們的子目錄下仍可以進一步細分,比如arm920t下就有at91rm9200、s3c24x0 
lib_i386類似  某一架構下通用的文件 
include  通用的函數  頭文件和開發板配置文件,開發板的配置文件都放在include/configs目錄下,U-Boot沒有make menuconfig類似的萊單來進行可視化配置,需要手動地修改配置文件中的宏定義 
lib_generic  通用的庫函數,比如printf等 
common  通用的函數,多是對下一層驅動程序的進一步封裝 
disk  通用的設備驅動程序  硬盤接口程序 
drivers  各類具體設備的驅動程序,基本上可以通用,它們通過宏從外面引入平臺/開發板相關的函數 
dtt  數字溫度測量器或者傳感器的驅動 
fs  文件系統 
nand_spl  U-Boot一般從ROM、NOR Flash等設備啓動,現在開始支持從NAND Flash啓動,但是支持的CPU種類還不多 
net  各種網絡協議 
post  上電自檢程序 
rtc  實時時鐘的驅動 
doc  文檔  開發、使用文檔 
examples  示例程序  一些測試程序,可以使用U-Boot下載後運行 
tools  工具  製作S-Record、U-Boot格式映像的工具,比如mkimage 

U-Boot中各目錄間也是有層次結構的,雖然這種分法不是絕對的,但是在移植過程中可以提供一些指導意義,如圖15.2所示。

[[Image:]]
圖15.2 U-Boot頂層目錄的層次結構

比如common/cmd_nand.c文件提供了操作NAND Flash的各種命令,這些命令通過調用drivers/nand/nand_base.c中的擦除、讀寫函數來實現。這些函數針對NAND Flash的共性作了一些封裝,將平臺/開發板相關的代碼用宏或外部函數來代替。而這些宏與外部函數,如果與平臺相關,就要在下一層次的cpu/xxx (xxx表示某型號的CPU)中實現;如果與開發板相關,就要在下一層次的board/xxx目錄(xxx表示某款開發板)中實現。本書移植的U- Boot,就是在cpu/arm920t/s3c24x0目錄下增加了一個nand_flash.c文件來實現這些函數。

以增加燒寫yaffs文件系統映像的功能爲例──就是在common目錄下的cmd_nand.c中增加命令,比如nand write.yaffs:這個命令要調用drivers/nand/nand_util.c中的相應函數,針對yaffs文件系統的特點依次調用擦除、燒寫函數。而這些函數依賴於drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中的相關函數。

目前u-boot-1.1.6支持10種架構──根目錄下有10個類似lib_i386的目錄、31個型號(類型)的CPU──cpu目錄下有31 個子目錄,214種開發板──board目錄下有214個子目錄,很容易從中找到與自己的板子相似的配置,在上面稍作修改即可使用。

 


[編輯] 15.2.3 U-Boot的配置、編譯、連接過程
[編輯] 1. U-Boot初體驗
u-boot-1.1.6中有幾千個文件,要想了解對於某款開發板,使用哪些文件、哪個文件首先執行、可執行文件佔用內存的情況,最好的方法就是閱讀它的Makefile。

根據頂層Readme文件的說明,可以知道如果要使用開發板board/<board_name>,就先執行“make <board_name>_config”命令進行配置,然後執行“make all”,就可以生成如下3個文件:

u-boot.bin:二進制可執行文件,它就是可以直接燒入ROM、NOR Flash的文件。
u-boot:ELF格式的可執行文件
u-boot.srec:Motorola S-Record格式的可執行文件
對於S3C2410的開發板,執行“make smdk2410_config”、“make all”後生成的u-boot.bin可以燒入NOR Flash中運行。啓動後可以看到串口輸出一些信息後進入控制界面,等待用戶的輸入。

對於S3C2440的開發板,燒入上面生成的u-boot.bin,串口無輸出,需要修改代碼。

在修改代碼之前,先看看上面兩個命令“make smdk2410_config”、“make all”做了什麼事情,以瞭解程序的流程,知道要修改哪些文件。

另外,編譯U-Boot成功後,還會在它的tools子目錄下生成一些工具,比如mkimage等。將它們複製到/usr/local/bin目錄下,以後就可以直接使用它們了,比如編譯內核時,會使用mkimage來生成U-Boot格式的內核映像文件uImage。

 


[編輯] 2. U-Boot的配置過程
在頂層Makefile中可以看到如下代碼:

SRCTREE:= $(CURDIR)

……

MKCONFIG:= $(SRCTREE)/mkconfig

……

smdk2410_config:unconfig

@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

假定我們在u-boot-1.1.6的根目錄下編譯,則其中的MKCONFIG就是根目錄下的mkconfig文件。$(@:_config=)的結果就是將“smdk2410_config”中的“_config”去掉,結果爲“smdk2410”。所以“make smdk2410_config”實際上就是執行如下命令:

./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0


再來看看mkconfig的作用,在mkconfig文件開頭第6行給出了它的用法:

06 # Parameters: Target Architecture CPU Board [VENDOR] [SOC]


這裏解釋一下概念,對於S3C2410、S3C2440,它們被稱爲SoC(System on Chip),上面除CPU外,還集成了包括UART、USB控制器、NAND Flash控制器等等設備(稱爲片內外設)。S3C2410/S3C2440中的CPU爲arm920t。

以下,分步驟分析mkconfig的作用:

(1)確定開發板名稱BOARD_NAME。

11 APPEND="no#" Default: Create new config file

12 BOARD_NAME=""# Name to print in make output

13

14 while [ $# -gt 0 ] ; do

15 case "$1" in

16 --) shift ; break ;;

17 -a) shift ; APPEND="yes" ;;

18 -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;

19 *) break ;;

20 esac

21 done

22

23 [ "${BOARD_NAME}" ] || BOARD_NAME="$1"

對於“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,其中沒有“--”、“-a”、“-n”等符號,所以第14~22行沒做任何事情。第11、12行兩個變量仍維持原來的值。

執行完第23行後,BOARD_NAME的值等於第1個參數,即“smdk2410”。


(2)創建到平臺/開發板相關的頭文件的鏈接。

略過mkconfig文件中的一些沒有起作用的行:

30 #

31 # Create link to architecture specific headers

32 #

33 if [ "$SRCTREE" != "$OBJTREE" ] ; then

……

45 else

46 cd ./include

47 rm -f asm

48 ln -s asm-$2 asm

49 fi

50

第33行判斷源代碼目錄和目標文件目錄是否一樣,可以選擇在其他目錄下編譯U-Boot,這可以令源代碼目錄保持乾淨,可以同時使用不同的配置進行編譯。不過本書直接在源代碼目錄下編譯的,第33行的條件不滿足,將執行else分支的代碼。

第46~48行進入include目錄,刪除asm文件(這是上一次配置時建立的鏈接文件),然後再次建立asm文件,並令它鏈接向asm-$2目錄,即asm-arm。


繼續往下看代碼:

51 rm -f asm-$2/arch

52

53 if [ -z "$6" -o "$6" = "NULL" ] ; then

54 ln -s ${LNPREFIX}arch-$3 asm-$2/arch

55 else

56 ln -s ${LNPREFIX}arch-$6 asm-$2/arch

57 fi

58

59 if [ "$2" = "arm" ] ; then

60 rm -f asm-$2/proc

61 ln -s ${LNPREFIX}proc-armv asm-$2/proc

62 fi

63

第51行刪除asm-$2/arch目錄,即asm-arm/arch。

對於“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,$6爲“s3c24x0”,不爲空,也不是“NULL”,所以第53行的條件不滿足,將執行else分支。

第56行中,LNPREFIX爲空,所以這個命令實際上就是:ln -s arch-$6 asm-$2/arch,即:ln -s arch-s3c24x0 asm-arm/arch。

第60、61行重新建立asm-arm/proc文件,並讓它鏈接向proc-armv目錄。


(3)創建頂層Makefile包含的文件include/config.mk。

64 #

65 # Create include file for Make

66 #

67 echo "ARCH = $2" > config.mk

68 echo "CPU = $3" >> config.mk

69 echo "BOARD = $4" >> config.mk

70

71 [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk

72

73 [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk

74


對於“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,上面幾行代碼創建的config.mk文件內容如下:

ARCH = arm

CPU = arm920t

BOARD = smdk2410

SOC = s3c24x0

(4)創建開發板相關的頭文件include/config.h。

75 #

76 # Create board specific header file

77 #

78 if [ "$APPEND" = "yes" ]# Append to existing config file

79 then

80 echo >> config.h

81 else

82 > config.h# Create new config file

83 fi

84 echo "/* Automatically generated - do not edit */" >>config.h

85 echo "#include <configs/$1.h>" >>config.h

86

前面說過,APPEND維持原值“no”,所以config.h被重新建立,它的內容如下:

/* Automatically generated - do not edit */

#include <configs/smdk2410.h>"


現在總結一下,配置命令“make smdk2410_config”,實際的作用就是執行“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。假設執行“./mkconfig $1 $2 $3 $4 $5 $6”命令,則將產生如下結果:

(1)開發板名稱BOARD_NAME等於$1;

(2)創建到平臺/開發板相關的頭文件的鏈接:

ln -s asm-$2 asm

ln -s arch-$6 asm-$2/arch

ln -s proc-armv asm-$2/proc# 如果$2不是arm的話,此行沒有

(3) 創建頂層Makefile包含的文件include/config.mk。

ARCH = $2

CPU = $3

BOARD = $4

VENDOR = $5# $5爲空,或者是NULL的話,此行沒有

SOC = $6# $6爲空,或者是NULL的話,此行沒有

(4)創建開發板相關的頭文件include/config.h。

/* Automatically generated - do not edit */

#include <configs/$1.h>"


從這4個結果可以知道,如果要在board目錄下新建一個開發板<board_name>的目錄,則在 include/config目錄下也要建立一個文件<board_name>.h,裏面存放的就是開發板< board_name>的配置信息。


U-Boot還沒有類似Linux一樣的可視化配置界面(比如使用make menuconfig來配置),要手動修改配置文件include/config/<board_name>.h來裁減、設置U-Boot。

配置文件中有兩類宏:

(1)一類是選項(Options),前綴爲“CONFIG_”,它們用於選擇CPU、SOC、開發板類型,設置系統時鐘、選擇設備驅動等。比如:

#define CONFIG_ARM920T1/* This is an ARM920T Core*/

#defineCONFIG_S3C24101/* in a SAMSUNG S3C2410 SoC */

#define CONFIG_SMDK24101/* on a SAMSUNG SMDK2410 Board */

#define CONFIG_SYS_CLK_FREQ12000000/* the SMDK2410 has 12MHz input clock */

#define CONFIG_DRIVER_CS89001/* we have a CS8900 on-board */


(2)另一類是參數(Setting),前綴爲“CFG_”,它們用於設置malloc緩衝池的大小、U-Boot的提示符、U-Boot下載文件時的默認加載地址、Flash的起始地址等。比如:

#define CFG_MALLOC_LEN(CFG_ENV_SIZE + 128*1024)

#defineCFG_PROMPT"100ASK> "/* Monitor Command Prompt*/

#defineCFG_LOAD_ADDR0x33000000/* default load address*/

#define PHYS_FLASH_10x00000000 /* Flash Bank #1 */

從下面的編譯、連接過程可知,U-Boot中幾乎每個文件都被編譯和連接,但是這些文件是否包含有效的代碼,則由宏開關來設置。比如對於網卡驅動drivers/cs8900.c,它的格式爲:

#include <common.h>/* 將包含配置文件include/config/<board_name>.h */

……

#ifdef CONFIG_DRIVER_CS8900

/* 實際的代碼 */

……

#endif/* CONFIG_DRIVER_CS8900 */

如果定義了宏CONFIG_DRIVER_CS8900,則文件中包含有效的代碼;否則,文件被註釋爲空。

可以這樣粗糙地認爲,“CONFIG_”除了設置一些參數外,主要用來設置U-Boot的功能、選擇使用文件中的哪一部分;而“CFG_”用來設置更細節的參數。

 


[編輯] 3. U-Boot的編譯、連接過程
配置完後,執行“make all”即可編譯,從Makefile中可以瞭解U-Boot使用了哪些文件、哪個文件首先執行、可執行文件佔用內存的情況。

先確定用到哪些文件,下面只摘取Makefile中與arm相關的部分:

117 include $(OBJTREE)/include/config.mk

118 exportARCH CPU BOARD VENDOR SOC

119

……

127 ifeq ($(ARCH),arm)

128 CROSS_COMPILE = arm-linux-

129 endif

……

163 # load other configuration

164 include $(TOPDIR)/config.mk

165

第117、164行用於包含其他的config.mk文件,第117行所要包含文件的就是在上面的配置過程中製作出來的 include/config.mk文件,其中定義了ARCH、CPU、BOARD、SOC等4個變量的值爲arm、arm920t、smdk2410、 s3c24x0。

第164行包含頂層目錄的config.mk文件,它根據上面4個變量的值確定了編譯器、編譯選項等。其中對我們理解編譯過程有幫助的是BOARDDIR、LDFLAGS的值,config.mk中:

88 BOARDDIR = $(BOARD)

……

91 sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk# include board specific rules

……

143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds

……

189 LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)

在board/smdk2410/config.mk中,定義了“TEXT_BASE = 0x33F80000”。所以,最終結果如下:BOARDDIR爲smdk2410;LDFLAGS中有“-T board/smdk2410/u-boot.lds -Ttext 0x33F80000”字樣。


繼續往下看Makefile:

166 #########################################################################

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

168

169 OBJS = cpu/$(CPU)/start.o

……

193 LIBS = lib_generic/libgeneric.a

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

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

……

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

200 LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a /

201 fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a

202 LIBS += net/libnet.a

……

212 LIBS += $(BOARDLIBS)

213

……

從第169行得知,OBJS的第一個值爲“cpu/$(CPU)/start.o”,即“cpu/arm920t/start.o”。

第193~213行指定了LIBS變量就是平臺/開發板相關的各個目錄、通用目錄下相應的庫,比如: lib_generic/libgeneric.a、board/smdk2410/libsmdk2410.a、 cpu/arm920t/libarm920t.a、lib_arm/libarm.a、fs/cramfs/libcramfs.a fs/fat/libfat.a等。

OBJS、LIBS所代表的.o、.a文件就是U-Boot的構成,它們通過如下命令由相應的源文件(或相應子目錄下的文件)編譯得到。

268 $(OBJS):

269 $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))

270

271 $(LIBS):

272 $(MAKE) -C $(dir $(subst $(obj),,$@))

273

274 $(SUBDIRS):

275 $(MAKE) -C $@ all

276

第268、269兩行的規則表示,對於OBJS中的每個成員,都將進入cpu/$(CPU)目錄(即cpu/arm920t)編譯它們。現在OBJS爲cpu/arm920t/start.o,它將由cpu/arm920t/start.S編譯得到。

第271、272兩行的規則表示,對於LIBS中的每個成員,都將進入相應的子目錄執行“make”命令。這些子目錄中的Makefile,結構相似,它們將Makefle中指定的文件編譯、連接成一個庫文件。

當所有的OBJS、LIBS所表示的.o和.a文件都生成後,就剩最後的連接了,這對應Makefile中如下幾行:

246 $(obj)u-boot.srec:$(obj)u-boot

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

248

249 $(obj)u-boot.bin:$(obj)u-boot

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

251

……

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

263 UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*/(__u_boot_cmd_.*/)/-u/1/p'|sort|uniq`;/

264 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) /

265 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) /

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

267

先使用第262~266的規則連接得到ELF格式的u-boot,最後轉換爲二進制格式u-boot.bin、S-Record格式u- boot.srec。LDFLAGS確定了連接方式,其中的“-T board/smdk2410/u-boot.lds -Ttext 0x33F80000”字樣指定了程序的佈局、地址。board/smdk2410/u-boot.lds文件如下:

28 SECTIONS

29 {

30 . = 0x00000000;

31

32 . = ALIGN(4);

33 .text :

34 {

35 cpu/arm920t/start.o(.text)

36 *(.text)

37 }

38

39 . = ALIGN(4);

40 .rodata : { *(.rodata) }

41

42 . = ALIGN(4);

43 .data : { *(.data) }

44

45 . = ALIGN(4);

46 .got : { *(.got) }

47

48 . = .;

49 __u_boot_cmd_start = .;

50 .u_boot_cmd : { *(.u_boot_cmd) }

51 __u_boot_cmd_end = .;

52

53 . = ALIGN(4);

54 __bss_start = .;

55 .bss : { *(.bss) }

56 _end = .;

57 }

從第35行可知,cpu/arm920t/start.o被放在程序的最前面,所以U-Boot的入口點在cpu/arm920t/start.S中。


現在來總結一下U-Boot的編譯流程:

(1)首先編譯cpu/$(CPU)/start.S,對於不同的CPU,還可能編譯cpu/$(CPU)下的其他文件。

(2)然後,對於平臺/開發板相關的每個目錄、每個通用目錄,都使用它們各自的Makefile生成相應的庫。

(3)將1、2步驟生成的.o、.a文件按照board/$(BOARDDIR)/config.mk文件中指定的代碼段起始地址、board/$(BOARDDIR)/u-boot.lds連接腳本進行連接。

(4)第3步得到的是ELF格式的U-Boot,後面Makefile還會將它轉換爲二進制格式、S-Record格式。

 


[編輯] 15.2.4 U-Boot的啓動過程源碼分析
首先強調,本書使用的U-Boot從NOR Flash啓動,下面以開發板smdk2410的U-Boot爲例。

U-Boot屬於兩階段的Bootloader,第一階段的文件爲cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平臺相關,後者是開發板相關。

[編輯] U-Boot第一階段代碼分析
它與15.1.2節中描述的Bootloader第一階段所完成的功能可以一一對應:

(1)硬件設備初始化。

依次完成如下設置:將CPU的工作模式設爲管理模式(svc),關閉WATCHDOG,設置FCLK、HCLK、PCLK的比例(即設置CLKDIVN寄存器),關閉MMU、CACHE。

代碼都在cpu/arm920t/start.S中,註釋也比較完善,讀者有不明白的地方可以參考前面硬件實驗的相關章節。

(2)爲加載Bootloader的第二階段代碼準備RAM空間。

所謂準備RAM空間,就是初始化內存芯片,使它可用。對於S3C2410/S3C2440,通過在start.S中調用lowlevel_init 函數來設置存儲控制器,使得外接的SDRAM可用。代碼在board/smdk2410/lowlevel_init.S中。

注意:lowlevel_init.S文件是開發板相關的,這表示如果外接的設備不一樣,可以修改lowlevel_init.S文件中的相關宏。

lowlevel_init函數並不複雜,只是要注意這時的代碼、數據都只保存在NOR Flash上,內存中還沒有,所以讀取數據時要變換地址。代碼如下:

129 _TEXT_BASE:

130 .wordTEXT_BASE

131

132 .globl lowlevel_init

133 lowlevel_init:

134 /* memory control configuration */

135 /* make r0 relative the current location so that it */

136 /* reads SMRDATA out of FLASH rather than memory ! */

137 ldr r0, =SMRDATA

138 ldrr1, _TEXT_BASE

139 subr0, r0, r1

140 ldrr1, =BWSCON/* Bus Width Status Controller */

141 add r2, r0, #13*4

142 0:

143 ldr r3, [r0], #4

144 str r3, [r1], #4

145 cmp r2, r0

146 bne 0b

147

148 /* everything is fine now */

149 movpc, lr

150

151 .ltorg

152 /* the literal pools origin */

153

154 SMRDATA:/* 13個寄存器的值 */

155 .word ……

156 .word ……


第137~139行進行地址變換,因爲這時候內存中還沒有數據,不能使用連接程序時確定的地址來讀取數據:

第137行中SMRDATA 表示這13個寄存器的值存放的開始地址(連接地址),值爲0x33F8xxxx,處於內存中。

第138行獲得代碼段的起始地址,它就是第130行中的“TEXT_BASE”,其值在board/smdk2410/config.mk中定義:“TEXT_BASE = 0x33F80000”。

第139行將0x33F8xxxx與0x33F80000相減,這就是13個寄存器值在NOR Flash上存放的開始地址。

(3)拷貝Bootloader的第二階段代碼到 RAM 空間中。

這裏將整個U-Boot的代碼(包括第一、第二階段)都複製到SDRAM中,這在cpu/arm920t/start.S中實現:

164 relocate:/* 將U-Boot複製到RAM中 */

165 adrr0, _start/* r0 = 當前代碼的開始地址 */

166 ldrr1, _TEXT_BASE/* r1 = 代碼段的連接地址 */

167 cmp r0, r1 /* 測試現在是在Flash中還是在RAM中 */

168 beq stack_setup/* 如果已經在RAM中(這通常是調試時,直接下載到RAM中),

* 則不需要複製

*/

169

170 ldrr2, _armboot_start/* _armboot_start在前面定義,是第一條指令的運行地址 */

171 ldrr3, _bss_start/* 在連接腳本u-boot.lds中定義,是代碼段的結束地址 */

172 subr2, r3, r2/* r2 = 代碼段長度 */

173 addr2, r0, r2/* r2 = NOR Flash上代碼段的結束地址 */

174

175 copy_loop:

176 ldmiar0!, {r3-r10}/* 從地址[r0]處獲得數據 */

177 stmiar1!, {r3-r10}/* 複製到地址[r1]處 */

178 cmpr0, r2/* 判斷是否複製完畢 */

179 blecopy_loop/* 沒複製完,則繼續 */

(4)設置好棧。

棧的設置靈活性很大,只要讓sp寄存器指向一段沒有使用的內存即可。

182 /* Set up the stack */

183 stack_setup:

184 ldr r0, _TEXT_BASE /* _TEXT_BASE爲代碼段的開始地址,值爲0x33F80000 */

185 sub r0, r0, #CFG_MALLOC_LEN /* 代碼段下面,留出一段內存以實現malloc */

186 sub r0, r0, #CFG_GBL_DATA_SIZE /* 再留出一段內存,存一些全局參數 */

187 #ifdef CONFIG_USE_IRQ

188 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) /* IRQ、FIQ模式的棧 */

189 #endif

190 sub sp, r0, #12 /* 最後,留出12字節的內存給abort異常,

* 往下的內存就都是棧了

*/

191

到了這一步,讀者可以知道內存的使用情況了,如下圖所示(圖中與上面的劃分稍有不同,這是因爲在cpu/arm920t/cpu.c中的cpu_init函數中才真正爲IRQ、FIQ模式劃分了棧):

[[Image:]]
圖15.3 U-Boot內存使用情況

(5)跳轉到第二階段代碼的C入口點。

在跳轉之前,還要清除BSS段(初始值爲0、無初始值的全局變量、靜態變量放在BSS段),代碼如下:

192 clear_bss:

193 ldrr0, _bss_start/* BSS段的開始地址,它的值在連接腳本u-boot.lds中確定 */

194 ldrr1, _bss_end/* BSS段的結束地址,它的值在連接腳本u-boot.lds中確定 */

195 mov r2, #0x00000000

196

197 clbss_l:strr2, [r0]/* 往BSS段中寫入0值 */

198 addr0, r0, #4

199 cmpr0, r1

200 bleclbss_l

201

現在,C函數的運行環境已經完全準備好,通過如下命令直接跳轉(這之後,程序纔在內存中執行),它將調用lib_arm/board.c中的start_armboot函數,這是第二階段的入口點:

223 ldrpc, _start_armboot

224

225 _start_armboot:.word start_armboot

226

 


[編輯] U-Boot第二階段代碼分析
它與15.1.2節中描述的Bootloader第二階段所完成的功能基本上一致,不過順序有點小差別。另外,U-Boot在啓動內核之前可以讓用戶決定是否進入下載模式,即進入U-Boot的控制界面。

第二階段從lib_arm/board.c中的start_armboot函數開始,先看從這個函數開始的程序流程圖,圖15.3所示。

[[Image:]]
圖15.3 U-Boot第二階段流程圖

移植U-Boot的主要工作在於對硬件的初始化、驅動,所以下面講解時將重點放在硬件的操作上。

(1)初始化本階段要使用到的硬件設備。:最主要的是設置系統時鐘、初始化串口,只要這兩個設置好了,就可以從串口看到打印信息。

board_init函數設置MPLL、改變系統時鐘,它是開發板相關的函數,在board/smdk2410/smdk2410.c中實現。值得注意的是,board_init函數中還保存了機器類型ID,這將在調用內核時傳給內核,代碼如下:

/* arch number of SMDK2410-Board */

gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; /* 值爲193 */

串口的初始化函數主要是serial_init,它設置UART控制器,是CPU相關的函數,在cpu/arm920t/s3c24x0/serial.c中實現。

(2)檢測系統內存映射(memory map)。對於特定的開發板,其內存的分佈是明確的,所以可以直接設置。board/smdk2410/smdk2410.c中的dram_init函數指定了本開發板的內存起始地址爲0x30000000,大小爲0x4000000。代碼如下:

int dram_init (void)

{

gd->bd->bi_dram[0].start = PHYS_SDRAM_1;/* 即0x300000000 */

gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;/* 即0x4000000 */


return 0;

}

這些設置的參數,將在後面向內核傳遞參數時用到。

(3)U-Boot命令的格式。

從圖15.3可以知道,即使是內核的啓動,也是通過U-Boot命令來實現的。U-Boot中每個命令都通過U_BOOT_CMD宏來定義,格式如下:

U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")

各項參數的意義爲:

① name:命令的名字,注意,它不是一個字符串(不要用雙引號括起來)。

② maxargs:最大的參數個數

③ repeatable:命令是否可重複,可重複是指運行一個命令後,下次敲回車即可再次運行。

④ command:對應的函數指針,類型爲(*cmd)(struct cmd_tbl_s *, int, int, char *[])。

⑤ usage:簡短的使用說明,這是個字符串。

⑥ help:較詳細的使用說明,這是個字符串。

宏U_BOOT_CMD在include/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}

Struct_Section也是在include/command.h中定義:

#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))

比如對於bootm命令,它如此定義:

U_BOOT_CMD(

bootm,CFG_MAXARGS,1,do_bootm,

“string1”,

“string2”

);


宏U_BOOT_CMD擴展開後就是:

cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {“bootm”, CFG_MAXARGS, 1, do_bootm, “string1”, “string2”};


對於每個使用U_BOOT_CMD宏來定義的命令,其實都是在".u_boot_cmd"段中定義一個cmd_tbl_t結構。連接腳本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/command.c中的find_cmd函數)。


內核的複製和啓動,可以通過如下命令來完成:bootm從內存、ROM、NOR Flash中啓動內核,bootp則通過網絡來啓動,而nboot從NAND Flash啓動內核。它們都是先將內核映像從各種媒介中讀出,存放在指定的位置;然後設置標記列表以給內核傳遞參數;最後跳到內核的入口點去執行。具體實現的細節不再描述,有興趣的讀者可以閱讀common/cmd_boot.c、common/cmd_net.c、common/cmd_nand.c來了解它們的實現。


(4)爲內核設置啓動參數。

與15.1.2小節中《Bootloader與內核的交互》所描述的一樣,U-Boot也是通過標記列表向內核傳遞參數。並且,15.1.2小節中內存標記、命令行標記的示例代碼就是取自U-Boot中的setup_memory_tags、setup_commandline_tag函數,它們都是在lib_arm/armlinux.c中定義。一般而言,設置這兩個標記就可以了,在配置文件include/configs/smdk2410.h 中增加如下兩個配置項即可:

#define CONFIG_SETUP_MEMORY_TAGS 1

#define CONFIG_CMDLINE_TAG 1

對於ARM架構的CPU,都是通過lib_arm/armlinux.c中的do_bootm_linux函數來啓動內核。這個函數中,設置標記列表,最後通過“theKernel (0, bd->bi_arch_number, bd->bi_boot_params)”調用內核。其中,theKernel指向內核存放的地址(對於ARM架構的CPU,通常是 0x30008000),bd->bi_arch_number就是前面board_init函數設置的機器類型ID,而bd-> bi_boot_params就是標記列表的開始地址。

[編輯] 15.2.5 U-Boot的移植
開發板smdk2410的配置適用於大多數S3C2410單板,或是隻需要極少的修改即可使用。但是目前U-Boot中沒有對S3C2440的支持,需要我們自己移植。

本書基於的S3C2410、S3C2440兩款開發板,它們的外接硬件相同:

BANK0外接容量爲1MB,位寬爲8的NOR Flash芯片AM29LV800
BANK3外接10M網卡芯片CS8900,位寬爲16
BANK6外接兩片容量爲32MB、位寬爲16的SDRAM芯片K4S561632,組成容量爲64MB、位寬爲32的內存
通過NAND Flash控制器外接容量爲64MB,位寬爲8的NAND Flash芯片K9S1208
對於NOR Flash和NAND Flash,如圖15.4所示劃分它們的使用區域。由於NAND Flash的“位反轉”現象比較常見,爲保證數據的正確,在讀寫數據時需要使用ECC較驗。另外,NAND Flash在使用過程中、運輸過程中還有可能出現壞塊。所以本書選擇在NOR Flash中保存U-Boot,在NAND Flash中保存內核和文件系統,並在使用U-Boot燒寫內核、文件系統時,進行壞塊檢查、ECC較驗。這樣,即使NAND Flash出現壞塊導致內核或文件系統不能使用,也可以通過NOR Flash中的U-Boot來重新燒寫。

[[Image:]]
圖15.4 開發板固態存儲器分區劃分


smdk2410開發板已經支持NOR Flash芯片AM29LV800,U-Boot本身也已經支持jffs2文件系統映像的燒寫。下面一步一步移植U-Boot(所有的修改都在補丁文件u -boot-1.1.6_100ask24x0.patch裏,讀者可以直接打補丁),增加如下新功能:

同時支持本書使用的S3C2410和S3C2440開發板
支持串口xmodem協議
支持網卡芯片CS8900
支持NAND Flash讀寫
支持燒寫yaffs文件系統映像
[編輯] 1. 同時支持S3C2410和S3C2440
我們將在開發板smdk2410的基礎上進行移植。

(1)新建一個開發板的相應目錄和文件。

爲了不破壞原來的代碼,在board目錄下將smdk2410複製爲100ask24x0目錄,並將board/100ask24x0/smdk2410.c改名爲100ask24x0.c。

根據前面描述的配置過程可知,還要在include/configs目錄下建立一個配置文件100ask24x0.h,可以將include/configs/smdk2410.h直接複製爲100ask24x0.h。

還要修改兩個Makefile,首先在頂層Makefile中增加如下兩行:

100ask24x0_config:unconfig

@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0

然後在board/100ask24x0/Makefile中,如下修改(因爲前面將smdk2410.c文件改名爲100ask24x0.c了):

COBJS:= smdk2410.o flash.o

改爲:

COBJS:= 100ask24x0.o flash.o


(2)修改SDRAM的配置。

SDRAM的初始化在U-Boot的第一階段完成,就是在board/100ask24x0/lowlevel_init.S文件中設置存儲控制器。

檢查一下BANK6的設置:位寬爲32──宏B6_BWSCON剛好爲DW32(表示32位),無需改變;另外還要根據HCLK設置SDRAM的刷新參數,主要是REFCNT寄存器。

本書所用開發板的HCLK都設爲100MHz,需要根據SDRAM芯片的具體參數重新計算REFCNT寄存器的值(請參考第6章)。代碼修改如下:

126 #define REFCNT 1113/* period="15".6us, HCLK="60Mhz", (2048+1-15.6*60) */

改爲

126 #define REFCNT 0x4f4/* period="7".8125us, HCLK="100Mhz", (2048+1-7.8125*100) */

對於其他BANK,比如網卡芯片CS8900所在的BANK2,原來的設置剛好匹配,無需更改;而對於BANK1、2、4、5、7,在U-Boot中並沒有使用到它們外接的設備,也不需要理會。


(3)增加對S3C2440的支持。

S3C2440是S3C2410的改進版,它們的操作基本相似。不過在系統時鐘的設置、NAND Flash控制器的操作等方面,有一些小差別。它們的MPLL、UPLL計算公式不一樣,FCLK、HCLK和PCLK的分頻化設置也不一樣,這在下面的代碼中可以看到。NAND Flash控制器的差別在增加對NAND Flash的支持時講述。

本章的目標是令同一個U-Boot二進制代碼既可以在S3C2410上運行,也可以在S3C2440上運行。首先需要在代碼中自動識別是 S3C2410還是S3C2440,這可以通過讀取GSTATUS1寄存器的值來分辨:0x32410000表示S3C2410,0x32410002表示S3C2410A,0x32440000表示S3C2440,0x32440001表示S3C2440A。S3C2410和S3C2410A、 S3C2440和S3C2440A,對本書來說沒有區別。

對於S3C2410開發板,將FCLK設爲200MHz,分頻比爲FCLK:HCLK:PCLK=1:2:4;對於S3C2440開發板,將 FCLK設爲400MHz,分頻比爲FCLK:HCLK:PCLK=1:4:8。還將UPLL設爲48MHz,即UCLK爲48MHz,以在內核中支持 USB控制器。

首先修改board/100ask24x0/100ask24x0.c中的board_init函數,下面是修改後的代碼:

33 /* S3C2440: MPLL = (2*m * Fin) / (p * 2^s), UPLL = (m * Fin) / (p * 2^s)

34 * m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2

35 */

36 #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))

37 #define S3C2440_UPLL_48MHZ ((0x38<<12)|(0x02<<4)|(0x02))

38 #define S3C2440_CLKDIV 0x05 /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL */

39

40 /* S3C2410: Mpll,Upll = (m * Fin) / (p * 2^s)

41 * m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2

42 */

43 #define S3C2410_MPLL_200MHZ ((0x5c<<12)|(0x04<<4)|(0x00))

44 #define S3C2410_UPLL_48MHZ ((0x28<<12)|(0x01<<4)|(0x02))

45 #define S3C2410_CLKDIV 0x03 /* FCLK:HCLK:PCLK = 1:2:4 */

46

上面幾行鍼對S3C2410、S3C2440分別定義了MPLL、UPLL寄存器的值。開發板輸入時鐘爲12MHz(這在 include/configs/100ask24x0.h中的宏CONFIG_SYS_CLK_FREQ中定義),讀者可以根據代碼中的計算公式針對自己的開發板修改系統時鐘。


下面是針對S3C2410、S3C2440,分別使用不同的宏設置系統時鐘:

58 int board_init (void)

59 {

60 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

61 S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

62

63 /* 設置GPIO */

64 gpio->GPACON = 0x007FFFFF;

65 gpio->GPBCON = 0x00044555;

66 gpio->GPBUP = 0x000007FF;

67 gpio->GPCCON = 0xAAAAAAAA;

68 gpio->GPCUP = 0x0000FFFF;

69 gpio->GPDCON = 0xAAAAAAAA;

70 gpio->GPDUP = 0x0000FFFF;

71 gpio->GPECON = 0xAAAAAAAA;

72 gpio->GPEUP = 0x0000FFFF;

73 gpio->GPFCON = 0x000055AA;

74 gpio->GPFUP = 0x000000FF;

75 gpio->GPGCON = 0xFF95FFBA;

76 gpio->GPGUP = 0x0000FFFF;

77 gpio->GPHCON = 0x002AFAAA;

78 gpio->GPHUP = 0x000007FF;

79

80 /* 同時支持S3C2410和S3C2440, www.100ask.net */

81 if ((gpio->GSTATUS1 == 0x32410000) || (gpio->GSTATUS1 == 0x32410002))

82 {

83 /* FCLK:HCLK:PCLK = 1:2:4 */

84 clk_power->CLKDIVN = S3C2410_CLKDIV;

85

86 /* 修改爲異步總線模式 */

87 __asm__( "mrc p15, 0, r1, c1, c0, 0/n" /* read ctrl register */

88 "orr r1, r1, #0xc0000000/n" /* Asynchronous */

89 "mcr p15, 0, r1, c1, c0, 0/n" /* write ctrl register */

90 :::"r1"

91 );

92

93 /* 設置PLL鎖定時間 */

94 clk_power->LOCKTIME = 0xFFFFFF;

95

96 /* 配置MPLL */

97 clk_power->MPLLCON = S3C2410_MPLL_200MHZ;

98

99 /* 配置MPLL後,要延時一段時間再配置UPLL */

100 delay (4000);

101

102 /* 配置UPLL */

103 clk_power->UPLLCON = S3C2410_UPLL_48MHZ;

104

105 /* 再延時一會 */

106 delay (8000);

107

108 /* 機器類型ID,這在調用Linux內核時用到 */

109 gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;

110 }

111 else

112 {

113 /* FCLK:HCLK:PCLK = 1:4:8 */

114 clk_power->CLKDIVN = S3C2440_CLKDIV;

115

116 /* 修改爲異步總線模式 */

117 __asm__( "mrc p15, 0, r1, c1, c0, 0/n" /* read ctrl register */

118 "orr r1, r1, #0xc0000000/n" /* Asynchronous */

119 "mcr p15, 0, r1, c1, c0, 0/n" /* write ctrl register */

120 :::"r1"

121 );

122

123 /* 設置PLL鎖定時間 */

124 clk_power->LOCKTIME = 0xFFFFFF;

125

126 /* 配置MPLL */

127 clk_power->MPLLCON = S3C2440_MPLL_400MHZ;

128

129 /* 配置MPLL後,要延時一段時間再配置UPLL */

130 delay (4000);

131

132 /* 配置UPLL */

133 clk_power->UPLLCON = S3C2440_UPLL_48MHZ;

134

135 /* 再延時一會 */

136 delay (8000);

137

138 /* 機器類型ID,這在調用Linux內核時用到,這個值要與內核相對應 */

139 gd->bd->bi_arch_number = MACH_TYPE_S3C2440;

140 }

141

142 /* 啓動內核時,參數存放位置。這個值在構造標記列表時用到 */

143 gd->bd->bi_boot_params = 0x30000100;

144

145 icache_enable();

146 dcache_enable();

147

148 return 0;

149 }

150


最後一步:獲取系統時鐘的函數需要針對S3C2410、S3C2440的不同進行修改。

在後面設置串口波特率時需要獲得系統時鐘,就是在U-Boot的第二階段,lib_arm/board.c中start_armboot函數調用 serial_init函數初始化串口時,會調用get_PCLK函數。它在cpu/arm920t/s3c24x0/speed.c中定義,與它相關的還有get_HCLK、get_PLLCLK等函數。

前面的board_init函數在識別出S3C2410或S3C2440後,設置了機器類型ID:gd->bd-> bi_arch_number,後面的函數可以通過它來分辨是S3C2410還是S3C2440。首先要在程序的開頭增加如下一行,這樣纔可以使用gd變量:

DECLARE_GLOBAL_DATA_PTR;


S3C2410和S3C2440的MPLL、UPLL計算公式不一樣,所以get_PLLCLK函數也需要修改:

56 static ulong get_PLLCLK(int pllreg)

57 {

58 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

59 ulong r, m, p, s;

60

61 if (pllreg == MPLL)

62 r = clk_power->MPLLCON;

63 else if (pllreg == UPLL)

64 r = clk_power->UPLLCON;

65 else

66 hang();

67

68 m = ((r & 0xFF000) >> 12) + 8;

69 p = ((r & 0x003F0) >> 4) + 2;

70 s = r & 0x3;

71

72 /* 同時支持S3C2410和S3C2440, by www.100ask.net */

73 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)

74 return((CONFIG_SYS_CLK_FREQ * m) / (p << s));

75 else

76 return((CONFIG_SYS_CLK_FREQ * m * 2) / (p << s)); /* S3C2440 */

77 }

78


由於分頻係數的設置方法也不一樣,get_HCLK、get_PCLK也需要修改。對於S3C2410,沿用原來的計算方法,else分支中是S3C2440的代碼:

85 /* for s3c2440 */

86 #define S3C2440_CLKDIVN_PDIVN (1<<0)

87 #define S3C2440_CLKDIVN_HDIVN_MASK (3<<1)

88 #define S3C2440_CLKDIVN_HDIVN_1 (0<<1)

89 #define S3C2440_CLKDIVN_HDIVN_2 (1<<1)

90 #define S3C2440_CLKDIVN_HDIVN_4_8 (2<<1)

91 #define S3C2440_CLKDIVN_HDIVN_3_6 (3<<1)

92 #define S3C2440_CLKDIVN_UCLK (1<<3)

93

94 #define S3C2440_CAMDIVN_CAMCLK_MASK (0xf<<0)

95 #define S3C2440_CAMDIVN_CAMCLK_SEL (1<<4)

96 #define S3C2440_CAMDIVN_HCLK3_HALF (1<<8)

97 #define S3C2440_CAMDIVN_HCLK4_HALF (1<<9)

98 #define S3C2440_CAMDIVN_DVSEN (1<<12)

99

100 /* return HCLK frequency */

101 ulong get_HCLK(void)

102 {

103 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

104 unsigned long clkdiv;

105 unsigned long camdiv;

106 int hdiv = 1;

107

108 /* 同時支持S3C2410和S3C2440, by www.100ask.net */

109 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)

110 return((clk_power->CLKDIVN & 0x2) ? get_FCLK()/2 : get_FCLK());

111 else

112 {

113 clkdiv = clk_power->CLKDIVN;

114 camdiv = clk_power->CAMDIVN;

115

116 /* 計算分頻比 */

117

118 switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {

119 case S3C2440_CLKDIVN_HDIVN_1:

120 hdiv = 1;

121 break;

122

123 case S3C2440_CLKDIVN_HDIVN_2:

124 hdiv = 2;

125 break;

126

127 case S3C2440_CLKDIVN_HDIVN_4_8:

128 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;

129 break;

130

131 case S3C2440_CLKDIVN_HDIVN_3_6:

132 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;

133 break;

134 }

135

136 return get_FCLK() / hdiv;

137 }

138 }

139

140 /* return PCLK frequency */

141 ulong get_PCLK(void)

142 {

143 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

144 unsigned long clkdiv;

145 unsigned long camdiv;

146 int hdiv = 1;

147

148 /* 同時支持S3C2410和S3C2440, by www.100ask.net */

149 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)

150 return((clk_power->CLKDIVN & 0x1) ? get_HCLK()/2 : get_HCLK());

151 else

152 {

153 clkdiv = clk_power->CLKDIVN;

154 camdiv = clk_power->CAMDIVN;

155

156 /* 計算分頻比 */

157

158 switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {

159 case S3C2440_CLKDIVN_HDIVN_1:

160 hdiv = 1;

161 break;

162

163 case S3C2440_CLKDIVN_HDIVN_2:

164 hdiv = 2;

165 break;

166

167 case S3C2440_CLKDIVN_HDIVN_4_8:

168 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;

169 break;

170

171 case S3C2440_CLKDIVN_HDIVN_3_6:

172 hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;

173 break;

174 }

175

176 return get_FCLK() / hdiv / ((clkdiv & S3C2440_CLKDIVN_PDIVN)? 2:1);

177 }

178 }

179


現在重新執行“make 100ask24x0_config”和“make all”生成的u-boot.bin文件既可以運行於S3C2410開發板,也可以運行於S3C2440開發板。將它燒入NOR Flash後啓動,就可以在串口工具(設置爲115200,8N1)中看到提示信息,可以輸入各種命令操作U-Boot了。


(4)選擇NOR Flash的型號。

但是,現在還無法通過U-Boot命令燒寫NOR Flash。本書所用開發板中的NOR Flash型號爲AM29LV800,而配置文件include/configs/100ask24x0.h中的默認型號爲AM29LV400。修改如下:

#define CONFIG_AMD_LV4001/* uncomment this if you have a LV400 flash */

#if 0

#define CONFIG_AMD_LV8001/* uncomment this if you have a LV800 flash */

#endif

改爲:

#if 0

#define CONFIG_AMD_LV4001/* uncomment this if you have a LV400 flash */

#endif

#define CONFIG_AMD_LV8001/* uncomment this if you have a LV800 flash */

本例中NOR Flash的操作函數在board/100ask24x0/flash.c中實現,它支持AM29LV400y和AM29LV800。對於其他型號的 NOR Flash,如果符合CFI接口標準,則可以在使用drivers/cfi_flash.c中的接口函數;否則,只好自己編寫了。如果要使用 cfi_flash.c,如下修改兩個文件:

在include/configs/100ask24x0.h中增加以下一行:

#define CFG_FLASH_CFI_DRIVER 1

在board/100ask24x0/Makefile中去掉flash.o:

COBJS:= 100ask24x0.o flash.o

改爲:

COBJS:= 100ask24x0.o


修改好對NOR Flash的支持後,重新編譯U-Boot:make clean、make all。運行後可以在串口中看到如下字樣:

Flash: 1 MB

現在可以使用loadb、loady等命令通過串口下載文件,然後使用erase、cp命令分別擦除、燒寫NOR Flash了,它們的效率比JTAG快上好幾倍。

 


[編輯] 2. 支持串口xmodem協議
上面的loadb命令需要配合Linux下的kermit工具來使用,loady命令通過串口ymodem協議來傳輸文件。Windows下的超級終端雖然支持ymodem,但是它的使用界面實在不友好。而本書推薦使用的Windows工具SecureCRT只支持xmodem和zmodem。爲了方便在Windows下開發,現在修改代碼增加對xmodem的支持,即增加一個命令 loadx。

依照loady的實現來編寫代碼,首先使用U_BOOT_CMD宏來增加loadx命令:

/* 支持xmodem, www.100ask.net */

U_BOOT_CMD(

loadx, 3, 0,do_load_serial_bin,

"loadx - load binary file over serial line (xmodem mode)/n",

"[ off ] [ baud ]/n"

" - load binary file over serial line"

" with offset 'off' and baudrate 'baud'/n"

);


其次,在do_load_serial_bin函數中增加對loadx命令的處理分支。也是依照loady來實現:

481 /* 支持xmodem, www.100ask.net */

482 if (strcmp(argv[0],"loadx")==0) {

483 printf ("## Ready for binary (xmodem) download "

484 "to 0x%08lX at %d bps.../n",

485 offset,

486 load_baudrate);

487

488 addr = load_serial_xmodem (offset);

489

490 } else if (strcmp(argv[0],"loady")==0) {

491 printf ("## Ready for binary (ymodem) download "

492 "to 0x%08lX at %d bps.../n",

……

第481~490行就是爲loadx命令增加的代碼。

在第288行調用load_serial_xmodem函數,它是依照load_serial_ymodem實現的一個新函數:

36 #if (CONFIG_COMMANDS & CFG_CMD_LOADB)

37 /* 支持xmodem, www.100ask.net */

38 static ulong load_serial_xmodem (ulong offset);

39 static ulong load_serial_ymodem (ulong offset);

40 #endif

……

995 /* 支持xmodem, www.100ask.net */

996 static ulong load_serial_xmodem (ulong offset)

997 {

……

1003 char xmodemBuf[1024];/* 原來是ymodemBuf,這只是爲了與函數名稱一致 */

……

1008 info.mode = xyzModem_xmodem;/* 原來是xyzModem_ymodem,對應ymodem */

……

首先在文件開頭增加load_serial_xmodem函數的聲明,然後複製load_serial_ymodem函數爲load_serial_xmodem,稍作修改:

① 將局部數組ymodemBuf改名爲xmodemBuf,並在後面使用到的地方統一修改。這只是爲了與函數名稱一致。

② info.mode的值從xyzModem_ymodem改爲xyzModem_xmodem。


重新編譯、燒寫u-boot.bin後,就可以使用loadx命令下載文件了。

 


[編輯] 3. 支持網卡芯片CS8900
使用串口來傳輸文件的速率太低,現在增加對網卡芯片CS8900的支持。

本書使用開發板的網卡芯片CS8900的連接方式與smdk2410完全一樣,所以現在的U-Boot中已經支持CS8900了,它的驅動程序爲 drivers/cs8900.c。只要在U-Boot控制界面中稍加配置就可以使用網絡功能。使用網絡之前,先設置開發板IP地址、MAC地址,服務器 IP地址,比如可以在U-Boot中執行以下命令:

setenv ipaddr 192.168.1.17

setenv ethaddr 08:00:3e:26:0a:5b

setenv serverip 192.168.1.11

saveenv


然後就可以使用tftp或nfs命令下載文件了,注意:服務器上要開啓tftp或nfs服務。比如可以使用如下命令將u-boot.bin文件下載到內存0x30000000中:

tftp 0x30000000 u-boot.bin

nfs 0x30000000 192.168.1.57:/work/nfs_root/u-boot.bin


可以修改配置文件,讓網卡的各個默認值就是上面設置的值。在此之前,先了解網卡的相關文件,這有助於移植代碼以支持其他連接方式的CS8900。

首先,CS8900接在S3C2410、S3C2440的BANK3,位寬爲16,使用WAIT、nBE信號。在設置存儲控制器時要設置好BANK3。代碼在board/100ask24x0/lowlevel_init.S中:

#define B3_BWSCON (DW16 + WAIT + UBLB)

……

/* 時序參數 */

#define B3_Tacs 0x0/* 0clk */

#define B3_Tcos 0x3/* 4clk */

#define B3_Tacc 0x7/* 14clk */

#define B3_Tcoh 0x1/* 1clk */

#define B3_Tah 0x0/* 0clk */

#define B3_Tacp 0x3 /* 6clk */

#define B3_PMC 0x0/* normal */

接下來,還要確定CS8900的基地址。這在配置文件include/configs/100ask24x0.h中定義:

#define CONFIG_DRIVER_CS89001/* 使用CS8900 */

#define CS8900_BASE0x19000300/* 基地址 */

#define CS8900_BUS161 /* 位寬爲16 */

從第6章可以知道網卡CS8900的訪問基址爲0x19000000,之所以再偏移0x300是由它的特性決定的。


最後,還是在配置文件include/configs/100ask24x0.h中定義CS8900的各個默認地址:

#define CONFIG_ETHADDR08:00:3e:26:0a:5b

#define CONFIG_NETMASK 255.255.255.0

#define CONFIG_IPADDR192.168.1.17

#define CONFIG_SERVERIP192.168.1.11


額外的,如果要增加ping命令,還可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_PING,如下:

#define CONFIG_COMMANDS /

(CONFIG_CMD_DFL | /

CFG_CMD_CACHE | /

CFG_CMD_PING | /

……

 


[編輯] 4. 支持NAND Flash
U-Boot 1.1.6中對NAND Flash的支持有新舊兩套代碼,新代碼在drivers/nand目錄下,舊代碼在drivers/nand_legacy目錄下。文檔 doc/README.nand對這兩套代碼有所說明:使用舊代碼需要定義更多的宏,而新代碼移植自Linux內核2.6.12,它更加智能,可以自動識別更多型號的NAND Flash。目前之所以還保留舊的代碼,是因爲兩個目標板NETTA、NETTA_ISDN使用JFFS文件系統,它們還依賴於舊代碼。當相關功能移植到新代碼之後,舊的代碼將從U-Boot中去除。

要讓U-Boot支持NAND Flash,首先在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND,如下:

#define CONFIG_COMMANDS /

(CONFIG_CMD_DFL | /

CFG_CMD_CACHE | /

CFG_CMD_PING | /

CFG_CMD_NAND| /

……

然後選擇使用哪套代碼:在配置文件中定義宏CFG_NAND_LEGACY則使用舊代碼,否則使用新代碼。

使用舊代碼時,需要實現drivers/nand_legacy/nand_legacy.c中使用到的各種宏,比如:

#define NAND_WAIT_READY(nand)/* 等待Nand Flash的狀態爲“就緒”,代碼依賴於具體的開發板 */

#define WRITE_NAND_COMMAND(d, adr)/* 寫NAND Flash命令,代碼依賴於具體的開發板 */


本書使用新代碼,下面講述移植過程。

代碼的移植沒有現成的文檔,可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND後,就編譯代碼,然後一個一個地解決出現的錯誤。編譯結果中出現的錯誤和警告如下:

nand.h:412: error: `NAND_MAX_CHIPS' undeclared here (not in a function)

nand.c:35: error: `CFG_MAX_NAND_DEVICE' undeclared here (not in a function)

nand.c:38: error: `CFG_NAND_BASE' undeclared here (not in a function)

nand.c:35: error: storage size of `nand_info' isn't known

nand.c:37: error: storage size of `nand_chip' isn't known

nand.c:38: error: storage size of `base_address' isn't known

nand.c:37: warning: 'nand_chip' defined but not used

nand.c:38: warning: 'base_address' defined but not used

在配置文件include/configs/100ask24x0.h中增加如下3個宏就可以解決上述錯誤。在Flash的驅動程序中,設備是邏輯上的概念,表示一組相同結構、訪問函數相同的Flash芯片。在本書所用開發板中,只有一個NAND Flash芯片,所以設備數爲1,芯片數也爲1。

#define CFG_NAND_BASE 0/* 無實際意義:基地址,這在board_nand_init中重新指定 */

#define CFG_MAX_NAND_DEVICE 1/* NAND Flash“設備”的數目爲1 */

#define NAND_MAX_CHIPS 1/* 每個NAND Flash“設備”由1個NAND Flash“芯片”組成 */


修改配置文件後再次編譯,現在只有一個錯誤了,“board_nand_init函數未定義”:

nand.c:50: undefined reference to `board_nand_init'


調用board_nand_init函數的過程爲:NAND Flash的初始化入口函數是nand_init,它在lib_arm/board.c的start_armboot函數中被調用;nand_init函數在drivers/nand/nand.c中實現,它調用相同文件中的nand_init_chip函數;nand_init_chip函數首先調用 board_nand_init函數來初始化NAND Flash設備,最後纔是統一的識別過程。

從board_nand_init函數的名稱就可以知道它是平臺/開發板相關的函數,需要自己編寫。本書在cpu/arm920t/s3c24x0 目錄下新建一個文件nand_flash.c,在裏面針對S3C2410、S3C2440實現了統一的board_nand_init函數。

在編寫board_nand_init函數的之前,需要針對S3C2410、S3C2440 NAND Flash控制器的不同定義一些數據結構和函數:

(1)在include/s3c24x0.h文件中增加S3C2440_NAND數據結構。

/* NAND FLASH (see S3C2440 manual chapter 6, www.100ask.net) */

typedef struct {

S3C24X0_REG32 NFCONF;

S3C24X0_REG32 NFCONT;

S3C24X0_REG32 NFCMD;

S3C24X0_REG32 NFADDR;

S3C24X0_REG32 NFDATA;

S3C24X0_REG32 NFMECCD0;

S3C24X0_REG32 NFMECCD1;

S3C24X0_REG32 NFSECCD;

S3C24X0_REG32 NFSTAT;

S3C24X0_REG32 NFESTAT0;

S3C24X0_REG32 NFESTAT1;

S3C24X0_REG32 NFMECC0;

S3C24X0_REG32 NFMECC1;

S3C24X0_REG32 NFSECC;

S3C24X0_REG32 NFSBLK;

S3C24X0_REG32 NFEBLK;

} /*__attribute__((__packed__))*/ S3C2440_NAND;

(2)在include/s3c2410.h文件中仿照S3C2410_GetBase_NAND函數定義S3C2440_GetBase_NAND函數。

/* for s3c2440, www.100ask.net */

static inline S3C2440_NAND * const S3C2440_GetBase_NAND(void)

{

return (S3C2440_NAND * const)S3C2410_NAND_BASE;

}


既然新的NAND Flash代碼是從Linux內核2.6.12中移植來的,那麼cpu/arm920t/s3c24x0/nand_flash.c文件也可以仿照內核中,對S3C2410、S3C2440的NAND Flash進行初始化的drivers/mtd/nand/s3c2410.c文件來編寫。爲了方便閱讀,先把 cpu/arm920t/s3c24x0/nand_flash.c文件的代碼全部列出來,再講解:

01 /*

02 * s3c2410/s3c2440的NAND Flash控制器接口, www.100ask.net

03 * 修改自Linux內核2.6.13文件drivers/mtd/nand/s3c2410.c

04 */

05

06 #include <common.h>

07

08 #if (CONFIG_COMMANDS & CFG_CMD_NAND) && !defined(CFG_NAND_LEGACY)

09 #include <s3c2410.h>

10 #include <nand.h>

11

12 DECLARE_GLOBAL_DATA_PTR;

13

14 #define S3C2410_NFSTAT_READY (1<<0)

15 #define S3C2410_NFCONF_nFCE (1<<11)

16

17 #define S3C2440_NFSTAT_READY (1<<0)

18 #define S3C2440_NFCONT_nFCE (1<<1)

19

20

21 /* S3C2410:NAND Flash的片選函數 */

22 static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)

23 {

24 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();

25

26 if (chip == -1) {

27 s3c2410nand->NFCONF |= S3C2410_NFCONF_nFCE;/* 禁止片選信號 */

28 } else {

29 s3c2410nand->NFCONF &= ~S3C2410_NFCONF_nFCE;/* 使能片選信號 */

30 }

31 }

32

33 /* S3C2410:命令和控制函數

34 *

35 * 注意,這個函數僅僅根據各種命令來修改“寫地址”IO_ADDR_W 的值(這稱爲tglx方法),

36 * 這種方法使得平臺/開發板相關的代碼很簡單。

37 * 真正發出命令是在上一層NAND Flash的統一的驅動中實現,

38 * 它首先調用這個函數修改“寫地址”,然後才分別發出控制、地址、數據序列。

39 */

40 static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)

41 {

42 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();

43 struct nand_chip *chip = mtd->priv;

44

45 switch (cmd) {

46 case NAND_CTL_SETNCE:

47 case NAND_CTL_CLRNCE:

48 printf("%s: called for NCE/n", __FUNCTION__);

49 break;

50

51 case NAND_CTL_SETCLE:

52 chip->IO_ADDR_W = (void *)&s3c2410nand->NFCMD;

53 break;

54

55 case NAND_CTL_SETALE:

56 chip->IO_ADDR_W = (void *)&s3c2410nand->NFADDR;

57 break;

58

59 /* NAND_CTL_CLRCLE: */

60 /* NAND_CTL_CLRALE: */

61 default:

62 chip->IO_ADDR_W = (void *)&s3c2410nand->NFDATA;

63 break;

64 }

65 }

66

67 /* S3C2410:查詢NAND Flash狀態

68 *

69 * 返回值:0 – 忙, 1 – 就緒

70 */

71 static int s3c2410_nand_devready(struct mtd_info *mtd)

72 {

73 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();

74

75 return (s3c2410nand->NFSTAT & S3C2410_NFSTAT_READY);

76 }

77

78

79 /* S3C2440:NAND Flash的片選函數 */

80 static void s3c2440_nand_select_chip(struct mtd_info *mtd, int chip)

81 {

82 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();

83

84 if (chip == -1) {

85 s3c2440nand->NFCONT |= S3C2440_NFCONT_nFCE;/* 禁止片選信號 */

86 } else {

87 s3c2440nand->NFCONT &= ~S3C2440_NFCONT_nFCE;/* 使能片選信號 */

88 }

89 }

90

91 /* S3C2440:命令和控制函數,與s3c2410_nand_hwcontrol函數類似 */

92 static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd)

93 {

94 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();

95 struct nand_chip *chip = mtd->priv;

96

97 switch (cmd) {

98 case NAND_CTL_SETNCE:

99 case NAND_CTL_CLRNCE:

100 printf("%s: called for NCE/n", __FUNCTION__);

101 break;

102

103 case NAND_CTL_SETCLE:

104 chip->IO_ADDR_W = (void *)&s3c2440nand->NFCMD;

105 break;

106

107 case NAND_CTL_SETALE:

108 chip->IO_ADDR_W = (void *)&s3c2440nand->NFADDR;

109 break;

110

111 /* NAND_CTL_CLRCLE: */

112 /* NAND_CTL_CLRALE: */

113 default:

114 chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;

115 break;

116 }

117 }

118

119 /* S3C2440:查詢NAND Flash狀態

120 *

121 * 返回值:0 – 忙, 1 – 就緒

122 */

123 static int s3c2440_nand_devready(struct mtd_info *mtd)

124 {

125 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();

126

127 return (s3c2440nand->NFSTAT & S3C2440_NFSTAT_READY);

128 }

129

130 /*

131 * Nand flash硬件初始化:

132 * 設置NAND Flash的時序, 使能NAND Flash控制器

133 */

134 static void s3c24x0_nand_inithw(void)

135 {

136 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();

137 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();

138

139 #define TACLS 0

140 #define TWRPH0 4

141 #define TWRPH1 2

142

143 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410)

144 {

145 /* 使能NAND Flash控制器,初始化ECC,使能片選信號,設置時序 */

146 s3c2410nand->NFCONF = (1<<15)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);

147 }

148 else

149 {

150 /* 設置時序 */

151 s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);

152 /* 初始化ECC,使能NAND Flash控制器,使能片選信號 */

153 s3c2440nand->NFCONT = (1<<4)|(0<<1)|(1<<0);

154 }

155 }

156

157 /*

158 * 被drivers/nand/nand.c調用, 初始化NAND Flash硬件,初始化訪問接口函數

159 */

160 void board_nand_init(struct nand_chip *chip)

161 {

162 S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND();

163 S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND();

164

165 s3c24x0_nand_inithw();/* Nand flash硬件初始化 */

166

167 if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) {

168 chip->IO_ADDR_R = (void *)&s3c2410nand->NFDATA;

169 chip->IO_ADDR_W = (void *)&s3c2410nand->NFDATA;

170 chip->hwcontrol = s3c2410_nand_hwcontrol;

171 chip->dev_ready = s3c2410_nand_devready;

172 chip->select_chip = s3c2410_nand_select_chip;

173 chip->options = 0;/* 設置位寬等,位寬爲8 */

174 } else {

175 chip->IO_ADDR_R = (void *)&s3c2440nand->NFDATA;

176 chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA;

177 chip->hwcontrol = s3c2440_nand_hwcontrol;

178 chip->dev_ready = s3c2440_nand_devready;

179 chip->select_chip = s3c2440_nand_select_chip;

180 chip->options = 0;/* 設置位寬等,位寬爲8 */

181 }

182

183 chip->eccmode = NAND_ECC_SOFT;/* ECC較驗方式:軟件ECC */

184 }

185

186 #endif

文件中分別針對S3C2410、S3C2440實現了NAND Flash最底層訪問函數,並進行了一些硬件的設置(比如時序、使能NAND Flash控制器等)。新的代碼對NAND Flash的封裝做得很好,只要向上提供底層初始化函數board_nand_init來設置好平臺/開發板相關的初始化、提供底層接口即可。

最後,只要將新建的nand_flash.c文件編入U-Boot中就可以擦除、讀寫NAND Flash了。如下修改cpu/arm920t/s3c24x0/Makefile文件即可:

COBJS = i2c.o interrupts.o serial.o speed.o /

usb_ohci.o

改爲:

COBJS = i2c.o interrupts.o serial.o speed.o /

usb_ohci.o nand_flash.o

現在,可以使用新編譯的u-boot.bin燒寫內核映像到NAND Flash去了,請參考15.2.6。

 


[編輯] 5. 支持燒寫yaffs文件系統映像
在實際生產中,可以通過燒片器等手段將內核、文件系統映像燒入固態存儲設備中,Bootloader不需要具備燒寫功能。但爲了方便開發,通常在Bootloader中增加燒寫內核、文件系統映像文件的功能。

增加了NAND Flash功能的U-Boot 1.1.6已經可以通過“nand write ……”、“nand write.jffs2 ……”等命令來燒寫內核,cramfs、jffs2文件系統映像文件。但是在NAND Flash上,yaffs文件系統的性能更佳,下面增加“nand write.yaffs ……”命令以燒寫yaffs文件系統映像文件。

“nand write.yaffs ……”字樣的命令中,“nand”是具體命令,“write.yaffs ……”是參數。nand命令在common/cmd_nand.c中實現:

U_BOOT_CMD(nand, 5, 1, do_nand,

"nand - NAND sub-system/n",

"info - show available NAND devices/n"

"nand device [dev] - show or set current device/n"

"nand read[.jffs2] - addr off|partition size/n"

"nand write[.jffs2] - addr off|partiton size - read/write `size' bytes starting/n"

" at offset `off' to/from memory address `addr'/n"

……


先在其中增加“nand write.yaffs ……”的使用說明:

U_BOOT_CMD(nand, 5, 1, do_nand,

"nand - NAND sub-system/n",

"info - show available NAND devices/n"

"nand device [dev] - show or set current device/n"

"nand read[.jffs2] - addr off|partition size/n"

"nand write[.jffs2] - addr off|partiton size - read/write `size' bytes starting/n"

" at offset `off' to/from memory address `addr'/n"

"nand read.yaffs addr off size - read the `size' byte yaffs image starting/n"

" at offset `off' to memory address `addr'/n"

"nand write.yaffs addr off size - write the `size' byte yaffs image starting/n"

" at offset `off' from memory address `addr'/n"

……


然後,在nand命令的處理函數do_nand中增加對“write.yaffs ……”的支持。do_nand函數仍在common/cmd_nand.c中實現,代碼修改如下:

331 (!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))) {

……

354 }else if ( s != NULL && !strcmp(s, ".yaffs")){

355 if (read) {

356 /* read */

357 nand_read_options_t opts;

358 memset(&opts, 0, sizeof(opts));

359 opts.buffer = (u_char*) addr;

360 opts.length = size;

361 opts.offset = off;

362 opts.readoob = 1;

363 opts.quiet = quiet;

364 ret = nand_read_opts(nand, &opts);

365 } else {

366 /* write */

367 nand_write_options_t opts;

368 memset(&opts, 0, sizeof(opts));

369 opts.buffer = (u_char*) addr;/* yaffs文件系統映像存放的地址 */

370 opts.length = size;/* 長度 */

371 opts.offset = off;/* 要燒寫到的NAND Flash的偏移地址 */

372 /* opts.forceyaffs = 1; *//* 計算ECC碼的方法,沒有使用 */

373 opts.noecc = 1; /* 不需要計算ECC,yaffs映像中有OOB數據 */

374 opts.writeoob = 1;/* 寫OOB區 */

375 opts.blockalign = 1;/* 每個“邏輯上的塊”大小爲1個“物理塊” */

376 opts.quiet = quiet;/* 是否打印提示信息 */

377 opts.skipfirstblk = 1;/* 跳過第一個可用塊 */

378 ret = nand_write_opts(nand, &opts);

379 }

380 } else {

……

385 }

386

第354~379行就是針對命令“nand read.yaffs ……”、“nand write.yaffs ……”增加的代碼。有興趣的讀者可以自己分析“if (read)”分支的代碼,下面只講解“else”分支,即“nand write.yaffs ……”命令的實現。

NAND Flash每一頁大小爲(512+16)字節(還有其他格式的NAND Flash,比如每頁大小爲(256+8)、(2048+64)等),其中的512字節就是一般存儲數據的區域,16字節稱爲OOB(Out Of Band)區。通常在OOB區存放壞塊標記、前面512字節的ECC較驗碼等。

cramfs、jffs2文件系統映像文件中並沒有OOB區的內容,如果將它們燒入NOR Flash中,則是簡單的“平鋪”關係;如果將它們燒入NAND Flash中,則NAND Flash的驅動程序首先根據OOB的標記略過壞塊,然後將一頁數據(512字節)寫入後,還會計算這512字節的ECC較驗碼,最後將它寫入OOB區,如此循環。cramfs、jffs2文件系統映像文件的大小通常是512的整數倍。

而yaffs文件系統映像文件的格式則跟它們不同,文件本身就包含了OOB區的數據(裏面有壞塊標記、ECC較驗碼、其他yaffs相關的信息)。所以燒寫時,不需要再計算ECC值,首先檢查是否壞塊(是則跳過),然後寫入512字節的數據,最後寫入16字節的OOB數據,如此循環。yaffs文件系統映像文件的大小是(512+16)的整數倍。


注意:燒寫yaffs文件系統映像時,分區上第一個可用的(不是壞塊)塊也要跳過。


下面分析上面的代碼。

第369~371行設置源地址、目的地址、長度。燒寫yaffs文件系統映像前,一般通過網絡將它下載到內存某個地址處(比如 0x30000000),然後通過類似“nand write.yaffs 0x30000000 0x00A00000 $(filesize)”的命令燒到NAND Flash的偏移地址0x00A00000處。對於這個命令,第369行中opts.buffer等於0x30000000,第370行中 opts.length等於$(filesize)的值,就是前面下載的文件的大小,第371行中的opts.offset等於0x00A00000。

這裏列出不使用的第372行,是因爲opts.forceyaffs這個名字很有欺騙性,它其實是指計算ECC較驗碼的一種方法。燒寫yaffs文件系統映像時,不需要計算ECC較驗碼。

第373、374行指定燒寫數據時不計算ECC較驗碼、而是燒入文件中的OOB數據。

第375行指定“邏輯塊”的大小,“邏輯塊”可以由多個“物理塊”組成,在yaffs文件系統映像中,它們是1:1的關係。

第377行的opts.skipfirstblk是新加的項,nand_write_options_t結構中沒有skipfirstblk成員。它表示燒寫時跳過第一個可用的邏輯塊──這是由yaffs文件系統的特性決定的。


既然skipfirstblk是在nand_write_options_t結構中新加的項,那麼就要重新定義nand_write_options_t結構,並在下面調用的nand_write_opts函數中對它進行處理。

首先在include/nand.h中如下修改,增加skipfirstblk成員:

struct nand_write_options {

u_char *buffer;/* memory block containing image to write */

ulong length;/* number of bytes to write */

ulong offset;/* start address in NAND */

int quiet;/* don't display progress messages */

int autoplace;/* if true use auto oob layout */

int forcejffs2;/* force jffs2 oob layout */

int forceyaffs;/* force yaffs oob layout */

int noecc;/* write without ecc */

int writeoob;/* image contains oob data */

int pad;/* pad to page size */

int blockalign;/* 1|2|4 set multiple of eraseblocks to align to */

int skipfirstblk; /* 新加,燒寫時跳過第一個可用的邏輯塊 */

};

typedef struct nand_write_options nand_write_options_t;


然後,修改nand_write_opts函數增加對skipfirstblk成員的支持。它在drivers/nand/nand_util.c文件中,下面的第301、第430~435行是新加的:

285 int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t *opts)

286 {

……

300 int result;

301 int skipfirstblk = opts->skipfirstblk;

……

430 /* skip the first good block when wirte yaffs image, by www.100ask.net */

431 if (skipfirstblk) {

432 mtdoffset += erasesize_blockalign;

433 skipfirstblk = 0;

434 continue;

435 }

……


進行了上面的移植後,U-Boot已經可以燒yaffs文件系統映像了。由於前面設置“opts.noecc = 1”不使用ECC較驗碼,在燒寫過程中會出現很多的提示信息:

Writing data without ECC to NAND-FLASH is not recommended

可以修改drivers/nand/nand_base.c文件的nand_write_page函數將它去掉:

917 case NAND_ECC_NONE:

918 printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended/n");

改爲:

917 case NAND_ECC_NONE:

918 //printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended/n");

 


[編輯] 6. 修改默認配置參數以方便使用
前面移植網卡芯片CS8900時,已經設置過默認IP地址等。爲了使用U-Boot時減少一些設置,現在修改配置文件include/configs/100ask24x0.h增加默認配置參數,其中一些在移植過程中已經增加的選項這裏也再次說明。

(1)Linux啓動參數。

增加如下3個宏:

#define CONFIG_SETUP_MEMORY_TAGS 1/* 向內核傳遞內存分佈信息 */

#define CONFIG_CMDLINE_TAG 1/* 向內核傳遞命令行參數 */

/* 默認命令行參數 */

#define CONFIG_BOOTARGS "noinitrd root="/dev/mtdblock2" init="/linuxrc" console="ttySAC0""

(2)自動啓動命令。

增加如下2個宏:

/* 自動啓動前延時3秒 */

#define CONFIG_BOOTDELAY3

/* 自動啓動的命令 */

#define CONFIG_BOOTCOMMAND “nboot 0x32000000 0 0; bootm 0x32000000”

自動啓動時(開機3秒內無輸入),首先執行“nboot 0x32000000 0 0”命令將第0個NAND Flash偏移地址0上的映像文件複製到內存0x32000000中;然後執行“bootm 0x32000000”命令啓動內存中的映像。

(3)默認網絡設置。

根據具體網絡環境增加、修改下面4個宏:

#define CONFIG_ETHADDR08:00:3e:26:0a:5b

#define CONFIG_NETMASK 255.255.255.0

#define CONFIG_IPADDR192.168.1.17

#define CONFIG_SERVERIP192.168.1.11

 


[編輯] 15.2.6 U-Boot的常用命令
[編輯] 1. U-Boot的常用命令的用法
進入U-Boot控制界面後,可以運行各種命令,比如下載文件到內存,擦除、讀寫Flash,運行內存、NOR Flash、NAND Flash中的程序,查看、修改、比較內存中的數據等。

使用各種命令時,可以使用其開頭的若干個字母代替它。比如tftpboot命令,可以使用t、tf、tft、tftp等字母代替,只要其他命令不以這些字母開頭即可。

當運行一個命令之後,如果它是可重複執行的(代碼中使用U_BOOT_CMD定義這個命令時,第3個參數是1),若想再次運行可以直接輸入回車。

U-Boot接受的數據都是16進制,輸入時可以省略前綴0x、0X。

下面介紹常用的命令:

(1)幫助命令help。

運行help命令可以看到U-Boot中所有命令的作用,如果要查看某個命令的使用方法,運行“help 命令名”,比如“help bootm”。

可以使用“?”來代替“help”,比如直接輸入“?”、“? bootm”。


(2)下載命令。

U-Boot支持串口下載、網絡下載,相關命令有:loadb、loads、loadx、loady和tftpboot、nfs。

前幾個串口下載命令使用方法相似,以loadx命令爲例,它的用法爲“loadx [ off ] [ baud ]”。中括號“[]”表示裏面的參數可以省略,off表示文件下載後存放的內存地址,baud表示使用的波特率。如果baud參數省略,則使用當前的波特率;如果off參數省略,存放的地址爲配置文件中定義的宏CFG_LOAD_ADDR。

tftpboot命令使用TFTP協議從服務器下載文件,服務器的IP地址爲環境變量serverip。用法爲“tftpboot [loadAddress] [bootfilename]”,loadAddress表示文件下載後存放的內存地址,bootfilename表示要下載的文件的名稱。如果 loadAddress省略,存放的地址爲配置文件中定義的宏CFG_LOAD_ADDR;如果bootfilename省略,則使用單板的IP地址構造一個文件名,比如單板IP爲192.168.1.17,則缺省的文件名爲C0A80711.img。

nfs命令使用NFS協議下載文件,用法爲“nfs [loadAddress] [host ip addr:bootfilename]”。loadAddress、bootfilename的意義與tftpboot命令一樣,host ip addr表示服務器的IP地址,默認爲環境變量serverip。

下載文件成功後,U-Boot會自動創建或更新環境變量filesize,它表示下載的文件的長度,可以在後續命令中使用“$(filesize)”來引用它。


(3)內存操作命令。

常用的命令有:查看內存命令md、修改內存命令md、填充內存命令mw、拷貝命令cp。這些命令都可以帶上後綴“.b”、“.w”或“.l”,表示以字節、字(2個字節)、雙字(4個字節)爲單位進行操作。比如“cp.l 30000000 31000000 2”將從開始地址0x30000000處,拷貝2個雙字到開始地址爲0x31000000的地方。

md命令用法爲“md[.b, .w, .l] address [count]”,表示以字節、字或雙字(默認爲雙字)爲單位,顯示從地址address開始的內存數據,顯示的數據個數爲count。

mm命令用法爲“mm[.b, .w, .l] address”,表示以字節、字或雙字(默認爲雙字)爲單位,從地址address開始修改內存數據。執行mm命令後,輸入新數據後回車,地址會自動增加,Ctrl+C退出。

mw命令用法爲“mw[.b, .w, .l] address value [count]”,表示以字節、字或雙字(默認爲雙字)爲單位,往開始地址爲address的內存中填充count個數據,數據值爲value。

cp命令用法爲“cp[.b, .w, .l] source target count”,表示以字節、字或雙字(默認爲雙字)爲單位,從源地址source的內存拷貝count個數據到目的地址的內存。


(4)NOR Flash操作命令。

常用的命令有查看Flash信息的flinfo命令、加/解寫保護命令protect、擦除命令erase。由於NOR Flash的接口與一般內存相似,所以一些內存命令可以在NOR Flash上使用,比如讀NOR Flash時可以使用md、cp命令,寫NOR Flash時可以使用cp命令(cp根據地址分辨出是NOR Flash,從而調用NOR Flash驅動完成寫操作)。

直接運行“flinfo”即可看到NOR Flash的信息,有NOR Flash的型號、容量、各扇區的開始地址、是否只讀等信息。比如對於本書基於的開發板,flinfo命令的結果如下:

Bank # 1: AMD: 1x Amd29LV800BB (8Mbit)

Size: 1 MB in 19 Sectors

Sector Start Addresses:

00000000 (RO) 00004000 (RO) 00006000 (RO) 00008000 (RO) 00010000 (RO)

00020000 (RO) 00030000 00040000 00050000 00060000

00070000 00080000 00090000 000A0000 000B0000

000C0000 000D0000 000E0000 000F0000 (RO)

其中的RO表示該扇區處於寫保護狀態,只讀。

對於只讀的扇區,在擦除、燒寫它之前,要先解除寫保護。最簡單的命令爲“protect off all”,解除所有NOR Flash的寫保護。

erase命令常用的格式爲“erase start end”──擦除的地址範圍爲start至end、“erase start +len”──擦除的地址範圍爲start至(start + len – 1),“erase all”──表示擦除所有NOR Flash。

注意:其中的地址範圍,剛好是一個扇區的開始地址到另一個(或同一個)扇區的結束地址。比如要擦除Amd29LV800BB的前5個扇區,執行的命令爲“erase 0 0x2ffff”,而非“erase 0 0x30000”。


(5)NAND Flash操作命令。

NAND Flash操作命令只有一個:nand,它根據不同的參數進行不同操作,比如擦除、讀取、燒寫等。

“nand info”查看NAND Flash信息。

“nand erase [clean] [off size]”擦除NAND Flash。加上“clean”時,表示在每個塊的第一個扇區的OOB區加寫入清除標記;off、size表示要擦除的開始偏移地址和長度,如果省略 off和size,表示要擦除整個NAND Flash。

“nand read[.jffs2] addr off size”從NAND Flash偏移地址off處讀出size個字節的數據,存放到開始地址爲addr的內存中。是否加後綴“.jffs”的差別只是讀操作時的ECC較驗方法不同。

“nand write[.jffs2] addr off size”把開始地址爲addr的內存中的size個字節數據,寫到NAND Flash的偏移地址off處。是否加後綴“.jffs”的差別只是寫操作時的ECC較驗方法不同。

“nand read.yaffs addr off size”從NAND Flash偏移地址off處讀出size個字節的數據(包括OOB區域),存放到開始地址爲addr的內存中。

“nand write.yaffs addr off size”把開始地址爲addr的內存中的size個字節數據(其中有要寫入OOB區域的數據),寫到NAND Flash的偏移地址off處。

“nand dump off”,將NAND Flash偏移地址off的一個扇區的數據打印出來,包括OOB數據。


(6)環境變量命令。

“printenv”命令打印全部環境變量,“printenv name1 name2 ...”打印名字爲name1、name2、……”的環境變量。

“setenv name value”設置名字爲name的環境變量的值爲value。

“setenv name”刪除名字爲name的環境變量。

上面的設置、刪除操作只是在內存中進行,“saveenv”將更改後的所有環境變量寫入NOR Flash中。


(7)啓動命令。

不帶參數的“boot”、“bootm”命令都是執行環境變量bootcmd所指定的命令。

“bootm [addr [arg ...]]”命令啓動存放在地址addr處的U-Boot格式的映像文件(使用U-Boot目錄tools下的mkimage工具製作得到),[arg ...]表示參數。如果addr參數省略,映像文件所在地址爲配置文件中定義的宏CFG_LOAD_ADDR。

“go addr [arg ...]”與bootm命令類似,啓動存放在地址addr處的二進制文件, [arg ...]表示參數。

“nboot [[[loadAddr] dev] offset]”命令將NAND Flash設備dev上偏移地址off處的映像文件複製到內存loadAddr處,然後,如果環境變量autostart的值爲“yes”,就啓動這個映像。如果loadAddr參數省略,存放地址爲配置文件中定義的宏CFG_LOAD_ADDR;如果dev參數省略,則它的取值爲環境變量 bootdevice的值;如果offset參數省略,則默認爲0。

 


[編輯] 2. U-Boot命令使用實例
下面通過一個例子來演示如何使用各種命令燒寫內核映像文件、yaffs映像文件,並啓動系統。

(1)製作內核映像文件。

對於本書使用的Linux 2.6.22.6版本,編譯內核時可以直接生成U-Boot格式的映像文件uImage。

對於不能直接生成uImage的內核,製作方法在U-Boot根目錄下的README文件中有說明,假設已經編譯好的內核文件爲vmlinux,它是ELF格式的。mkimage是U-Boot目錄tools下的工具,它在編譯U-Boot時自動生成。執行以下3個命令將內核文件vmlinux製作爲U-Boot格式的映像文件uImage,它們首先將vmlinux轉換爲二進制格式,然後壓縮,最後構造頭部信息(裏面包含有文件名稱、大小、類型、 CRC較驗碼等):

① arm-linux-objcopy -O binary -R .note -R .comment -S vmlinux linux.bin

② gzip -9 linux.bin

③ mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 -e 0x30008000 -n "Linux Kernel Image" -d linux.bin.gz uImage


(2)燒寫內核映像文件uImage。

首先將uImage放在主機上的tftp或nfs目錄下,確保已經開啓tftp或nfs服務。

然後運行如下命令下載文件,擦除、燒寫NAND Flash:

① tftp 0x30000000 uImage 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/uImage

② nand erase 0x0 0x00200000

③ nand write.jffs2 0x30000000 0x0 $(filesize)

第3條命令之所以使用“nand write.jffs2”而不是“nand write”,是因爲前者不要求文件的長度是頁對齊的(512字節對齊)。也可以使用“nand write”,但是需要將命令中的長度參數改爲$(filesize)向上進行512取整後的值。比如uImage的大小爲1540883,向上進行 512取整後爲1541120(即0x178400),可以使用命令“nand write 0x30000000 0x0 0x178400”進行燒寫。


(3)燒寫yaffs文件系統映像。

假設yaffs文件系統映像的文件名爲yaffs.img,首先將它放在主機上的tftp或nfs目錄下,確保已經開啓tftp或nfs服務;然後執行如下命令下載、擦除、燒寫:

① tftp 0x30000000 yaffs.img 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/yaffs.img

② nand erase 0xA00000 0x3600000

③ nand write.yaffs 0x30000000 0xA00000 $(filesize)

這時,重啓系統,在U-Boot倒數3秒之後,就會自動啓動Linux系統。


(4)燒寫jffs2文件系統映像。

假設jffs2文件系統映像的文件名爲jffs2.img,首先將它放在主機上的tftp或nfs目錄下,確保已經開啓tftp或nfs服務;然後執行如下命令下載、擦除、燒寫:

① tftp 0x30000000 jffs2.img 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/jffs2.img

② nand erase 0x200000 0x800000

③ nand write.jffs2 0x30000000 0x200000 $(filesize)

系統啓動後,就可以使用“mount -t jffs2 /dev/mtdblock1 /mnt”掛接jffs2文件系統。

 


[編輯] 15.2.7 使用U-Boot來執行程序
在前面的硬件實驗中使用JTAG燒寫程序到NAND Flash,燒寫過程十分緩慢。如果使用U-Boot來燒寫NAND Flash,效率會高很多。燒寫二進制文件到NAND Flash中所使用的命令與上面燒寫內核映像文件uImage的過程類似,只是不需要將二進制文件製作成U-Boot格式。

另外,可以將程序下載到內存中,然後使用go命令執行它。假設有一個程序的二進制可執行文件test.bin,連接地址爲0x30000000。首先將它放在主機上的tftp或nfs目錄下,確保已經開啓tftp或nfs服務;然後將它下載到內存0x30000000處,最後使用go命令執行它:

① tftp 0x30000000 test.bin 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/test.bin

② go 0x30000000

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章