cortex-a8 uboot系列: 第四章 uboot的配置与编译过程
0赞一、 uboot的Makefile分析
1. 版本号
使用几个变量来说明版本号。最终的版本号是变量U_BOOT_VERSION。版本号分为3个级别。
VERSION:主版本号
PATHCHLEVEL: 补丁版本号
SUBLEVEL: 子版本号
Makelfile最终生成U_BOOT_VERSION这个变量,这个变量记录了Makefile中配置的版本号。
VERSION = 1 PATCHLEVEL = 3 SUBLEVEL = 4 EXTRAVERSION = weiqi7777 U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) VERSION_FILE = $(obj)include/version_autogenerated.h |
include/version_autogenerated.h文件是编译过程中自动生成的一个文件,在编译后的uboot中就有了。里面的内容是一个宏定义,宏定义的值就是在Makefile中配置的uboot的版本号。
2. HOSTARCH和HOSTOS
设置两个环境变量。使用export导出。
HOSTARCH:主机的架构体系。先使用命令uname –m得到主机的架构体系,使用管道操作,对得到的结果进行替换。如在虚拟机中,使用命令uname –m 得到i686,使用sed –e s/i.86/i386, 将i686替换成i386。
HOSTOS:主机的操作系统。使用命令uname –s得到主机操作系统Linux,使用tr命令,将Linux转换为小写linux,在使用sed命令,将(cygwin)xxx替换成cygwin。
关于cygwin是以前windows下仿linux环境软件,现在有了虚拟机,都不使用了。
HOSTARCH := $(shell uname -m | \ sed -e s/i.86/i386/ \ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ \ -e s/sa110/arm/ \ -e s/powerpc/ppc/ \ -e s/ppc64/ppc/ \ -e s/macppc/ppc/) HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ sed -e 's/\(cygwin\).*/cygwin/') export HOSTARCH HOSTOS |
3. 静默编译
默认编译时命令行会打印出来很多编译信息。但是有时候不想要看到这些编译信息,就后台编译即可。就需要静默编译。
Uboot编译如果需要静默编译,加-s选项,即make –s。-s会作为MAKEFLAGS传给Makefile,if判断不满足,XECHO就等于空。
# Allow for silent builds ifeq (,$(findstring s,$(MAKEFLAGS))) XECHO = echo else XECHO = : endif |
4. 设置编译方法
Makefile提供两种编译管理方法。默认情况下文件夹中的.c文件,编译出来的.o会和.c在同一个目录下,这叫原地编译。原地编译的好处是处理简单,但是坏处是污染了原目录,另外一套代码只能按照一种配置和编译方法进行处理,无法同时生成2个以上的配置编译方式。
为了解决以上两种缺陷,uboot支持单独输出文件夹方式编译(linux kernel也支持)。基本思路是编译时另外制定一个输出目录,将来所有的编译生成的.o文件和其他文件都放在这个输出目录里,源代码目录不生成任何文件,对源代码目录就不污染了。这样输出目录就承载了本次配置编译的所有结果。
使用的用法:
第一种:在make时,添加O= 为make的编译选项
如make O=/tmp/build all
第二部:添加环境变量,BUILD_DIR
export BUILD_DIR=/tmp/build
make
如果上述两种都使用,第一种的优先级高。如果都没有使用,那么就认为是原地编译。
# U-boot build supports producing a object files to the separate external # directory. Two use cases are supported: # 1) Add O= to the make command line # 'make O=/tmp/build all' # 2) Set environement variable BUILD_DIR to point to the desired location # 'export BUILD_DIR=/tmp/build' # 'make' # The second approach can also be used with a MAKEALL script # 'export BUILD_DIR=/tmp/build' # './MAKEALL' # Command line 'O=' setting overrides BUILD_DIR environent variable. # When none of the above methods is used the local build is performed and # the object files are placed in the source directory. ifdef O ifeq ("$(origin O)", "command line") BUILD_DIR := $(O) endif endif |
判断是否定义了O,当使用make O=xxx时,此时就定义了O,下面判断O的来源是不是来自命令行,是的话,就定义一个变量BUILD_DIR,值为$(O),也就是xxx。
ifneq ($(BUILD_DIR),) saved-output := $(BUILD_DIR) # Attempt to create a output directory. $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}) # Verify if it was successful. BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd) $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist)) endif # ifneq ($(BUILD_DIR),) |
判断变量BUILD_DIR是否定义,如果在之前使用O参数,就会定义这个变量,ifneq就会为真,执行下面的语句。如果之前未使用O参数,BUILD_DIR变量就不会定义,ifneq判断为假,后续的语句全部就跳过了,也就是使用本地编译。
定义saved-output变量,和BUILD_DIR的值一样,指示保存的输出的目录。然后使用shell命令判断输出的目录是否存在,不存在的话,就强制创建目录。然后调用shell命令进入到该目录中,使用pwd命令,获取目录的绝对路径。判断该路径是否为空,为空说明输出目录不正确,输出错误信息。
$(error output directory "$(saved-output)" does not exist), 表示产生错误,并输出信息output directory "$(saved-output)" does not exist,此时Makefile停止工作。
在makefile中打印警告或者错误消息的方法:$(warning xxxxx)或者$(error xxxxx) ,但是对于$(error xxxxx),makefile会退出。
通过上面的程序,BUILD_DIR保存的是目录的绝对路径,saved-output保存的是目录的名字(也就是O传参指定的)。
5. 定义文件树目录
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR)) SRCTREE := $(CURDIR) TOPDIR := $(SRCTREE) LNDIR := $(OBJTREE) export TOPDIR SRCTREE OBJTREE MKCONFIG := $(SRCTREE)/mkconfig export MKCONFIG ifneq ($(OBJTREE),$(SRCTREE)) REMOTE_BUILD := 1 export REMOTE_BUILD endif |
定义几个变量。
OBJTREE: 编译uboot生成的文件放置的根目录
SRCTREE: 源代码的目录,其实就是当前目录,因为是在当前目录下编译的。$(CURDIR)是一个makefile自带的环境变量,值是pwd的结果。
默认编译下,OBJTREE和SRCTREE是相等的;在O=XX这种编译下两个不相等,以实现单独输出文件夹方式编译。
TOPDIR: 源程序的顶层目录
MKCONFIG: 配置脚本的目录位置,他的值就是源码根目录下的mkconfig文件,这个mkconfig是一个脚本,也就是uboot配置的脚本。以后uboot会调用这个脚本,生成配置文件。
后面比较生成目标的目录的源代码目录是否一致,一致的话,说明是本地编译,REMOTE_BUILD为空,否则说明是单独输出文件夹方式编译,REMOTE_BUILD为1。
# $(obj) and (src) are defined in config.mk but here in main Makefile # we also need them before config.mk is included which is the case for # some targets like unconfig, clean, clobber, distclean, etc. ifneq ($(OBJTREE),$(SRCTREE)) obj := $(OBJTREE)/ src := $(SRCTREE)/ else obj := src := endif export obj src |
得到obj和src两个变量。这两个变量在后面指示某些要使用的文件的位置。obj就是生成文件的根目录,src就是uboot源代码的根目录
6. 导入配置文件
在uboot编译的时候,首先要make 配置文件,这里是make x210_sd_config, 执行该命令,会调用以下的命令。执行$(MKCONFIG)程序,传递6个参数,第一个参数将目标x210_sd_config的config替换成空,也就是第一个参数为x210_sd。
对于$(@:_config=),@表示目标,也就是x210_sd_config,_config=表示将x210_sd_config的_config替换为空。
x210_nand_config : unconfig @$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110 @echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk x210_sd_config : unconfig @$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110 @echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk |
脚本执行后,就会在$(obj)include目录下生成一个config.mk的文件,这个文件里面记录了下图所示几个参数。也会在$(obj)board/samsung/x210/config.mk文件中写入TEXT_BASE = 0xc3e00000(链接器脚本使用的链接地址)。
在主Makefile中将上面这个配置文件包含进来,得到几个变量的值。并导出。上面的ARCH参数,影响交叉编译器。
ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) # load ARCH, BOARD, and CPU configuration include $(obj)include/config.mk export ARCH CPU BOARD VENDOR SOC |
7. 设置交叉编译器
判断ARCH(从include/config.mk文件中得来)是什么,从而确定使用什么交叉编译器对代码进行编译。
ifndef CROSS_COMPILE ifeq ($(HOSTARCH),$(ARCH)) CROSS_COMPILE = else ifeq ($(ARCH),ppc) CROSS_COMPILE = ppc_8xx- endif ifeq ($(ARCH),arm) #CROSS_COMPILE = arm-linux- #CROSS_COMPILE = /usr/local/arm/4.4.1-eabi-cortex-a8/usr/bin/arm-linux- #CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux- CROSS_COMPILE = /usr/local/arm/bin/arm-none-linux-gnueabi- endif export CROSS_COMPILE |
在Makefile中,判断了很多种架构的CPU,使用在上面定义的ARCH变量的值。对于我们使用arm的,只用关心arm架构下的交叉编译器。
这里只需要定义交叉编译器的前缀就行了。
8. 添加额外的配置文件config.mk
# load other configuration include $(TOPDIR)/config.mk |
将uboot根目录下的config.mk文件包含进来,这个是一个脚本文件。
在这个文件中,定义了若干的变量:
# # 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 LDR = $(CROSS_COMPILE)ldr STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump RANLIB = $(CROSS_COMPILE)RANLIB |
设置了工具链变量。以后uboot编译会用到这些变量来进行编译。
# Load generated board configuration sinclude $(OBJTREE)/include/autoconf.mk 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 VENDOR BOARDDIR = $(VENDOR)/$(BOARD) else BOARDDIR = $(BOARD) endif ifdef BOARD sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules endif |
根据主Makefile导出的参数(ARCH,CPU,SOC,VENDOR,BOARD),决定引入一些配置文件。sinclude相当于-include,包含文件的时候,文件不存在,makefile不理会。
对于第一个autoconf.mk,不是由源码提供,配置过程自动生成,也就是make配置后会生成这个文件。这个文件的作用指导整个uboot的编译过程。文件的内容就是很多CONFIG_开头的宏(和linux的.config文件类似),这些宏影响uboot编译过程的走向(原理就是条件编译)。在uboot中有很多地方代码使用条件编译编写,条件编译是用来实现可移植性的。
CONFIG_CMD_FAT=y CONFIG_USB_OHCI=y CONFIG_SYS_CLK_FREQ=24000000 CONFIG_CMD_ITEST=y CONFIG_S3C_HSMMC=y CONFIG_DISPLAY_BOARDINFO=y CONFIG_CMD_XIMG=y CONFIG_CMD_CACHE=y CONFIG_STACKSIZE="0x40000" CONFIG_BOOTDELAY=3 CONFIG_CHECK_MPLL_LOCK=y CONFIG_NR_DRAM_BANKS=2 CONFIG_ETHADDR="00:40:5c:26:0a:5b" CONFIG_CMD_CONSOLE=y |
这个文件不是凭空产生,配置过程需要原材料来产生这个文件。原材料在源码目录的include/configs/xxx.h头文件。(x210开发板中为include/configs/x210_sd.h)。这个头文件里面全部都是宏定义,这些宏定义就是对当前开发板的移植。每一个开发板的移植都对应这个目录下的一个头文件,这个头文件每一个宏定义都很重要,这些配置的宏定义就是移植uboot的关键所在。
其他包含的文件,都是提前写好的,用来指导编译。
判断是否启动方式是否从NAND,是的话,使用nand的链接脚本。否则使用其他链接脚本。
ifndef LDSCRIPT #LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug ifeq ($(CONFIG_NAND_U_BOOT),y) LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds else LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds endif endif |
在make x210_sd_config后,会在board/samsung/x210下产生config.mk文件。这个文件会在之前的sinclude中包含进来。
TEXT_BASE是将来整个uboot链接时指定的虚拟链接起始地址。Uboot中启用了MMU,因此这个0xc3e00000就等于物理地址0x23e00000(也可能是0x33e00000)。
LDFLAGS += -Bstatic -T $(LDSCRIPT) $(PLATFORM_LDFLAGS) ifneq ($(TEXT_BASE),) LDFLAGS += -Ttext $(TEXT_BASE) endif |
链接的时候,使用了 LDFLAGS参数,这个参数包含了–Tu-boot.lds –Ttext TEXT_BASE。使用u-boot.lds作为链接脚本,但是u-boot.lds中的起始地址是0x0,所以使用–Ttext TEXT_BASE,将链接起始地址进行偏移。
自动推导,对.S的代码,编译成.s的,对.s的代码,编译成.o的,对.c的代码,也编译成.o的。
ifndef REMOTE_BUILD %.s: %.S $(CPP) $(AFLAGS) -o $@ $< %.o: %.S $(CC) $(AFLAGS) -c -o $@ $< %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< else $(obj)%.s: %.S $(CPP) $(AFLAGS) -o $@ $< $(obj)%.o: %.S $(CC) $(AFLAGS) -c -o $@ $< $(obj)%.o: %.c $(CC) $(CFLAGS) -c -o $@ $< endif |
9. 设置起始OBJS
设置uboot的起始段的目标。对于使用的s5pc11x,OBJS=cpu/s5pc11x/start.o。这个start.o中的代码,就是uboot最开始执行的代码。对于其他架构,还需要包括其他的.o代码。
######################################################################### # U-Boot objects....order is important (i.e. start must be first) OBJS = cpu/$(CPU)/start.o ifeq ($(CPU),i386) OBJS += cpu/$(CPU)/start16.o OBJS += cpu/$(CPU)/reset.o endif OBJS := $(addprefix $(obj),$(OBJS)) |
$(addprefix )给变量添加前缀,这里是往$(OBJS)变量前加上$(obj)的值。
10. 设置LIBS
设置库文件。
LIBS = lib_generic/libgeneric.a LIBS += $(shell if [ -f board/$(VENDOR)/common/Makefile ]; then echo \ "board/$(VENDOR)/common/lib$(VENDOR).a"; fi) LIBS += cpu/$(CPU)/lib$(CPU).a ifdef SOC LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a endif ifeq ($(CPU),ixp) LIBS += cpu/ixp/npe/libnpe.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 += drivers/bios_emulator/libatibiosemu.a LIBS += drivers/block/libblock.a LIBS += drivers/dma/libdma.a LIBS += drivers/hwmon/libhwmon.a LIBS += drivers/i2c/libi2c.a LIBS += drivers/input/libinput.a LIBS += drivers/misc/libmisc.a LIBS += drivers/mmc/libmmc.a LIBS += drivers/mtd/libmtd.a LIBS += drivers/mtd/nand/libnand.a LIBS += drivers/mtd/nand_legacy/libnand_legacy.a LIBS += drivers/mtd/onenand/libonenand.a LIBS += drivers/mtd/ubi/libubi.a LIBS += drivers/mtd/spi/libspi_flash.a LIBS += drivers/net/libnet.a LIBS += drivers/net/sk98lin/libsk98lin.a LIBS += drivers/pci/libpci.a LIBS += drivers/pcmcia/libpcmcia.a LIBS += drivers/spi/libspi.a ifeq ($(CPU),mpc83xx) LIBS += drivers/qe/qe.a endif ifeq ($(CPU),mpc85xx) LIBS += drivers/qe/qe.a endif LIBS += drivers/rtc/librtc.a LIBS += drivers/serial/libserial.a LIBS += drivers/usb/libusb.a LIBS += drivers/video/libvideo.a LIBS += common/libcommon.a LIBS += libfdt/libfdt.a LIBS += api/libapi.a LIBS += post/libpost.a LIBS := $(addprefix $(obj),$(LIBS)) .PHONY : $(LIBS) $(VERSION_FILE) LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a LIBBOARD := $(addprefix $(obj),$(LIBBOARD))
# Add GCC lib PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
|
这些库文件,以后会被其他代码所调用。
11. 判断是否从nand或inand启动
判断启动方式,得到不同的输出bin。
ifeq ($(CONFIG_NAND_U_BOOT),y) NAND_SPL = nand_spl U_BOOT_NAND = $(obj)u-boot-nand.bin endif
ifeq ($(CONFIG_ONENAND_U_BOOT),y) ONENAND_IPL = onenand_bl1 U_BOOT_ONENAND = $(obj)u-boot-onenand.bin endif |
对于九鼎的x210开发板,有两种启动方式,一种是nand启动,一种是inand启动,也就是SD启动。
12. 得到新的变量
__OBJS := $(subst $(obj),,$(OBJS)) __LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD)) |
这两个变量,在编译的时候会被用到。
13. 得到目标
ALL就是总目标,包含若干个文件。
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND) $(obj)u-boot.dis ifeq ($(ARCH),blackfin) ALL += $(obj)u-boot.ldr endif |
最终uboot编译后,就会生成这些目标文件。其中u-boot.bin就是生成的烧录到开发板的uboot文件。
14. make目标
在uboot根目录下make其实就是make all。
目标all,依赖$(ALL),$(ALL)是由多个文件组成,所以就一次性生成了多个文件。
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 $< $@ $(obj)u-boot.ldr: $(obj)u-boot $(LDR) -T $(CONFIG_BFIN_CPU) -f -c $@ $< $(LDR_FLAGS) $(obj)u-boot.ldr.hex: $(obj)u-boot.ldr $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@ -I binary $(obj)u-boot.ldr.srec: $(obj)u-boot.ldr $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ -I binary $(obj)u-boot.img: $(obj)u-boot.bin ./tools/mkimage -A $(ARCH) -T firmware -C none \ -a $(TEXT_BASE) -e 0 \ -n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \ sed -e 's/"[ ]*$$/ for $(BOARD) board"/') \ -d $< $@ $(obj)u-boot.sha1: $(obj)u-boot.bin $(obj)tools/ubsha1 $(obj)u-boot.bin $(obj)u-boot.dis: $(obj)u-boot $(OBJDUMP) -d $< > $@ $(obj)u-boot: depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \ sed -n -e 's/.*\($(SYM_PREFIX)__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 |
首先是生成uboot的可执行程序,然后该uboot转换为其他格式的文件。
目标里面有一些是重要的:
u-boot是最终编译链接生成的uboot的elf格式的可执行文件。
u-boot.hex: elf转化的hex文件
u-boot.dis: uboot的反汇编文件
u-boot.bin: 是最终烧录的二进制文件。
15. make unconfig
这个符号用来删除上一次生成的配置文件。
unconfig: @rm -f $(obj)include/config.h $(obj)include/config.mk \ $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \ $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep \ $(obj)board/$(VENDOR)/$(BOARD)/config.mk
|
如对于以下,
x210_sd_config : unconfig @$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110 @echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk |
首先会先去执行make unconfig,将之前生成的配置文件都给删除掉。然后再执行后面的两行命令。
二、 uboot的配置
在Makefile中,配置的命令如下:
x210_sd_config : unconfig @$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110 @echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk |
调用uboot根目录下的mkconfig,然后传递6个参数。
第一个参数 $1:$(@:_config=), @表示x210_sd_config,:_config=表示将@的_config替换为空。所以第一个参数就是x210_sd。
第二个参数 $2:arm (ARCH)
第三个参数 $3:s5pc11x (CPU)
第四个参数 $4:x210 (BOARD)
第五个参数 $5:samsung (VENDOR)
第六个参数 $6:s5pc110 (SOC)
所以$# = 6
对于mkconfig,首先判断参数个数
APPEND=no # Default: Create new config file BOARD_NAME="" # Name to print in make output while [ $# -gt 0 ] ; do case "$1" in --) shift ; break ;; -a) shift ; APPEND=yes ;; -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;; *) break ;; esac done |
$#为6,所以进入while循环,进行case判断,因为$1为x210_sd,符合*,所以break跳出while循环。这里相当于while循环什么都没有做。
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" |
简略的if语句,判断${BOARD_NAME}是否为空,因为之前定义为空,所以执行后面一句,BOARD_NAME="$1",所以BOARD_NAME=x210_sd。
[ $# -lt 4 ] && exit 1 [ $# -gt 6 ] && exit 1 echo "Configuring for ${BOARD_NAME} board..." |
输入参数个数如果小于4或者大于6,就直接退出(exit 1,也就是mkconfig脚本返回1)。否则向下执行,打印信息。
# Create link to architecture specific headers if [ "$SRCTREE" != "$OBJTREE" ] ; then mkdir -p ${OBJTREE}/include mkdir -p ${OBJTREE}/include2 cd ${OBJTREE}/include2 rm -f asm ln -s ${SRCTREE}/include/asm-$2 asm LNPREFIX="../../include2/asm/" cd ../include rm -rf asm-$2 rm -f asm mkdir asm-$2 ln -s asm-$2 asm else cd ./include rm -f asm ln -s asm-$2 asm fi
rm -f asm-$2/arch
if [ -z "$6" -o "$6" = "NULL" ] ; then ln -s ${LNPREFIX}arch-$3 asm-$2/arch else ln -s ${LNPREFIX}arch-$6 asm-$2/arch fi
# create link for s5pc11x SoC if [ "$3" = "s5pc11x" ] ; then rm -f regs.h ln -s $6.h regs.h rm -f asm-$2/arch ln -s arch-$3 asm-$2/arch fi |
从33行到118行,都在创建一些符号链接。这些符号链接文件的存在就是整个配置过程的核心,这些符号链接文件(文件夹)的主要作用是给头文件包含等过程提供指向性连接。根本目的是让uboot具有可移植性。
Uboot可移植性的实现原理:uboot中有很多彼此平行的代码,各自属于各自不同的架构/CPU/开发板,在具体的一个开发板的编译时用符号连接的方式提供一个具体的名字的文件夹供编译时用。这样就可以在配置的过程中通过不同的配置时用不同的文件,就可以正确的包含正确的文件。
创建的符号链接:在本地编译条件下。
第一个:在include目录下创建asm符号链接,指向asm-$2也就是asm-arm。
cd ./include rm -f asm ln -s asm-$2 asm |
rm -f asm-$2/arch |
删除asm-arm下的arch文件。因为之后要创建这个文件。
第二个:在include/asm-arm下创建一个arch文件,指向include/asm-arm/arch-s5pc110。
if [ -z "$6" -o "$6" = "NULL" ] ; then ln -s ${LNPREFIX}arch-$3 asm-$2/arch else ln -s ${LNPREFIX}arch-$6 asm-$2/arch fi |
条件判断,$6为空或者$6为NULL,条件真,而$6为s5pc110,不为空,所以条件为假,执行第二个符号链接。创建了一个新的链接,asm-arm/arch,指向arch-s5pc110。
这里${LNPREFIX}为空。此时是在include目录下,所以创建的符号链接是在include目录下。
第三个:
# create link for s5pc11x SoC if [ "$3" = "s5pc11x" ] ; then rm -f regs.h ln -s $6.h regs.h rm -f asm-$2/arch ln -s arch-$3 asm-$2/arch fi |
在46行,使用cd ./include,所以当前目录为include,删除这个目录下的regs.h,然后创建一个符号链接regs.h,指向$6.h,也就是s5pc110.h。这个s5pc110.h中,定义了各个外设寄存器的结构体。
第四个:
删除asm-arm下的arch文件夹,也就是把刚刚创建的第二个符号链接给删除掉,然后创建一个符号链接asm-arm/arch,指向arch-s5pc11x。
第五个:
if [ "$2" = "arm" ] ; then rm -f asm-$2/proc ln -s ${LNPREFIX}proc-armv asm-$2/proc fi |
$2为arm,条件符合,删除掉asm-arm下的proc。然后创建一个proc文件,指向include/asm/proc-armv。
总结:一共创建了4个符号链接(第二个被删除掉了)。这4个符号链接将来在写代码过程中,头文件包含是非常有用。比如,一个头文件包含可能是:#include<asm/xx.h>,这个时候就会在asm指向的符号链接地方去找xx.h。
创建include/config.mk参数文件。
# Create include file for Make echo "ARCH = $2" > config.mk echo "CPU = $3" >> config.mk echo "BOARD = $4" >> config.mk [ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk |
往配置文件中写内容,主要是写入几个变量。因为这个时候目录是在include目录下,所以创建的文件config.mk也就是在include目录下。
主要是写入几个变量:
创建这个文件,是为了让主Makefile来包含这个文件,得到几个参数的值。
Uboot的配置和编译过程的配合。
编译的时候需要ARCH=arm,CPU=s5pc11x等这些变量来指导编译,配置的时候就是为编译阶段提供这些变量。
但是为什么Makefile中不直接定义这些变量,而要在mkconfig脚本中创建config.mk文件然后又在Makefile中引入这个文件获取这些变量?这样的做法是为了以后好维护,好移植。
# Create board specific header file if [ "$APPEND" = "yes" ] # Append to existing config file then echo >> config.h else > config.h # Create new config file fi echo "/* Automatically generated - do not edit */" >>config.h echo "#include <configs/$1.h>" >>config.h |
文件内容如下,这个文件时自动生成的。生成的文件,是在include目录下。
默认情况下,创建该文件,如果是make –a,就追加内容到该文件(前提是该文件已存在,但是该文件默认不存在)。功能就是包含一个头文件,configs/x210_sd.h,这个头文件是移植x210开发板时,对开发板的宏定义配置文件。这个文件时移植x210时最主要的文件。
x210_sd.h文件会被用来生成一个autoconfig.mk文件,这个文件会被主Makefile引入,指导整个编译过程。里面的这些宏定义会影响对uboot中大部分.c文件中一些条件编译的选择。
exit 0 |
最后返回0,表示执行shell脚本成功。
Uboot的整个配置过程,很多文件是有关联的(有时候这个文件是在另外一个文件中创建出来的,有时候这个文件被另外一个文件包含进去,有时候这个文件是由另外一个文件的内容生成决定的)。
Uboot中配置和编译过程,所有的文件或者全局变量都是字符串形式的。意味着uboot的配置过程都是字符串匹配的,所以要注意大小写,不要写错。不然出错了很难排查。
三、 uboot的链接脚本
链接脚本是在config.mk中有定义:
ifndef LDSCRIPT #LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug ifeq ($(CONFIG_NAND_U_BOOT),y) LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds else LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds endif endif |
位置是在board\samsung\x210\u-boot.lds中。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") /*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/ OUTPUT_ARCH(arm) ENTRY(_start) |
OUTPUT_ARCH(arm),表示执行程序的架构是arm
ENTRY(_start)用来指定整个程序的入口地址。所谓入口地址就是整个程序的开头地址,可以认为就是整个程序的第一句指令。
SECTIONS { . = 0x00000000; |
指定程序的链接起始地址有2种方法,一种是在链接脚本SECTION的开头使用. = 地址来指定,一种是在链接中ld的flags使用-Ttext 地址来指定。后面一种的优先级高。
Uboot的Makefile使用第二种方法,使用TEXT_BASE指定链接的起始地址。因为最终uboot的链接地址就是TEXT_BASE。
. = ALIGN(4); .text : { cpu/s5pc11x/start.o (.text) cpu/s5pc11x/s5pc110/cpu_init.o (.text) board/samsung/x210/lowlevel_init.o (.text) cpu/s5pc11x/onenand_cp.o (.text) cpu/s5pc11x/nand_cp.o (.text) cpu/s5pc11x/movi.o (.text) common/secure_boot.o (.text) common/ace_sha1.o (.text) cpu/s5pc11x/pmic.o (.text) *(.text) } |
指定text段的程序分布。首先起始地址要4字节对齐。然后按照链接脚本中规定的程序的顺序将text段放入到最终目标的text段中。第一个是start.o,然后是cpu_init.o…,这个段的顺序非常重要,如果弄错了,那么uboot基本是运行不起来的。
. = 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); .mmudata : { *(.mmudata) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) } _end = .; |
后面就是指定其他段。这里可以使用自定义段。比如uboot的 .u_boot_cmd就是自定义段,这段和uboot的命令有关的。
四、 u-boot.map文件解析
生成uboot这个可执行程序时,会生成u-boot.map文件,这个文件,就定义了可执行程序中,程序,标号,全局变量的地址分配。
1. 代码段(.text)
text段的起始地址是0xc3e0_0000,这个就是uboot的链接地址。这个地址放的是start.o的代码段,这个和链接脚本中指定的一致。
.text 0xc3e00000 0x2a1dc cpu/s5pc11x/start.o(.text) .text 0xc3e00000 0x6c0 cpu/s5pc11x/start.o 0xc3e00010 _start 0xc3e00050 _end_vect 0xc3e00058 _armboot_start 0xc3e0005c _bss_start 0xc3e00060 _bss_end 0xc3e001fc copy_from_nand 0xc3e00248 theLastJump 0xc3e00540 arm_cache_flush 0xc3e00560 v7_flush_dcache_all 0xc3e00600 disable_l2cache 0xc3e00620 enable_l2cache 0xc3e00640 set_l2cache_auxctrl 0xc3e00660 set_l2cache_auxctrl_cycle |
在这个start.o中,有若干个标号,如对于标号_start,地址从0xc3e0_0010开始,到0xc3e0_004c。然后接着是_end_vect标号。查看反汇编,可以看到_start标号,就是从0xc3e0_0010开始的。
Disassembly of section .text:
c3e00000 <_start-0x10>: c3e00000: 00002000 .word 0x00002000 ...
c3e00010 <_start>: c3e00010: ea000013 b c3e00064 <reset> c3e00014: e59ff014 ldr pc, [pc, #20] ; c3e00030 <_undefined_instruction> c3e00018: e59ff014 ldr pc, [pc, #20] ; c3e00034 <_software_interrupt> c3e0001c: e59ff014 ldr pc, [pc, #20] ; c3e00038 <_prefetch_abort> c3e00020: e59ff014 ldr pc, [pc, #20] ; c3e0003c <_data_abort> c3e00024: e59ff014 ldr pc, [pc, #20] ; c3e00040 <_not_used> c3e00028: e59ff014 ldr pc, [pc, #20] ; c3e00044 <_irq> c3e0002c: e59ff014 ldr pc, [pc, #20] ; c3e00048 <_fiq> |
在start.o代码段之后,就是cpu_inti.o的代码段。从0xc3e0_06c0地址开始。同样,也可以看各个标号的起始地址。
cpu/s5pc11x/s5pc110/cpu_init.o(.text) .text 0xc3e006c0 0x410 cpu/s5pc11x/s5pc110/cpu_init.o 0xc3e006c0 mem_ctrl_asm_init 0xc3e00a38 cleanDCache 0xc3e00a44 cleanFlushDCache 0xc3e00a50 cleanFlushCache |
2. 只读段(.rodata)
只读段的起始地址从0xc3e2_a1dc。interrupts.o的只读段是只读段的第一个,接着是board.o的只读段。
.rodata 0xc3e2a1dc 0x22b34 *(.rodata) .rodata 0xc3e2a1dc 0x80 cpu/s5pc11x/libs5pc11x.a(interrupts.o) .rodata 0xc3e2a25c 0x40 lib_arm/libarm.a(board.o) 0xc3e2a25c version_string .rodata 0xc3e2a29c 0x7 net/libnet.a(net.o) *fill* 0xc3e2a2a3 0x1 00 .rodata 0xc3e2a2a4 0xc4 common/libcommon.a(cmd_bootm.o) .rodata 0xc3e2a368 0x58 common/libcommon.a(cmd_mem.o) .rodata 0xc3e2a3c0 0x20 common/libcommon.a(xyzModem.o) .rodata 0xc3e2a3e0 0x200 common/libcommon.a(crc16.o) .rodata 0xc3e2a5e0 0x400 lib_generic/libgeneric.a(crc32.o) .rodata 0xc3e2a9e0 0x90 lib_generic/libgeneric.a(zlib.o) .rodata 0xc3e2aa70 0xac cpu/s5pc11x/libs5pc11x.a(usbd-otg-hs.o) 0xc3e2aa70 config_full 0xc3e2aa7c config_full_total 0xc3e2aa9c config_high 0xc3e2aaa8 config_high_total 0xc3e2aac8 qualifier_desc 0xc3e2aad4 string_desc0 |
对于usbd-otg-hs.o的只读段,还显示了只读段的变量,以及各个变量的起始地址。
3. 数据段(.data)
data段的起始地址从0xc3e53e38开始。对于board.o之前的代码,没有用到data段,所以地址均没有变化,都是0xc3e53e38。
.data 0xc3e53e38 0x4d30 *(.data) .data 0xc3e53e38 0x0 cpu/s5pc11x/start.o .data 0xc3e53e38 0x0 cpu/s5pc11x/s5pc110/cpu_init.o .data 0xc3e53e38 0x0 board/samsung/x210/lowlevel_init.o .data 0xc3e53e38 0x0 cpu/s5pc11x/onenand_cp.o .data 0xc3e53e38 0x0 cpu/s5pc11x/nand_cp.o .data 0xc3e53e38 0x0 cpu/s5pc11x/movi.o .data 0xc3e53e38 0x0 common/secure_boot.o .data 0xc3e53e38 0x0 common/ace_sha1.o .data 0xc3e53e38 0x0 cpu/s5pc11x/pmic.o .data 0xc3e53e38 0x0 cpu/s5pc11x/libs5pc11x.a(interrupts.o) .data 0xc3e53e38 0x0 cpu/s5pc11x/libs5pc11x.a(cpu.o) .data 0xc3e53e38 0x0 cpu/s5pc11x/libs5pc11x.a(setup_hsmmc.o) .data 0xc3e53e38 0x0 cpu/s5pc11x/libs5pc11x.a(serial.o) .data 0xc3e53e38 0x0 cpu/s5pc11x/s5pc110/libs5pc110.a(speed.o) .data 0xc3e53e38 0x0 lib_arm/libarm.a(_divsi3.o) .data 0xc3e53e38 0x0 lib_arm/libarm.a(_udivsi3.o) .data 0xc3e53e38 0x38 lib_arm/libarm.a(board.o) 0xc3e53e38 init_sequence 0xc3e53e6c check_flash_flag |
在board.o中,有定义两个全局变量,init_sequence和check_flash_flag,因此这两个变量是要放到data段中的。变量init_sequence的起始地址是0xc3e5_3e38,check_flash_flag的起始地址是0xc3e5_3e6c。
4. uboot命令段(.u_goot_cmd)
uboot命令段,用来存放uboot的命令。命令段的起始地址是0xc3e58b68。存放的第一个命令是weiqi,然后是bdinfo,然后是go….
.u_boot_cmd 0xc3e58b68 0x660 *(.u_boot_cmd) .u_boot_cmd 0xc3e58b68 0x18 common/libcommon.a(cmd_weiqi.o) 0xc3e58b68 __u_boot_cmd_weiqi .u_boot_cmd 0xc3e58b80 0x18 common/libcommon.a(cmd_autoscript.o) 0xc3e58b80 __u_boot_cmd_autoscr .u_boot_cmd 0xc3e58b98 0x18 common/libcommon.a(cmd_bdinfo.o) 0xc3e58b98 __u_boot_cmd_bdinfo .u_boot_cmd 0xc3e58bb0 0x48 common/libcommon.a(cmd_boot.o) 0xc3e58bb0 __u_boot_cmd_go 0xc3e58bc8 __u_boot_cmd_reset 0xc3e58be0 __u_boot_cmd_re |
5. bss段
bss段的起始地址是0xc3e6_0000。interrupt.o之前的代码,没有用到bss段,所以bss段的地址没有变化。
bss 0xc3e60000 0x44e18 *(.bss) .bss 0xc3e60000 0x0 cpu/s5pc11x/start.o .bss 0xc3e60000 0x0 cpu/s5pc11x/s5pc110/cpu_init.o .bss 0xc3e60000 0x0 board/samsung/x210/lowlevel_init.o .bss 0xc3e60000 0x0 cpu/s5pc11x/onenand_cp.o .bss 0xc3e60000 0x0 cpu/s5pc11x/nand_cp.o .bss 0xc3e60000 0x0 cpu/s5pc11x/movi.o .bss 0xc3e60000 0x0 common/secure_boot.o .bss 0xc3e60000 0x0 common/ace_sha1.o .bss 0xc3e60000 0x0 cpu/s5pc11x/pmic.o .bss 0xc3e60000 0xc cpu/s5pc11x/libs5pc11x.a(interrupts.o) 0xc3e60008 timer_load_val |
interrupt.o有用到bss段,这个bss段用于timer_load_val,从地址可以看出,这个变量占据了8个字节的大小。
当然这个map文件中,还有其他的一些段,分析方法是一致的。分析这个map文件,可以加深对程序生成的相关知识,如段,标号等。