海思(Hi3521a)uboot详细分析(1)——顶层Makefile分析

    顶层Makefile对于uboot的配置和编译都非常重要,对顶层Makefile的分析有助于进一步的了解uboot的启动流程和uboot的配置及移植,uboot的其它内容分析可以参考博客《序言和目录》

顶层Makefile的内容主要结构为:

  1. 确定版本号及主机信息
  2. 实现静默编译功能
  3. 设置各种路径
  4. 设置编译工具链
  5. 设置规则
  6. 设置与cpu相关的伪目标

需要注意的是,结构顺序并不代表代码执行顺序。

1.确定版本号及主机信息

VERSION = 2010
PATCHLEVEL = 06
SUBLEVEL =
EXTRAVERSION =
ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif
TIMESTAMP_FILE = $(obj)include/timestamp_autogenerated.h
VERSION_FILE = $(obj)include/version_autogenerated.h
  • 最开始四个变量的含义依次为:主版本号、次版本号、再次版本号、附加的版本信息(值为空,可以给用户使用)
  • U-Boot_VERSION 这个变量的值为真正的uboot版本号,其值为2010.06
  • VERSION_FILE变量的值是一个.h文件
  • timestamp_autogenerated.h 这个文件是在make之后自动生成的,文件内容是一条宏,这条宏给将其他.c文件提供uboot的编译时间
  • version_autogenerated.h这个文件是在make之后自动生成的,文件内容是一条宏,这条宏给将其他.c文件提供uboot的版本号
HOSTARCH := $(shell uname -m | \
	sed -e s/i.86/i386/ \
	    -e s/sun4u/sparc64/ \
	    -e s/arm.*/arm/ \
	    -e s/sa110/arm/ \
	    -e s/ppc64/powerpc/ \
	    -e s/ppc/powerpc/ \
	    -e s/macppc/powerpc/)
  • makefile的函数调用与变量调用很类似,格式是$( function arguments),其实上面一大段的意思是变量HOSTARCH的值是一个函数的返回值shell是makefile中的一个函数,$(shell XXX)会被解析成执行shell命令XXX;此处是执行了一条 uname -m |sed -e ……,符号 \是makefile的换行符其中,|是shell语法中的管道结构,例如:XXX | YYY ,表达式XXX 的输出将作为表达式YYY的输入,YYY的输出才是整句表达式的输出uname -m 指令将输出负责编译的主机cpu架构,比如ixx86;sed -e是替换命令,比如把ixx86替换为i386,由此可见这个HOSTARCH变量的值将得到负责编译的主机cpu架构。大部分情况下我们得到的都是i386
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
	    sed -e 's/\(cygwin\).*/cygwin/')
  • 这个HOSTOS变量和上一句HOSTARCH变量的原理类似,管道第一部分uname -s会得到负责编译的主机的OS,比如Linux
  • 管道第二部分是将大写转换成小写
  • 管道第三部分的意思是如果前面一个部分得到了cygwin系统,则格式要转换一下。不必深究,因为cygwin基本没人用……
  • 由此可见这个HOSTOS变量的值将得到负责编译的主机操作系统,大部分情况下我们得到的都是linux
# Set shell to bash if possible, otherwise fall back to sh
SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
	else if [ -x /bin/bash ]; then echo /bin/bash; \
	else echo sh; fi; fi)

export	HOSTARCH HOSTOS SHELL
  • SHELL定义使用哪种sh解释器来解释脚本,BASH的值为/bin/sh也可能为,整个语句的意思是判断/bin/sh是否可执行
  • 如果可执行将/bin/sh 赋值给变量SHELL
  • 如果不可执行,进一步判断/bin/bash的可执行性
  • 如果/bin/sh和/bin/bash都不可执行,则直接将sh赋值给SHELL变量
  • export    HOSTARCH HOSTOS SHELL
  • 导出上面三个变量到全局,使其为环境变量,让其他的文件也可以使用架构和系统信息

2.实现静默编译功能

