日本高清不卡中文字幕-一起草草视频在线观看-亚洲精品一区二区三区色-国产亚洲精品免费视频

您好,歡迎進入深圳市穎特新科技有限公司官方網站!

您現(xiàn)在的位置:首頁 新聞資訊 >> 新聞頭條 >> Bootloader之uBoot簡介(轉)
新聞資訊
NEWS INFORMATION

Bootloader之uBoot簡介(轉)

發(fā)布時間:2019-05-22

來自http://blog.ednchina.com/hhuwxf/1915416/message.aspx,感謝作者

一、Bootloader的引入從前面的硬件實驗可以知道,系統(tǒng)上電之后,需要一段程序來進行初始化:關閉 WATCHDOG、改變系統(tǒng)時鐘、初始化存儲控制器、將更多的代碼復制到內存中等等。如果它能將操作系統(tǒng)內核(無論從本地,比如Flash;還是從遠端, 比如通過網絡)復制到內存中運行,就稱這段程序為Bootloader。

簡單地說,Bootloader就是這么一小段程序,它在系統(tǒng)上電時開始執(zhí)行,初始化硬件設備、準備好軟件環(huán)境,最后調用操作系統(tǒng)內核。

可以增強Bootloader的功能,比如增加網絡功能、從PC上通過串口或網絡下載文件、 燒寫文件、將Flash上壓縮的文件解壓后再運行等──這就是一個功能更為強大的Bootloader,也稱為Monitor。實際上,在最終產品中用戶 并不需要這些功能,它們只是為了方便開發(fā)。Bootloader的實現(xiàn)嚴重依賴于具體硬件,在嵌入式系統(tǒng)中硬件配置千差萬別,即使是相同的CPU,它的外設(比如Flash)也可能不同,所以不可能有一個Bootloader支持所有的CPU、所有的電路板。即使是支持CPU架構比較多的U-Boot,也不是一拿來就可以使用的(除非里面的配置剛好與你的板子相同),需要進行一些移植。

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

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

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

上電后,Bootloader從板子上的某個固態(tài)存儲設備上將操作系統(tǒng)加載到RAM中運行,整個過程并沒有用戶的介入。產品發(fā)布時,Bootloader工作在這種模式下。

(2)下載(Downloading)模式。

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

板子與主機間傳輸文件時,可以使用串口的xmodem/ymodem/zmodem協(xié)議,它們使用簡單,只是速度比較慢;還可以使用網絡通過tftp、nfs協(xié)議來傳輸,這時,主機上要開啟tftp、nfs服務;還有其他方法,比如USB等。像Blob或U-Boot等這樣功能強大的Bootloader通常同時支持這兩種工作模式,而且允許用戶在這兩種工作模式之間進行切換。比如,U-Boot在啟動時處于正常的啟動加載模式,但是它會延時若干秒(這可以設置)等待終端用戶按下任意鍵而將U-Boot切換到下載模式。如果在指定時間內沒有用戶按鍵,則U-Boot繼續(xù)啟動Linux內核。 ]

15.1.2 Bootloader的結構和啟動過程

  • 1. 概述

在移植之前先了解Bootloader的一些通用概念,對理解它的代碼會有所幫助。

在一個嵌入式Linux系統(tǒng)中,從軟件的角度通常可以分為4個層次:

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

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

在大多嵌入式系統(tǒng)中并沒有固件,Bootloader是上電后執(zhí)行的第一個程序。

(2)Linux內核。

特定于嵌入式板子的定制內核以及內核的啟動參數(shù)。內核的啟動參數(shù)可以是內核默認的,或是由Bootloader傳遞給它的。

(3)文件系統(tǒng)。

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

(4)用戶應用程序。

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

顯然,在嵌入系統(tǒng)的固態(tài)存儲設備上有相應的分區(qū)來存儲它們。

“Boot parameters”分區(qū)中存放一些可設置的參數(shù),比如IP地址、串口波特率、要傳遞給內核的命令行參數(shù)等。正常啟動過程中,Bootloader首先 

