uboot是一款嵌入式的bootloader软件,能支持多种arch、cpu、board等。uboot的编译分成几个阶段:配置和编译。配置主要是选择单板的配置参数和cpu等信息;编译主要是根据配置信息进行编译。uboot源代码目录下README中说编译两步是 make NAME_config make all两个命令。现来看看make config这个是怎么回事。
我们以分析apollon_config为例进行分析,先把相关的依赖关系贴出来:
apollon_config : unconfig
@mkdir -p $(obj)include
@echo "#define CONFIG_ONENAND_U_BOOT" > $(obj)include/config.h
@echo "CONFIG_ONENAND_U_BOOT = y" >> $(obj)include/config.mk
@$(MKCONFIG) $@ arm arm1136 apollon - omap24xx
这里apollon_config首先是依赖于unconfig,这个主要是作一些清理工作,现不去了解。接下来生成一个$(obj)include目录,$(obj)是生成obj的目录,默认是uboot源码目录下,可以通过O=/your/obj/dir来指定,然后接下来两句是定义一些单板相关的宏输出到config.h和config.mk中,这两个非常重要,它就是生成的配置文件,接下来调用mkconfig脚本,进一步生成配置信息。首先mkconfig的入参格式可以参考boards.cfg里面的格式:
# Target ARCH CPU Board name Vendor SoC Options
###########################################################################################################
integratorcp_cm1136 arm arm1136 integrator armltd - integratorcp
qong arm arm1136 - davedenx mx31
mx31ads arm arm1136 - freescale mx31
imx31_litekit arm arm1136 - logicpd mx31
mx35pdk arm arm1136 - freescale mx35
omap2420h4 arm arm1136 - ti omap24xx
tnetv107x_evm arm arm1176 tnetv107xevm ti tnetv107x
integratorap_cm720t arm arm720t integrator armltd - integratorap
integratorap_cm920t arm arm920t integrator armltd - integratorap
integratorcp_cm920t arm arm920t integrator armltd - integratorcp
a320evb arm arm920t - faraday a320
at91rm9200ek arm arm920t at91rm9200ek atmel at91 at91rm9200ek
at91rm9200ek_ram arm arm920t at91rm9200ek atmel at91 at91rm9200ek:RAMBOOT
接下来主要就是分析mkconfig这个文件它到底做了些什么事情:
if [ \( $# -eq 2 \) -a \( "$1" = "-A" \) ] ; then
# Automatic mode
line=`egrep -i "^[[:space:]]*${2}[[:space:]]" boards.cfg` || {
echo "make: *** No rule to make target \`$2_config'. Stop." >&2
exit 1
}
set ${line}
# add default board name if needed
[ $# = 3 ] && set ${line} ${1}
fi
第一段主要是分析输入参数是否只有两个并且第一个参数是-A,上面调到boards.cfg里面已经定义好了单板的配置,因此我们如果把单板信息放在这里的话,就不需要在makefile里面些很多参数了,直接 mkconfig -A boardname即可。在mkconfig中会自动去boards.cfg中去找。接下来就是使用egrep命令把单板名字$2的那行找出来,如果失败的话说明没有找到相应的单板,退出。如果找到的话,我们需要的信息都有了,于是我们set $(line) 这句话就是将line的信息设置成系统的位置变量。解析来我们就进行解析了。
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%_config}" ; shift ;;
-t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;
*) break ;;
esac
done
这一段还是对输入特殊符号进行处理,得到相关信息,上面的那个配置信息并没有这些特殊符号,暂时跳过。
[ $# -lt 4 ] && exit 1
[ $# -gt 7 ] && exit 1
这两句主要对参数总数进行判断,检查是否参数过少或者过多的情况。
CONFIG_NAME="${1%_config}"
[ "${BOARD_NAME}" ] || BOARD_NAME="${1%_config}"
第一句是获得配置名,对应到这里$1 = apollon_config,为了获取名字使用了%_config,这是一个bash字符匹配的用法,即从后面开始匹配_config字符,然后
去掉最短的匹配部分获得apollon,这就是配置的名字。第二句如果boardname没有赋值的话,那么boardname就跟配置名一样。
arch="$2"
cpu="$3"
if [ "$4" = "-" ] ; then
board=${BOARD_NAME}
else
board="$4"
fi
这几句主要是获取ach、cpu、board的信息,格式参考上面的boards.cfg。
[ $# -gt 4 ] && [ "$5" != "-" ] && vendor="$5"
[ $# -gt 5 ] && [ "$6" != "-" ] && soc="$6"
[ $# -gt 6 ] && [ "$7" != "-" ] && {
# check if we have a board config name in the options field
# the options field mave have a board config name and a list
# of options, both separated by a colon (':'); the options are
# separated by commas (',').
#
# Check for board name
tmp="${7%:*}"
if [ "$tmp" ] ; then
CONFIG_NAME="$tmp"
fi
# Check if we only have a colon...
if [ "${tmp}" != "$7" ] ; then
options=${7#*:}
TARGETS="`echo ${options} | sed 's:,: :g'` ${TARGETS}"
fi
}
这几句是获取 vendor、soc、以及options。这里解析$7还是有点麻烦,主要是这里有两类格式:
integratorap_cm926ejs arm arm926ejs integrator armltd - integratorap
versatileqemu arm arm926ejs versatile armltd versatile versatile:ARCH_VERSATILE_QEMU,ARCH_VERSATILE_PB
这里options跟config name之间是:,options之间是用,间隔。所以获取的时候还需要转化下:即把,去掉,在这里处理的时候使用了一个sed命令sed 's:,: :g'即将所有的,全部换成空格。
if [ "${ARCH}" -a "${ARCH}" != "${arch}" ]; then
echo "Failed: \$ARCH=${ARCH}, should be '${arch}' for ${BOARD_NAME}" 1>&2
exit 1
fi
这里对arch进行了检查,至此我们的解析工作基本已经完成了,下面就需要根据这些信息生成配置信息了。
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/arch/${arch}/include/asm asm
LNPREFIX=${SRCTREE}/arch/${arch}/include/asm/
cd ../include
mkdir -p asm
else
cd ./include
rm -f asm
ln -s ../arch/${arch}/include/asm asm
fi
rm -f asm/arch
if [ -z "${soc}" ] ; then
ln -s ${LNPREFIX}arch-${cpu} asm/arch
else
ln -s ${LNPREFIX}arch-${soc} asm/arch
fi
if [ "${arch}" = "arm" ] ; then
rm -f asm/proc
ln -s ${LNPREFIX}proc-armv asm/proc
fi
上面主要是判断下是不是obj存放目录是不是跟本地一致,如果不一致的话需要另外创建文件等等。
echo "ARCH = ${arch}" > config.mk
echo "CPU = ${cpu}" >> config.mk
echo "BOARD = ${board}" >> config.mk
[ "${vendor}" ] && echo "VENDOR = ${vendor}" >> config.mk
[ "${soc}" ] && echo "SOC = ${soc}" >> config.mk
这几句就是将解析所得到的arch、cpu、board、vendor、soc等信息输入到config.mk中,以便以后编译的时候使用。
接下来是config.h
if [ -z "${vendor}" ] ; then
BOARDDIR=${board}
else
BOARDDIR=${vendor}/${board}
fi
#
# 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
for i in ${TARGETS} ; do
i="`echo ${i} | sed '/=/ {s/=/ /;q; } ; { s/$/ 1/; }'`"
echo "#define CONFIG_${i}" >>config.h ;
done
cat << EOF >> config.h
#define CONFIG_BOARDDIR board/$BOARDDIR
#include <config_cmd_defaults.h>
#include <config_defaults.h>
#include <configs/${CONFIG_NAME}.h>
#include <asm/config.h>
EOF
下面跟上面的类似,主要是options处理这块稍微麻烦点。这里先说下options的处理方式:变量定义两种形式:NAME ;NAME = VALUE。对于只有NAME没有赋值的默认等于1,在config.h中这些配置信息的形式是#define CONFIG_NAME VALUE。所以需要对options进行下处理,sed '/=/ {s/=/ /;q; } ; { s/$/ 1/; }‘,即对于有=符号的去掉,没有的在加个 1,也就是默认为1;然后再config信息输出到config.h中。这样我们的config.mk,config.h都有了。接下来就可以根据这些信息进行make了
我们在看看make all里面怎么怎么使用这些配置信息的:
一处是这里:include $(obj)include/config.mk
export ARCH CPU BOARD VENDOR SOC
直接将之前生成的config.mk包进来了,然后将config.mk的几个重要的全局信息export出来。
还有一处是在:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk
其中 $(obj)include/autoconf.mk的依赖在:
$(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 > $@.tmp && \
mv $@.tmp $@
为什么还需要这个呢?不是配置已经生成了,这里我们需要留意下标准的makefile定义是CONFIG_NAME y或者其他,y表示要编译的,否则不编译。前面我们看到的处理默认是1 ,因此还需要转化下,这个任务就是 tools/scripts/define2mk.sed完成的。
/^#define CONFIG_[A-Za-z0-9_][A-Za-z0-9_]*/ {
# Strip the #define prefix
s/#define *//;
# Change to form CONFIG_*=VALUE
s/ */=/;
# Drop trailing spaces
s/ *$//;
# drop quotes around string values
s/="\(.*\)"$/=\1/;
# Concatenate string values
s/" *"//g;
# Assume strings as default - add quotes around values
s/=\(..*\)/="\1"/;
# but remove again from decimal numbers
s/="\([0-9][0-9]*\)"/=\1/;
# ... and from hex numbers
s/="\(0[Xx][0-9a-fA-F][0-9a-fA-F]*\)"/=\1/;
# Change '1' and empty values to "y" (not perfect, but
# supports conditional compilation in the makefiles
s/=$/=y/;
s/=1$/=y/;
# print the line
p
}
这个脚本是将宏转化成makefile的变量,最后两句就是将=1或则=后面没有赋值的赋值为=y。