# Allow for silent builds
ifeq (,$(findstring s,$(MAKEFLAGS)))
XECHO = echo
else
XECHO = :
endif
  • 这整段是为了实现make的静默选项功能,其中,findstring一个函数,$(findstrings, $ (MAKEFLAGS))功能是从$(MAKEFLAGS)中找出字符‘s’
  • $(MAKEFLAGS)是make的flag(选项),如果在控制台中输入make -s,则$(MAKEFLAGS)的值为‘s’
  • 如果$(findstring $(MAKEFLAGS))没找到‘s’,这个表达式的值为空,则ifeq()为真,即make时无需静默
  • 无需静默的实现方法是令变量XECHO值为关键字echo,因为makefile中每次需要打印都会使用XECHO,而不是直接使用echo本身
  • 静默make的实现方法是令变量XECHO值为空,当makefile需要打印时会调用XECHO,由于其为空,故无法打印make信息

3.设置各种路径

ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
  • 这里开始阐述了makefile所支持的“单独外部路径编译”,
  • 它的原理和keil把.o、.l、.bin、.a等中间文件放在单独的文件夹内的原理相同,都是为了使源码更简洁明了,
  • 防止被中间文件污染;以及为了同时维护多个配置编译方式
  • 若要使用单独外部路径编译,有两种方法,
  • 方法一:例如在控制台中输入 make O=/tmp/build ,将输出文件夹路径作为参数
  • 方法二:导出环境变量,即export BUILD_DIR=/tmp/build
  • 注:方法一的优先级高,会覆盖方法二。
  • 而且方法一必须每次输入make时都要输入参数(不论是make clean还是make config 的时候),
  • 格式如make O=/tmp/build disclean
ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)

# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})

  • 这句是shell语法中简写的if表达式,其作用是当输出文件夹路径不存在时就创建它
  • 其中包含两个表达式,表达式1||表达式2,当表达式1为真时,表达式2不会被解释器执行,因为总结果一定为真,(尽管总的结果没有意义)。唯有表达式1为假时,解释器才会去执行表达式2,这样就能用逻辑表达式来实现if语句的功能了
  • 表达式1中,方括号是固定用法,是为了突出里面表达式的作用是判断语句。-p是shell中判断路径是否存在的符号,如果路径存在则为表达式1为真;如果路径不存在,表达式1为假,解释器会执行表达式2,即创建输出文件夹路径。
# 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),)
  • 这整个一段功能是确保输出文件夹路径创建成功,&&的功能是连续执行两句语句,当cd $(BUILD_DIR)执行完后,执行/bin/pwd,即打印当前路径;
  • $(if xxx,yyy,zzz)是makefile的判断函数,如果xxx为真,则执行yyy并返回值,否则执行zzz并返回值,由此可知如果未成功创建BUILD_DIR,就会输出错误打印信息;
OBJTREE		:= $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE		:= $(CURDIR)
TOPDIR		:= $(SRCTREE)
LNDIR		:= $(OBJTREE)
  • 这段代码是设置并导出了很多和路径有关的环境变量
  • 如果BUILD_DIR不为空,OBJTREE(放产生的.o文件)的值就为BUILD_DIR,如果为空,则OBJTREE的值就为CURDIR(即current direction,当前源码所在的目录)
  • SRCTREE的值为当前源码所在目录,TOPDIR的值为SRCTREE的值即当前源码所在目录,LNDIR(应该是放链接产生的文件)的值和OBJTREE相同
MKCONFIG	:= $(SRCTREE)/mkconfig
export MKCONFIG
  • 将变量MKCONFIG的值设置为当前源码目录下的mkconfig文件,并将其导出为环境变量,这个shell脚本文件是正式make之前的配置脚本,十分重要
  • 该mkconfig文件的分析可以参考博客《海思(Hi3521a)uboot详细分析(2)——顶层mkconfig分析》
ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD	:= 1
export REMOTE_BUILD
endif
  • 判断OBJTREE的值和SRCTREE的值是否不相等,即判断是否使用了“单独外部路径编译”,如果使用了这个功能,则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

# Make sure CDPATH settings don't interfere
unexport CDPATH
  • 这一段的意思非常简单,判断OBJTREE的值和SRCTREE的值是否不相等,即判断是否使用了“单独外部路径编译”功能,如果使用了这个功能, 则将obj的值赋为OBJTREE的值,将src的值赋为SRCTREE的值;反之,值都为空。最后将他们全部导出至全局

4.设置编译工具链(大部分在config.mk内)

# The "tools" are needed early, so put this first
# Don't include stuff already done in $(LIBS)
SUBDIRS	= tools \
	  examples/standalone \
	  examples/api

.PHONY : $(SUBDIRS)

ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk))

# Include autoconf.mk before config.mk so that the config options are available
# to all top level build files.  We need the dummy all: target to prevent the
# dependency target in autoconf.mk.dep from being the default.
all:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk

# load ARCH, BOARD, and CPU configuration
include $(obj)include/config.mk
export	ARCH CPU BOARD VENDOR SOC
  • config.mk这个文件其实uboot源码中是不存在的,它是由配置过程中由mkconfig这个脚本创建的,
  • 执行命令:make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config
  • 它会去执行根目录下mkconfig这个脚本,脚本中将创建include/config.mk并将参数填充进去。
  • config.mk的内容分别定义了ARCH CPU BOARD VENDOR SOC这几个变量的值,
  • 通过把这个.mk文件include进来,其内容将在本makefile中原地展开
# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
  • CROSS_COMPILE是由我们外面的命令行传入的,不在这里定义。
# load other configuration
include $(TOPDIR)/config.mk
  • 这里包含了一个源码目录下的config.mk文件,和之前的那个hi3521a_config生成的include/config.mk不同,
  • 这个.mk文件是源码自带的,对编译属性和链接属性进行了很多设置
  • 这个位于根目录的config.mk被include进来,将在原地展开,
  • 但是由于代码量较大,故其注释写在了config.mk里面,请参考博客《海思(Hi3521a)uboot详细分析(3)——顶层config.mk分析》

5.设置规则

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

OBJS  = $(CPUDIR)/start.o
ifeq ($(CPU),i386)
OBJS += $(CPUDIR)/start16.o
OBJS += $(CPUDIR)/resetvec.o
endif
.......
.......
.......
# Add GCC lib
ifdef USE_PRIVATE_LIBGCC
ifeq ("$(USE_PRIVATE_LIBGCC)", "yes")
PLATFORM_LIBGCC = -L $(OBJTREE)/arch/$(ARCH)/lib -lgcc
else
PLATFORM_LIBGCC = -L $(USE_PRIVATE_LIBGCC) -lgcc
endif
else
PLATFORM_LIBGCC = -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
endif
PLATFORM_LIBS += $(PLATFORM_LIBGCC)
export PLATFORM_LIBS
  • 这里主要做了两件事:
  • 一将众多.o文件赋给了OBJS变量,这个变量会成为后面目标u-boot的依赖
  • 二将众多.a库文件赋给了LIBS变量,以及将和板子有关的.a文件赋给了LIBBOARD变量,这两个变量也会成为后面目标u-boot的依赖
LDPPFLAGS += \
	-include $(TOPDIR)/include/u-boot/u-boot.lds.h \
	$(shell $(LD) --version | \
	  sed -ne 's/GNU ld version \([0-9][0-9]*\)\.\([0-9][0-9]*\).*/-DLD_MAJOR=\1 -DLD_MINOR=\2/p')

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_ipl
U_BOOT_ONENAND = $(obj)u-boot-onenand.bin
ONENAND_BIN ?= $(obj)onenand_ipl/onenand-ipl-2k.bin
endif

__OBJS := $(subst $(obj),,$(OBJS))
__LIBS := $(subst $(obj),,$(LIBS)) $(subst $(obj),,$(LIBBOARD))
  • 这里应该是没有使用到的
# Always append ALL so that arch config.mk's can add custom ones
ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)

all:		$(ALL)

