imx6ul:uboot-2013.10启动过程解析

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,.c,flash.c和u-boot.lds

     第三步:为你的建立一个新的配置文件“include/configs/.h”

    第四步:输入“make_config”

    第五步: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?

cat << EOF >> config.h

#define CONFIG_BOARDDIR board/$BOARDDIR

#include

#include

#include

#include #include

#include

 

   这里调用了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 

mkdir -p ${OBJTREE}/include 

mkdir -p ${OBJTREE}/include2 

cd ${OBJTREE}/include2 

rm -f asm 

ln -s ${SRCTREE}/arch/${arch}/include/asm asm 

LNPREFIX=${SRCTREE}/arch/${arch}/include/asm/ 

cd ../include 

mkdir -p asm

else

cd ./include 

rm -f asm 

ln -s ../arch/${arch}/include/asm asm

fi

rm -f asm/arch

if [ -z "${soc}" ] ; then 

ln -s ${LNPREFIX}arch-${cpu} asm/arch

else

ln -s ${LNPREFIX}arch-${soc} asm/arch

fi

if "${arch}" "arm" ] ; then 

rm -f asm/proc 

ln -s ${LNPREFIX}proc-armv asm/proc

fi

 

    以上是建立软连接的过程,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

if [ -z "${soc}" ] ; then 

ln -s ${LNPREFIX}arch-${cpu} asm/arch

else

ln -s ${LNPREFIX}arch-${soc} asm/arch

fi

if "${arch}" "arm" ] ; then

rm -f asm/proc 

ln -s ${LNPREFIX}proc-armv asm/proc

fi

 

    首先判断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

( echo "ARCH   = ${arch}"  

if [ ! -z "$spl_cpu" ] ; then 

echo 'ifeq ($(CONFIG_SPL_BUILD),y)'

echo "CPU    = ${spl_cpu}"

echo "else"

echo "CPU    = ${cpu}"

echo "endif"   

else

echo "CPU    = ${cpu}"   

fi    

echo "BOARD  = ${board}"

    "${vendor}" ] && echo "VENDOR = ${vendor}"  

"${soc}"    ] && echo "SOC    = ${soc}"  

exit 0 ) > config.mk

 

     上面的代码其实就是判断有没有定义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

if "$APPEND" "yes" ] # Append to existing config file then

echo >> config.h

else  > config.h  # Create new config file

fi

echo "/* Automatically generated - do not edit */" >>config.h

for i in ${TARGETS} ; do

i="`echo ${i} | sed '/=/ {s/=/ /;q; } ; { s/$/ 1/; }'`"

echo "#define CONFIG_${i}" >>config.h ; done

echo "#define CONFIG_SYS_ARCH  \"${arch}\""  >> config.h

echo "#define CONFIG_SYS_CPU   \"${cpu}\""   >> config.h

echo "#define CONFIG_SYS_BOARD \"${board}\"" >> config.h

"${vendor}" ] && echo "#define CONFIG_SYS_VENDOR \"${vendor}\"" >> config.h

"${soc}"    ] && echo "#define CONFIG_SYS_SOC    \"${soc}\""    >> config.h

cat << EOF >> config.h

#define CONFIG_BOARDDIR board/$BOARDDIR

#include

#include

#include

#include

#include

#include

EOF

exit 0

 

    首先检查config.h存在否,如果不存在就建立一个config,h,然后依次定义宏到config.h中,最后加入一些arm平台下通用的头文件,最后保存退出。打开config.h文件,可以清晰看到如下内容:

    这里并未定义文件里面的前四行内容,应该是手动添加进去的,确定mxl是否是有SPL启动,具体是哪个型号,然后根据具体型号再做一个配置,这里写到imx6image.cfg文件里查看。

imx6img.cfg

?

1

2

3

4

5

6

7

8

9

/* image version */

IMAGE_VERSION 2

/*  * Boot Device : one of  * spi, sd (the board has no nand neither onenand)  */