運行,然后它將內核復制到內存中(也有些內核可以在固態(tài)存儲設備上直接運行),并且在內存某個固定的地址設置好要傳遞給內核的參數(shù),最后運行內核。內核啟 
動之后,它會掛接(mount)根文件系統(tǒng)(“Root filesystem”),啟動文件系統(tǒng)中的應用程序。

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

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

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

    • 硬件設備初始化。
    • 為加載Bootloader的第二階段代碼準備RAM空間。
    • 拷貝Bootloader的第二階段代碼到 RAM 空間中。
    • 設置好棧。
    • 跳轉到第二階段代碼的C入口點。

在第一階段進行的硬件初始化一般包括:關閉WATCHDOG、關中斷、設置CPU的速度和時鐘頻率、RAM初始化等。這些并不都是必需的,比如S3C2410/S3C2440的開發(fā)板所使用的U-Boot中,就將CPU的速度和時鐘頻率的設置放在第二階段。

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

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

    • 初始化本階段要使用到的硬件設備。
    • 檢測系統(tǒng)內存映射(memory map)。
    • 將內核映像和根文件系統(tǒng)映像從Flash上讀到RAM空間中。
    • 為內核設置啟動參數(shù)。
    • 調用內核。

為了方便開發(fā),至少要初始化一個串口以便程序員與Bootloader進行交互。

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

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

將根文件系統(tǒng)映像復制到RAM中,這不是必需的。這取決于是什么類型的根文件系統(tǒng),以及內核訪問它的方法。

為內核設置啟動參數(shù)將在下一小節(jié)介紹。

將內核存放在適當?shù)奈恢煤,直接跳到到它的入口點即可調用內核。調用內核之前,下列條件要滿足:

(1)CPU 寄存器的設置。

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

(2)CPU工作模式。

    • 必須禁止中斷(IRQs和FIQs)
    • CPU 必須 SVC 模式

(3)Cache 和 MMU 的設置。

    • MMU 必須關閉
    • 指令 Cache 可以打開也可以關閉
    • 數(shù)據 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將各類參數(shù)傳給內核。由于它們不能同時運行,傳遞辦法只有一個:Bootloader將參數(shù)放在某個約定的地方之后,再啟動內核,內核啟動后從這個地方獲得參數(shù)。除了約定好參數(shù)存放的地址外,還要規(guī)定參數(shù)的結構。Linux 2.4.x 

以后的內核都期望以標記列表(tagged list)的形式來傳遞啟動參數(shù)。標記,就是一種數(shù)據結構;標記列表,就是挨著存放的多個標記。標記列表以標記ATAG_CORE 開始,以標記ATAG_NONE 結束。標記的數(shù)據結構為tag,它由一個tag_header結構和一個聯(lián)合(union)組成。tag_header結構表示標記的類型及長度,比如是 表示內存還是表示命令行參數(shù)等。對于不同類型的標記使用不同的聯(lián)合(union),比如表示內存時使用tag_mem32,表示命令行時使用 tag_cmdline。數(shù)據結構tag和tag_header定義在Linux內核源碼的include/asm/setup.h頭文件中:

1
2
3
4
5
6
7
8
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; };

下面以設置內存標記、命令行標記為例說明參數(shù)的傳遞:

(1)設置標記 ATAG_CORE。

標記列表以標記 ATAG_CORE開始,假設Bootloader與內核約定的參數(shù)存放地址為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)設置內存標記。

假設開發(fā)板使用的內存起始地址為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"表示根文件系統(tǒng)在MTD2分區(qū)上,系統(tǒng)啟動后執(zhí)行的第一個程序為/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;

常用Bootloader介紹

現(xiàn)在Bootloader種類繁多,比如x86上有LILO、GRUB等。對于ARM架構的CPU,有U-Boot、Vivi等。它們各有特點,下面列出Linux的開放源代碼的Bootloader及其支持的體系架構,如表15.1所示。

開放源碼的Linux引導程序

<table fck__showtableborders?="">

Bootloader 
Monitor 
描述 
X86 
ARM 
PowerPC

LILO 
否 
Linux磁盤引導程序 
是 
否 

