一个嵌入式产品的开发阶段,需要不断地把bootloader下载到存储器中,如果存储器使用nand flash,但是第一次里面什么都没有,所以只能根据处理器的启动方式从其他方式启动如sd卡或nor存储器启动,然后在SD卡或nor存储器启动的基础之上使用USB或网络接口把u-boot.bin先下载到内存中,然后再把内存中的内容写到nand中,但是写前4页时只能写每页的前2KB数据(对于OK6410开发板来说,处理器使用S3C6410处理器,nand使用每页4KB的存储器,当从nand启动时,处理器会自动地把nand的前4页的每一页的前2KB拷贝片内8KB的SRAM中运行,这是处理器硬件所决定,所以这里只能存每一页的前2KB),前4页后的所有页都是全写。

由于u-boot开发中需要不断调试u-boot,而此时nand中已经有u-boot,所以可以从nand存储器启动,然后根据开发的下载模式下的菜单选项,可以重新下载u-boot到nand中。

当u-boot开发好后,并且从nand启动,一上电,处理器硬件会自动把nand flash的前4页中每页前2KB拷贝到片内SRAM中运行,而在SRAM中运行的代码又实现了把nand中从0到240KB(这个大小可以变)的代码拷贝到内存,然后跳到内存中运行,它在内存运行时,会申请更多的空间(除开本身占用的内存空间外,会包括12字节用于abort异常、堆栈空间、malloc内存池空间、环境参数空间、某些全局变量空间),总共2MB,详细查看(三.2.1)u-boot内存分布图。

一个嵌入式产品出厂时,在nand 存储器里面已经有了u-boot、内核、文件系统,并能实现对应功能(如果使用nand存储器,那么编译好的映像是存储在nand中的,运行是在内存中运行,对于S3C6410,先在8KB片内SRAM运行,然后才跳到内存DDR中运行)。U-boot运行时会把nand里的内核映像拷贝到内存,然后运行内核。

一、U-Boot-1.1.6顶层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      := $(SRCTREE)
LNDIR       := $(OBJTREE)
export  TOPDIR SRCTREE OBJTREEMKCONFIG  := $(SRCTREE)/mkconfig
export MKCONFIGifneq ($(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文件中,将进行如下几点的工作:

  1. 确定开发板名称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)。

注意:

在U-Boot1.1.6-for-OK6410源代码中,BOARD_NAME=""一行后还有一行SETMMU="no",表示在nand启动是用mmu,只是这里先把值设置为no。

  1. 检查参数合法性

[ $# -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命令后串口终端输出的信息。

  1. 创建到平台/开发板相关的头文件的符号连接
# Create link to architecture specific headers
if [ "$SRCTREE" != "$OBJTREE" ] ; thenmkdir -p ${OBJTREE}/includemkdir -p ${OBJTREE}/include2cd ${OBJTREE}/include2rm -f asmln -s ${SRCTREE}/include/asm-$2 asmLNPREFIX="../../include2/asm/"cd ../includerm -rf asm-$2rm -f asmmkdir asm-$2ln -s asm-$2 asm
elsecd ./includerm -f asmln -s asm-$2 asm  # 符号连接,即软链接
fi

第一行代码判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译U-BOOT,这可令源代码保持干净,可以同时使用不同的配置进行编译。OK6410的U-BOOT移植过的源代码是在源代码目录下编译的,所以源代码目录等于目标文件目录,所以条件不满足,将执行else分支的代码。

在else分支的代码中,先进入include目录,删除asm文件(这是上一次配置时建立的链接文件),然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm目录。此举的作用为:在源码中常调用头文件,如#include <asm-arm/type.h>,对不同架构需要修改”asm-XXX架构”,先建立asm到asm-arm的符号链接后,以后包含头文件时直接包含

1:rm -f asm-$2/arch
2:if [ -z "$6" -o "$6" = "NULL" ] ; then
3:       ln -s ${LNPREFIX}arch-$3 asm-$2/arch
4:else
5:       ln -s ${LNPREFIX}arch-$6 asm-$2/arch
6:fi
7:if [ "$2" = "arm" ] ; then
8:       rm -f asm-$2/proc
9:       ln -s ${LNPREFIX}proc-armv asm-$2/proc
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 时直接进入链接文件指向的目录进行编译,而不必根据不同开发板来选择不同目录。

注意:

在U-Boot1.1.6-for-OK6410源代码中,在第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
  1. 指定开发板代码所在目录

(可选,如果待移植的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 。

  1. 创建开发板相关的头文件include/config.h
if [ "$APPEND" = "yes" ]            # Append to existing config file
2:then
3:   echo >> config.h
4:else
5:       > config.h               # Create new config file
6:fi
7:echo "/* Automatically generated - do not edit */" >>config.h
8:echo "#include <configs/$1.h>" >>config.h
9:exit 0“>” 和 “>>”为linux命令,> config.h表示重新建立config.h文件,echo "#include <configs/$1.h>" >>config.h表示把#include <configs/$1.h>添加到config.h文件中。APPEND维持原值”no”,所以config.h被重新建立,并添加了如下内容:
/*  Automatically  generated  -  do  not  edit  */
#include <configs/smdk6410.h>到这里,include/config.h文件中就有以上的内容了。
注意:在U-Boot1.1.6-for-OK6410源代码中,在第7行到8行之间,增加了如下部分代码for OK6410:case $7 in
SD)echo "#define FORLINX_BOOT_SD"   >> config.hSETMMU="no";;
NAND)echo "#define FORLINX_BOOT_NAND" >> config.hSETMMU="yes";;
*);;
esac
case $8 in
ram128)echo "#define FORLINX_BOOT_RAM128" >> config.h> ../board/samsung/smdk6410/config.mk    # clear file contextecho "ifndef TEXT_BASE"  >> ../board/samsung/smdk6410/config.mkif [ ${SETMMU} = "yes" ]thenecho "TEXT_BASE = 0xC7E00000" >> ../board/samsung/smdk6410/config.mkelse echo "TEXT_BASE = 0x57E00000" >> ../board/samsung/smdk6410/config.mkfiecho "endif" >> ../board/samsung/smdk6410/config.mk;;
ram256)echo "#define FORLINX_BOOT_RAM256" >> config.h> ../board/samsung/smdk6410/config.mk # clear file contextecho "ifndef TEXT_BASE"  >> ../board/samsung/smdk6410/config.mkif [ ${SETMMU} = "yes" ]thenecho "TEXT_BASE = 0xCFE00000" >> ../board/samsung/smdk6410/config.mkelse echo "TEXT_BASE = 0x5FE00000" >> ../board/samsung/smdk6410/config.mkfiecho "endif" >> ../board/samsung/smdk6410/config.mk;;
*);;
esac
if [ "$9" = "hdmi" ] ; thenecho "#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 >目录,在include/configs目录下建立一个<board_name>.h,里面存放的就是开发板< board_name >的配置信息。

U-Boot还没有类似Linux一样的可视化配置界面(如用make menuconfig来配置),需要手动修改配置头文件/include/configs/smdk6410.h来裁剪、设置U-Boot。此配置头文件中有以下两类宏:

  • 一类是选项(Options),前缀为“CONFIG_”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:

#define CONFIG_S3C6410          1      /* in a SAMSUNG S3C6410 SoC      */

#define CONFIG_S3C64XX       1      /* in a SAMSUNG S3C64XX Family   */

#define CONFIG_SMDK6410      1      /* on a SAMSUNG SMDK6410 Board  */

  • 另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-BOOT的提示符、U-BOOT下载文件时的默认加载地址、Flash的起始地址等。比如:

#define CFG_MALLOC_LEN  (CFG_ENV_SIZE+128*1024)

#define CFG_PROMPT

#define CFG_LOAD_ADDR  0x50000000

从下面的编译、链接过程可知,U-Boot中几乎每个文件都被编译和链接,但是这样文件是否包含有效代码,则由宏开关来设置。比如对于网卡驱动drivers/dm9000x.c,它的格式为:
#include <common.h>
#include <command.h>
#include <net.h>#ifdef CONFIG_DRIVER_DM9000
#include "dm9000x.h"/*实际代码*/
…#endif             /* CONFIG_DRIVER_DM9000 */

