U-Boot Makefile分析(1)配置脚本mkconfig分析

时间:2023-03-08 16:37:37

  我们在编译U-Boot之前,需要根据当前使用的板子进行配置,例如make s5p_goni_config,接着才能进行编译make。下面首先分析配置阶段U-Boot做了哪些事情。

  由于执行这些命令是在源码根目录下,所以需要到主Makefile中找一下s5p_goni_config这个目标,搜索结果显示没有匹配的信息,后来发现其实U-Boot设计者已经将配置选项设计成通用模式了:

%_config:: unconfig

  @$(MKCONFIG) -A $(@:_config=)

  上面的写法比较通用,因为U-Boot支持很多板卡,不同使用者会使用不同的配置,如make mini2440_config; make smdk2410_config; make zynq_config等等,假如每个板卡都在Makefiile中占用一个目标,那么整个Makefile显得很冗余,不如用脚本处理下。

  回到上面的配置目标和依赖描述,显然s5p_goni_config是一个伪目标,依赖是unconfig,不知道为什么这里目标和依赖之间是两个冒号,小纠结。。。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

  伪目标下面的命令总是要执行的:删除include/config.h include/config.mk include/autoconf.mk include/autoconf.mk.dep,以及各个板卡的临时配置信息文件config.tmp。因此,每次我们执行make s5p_goni_config之类的配置命令时,都会先清除之前的配置。接下来呢?接下就要执行@$(MKCONFIG) -A $(@:_config=)了,$(MKCONFIG) 是在主Makefile前面部分定义的变量:

MKCONFIG := $(SRCTREE)/mkconfig

  mkconfig是源码根目录下的一个脚本文件,$(@:_config=)是makefile中变量替换的一种方式,表示把目标$@变量的_config部分替换为空,效果上就是删除_config后缀,因此我们就得到了下面的信息:

s5p_goni_config:: unconfig

  mkconfig -A s5p_goni

 接下来,我们的任务是分析mkconfig脚本,下面分析主要流程。

  首先定义以下变量,概括地说,mkconfig脚本的主要功能就是根据用户传入的参数,获取以下各变量的值,然后再自动产生一些头文件。

 APPEND=no