GRUB 
否 
GNU的LILO替代程序 
是 
否 

Loadlin 
否 
從DOS引導Linux 
是 
否 

ROLO 
否 
從ROM引導Linux而不需要BIOS 
是 
否 

Etherboot 
否 
通過以太網卡啟動Linux系統(tǒng)的固件 
是 
否 

LinuxBIOS 
否 
完全替代BUIS的Linux引導程序 
是 
否 

BLOB 
是 
LART等硬件平臺的引導程序 
否 
是 

U-Boot 
是 
通用引導程序 
是 
是 

RedBoot 
是 
基于eCos的引導程序 
是 
是 

Vivi 
是 
Mizi公司針對SAMSUNG的ARM CPU設計的引導程序 
否 
是 

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

2 U-Boot分析與移植

2.1 U-Boot工程簡介

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

它的名字“通用”有兩層含義:可以引導多種操作系統(tǒng)、支持多種架構的CPU。它支持如下操作 系統(tǒng):Linux、NetBSD、 VxWorks、QNX、RTEMS、ARTOS、LynxOS等,

支持如下架構的CPU:PowerPC、MIPS、x86、ARM、NIOS、 XScale等。

U-Boot有如下特性:

  • 開放源碼;
  • 支持多種嵌入式操作系統(tǒng)內核,如Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS;
  • 支持多個處理器系列,如PowerPC、ARM、x86、MIPS、XScale;
  • 較高的可靠性和穩(wěn)定性;
  • 高度靈活的功能設置,適合U-Boot調試、操作系統(tǒng)不同引導要求、產品發(fā)布等;
  • 豐富的設備驅動源碼,如串口、以太網、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、鍵盤等;
  • 較為豐富的開發(fā)調試文檔與強大的網絡技術支持;
  • 支持NFS掛載、RAMDISK(壓縮或非壓縮)形式的根文件系統(tǒng)
  • 支持NFS掛載、從FLASH中引導壓縮或非壓縮系統(tǒng)內核;
  • 可靈活設置、傳遞多個關鍵參數(shù)給操作系統(tǒng),適合系統(tǒng)在不同開發(fā)階段的調試要求與產品發(fā)布,尤對Linux支持最為強勁;
  • 支持目標板環(huán)境變量多種存儲方式,如FLASH、NVRAM、EEPROM;
  • CRC32校驗,可校驗FLASH中內核、RAMDISK鏡像文件是否完好;
  • 上電自檢功能:SDRAM、FLASH大小自動檢測;SDRAM故障檢測;CPU型號;
  • 特殊功能:XIP內核引導;

可以從http://sourceforge.net/projects/u-boot獲得U-Boot的最新版本,如果使用過程中碰到問題或是發(fā)現(xiàn)Bug,可以通過郵件列表網站http://lists.sourceforge.net/lists/listinfo/u-boot-users/獲得幫助。

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

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)平臺相關的或開發(fā)板相關的。

(2)通用的函數(shù)。

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

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

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

表2 U-Boot頂層目錄說明

<table fck__showtableborders?="">

目錄 
特性 
解釋說明

board 
開發(fā)板相關 
對應不同配置的電路板(即使CPU相同),比如smdk2410、sbc2410x

cpu 
平臺相關 
對應不同的CPU,比如arm920t、arm925t、i386等;在它們的子目錄下仍可以進一步細分,比如arm920t下就有at91rm9200、s3c24x0

lib_i386類似 
某一架構下通用的文件

include 
通用的函數(shù) 
頭文件和開發(fā)板配置文件,開發(fā)板的配置文件都放在include/configs目錄下,U-Boot沒有make menuconfig類似的萊單來進行可視化配置,需要手動地修改配置文件中的宏定義

lib_generic 
通用的庫函數(shù),比如printf等

common 
通用的函數(shù),多是對下一層驅動程序的進一步封裝

disk 
通用的設備驅動程序 
硬盤接口程序

drivers 
各類具體設備的驅動程序,基本上可以通用,它們通過宏從外面引入平臺/開發(fā)板相關的函數(shù)