BOOT_FROM      sd

#define __ASSEMBLY__

#include

#include "asm/arch/iomux.h"

#include "asm/arch/crm_regs.h"

#include "clocks.cfg"

 

这个文件加入了另外几个头文件,猜测这个文件是和启动方式有关的配置文件,这里又加入了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

OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm")

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS {

  . = 0x00000000;

  . = ALIGN(4);

.text : 

*(.__image_copy_start)   //这里指定了影响文件复制的起始地址

arch/arm/cpu/armv7/start.o (.text*)  //指明了启动第一阶段的文件为制定目录下的start.s

*(.text*) 

}

 

    下面开始分析start.s和具体上电后的操作。

4.启动分析

start.S

?

22

23

.globl _start

_start: b reset

 

    这里声明一个连接入口_start,上电后或者复位后第一句就跳转到reset,切过去:

start.S

?

110

111

112

113

114

115

116

117

118

reset: 

bl save_boot_params   

mrs r0, cpsr  //将当前状态寄存器的值读到r0

and r1, r0, #0x1f //将r0的低五位状态赋值r1,也就是cpsr的低五位状态

teq r1, #0x1a  //比较CPSR的低五位状态是否等于0x1a,该状态说明对应HYP模式,一种非安全状态行运行的新模式。

bicne r0, r0, #0x1f //如果不等于那就清楚低五位 

orrne r0, r0, #0x13 //同样设置低五位为0x13,也就是10011,对应的是ARM的SVC管理模式

orr r0, r0, #0xc0  //11000000,禁止IRQ和FIQ

msr cpsr,r0//将r0的值读到CPSR,这时候am谁svc模式,中断被禁止。

 

    

                                                               CPSR寄存器

     这里先跳转到save_boot_params,bl跳转后会返回回来继续执行,还是切过去看save_boot_params做了什么。

save_boot_params

?

179

180

181

182

ENTRY(save_boot_params) 

bx lr   @ back to my caller

ENDPROC(save_boot_params) 

.weak save_boot_params

 

    这里bx lr就直接返回跳转来之前的地址,也就是什么都不做,下面的.weak关键字作用是如果其他地方定义了save_boot_params那就调用,如果没有定义,这就是个空函数。

    接着上面的代码段,具体做了什么已经注释的比较清楚了,接下来:

start.S

?

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD)) 

/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */

mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register 

bic r0, #CR_V  @ V = 0

mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register

/* Set vector address in CP15 VBAR register */

ldr r0, =_start 

mcr p15, 0, r0, c12, c0, 0 @Set VBAR

#endif

#ifndef CONFIG_SKIP_LOWLEVEL_INIT 

bl cpu_init_cp15 

bl cpu_init_crit

#endif

bl _main

 

     这里我们没有定义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

ENTRY(cpu_init_cp15)

/* 

* Invalidate L1 I/D 

* 使无效整个数据和指令TLB,然后使无效整个指令cache,清空整个跳转目标的cache,清空预取缓冲区,清空写缓冲区   

*/

mov r0, #0   @ set up for MCR 

mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs 

mcr p15, 0, r0, c7, c5, 0 @ invalidate icache 

mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array 

mcr p15, 0, r0, c7, c10, 4 @ DSB 

mcr p15, 0, r0, c7, c5, 4 @ ISB

/*

* disable MMU stuff and caches  

* 设置低端异常中断向量,禁止MMU,禁止地址对齐检查,禁止数据Cache,前面已经禁止了指令cache。紧接着使能地址对齐检查,使能跳转预测功能   

*/

mrc p15, 0, r0, c1, c0, 0 

bic r0, r0, #0x00002000 @ clear bits 13 (--V-) 

bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) 

orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align 

orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB

#ifdef CONFIG_SYS_ICACHE_OFF 

bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache

#else 

orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache //这里没有定义ICACHE_OFF,因此这里使能指令Cache

#endif 