如果在/include/configs/smdk6410.h中定义了宏CONFIG_DRIVER_DM9000,则文件中包含有效代码;否则,文件被注释为空。

可以这样认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。

2. U-Boot的编译、链接过程(接着顶层目录中的Makefile分析)

(1)顶层Config.mk文件、顶层Makefile文件剩余代码分析

配置完后,执行“ make  all ”即可编译。若没有执行过“make forlinx_nand_ram256_config”命令就直接执行“make all”命令则会出现“System not configured - see README”错误信息,

然后停止编译。

由于执行完“make forlinx_nand_ram256_config”命令后,会在include目录下创建一个config.mk文件,U-Boot就是判断是否有这个文件而确定用户是否执行过“make forlinx_nand_ram256_config”命令,注意exit 1即返回,相关代码如下:

114: ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))
…
241: all:
…
249: $(obj)u-boot.bin:   $(obj)u-boot
250:      $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@…
322: else…
327:  @echo "System not configured - see README" >&2
328:  @ exit 1
329: endif在顶层Makefile中,继续分析如下代码:
# load ARCH, BOARD, and CPU configuration  下面为与ARM相关部分
117:  include $(OBJTREE)/include/config.mk
118:  export    ARCH CPU BOARD VENDOR SOC
…
127:  ifeq ($(ARCH),arm)
128:  CROSS_COMPILE = arm-linux-
129:  endif
…
162:  export    CROSS_COMPILE
164:  # load other configuration
165:  include $(TOPDIR)/config.mk

上面代码中127-129行表示ARCH与目标机器体系架构相同,则使用 arm-linux编译器(ARCH=arm)。

注意:

在U-Boot1.1.6-for-OK6410源代码中,在162行前面加了如下代码:

CROSS_COMPILE = /usr/local/arm/4.3.2/bin/arm-linux-即不管上面如何判断等,交叉编译器始终使用/usr/local/arm/4.3.2/bin目录下的arm-linux-编译器。

在117和165行中,用于包含config.mk文件,117行将make forlinx_nand_ram256_config命令生成的include/config.mk包含进来,即配置过程中制作出来的include/config.mk文件,其中定义了ARCH/CPU/BOARD/VENDOR/SOC的值分别为arm、s3c64xx、smdk6410、samsung、s3c6410。165行将 U-Boot 顶层目录下的 config.mk 文件包含进来,该文件包含了对编译的一些设置,它根据ARCH/CPU/BOARD/VENDOR/SOC变量的值确定了编译器、编译选项等。

顶层config.mk文件分析:     

    设置 obj 与 src
ifneq ($(OBJTREE),$(SRCTREE))
ifeq ($(CURDIR),$(SRCTREE))
dir :=
else
dir := $(subst $(SRCTREE)/,,$(CURDIR))
endifobj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)
src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)$(shell mkdir -p $(obj))
else
obj :=
src :=
endif由于目标输出到源代码目录下,因此执行完上面的代码后,src 和 obj 都是空。
   设置编译选项
PLATFORM_RELFLAGS =
PLATFORM_CPPFLAGS =     #编译选项
PLATFORM_    =                 #连接选项
用这 3 个变量表示交叉编译器的编译选项,在后面 Make 会检查交叉编译器支持的编译选项,然后将适当的选项添加到这3个变量中。
   包含与开发板相关的配置文件
(跳过54到74行的代码,这些代码是在NetBSD上使用交叉编译器时需要的定义)
ifdef    ARCH
sinclude $(TOPDIR)/$(ARCH)_config.mk    # include architecture dependend rules
endif
$(ARCH)的值是“arm”,因此将顶层目录下的“arm _ config.mk”包含进来,而顶层目录的arm _ config.mk文件中基本上什么都没有做,只有一行代码用来设置PLATFORM_CPPFLAGS编译选项(与arm处理器相关)。
ifdef   CPU
sinclude $(TOPDIR)/cpu/$(CPU)/config.mk     # include  CPU  specific rules
endif
$(CPU)的值是“s3c64xx”,因此将“cpu/ s3c64xx /config.mk”包含进来。这个脚本主要
设定了跟s3c64xx处理器相关的编译选项(与arm相关的PLATFORM_CPPFLAGS编译选项)。
ifdef   SOC
sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk  # include  SoC  specific rules
endif
$(SOC)的值是s3c6410,因此Make程序尝试将cpu/s3c64xx/s3c6410/config.mk包含进来,而这个文件并不存在,但是由于用的是“sinclude”命令,所以并不会报错。
ifdef    VENDOR
BOARDDIR = $(VENDOR)/$(BOARD)
else
BOARDDIR = $(BOARD)
endif
$(BOARD)的值是smdk6410,VENDOR的值是 samsung,因此BOARDDIR的值是 samsung/ smdk6410。BOARDDIR 变量表示开发板特有的代码所在的目录。
ifdef  BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk  # include board specific rules
endif
顶层目录下的config.mk文件将“board/samsung/smdk6410/config.mk”包含进来。该脚本内容如下:
ifndef  TEXT_BASE
TEXT_BASE = 0xCFE00000
endif
U-Boot编译时将使用TEXT_BASE作为代码段连接的起始地址(这个地址是经过MMU映射过的)。
   其他代码1  (95行---115行)
CONFIG_SHELL    := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \else if [ -x /bin/bash ]; then echo /bin/bash; \else echo sh; fi ; fi)ifeq ($(HOSTOS)-$(HOSTARCH),darwin-ppc)
HOSTCC      = cc
else
HOSTCC      = gcc
endif
HOSTCFLAGS  = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
HOSTSTRIP   = stripcc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \> /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
在上面最后两行代码中,变量CC和CFLAGS在后面的代码定义为延时变量,其中的 CC 即 arm-linux-gcc。函数cc-option 用于检查编译器 CC 是否支持某选项。将 2 个选项作为参数传递给 cc-option 函数,该函数调用 CC 编译器检查参数 1 是否支持,若支持则函数返回参数 1,否则返回参数 2 (因此CC 编译器必须支持参数 1 或参数 2,若两个都不支持则会编译出错)。可以像下面这样调用cc-option 函数,并将支持的选项添加到 FLAGS 中:
FLAGS +=$(call cc-option,option1,option2)
   指定交叉编译工具
AS  = $(CROSS_COMPILE)as
LD  = $(CROSS_COMPILE)ld
CC  = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR  = $(CROSS_COMPILE)ar
NM  = $(CROSS_COMPILE)nm
STRIP   = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
RANLIB  = $(CROSS_COMPILE)RANLIB

对于 arm 架构处理器,其中的 CROSS_COMPILE在顶层makefile文件中定义:

CROSS_COMPILE = /usr/local/arm/4.3.2/bin/arm-linux-(161行左右)

因此以上代码指定了使用前缀为“arm-linux-”的编译工具,即arm-linux-gcc,arm-linux-ld 等等。

  • 其他代码2  (130行---217行)

在这部分代码中,143行(LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds)给出了LDSCRIPT的值为顶层目录下/board/Samsung/smdk6410/u-boot.lds(:=表示替换以前的值),而189行代码如下:

LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)

所以,根据189行,使LDFLAGS中包含“-Bstatic  -T /board/Samsung/smdk6410/u-boot.lds ”和“-Ttext  0xCFE00000”的字样(+=表示添加)。

  • 指定隐含的编译规则

在最后部分代码中(218-246行),指定隐含的编译规则,例如:根据以上的定义,以“.s”结尾的目标文件将根据第一条规则由同名但后缀为“.S”的源文件来生成,若不存在“.S”结尾的同名文件则根据最后一条规则由同名的“.c”文件生成。

上面就是顶层目录config.mk文件的内容,下面为顶层目录中Makefile剩下的内容。