dtt 
數(shù)字溫度測量器或者傳感器的驅動

fs 
文件系統(tǒng)

nand_spl 
U-Boot一般從ROM、NOR Flash等設備啟動,現(xiàn)在開始支持從NAND Flash啟動,但是支持的CPU種類還不多

net 
各種網絡協(xié)議

post 
上電自檢程序

rtc 
實時時鐘的驅動

doc 
文檔 
開發(fā)、使用文檔

examples 
示例程序 
一些測試程序,可以使用U-Boot下載后運行

tools 
工具 
制作S-Record、U-Boot格式映像的工具,比如mkimage

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

2 U-Boot頂層目錄的層次結構

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

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

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

2.3 U-Boot的配置、編譯、連接過程

1. U-Boot初體驗

u-boot-1.1.6中有幾千個文件,要想了解對于某款開發(fā)板,使用哪些文件、哪個文件首先執(zhí)行、可執(zhí)行文件占用內存的情況,最好的方法就是閱讀它的Makefile。

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

  • u-boot.bin:二進制可執(zhí)行文件,它就是可以直接燒入ROM、NOR Flash的文件。
  • u-boot:ELF格式的可執(zhí)行文件
  • u-boot.srec:Motorola S-Record格式的可執(zhí)行文件

對于S3C2410的開發(fā)板,執(zhí)行“make smdk2410_config”、“make all”后生成的u-boot.bin可以燒入NOR Flash中運行。啟動后可以看到串口輸出一些信息后進入控制界面,等待用戶的輸入。

對于S3C2440的開發(fā)板,燒入上面生成的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”實際上就是執(zhí)行如下命令:./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)確定開發(fā)板名稱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行兩個變量仍維持原來的值。

執(zhí)行完第23行后,BOARD_NAME的值等于第1個參數(shù),即“smdk2410”。

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

略過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行的條件不滿足,將執(zhí)行else分支的代碼。

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

繼續(xù)往下看代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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行的條件不滿足,將執(zhí)行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)創(chuàng)建頂層Makefile包含的文件include/config.mk。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

ARCH = arm

CPU = arm920t

BOARD = smdk2410

SOC = s3c24x0

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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>"

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

(1)開發(fā)板名稱BOARD_NAME等于$1;

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

ln -s asm-$2 asm

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

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

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

ARCH = $2

CPU = $3

BOARD = $4

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

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

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

/* Automatically generated - do not edit */

#include <configs/$1.h>"

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

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

配置文件中有兩類宏:

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

#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)另一類是參數(shù)(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_”除了設置一些參數(shù)外,主要用來設置U-Boot的功能、選擇使用文件中的哪一部分;而“CFG_”用來設置更細節(jié)的參數(shù)。

3. U-Boot的編譯、連接過程

配置完后,執(zhí)行“make all”即可編譯,從Makefile中可以了解U-Boot使用了哪些文件、哪個文件首先執(zhí)行、可執(zhí)行文件占用內存的情況。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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中:

1
2
3
4
5
6
7
8
9
10
11
12
13
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”字樣。

繼續(xù)往下看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變量就是平臺/開發(fā)板相關的各個目錄、通用目錄下相應的 
庫,比如: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兩行的規(guī)則表示,對于OBJS中的每個成員,都將進入cpu/$(CPU)目錄(即cpu/arm920t)編譯它們,F(xiàn)在OBJS為cpu/arm920t/start.o,它將由cpu/arm920t/start.S編譯得到。

第271、272兩行的規(guī)則表示,對于LIBS中的每個成員,都將進入相應的子目錄執(zhí)行“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的規(guī)則連接得到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中。

現(xiàn)在來總結一下U-Boot的編譯流程:

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

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

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

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

2.4 U-Boot的啟動過程源碼分析

首先強調,本書使用的U-Boot從NOR Flash啟動,下面以開發(fā)板smdk2410的U-Boot為例。

U-Boot屬于兩階段的Bootloader,第一階段的文件為cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平臺相關,后者是開發(fā)板相關。

U-Boot第一階段代碼分析