BOARD_NAME=""
TARGETS="" arch=""
cpu=""
board=""
vendor=""
soc=""
options=""

  下面是关键的一步。

 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} [ $# = 3 ] && set ${line} ${1}
elif [ "${MAKEFLAGS+set}${MAKELEVEL+set}" = "setset" ] ; then
# only warn when using a config target in the Makefile
cat <<-EOF warning: Please migrate to boards.cfg. Failure to do so will
mean removal of your board in the next release. EOF
sleep 5
fi

  显然我们的参数个数和内容是符合分支判断要求的,因此执行那条文本数据处理命令,目的是在boards.cfg文件中根据s5p_goni匹配一行信息,这条信息放在line变量中,如果返回值为空字符串那么就报错退出,提示没有用户所说的这块板子的相关配置。那么boards.cfg文件中是什么内容呢?它是目前U-Boot支持板卡的信息汇总,通过这个文件,我们可以知道U-Boot支持哪些架构、哪些处理器、哪些SoC、哪些board,是一个很好的参考,它的每一个entry都是有格式要求的:

Target   ARCH   CPU   Board name   Vendor   SoC     Option

s5p_goni  arm    armv7    goni    samsung  s5pc1xx 

zynq    arm    armv7    zynq     xilinx   zynq 

mini2440   arm    arm920t  mini2440  friendlyARM s3c24x0

  上面的格式很简单,不多说,如果将来想支持自己的板卡,那么加入自己的选项就好了。比如

tiny210 arm armv7   tiny210 frirndlyARM s5pc1xx

  

  回到脚本分析,set ${line}这条语句不简单,我由于不知道这句话的含义,纠结了很久,后来做实验才明白这句话的作用:改变当前脚本的参数$# $1 $2 $3...,这些参数的内容完全由{line}决定。此时它的内容就是s5p_goni那一行,那么

$# = 6

$1 = s5p_goni  $2 = arm  $3 = armv7  $4 = goni  $5 = samsung  $6 = s5pc1xx

 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 # Strip all options and/or _config suffixes
CONFIG_NAME="${1%_config}" [ "${BOARD_NAME}" ] || BOARD_NAME="${1%_config}" arch="$2"
cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $1}'`
spl_cpu=`echo $3 | awk 'BEGIN {FS = ":"} ; {print $2}'`
if [ "$4" = "-" ] ; then
board=${BOARD_NAME}
else
board="$4"
fi
[ $# -gt 4 ] && [ "$5" != "-" ] && vendor="$5"
[ $# -gt 5 ] && [ "$6" != "-" ] && soc="$6"
[ $# -gt 6 ] && [ "$7" != "-" ] && {
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
} if [ "${ARCH}" -a "${ARCH}" != "${arch}" ]; then
echo "Failed: \$ARCH=${ARCH}, should be '${arch}' for ${BOARD_NAME}" 1>&2
exit 1
fi if [ "$options" ] ; then
echo "Configuring for ${BOARD_NAME} - Board: ${CONFIG_NAME}, Options: ${options}"
else
echo "Configuring for ${BOARD_NAME} board..."
fi

  上面代码的while循环对于当前这个板卡是没有作用的,且明显参数个数是满足继续运行的要求的,接下来就开始赋值了

CONFIG_NAME = s5p_goni

BOARD_NAME = s5p_goni

arch = arm

cpu = armv7

spl_cpu =

board = goni

vendor = samsung

soc = s5pc1xx

  这里我们没有第七个参数,因此相关的option置空,那么配置的时候屏幕会输出一句“Configuring for s5p_goni board...”。

  重要的变量都得到了,下面开始第二项重要工作,建立相关软链接和头文件。

 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

  上面SRCTREE与OBJTREE是相同的,代表所有源码都在本地,再本地编译链接,得到目标,因此执行else分支:再当前shell运行的子进程中进入include文件夹下,删除其下面的asm(asm是一个软链接文件),接着为arch/arm/include/asm文件夹建立软链接asm。

 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

  首先删除asm/arch,注意!此处的asm已经是软链接了,所以删除的是arch/arm/include/asm/arch(其实arch也是软链接文件)。soc变量长度非零,因此为arch/arm/include/asm/arch-s5pc1xx 建立软链接asm/arch。其实上面两段代码的作用就是建立asm和arch两个软链接,以后在C语言包含头文件时,头文件的路径写起来就比较方便。

 ( echo "ARCH   = ${arch}"
if [ ! -z "$spl_cpu" ] ; then
echo 'ifeq ($(CONFIG_SPL_BUILD),y)'
echo "CPU = ${spl_cpu}"
echo "else"
echo "CPU = ${cpu}"
echo "endif"
else
echo "CPU = ${cpu}"
fi
echo "BOARD = ${board}" [ "${vendor}" ] && echo "VENDOR = ${vendor}"
[ "${soc}" ] && echo "SOC = ${soc}"
exit 0 ) > config.mk # Assign board directory to BOARDIR variable
if [ -z "${vendor}" ] ; then
BOARDDIR=${board}
else
BOARDDIR=${vendor}/${board}
fi

  向include/config.mk中写入一些信息:

ARCH = arm
CPU = armv7
BOARD = goni
VENDOR = samsung
SOC = s5pc1xx

  打开include/config.mk发现其内容和上述一致。上面最后一句BOARDDIR=samsung/goni,其实这就提示我们,在boards.cfg文件中添加信息后,还要在board目录下建立vendor/board目录,两者都要有,mkdir -p vendor/board。

 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 exit 0

  我们这块板子并没有append option,所以直接建立config.h文件,建立的方式比较简单:> config.h,第一次见到这种建立空白文件的方式,新技能get!接着在该文件的头部添加一句提示语。最后,向config.h写入一些信息,这里只是包含一些其他头文件:

#define CONFIG_BOARDDIR  board/samsung/goni

#include <config_cmd_defaults.h>

#define <config_defaults.h>

#define <configs/s5p_goni.h>  //这就对应当前板子的头文件,应该放在include/configs目录下。

#include <asm/config.h>

注意脚本中向文件写内容方式:

cat << EOF >> filename

line1

line2

....

EOF