166:#########################################################################
167:# U-Boot objects....order is important (i.e. start must be first)
168:
169:OBJS  = cpu/$(CPU)/start.o
170:ifeq ($(CPU),i386)
171:OBJS += cpu/$(CPU)/start16.o
172:OBJS += cpu/$(CPU)/reset.o
173:endif
174:ifeq ($(CPU),ppc4xx)
175:OBJS += cpu/$(CPU)/resetvec.o
176:endif
177:ifeq ($(CPU),mpc83xx)
178:OBJS += cpu/$(CPU)/resetvec.o
179:endif
180:ifeq ($(CPU),mpc85xx)
181:OBJS += cpu/$(CPU)/resetvec.o
182:endif
183:ifeq ($(CPU),mpc86xx)
184:OBJS += cpu/$(CPU)/resetvec.o
185:endif
186:ifeq ($(CPU),bf533)
187:OBJS += cpu/$(CPU)/start1.o    cpu/$(CPU)/interrupt.o  cpu/$(CPU)/cache.o
188:OBJS += cpu/$(CPU)/cplbhdlr.o  cpu/$(CPU)/cplbmgr.o    cpu/$(CPU)/flush.o
189:endif
190:
191:OBJS := $(addprefix $(obj),$(OBJS))
192:
193:LIBS  = lib_generic/libgeneric.a
194:LIBS += board/$(BOARDDIR)/lib$(BOARD).a
195:LIBS += cpu/$(CPU)/lib$(CPU).a
196:ifdef SOC
197:LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
198:endif
199:LIBS += lib_$(ARCH)/lib$(ARCH).a
200:LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
201: fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
202:LIBS += net/libnet.a
203:LIBS += disk/libdisk.a
204:LIBS += rtc/librtc.a
205:LIBS += dtt/libdtt.a
206:LIBS += drivers/libdrivers.a
207:LIBS += drivers/nand/libnand.a
208:LIBS += drivers/nand_legacy/libnand_legacy.a
209:LIBS += drivers/sk98lin/libsk98lin.a
210:LIBS += post/libpost.a post/cpu/libcpu.a
211:LIBS += common/libcommon.a
212:LIBS += $(BOARDLIBS)
213:
214:LIBS := $(addprefix $(obj),$(LIBS))
215:.PHONY : $(LIBS)
216:
217:# Add GCC lib
218:PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
219:
220:# The "tools" are needed early, so put this first
221:# Don't include stuff already done in $(LIBS)
222:SUBDIRS  = tools \
223:   examples \
224:   post \
225:   post/cpu
226:.PHONY : $(SUBDIRS)
227:
228:ifeq ($(CONFIG_NAND_U_BOOT),y)
229:NAND_SPL = nand_spl
230:U_BOOT_NAND = $(obj)u-boot-nand.bin
231:endif
232:
233:__OBJS := $(subst $(obj),,$(OBJS))
234:__LIBS := $(subst $(obj),,$(LIBS))
235:
236:#########################################################################
237:#########################################################################
238:
239:ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
240:
241:all:     $(ALL)
242:
243:$(obj)u-boot.hex:    $(obj)u-boot
244:     $(OBJCOPY) ${OBJCFLAGS} -O ihex $< $@
245:
246:$(obj)u-boot.srec:   $(obj)u-boot
247:     $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
248:
249:$(obj)u-boot.bin:    $(obj)u-boot
250:     $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
251:
252:$(obj)u-boot.img:    $(obj)u-boot.bin
253:     ./tools/mkimage -A $(ARCH) -T firmware -C none \
254:     -a $(TEXT_BASE) -e 0 \
255:     -n $(shell sed -n -e 's/.*U_BOOT_VERSION//p' $(VERSION_FILE) | \
256:         sed -e 's/"[   ]*$$/ for $(BOARD) board"/') \
257:     -d $< $@
258:
259:$(obj)u-boot.dis:    $(obj)u-boot
260:     $(OBJDUMP) -d $< > $@
261:
262:$(obj)u-boot:        depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
263: UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
264:     cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
265:         --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
266:         -Map u-boot.map -o u-boot
267:
268:$(OBJS):
269:     $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
270:
271:$(LIBS):
272:     $(MAKE) -C $(dir $(subst $(obj),,$@))
273:
274:$(SUBDIRS):
275:     $(MAKE) -C $@ all
276:
277:$(NAND_SPL): version
278:     $(MAKE) -C nand_spl/board/$(BOARDDIR) all
279:
280:$(U_BOOT_NAND):  $(NAND_SPL) $(obj)u-boot.bin
281:     cat $(obj)nand_spl/u-boot-spl-16k.bin $(obj)u-boot.bin > $(obj)u-boot-nand.bin
282:
283:version:
284:     @echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
285:     echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
286:     echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
287:          $(TOPDIR)) >> $(VERSION_FILE); \
288:     echo "\"" >> $(VERSION_FILE)
289:
290:gdbtools:
291:     $(MAKE) -C tools/gdb all || exit 1
292:
293:updater:
294:     $(MAKE) -C tools/updater all || exit 1
295:
296:env:
297:     $(MAKE) -C tools/env all || exit 1
298:
299:depend dep:
300:     for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
301:
302:tags ctags:
303:     ctags -w -o $(OBJTREE)/ctags `find $(SUBDIRS) include \
304:             lib_generic board/$(BOARDDIR) cpu/$(CPU) lib_$(ARCH) \
305:             fs/cramfs fs/fat fs/fdos fs/jffs2 \
306:             net disk rtc dtt drivers drivers/sk98lin common \
307:         \( -name CVS -prune \) -o \( -name '*.[ch]' -print \)`
308:
309:etags:
310:     etags -a -o $(OBJTREE)/etags `find $(SUBDIRS) include \
311:             lib_generic board/$(BOARDDIR) cpu/$(CPU) lib_$(ARCH) \
312:             fs/cramfs fs/fat fs/fdos fs/jffs2 \
313:             net disk rtc dtt drivers drivers/sk98lin common \
314:         \( -name CVS -prune \) -o \( -name '*.[ch]' -print \)`
315:
316:$(obj)System.map:    $(obj)u-boot
317:     @$(NM) $< | \
318:     grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
319:     sort > $(obj)System.map
320:
321:#########################################################################
322:else
323:all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \
324:$(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \
325:$(SUBDIRS) version gdbtools updater env depend \
326:dep tags ctags etags $(obj)System.map:
327: @echo "System not configured - see README" >&2
328: @ exit 1
329:endif
330:
331:.PHONY : CHANGELOG
332:CHANGELOG:
333: git log --no-merges U-Boot-1_1_5.. | \
334: unexpand -a | sed -e 's/\s\s*$$//' > $@
335:
336:#########################################################################
337:
338:unconfig:
339: @rm -f $(obj)include/config.h $(obj)include/config.mk \
340:     $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
341:

在上面代码中的169行,OBJS的第一个值为“cpu/$(CPU)/start.o”,即“cpu/s3c64xx/start.o”,根据上一行的注释,u-boot中OBJS(目标)非常重要,start.o必须放在第一个,即首先编译的start.s。在上面代码的170-191行为其他架构CPU的目标,在这里没什么用处。

注意:

175-177行的代码在U-Boot1.1.6-for-OK6410源代码中没有,理论上有没有无影响。

在代码193行到218行中,LIBS 变量指明了 U-Boot 需要的库文件,包括平台/开发板相关的目录、通用目录下相应的库,都通过相应的子目录编译得到的,在215行中,定义伪目标(.PHONY : $(LIBS)),意在当前目录下如果有LIBS变量对应的值(即U-Boot 需要的库文件,.a文件)时,执行make后不会显示“*.a is up-to-date”,而且会重新编译生成对应的.a文件。

注意:在208-209行之间,U-Boot1.1.6-for-OK6410源代码中增加了如下代码:

# add to support onenand. by scsuh
LIBS += drivers/onenand/libonenand.a
ifeq ($(CPU),mpc83xx)
LIBS += drivers/qe/qe.a
endif增加对onenand flash的支持。

228到231行是些与平台无关的代码,因为对于某些开发板(包括OK6410),u-boot支持在nand flash启动,这些开发板配置文件中可能宏定义了CONFIG_NAND_U_BOOT,这样在239行依赖U_BOOT_NAND不会为空。239行代码即在执行make all后,将要生成u-boot.srec,u-boot.bin,System.map。其中u-boot.srec 是 Motorola S-Record format 文件,System.map 是 U-Boot 的符号表,u-boot.bin 是最终烧写到开发板的二进制可执行的文件。如果U_BOOT_NAND不为空,还将生成u-boot-nand.bin文件。

OBJS、LIBS所代表的.o、.a文件就是U-boot的构成,它们通过268-272行命令,由相应的源文件(或相应子目录下的文件)编译得到。第268-269行规则表示,对于OBJS的每个成员,都将进入cpu/$(CPU)目录(即cpu/s3c64xx)编译它们,对于smdk6410开发板,OBJS为cpu/s3c64xx/start.o,它将由cpu/s3c64xx/start.S编译得到。第271-272行规则表示,对于LIBS中的每个成员,都将进入相应的子目录执行“make”命令。这些子目录的Makefile,结构相似,它们将Makefile中指定的文件编译、链接成一个库文件。

当所有的OBJS、LIBS所表示的.o和.a文件都生成后,最后连接,这对应上面代码中的243到267行,先使用262-266行的规则链接得到ELF格式的U-BOOT,最后转换为二进制格式的u-boot.bin、S-Record格式的U-Boot.srec等等(即243-261行)。对于ELF格式的U-Boot的依赖,下面分别介绍:

  • 依赖目标depend

行为依赖目标depend的规则,对于300行中$(SUBDIRS),进入该目录执行“make _depend”,生成各个子目录的.depend 文件,.depend 列出每个目标文件的依赖文件。

  • 依赖 SUBDIRS

对于version依赖,即U-Boot的版本,这里不详细介绍。274-275行为依赖SUBDIRS的规则,SUBDIRS的值222-226有定义,所以将执行tools、examples、post、post/cpu目录下的makefile。

  • 依赖OBJS、LIBS

这两个依赖在上面已经说明。

  • 依赖LDSCRIPT

此依赖在顶层config.mk文件中有定义,LDSCRIPT的值为顶层目录下/board/Samsung/smdk6410/u-boot.lds。

U-BOOT规则命令中,264-266行表示真正连接,LDFLAGS确定了连接方式,LDFLAGS里/board/Samsung/smdk6410/u-boot.lds ”和“-Ttext  0xCFE00000”的字样(根据顶层config.mk文件),这些字样指定了程序的布局、地址(但是连接起始地址为连接脚本中的偏移地址0+0xCFE00000)。所以,$(LDFLAGS)即使用u-boot.lds链接脚本及-Ttext  0xCFE00000。执行连接命令其实就是把 start.o 和各个子目录 makefile 生成的库文件按照 LDFLAGS 连接在一起,生成 ELF 文件 u-boot 和连接时内存分配图文件 u-boot.map。

注意:在上面代码中250到251行之间, U-Boot1.1.6-for-OK6410源代码中增加了一行代码:$(OBJDUMP) -d $< > $<.dis。目的是生成U-Boot的反汇编文件。

顶层Makefile代码中341行以后的代码都是添加CPU架构相关的_config文件(整个 makefile 剩下的内容全部是各种不同的开发板的*_config:目标的定义),本开发板OK6410添加了如下(当然还有三星s3c24及64系列的其他处理器):

forlinx_nand_ram256_config :  unconfig

@$(MKCONFIG) smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256

注意:在顶层目录makefile文件的末尾部分,是执行make clean后的规则,在U-Boot1.1.6-for-OK6410源代码中增加了二行代码:分别在2261行后加了:

-o -name '*~' -o -name '.depend*' \

在2263行后增加了:

rm -f u-boot*

(2)链接脚本分析(/board/Samsung/smdk6410/u-boot.lds)

24:OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
25:/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
26:OUTPUT_ARCH(arm)   /* 指定输出平台为arm */
27:ENTRY(_start)
28:SECTIONS
29:{
30:  . = 0x00000000;
31:
32:  . = ALIGN(4);  /* 代码以4字节对齐 */
33:  .text      :    /* 指定代码段,.text的基地址由LDFLAGS中-Ttext $(TEXT_BASE)指定*/
34:  {
35:    cpu/s3c64xx/start.o   (.text)  /* 代码段的第一个代码部份 */
36:    cpu/s3c64xx/s3c6410/cpu_init.o    (.text)
37:    cpu/s3c64xx/onenand_cp.o  (.text)
38:    cpu/s3c64xx/nand_cp.o (.text)
39:    cpu/s3c64xx/movi.o (.text)
40:    *(.text)                  /*其他代码部分*/
41:    lib_arm/div0.o
42:  }
43:
44:  . = ALIGN(4);
45:  .rodata : { *(.rodata) }   /*指定只读数据段*/
46:
47:  . = ALIGN(4);
48:  .data : { *(.data) }     /*指定读或写数据段*/
49:
50:  . = ALIGN(4);
51:  .got : { *(.got) }
52:
53:  __u_boot_cmd_start = .;
54:  .u_boot_cmd : { *(.u_boot_cmd) }
55:  __u_boot_cmd_end = .;
56:
57:  . = ALIGN(4);
58:  .mmudata : { *(.mmudata) }
59:
60:  . = ALIGN(4);
61:  __bss_start = .;     /*把__u_boot_start赋值为当前位置,即bss段的开始位置*/
62:  .bss : { *(.bss) }    /*指定bss段*/
63:  _end = .;          /*把_end赋值为当前位置,即bss段的结束位置*/
64:}

对.lds文件形式的完整描述:

SECTIONS {定义域中所包含的段

...

secname start BLOCK(align) (NOLOAD) : AT( ldadr )

{ contents } >region :phdr =fill

...

}

secname和contents是必须的,其他的都是可选的。下面是几个常用的:

  1. secname:段名,如文本段、数据段、只读数据段、BSS段
  2. contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
  1. start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。

U-Boot的链接脚本体现了很多原材料是怎么组成U-Boot的,谁放在前面,谁放在后面等。在上面代码30,表示起始地址为0,但要加上链接脚本相同目录下的config.mk文件中的内容TEXT_BASE(值为0xCFE00000,经过MMU映射过)的值,所以U-Boot的链接地址为0xCFE00000,即运行于0xCFE00000处。也就是说,系统启动从偏移地址0处开始,这里的0只是个代码地址偏移值,真正连接起始地址由编译连接时LDFLAGS指定。

24行表示指定输出可执行文件是elf格式,31 位ARM指令,小端存储,

35行表示CPU/S3C64xx/start.S放在代码段的最前面,保证最先执行的是 start.o,所以U-Boot的入口点也在CPU/S3C64xx/start.S中。36-39行必须放在U-Boot的前8KB,所以也放在最前面,包括存储器初始化等。其中38行包含包含 U-Boot 从 NAND Flash 搬运自身的代码。

50-51行表示U-Boot自定义的的got段,53/55行表示将__u_boot_cmd_start指定为当前地址,54行表示存放所有U-Boot命令对应的cmd_tbl_t 结构体。

3. U-Boot顶层Makefile文件分析总结

  • 确定U-Boot版本信息
  • 确定开发主机架构和操作系统类型
  • 确定源代码目录和输出目录,即src、SRCTREE、obj、OBJTREE的值
  • MKCONFIG    := $(SRCTREE)/mkconfig即执行make forlinx_nand_ram256_config即

将“smdk6410  arm  s3c64xx  smdk6410  samsung  s3c6410  NAND  ram256”作为参数传递给当前目录下的mkconfig 脚本执行,所以此时跳到当前目录的mkconfig脚本中执行。在此脚本中大致工作如下:

(1)建立符号链接

(2)创建顶层Makefile包含的/include/config.mk文件(移植时此文件不需要手动创建)

(3)指定开发板相关目录,即/Board/samsung/smdk6410目录。(在移植时,这个目录及里面的内容需要自己创建或修改,比如u-boot.lds、lowlevel_init.S、smdk6410.c、config.mk)

(4)创建开发板相关的头文件include/config.h(移植时此文件不需要手动创建),此头文件中包含了/include/configs/smdk6410.h配置头文件(所以在移植时,此配置头文件需要手动创建)

  • 包含在mkconfig脚本中创建的/include/config.mk文件(只要执行“make forlinx_nand_ram256_config”命令,就会在顶层目录中的Makefile中调用执行mkconfig脚本,在里面会创建此config.mk文件)
  • 包含顶层目录下的config.mk文件,在顶层目录里的config.mk文件还会包含开发板相关目录/Board/samsung/smdk6410/config.mk文件。

最后是编译、连接生成目标。即顶层Makefile实际工作如下:

(1)首先编译/CPU/S3C64xx/start.S。

(2)然后,对于平台/开发板相关的每个目录、每个通用目录都使用它们各自的Makefile生成相应的库。

(3)将1、2步骤生成的.o、.a文件按照/board/Samsung/smdk6410/config.mk文件中指定的代码段起始地址、U-Boot.lds连接脚本进行连接。

(4)把第3步得到的ELF格式的U-Boot转换为二进制格式、S-Record格式。

当然在移植时,cpu/s3c64xx和cpu/s3c64xx/s3c6410目录下的文件也需要修改或创建。

二、U-Boot-1.1.6目录结构、启动模式、启动流程

1.目录结构

Board:存放一些已有开发板有关的文件。每一个开发板都以一个子目录出现在当前目录中,
子目录中存放与开发板相关的配置文件。在board\samsung\smdk6410目录下有:
makefile
config.mk
smdk6410.c 和板子相关的代码(以smdk6410为例)
lowlevel_init.S
flash.c Flash操作代码
smdk6410_val.h
u-boot.lds 对应的连接文件
common:通用,实现uboot命令行下支持的命令,每一条命令都对应一个文件。例如bootm命令对应就是cmd_bootm.c。另外还有写较重要的c文件,如main.c、hush.c等。
cpu:与特定CPU架构相关目录,每一款Uboot下支持的CPU在该目录下对应一个子目录,
比如有子目录s3c64xx等。在cpu\s3c64xx主要有如下文件(不是全部):
makefile
config.mk
cpu.c 和处理器相关的代码
interrupts.c 中断处理代码
serial.c 串口初始化代码
start.S 全局开始启动代码
在cpu\s3c64xx\s3c6410下有如下文件:
cpu_init.S、Makefile、speed.c、libs3c6410.a
lib_xxxx: 与体系结构相关的库文件。如与ARM相关的库放在lib_arm中。
lib_microblaze: 存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数。
lib_generic: 通用,对所有体系结构通用库函数的实现,如vsprintf、string等函数实现。
Include: Uboot使用的头文件,还有对各种硬件平台支持的汇编文件,系统的配置文件和对文件系统支持的文件。该目录下configs目录有与开发板相关的配置头文件,如smdk6410.h。该目录下的asm目录有与CPU体系结构相关的头文件,asm被建立符号链接到asm-arm。
drivers:通用,Uboot支持的设备驱动程序都放在该目录,比如各种网卡、支持CFI的Flash、串口和USB等。
disk:通用,对磁盘的支持,硬盘接口驱动程序
dtt:通用,传感器的驱动程序
fs:通用,存放文件系统相关的程序, Uboot现在支持cramfs、fat、fdos、jffs2和registerfs。
nand_spl:通用,Nand Flash boot的程序
net:通用,存放网络相关的程序,即与网络协议栈相关的代码,BOOTP协议、TFTP协议、RARP协议和NFS文件系统的实现。
Post:通用,存放上电自检的程序
Rtc:通用,实时时钟(RTC)的驱动程序
Examples:应用例程,一些独立运行的应用程序的例子,例如helloworld
Tools:工具,存放制作S-Record或者U-boot格式的映像等工具,例如mkimage
Doc:文档,开发使用文档

移植时需要关注的目录即比较重要的目录如下:

Include、include/configs、board/Samsung/smdk6410、cpu/s3c64xx、cpu/s3c64xx/s3c6410、common、lib_arm、drivers、lib_generic、nand_spl等。

2.启动模式介绍

大多数Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。

启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到RAM 中运行,整个过程并没有用户的介入。这种模式是BootLoader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

下载(Downloading)模式:在这种模式下,目标机上的Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被BootLoader 保存到目标机的RAM 中,然后再被BootLoader 写到目标机上的FLASH 类固态存储设备中。BootLoader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用BootLoader 的这种工作模式。工作于这种模式下的Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

UBoot这样功能强大的Boot Loader 同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。大多数bootloader都分为阶段1(stage1)和阶段2(stage2)两大部分,uboot也不例外。依赖于CPU体系结构的代码(如CPU初始化代码等)通常都放在阶段1中且通常用汇编语言实现,而阶段2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。

3.启动流程

u-boot的stage1代码通常放在start.s和文件lowlevel——init.S中,它用汇编语言写成。对于第二阶段,即C语言代码部分,lib_arm/board.c中的start_armboot是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot) 的主函。