它與1.2節(jié)中描述的Bootloader第一階段所完成的功能可以一一對應:

(1)硬件設備初始化。

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

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

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

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

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

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

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行進行地址變換,因為這時候內存中還沒有數(shù)據,不能使用連接程序時確定的地址來讀取數(shù)據:

第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中實現(xiàn):

164 relocate:/* 將U-Boot復制到RAM中 */

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

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

167 cmp r0, r1 /* 測試現(xiàn)在是在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]處獲得數(shù)據 */

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

178 cmpr0, r2/* 判斷是否復制完畢 */

179 blecopy_loop/* 沒復制完,則繼續(xù) */

(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 /* 代碼段下面,留出一段內存以實現(xiàn)malloc */

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

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字節(jié)的內存給abort異常,

* 往下的內存就都是棧了

*/

191

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

圖3 U-Boot內存使用情況

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

在跳轉之前,還要清除BSS段(初始值為0、無初始值的全局變量、靜態(tài)變量放在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

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

223 ldrpc, _start_armboot

224

225 _start_armboot:.word start_armboot

226

U-Boot第二階段代碼分析

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

第二階段從lib_arm/board.c中的start_armboot函數(shù)開始,先看從這個函數(shù)開始的程序流程圖。

圖3 U-Boot第二階段流程圖

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

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

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

/* arch number of SMDK2410-Board */

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

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

(2)檢測系統(tǒng)內存映射(memory 
map)。對于特定的開發(fā)板,其內存的分布是明確的,所以可以直接設置。board/smdk2410/smdk2410.c中的dram_init函數(shù) 
指定了本開發(fā)板的內存起始地址為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;

}

這些設置的參數(shù),將在后面向內核傳遞參數(shù)時用到。

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

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

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

各項參數(shù)的意義為:

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

② maxargs:最大的參數(shù)個數(shù)

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

④ command:對應的函數(shù)指針,類型為(*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結構,然后調用它的函數(shù)(請參考common/command.c中的find_cmd函數(shù))。

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

(4)為內核設置啟動參數(shù)。

與15.1.2小節(jié)中《Bootloader與內核的交互》所描述的一樣,U-Boot也是 
通過標記列表向內核傳遞參數(shù)。并且,15.1.2小節(jié)中內存標記、命令行標記的示例代碼就是取自U-Boot中的setup_memory_tags、 
setup_commandline_tag函數(shù),它們都是在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函數(shù)來啟動內核。這個函數(shù)中,設置標記列表,最后通過“theKernel (0, 
bd->bi_arch_number, 
bd->bi_boot_params)”調用內核。其中,theKernel指向內核存放的地址(對于ARM架構的CPU,通常是 
0x30008000),bd->bi_arch_number就是前面board_init函數(shù)設置的機器類型ID,而 
bd->bi_boot_params就是標記列表的開始地址。

2.5 U-Boot的移植

開發(fā)板smdk2410的配置適用于大多數(shù)S3C2410單板,或是只需要極少的修改即可使用。但是目前U-Boot中沒有對S3C2440的支持,需要我們自己移植。

本書基于的S3C2410、S3C2440兩款開發(fā)板,它們的外接硬件相同:

  • 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所示劃分它們的使用區(qū)域。由于NAND 
Flash的“位反轉”現(xiàn)象比較常見,為保證數(shù)據的正確,在讀寫數(shù)據時需要使用ECC較驗。另外,NAND 
Flash在使用過程中、運輸過程中還有可能出現(xiàn)壞塊。所以本書選擇在NOR Flash中保存U-Boot,在NAND 
Flash中保存內核和文件系統(tǒng),并在使用U-Boot燒寫內核、文件系統(tǒng)時,進行壞塊檢查、ECC較驗。這樣,即使NAND 
Flash出現(xiàn)壞塊導致內核或文件系統(tǒng)不能使用,也可以通過NOR Flash中的U-Boot來重新燒寫。 [[Image:]]

圖15.4 開發(fā)板固態(tài)存儲器分區(qū)劃分

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

  • 同時支持本書使用的S3C2410和S3C2440開發(fā)板
  • 支持串口xmodem協(xié)議
  • 支持網卡芯片CS8900
  • 支持NAND Flash讀寫
  • 支持燒寫yaffs文件系統(tǒng)映像

1. 同時支持S3C2410和S3C2440

我們將在開發(fā)板smdk2410的基礎上進行移植。

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

為了不破壞原來的代碼,在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的刷新參數(shù),主要是REFCNT寄存器。

本書所用開發(fā)板的HCLK都設為100MHz,需要根據SDRAM芯片的具體參數(shù)重新計算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的改進版,它們的操作基本相似。不過在系統(tǒng)時鐘的設置、 
NAND 
Flash控制器的操作等方面,有一些小差別。它們的MPLL、UPLL計算公式不一樣,F(xiàn)CLK、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,對本書來說沒有區(qū) 
別。

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

首先修改board/100ask24x0/100ask24x0.c中的board_init函數(shù),下面是修改后的代碼:

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寄存器的值。開發(fā) 
板輸入時鐘為12MHz(這在include/configs/100ask24x0.h中的宏CONFIG_SYS_CLK_FREQ中定義),讀者可 
以根據代碼中的計算公式針對自己的開發(fā)板修改系統(tǒng)時鐘。

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

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 /* 啟動內核時,參數(shù)存放位置。這個值在構造標記列表時用到 */

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

144

145 icache_enable();

146 dcache_enable();

147

148 return 0;

149 }

150

最后一步:獲取系統(tǒng)時鐘的函數(shù)需要針對S3C2410、S3C2440的不同進行修改。

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

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

DECLARE_GLOBAL_DATA_PTR;

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

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

由于分頻系數(shù)的設置方法也不一樣,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

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

(4)選擇NOR Flash的型號。

但是,現(xiàn)在還無法通過U-Boot命令燒寫NOR Flash。本書所用開發(fā)板中的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的操作函數(shù)在board/100ask24x0/flash.c中實現(xiàn),它支持AM29LV400y和AM29LV800。對于其他型號的 
NOR 
Flash,如果符合CFI接口標準,則可以在使用drivers/cfi_flash.c中的接口函數(shù);否則,只好自己編寫了。如果要使用 
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

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

2. 支持串口xmodem協(xié)議

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

依照loady的實現(xiàn)來編寫代碼,首先使用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函數(shù)中增加對loadx命令的處理分支。也是依照loady來實現(xiàn):

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函數(shù),它是依照load_serial_ymodem實現(xiàn)的一個新函數(shù):

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,這只是為了與函數(shù)名稱一致 */

……

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

……

首先在文件開頭增加load_serial_xmodem函數(shù)的聲明,然后復制load_serial_ymodem函數(shù)為load_serial_xmodem,稍作修改:

① 將局部數(shù)組ymodemBuf改名為xmodemBuf,并在后面使用到的地方統(tǒng)一修改。這只是為了與函數(shù)名稱一致。

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

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

3. 支持網卡芯片CS8900

使用串口來傳輸文件的速率太低,現(xiàn)在增加對網卡芯片CS8900的支持。

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

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)

