1.源码结构分析
首先一个问题,老版本的u-boot是没有SPL这个文件的,新版u-boot开始包含SPL文件,原来u-boot启动比如放到nand中,在cpu内部有一个stepping stone,可以拷贝nand中的u-boot到ram中运行,然后u-boot自己再启动第二阶段在对应内存中好到系统的image启动。现在加了这个SPL之后,我的理解这是一个u-boot的loader。及cpu上电后,首先运行这个spl,然后通过这个spl再将u-boot放到对应的位置运行,之后的操作就和老版本基本一样了。至于为什么这样做,暂时还不明白,后期再研究下。
编译成功的u-boot-2013.10共有19个子目录,大约15个有用的文件,其中各个子目录和重要文件功能见下表:
名称 |
类型 |
功能说明 |
api |
通用 |
U-boot提供的一些接口函数 |
arch |
平台相关 |
当前U-BOOT重要的目录,arch下每个子目录代表一种处理器类型 |
board |
平台相关 |
里面有很多支持的开发板型号,这里关注具体开发板和config.mk |
common |
通用 |
主要跟U-BOOT的命令有关,cmd_xxx.c以及环境变量的处理代码env_xxx.c |
spl |
平台相关 |
u-boot的第一阶段相关,搬运第二阶段代码到内存中 |
disk |
通用 |
磁盘驱动的分区处理代码 |
doc |
说明文档 |
可以用来做配置参考 |
drivers |
通用 |
设备的驱动程序,每种类型一个子目录包括网卡,USB,LCD等 |
dts |
通用 |
设备树的控制,主要是由于LINUX 3.X中去除了很多冗余的代码,引入device tree,许多硬件细节可以直接传递给LINUX,是新的东西 |
examples |
通用 |
一些示例程序 |
fs |
通用 |
文件系统支持 |
include |
通用 |
头文件和开发板的配制,configs子目录重要 |
lib |
通用 |
通用的库文件 |
Licenses |
通用 |
可以忽略。。。 |
nand_spl |
平台相关 |
支持了部分平台的nand启动 |
net |
通用 |
网络相关的代码,小型的协议栈 |
post |
通用 |
加电自检程序 |
scripts |
通用 |
脚本程序 |
test |
通用 |
测试程序 |
tools |
通用 |
工具,mkimage就在这里 |
boards.cfg |
文件,平台相关 |
修改添加开发板配置现在在此处 |
Makefile MAKEALL config.mk rules.mk mkconfig |
文件,通用 |
整个U-BOOT编译过程的规则文件 |
kbuild mkconfig |
文件,通用 |
对Makefile功能的补充,使得编译更加高效 |
其余 |
文件,通用 |
介绍文档以及其他 |
移植工作主要集中在一些编译规则文件,还有board和arch目录下。
2 Makefile分析
u-boot的README里面其实讲的很清楚u-boot的移植过程,翻译过来如下:
第一步:在boards.cfg里面添加自己的开发板,必须按照现有的规则添加。
第二步:为自己的开发板建立一个目录,在目录下添加你需要的文件,这目录下必须要有以下几个文件,Makefile,
第三步:为你的建立一个新的配置文件“include/configs/
第四步:输入“make
第五步:make
第六步:调试并解决出现的问题(当然,这一步远比听起来的难很多)
Makefile的分析可以了解整个U-boot的代码结构是怎样的,文件是如何编译、链接的。
2.1分析配置过程第一步
在编译的时候第一步是输入:make wandboard_config,当输入这个指令的时候,Makefile就会调用以下语句:
%_config:: unconfig
@$(MKCONFIG) -A $(@:_config=)
%通配符匹配到执行xxx_config的时候,就调用下面的@$(MKCONFIG),这个MKCONFIG可以搜索在以下定义:
MKCONFIG := $(SRCTREE)/mkconfig
export MKCONFIG
2.2分析mkconfig文件
mkconfig为$(SRCTREE)/目录下的mkconfig文件,就是我们源码目录下的mkconfig文件,也就是说,我们输入了make wandboard_config之后,就执行了mkconfig。
mkconfig里面其实就是根据输入的板子的型号,这里是wandboard,调用boards.cfg文件,将arch cpu soc vender board等信息全部读出来,然后解析这些信息,进行通用头文件和库文件的自动配置,比如arm平台,很多lib库和头文件都是可以共用的。就在这一步生成头文件和很多宏,并将我们的板子的宏配置进去,如下所示为boards.cfg和Imx6平台相关的内容:
boards.cfg文件与imx6相关配置 展开原码
expand source?
|
这里调用了boards.cfg文件,切进去查看该文件,这个文件里面其实定义了u-boot可以支持的所有开发板,如下图:
2.3分析建立软连接过程
mkconfig建立软连接 展开原码
expand source?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
以上是建立软连接的过程,if [ "$SRCTREE" != "$OBJTREE" ] ; then 表示判断源码目录是不是我们目标文件生产的目录,显然是的,我们生成的目标文件是在u-boot源码目录下的,所以直接跳到else后面,执行下面的语句:
cd ./include
rm -f asm
ln -s ../arch/${arch}/include/asm asm
切换到源码目录的include目录下,删除asm软连接,然后将上一级目录下arch/arm/include/asm目录链接到这个目录来,这是建立了第一个软连接。可以看得到:
接着rm -f asm/arch删除asm目录下的arch软连接,下面的代码:
建立软连接2
?
1 2 3 4 5 6 7 8 9 |
|
首先判断soc是否为空,这里soc显然不为空,执行ln -s ${LNPREFIX}arch-${soc} asm/arch ,这里LNPREFIX为空,所以这句其实就是ln -s ./arch-mx6 asm/arch,下面的同样是将arm相关的proc链接进去。
结果可以通过ls -l来查看:
2.4生成config.mk和头文件
生成config.mk文件
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
上面的代码其实就是判断有没有定义spl_cpu如果定义了那就将spl_cpu信息输出到CPU,这里没有定义,因此依次就是确定了CPU.BOARD.SOC这些信息,最后一句> config.mk将以上信息输出到config.mk后退出。可以切换到./include/目录下,看到一个config.mk文件,打开看到如下内容:
接着mkconfig文件还做了自动生成头文件的工作,这部分代码如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
首先检查config.h存在否,如果不存在就建立一个config,h,然后依次定义宏到config.h中,最后加入一些arm平台下通用的头文件,最后保存退出。打开config.h文件,可以清晰看到如下内容:
这里并未定义文件里面的前四行内容,应该是手动添加进去的,确定mxl是否是有SPL启动,具体是哪个型号,然后根据具体型号再做一个配置,这里写到imx6image.cfg文件里查看。
imx6img.cfg
?
1 2 3 4 5 6 7 8 9 |
|
这个文件加入了另外几个头文件,猜测这个文件是和启动方式有关的配置文件,这里又加入了clocks.cfg文件,配置了启动时候的时钟,这部分代码后面分析启动过程的时候再分析。
2.5编译
这部分代码比较多,但是主要完成了以下几个剩余的工作:
1.u-boot版本号确认及语言环境确认
2.解析make后面传入的参数,例如make -v=1之类的,这里我们没有
3.指定源码目录和目标目录
4.获取machine号
5.确定交叉编译工具链,制定了我们的shell名称:/bin/bash,编译器套件名称:gcc,以及一些编译参数,-Wall表示要提示所有的warning。
6.设置头文件包含路径,输出目标制定目录,添加平台相关的头文件到指定目录。
7.根据配置执行make以及depend的依赖关系分别调用各子目录,生成所有的obj文件。
8.交代了u-boot是如何组装起来的,组装规则是u-boot.lds这个文件,把start.o和各个子目录makefile生成的库文件按照LDFLAGS连接在一起,生成ELF文件u-boot 和连接时内存分配图文件u-boot.map。这里,我们的u-boot.bin文件我理解的是从u-boot.elf拷贝过来的。
3.u-boot.lds
u-boot的代码是根据u-boot.lds组装起来的,由于u-boot.lds的代码比较晦涩,不过不要紧,只要能找到每一个阶段的入口就可以了,该文件内容如下:
第一启动阶段代码入口
?
1 2 3 4 5 6 7 8 9 10 11 12 |
|
下面开始分析start.s和具体上电后的操作。
4.启动分析
start.S
?
22 23 |
|
这里声明一个连接入口_start,上电后或者复位后第一句就跳转到reset,切过去:
start.S
?
110 111 112 113 114 115 116 117 118 |
|
CPSR寄存器
这里先跳转到save_boot_params,bl跳转后会返回回来继续执行,还是切过去看save_boot_params做了什么。
save_boot_params
?
179 180 181 182 |
|
这里bx lr就直接返回跳转来之前的地址,也就是什么都不做,下面的.weak关键字作用是如果其他地方定义了save_boot_params那就调用,如果没有定义,这就是个空函数。
接着上面的代码段,具体做了什么已经注释的比较清楚了,接下来:
start.S
?
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
|
这里我们没有定义CONFIG_OMAP44XX和CONFIG_SPL_BUILD,因此执行
mrc p15, 0, r0, c1, c0, 0,这是协处理器操作,只有mrc和mcr才能对arm的协处理器进行操作:
MRC {条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,{协处理器操作码2}
MCR {条件}协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,{协处理器操作码2}
这两个指令一般是成对使用,读出来在写进去,设置CP15协处理器的C1寄存器V位为0,查看寄存器手册:
设置地段一场中断向量0x0~0x1c。
然后将_start的地址给r0,再将该地址写到c12寄存器,也就是设置异常向量的基地址:
紧接着,这里没有定义skip_lowlevel_init,跳入cpu_init_cp15 ,顾名思义还是对cp15协处理器的设置。代码如下:
cpu_init_cp15 展开原码
expand source?
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
|
这部分首先对r0清零,然后使无效整个数据和指令TLB,然后使无效整个指令cache,清空整个跳转目标的cache,清空预取缓冲区,清空写缓冲区, 设置低端异常中断向量,禁止MMU,禁止地址对齐检查,禁止数据Cache,前面已经禁止了指令cache。紧接着使能地址对齐检查,使能跳转预测功能 。然后后面有三个勘误宏,这里定义了三个,分别作了以下事情:对CP15的C15寄存器进行了操作,这里叫做诊断寄存器,然后将4,6,15都置位,这里我没找到c15寄存器的手册说明,具体意义不明,不过应该不影响后面的启动过程。
具体CP15的C0到C15寄存器信息参考下面的链接。
http://blog.sina.com.cn/s/blog_858820890102v1gc.html
完了之后,跳回子函数,然后顺序执行到函数cpu_init_crit:
cpu_init_crit 展开原码
expand source?
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
|
未定义SKIP_LOWLEVEL_INIT这部分代码其实就是跳转到lowlevel_init去了,lowlevel_init的作用就是引导加载c函数做进一步的初始化,切过去。
low_level_init.S
?
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
这里首先设置了一个临时的堆空间,将CONFIG_SYS_INIT_SP_ADDR的地址送到SP,这个地址=(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET),CONFIG_SYS_INIT_RAM_ADDR 在
imx-regs.h里面定义了0x00900000,后面的CONFIG_SYS_INIT_SP_OFFSET未找到,忽略。
设置SP八个字节对齐之后,这里定义了CONFIG_SPL_BUILD。将gdata赋值给r9,跳转到s_init函数中去。
s_init在arch\arm\cpu\armv7\mx6的soc.c中,s_init主要是对IMX6的PFDs进行了板级设置。
在调用结束s_init之后,程序跳转到到_main函数里面,搜索定位该感受在arch/arm/lib/crt0.S下,这里是main函数的入口,主要做了以下工作:
board_init_f在arch/arm/lib/spl.c中,主要做了以下事情:
接着跳转到board_init_r,在common/spl/spl.c下面,主要做了以下事情:
对memory,timer初始化,选择在什么介质启动,最后判断image的类型,是u-boot还是linux。
5.总结
(reset)