三、U-Boot启动过程源代码分析

1.U-Boot第一阶段代码分析

U-Boot属于两阶段的Bootloader,第一阶段的文件为/CPU/S3C64xx/start.S和board/Samsung/smdk6410/lowlevel_init.S。前者是平台相关的,后者是开发板相关的。
|-->lowlevel_init: |
|-->cup_init_crit: |              |-->cpu_init_crit:|
_start: -->reset|                                       |-->reset: -->relocate: -->_start_armboot:-->Start_armboot() -->main_loop() ---|
↑        |
|________|
u-boot-1.1.6/cpu/xxx/Start.S   _start:
u-boot-1.1.6/cpu/xxx/Start.S   reset:
u-boot-1.1.6/cpu/xxx/Start.S   cup_init_crit:
u-boot-1.1.6/board/Samsung/yyy/lowlevel_init.S lowlevel_init:
u-boot-1.1.6/cpu/xxx/Start.S   relocate:
u-boot-1.1.6/cpu/xxx/Start.S   _start_armboot:
u-boot-1.1.6/lib_arm/board.c   start_armboot()
u-boot-1.1.6/common/main.c     main_loop()
说明:xxx(板子上具体的cpu型号目录,如arm920t、S3C64xx)
yyy(开发板的型号目录,如smdk2410、smdk6410)

(1)硬件设备初始化(在/CPU/S3C64xx/start.S文件中)

  • 设置全局入口
  • 跳到reset处,即U-boot的第2条指令
  • 使CPU进入SVC管理模式
  • 初始化CPU关键寄存器,如清除指令缓存I Cache和数据缓存D Cache,禁止MMU等。

注意:

在U-Boot1.1.6-for-OK6410源代码中,由于使用nand启动,所以后面代码开启了MMU。

对于start.S文件详细部分查看本文档目录的注释过的start.S文件,并配合前面裸跑程序中的start.S、本目录下的u-boot.lds文件、及“Uboot中start_S源码的指令级的详尽解析.mht”。

(2)进一步初始化

在CPU/S3C64xx/start.S文件中执行bl  lowlevel_init指令,进入board/Samsung/smdk6410/lowlevel_init.S文件中进行进一步初始化,注意内存初始化在cpu\s3c64xx\s3c6410\cpu_init.S文件中。

  • 首先把lr保存在r12中,然后设置些GPIO和led等
  • 设置MEM1DRVCON 寄存器的每位写为0,即5mA
  • 关看门狗
  • 把中断方式改为irq,并清理中断。
  • 初始化系统时钟
  • 初始化串口
  • 初始化nand flash  (简单初始化,设置时间参数,使能nand控制器等)
  • MMU Table for SMDK6410
  • 初始化DDR

具体查看board/Samsung/smdk6410/lowlevel_init.S文件,里面有详细注释,对于系统时钟、串口、nand、DDR的初始化可以参考裸跑部分的驱动。

(3)代码搬运,即重定位

先判断此时的PC指针是否已经指向内存,即判断此时是否已经在内存中运行,由于从nand启动(前8KB是拷贝到片内SRAM中运行的),所以此时不可能在DDR中,所以需要代码重定位。

从lowlevel_init.S文件中返回后,跳入copy_from_nand标号处,此时还是在Start.S文件中,直到跳到C函数接口copy_uboot_to_ram,此接口的代码在cpu\s3c64xx\nand_cp.c中,在此文件中,对于OK6410的nand flash,拷贝U-Boot到内存中时,是从nand的0地址处开始拷贝,拷贝到DDR的0x5FE00000处,拷贝大小为0x3c000。

重定位完后打开了MMU。

(4)设置堆栈与清理BSS段

在/CPU/S3C64xx/start.S文件中,由于使能了MMU,所以此时的堆栈指针已经在0xC7FFFFF4位置处了。

在跳转到第二阶段入口之前需要清理BSS段,初始值为0、无初始值的全局变量、静态变量放在BSS段。

2.U-Boot第二阶段代码分析

在/CPU/S3C64xx/start.S文件中,使能了MMU,然后重新设置了堆栈,清理完BSS段后,C函数的运行环境已经完全准备好,然后通过ldr   pc, _start_armboot指令跳到lib_arm/board.c文件中。此条指令后的程序才在内存DDR中执行(因为此时跳转时,使用了与位置相关指令ldr,此时会跳转到连接地址处)。

前面介绍了u-boot启动第一阶段(汇编部分),在那里进行了一些简单的初始化,并且为C语言的执行建立的环境(堆栈),从汇编到C执行的第一个函数( start_armboot (),在lib_arm\board.c中),该函数进行了一系列的外设初始化,然后调用main_loop (),根据配置来选择是直接加载Linux内核还是进入等待命令模式。

(1)U-Boot内存分布

在u-boot配置、编译后,连接时,生成了u-boot.map文件,即U-Boot的内存分配图文件。此文件主要用来debug查错,比如在u-boot源代码中使用一个函数,但是此函数在多个文件中定义,哪个文件中定义的函数才是我们真正使用过、有用的呢,此时借助u-boot.map文件查找此函数,就会得到此函数路径等信息(但是除开宏定义)。

在u-boot配置、编译后,连接时,生成了system.map文件,即u-boot符号表。它包含了 U-Boot 的全局变量和函数的地址信息。也就是将 arm-linux-nm 命令查看 u-boot 的输出信息经过过滤和排序后输出到 System.map。System.map 表示的是地址标号到该标号表示的地址的一个映射关系。System.map 每一行的格式都是“addr type name”,addr 是标号对应的地址值,name 是标号名,type 表示标号的类型。U-Boot的编译和运行并不一定要生成 System.map,这个文件主要是提供给用户或外部程序调试时使用的。下图就是参考了u-boot.map和system.map文件。


(2)U-Boot中几个重要的数据结构(其他关于nand的结构查看本目录下的struct_and_other.c)

1)gd_t结构体

该数据结构保存了u-boot需要的配置信息(include/asm-arm/Global_data.h)

        只要用到gd_t的地方就需要用宏定义进行声明:DECLARE_GLOBAL_DATA_PTR,指定占用寄存器R8,所以不占用内存。

typedef    struct    global_data {     bd_t            *bd;   //与板子相关的结构,参见 include/asm-arm/u-boot.h     unsigned long    flags;  //指示标志,如设备已经初始化完标志等,在console_init_r函数中被赋值gd->flags |= GD_FLG_DEVINIT。
unsigned long    baudrate;   //串口波特率
unsigned long    have_console;    /* serial_init() was called ,即串口初始化标志,在console_init_f函数中被设置为1*/       unsigned long    reloc_off;    /* 重定义偏移,实际定向的位置与编译连接时指定
的位置之差,一般为0 */       unsigned long    env_addr;     /* 环境参数地址,即存放default_environment数组
的首地址&default_environment[0] ,在env_init函数中被设置,在后面的env_relocate函数中,又被赋为&(env_ptr->data) ,起始此时的env_ptr->data 也是指向&default_environment[0] */
unsigned long    env_valid;     /* 环境参数CRC检验有效标志,在env_init函数中被设置为1*/       unsigned long    fb_base;   /* base address of frame buffer */
#ifdef  CONFIG_VFD  //我们一般没有配置这个,fb_base是frame buffer的首地址     unsigned char  vfd_type;   /* display type */
#endif
void        **jt;       /* jump table  跳转表,在U-Boot1.1.6中用来函数调用地址登记,在board.c文件的start_armboot函数中调用了jumptable_init ()函数,在此函数中设置*/
} gd_t;

2) bd_t结构体

保存与板子相关的配置参数(include/asm-arm/u-boot.h)

typedef struct bd_info {int          bi_baudrate;    /* serial console baudrate */unsigned long  bi_ip_addr; /* IP Address   在board.c的start_armboot函数中被赋值*/unsigned char bi_enetaddr[6]; /* Ethernet adress  MAC地址,在board.c的start_armboot函数中被赋值*/struct environment_s        *bi_env;  /*环境变量结构*/ulong            bi_arch_number; /* unique id for this board   标识板子唯一的id*/ulong          bi_boot_params; /* where this board expects params 启动参数 */

/*每个DRAM  bank的起始地址和大小,CONFIG_NR_DRAM_BANKS=1,即DRAM有两个bank */

struct{ulong start;  //在dram_init函数中被设置为了PHYS_SDRAM_1,即0x50000000
ulong size;  //在dram_init函数中被设置为256MB
}bi_dram[CONFIG_NR_DRAM_BANKS];  #ifdef  CONFIG_HAS_ETH1/* second onboard ethernet port */unsigned char   bi_enet1addr[6];
#endif
} bd_t;

U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。

3) 全局数据标志Global Data Flags

#define    GD_FLG_RELOC      0x00001        /* Code was relocated to RAM        */
#define    GD_FLG_DEVINIT    0x00002        /* Devices have been initialized    */
#define    GD_FLG_SILENT     0x00004        /* Silent mode                */

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t  *gd  asm ("r8")

定义gd为gd_t类型指针,存储在寄存器r8中,register表示变量对于执行速度非常重要,因此应该放在处理器的寄存器中(寄存器独立于内存,通常在处理器芯片上) ,volatile用于指定变量的值可以由外部过程异步修改,例如中断例程。

(3)start_armboot函数分析(\uboot1.1.6\lib_arm\board.c)

结合“自己总结资料文档\U-Boot学习”目录下的board.c文件和其他文档理解此函数。start_armboot()函数代码里有许多的宏开关,供用户根据自己开发板的情况进行配置。此函数中调用的时钟、串口初始化函数基本上什么也没做,因为以前已经初始化过了(包括DDR)

  1. U-Boot的库
在顶层Makefile中,190行左右,有如下的规则:
LIBS  = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
LIBS += lib_$(ARCH)/lib$(ARCH).a
…
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a                     //如board.c中的nand_init函数需要的库函数
LIBS += drivers/nand_legacy/libnand_legacy.a
…
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a                      //如打印函数printf函数需要的库函数
LIBS += $(BOARDLIBS)
(“+=”在makefile中是添加等号后面的值)

LIBS 变量指明了 U-Boot 需要的库文件,包括平台/开发板相关的目录、通用目录下相应的库,都通过相应的子目录编译得到。在U-Boot的第二阶段代码中有许多函数调用并不是source insight追到的地方,要结合u-boot.map文件找到最终调用关系。

        2)U-boot中MTD设备框架(如Nand_init函数)

(涉及结构体:mtd_info、nand_chip、nand_flash_dev、nand_flash_ids、nand_ecclayout、nand_ecc_ctrl等)

根据上面的描述,在本文件中调用nand_init函数实际上是调用库,因为drivers/nand/nand.c被制作进库libnand.a中,而根据u-boot.map文件可知,nand_init函数最终调用路径为drivers/nand/libnand.a(nand.o)。

  1. 对于nand存储器部分的初始化,分为三个阶段:

