| |
|
开发:
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)版本说明
(2)定义主机系统架构
????????“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)定义主机操作系统类型
????????“uname? –s”输出主机内核名字,开发主机使用 Linux 发行版 fedora-12,因此“uname? –s”结果是“Linux”。“tr '[:upper:]' '[:lower:]'”作用是将标准输入中的所有大写字母转换为响应的小写字母。因此执行结果是将 HOSTOS 设置为“linux”。 (4)定义执行shell脚本的shell(源码中没有这部分)
????????"$$BASH"的作用实质上是生成了字符串“$BASH”(前一个$号的作用是指明第二个$是普通的字符)。若执行当前 Makefile 的 shell 中定义了“$BASH”环境变量,且文件“$BASH”是可执行文件,则 SHELL 的值为“$BASH”。否则,若“/bin/bash”是可执行文件,则 SHELL 值为“/bin/bash”。若以上两条都不成立,则将“sh”赋值给 SHELL 变量。如果机器安装了bash? shell,且shell 默认环境变量中定义了“$BASH”,因此 SHELL 被设置为$BASH 。 (5)设定编译输出目录
????????函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量 variable 定义的方式决定,若 variable 在命令行中定义过,则origin函数返回值为"command line"。假若在命令行中执行了“export BUILD_DIR=/tmp/build”的命令,则“$(origin O)”值为“command line”,而 BUILD_DIR 被设置为“/tmp/build”。 ????????下面内容表示若${BUILD_DIR}表示的目录没有定义,则创建该目录:
????????下面内容表示若$(BUILD_DIR)为空,则将其赋值为当前目录路径(源代码目录)。并检查$(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 脚本。
????????在上面内容中,MKCONFIG????? := $(SRCTREE)/mkconfig即在根目录下的mkconfig文件。 (6)执行make ?forlinx_nand_ram256_config过程 ????????分析这个过程有助于理解移植U-Boot过程中需要修改哪些文件。执行这个命令前提是在移植U-Boot时,在根目录的Makefile中加入了类似如下的内容:
????????其中“@”的作用是执行该命令时不在 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文件中,将进行如下几点的工作:
????????对于命令./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。
[ $# -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命令后串口终端输出的信息。
????????第一行代码判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译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行删除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
????????当执行“./mkconfig ?smdk6410 ?arm ?s3c64xx ?smdk6410 ?samsung ?s3c6410 ?NAND ?ram256”命令后,上面几行代码创建的include/config.mk文件内容如下:
(可选,如果待移植的U-Boot源代码中已经有了那些目录,就不需要下面的代码,比如三星官方提供的U-Boot源代码。)
????????以上代码指定 board 目录下的一个目录为当前开发板专有代码的目录。若$5(VENDOR)为空则BOARDDIR设置为$4(BOARD),否则设置为$5/$4(VENDOR/BOARD,即samsung/smdk6410)。在这里由于$5 不为空,即BOARDDIR 被设置为 samsung/smdk6410 。
????????总的来说,执行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。此配置头文件中有以下两类宏:
#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? */
#define CFG_MALLOC_LEN? (CFG_ENV_SIZE+128*1024) #define CFG_PROMPT?? #define CFG_LOAD_ADDR? 0x50000000
????????如果在/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即返回,相关代码如下:
上面代码中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文件分析:?????
????????对于 arm 架构处理器,其中的 CROSS_COMPILE在顶层makefile文件中定义: CROSS_COMPILE = /usr/local/arm/4.3.2/bin/arm-linux-(161行左右) 因此以上代码指定了使用前缀为“arm-linux-”的编译工具,即arm-linux-gcc,arm-linux-ld 等等。
????????在这部分代码中,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剩下的内容。
????????在上面代码中的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源代码中增加了如下代码:
????????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的规则,对于300行中$(SUBDIRS),进入该目录执行“make _depend”,生成各个子目录的.depend 文件,.depend 列出每个目标文件的依赖文件。
对于version依赖,即U-Boot的版本,这里不详细介绍。274-275行为依赖SUBDIRS的规则,SUBDIRS的值222-226有定义,所以将执行tools、examples、post、post/cpu目录下的makefile。
这两个依赖在上面已经说明。
此依赖在顶层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)
对.lds文件形式的完整描述: SECTIONS {定义域中所包含的段 ... secname start BLOCK(align) (NOLOAD) : AT( ldadr ) { contents } >region :phdr =fill ... } secname和contents是必须的,其他的都是可选的。下面是几个常用的:
????????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文件分析总结
? 将“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配置头文件(所以在移植时,此配置头文件需要手动创建)
????????最后是编译、连接生成目标。即顶层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.目录结构
????????移植时需要关注的目录即比较重要的目录如下: 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第一阶段代码分析
(1)硬件设备初始化(在/CPU/S3C64xx/start.S文件中)
注意: ??? 在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文件中。
具体查看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,所以不占用内存。 ?
2) bd_t结构体 ????????保存与板子相关的配置参数(include/asm-arm/u-boot.h)
/*每个DRAM? bank的起始地址和大小,CONFIG_NR_DRAM_BANKS=1,即DRAM有两个bank */
????????U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。 3) 全局数据标志Global Data Flags #define??? GD_FLG_RELOC????? 0x00001??????? /* Code was relocated to RAM??????? */ #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)
????????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)。
????????第一个阶段在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,主要是框架):
????????对于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)
当发完0x60命令+三个地址周期+0xd0命令后,就会进行擦除,擦除大小以块为单位,即发完每一块地址后,将自动把这一块中总共128页擦除(总容量为1GB大小)。
查看第五点的第4小点。
????????向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区域。 ????????但你读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中的设备管理框架
http://blog.csdn.net/wangpengqi/article/details/8477320
In:????? serial Out:???? serial Err:???? serial 6) 开启中断 ????????在cpu/s3c64xx/libs3c64xx.a(interrupts.o)中开中断异常向量表,由于在smdk6410.h中没有宏定义CONFIG_USE_IRQ,所以如下函数什么也没有做。 void enable_interrupts(void) { return; } 7)main_loop (此函数是从board.c中的start_armboot函数中跳转而来,在common/main.c中)
????????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。
当没有按键按下时,会往下执行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例程四---定时器中断(附工 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
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- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |