周 力,姚茂群
(杭州師范大學,浙江 杭州 311121)
U-boot就是universal bootloader的簡稱,即通用的啟動代碼,在源代碼級別具有移植性,可以針對多個開發(fā)板進行移植。U-boot起先是SourceForge的一個開源項目,由一個人發(fā)起,后由整個網絡上所有感興趣的人共同維護發(fā)展而來的一個bootloader。U-boot經過多年的發(fā)展,已經成為bootloader的標準,現在大部分的嵌入式設備都使用U-boot做為bootloader。U-boot的終極目的是啟動內核,在U-boot中事先給Linux內核準備一些啟動參數放在內存中,然后這些參數將被用來指導Linux內核的啟動,借助U-boot可以部署整個Liunx系統(tǒng),包括kernel、rootfs的鏡像,也可以進行cpu級和板級硬件管理。當啟動內核以后,U-boot的生命周期結束[1]。
U-boot源代碼是從U-boot官網下載的,由很多目錄構成,一些主要目錄的功能如表1所示。本次實驗中使用的U-boot版本是U-boot官方的2013-10版本,在windows和ubuntu的共享文件下創(chuàng)建u-boot-2013-10目錄,并從官網下載U-boot,解壓到共享文件夾下,在ubuntu中也解壓一份代碼,在windows中修改代碼,在ubuntu中編譯代碼,以方便整個實驗進行[1-2]。

表1 U-boot主要源碼目錄與作用
一個同時安裝有bootloader、內核鏡像和根文件系統(tǒng)鏡像的固態(tài)存儲設備的典型空間分配結構如圖1所示。從固態(tài)存儲設備上啟動bootloader一般可以分為兩個階段,即階段1和階段2。階段1為匯編階段、階段2為c語言階段。階段1注重SoC內部,在SRAM中完成,階段2注重SoC外部,在DRAM中完成[2]。

圖1 固態(tài)存儲設備的典型空間分配結構
U-boot的第一階段主要完成異常向量表構建,設置cpu為SVC模式,初始化時鐘,重定位,加載U-boot第二階段代碼到DRAM等一些工作,具體步驟如圖2所示。

圖2 U-boot第一階段
U-boot的第二階段主要完成對開發(fā)板級別的硬件、軟件數據結構進行初始化,包括網卡、控制臺、SD卡、環(huán)境變量等的初始化[3]。具體步驟如圖3所示。

