IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 基于ARM处理器的U-BOOT详细移植总结 -> 正文阅读

[嵌入式]基于ARM处理器的U-BOOT详细移植总结

????????一个嵌入式产品的开发阶段,需要不断地把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文件分析

(顶层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 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文件中,将进行如下几点的工作:

  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" ] ; 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
else
	cd ./include
	rm -f asm
	ln -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$6S3C6410,不为空,也不为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 目录下的一个目录为当前开发板专有代码的目录。若$5VENDOR)为空则BOARDDIR设置为$4BOARD),否则设置为$5/$4VENDOR/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.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 >目录,在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))
endif

obj := $(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	= strip

cc-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-gccarm-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

...

}

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

  1. secname:段名,如文本段、数据段、只读数据段、BSS
  2. contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
  1. start:本段连接(运行)的地址,如果没有使用ATldadr),本段存储的地址也是startGNU网站上说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)循环,会一直执行里面的内容,不会退出。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-10-08 11:56:38  更:2021-10-08 11:56:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:27:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码