……

/* 時序參數(shù) */

#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文件系統(tǒng),它們還依賴于舊代碼。當相關功能移植到 
新代碼之后,舊的代碼將從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則使用舊代碼,否則使用新代碼。

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

#define NAND_WAIT_READY(nand)/* 等待Nand Flash的狀態(tài)為“就緒”,代碼依賴于具體的開發(fā)板 */

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

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

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

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的驅動程序中,設備是邏輯上的概念,表示一組相同結構、訪問函數(shù)相同的Flash芯片。在本書所用開發(fā)板中,只有一個 
NAND Flash芯片,所以設備數(shù)為1,芯片數(shù)也為1。

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

#define CFG_MAX_NAND_DEVICE 1/* NAND Flash“設備”的數(shù)目為1 */

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

修改配置文件后再次編譯,現(xiàn)在只有一個錯誤了,“board_nand_init函數(shù)未定義”:

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

調用board_nand_init函數(shù)的過程為:NAND 
Flash的初始化入口函數(shù)是nand_init,它在lib_arm/board.c的start_armboot函數(shù)中被調用;nand_init函 
數(shù)在drivers/nand/nand.c中實現(xiàn),它調用相同文件中的nand_init_chip函數(shù);nand_init_chip函數(shù)首先調用 
board_nand_init函數(shù)來初始化NAND Flash設備,最后才是統(tǒng)一的識別過程。

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

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

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