第一个阶段在u-boot第一阶段代码中的lowlevel_init.S中进行过简单的初始化。首先设置NFCONF寄存器(0x70200000), 把bit4-6、bit8-10、bit12-14设置为1,即把TACLS、TWRPH0、TWRPH1都设置为7(时间参数);设置NAND存储器控制器寄存器(0x70200004),把bit0设为1即使能,把bit1设置为1,即强制 nGCS[2]为高(禁用片选),但是只有在bit0为1时才有效。

第二个阶段在重定位代码时,nand_cp.c中进行了nand的复位,并实现了读页、读块函数。只是在每次读或复位操作之前,需要使能片选,操作完成后再禁止片选(设置NAND存储器控制器寄存器(0x70200004)的bit1)。

第三阶段在drives/nand/nand.c中(主要是做比较细节的初始化+用高级的代码让后面操作nand更容易,这里的高级代码实际就是U-BOOT中MTD框架),nand_init函数不但会打印OK6410开发板使用的nand flash的大小,还会初始化结构体nand_info和结构体nand_chip。nand_info[i]是描述nand的结构体,nand_chip[i]是代表“nand”的设备结构体,下面是它们的具体介绍:

nand_info[i] :nand_info_t  nand_info[CFG_MAX_NAND_DEVICE];

typedef  struct  mtd_info  nand_info_t;

上面的定义关系分别在nand.c、nand.h、mtd.h中有定义,最终的mtd_info结构体是mtd层对设备的抽象和对块设备的接口统一。

nand_chip[i] :static struct nand_chip nand_chip[CFG_MAX_NAND_DEVICE];

nand.h中的nand_chip结构体;

上面的定义关系分别在nand.c、nand.h中有定义,最终的nand_chip结构体是设备的实体,所有对设备的读写控制操作都最终通过这个结构体完成。

(具体详细注释的代码在uboot1.1.6-for-OK6410的SI工程中)大致框架如下(基于2440和不同版本的u-boot,主要是框架):

    lib_arm/board.c中start_armboot()函数中调用了nand_init()。
   nand_init()函数定义在drivers/nand/Nand.c文件中,其最终目的是在串口终端上打印出NAND的容量。它调用了同文件下的nand_init_chip()函数,此函数的三个参数分别对应&nand_info[i]、&nand_chip[i]、base_address[i],如果开发板上只有一块nand,那么i=0表示第一块MTD设备。
   nand_init_chip()函数初始化了IO_ADDR_R和IO_ADDR_W,前后调用board_nand_init()和nand_scan()函数,board_nand_init()函数在cpu/s3c64xx/nand.c中,nand_scan()函数在drivers/nand/nand_base.c中。
   board_nand_init()函数主要初始化nand_chip结构体,比如给此结构体中过的一些函数指针赋值,让他们指向自己为nand驱动编写的一些函数。
   nand_scan()函数→nand_scan_ident函数→nand_set_defaults和nand_get_flash_type函数。其中nand_set_defaults函数设置mtd设备层的接口函数并且继续给nand_chip结构体的函数指针赋值;nand_get_flash_type函数主要的功能还是再此获取nand芯片的厂商信息和ID,并判断是否支持,如果支持为这个nand设备和mtd结构体填充一些功能接口函数。nand_scan()函数→nand_scan_tail函数,此函数进行了ECC的设置和剩下的MTD驱动函数的初始化,完后返回0。
   nand_select_device()函数未执行,最后返回nand_init(),nand的初始化结束。

        对于NAND芯片来说,基本操作流程如下 (下面涉及到的函数是U-boot源代码中的函数):

在初始化nand芯片的前提下,nand的读、写、擦除等操作都是先发命令,然后发地址。由于OK6410开发板使用的nand flash地址线、数据线、命令线是复用的,共8根,8根线是发的地址、数据、还是命令是通过CLE引脚和ALE引脚控制,如果命令锁存使能信号CLE为高电平时候,传送的是命令,当ALE为高电平时传送的是地址,当都为低电平时,传送的是数据,详见芯片手册P17的真值表,这里是异步模式,同步模式是ALE与CLE都为高是输入输出数据。

在u-boot1.1.6中,发送命令、地址的函数为nand_chip结构体一个函数指针,如:

void (*cmd_ctrl)(struct mtd_info *mtd, int dat,unsigned int ctrl);

此函数指针指向s3c_nand_hwcontrol函数,此函数的第3个参数决定是发命令还是地址,如果值或上了宏NAND_CLE,即发命令,或上了NAND_ALE即发地址。第2个参数为相应的命令或地址。在发命令或地址之前,此函数中还会控制NFCONT寄存器,使能片选。(写命令对应的寄存器为NFCMMD,写地址对应的寄存器为NFADDR)

发完命令、地址后,就可以通过NFDATA寄存器获得数据。

(注意,发命令、发地址都有的在chip->cmdfunc中,而这个函数指针又指向nand_command_lp)

  1. NAND存储设备的擦除操作

当发完0x60命令+三个地址周期+0xd0命令后,就会进行擦除,擦除大小以块为单位,即发完每一块地址后,将自动把这一块中总共128页擦除(总容量为1GB大小)。

  1. NAND 读写操作

查看第五点的第4小点。

  1. NAND Flash读写操作ECC原理

向NAND Flash写数据时,每256或512字节会生成一个校验码写在每个page的OOB区,当从NAND Flash读数据时,每读取256或512字节数据,也会生成一个ECC校验码,拿这个校验码与存放在OOB区的校验吗对比看看是否一致,就可以知道读取的数据是否正确.

一般来说,kernel与cramfs是通过U-BOOT烧写到NAND Flash中,这个过程是向NAND Flash写数据,会产生ECC码,使用的是U—BOOT的ECC机制产生的(不管是硬件产生还是软件产生),然后这些ECC码会存放在NAND Flash的OOB区域。
当你启动Kernel时,会从NAND Flash中读数据,这是时候也会产生ECC码。使用的还是U—BOOT的ECC机制产生的,因为这个读NAND操作是还是处在U-Boot运行状态下。然后这个ECC码会与写Kernel时产生的ECC码(保存在OOB区域)做比较,看是否有错误。由于写Kernel与读Kernel都用的是U-BOOT的ECC产生机制,所以一般不会出错。

但你读Cramfs时,这个时候已经是内核状态啦。所以读Cramfs时用的Kernel的ECC产生机制,前面写Cramfs用的U-Boot机制,如果两种机制不一致(包括一个是硬件产生,一个是软件产生,或是软件产生算法不一样,或是OOB区域规划不一样),就会产生错误。

3) 环境变量初始化

对于U-BOOT1.1.6,环境变量在flash中存储范围为0x100000-0x180000共512KB,在系统第一次启动时,nand flash中一般没有存放环境变量的,所以在第二阶段C初始化代码中检验时会出错,当时的处理是把gd_t结构的env_addr成员指向env_ptr->data,而env_ptr->data也是指向字符串数组default_environment的,在default_environment数组中有9个默认环境变量,这里涉及U-BOOT重要结构体gd_t、bd_t中的gd_t结构(查看上面第2点)。

gd_t结构体的env_addr成员在env_init函数中被设置为&default_environment[0],即默认值。在lib_arm/board.c中调用env_relocate函数后,又被赋为&(env_ptr->data) ,其实此时的env_ptr->data 也是指向&default_environment[0]。

在env_relocate函数中,首先在内存中分配一段512KB的空间env_ptr用来存放环境变量(临时的),然后从nand flash中,将环境变量从CFG_ENV_OFFSET(1MB处)处读出,读出大小为CFG_ENV_SIZE(512KB),读到env_ptr所指的地方去,读出数据后再调用crc32对env_ptr->data进行校验并与保存在 env_ptr->crc 的校验码对比,看数据是否出错(如果出错,将使用默认,即从default_environment所指的内存地址的起始位置开始拷贝default_environment_size个字节到目标env_ptr->data所指的内存地址的起始位置中)。

从这里也可以看出在系统第一次启动时,如果Nand Flash里面没有存储任何环境变量,crc校验肯定会出错(在串口终端中输出Warning - bad CRC or NAND, using default environment的信息),当我们保存环境变量后,接下来再启动板子u-boot就不会再报crc32出错了。(需继续研究U-Boot中环境变量的存储和调用)