mcr p15, 0, r0, c1, c0, 0

#ifdef CONFIG_ARM_ERRATA_716044   //没有定义该号码的宏,跳过

mrc p15, 0, r0, c1, c0, 0 @ read system control register

orr r0, r0, #1 << 11 @ set bit #11 

mcr p15, 0, r0, c1, c0, 0 @ write system control register

#endif

/*

* 这里是对CP15的C15寄存器进行了操作,这里叫做诊断寄存器,然后将4,6,15都置位,这里我没找到c15寄存器的手册说明。

*/

#ifdef CONFIG_ARM_ERRATA_742230 

mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register

orr r0, r0, #1 << 4  @ set bit #4 

mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register

#endif

#ifdef CONFIG_ARM_ERRATA_743622 

mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register

orr r0, r0, #1 << 6  @ set bit #6 

mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register

#endif

#ifdef CONFIG_ARM_ERRATA_751472 

mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register

orr r0, r0, #1 << 11 @ set bit #11 

mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register

#endif

mov pc, lr   @ back to my caller ENDPROC(cpu_init_cp15)

 

    这部分首先对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

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

/************************************************************************* 

*

* CPU_init_critical registers 

* setup important registers 

* setup memory timing 

* *************************************************************************/

ENTRY(cpu_init_crit) 

/*  

* Jump to board specific initialization...  

* The Mask ROM will have already initialized  

* basic memory. Go here to bump up clock rate and handle  

* wake up conditions.   */

   b lowlevel_init  @ go setup pll,mux,memory

ENDPROC(cpu_init_crit)

#endif

 

     未定义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

ENTRY(lowlevel_init) 

/*  

* Setup a temporary stack  

*/

ldr sp, =CONFIG_SYS_INIT_SP_ADDR 

bic sp, sp, #7 /* 8-byte alignment for ABI compliance */

#ifdef CONFIG_SPL_BUILD 

ldr r9, =gdata

#else 

sub sp, #GD_SIZE 

bic sp, sp, #7 

mov r9, sp

#endif 

/*  

* Save the old lr(passed in ip) and the current lr to stack  

*/

push {ip, lr}

/*  

* go setup pll, mux, memory  

*/

bl s_init 

pop {ip, pc}

ENDPROC(lowlevel_init)

 

    这里首先设置了一个临时的堆空间,将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函数的入口,主要做了以下工作:

  • 重新对SP赋值, 确认sp是8字对齐
  • 在栈顶保留一个global_data的大小, 这个global_data是uboot里面的一个全局数据, 很多地方都会用到. 俗称 gd_t
  • 确认更新后的sp是8字对齐
  • r9指向global_data, 后面别的地方想用global_data时候, 可以直接从r9里面获取地址.
  • r0赋值0
  • bl board_init_f: 跳转到board_init_f. 在编译SPL时, 分析Makefile可以看出, 该函数的实现是在.

    board_init_f在arch/arm/lib/spl.c中,主要做了以下事情:

  • 对BSS段进行清零操作
  • gd = &gdata;
    • gd的定义在DECLARE_GLOBAL_DATA_PTR
      • #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")
        • r9之前初始化了
    • gdata的定义在本文件中: gd_t gdata __attribute__ ((section(".data"))); 
      • 它是一个 gd_t 也就是global_data类型的变量
      • __attribute__表示这个变量会被放到".data"这个输入段中. 连接器会把输入段按照链接脚本(u-boot-spl.lds)里面指定的规则存放到输出段

    接着跳转到board_init_r,在common/spl/spl.c下面,主要做了以下事情:

    对memory,timer初始化,选择在什么介质启动,最后判断image的类型,是u-boot还是linux。

5.总结

    (reset) (b lowlevel_init: arch/arm/cpu/armv7/lowlevel_init.S) (b _main) --> (bl board_init_f) --> (board_init_r) --> (jump_to_image_no_args去启动u-boot)

你可能感兴趣的:(Bootloader)