/* 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函數(shù)定義S3C2440_GetBase_NAND函數(shù)。

/* 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的片選函數(shù) */

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:命令和控制函數(shù)

34 *

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

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

37 * 真正發(fā)出命令是在上一層NAND Flash的統(tǒng)一的驅動中實現(xiàn),

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

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狀態(tài)

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的片選函數(shù) */

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:命令和控制函數(shù),與s3c2410_nand_hwcontrol函數(shù)類似 */

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狀態(tài)

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硬件,初始化訪問接口函數(shù)

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實現(xiàn)了NAND 
Flash最底層訪問函數(shù),并進行了一些硬件的設置(比如時序、使能NAND Flash控制器等)。新的代碼對NAND 
Flash的封裝做得很好,只要向上提供底層初始化函數(shù)board_nand_init來設置好平臺/開發(fā)板相關的初始化、提供底層接口即可。

最后,只要將新建的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

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

5. 支持燒寫yaffs文件系統(tǒng)映像

在實際生產中,可以通過燒片器等手段將內核、文件系統(tǒng)映像燒入固態(tài)存儲設備中,Bootloader不需要具備燒寫功能。但為了方便開發(fā),通常在Bootloader中增加燒寫內核、文件系統(tǒng)映像文件的功能。

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

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

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命令的處理函數(shù)do_nand中增加對“write.yaffs ……”的支持。do_nand函數(shù)仍在common/cmd_nand.c中實現(xiàn),代碼修改如下:

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文件系統(tǒng)映像存放的地址 */

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

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

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

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

374 opts.writeoob = 1;/* 寫OOB區(qū) */

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 ……”命令的實現(xiàn)。

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

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

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

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

下面分析上面的代碼。

第369~371行設置源地址、目的地址、長度。燒寫yaffs文件系統(tǒng)映像前,一般通過網 
絡將它下載到內存某個地址處(比如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文件系統(tǒng)映像時,不需要計算ECC較驗碼。

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

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

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

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

首先在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函數(shù)增加對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文件系統(tǒng)映像了。由于前面設置“opts.noecc = 1”不使用ECC較驗碼,在燒寫過程中會出現(xiàn)很多的提示信息:

Writing data without ECC to NAND-FLASH is not recommended

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

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. 修改默認配置參數(shù)以方便使用

前面移植網卡芯片CS8900時,已經設置過默認IP地址等。為了使用U-Boot時減少一些設置,現(xiàn)在修改配置文件include/configs/100ask24x0.h增加默認配置參數(shù),其中一些在移植過程中已經增加的選項這里也再次說明。

(1)Linux啟動參數(shù)。

增加如下3個宏:

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

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

/* 默認命令行參數(shù) */

#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秒內無輸入),首先執(zhí)行“nboot 0x32000000 0 0”命令將第0個NAND Flash偏移地址0上的映像文件復制到內存0x32000000中;然后執(zhí)行“bootm 0x32000000”命令啟動內存中的映像。

(3)默認網絡設置。

根據具體網絡環(huán)境增加、修改下面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

2.6 U-Boot的常用命令

1. U-Boot的常用命令的用法

進入U-Boot控制界面后,可以運行各種命令,比如下載文件到內存,擦除、讀寫Flash,運行內存、NOR Flash、NAND Flash中的程序,查看、修改、比較內存中的數(shù)據等。

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

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

U-Boot接受的數(shù)據都是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 
]”。中括號“[]”表示里面的參數(shù)可以省略,off表示文件下載后存放的內存地址,baud表示使用的波特率。如果baud參數(shù)省略,則使用當前的波特 
率;如果off參數(shù)省略,存放的地址為配置文件中定義的宏CFG_LOAD_ADDR。

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

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

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

