U-BOOT是一個LINUX下的工程,在編譯之前必須已經安裝對應體系結構的交叉編譯環境,這裏只針對ARM,編譯器系列軟件爲arm-linux-*。
U-BOOT的下載地址: http://sourceforge.net/projects/u-boot
我下載的是1.1.6版本,一開始在FTP上下載了一個次新版,結果編譯失敗。1.1.6是沒問題的。
u-boot源碼結構
解壓就可以得到全部u-boot源程序。在頂層目錄下有18個子目錄,分別存放和管理不同的源程序。這些目錄中所要存放的文件有其規則,可以分爲3類。
第1類目錄與處理器體系結構或者開發板硬件直接相關;
第2類目錄是一些通用的函數或者驅動程序;
第3類目錄是u-boot的應用程序、工具或者文檔。
u-boot的源碼頂層目錄說明
目 錄 特 性 解 釋 說 明
board 平臺依賴 存放電路板相關的目錄文件,
例如:RPXlite(mpc8xx)、
smdk2410(arm920t)、
sc520_cdp(x86) 等目錄
cpu 平臺依賴 存放CPU相關的目錄文件
例如:mpc8xx、ppc4xx、
arm720t、arm920t、 xscale、i386等目錄
lib_ppc 平臺依賴 存放對PowerPC體系結構通用的文件,
主要用於實現PowerPC平臺通用的函數
lib_arm 平臺依賴 存放對ARM體系結構通用的文件,
主要用於實現ARM平臺通用的函數
lib_i386 平臺依賴 存放對X86體系結構通用的文件,
主要用於實現X86平臺通用的函數
include 通用 頭文件和開發板配置文件,
所有開發板的配置文件都在configs目錄下
common 通用 通用的多功能函數實現
lib_generic 通用 通用庫函數的實現
net 通用 存放網絡的程序
fs 通用 存放文件系統的程序
post 通用 存放上電自檢程序
drivers 通用 通用的設備驅動程序,主要有以太網接口的驅動
disk 通用 硬盤接口程序
rtc 通用 RTC的驅動程序
dtt 通用 數字溫度測量器或者傳感器的驅動
examples 應用例程 一些獨立運行的應用程序的例子,例如helloworld
tools 工具 存放製作S-Record或者u-boot格式的映像等工具,
例如mkimage
doc 文檔 開發使用文檔
u-boot的源代碼包含對幾十種處理器、數百種開發板的支持。可是對於特定的開發板,配置編譯過程只需要其中部分程序。這裏具體以S3C2410 & arm920t處理器爲例,具體分析S3C2410處理器和開發板所依賴的程序,以及u-boot的通用函數和工具。
編譯
以smdk_2410板爲例,編譯的過程分兩部:
# make smdk2410_config
# make
頂層Makefile分析
要了解一個LINUX工程的結構必須看懂Makefile,尤其是頂層的,沒辦法,UNIX世界就是這麼無奈,什麼東西都用文檔去管理、配置。首先在這方面我是個新手,時間所限只粗淺地看了一些Makefile規則。
以smdk_2410爲例,順序分析Makefile大致的流程及結構如下:
1) Makefile中定義了源碼及生成的目標文件存放的目錄,目標文件存放目錄BUILD_DIR可以通過make O=dir 指定。如果沒有指定,則設定爲源碼頂層目錄。一般編譯的時候不指定輸出目錄,則BUILD_DIR爲空。其它目錄變量定義如下:
#OBJTREE和LNDIR爲存放生成文件的目錄,TOPDIR與SRCTREE爲源碼所在目錄
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE := $(CURDIR)
TOPDIR := $(SRCTREE)
LNDIR := $(OBJTREE)
export TOPDIR SRCTREE OBJTREE
2)定義變量MKCONFIG:這個變量指向一個腳本,即頂層目錄的mkconfig。
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
在編譯U-BOOT之前,先要執行
# make smdk2410_config
smdk2410_config是Makefile的一個目標,定義如下:
smdk2410_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
unconfig::
@rm -f $(obj)include/config.h $(obj)include/config.mk /
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
顯然,執行# make smdk2410_config時,先執行unconfig目標,注意不指定輸出目標時,obj,src變量均爲空,unconfig下面的命令清理上一次執行make *_config時生成的頭文件和makefile的包含文件。主要是include/config.h 和include/config.mk文件。
然後才執行命令
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
MKCONFIG 是頂層目錄下的mkcofig腳本文件,後面五個是傳入的參數。
對於smdk2410_config而言,mkconfig主要做三件事:
在include文件夾下建立相應的文件(夾)軟連接,
#如果是ARM體系將執行以下操作:
#ln -s asm-arm asm
#ln -s arch-s3c24x0 asm-arm/arch
#ln -s proc-armv asm-arm/proc
生成Makefile包含文件include/config.mk,內容很簡單,定義了四個變量:
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
生成include/config.h頭文件,只有一行:
/* Automatically generated - do not edit */
#include "config/smdk2410.h"
mkconfig腳本文件的執行至此結束,繼續分析Makefile剩下部分。
3)包含include/config.mk,其實也就相當於在Makefile裏定義了上面四個變量而已。
4) 指定交叉編譯器前綴:
ifeq ($(ARCH),arm)#這裏根據ARCH變量,指定編譯器前綴。
CROSS_COMPILE = arm-linux-
endif
5)包含config.mk:
#包含頂層目錄下的config.mk,這個文件裏面主要定義了交叉編譯器及選項和編譯規則
# load other configuration
include $(TOPDIR)/config.mk
下面分析config.mk的內容:
@包含體系,開發板,CPU特定的規則文件:
ifdef ARCH #指定預編譯體系結構選項
sinclude $(TOPDIR)/$(ARCH)_config.mk # include architecture dependend rules
endif
ifdef CPU #定義編譯時對齊,浮點等選項
sinclude $(TOPDIR)/cpu/$(CPU)/config.mk # include CPU specific rules
endif
ifdef SOC #沒有這個文件
sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk # include SoC specific rules
endif
ifdef BOARD #指定特定板子的鏡像連接時的內存基地址,重要!
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
endif
@定義交叉編譯鏈工具
# Include the make variables (CC, etc...)
#
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
RANLIB = $(CROSS_COMPILE)RANLIB
@定義AR選項ARFLAGS,調試選項DBGFLAGS,優化選項OPTFLAGS
預處理選項CPPFLAGS,C編譯器選項CFLAGS,連接選項LDFLAGS
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS) #指定了起始地址TEXT_BASE
@指定編譯規則:
$(obj)%.s: %.S
$(CPP) $(AFLAGS) -o $@ $<
$(obj)%.o: %.S
$(CC) $(AFLAGS) -c -o $@ $<
$(obj)%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
回到頂層makefile文件:
6)U-boot需要的目標文件。
OBJS = cpu/$(CPU)/start.o # 順序很重要,start.o必須放第一位
7)需要的庫文件:
LIBS = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a /
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
LIBS := $(addprefix $(obj),$(LIBS))
.PHONY : $(LIBS)
根據上面的include/config.mk文件定義的ARCH、CPU、BOARD、SOC這些變量。硬件平臺依賴的目錄文件可以根據這些定義來確定。SMDK2410平臺相關目錄及對應生成的庫文件如下。
board/smdk2410/ :庫文件board/smdk2410/libsmdk2410.a
cpu/arm920t/ :庫文件cpu/arm920t/libarm920t.a
cpu/arm920t/s3c24x0/ : 庫文件cpu/arm920t/s3c24x0/libs3c24x0.a
lib_arm/ : 庫文件lib_arm/libarm.a
include/asm-arm/ :下面兩個是頭文件。
include/configs/smdk2410.h
8)最終生成的各種鏡像文件:
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
all: $(ALL)
$(obj)u-boot.hex: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
#這裏生成的是U-boot 的ELF文件鏡像
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e ''''''''''''''''''''''''''''''''s/.*/(__u_boot_cmd_.*/)/-u/1/p''''''''''''''''''''''''''''''''|sort|uniq`;/
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) /
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) /
-Map u-boot.map -o u-boot
分析一下最關鍵的u-boot ELF文件鏡像的生成:
@依賴目標depend :生成各個子目錄的.depend文件,.depend列出每個目標文件的依賴文件。生成方法,調用每個子目錄的make _depend。
depend dep:
for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
@依賴目標version:生成版本信息到版本文件VERSION_FILE中。
version:
@echo -n "#define U_BOOT_VERSION /"U-Boot " > $(VERSION_FILE); /
echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); /
echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion /
$(TOPDIR)) >> $(VERSION_FILE); /
echo "/"" >> $(VERSION_FILE)
@僞目標SUBDIRS: 執行tools ,examples ,post,post/cpu 子目錄下面的make文件。
SUBDIRS = tools /
examples /
post /
post/cpu
.PHONY : $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ all
@依賴目標$(OBJS),即cpu/start.o
$(OBJS):
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
@依賴目標$(LIBS),這個目標太多,都是每個子目錄的庫文件*.a ,通過執行相應子目錄下的make來完成:
$(LIBS):
$(MAKE) -C $(dir $(subst $(obj),,$@))
@依賴目標$(LDSCRIPT):
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
對於smdk2410,LDSCRIPT即連接腳本文件是board/smdk2410/u-boot.lds,定義了連接時各個目標文件是如何組織的。內容如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :/*.text的基地址由LDFLAGS中-Ttext $(TEXT_BASE)指定*/
{ /*smdk2410指定的基地址爲0x33f80000*/
cpu/arm920t/start.o (.text) /*start.o爲首*/
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
@執行連接命令:
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) /
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) /
-Map u-boot.map -o u-boot
其實就是把start.o和各個子目錄makefile生成的庫文件按照LDFLAGS連接在一起,生成ELF文件u-boot 和連接時內存分配圖文件u-boot.map。
9)對於各子目錄的makefile文件,主要是生成*.o文件然後執行AR生成對應的庫文件。如lib_generic文件夾Makefile:
LIB = $(obj)libgeneric.a
COBJS = bzlib.o bzlib_crctable.o bzlib_decompress.o /
bzlib_randtable.o bzlib_huffman.o /
crc32.o ctype.o display_options.o ldiv.o /
string.o vsprintf.o zlib.o
SRCS := $(COBJS:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS))
$(LIB): $(obj).depend $(OBJS) #項層Makefile執行make libgeneric.a
$(AR) $(ARFLAGS) $@ $(OBJS)
整個makefile剩下的內容全部是各種不同的開發板的*_config:目標的定義了。
概括起來,工程的編譯流程也就是通過執行執行一個make *_config傳入ARCH,CPU,BOARD,SOC參數,mkconfig根據參數將include頭文件夾相應的頭文件夾連接好,生成 config.h。然後執行make分別調用各子目錄的makefile 生成所有的obj文件和obj庫文件*.a. 最後連接所有目標文件,生成鏡像。不同格式的鏡像都是調用相應工具由elf鏡像直接或者間接生成的。
剩下的工作就是分析U-Boot源代碼了。
限於個人水平,有謬誤之處,歡迎指正交流。。
從U-Boot源碼看C語言對彙編代碼中的符號引用[轉]
aaronwong: u-boot中代碼的疑問(_armboot_start與_start)?
---------------------------
我使用的是u-boot-1.3.0-rc2。在cpu/pxa/start.S中,有如下的標號定義:
_TEXT_BASE:
.word TEXT_BASE /*uboot映像在SDRAM中的重定位地址,我設置爲0xa170 0000 */
.globl _armboot_start
_armboot_start:
.word _start /*_start是程序入口,鏈接完畢它的值應該是0xa170 0000=TEXT_BASE*/
/* 這句話的意思應該是在_armboot_start標號處,保存了_start的值,也就是說,_armboot_start是存放_start的地址,該地址對應的存儲單元內容是0xa170 0000*/
/*
* These are defined in the board-specific linker script. 下面的定義與上面應該是一個意思。
*/
.globl _bss_start
_bss_start:
.word __bss_start
======================
按照上面的理解,__bss_start是uboot 的bss段起始地址,那麼uboot映像的大小就是__bss_start - _start;在relocate代碼段中計算uboot的大小時,也體現了這一點。
實際上,_armboot_start並沒有實際意義,它只是在"ldr r2, _armboot_start"中用來尋址_start的值而已,_bss_start也是一樣的道理,真正有意義的應該是_start和 __bss_start本身。
但是,令我不解的是,在C入口函數start_armboot()中(對應文件爲lib_arm/board.c),有如下代碼:
void start_armboot (void)
{
.........
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); //第一句話
..........
monitor_flash_len = _bss_start - _armboot_start; //第二句話
...............
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //第三句話
..........
}
==============================================
按照上面的理解,_armboot_start與_bss_start都是沒有實際意義的,它們只是一個地址,有實際意義的是地址中的內容_start和 __bss_start(雖然也還是地址)。象第一句話,其“意圖”很明顯,是把gd作爲全局數據結構體的指針,並初始化爲“SDRAM中的uboot起始地址(即TEXT_BASE)-CFG_MALLOC_LEN-全局數據結構體大小”。
要實現這個“意圖”,應該是寫成:gd = (gd_t*)(_start - CFG_MALLOC_LEN - sizeof(gd_t));或者gd = (gd_t*)(TEXT_BASE- CFG_MALLOC_LEN - sizeof(gd_t));纔對阿?用_armboot_start來作運算應該是沒有任何意義纔對!?
第二句話也是一樣的道理,它的意圖是要計算u-boot映像的大小,應該寫成__bss_start - _start纔對阿?
我使用readelf工具查看編譯所得到的uboot映像文件得到信息如下:
[aaronwong@localhost build]$ readelf -s u-boot|grep _start
1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start
1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start
1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start
1197: a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
上面我刪除了與該討論無關的包含“_start""t的標號信息。
顯然,我前面的理解應該是正確的(_start=TEXT_BASE=0xa170 0000)。那麼u-boot源代碼中的monitor_flash_len=_bss_start - _armboot_start=0xa1700048 - 0xa1700044 = 4,有什麼意義??
迷茫中,期盼大蝦指點迷津,謝謝~!!!
eltshan: [Re: aaronwong]
-----------------
1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start
1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start
1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start
1197: a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
我想:
_start所在的地址是a1700000,
_armboot_start 所在的地址是a1700044,
那麼 根據這句:
_armboot_start: .word _start
所以_armboot_start的值應該是a1700000
所以
monitor_flash_len = _bss_start - _armboot_start = a171b070 - a1700000 = 1b070
而不是你說的 = 4
以上個人意見.
aaronwong: [Re: eltshan]
-------------------
謝謝,eltshan!你的理解是正確的,不過我看了之後還是沒能想得很明白,因爲我在想,按你所說,那麼_start的值應該是多少呢?難道是“b reset”這條指令的機器碼?所以我對ELF格式的u-boot映像文件作了反彙編,分析之後終於找到了癥結所在。以下是部分分析過程,首先是反彙編:
arm-iwmmxt-linux-gnueabi-objectdump -D u-boot > u-boot.s
並提取了monitor_flash_len = _bss_start - _armboot_start;這條語句相關的反彙編代碼如下:
==============================
a1700044 <_armboot_start>:
a1700044: a1700000 .word 0xa1700000
a1700048 <_bss_start>:
a1700048: a171b070 .word 0xa171b070
a171b070 :
a171b070: 00000000 .word 0x00000000
.....
a1700f40: e59f41d0 ldr r4, [pc, #464] ; a1701118
//r4=[a1701118]=a1700044
.....
a1700f7c: e59f3198 ldr r3, [pc, #408] ; a170111c
//r3=[a1700044]=a1700048
a1700f80: e5942000 ldr r2, [r4]
//r2=[a1700044]=a1700000
a1700f84: e59f4194 ldr r4, [pc, #404] ; a1701120
//r4=[a1701120]=a1719d24
a1700f88: e5933000 ldr r3, [r3]
//r3=[a1700048]=a171b070
a1700f8c: e0623003 rsb r3, r2, r3
//r3= r3-r2 = a171b070-a1700000 = 1b070;
a1700f90: e59f218c ldr r2, [pc, #396] ; a1701124
//r2=[a1701124]=a171b070
a1700f94: e5823000 str r3, [r2]
//monitor_flash_len=[r2]=r3=1b070
......
a1701118: a1700044 .word 0xa1700044
a170111c: a1700048 .word 0xa1700048
a1701120: a1719d24 .word 0xa1719d24
a1701124: a171b070 .word 0xa171b070
========================================
上面//是我自己的註釋。這表明,你的理解的確是正確的。
經過這個過程之後,我終於認識到自己的誤解在哪裏了。原來,我是把"彙編語言中LDR僞指令對符號的引用"與"C語言中對彙編程序中符號/常量/變量的引用"搞混淆了。我想說明以下幾點:
(1) readelf以及u-boot.map和System.map所給出的符號表中符號的值,實際上是表示符號所在的地址,而不是指符號本身的值。
(2) 彙編語言中沒有指針的概念,因此對符號的引用是"赤裸裸"的。例如:
==========
.globl _armboot_start
_armboot_start: .word _start
ldr r2, _armboot_start
==========
實際上反彙編以後是:
============
a1700044 <_armboot_start>:
a1700044: a1700000 .word 0xa1700000
a1700074: e51f2038 ldr r2, [pc, #-56] ; a1700044 <_armboot_start>
============
也就是說,_armboot_start是一個地址0xa1700044,其中的內容是0xa1700000,上面對_armboot_start的引用是直接將其替換爲其表示的地址0xa1700044,而非其中的內容0xa1700000。這就是"赤裸裸"的引用。
(3) C語言則不同,對變量/符號/常量的引用必須要通過地址來尋址,不管是全局變量還是局部變量,不同的是局部變量在生命期結束後,所佔的地址空間會被釋放而已。即使是函數調用時的參數傳遞,雖然是將實參的值"拷貝"給形參,但"拷貝"的過程也是通過實參和形參的地址來對兩者進行訪問的。
所以,在C語言中的 "monitor_flash_len = _bss_start - _armboot_start" 這句話中對_armboot_star的引用,實際上是把它用作了指針,把它作爲訪問對象的地址來使用,通過這個地址即a1700044 來訪問對應存儲空間所存放的內容亦即0xa1700000,_bss_start也是同樣的道理。所以這句話實際上是monitor_flash_len =[a1700048]-[0xa1700044]=a171b070-a1700000 = 1b070,這樣就得到了正確的結果。
現在,我們再回答最前面的問題:_start的值是什麼?_start 表示地址0xa1700000 ,在彙編語言中,對_start的"絕對引用"(這裏是與用相對尋址進行跳轉進行區別)就是將其替換爲0xa1700000,但其中存放的內容的的確確就是"b reset"這條指令的機器碼,所以如果在C語言中引用_start,得到的結果反而就是這個指令的機器碼了。其實這個問題很簡單,只是和C語言的引用攪在一起,一些概念被偷換了而已。
以上是我的個人理解,有什麼不對,還請指正。
再次感謝eltshan的提點。
U-Boot的移植之(一)基礎篇:添加新的目標板定義
U-Boot的移植之(一)基礎篇:添加新的目標板定義
本文使用最新的U-Boot-1.3.0-rc2。
U-Boot本身支持很多開發板,在其源代碼中,每個板子都對應一個board/目錄下的文件夾(筆者注:這並不確切,因爲有的文件夾是供應商名稱,下面可以有多個目標板目錄,這裏只考慮最簡單的情況),以及include/configs/目錄下的目標板配置頭文件。因此,要添加U-Boot對我們的目標板的支持,首先就是要建立目標板文件夾和配置頭文件,並修改相關的Makefile。
下面以實例說明爲U-Boot添加新的目標板定義的步驟和過程。
(1)在board/目錄下建立目標板目錄。
筆者的目標板是XSBASE270,處理器是PXA270。由於U-Boot中本身支持很多開發板和處理器,可以從中找出與自己處理器型號相同或相近的開發板,在此基礎上再做後續修改。
adsvix使用的也是PXA27x處理器,因此可以把它作爲模板。
cd board/
cp -arv adsvix xsbase270
mv xsbase270/adsvix.c xsbase270/xsbase270.c
(2)在include/configs/目錄下建立目標板配置頭文件。
cd include/configs/
cp adsvix.h xsbase270.h
(3)修改Makefile。
一是要在總的Makefile(U-Boot源碼頂層目錄下)中加入目標板的編譯配置選項,這也可以參考adsvix的進行修改,只要把目標板名稱改換爲xsbase270即可:
adsvix_config: unconfig
@$(MKCONFIG) $(@:_config=) arm pxa adsvix
xsbase270_config: unconfig
@$(MKCONFIG) $(@:_config=) arm pxa xsbase270
這裏xsbase270與board/目錄下目標板文件夾名稱xsbase270一致。
另外,還需要注意,該Makefile中定義了CROSS_COMPILE的值,以在交叉編譯時指定交叉編譯器。缺省情況下對ARM的CROSS_COMPILE定義如下:
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-
endif
即定義交叉編譯器名的前綴爲arm-linux-,如果您使用的toolchain的名字不同,則需要作相應修改。例如筆者使用的是arm-iwmmxt-linux-gnueabi-gcc,因此要將上面改爲:CROSS_COMPILE = arm-iwmmxt-linux-gnueabi- 。
二是要修改board/xsbase270下的Makefile。
#COBJS := adsvix.o pcmcia.o
COBJS := xsbase270.o pcmcia.o
這是因爲前面將該目錄下的源文件adsvix.c改爲了xsbase270.c。
至此,將新的目標板xsbase270的定義添加到U-Boot中的工作就算完成了。下面的命令可以編譯得到xsbase270的U-Boot:
# assuming you are at the top directory of u-boot
# define a build directory to keep object files during make process and also finally u-boot image
export BUILD_DIR=~/u-boot_xsbase270/build/
make xsbase270_config
# if you edit your source file and want to make again, just type “make distclean”and then call the above commands again.
make
當然,要使編譯出來的這個u-boot能真正適用於我們的目標板,還有很多工作要做,包括處理器工作狀態、存儲器映射設置、網卡驅動的移植等等。所以,本篇的標題只是在U-Boot中添加對新目標板的“定義”,而非對新目標板的“支持”,這些工作需要對U-Boot的源代碼有整體的認識,並結合自己的目標板的特性來完成。後續的篇章將繼續介紹後面的內容。
作爲本篇的補充內容,您也許仍有必要了解以下要點:
(1) 在MAKEALL文件中可以將新的目標板xsbase270添加到下面的list中:
#########################################################
## Xscale Systems
#########################################################
LIST_pxa=" /
adsvix /
cerf250 /
cradle /
csb226 /
delta /
innokom /
lubbock /
pleb2 /
pxa255_idp /
wepep250 /
xaeniax /
xm250 /
xsengine /
zylonite /
"
這並不是必須的,因爲MAKEALL文件只用於爲其中的所有目標板都編譯一個u-boot時使用。
(2) 如何在U-Boot已有的目標板中找到與自己的目標板相近的目標板?
首要的是要找到與自己的目標板所用的處理器相同或統一系列的的目標板。在頂層目錄下的Makefile中有各個板子的config列表,例如XScale系列的板子列表如下:
#########################################################
## XScale Systems
#########################################################
adsvix_config : unconfig
@$(MKCONFIG) $(@:_config=) arm pxa adsvix
xsbase270_config: unconfig
@$(MKCONFIG) $(@:_config=) arm pxa xsbase270
cerf250_config : unconfig
@$(MKCONFIG) $(@:_config=) arm pxa cerf250
cradle_config : unconfig
@$(MKCONFIG) $(@:_config=) arm pxa cradle
csb226_config : unconfig
@$(MKCONFIG) $(@:_config=) arm pxa csb226
delta_config :
@$(MKCONFIG) $(@:_config=) arm pxa delta
# ..... 以下省略。
(3) 修改目標板的編譯優化選項。
在cpu/pxa/config.mk文件中定義了目標板的編譯優化選項PLATFORM_RELFLAGS和
PLATFORM_CPPFLAGS,您可以根據自己的需要進行修改。
筆者的交叉編譯器arm-iwmmxt-linux-gnueabi-gcc默認有-march=iwmmxt,遵循新的ARM EABI標準,但仍要保留PLATFORM_CPPFLAGS中的“-mapcs-32,-mabi=apcs-gnu”選項,使用舊的ABI標準來編譯,因爲u-boot的彙編代碼並非按照新的ABI規範編寫。可使用-march=armv5te來避免”warning: target CPU does not support interworking”警告。
如果編譯過程中出現了關於IDE方面的錯誤,應修改include/configs/xsbase270.h,註釋掉”#define CONFIG_CMD_IDE”這一行,以禁止編譯IDE的操作命令,因爲在目標板啓動階段不需要對IDE接口進行其他操作。
U-Boot的移植之(二)進階篇:從源代碼看系統啓動過程(一)
U-Boot的移植之(二)進階篇:從源代碼看系統啓動過程
爲什麼要分析源代碼?分析優秀的源代碼本身就是一個學習的過程,也是進行深入研究的必經之路。不過在此我們的主要目的並非要研究U-boot或Bootloader技術本身,而僅僅是爲了成功的並且恰當的將U-Boot移植到我們的開發板上。只有結合源代碼瞭解了U-boot的系統引導過程,才能在移植和調試過程中保持清晰的思路,才能在碰到困難和問題時從根本上加以解決。
在動手分析之前,至少應該對U-Boot的源代碼結構有基本的瞭解,很多參考書都有這方面的介紹,華清遠見的《嵌入式Linux系統開發技術詳解——基於ARM》的講解就比較清晰。
本文以lubbock開發板爲例,以系統啓動的流程爲線索進行縱向分析:後續的移植工作也將以此開發板爲模板。Lubbock使用PXA255處理器。
首先要找到程序入口點。從board/lubbock/u-boot.lds可以發現,u-boot的程序入口爲_start,在cpu/pxa/start.o當中。因此首先要分析start.S程序,U-Boot中所有的PXA系列的處理器都從這裏開始執行第一條語句。
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
0x0地址開始是ARM異常向量表,學過ARM體系結構與編程的都明白,非常簡單,不多廢話。一上電的第一條指令是跳轉到reset復位處理程序:
reset:
/* 進入SVC模式 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit /* we do sys-critical inits */
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:
......
一般不要定義CONFIG_SKIP_LOWLEVEL_INIT,因此,接下來跳轉到cpu_init_crit處開始執行:
cpu_init_crit:
/* 屏蔽所有中斷 */
/* 設置時鐘源,關閉除FFUART,SRAM,SDRAM,FLASH以外的外設時鐘 */
......
#ifdef CFG_CPUSPEED
ldr r0, CC_BASE /* 時鐘控制寄存器基址 */
ldr r1, cpuspeed
/* cpuspeed: .word CFG_CPUSPEED */
str r1, [r0, #CCCR]
mov r0, #2
mcr p14, 0, r0, c6, c0, 0
setspeed_done:
#endif /* CFG_CPUSPEED */
/* 跳轉到lowlevel_init,這裏ip即r12,用作暫存寄存器 */
mov ip, lr
bl lowlevel_init
mov lr, ip
/* Memory interfaces are working. Disable MMU and enable I-cache. */
ldr r0, =0x2001
......
/* 關閉MMU,使能I-Cache(可選) */
mov pc, lr /* 這裏是從cpu_init_crit返回到relocate標號 */
可見,在cpu_init_crit中的主要工作是設置時鐘,配置處理器主頻(這時CPU的工作頻率還沒有改變),調用lowlevel_init函數進行底層初始化(包括調整處理器工作頻率、系統總線頻率、存儲器時鐘頻率以及存儲系統的初始化等工作),隨後關閉MMU並使能I-Cache,再返回。
lowlevel_init函數在board/lubbock/lowlevel_init.S中定義,其流程都是按照PXA27X的開發手冊來的,所以不再贅述。僅指出,其中的寄存器在include/asm-arm/arch-pxa/pxa-regs.h頭文件中定義,寄存器初始化值在include/configs/lubbock.h中定義。另外,在後面的實際移植工作中,由於目標板XSBASE270使用的PXA270處理器,可使用adsvix開發板的lowlevel_init.S文件(lubbock中沒有開啓turbo模式)。
接着程序的執行線索進行分析。從cpu_init_crit返回後就開始relocate(重定位),即將U-boot從FLASH存儲器搬運到SDRAM中TEXT_BASE開始的存儲空間(TEXT_BASE在board/lubbock/config.mk中定義),並初始化堆棧(清零.bss段),以在SDRAM中開始進入到Bootloader stage 2的C程序入口。Relocate部分開始的代碼如下:
/* 之前已定義的部分變量有:
_TEXT_BASE: .word TEXT_BASE
_armboot_start: .word _start
_bss_start: .word __bss_start
_bss_end: .word _end */
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start /* 讀入_start到r2 */
ldr r3, _bss_start /* 讀入__bss_start到r3 */
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
_start_armboot: .word start_armboot
這是很經典的一段代碼,相信學習凡是過ARM編程的,都分析過這段代碼,所以也不再贅述。之所以列出這段代碼,一是爲了找到C程序入口start_armboot,二是爲了給出U-Boot的一個存儲器映射圖
s
U-Boot的移植之(二)進階篇:從源代碼看系統啓動過程(二)
接下來進入到Bootloader Stage 2即C語言代碼部分,入口是start_armboot,對應的源文件是lib_arm/board.c,這一文件對所有的ARM處理器都是通用的,因此在移植的時候不用修改。相關源代碼如下:
DECLARE_GLOBAL_DATA_PTR;
/* 在include/asm-arm/global_data.h中定義的一個全局寄存器變量的聲明:
* #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
* 用於存放全局數據結構體gd_t的地址。
*/
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
/* 本次移植暫不配置VFD和LCD,後面也將不考慮的部分略去 */
/* 初始化全局數據結構體指針gd */
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
....../* memset在lib_generic/string.c中定義*/
memset ((void*)gd, 0, sizeof (gd_t)); /*用0填充全局數據表*gd */
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t)); /*用0填充(初始化) *gd->bd */
monitor_flash_len = _bss_start - _armboot_start;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang (); /* 打印錯誤信息並死鎖 */
}
}
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init (); /* drivers/cfi_flash.c或自定義 */
display_flash_config (size);
#endif /* CFG_NO_FLASH */
/*armboot_start is defined in the board-specific linker script*/
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
......
/* initialize environment */
env_relocate ();
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
devices_init (); /* get the devices list going. */
.......
jumptable_init ();
console_init_r (); /* fully init console as a device */
enable_interrupts (); /* enable exceptions */
/* Perform network card initialisation if necessary */
#if defined(CONFIG_DRIVER_SMC91111)| |defined (CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr"))
smc_set_mac_addr(gd->bd->bi_enetaddr);
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL)
copy_filename (BootFile, s, sizeof (BootFile));
#endif
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
......
/*main_loop() can return to retry autoboot, if so just run it again.*/
for (;;) {
main_loop ();
}
}
gd_t是全局數據表類型,在include/asm-arm/global_data.h中定義如下:
/*
Keep it *SMALL* and remember to set CFG_GBL_DATA_SIZE > sizeof(gd_t)
*/
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /*Checksum of Environment valid?*/
unsigned long fb_base; /* base address of frame buffer */
.......
void **jt; /* jump table */
} gd_t;
其中,bd_t在include/asm-arm/u-boot.h中定義如下:
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params*/
struct /* RAM configuration */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
jt是函數數組指針,隨後將在jumptable_init()函數中初始化。
從lib_arm/board.c的源碼不難分析出系統的啓動流程:首先初始化全局數據表,然後順序執行函數指針數組init_sequence中的一系列初始化函數——由其在本文件中的相關定義可得知初始化流程:
typedef int (init_fnc_t) (void);
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup -- cpu/pxa/cpu.c */
board_init, /* basic board setup --board/lubbock/lubbock.c */
interrupt_init, /* set up exceptions -- cpu/pxa/interrupts.c */
env_init, /* initialize environment -- common/env_flash.c */
init_baudrate, /* initialze baudrate settings--lib_arm/board.c */
serial_init, /* serial communications setup--cpu/pxa/serial.c */
console_init_f, /* stage 1 init of console -- common/console.c */
display_banner, /* say that we are here -- lib_arm/board.c */
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
dram_init, /* configure available RAM banks --board/lubbock/lubbock.c */
display_dram_config, /* lib_arm/board.c */
NULL,
};
在執行這個函數序列的過程中,任何一個函數異常返回都會導致u-boot“死鎖”或說“掛起”在hang()函數的死循環當中。
若一切順利,接下來就調用flash_init()函數初始化CFI FLASH(針對NOR型閃存而言),該函數在drivers/cfi_flash.c中定義,不過,只有在目標板頭文件中”#define CFG_FLASH_CFI_DRIVER”之後該驅動纔會被編譯;在lubbock的u-boot實現當中,include/configs/lubbock.h中沒有定義CFG_FLASH_CFI_DRIVER,而是在board/lubbock/ flash.c中實現了自己的FLASH驅動,包括flash_init()在內。在移植U-Boot時,可以根據實際情況選擇使用U-Boot自帶的FLASH驅動還是自己編寫新的驅動。如果配置了NAND閃存,還會對其進行初始化;筆者的XSABSE270板沒有焊接NAND FLASH,故對此不作討論。
接下來調用env_relocate()函數初始化環境變量,該函數在common/env_common.c文件中定義。在同一文件中可以發現還定義了一個字符數組default_environment[],用於描述缺省的環境變量,這些都要在include/configs/lubbock.h頭文件中進行設置,包括啓動命令CONFIG_BOOTCOMMAND,波特率CONFIG_BAUDRATE,IP地址CONFIG_IPADDR等等。
然後是獲取自設置的目標板的網絡地址,包括IP地址和MAC地址。
再然後是調用common/devices.c中定義的devices_init()函數來創建設備列表,並初始化相應的設備,主要是”stdin”,”stdout”,”stderr”以及自定義的設備如I2C,LCD等。這些相關代碼是與平臺無關的,因此從移植的角度考慮,不必作細緻的研究與分析。
接着調用common/exports.c中定義的jumptable_init()函數,初始化全局數據表中的跳轉表gd->jt,跳轉表是一個函數指針數組,定義了u-boot中基本的常用的函數庫;而gd->jt是這個函數指針數組的首指針。部分代碼如下:
void jumptable_init (void) {
int i;
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy;
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
......
}
上面的XF_get_version, XF_malloc, XF_free等在include/exports.h的枚舉變量中定義,因此,實際上是作爲”Label式整型序號”使用,即XF_get_version=1, XF_malloc=2, XF_free=3 ...,相關代碼如下:
enum { /* include/exports.h */
#define EXPORT_FUNC(x) XF_ ## x ,
#include <_exports.h>
#undef EXPORT_FUNC
XF_MAX
};
EXPORT_FUNC(get_version)
EXPORT_FUNC(getc)
EXPORT_FUNC(tstc)
EXPORT_FUNC(putc)
EXPORT_FUNC(puts)
EXPORT_FUNC(printf)
......... /* include/_exports.h */
由於這些也是平臺無關的代碼,因此在移植過程中也不必深究。
然後是調用common/console.c中定義的函數console_init_r()初始化串口控制檯,這同樣是平臺無關的代碼,所以不必關心。
這時U-Boot的基本功能已經初始化完畢,便可開中斷,並進行附加功能的配置與初始化,包括網卡驅動配置,目標板使用LAN91C1111網卡,對應SMC91111網卡驅動,可以根據需要配置其他的網卡驅動如CS8900等,這些都在include/configs/lubbock.h中定義。
然後是調用board/lubbock/lubbock.c中定義的board_late_init()函數進行板級的後期初始化,實際上是配置stdout和stderr的硬件設備。相關源代碼如下:
int board_late_init(void)
{
setenv("stdout", "serial");
setenv("stderr", "serial");
return 0;
}
最後需要注意的一個很重要的文件是lib_arm/armlinux.c,它實現的功能包括設置內核啓動參數,並負責將這些參數傳遞給內核,最後跳轉到Linux內核入口函數,將控制權交給內核。
具體傳遞哪些參數,是通過在include/configs/lubbock.c中指定條件編譯選項來控制的,對應於lib_arm/armlinux.c中的部分源代碼形式如下:
#if defined (CONFIG_SETUP_MEMORY_TAGS) || /
defined (CONFIG_CMDLINE_TAG) || /
defined (CONFIG_INITRD_TAG) || /
defined (CONFIG_SERIAL_TAG) || /
defined (CONFIG_REVISION_TAG)
static void setup_start_tag (bd_t *bd);
# ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd);
# endif
static void setup_commandline_tag (bd_t *bd, char *commandline);
# ifdef CONFIG_INITRD_TAG
static void setup_initrd_tag (bd_t *bd, ulong initrd_start,
ulong initrd_end);
# endif
static void setup_end_tag (bd_t *bd);
static struct tag *params;
#endif
......
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
......
void (*theKernel)(int zero, int arch, uint params);
......
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
......
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
關於這個參數列表中各個參數的定義及含義,以及參數列表的初始化過程,可以參考Booting ARM Linux一文。內核是如何找到這個參數列表在內存中的位置,以接收這些參數的呢?實際上,參數列表(tag list)在內存中的起始地址會保存在通用寄存器R2中,並傳遞給內核。而按照習慣或說慣例,通常tag list的首地址(物理地址)會設置爲RAM起始地址+ 0x100偏移量,因此R2的值實際上是確定不變的。另外,還要正確設置R0和R1的值,在呼叫內核時,R0的值應爲0,R1中則應保存機器類型(machine type)編號。R0,R1和R2都會作爲參數傳遞給內核。
在上面的代碼中,定義了一個函數指針theKernel,通過倒數第二條語句將內核入口地址賦給theKernel(hdr是include/image.h中定義的一個image_header結構體類型的數據,hdr->ih_ep中保存了內核入口地址,ntohl的功能是字節順序的大小端轉換,相關代碼可以參考tools/mkimage.c),最後,根據APCS規則,將0, bd->bi_arch_number, bd->bi_boot_params 依次作爲參數通過R0,R1和R2傳遞給theKernel函數,並進入內核啓動部分。
至此,我們已經從源代碼入手簡要分析了U-Boot的啓動流程,在這個過程中,我們對前一篇文章“添加新的目標板定義”也有了更進一步的理解和認識:爲什麼要添加這些文件;哪些文件是平臺相關的並且必須要根據平臺特性進行修改的;哪些文件是平臺無關的,是不需要修改的,只需在頭文件中作適當配置即可。
下一節,我們將給出移植U-Boot到XSBASE270開發板的實例。
U-Boot的移植之(三)實戰篇:移植U-Boot到XSBASE270開發板(一)
U-Boot的移植之(三)實戰篇:移植U-Boot到XSBASE270開發板
1. 在U-Boot中添加XSBASE270目標板的定義
具體方法可參考第一節,本篇給出部分細節和要點,假定$U-BOOT爲源碼根目錄。
############################################################
# (1)建立目標板目錄
# 其中lowlevel_init.S採用adsvix的文件,以開啓turbo mode,並註釋掉
# 其中對pxavoltage.S文件中initPXAvolatage函數的調用。
############################################################
cd board/
cp -arv lubbock xsbase270
mv xsbase270/lubbock.c xsbase270/xsbase270.c
cp adsvix/lowlevel_init.S xsbase270/
vim xsbase270/lowlevel_init.S
@setvoltage:
@ mov r10, lr
@ bl initPXAvoltage
@ mov lr, r10
############################################################
# (2)建立目標板配置頭文件
############################################################
cd $U-BOOT/include/configs
cp lubbock.h xsbase270.h
############################################################
# (3)修改Makefile
############################################################
#########在$U-BOOT/Makefile中添加:
xsbase270_config: unconfig
@$(MKCONFIG) $(@:_config=) arm pxa xsbase270
#########在$U-BOOT/Makefile中修改CROSS_COMPILE:
CROSS_COMPILE = arm-iwmmxt-linux-gnueabi-
#########在$U-BOOT/board/xsbase270/Makefile中修改:
#COBJS := lubbock.o flash.o
COBJS := xsbase270.o
這裏去掉了flash.c文件,因爲它是在lubbock板中自定義的FLASH存儲器驅動,lubbock不使用U-Boot自帶的FLASH驅動;而在本次移植中,我們將使用U-Boot自帶的drivers/cfi_flash.c作爲XSBASE270開發板的NOR型閃存28F128K18C的驅動程序,具體過程後述。
實際移植過程中還可能要作如下改動:
#############################################################
# (1) cpu/pxa/config.mk
#############################################################
#armv5-->armv5te, modified by aaron wong
PLATFORM_CPPFLAGS += -march=armv5te -mtune=xscale
#############################################################
# (2) include/asm-arm/mach-types.h
#############################################################
/* added by aaron */
#define MACH_TYPE_XSBASE270 1141
編譯U-Boot:
export BUILD_DIR=~/u-boot_xsbase270/build/
make xsbase270_config
make
作其他必要修改,直至能正常編譯通過。然後再進行後續的針對目標板的定製步驟。
2. 修改U-Boot Stage 1(彙編級)的平臺相關代碼
U-Boot第一階段的代碼包括:
(1) cpu/pxa/start.S (平臺無關,處理器架構相關)
(2) board/xsbase270/lowlevel_init.S (平臺與處理器型號相關)
(3) board/xsbase270/config.mk (平臺相關,設置TEXT_BASE)
(4) include/configs/xsbase270.h (平臺相關,設置寄存器初值等)
lowlevel_init.S已在第一步作了相應修改。config.mk中設置TEXT_BASE(U-Boot的鏈接起始地址),暫時不改動(0xa3080000)。
xsbase270.h中定義了系統初始化時的寄存器初值(主要是GPIO配置,時鐘與處理器頻率設置,片上存儲器控制器與存儲系統的初始化),這需要根據平臺進行配置。下面給出部分代碼示例及註釋:
/*
* High Level Configuration Options (easy to change)
*/
#define CONFIG_PXA27X 1 /*to keep PXA27x specific code*/
#define CONFIG_XSBASE270 1
#define BOARD_LATE_INIT 1
#undef CONFIG_USE_IRQ /* we don't need IRQ/FIQ stuff */
......
/*
* Size of malloc() pool
*/
#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024)
#define CFG_GBL_DATA_SIZE 128
/*
* Stack sizes
* The stack sizes are set up in start.S using the settings below
*/
#define CONFIG_STACKSIZE (128*1024) /* regular stack */
#ifdef CONFIG_USE_IRQ
#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */
#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */
#endif
/*
* Miscellaneous configurable options
*/
#define CFG_CPUSPEED 0x207 /* cpu start-up frequency,91MHz */
/*
* GPIO settings
*/
#define CFG_GPSR0_VAL 0x00003000
#define CFG_GPSR1_VAL 0x00000000
#define CFG_GPSR2_VAL 0x00010000
#define CFG_GPSR3_VAL 0x00020000
#define CFG_GPCR0_VAL 0x00000800
......
/*
* Clock settings
*/
#define CFG_CKEN 0x00400200
#define CFG_CCCR 0x08000290 /* 520 MHz */
/*
* Memory settings
*/
#define CFG_MSC0_VAL 0x7FF82BD0
#define CFG_MSC1_VAL 0x7FF87FF8
#define CFG_MSC2_VAL 0x7FF87FF8
#define CFG_MDCNFG_VAL 0x00001AC9
#define CFG_MDREFR_VAL 0x0000001E
#define CFG_MDMRS_VAL 0x00000000
......
/*
* PCMCIA and CF Interfaces
*/
#define CFG_MECR_VAL 0x00000001
#define CFG_MCMEM0_VAL 0x00010504
#define CFG_MCMEM1_VAL 0x00010504
......
3. 修改U-Boot Stage 2(C語言級)的平臺相關代碼
U-Boot第二階段的大部分代碼是平臺無關的。從移植的角度,我們僅需要關注下面一些平臺相關的代碼:
(1) include/configs/xsbase270.h:通過使用定義或取消定義相關的預編譯變量,用於對平臺無關的代碼進行平臺相關的定製,包括定製U-Boot命令、缺省的環境變量、存儲器映射、串口控制檯配置、驅動程序等。
(2) board/xsbase270/xsbase270.c:板級初始化,只需進行最基本的配置,包括設置mach-type,啓動參數列表首地址,設置標準輸入輸出設備,獲取系統RAM配置信息等。
(3) 驅動程序的移植。最基本的是FLASH存儲器驅動程序和以太網卡驅動程序。對於U-Boot中已經支持的器件,可以進行簡單移植,否則需要自己加入相關的設備驅動程序。
下面對以上三部分分別闡述。
U-Boot的移植之(三)實戰篇:移植U-Boot到XSBASE270開發板2
3.1 配置xsbase270.h
可以參考lubbock.h,adsvix.h等相關開發板的設置,另外也可以從U-Boot源碼的README文件獲取更多信息。
(1) 存儲器映射配置:
/*
* Physical Memory Map
*/
#define CONFIG_NR_DRAM_BANKS 4 /* we have 2 banks of DRAM*/
#define PHYS_SDRAM_1 0xa0000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
#define PHYS_SDRAM_2 0xa4000000 /* SDRAM Bank #2 */
#define PHYS_SDRAM_2_SIZE 0x00000000 /* 0 MB */
#define PHYS_SDRAM_3 0xa8000000 /* SDRAM Bank #3 */
#define PHYS_SDRAM_3_SIZE 0x00000000 /* 0 MB */
#define PHYS_SDRAM_4 0xac000000 /* SDRAM Bank #4 */
#define PHYS_SDRAM_4_SIZE 0x00000000 /* 0 MB */
#define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */
#define PHYS_FLASH_2 0x04000000 /* Flash Bank #2 */
#define PHYS_FLASH_SIZE 0x02000000 /* 32 MB */
#define PHYS_FLASH_BANK_SIZE 0x02000000 /* 32 MB Banks */
#define PHYS_FLASH_SECT_SIZE 0x00040000 /* 256 KB sectors (x2) */
#define CFG_DRAM_BASE 0xa0000000
#define CFG_DRAM_SIZE 0x04000000
#define CFG_FLASH_BASE PHYS_FLASH_1
//you can also add other IO address map here, such as a FPGA
(2) 定製U-Boot命令:
在include/config_cmd_default.h頭文件中已經預定義了一些常用的U-Boot命令,我們可以在include/configs/xsbase270.h中包含該頭文件,對於其中已定義的不需要的命令,可用undef去除;對於要添加的命令,使用define定義相關的符號即可。
/*
* Command line configuration.
*/
#include
#define CONFIG_CMD_PING
(3) 控制檯串口配置:
包括指定控制檯所用的PXA27X串口,缺省的串口通信波特率等。
/*
* select serial console configuration
*/
#define CONFIG_FFUART 1 /* we use FFUART on XSBASE270 */
#define CONFIG_BAUDRATE 115200
(4) 環境變量設置
包括BOOTP選項設置,缺省環境變量設置,啓動參數列表配置等。
/*
* BOOTP options
*/
#define CONFIG_BOOTP_BOOTFILESIZE
#define CONFIG_BOOTP_BOOTPATH
#define CONFIG_BOOTP_HOSTNAME
#define CONFIG_BOOTDELAY 3
#define CONFIG_ETHADDR 08:00:3e:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0
#define CONFIG_IPADDR 192.168.0.21
#define CONFIG_SERVERIP 192.168.0.250
#define CONFIG_BOOTCOMMAND "bootm 80000"
#define CONFIG_BOOTARGS "root=/dev/ram0,rw mem=64M console=ttyS0, 115200"
#define CONFIG_CMDLINE_TAG
#define CONFIG_TIMESTAMP
/* allow to overwrite serial and ethaddr */
#define CONFIG_ENV_OVERWRITE
其中,CONFIG_BOOTCOMMAND和CONFIG_BOOTARGS在後續的引導內核實驗中還需要進行修正。
(5) 網卡驅動程序配置:
/*
* Hardware drivers
*/
#define CONFIG_DRIVER_SMC91111
#define CONFIG_SMC91111_BASE 0x0C000000
#define CONFIG_SMC_USE_32_BIT 1
XSBASE270採用的網卡是LAN91C111,U-Boot自帶的驅動程序drivers/smc91111.c可支持這款網卡,因此只要在這裏作相應的配置即可。CONFIG_SMC91111_BASE要根據PXA27X對網卡的地址譯碼來決定(片選信號CSx和高位地址線),CONFIG_SMC_USE_32_BIT指定了網卡工作於32位數據總線模式。可以查看驅動程序源代碼得到更多配置選項。
(6) NOR型閃存驅動程序配置:
U-Boot本身支持一系列符合CFI(Common Flash Interface)接口規範的閃存,其缺省支持的閃存芯片信息在include/flash.h中定義,該頭文件中還定義了CFI閃存驅動所必需的數據結構和其他物理及結構特性描述符。NAND閃存驅動在drivers/nand目錄下,這裏不予考慮。CFI是針對NOR型FLASH所提出的一種獲取閃存芯片物理和結構參數的操作規程和標準。
XSBASE270採用兩片Intel 28F128K18C的兼容CFI標準的NOR型閃存,單片容量爲16MB,數據線寬度爲16-bit,兩片並作一個32MB容量的數據寬度爲32-bit的BANK來使用。在頭文件include/flash.h中沒有定義該芯片的相關信息,可以手動添加;這並不是必須的,如果你並不需要使用這些信息的話(例如將CFI驅動所檢測到的Device Id與頭文件中定義的Device ID進行比對與驗證)。
/* file : include/flash.h */
#define INTEL_ID_28F128K18 0x88068806 /* added by aaron */
#define FLASH_28F128K18 0x00BA /*Intel 28F128K18 (128M=8Mx16)*/
要使用U-Boot自帶的CFI閃存驅動,必須要作的是在include/configs/xsbase270.h中添加如下定義:
#define CFG_FLASH_CFI
#define CFG_FLASH_CFI_DRIVER 1
/* avoid long time detection, added by aaron ,see include/flash.h */
#define CFG_FLASH_CFI_WIDTH FLASH_CFI_32BIT
#define CFG_MAX_FLASH_BANKS 1 /* max number of memory banks */
#define CFG_MAX_FLASH_SECT 128 /*max number of sectors on one chip*/
/* timeout values are in ticks */
#define CFG_FLASH_ERASE_TOUT (25*CFG_HZ) /*Timeout for Flash Erase */
#define CFG_FLASH_WRITE_TOUT (25*CFG_HZ) /*Timeout for Flash Write */
/* write flash less slowly */
#define CFG_FLASH_USE_BUFFER_WRITE 1
另外,如果把環境變量保存在FLASH中,還有如下相關定義:
/* NOTE: many default partitioning schemes assume the kernel starts at
* the second sector, not an environment. You have been warned!
*/
#define CFG_MONITOR_BASE 0
#define CFG_MONITOR_LEN PHYS_FLASH_SECT_SIZE
#define CFG_ENV_IS_IN_FLASH 1
#define CFG_ENV_ADDR (PHYS_FLASH_1 + PHYS_FLASH_SECT_SIZE)
#define CFG_ENV_SECT_SIZE PHYS_FLASH_SECT_SIZE
#define CFG_ENV_SIZE (PHYS_FLASH_SECT_SIZE / 16)
/* If defined, hardware flash sectors protection is used instead of
* U-Boot software protection. */
#define CFG_FLASH_PROTECTION
(7) 其他配置:
/*
* Miscellaneous configurable options
*/
#define CFG_HUSH_PARSER 1
#define CFG_PROMPT_HUSH_PS2 "> "
#define CFG_LONGHELP /* undef to save memory */
#ifdef CFG_HUSH_PARSER
#define CFG_PROMPT "$ " /* Monitor Command Prompt */
#endif
#define CFG_CBSIZE 256 /* Console I/O Buffer Size*/
/* Print Buffer Size */
#define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16)
#define CFG_MAXARGS 16 /* max number of command args */
#define CFG_BARGSIZE CFG_CBSIZE /*Boot Argument Buffer Size*/
#define CFG_DEVICE_NULLDEV 1
#define CFG_MEMTEST_START 0xa0400000 /* memtest works on */
#define CFG_MEMTEST_END 0xa0800000 /* 4 ... 8 MB in DRAM */
#undef CFG_CLKS_IN_HZ /* everything, incl board info, in Hz */
/*default load address */
#define CFG_LOAD_ADDR (CFG_DRAM_BASE + 0x8000)
#define CFG_HZ 3686400 /* incrementer freq: 3.6864 MHz */
/* valid baudrates */
#define CFG_BAUDRATE_TABLE { 9600, 19200, 38400, 57600, 115200 }
至此,目標板配置頭文件xsbase270.h就完成了。
3.2 板級初始化代碼xsbase270.c
只需修改board_init()函數即可,完整代碼如下:
#include
DECLARE_GLOBAL_DATA_PTR;
/* Miscelaneous platform dependent initialisations */
int board_init (void)
{
/* memory and cpu-speed are setup before relocation */
/* so we do _nothing_ here */
/* arch number of XSBASE270-Board */
gd->bd->bi_arch_number = MACH_TYPE_XSBASE270;
/* adress of boot parameters */
gd->bd->bi_boot_params = 0xa0000100;
return 0;
}
int board_late_init(void)
{
setenv("stdout", "serial");
setenv("stderr", "serial");
return 0;
}
int dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
gd->bd->bi_dram[2].start = PHYS_SDRAM_3;
gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
gd->bd->bi_dram[3].start = PHYS_SDRAM_4;
gd->bd->bi_dram[3].size = PHYS_SDRAM_4_SIZE;
return 0;
}
3.3 驅動程序移植
最主要的是閃存和網卡驅動程序的移植。由於使用U-Boot自帶的CFI閃存驅動程序和SMC91111網卡驅動程序,應此只要在頭文件中進行相關配置即可完成。具體見3.1節。如果需要自行添加相關的設備驅動,則需要在board/xsbase270/目錄下添加驅動源文件,並將其添加到該目錄下的Makefile中進行編譯與鏈接。
至此,針對特定目標板的U-Boot軟件移植工作基本完成。在下一節中,將簡單討論U-Boot的基本的硬件調試方法與技巧。
U-Boot的移植之(四)調試篇:下載U-Boot到目標板進行調試
U-Boot的移植之(四)調試篇:下載U-Boot到目標板進行調試
編譯完成之後,得到的幾個重要文件是:
(1) u-boot.bin: 116K,原始二進制文件,用於下載到啓動ROM進行系統引導;
(2) u-boot: 384K,ELF格式映像文件,可加載到SDRAM或SRAM中進行調試;
(3) u-boot.srec: Motorola S-Records格式映像。
(4) System.map: U-Boot映像文件的符號表,各符號的鏈接地址。
最有效的調試方法是下載到目標板的啓動閃存,使用硬件仿真器進行跟蹤調試。使用Skyeye,Qemu等軟件仿真器不能達到真實的調試效果,尤其不能真實反映第一階段的底層初始化過程,只適合作U-Boot的學習與研究之用。有人提出在沒有硬件仿真器的情況下,使用“點燈***(利用目標板的LED指示程序運行階段)”進行跟蹤調試,這實際上無異於盲人摸象,特別是在底層初始化階段,一條指令就可能導致異常。也有人提出註釋掉start.S中的lowlevel_init調用,將U-Boot映像加載到SDRAM中進行調試,這實際上只能對U-Boot進行功能調試,而無法跟蹤U-Boot的底層初始化過程。當然,如果實在嫌燒寫FLASH的速度較慢,又心疼其擦寫壽命,也可以將U-Boot映像加載到片上SRAM中調試,因爲U-Boot的開始一部分代碼是位置無關的(除了後6個異常向量外,不過並不構成影響);這要求片上SRAM夠大,因爲U-Boot的映像大小約有300K-400K。
筆者使用Banyan-U ARM EMULATOR JTAG仿真器,結合AXD軟件平臺進行調試。
首先將u-boot.bin下載到FLASH地址0x0,連接好串口,啓動minicom或超級終端,目標板上電後,串口控制檯無任何輸出。這很有可能是lowlevel_init那段代碼出了問題,因爲它牽涉到GPIO的配置,處理器時鐘頻率設置,系統總線頻率與存儲器的時序匹配及初始化,稍有差錯就會當機。當然也有可能是串口的配置不正確,但這部分比較簡單,出錯的可能性比較小。
對於下載到FLASH存儲器的原始二進制文件,只能進行彙編級的跟蹤調試。先利用objdump工具生成U-Boot映像的反彙編代碼:
arm-iwmmxt-linux-gnueabi-objdump -S u-boot > u-boot.S
反彙編代碼u-boot.S和符號表System.map將是跟蹤調試過程中的得力助手。
另一個重要的調試技巧是在AXD中現場修改寄存器和存儲單元的內容,這樣可以幫助我們找到問題所在,而不必每次改動都重新編譯u-boot,也避免了FLASH的頻繁燒寫。
例如,筆者在單步跟蹤調試時,發現在地址0xa30804b4處,使用指令”str r1, [r0]”配置GPDR1(GPIO方向寄存器1)後,存儲器0x0地址開始的大片內容全部被更改,導致異常終止。這時可以在該處設置一個斷點,復位目標板全速運行到斷點處,修改寄存器r1的值(GPDR1的初值),再執行該條指令。經試驗發現,對於XSBASE270開發板,必須要先初始化GAFRx,再初始化GPDRx,才不致於發生上述異常。而在start.S中,是先完成GPDRx的初始化之後,再初始化GAFRx的,因此需要在源代碼中將這兩段代碼的位置互換,重新編譯後,再下載到FLASH中。
U-Boot的串口控制檯輸出如下:
U-Boot 1.3.0-rc2 (Oct 16 2007 - 01:57:29)
DRAM: 64 MB
Flash: 32 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
$
另一個問題是環境變量的設置與保存。將環境變量保存在FLASH中,使用setenv命令設置環境變量,再使用saveenv命令保存,這樣在下次開機時,就會使用新的環境變量。如果使用的是U-Boot自帶的CFI閃存驅動,在保存環境變量時可能會出現如下問題:
$ setenv ipaddr 192.168.1.21
$ saveenv
Saving Environment to Flash...
Un-Protected 1 sectors
Erasing Flash...
Flash erase error at address 40000
Block Erase Error.
Block locked.
done
Erased 1 sectors
這是因爲缺省情況下U-Boot對FLASH有軟件寫保護,這時在U-Boot啓動完畢後即使使用jflashmm工具也無法對FLASH進行燒寫:
[aaronwong@localhost Jflash-XSBase270]$ sudo ./jflashmm u-boot.bin
JFLASH Version 5.01.007
COPYRIGHT (C) 2000 - 2003 Intel Corporation
PLATFORM SELECTION:
Processor= PXA27x
Development System= XSBase270
Data Version= 1.00.001
PXA27x revision ??
Found flash type: 28F128K18
Erasing block at address 0
Error, Block erase timed out
解決辦法可參考Uboot-Users郵件列表Erase error on dual P30(CFI) flash chips主題討論,具體是在include/configs/xsbase270.h中定義CFG_FLASH_PROTECTION,該選項在README文件中的描述如下:
- CFG_FLASH_PROTECTION
If defined, hardware flash sectors protection is used
instead of U-Boot software protection.
修改完畢重新編譯U-Boot,在目標板上電後U-Boot啓動完畢之前,使用jflashmm工具將新的u-boot.bin燒寫到目標板啓動閃存。這時可成功修改環境變量並保存到FLASH中:
$ setenv ipaddr 192.168.1.21
$ saveenv
Saving Environment to Flash...
. done
Un-Protected 1 sectors
Erasing Flash...
. done
Erased 1 sectors
Writing to Flash... done
. done
Protected 1 sectors
一旦U-Boot的基本功能調試通過,能正常在目標板運行,剩餘的工作就是根據實際情況調整TEXT_BASE以及內核引導參數,使用U-Boot來引導Linux內核。在下一節中,將給出U-Boot引導Linux內核的實例。