U-Boot-1.1.6 顶层 Makefile 文件分析
(针对 OK6410 开发板)
(顶层 makefile 文件内容为黑色,其他文件中的内容为蓝色,移植时修改过的代码为红色)
根据 uboot 根目录下的 Readme 文件的说明,可以知道如果想把 u-boot 使用于开发板,
应先配置,即执行 make orlinx_nand_ram256_config 命令进行配置(在顶层目录 Makefile 中加
入 forlinx_nand_ram256_config 目标等选项),然后执行 make all,就可以生成如下 3 个文件:
U-Boot.bin、U-Boot ELF 格式文件、U-Boot.srec。
1. U-Boot 的配置过程
(1)版本说明
VERSION = 1
PATCHLEVEL = 1
SUBLEVEL = 6
EXTRAVERSION =
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
VERSION_FILE = $(obj)include/version_autogenerated.h
(2)定义主机系统架构
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/macppc/ppc/)
“sed –e”表示后面跟的是一串命令脚本,而表达式“s/abc/def/”表示要从标准输入中,查
找到内容为“abc”的,然后替换成“def”。其中“abc”表达式用可以使用“.”作为通配符。命令
“uname –m”将输出主机 CPU 的体系架构类型。如电脑使用 Intel Core2 系列的 CPU,那么
“uname –m”将输出“i686”。 “i686”可以匹配命令“sed -e s/i.86/i386/”中的“i.86”,因此在机
器上执行 Makefile,HOSTARCH 将被设置成“i386” 。
(3)定义主机操作系统类型
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
export HOSTARCH HOSTOS
# Deal with colliding definitions from tcsh etc.
VENDOR=
“uname –s”输出主机内核名字,开发主机使用 Linux 发行版 fedora-12,因此“uname
–s”结果是“Linux”。“tr '[:upper:]' '[:lower:]'”作用是将标准输入中的所有大写字母转换为响应
的小写字母。因此执行结果是将 HOSTOS 设置为“linux”。
(4)定义执行 shell 脚本的 shell
# 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)
"$$BASH"的作用实质上是生成了字符串“$BASH”(前一个$号的作用是指明第二个$是
普通的字符)。若执行当前 Makefile 的 shell 中定义了“$BASH”环境变量,且文件“$BASH”
是可执行文件,则 SHELL 的值为“$BASH”。否则,若“/bin/bash”是可执行文件,则 SHELL
值为“/bin/bash”。若以上两条都不成立,则将“sh”赋值给 SHELL 变量。如果机器安装了 bash
shell,且 shell 默认环境变量中定义了“$BASH”,因此 SHELL 被设置为$BASH 。
(5)设定编译输出目录
ifdef O
ifeq ("$(origin O)", "command line")
BUILD_DIR := $(O)
endif
endif
函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量 variable 定义的方
式决定,若 variable 在命令行中定义过,则 origin 函数返回值为"command line"。假若在命
令行中执行了“export BUILD_DIR=/tmp/build”的命令,则“$(origin O)”值为“command line”,
而 BUILD_DIR 被设置为“/tmp/build”。
下面内容表示若${BUILD_DIR}表示的目录没有定义,则创建该目录:
ifneq ($(BUILD_DIR),)
saved-output := $(BUILD_DIR)
# Attempt to create a output directory.
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
下面内容表示若$(BUILD_DIR)为空,则将其赋值为当前目录路径(源代码目录)。并检
查$(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),)
在下面内容中,CURDIR 变量指示 Make 当前的工作目录,由于当前 Make 在 U-Boot
顶层目录执行 Makefile,因此 CURDIR 此时就是 U-Boot 顶层目录。执行完下面的代码后,
SRCTREE,src 变量就是 U-Boot 代码顶层目录,而 OBJTREE,obj 变量就是输出目录,若
没有定义 BUILD_DIR 环境变量,则 SRCTREE, src 变量与 OBJTREE,obj 变量都是 U-Boot
源代码目录。而 MKCONFIG 则表示 U-Boot 根目录下的 mkconfig 脚本。
OBJTREE
:= $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
SRCTREE
:= $(CURDIR)
TOPDIR
LNDIR
:= $(SRCTREE)
:= $(OBJTREE)
export TOPDIR SRCTREE OBJTREE
MKCONFIG
:= $(SRCTREE)/mkconfig
export MKCONFIG
ifneq ($(OBJTREE),$(SRCTREE))
REMOTE_BUILD
:= 1
export REMOTE_BUILD
endif
# $(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
在上面内容中,MKCONFIG
:= $(SRCTREE)/mkconfig 即在根目录下的 mkconfig 文
件。
(6)执行 make forlinx_nand_ram256_config 过程
分析这个过程有助于理解移植 U-Boot 过程中需要修改哪些文件。执行这个命令前提是
在移植 U-Boot 时,在根目录的 Makefile 中加入了类似如下的内容:
forlinx_nand_ram256_config : unconfig
@$(MKCONFIG) smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256
其中的依赖“unconfig”定义如下(Makefile 文件的 330-350 行左右):
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
其中“@”的作用是执行该命令时不在 shell 显示。“obj”变量就是编译输出的目录,因
此 “unconfig” 的 作 用 就 是 清 除 上 次 执 行 make *_config 命 令 生 成 的 配 置 文 件 ( 如
include/config.h,include/config.mk 等)。
$(MKCONFIG)在上面(5)指定为“$(SRCTREE)/mkconfig”,即根目录的 mkconfig 文件。
如果有$(@:_config=)一项,$(@:_config=)为将传进来的所有参数中的_config 替换为空(其
中“@”指规则的目标文件名,在这里就是“forlinx_nand_ram256_config ”。$(text:patternA=patternB),
这样的语法表示把 text 变量每一个元素中结尾的 patternA 的文本替换为 patternB,然后输
出)。因此$(@:_config=)的作用就是将 forlinx_nand_ram256_config 中的_config 去掉,得到
forlinx_nand_ram256,而在 OK6410 移植过的 U-BOOT 中没有$(@:_config=)项。
根据以上分析,执行完 make forlinx_nand_ram256_config,实际上执行如下命令:
./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256
所以执行 make forlinx_nand_ram256_config 即将“smdk6410 arm s3c64xx smdk6410
samsung s3c6410 NAND ram256”作为参数传递给当前目录下的 mkconfig 脚本执行。
这些参数实际意义如下:
smdk6410:Target(目标板型号)
arm:Architecture (目标板的 CPU 架构)
s3c64xx:CPU(具体使用的 CPU 型号)
smdk6410:Board
samsung:VENDOR(生产厂家名)
s3c6410:SOC
NAND:
ram256:
在 mkconfig 文件中,将进行如下几点的工作:
确定开发板名称 BOARD_NAME
APPEND=no
# no 表示创建新的配置文件,yes 表示追加到配置文件中
BOARD_NAME="" # Name to print in make output
TARGETS=""
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
[ "${BOARD_NAME}" ] || BOARD_NAME ="$1"
对于命令./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256,
其中没有“--”“-a”“-n”“-t”“*”符号,所以 while 循环里面没有做任何事情。而头两行
中的两个值仍然维持原来的值,但执行完最后一行后,BOARD_NAME 的值等于第 1 个参
数,即 smdk6410(传进的几个参数在 mkconfig 文件中以$x 表示,$0= mkconfig,$1= smdk6410,
$2= arm,$3= s3c64xx,$4= smdk6410,$5= samsung,$6= s3c6410,$7= NAND,$8= ram256)。
检查参数合法性
[ $# -lt 4 ] && exit 1
[ $# -gt 9 ] && exit 1
echo "Configuring for ${BOARD_NAME} board which boot from $7 $8 $9..."
上面代码的作用是检查参数个数和参数是否正确,参数个数少于 4 个或多于 9 个都被
认为是错误的。环境变量$#表示传递给脚本的参数个数,这里的命令有 9 个参数,因此$#
是 9。Configuring for ${BOARD_NAME} board which boot from $7 $8 $9...即执行完 make
forlinx_nand_ram256_config 命令后串口终端输出的信息。
创建到平台/开发板相关的头文件的符号连接
# 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
cd ./include
rm -f asm
ln -s asm-$2 asm # 符号连接,即软链接
else
fi
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
第一行代码判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译
U-BOOT,这可令源代码保持干净,可以同时使用不同的配置进行编译。OK6410 的 U-BOOT 移
植过的源代码是在源代码目录下编译的,所以源代码目录等于目标文件目录,所以条件不满
足,将执行 else 分支的代码。
在 else 分支的代码中,先进入 include 目录,删除 asm 文件(这是上一次配置时建立的
链接文件),然后再次建立 asm 文件,并令它链接向 asm-$2 目录,即 asm-arm 目录。此举的
作用为:在源码中常调用头文件,如#include
,对不同架构需要修
改”asm-XXX 架构”,先建立 asm 到 asm-arm 的符号链接后,以后包含头文件时直接包含
#include 即包含#include 。
1:rm -f asm-$2/arch
2:if [ -z "$6" -o "$6" = "NULL" ] ; then
3:
4:else
5:
6:fi
7:if [ "$2" = "arm" ] ; then
8:
9:
10:fi
第 1 行删除 include/asm-arm/arch 目录,对于命令./mkconfig smdk6410 arm s3c64xx
smdk6410 samsung s3c6410 NAND ram256,$6 为 S3C6410,不为空,也不为 NULL,
所以第 2 行条件不满足,执行 else 分支。第 5 行中,LNPREFIX 为空(在顶层 makefile 中未
定义),所以第 5 行即执行 ln –s arch-s3c6410 asm-arm/arch(在 U-Boot1.1.6-for-OK6410
源代码中,后面又把 include/asm-arm/arch 链接到了 include/asm-arm/arch-s3c64xx)。
第 8-9 行 表 示 : 若 目 标 板 是 arm 架 构 , 则 上 面 的 代 码 将 建 立 符 号 连 接
include/asm-arm/proc,使其链接到目录 include/asm-arm/proc-armv 目录。建立以上的链接的
好处:编译 U-Boot 时直接进入链接文件指向的目录进行编译,而不必根据不同开发板来选
择不同目录。
注意:
ln -s ${LNPREFIX}proc-armv asm-$2/proc
rm -f asm-$2/proc
在第 6 行到 7 行之间,增加了如下部分代码 for OK6410:
# create link for s3c24xx SoC
if [ "$3" = "s3c24xx" ] ; then
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
fi
# create link for s3c64xx SoC
if [ "$3" = "s3c64xx" ] ; then
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
fi
即如果"$3" = "s3c64xx",将删除 include/regs.h,并把 regs.h 链接到 include/s3c6410.h,
在此头文件中做了写 S3C6410 的寄存器定义。然后删除 include/asm-arm/arch 目录,重新建
立并把 include/asm-arm/arch 目录链接到 include/asm-arm/arch-S3C64xx 目录。
在第 9 行和 10 行之间,增加了如下部分代码:
fi
# create link for s3c64xx-mp SoC
if [ "$3" = "s3c64xx-mp" ] ; then
rm -f regs.h
ln -s $6.h regs.h
rm -f asm-$2/arch
ln -s arch-$3 asm-$2/arch
由于$3=s3c64xx,所以上面代码中条件不成立,即上面代码没有做任何事。
创建顶层 Makfile 包含的文件 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
当执行“./mkconfig smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256”
命令后,上面几行代码创建的 include/config.mk 文件内容如下:
ARCH = arm
CPU = s3c64xx
BOARD = smdk6410
VENDOR = samsung
SOC = s3c6410
指定开发板代码所在目录
(可选,如果待移植的 U-Boot 源代码中已经有了那些目录,就不需要下面的代码,比如
三星官方提供的 U-Boot 源代码。)
# Assign board directory to BOARDIR variable
if [ -z "$5" -o "$5" = "NULL" ] ; then
BOARDDIR=$4
else
BOARDDIR=$5/$4
fi
以上代码指定 board 目录下的一个目录为当前开发板专有代码的目录。若$5(VENDOR)
为 空则 BOARDDIR 设置 为 $4( BOARD ),否则 设置为 $5/$4 ( VENDOR/BOARD , 即
samsung/smdk6410)。在这里由于$5 不为空,即 BOARDDIR 被设置为 samsung/smdk6410 。
创建开发板相关的头文件 include/config.h
# Append to existing config file
echo >> config.h
1:if [ "$APPEND" = "yes" ]
2:then
3:
4:else
5:
6:fi
7:echo "/* Automatically generated - do not edit */" >>config.h
8:echo "#include " >>config.h
9:exit 0
# Create new config file
> config.h
“>” 和 “>>”为 linux 命令,> config.h 表示重新建立 config.h 文件,echo "#include
" >>config.h 表示把#include 添加到 config.h 文件中。
APPEND 维持原值”no”,所以 config.h 被重新建立,并添加了如下内容:
/* Automatically generated - do not edit */
#include
到这里,include/config.h 文件中就有以上的内容了。
注意:
在第 7 行到 8 行之间,增加如下部分代码 for OK6410:
case $7 in
SD)
echo "#define FORLINX_BOOT_SD" >> config.h
SETMMU="no"
;;
NAND)
echo "#define FORLINX_BOOT_NAND" >> config.h
SETMMU="yes"
*)
;;
;;
esac
case $8 in
ram128)
echo "#define FORLINX_BOOT_RAM128" >> config.h
> ../board/samsung/smdk6410/config.mk # clear file context
echo "ifndef TEXT_BASE" >> ../board/samsung/smdk6410/config.mk
if [ ${SETMMU} = "yes" ]
then
echo "TEXT_BASE = 0xC7E00000" >> ../board/samsung/smdk6410/config.mk
else
echo "TEXT_BASE = 0x57E00000" >> ../board/samsung/smdk6410/config.mk
fi
echo "endif" >> ../board/samsung/smdk6410/config.mk
;;
ram256)
echo "#define FORLINX_BOOT_RAM256" >> config.h
> ../board/samsung/smdk6410/config.mk # clear file context
echo "ifndef TEXT_BASE" >> ../board/samsung/smdk6410/config.mk
if [ ${SETMMU} = "yes" ]
then
echo "TEXT_BASE = 0xCFE00000" >> ../board/samsung/smdk6410/config.mk
else
echo "TEXT_BASE = 0x5FE00000" >> ../board/samsung/smdk6410/config.mk
fi
echo "endif" >> ../board/samsung/smdk6410/config.mk
*)
;;
;;
esac
if [ "$9" = "hdmi" ] ; then
echo "#define FORLINX_LCDOUT_HDMI" >> config.h
fi
对于 OK6410 开发板,$7=NAND,所以会把#define FORLINX_BOOT_NAND 添加到
include/config.h 文 件 中 , 并 把 SETMMU 值 设 置 为 yes ( 在 上 面 确 定 开 发 板 名 称
BOARD_NAME 部分代码中把 SETMMU 值设为 no,如果从 nand 启动,需要用到 mmu,所
以这里设置为 yes)。
对于 OK6410 开发板,$8=ram256,所以会把#define FORLINX_BOOT_RAM256 添加到
include/config.h 文件中,并且会把 include 的上层目录的/board/samsung/smdk6410/config.mk
文件(开发板代码所在目录)清空(执行“> ../board/samsung/smdk6410/config.mk # clear file
context”一行即实现清空), 清空后再往里面添加 ifndef TEXT_BASE,由于 SETMMU 的
值为 yes,所以再往里面添加 TEXT_BASE = 0xCFE00000(此地址为映射过的地址,使用 mmu
后,这个内存地址被映射。如果是 128 内存,此值为 0XC7E00000,只要不超过 DMC1 的最
大范围物理地址 0x6FFFFFFF 对应的虚拟地址即可),最后添加 endif。
对于 OK6410 开发板,$9 没有被传入,所以没有把#define FORLINX_LCDOUT_HDMI
添加到/include/ config.h 文件中。
总的来说,执行 make forlinx_nand_ram256_config 命令后,会在 mkconfig 脚本中进行上
面第(6)点中的所有动作,由这些动作可知,要在 board 目录下新建一个开发板< board_name >