$(obj)u-boot.hex:	$(obj)u-boot
		$(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@

$(obj)u-boot.srec:	$(obj)u-boot
		$(OBJCOPY) -O srec $< $@

$(obj)u-boot.bin:	$(obj)u-boot
		$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@


$(obj)u-boot.ldr:	$(obj)u-boot
		$(CREATE_LDR_ENV)
		$(LDR) -T $(CONFIG_BFIN_CPU) -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
		$(obj)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.imx:       $(obj)u-boot.bin
		$(obj)tools/mkimage -n $(IMX_CONFIG) -T imximage \
		-e $(TEXT_BASE) -d $< $@

$(obj)u-boot.kwb:       $(obj)u-boot.bin
		$(obj)tools/mkimage -n $(KWD_CONFIG) -T kwbimage \
		-a $(TEXT_BASE) -e $(TEXT_BASE) -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 $< > $@

GEN_UBOOT = \
		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
$(obj)u-boot:	ddr_training depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds
		$(GEN_UBOOT)
ifeq ($(CONFIG_KALLSYMS),y)
		smap=`$(call SYSTEM_MAP,u-boot) | \
			awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \
		$(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\"" \
			-c common/system_map.c -o $(obj)common/system_map.o
		$(GEN_UBOOT) $(obj)common/system_map.o
endif
  • 本段出现了顶层makefile的第一条规则(即目标-依赖-操作),
  • 故默认情况下将以all这个变量作为最终目标。但是由于本条规则没有任何操作, 
  • 所以一旦把依赖(也就是$(ALL))实现了,整个makefile文件的最终目标就达成了
  • 可以认为,本makefile的终极目标其实是$(ALL)所代表的那些文件    
  • 下面的这些都是为了产生最终文件的规则
# Auto-generate the autoconf.mk file (which is included by all makefiles)
#
# This target actually generates 2 files; autoconf.mk and autoconf.mk.dep.
# the dep file is only include in this top level makefile to determine when
# to regenerate the autoconf.mk file.
$(obj)include/autoconf.mk.dep: $(obj)include/config.h include/common.h
	@$(XECHO) Generating $@ ; \
	set -e ; \
	: Generate the dependancies ; \
	$(CC) -x c -DDO_DEPS_ONLY -M $(HOSTCFLAGS) $(CPPFLAGS) \
		-MQ $(obj)include/autoconf.mk include/common.h > $@

$(obj)include/autoconf.mk: $(obj)include/config.h
	@$(XECHO) Generating $@ ; \
	set -e ; \
	: Extract the config macros ; \
	$(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \
		sed -n -f tools/scripts/define2mk.sed > [email protected] && \
	mv [email protected] $@
  • 本段的功能是根据include/config.h来生成autoconf.mk,
  • config.h 是在执行配置命令make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config时生成的,它的内容为:
  • #define CONFIG_BOARDDIR board/hi3520dv400
  • #include <config_defaults.h>
  • #include <configs/hi3521a.h>
  • #include <asm/config.h>
  • config.h里面的内容根据不同的配置,这里包含的头文件就不同。这主要是 为了方便uboot的移植。
  • makefile利用这些autoconf.mk中的变量来指导编译过程的走向
  • 这里的内容非常重要,决定Makefile的条件编译。

6.设置与cpu相关的伪目标

hi3521a_config: unconfig
	@$(MKCONFIG) $(@:_config=) arm hi3521a hi3521a NULL hi3521a

hi3521d_config: unconfig
	@$(MKCONFIG) $(@:_config=) arm hi3521d hi3521d NULL hi3521d

hi3520dv300_config: unconfig
	@$(MKCONFIG) $(@:_config=) arm hi3521a hi3521a NULL hi3521a
  • 本段代码主要负责了正式make之前的配置过程,即在控制台输入
  • make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config
  • 其依赖是unconfig,此变量代表了未配置的意思,通过这个方法,我们便可以多次配置。
  • MKCONFIG这个变量代表的是源码目录下最关键的一个shell文件即mkconfig,这个shell文件负责了make之前的配置过程。
  • 行首的@代表静默执行,这段代码在执行$(MKCONFIG)前还把$(@:_config=)、arm hi3521a、 hi3521a、 NULL、 hi3521a这6个主要参数传给了mkconfig
  • 其中参数$(@:_config=)是引用了一个替换函数,将该规则中的目标(用@表示)中的_config用空替换,故$(@:_config=)的值为hi3521a
  • 关于mkconfig,请参考博客《海思(Hi3521a)uboot详细分析(2)——顶层mkconfig分析》
  • 注意:对于hi3521a与hi3520dv300配置的不同
  •  对于hi3520dv300官方给的配置文件为:
  • make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3521a_config
  • make ARCH=arm CROSS_COMPILE=arm-hisiv500-linux- hi3520dv300_config 
  • 它需要同时配置hi3521a和hi3520dv300这两款设备,从它们的从hi3520dv300的配置参数$(@:_config=) arm hi3521a hi3521a NULL hi3521a 可以看出,它只有第一个参数不一样,体现到最终的配置文件,hi3521a与hi320dv300的不同配置是在./u-boot-2010/include/config.h 中包含的头问价不同,hi3520dv300包含的是#include <configs/hi3520dv300.h>,其它的都相同
  • 从这里可以看出,hi3520dv300的启动代码和连接文件等信息都是引用的hi3521a的启动文件和连接文件,只是配置信息使用的是hi3520dv300的配置
发布了164 篇原创文章 · 获赞 229 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/li_wen01/article/details/103205446