圖3 U-boot第二階段
U-boot本質上是一個C語言項目,由很多個項目組成,可以通過Makefile來做項目管理,方便編譯鏈接。在進行移植時,U-boot中差別最大的是board和cpu這兩個目錄,這兩個目錄是和開發(fā)板有關的,在本次移植中使用的開發(fā)板cpu是三星公司的s5pv210,所以要找U-boot中針對s5pv210或者s5pc110的cpu進行移植的作為參考[4]。
根據移植的一般規(guī)律,參考include/configs/s5p_goni.h這個頭文件,找到對應的board在board/samsung/goni這個目錄下。
這個版本中的U-boot的Makefile使用了boards.cfg文件,因此在make xxx_config編譯生成U-boot.bin的可執(zhí)行二進制文件時,這個xxx要到boards.cfg文件中查找。經過分析為s5p_goni,配置時對應的cpu、board文件夾分別為:
cpu:u-boot-2013.10archarmcpuarmv7
board:u-boot-2013.10oardsamsunggoni
修改Makefile及CROSS_COMPILE:
由于使用的cpu是三星公司的s5pv210,屬于Cortex-A8系列架構,所以它的ARCH屬于arm系列,在ubuntu系統(tǒng)上找到交叉編譯工具鏈安裝的地方,復制相關路徑到主Makefile中的CROSS_COMPILE,具體代碼修改如下[5]:
ARCH=arm
CROSS_COMPILE=/usr/local/arm/arm-2009q3/bin//arm-none-linux-gnueabi-
在U-boot的主Makeflie中存在以下兩行代碼:
%_config::unconfig
@$(MKCONFIG) -A $(@:_config=)
實際配置開發(fā)板時主Makefile傳2個參數:-A和s5p_goni到mkconfig腳本中,在mkconfig腳本中使用awk正則表達式將boards.cfg文件中與剛才s5p_goni能夠匹配上的那一行截取出來賦值給變量line,然后將line的內容以空格為間隔依次分開,分別賦值給$1,$2,…,$8。在解析完boards.cfg之后,$1到$8就有了新的值,將這些值用于開發(fā)板的配置,具體的值如下所示[5]:
$1=Active,$2=arm,$3=armv7,$4=s5pc1xx,$5=samsung,$6=goni,$7=s5p_goni,$8=-
在C語言中整個項目的入口就是main函數,而在U-boot階段由于有匯編階段的參與,整個程序的入口取決于u-boot.lds中ENTRY聲明的地方。因此start.S即是整個U-boot的入口。在start.S中通過一句跳轉指令bl cpu_init_crit,短跳轉到lowlevel_init函數。lowlevel_init函數中完成的工作有:關看門狗、調用uart_asm_init來初始化串口、初始化時鐘。跳轉出來以后在cpu_init_crit函數中初始化完DDR,從而進入到第二階段,所以在這里執(zhí)行重定位代碼,其中的_TEXT_BASE是U-boot起始鏈接地址。U-boot的第二階段就在crt0.S文件中,第二階段的入口就是_main函數。第二階段分成了board_init_f和board_init_r這兩個函數,board_init_f函數主要是各種板級初始化。初始化控制臺、時鐘、DRAM等等。board_init_r函數主要是各種外設的初始化,如初始化iNand/SD、環(huán)境變量、網卡等等。在本次實驗中移植和分析U-boot最主要的幾個模塊。
在s5pv210的核心板中有兩塊DDR內存,每塊128 MB,總共256 MB。將三星版本的U-boot中的DDR內存初始化函數有關的cpu_init.S和頭文件s5pc110.h復制到U-boot-2013-10/board/Samsung/goni下,這部分代碼必須在程序運行代碼前8 kb以內,否則校驗會不通過,導致U-boot無法正常啟動。在/board/Samsung/goni/Makefile中和/arch/arm/cpu/u-boot.lds中分別做相應修改,如下:
LOW:=lowlevel_init.o cpu_init.o
board/samsung/goni/lowlevel_init.o (.text*) board /samsung/goni/cpu_init.o (.text*)
下一步修改相應的DDR配置參數。將三星版本的include/configs/smdkv210single.h中的DDR相關參數復制到include/configs/s5p_goni.h下,查相關數據手冊得,在s5pv210的核心板中MEMORY_BASE_ADDRESS為0x30000000[6],而在三星版本中MEMORY_BASE_ADDRESS為0x20000000,做如下修改:
#define MEMORY_BASE_ADDRESS 0x20000000改為:
#define MEMORY_BASE_ADDRESS 0x3000000
#define DMC0_MEMCONFIG_0 0x20F01323改為:
#define DMC0_MEMCONFIG_0 0x30F01323
從邏輯上來說,重定位部分代碼應該在DDR初始化之后和U-boot第二階段來臨前之間。U-boot的第一階段和第二階段的劃分并不是絕對的,唯一必須遵循的就是第一階段代碼不能大于8 KB。所以U-boot的第一階段至少要完成DDR初始化和重定位。在滿足這些條件之后,選擇在u-boot-2013-10/board/samsung/goni/movi.c中完成重定位工作。將三星版本中的cpu/s5pc11x/movi.c和include/movi.h分別復制到u-boot-2013-10/board/samsung/goni和u-boot-2013-10/include下[6-7]。重定位完成以后,清理完bss段,數據段,設置好棧,用一句ldrpc, __main跳轉到_main函數,這也是U-boot第一階段和第二階段的分界線。修改相應的makefile和u-boot.lds,做如下修改:
LOW:=movi.o
board/samsung/goni/movi.o (.text*)
4.3.1 SD卡驅動移植分析
SD卡驅動工作包含2部分內容,一部分是drivers/mmc目錄下的驅動,另外一部分是U-boot自己提供的初始化代碼,譬如GPIO初始化、時鐘初始化等。在u-boot-2013-10中,驅動相關的文件主要有[7]:
drivers/mmc/mmc.c
drivers/mmc/sdhci.c
drivers/mmc/s5p_sdhci.c
board/samsung/goni/goni.c
在三星移植版本中,驅動相關的文件主要有:
drivers/mmc/mmc.c
drivers/mmc/s3c_hsmmc.c
cpu/s5pc11x/cpu.c
cpu/s5pc11x/setup_hsmmc.c
4.3.2 復制文件以及修改Makefile
將三星的drivers/mmc復制到u-boot-2013-10的drivers/mmc中,為了使原來三星中的sdhci.o和s5p_sdhci.o不發(fā)生編譯錯誤,需要將drivers/mmc/Makefile中涉及到的相應宏在s5p_goni.h中注釋掉,如下:
//#define CONFIG_SDHCI
//#define CONFIG_S5P_SDHCI
在drivers/mmc/Makefile中添加從三星復制過來的文件的編譯項COBJS-$(CONFIG_S3C_HSMMC)+=s3c_hsmmc.,再在u-boot-2013-10/include/configs/s5p_goni.h中添加原來在三星定義的關于SD卡的宏,主要如下[8]:
#define CONFIG_S3C_HSMMC
#define SDMMC_BLK_SIZE (0xD003A500)
#define COPY_SDMMC_TO_MEM (0xD003E008)
#define USE_MMC0
#define USE_MMC2
#define MMC_MAX_CHANNEL4
4.3.3 驅動相關移植
對比兩個U-boot,找到u-boot-2013-10中沒有定義的函數的頭文件所在路徑,將三星版本的include目錄下的mmc.h和s3c_hsmmc.h移植到u-boot-2013-10中include目錄下。將三星的drivers/mmc.c中的cpu_mmc_init函數復制到board/samung/goni中board_mmc_init函數中,移植三星的cpu/s5pc11x/setup_hsmmc.c到board/Samsung/goni下,在goni目錄下的Makefile中添加COBJS-y:=goni.o setup_hsmmc.o。
在本次實驗中用SD卡從ubuntu中拷貝程序,整個U-boot能成功啟動,即代表SD卡驅動移植成功。
4.4.1 環(huán)境變量理解
環(huán)境變量有2份,一份在Flash中,另一份在DDR中。U-boot開機時一次性從Flash中讀取全部環(huán)境變量到DDR中作為環(huán)境變量的初始化值,使用過程中都是用DDR中這一份,可以用saveenv指令將DDR中的環(huán)境變量重新寫入Flash中去更新Flash中環(huán)境變量。下次開機時又會從Flash中再讀取一次[8]。
4.4.2 環(huán)境變量保存位置分析
U-boot燒錄時使用的扇區(qū)數是SD卡的扇區(qū)1-16和49-x(x-49大于等于U-boot的大小)。從U-boot的燒錄情況來看,SD卡的扇區(qū)0空閑,扇區(qū)1-16被U-boot的BL1占用,扇區(qū)17-48空閑,扇區(qū)49-x被U-boot的階段二占用。再往后就是kernel、rootfs等鏡像的分區(qū)了。所以ENV不能往扇區(qū)1-16或者49-x中來放置,其他地方都可以。ENV的大小是16 K字節(jié)也就是32個扇區(qū)。在環(huán)境變量移植中宏CONFIG_ENV_OFFSET決定了環(huán)境變量在SD卡中相對SD卡扇區(qū)0的偏移量,也就是ENV寫到SD卡的哪里去了。修改這個CONFIG_ENV_OFFSET的值,將ENV寫到從第17扇區(qū)開始的地方,具體如下:
#define CONFIG_ENV_OFFSET 17*512 //第17扇區(qū)開始的位置
#define MOVI_BL2_POS ((eFUSE_SIZE/MOVI_BLKSIZE)+MOVI_BL1_BLKCNT+ MOVI_ENV_BLKCNT)
宏MOVI_BL2_POS決定了U-boot的BL2開始扇區(qū),宏(eFUSE_SIZE/MOVI_BLKSIZE)為1,代表扇區(qū)0,宏MOVI_BL1_BLKCNT為16,存放U-boot的BL1,宏MOVI_ENV_BLKCNT為32,存放ENV,從扇區(qū)49開始就是U-boot的BL2了。為了實現環(huán)境變量不沖突,將ENV放到17扇區(qū)起始的地方即可。
4.4.3 環(huán)境變量的測試
程序修改重新編譯后啟動,為了確保iNand里面沒有之前保存過的環(huán)境變量,對iNand的前49個扇區(qū)進行擦除。使用命令:mmc write 0 30000000 0# 49來擦除SD卡的扇區(qū)0-48,這樣以前的環(huán)境變量都沒有了。重新開機后先修改bootdelays等于3作為標記后saveenv保存環(huán)境變量后重啟。測試方法是,使用命令mmc read 0 30000000 17# 32將iNand的17開始的32個扇區(qū)讀出來到內存30000000處,用md命令查看。找到顯示區(qū)域里面的各個環(huán)境變量,看到讀出來的值與剛才標記的值一樣,說明修改成功[9]。具體如圖4所示。