(3)內存操作命令。

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

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

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

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

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

(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的型號、容量、各扇區(qū)的開始地址、是否只讀等信息。比如對于本書基于的開發(fā)板,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表示該扇區(qū)處于寫保護狀態(tài),只讀。

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

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

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

(5)NAND Flash操作命令。

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

“nand info”查看NAND Flash信息。

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

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

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

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

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

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

(6)環(huán)境變量命令。

“printenv”命令打印全部環(huán)境變量,“printenv name1 name2 ...”打印名字為name1、name2、……”的環(huán)境變量。

“setenv name value”設置名字為name的環(huán)境變量的值為value。

“setenv name”刪除名字為name的環(huán)境變量。

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

(7)啟動命令。

不帶參數(shù)的“boot”、“bootm”命令都是執(zhí)行環(huán)境變量bootcmd所指定的命令。

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

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

“nboot [[[loadAddr] dev] offset]”命令將NAND 
Flash設備dev上偏移地址off處的映像文件復制到內存loadAddr處,然后,如果環(huán)境變量autostart的值為“yes”,就啟動這個映 
像。如果loadAddr參數(shù)省略,存放地址為配置文件中定義的宏CFG_LOAD_ADDR;如果dev參數(shù)省略,則它的取值為環(huán)境變量 
bootdevice的值;如果offset參數(shù)省略,則默認為0。

2. U-Boot命令使用實例

下面通過一個例子來演示如何使用各種命令燒寫內核映像文件、yaffs映像文件,并啟動系統(tǒng)。

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

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

對于不能直接生成uImage的內核,制作方法在U-Boot根目錄下的README文件中 
有說明,假設已經編譯好的內核文件為vmlinux,它是ELF格式的。mkimage是U-Boot目錄tools下的工具,它在編譯U-Boot時自 
動生成。執(zhí)行以下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字節(jié)對齊)。也可以使用“nand 
write”,但是需要將命令中的長度參數(shù)改為$(filesize)向上進行512取整后的值。比如uImage的大小為1540883,向上進行 
512取整后為1541120(即0x178400),可以使用命令“nand write 0x30000000 0x0 
0x178400”進行燒寫。

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

假設yaffs文件系統(tǒng)映像的文件名為yaffs.img,首先將它放在主機上的tftp或nfs目錄下,確保已經開啟tftp或nfs服務;然后執(zhí)行如下命令下載、擦除、燒寫:

① 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)

這時,重啟系統(tǒng),在U-Boot倒數(shù)3秒之后,就會自動啟動Linux系統(tǒng)。

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

假設jffs2文件系統(tǒng)映像的文件名為jffs2.img,首先將它放在主機上的tftp或nfs目錄下,確保已經開啟tftp或nfs服務;然后執(zhí)行如下命令下載、擦除、燒寫:

① 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)

系統(tǒng)啟動后,就可以使用“mount -t jffs2 /dev/mtdblock1 /mnt”掛接jffs2文件系統(tǒng)。

2.7 使用U-Boot來執(zhí)行程序

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

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

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

② go 0x30000000

聯(lián)系方式0755-82591179

傳真:0755-82591176

郵箱:vicky@yingtexin.net

地址:深圳市龍華區(qū)民治街道民治大道973萬眾潤豐創(chuàng)業(yè)園A棟2樓A08

华安县| 延寿县| 泰来县| 股票| 云安县| 贺州市| 龙泉市| 抚州市| 南岸区| 宁晋县| 潜山县| 金溪县| 达日县| 大连市| 开封县| 若尔盖县| 新安县| 于田县| 泽普县| 贵阳市| 新郑市| 霞浦县| 尚义县| 高碑店市| 汨罗市| 清涧县| 轮台县| 宣汉县| 科技| 渭南市| 广水市| 科技| 纳雍县| 宁安市| 博乐市| 隆化县| 铜梁县| 呼图壁县| 浦江县| 齐河县| 象州县|