4 ) 网络初始化

网络初始化分为IP地址初始化和MAC地址初始化,注意里面查环境参数表的两个for循环没有仔细看。而OK6410使用的网卡还没初始化。

5)U-BOOT中的设备管理框架

  1. 查看本目录下的文档:<U-BOOT中的设备管理框架>
  2. devices_init ()函数还可参考如下链接:

http://blog.csdn.net/wangpengqi/article/details/8477320

  1. 由于在devices_init ()函数中,注册了串口设备,设备列表list里的设备数 ,注册第一个设备"serial",为0。在后面的console_init_r ()函数中设定一个控制台,然后在串口终端中输出如下信息:

In:      serial

Out:     serial

Err:     serial

6) 开启中断

在cpu/s3c64xx/libs3c64xx.a(interrupts.o)中开中断异常向量表,由于在smdk6410.h中没有宏定义CONFIG_USE_IRQ,所以如下函数什么也没有做。

void enable_interrupts(void)

{

return;

}

7main_loop

(此函数是从board.c中的start_armboot函数中跳转而来,在common/main.c中)

  1. Boot延迟操作

Boot延迟即进入了main_loop之后,会等待一段时间(环境变量"bootdelay"的值)来判断键盘上是否有任意键按下,如果按下就进入U-BOOT的下载模式,否则直接引导操作系统。

由于CONFIG_BOOTDELAY宏被定义,所以在main_loop里面先初始化boot延迟操作,先通过getenv函数得到"bootdelay"环境变量的地址,然后通过simple_strtol函数得到的环境变量的值赋给bootdelay。

在if判断中执行abortboot函数,在里面先会向串口终端输出Hit any key to stop autoboot:  1的字样。先会判断是否有键按下,如果有,则把Hit any key to stop autoboot:  1的字样的1改为0,并退出此函数返回1。如果此时判断没有键按下,先会把bootdelay变量设置为0,然后等待一秒钟,在这一秒之中会不断地判断是否有键按下,如果还是没有按键按下,则终止此函数并返回0,如果有键按下,终止函数返回1。

  1. U-BOOT下载模式(NAND_ARMMenu函数)

当没有按键按下时,会往下执行NAND_ARMMenu函数,在函数中先会打印如下信息:

###################### User Menu for OK6410#####################

[1] Format the nand flash

[2] Burn image from USB

[3] configure the lcd size

[4] Boot the system

[5] Reboot the u-boot

[6] Exit to command line

-----------------------------Select---------------------------------

Enter your Selection:

注意在u-boot中getc()函数和tstc ()等函数都是递归调用实现,即如果串口终端中没有输入时,是不会返回的。

当有输入时,getc()函数返回输入的字符,然后printf输入的序号,根据序号判断进入相应的函数。

NAND_ARMMenu函数是一个while(1)循环,会一直执行里面的内容,不会退出。

基于ARM处理器的U-BOOT详细移植总结相关推荐

  1. [转载]基于ARM的linux内核裁剪与移植

    基于ARM的linux内核裁剪与移植 http://bbs.elecfans.com/forum.php?mod=viewthread&tid=185020  wutaimin( 楼主 ) 2 ...

  2. 《嵌入式Linux应用开发完全手册》——1.2 基于ARM处理器的嵌入式Linux系统

    本节书摘来自异步社区<嵌入式Linux应用开发完全手册>一书中的第1章,第1.2节,作者 韦东山,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.2 基于AR ...

  3. 基于ARM处理器的LCD控制及触摸屏接口设计

    作者:menuconfig 转自:http://blog.csdn.net/menuconfig/article/details/2621231 研究了一种基于ARM处理器的嵌入式网络收音机的设计方案 ...

  4. arm linux sms,基于arm处理器的手机短消息加密系统 encryption system for sms based on arm.pdf...

    基于arm处理器的手机短消息加密系统 encryption system for sms based on arm 第 22卷 第 期 电子测量与仪器学报 Vol.22 No.2 2 4 - - 20 ...

  5. 基于ARM处理器的无线SoC实现

    随着新一代移动通信技术的快速发展,无线SoC已经成为物联网产业发展的基础,在嵌入式领域中占有越来越重要的地位.实际生活中,无线通信应用几乎无处不在,例如目前智能家居主流2.4G无线通信方案Zigbee ...

  6. 基于ARM处理器的“云计算”是下一个计算浪潮

    p { margin-bottom: 0.21cm; } 今天,我们的话题是:基于 ARM 处理器的 "云计算"(即互联网的大型"计算节点") 可能存在吗?为何 ...

  7. 【2022集创赛】安谋科技杯三等奖:基于ARM处理器的无线SoC设计

    本篇文章是2022年第六届全国大学生集成电路创新创业大赛安谋科技杯三等奖作品分享,参加极术社区的**[有奖征集]分享你的2022集创赛作品,秀出作品风采**活动. 团队介绍 参赛单位:厦门大学 队伍名 ...

  8. 恩智浦arm芯片Linux,基于ARM处理器的工业控制系列【恩智浦】

    中国上海,2011年2月22日 -- 恩智浦半导体(NXP Semiconductors N.V.)今天宣布推出其基于ARM? Cortex?-M0处理器的LPC1200工业控制系列.LPC1200进 ...

  9. linux 挂iscisc存储,基于arm的嵌入式linux操作系统的移植研究-通信与信息系统专业论文.docx...

    基于arm的嵌入式linux操作系统的移植研究-通信与信息系统专业论文 Classified Index: TP316.8 U.D.C: 621.38 Dissertation for the Mas ...

  10. 服务器可否替代手机芯片,基于ARM的处理器能取代桌面处理器吗?

    科技的发展速度太快了,从手机进入智能时代开始,手机处理器的速度也越来越快了,以前人们办公都是在电脑端进行的,随着手机的功能越来越多,手机处理器速度越来越快,已经有一小部分的工作可以在手机上完成了.那么 ...

最新文章

  1. UiBot新版本即将上线!添加Java程序支持!
  2. Linux高级篇——IO系统编程
  3. mysql利用外连接删除数据_MySQL-快速入门(6)连接查询、子查询、正则表达式查询、数据的插入删除更新...
  4. 蓝桥杯省内模拟赛解题过程
  5. SOA的十大技术理论体系
  6. 在线VLOOKUP数据查找工具
  7. 计算机设备硬件设备,计算机硬件设备有哪些
  8. 自己开发个游戏其实挺简单(这是一个神奇的游戏-看透美女其实很容易)
  9. 无人机影像的植被覆盖度、叶面积指数估算
  10. 约束优化方法_2_——Frank-Wolfe方法
  11. html excel2007打开很慢,excel打开很慢,教您Excel表格打开后很卡很慢怎么办
  12. 中企海外周报 | 哈弗F7x性能版在俄罗斯上市;徐工首家海外银行落户巴西
  13. 算法(赛马问题)图解
  14. 华盛顿大学西雅图 计算机科学 申请条件,华盛顿大学西雅图分校申请有哪些条件...
  15. STRM--解决因配置Streams而在alert中出现的 ORA-02068错误
  16. STM32 串口 FIFO
  17. android模拟遥控器home点击
  18. Git 常用的命令之避免尴尬
  19. 【STM32】STM32CUBEMX + ADC(单通道,双通道DMA)
  20. STM32的USART中RTS、CTS的作用和意义

热门文章

  1. C语言str函数系列
  2. 1999年秋浙江省计算机等级考试二级c 编程题,2004年秋浙江省计算机等级考试二级C 编程题(2) (C++代码)...
  3. android 添加蒙版实现护眼模式(夜间模式)
  4. 函数论_E.C.Tichmarsh_Page 4 级数一致收敛的魏尔斯特拉斯 M-判别法 的推广
  5. 清明:直面骄阳、向死而生
  6. 用纯JavaScript实现的微信二维码图片生成器
  7. Aggressive cows--二分法思想
  8. Java日期计算总结(包含工作日排除节假日)
  9. cocos2d-x开源游戏引擎,C++开发iphone/android/uphone/win32游戏
  10. 用python查询生成国内法定节假日安排