圖4 環(huán)境變量測試效果
4.5.1 網卡移植分析
U-boot中對各種功能的實現是一種可配置可裁剪的設計,默認情況下U-boot沒有選擇支持網絡。當在配置頭文件中添加一行#define CONFIG_CMD_NET,即添加了網絡支持宏之后,U-boot的board_init_r函數中初始化時就會執(zhí)行eth_initialize函數,從而網絡相關代碼初始化就會被執(zhí)行,將來網絡就可以使用[10]。
4.5.2 設置網絡相關的環(huán)境變量
#define CONFIG_ETHADDR00 40:5c:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0 //子網掩碼
#define CONFIG_IPADDR 172.17.64.188 //開發(fā)板的地址
#define CONFIG_SERVERIP 172.17.64.167 //虛擬機IP地址
#define CONFIG_GATEWAYIP 172.17.64.1 //網關
4.5.3 添加ping和tftp命令
在Linux系統(tǒng)中網絡底層驅動被上層應用調用的接口是socket,是一個典型的分層結構,底層和上層是完全被socket接口隔離的。但是在U-boot中網絡底層驅動和上層應用是不分層的[11-12]。即上層網絡的每一個應用都是自己去調用底層驅動中的操作硬件的代碼來實現的。U-boot中有很多預先設計的需要用到網絡的命令,直接相關的就是ping和tftp這兩個命令。這兩個命令在U-boot中也是需要用相應的宏開關來打開或者關閉的。
經分析,發(fā)現ping命令開關宏為CONFIG_CMD_PING,而tftp命令的開關為CONFIG_CMD_NET,已經存在。所以只需在include/configs/S5p_goni.h中添加ping命令相關宏定義即可,如下:
#define CONFIG_CMD_PING
4.5.4 移植及注冊網卡驅動
為了讓網卡初始化函數dm9000_pre_init在u-boot-2013-10/board/samsung/goni.c中的board_init函數下調用,添加dm9000_pre_init函數到goni.c下,將網卡相關的宏添加到s5p_goni.h下,并且添加上相應的頭文件。添加的宏如下:
#define CONFIG_DRIVER_DM9000 1
#define CONFIG_DM9000_BASE(0x88000300) //bank1基地址
#define DM9000_IO(CONFIG_DM9000_BASE) //IO基地址
#define DM9000_DATA(CONFIG_DM9000_BASE+4) //ADD2加4為了得到地址
在Linux的網卡驅動體系中,有一個數據結構(struct eth_device)用來封裝一個網卡的所有信息,系統(tǒng)中注冊一個網卡時就是要建立一個這個結構體的實例,然后填充這個實例中的各個元素,最后將這個結構體實例加入到eth_devices鏈表上,就完成了注冊[13]。在/drivers/net/dm9000x.c中的最后一個函數int dm9000_initialize(bd_t *bis),就是用來注冊dm9000網卡驅動的。自己定義一個board_eth_init函數用來做網卡驅動添加工作,即可以注冊驅動成功。在本次實驗中能使用tftp加載內核最終啟動Linux系統(tǒng)則說明網卡驅動移植成功。
當U-boot移植完以后,需要通過設置tftp加載ubuntu中編譯生成的內核鏡像和用nfs的方式遠程掛載ubuntu中制作好的根文件系統(tǒng)。配置好tftp和nfs以后,設置U-boot的bootargs,如根目錄所在路徑,主機IP,開發(fā)板IP,網關,串口,波特率等以支持nfs方式掛載rootfs,具體設置如下[13-14]:
setenv bootargs root=/dev/nfs
nfsroot=192.168.1.141:/root/porting_x210/rootfs
ip=172.17.64.188:172.17.64.167:172.17.64.1:255.255.255.0::eth0:off
init=/linuxrc console=ttySAC2,115200
設置完成后,成功加載內核和根文件系統(tǒng),整個Linux系統(tǒng)啟動起來。成功啟動U-boot如圖5所示,成功啟動Linux系統(tǒng)如圖6所示。

圖5 U-boot成功啟動

圖6 Linux系統(tǒng)成功啟動
在Linux系統(tǒng)中U-boot是必不可少的一部分,完成了包括cpu級和板級必要硬件設備的初始化,為操作系統(tǒng)的啟動提供了必要的條件和參數。在本次移植過程中,詳細闡述了U-boot的啟動過程,分析了U-boot的主Makefile和mkconfig,介紹了U-boot移植中主要幾個移植對象的移植方法。實驗結果表明,將官方U-boot移植到基于s5pv210的x210的開發(fā)板上可行,同時通過tftp的方式加載內核和nfs方式掛載啟動ext2的根文件系統(tǒng),從而啟動整個Linux系統(tǒng),為以后嵌入式系統(tǒng)開發(fā)提供必要環(huán)境。