u-boot的启动阶段分为两个,第一部分主要为start.S文件,帖子尽可能的分析了每一行代码的意思,查看了很多手册,分析的目的也是为了学习吧,写博客也是想给自己的学习留下点东西,这些东西网上其实很多,但是感觉看别人写的和字自己分析写一遍,差别好大。转载请注明出处,下面进入正题。
第11行:
#include <asm-offsets.h>//由kbuild自动生成,且不管
#include <common.h> //include下包含其他通用头文件的头文件
#include <config.h> //很多体系架构下都有这个各自体系的配置头文件
一.start_code及CPSR分析
第24行:
.globl _start
_start: b start_code
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
这里从_start开始,第一句,b start_code意义是跳转到start_code这部分。我们切过去看。
在第79行:
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
这部分是将cpu设置成SVC32模式,我们知道arm有7种工作模式,这里设置为等级最高,一般我们是工作在usr模式。
mrs r0,cpsr
将状态寄存器cpsr的内容送到通用寄存器r0中
bic r0, r0, #0x1f
这句话起始就是对r0的低5位清零。bic指令的意思对 Rn 中的值和 Operand2 值的反码按位进行逻辑“与”运算。
orr r0, r0,#0xd3
这里是设置r0里面的低5位为10011,即svc模式,同时关闭irq和fiq。
msr cpsr,r0
将r0的值写回给cpsr。
这里我们看一下s3c2440的datasheet:
这里给出了CPSR寄存器各个位的定义,我们这里着重关心低八位,0到5位设置工作模式,下面的图里面给出了各个模式对应的位应该设置为何值。5,6,7三位分别对应状态位,fiq和irq,上面给r0赋值0xd3就是11010011,对应进去一目了然。
二.关中断,时钟设置分析
下面第102行:
#ifdef CONFIG_S3C24X0
/* turn offthe watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /*Interrupt-Controller base addresses */
# defineCLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /*Interrupt-Controller base addresses */
# defineINTSUBMSK 0x4A00001C
# defineCLKDIVN 0x4C000014 /* clock divisor register */
# endif
这段的功能是定义看门狗,中断和子中断以及分频器的寄存器地址,此处我们的是s3c2440,可以看看数据手册,以INTMSK为例。·
INTMSK的基地址果然是0x4A000008.
下面,第116行:
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
ldr这里使用了伪指令,是将pWTCON的地址写到r0里面。mov讲0赋给r1,最后str是将r1的值放到r0代表的地址的内存里面去。这段话其实就是对pWTCON清零来关闭看门狗。
第123行:
mov r1,#0xffffffff
ldr r0, =INTMSK
str r1, [r0]
参考上一段话,因为中断控制寄存器都是置一清零的,这里是关闭所有的中断。
第133行:
#if defined(CONFIG_S3C2440)
ldr r1, =0x7fff
ldr r0, =INTSUBMSK
str r1, [r0]
#endif
这部分是屏蔽子中断,2440的子中断控制寄存器只有低15位有效,别的位保留,所以这里为0x7fff。
第139行:
#if defined(CONFIG_S3C2440)
#define MPLLCON 0x4c000004
#define UPLLCON 0x4c000008
#define CAMDIVN 0x4c000018
ldrr0,=CAMDIVN
mov r1,#0
str r1,[r0]
ldrr0,=CLKDIVN
movr1,#0x05
str r1,[r0]
mrcp15,0,r0,c1,c0,0
orrr1,r1,#0xc0000000
mcrp15,0,r0,c1,c0,0
ldrr0,=UPLLCON
ldrr1,=0x38022
str r1,[r0]
nop
nop
nop
nop
nop
nop
nop
ldrr0,=MPLLCON
ldrr1,=0x5c011
str r1,[r0]
#else
上面这段主要做了三个时钟的设置,第一个关闭了为摄像头的分频器,第二个设置USB的时钟频率为48M,第三设置系统的时钟频率为400MHZ.具体的为什么,我们来看看。
CLKDIVN寄存器参看数据手册如下:
mov r1,#0x05也就是0101,由于我们设置了CAMDIV均为0,因此对应的PCLK:HCLK:FCLK=1:2:8,分频系数已经设置好。
mrc p15,0,r0,c1,c0,0
orr r1,r1,#0xc0000000
mcr p15,0,r0,c1,c0,0
此处为将协处理器p15的寄存器中的数据传送到ARM处理器的寄存器r0中,其中1是协处理器操作码1,0是协处理器操作码2,c1存放第一个操作数的协处理器寄存器,c0存放第二个操作数的协处理器寄存器。最终目的是将arm从快速总线模式转换为异步总线模式。
ldr r0,=UPLLCON
ldr r1,=0x38022
str r1,[r0]
这里设置USB的时钟频率为48M,具体的参照手册。
ldr r0,=MPLLCON
ldr r1,=0x5c011
str r1,[r0]
这里最终将系统时钟频率设置为400M,MPLLCON是控制FCLK和Fin之间的关系,查看数据手册如下:
这里给出了m,p,s三个参数是如何来的,我们再看
这里设置为0x5c011也就是0101 1100 0000 0001 0001,根据数据位,可以计算得到s=1,p=1+2=3,m=172+8=100,又因为我们的晶振为12MHZ,所以mpll=fclk=(2*100*12)/(3*2)=400MHZ。
三.cpu_init_crit及MMU CP15协处理器配置分析
下面,第186行:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
如果没有定义CONFIG_SKIP_LOWLEVEL_INIT,那就跳转到cpu_init_crit这个函数,这里使用了bl,也就是下一条指令的执行地址,存放在lr链接寄存器里面,说明子函数运行结束之后,使用mov pc lr,程序还是要回到这里继续执行,切过去,第212行:
/*
*flush v4 I/D caches
*/
mov r0,#0
mcr p15,0, r0, c7, c7, 0 /* flush v3/v4 cache*/
mcr p15,0, r0, c8, c7, 0 /* flush v4 TLB */
/*
*disable MMU stuff and caches
*/
mrc p15,0, r0, c1, c0, 0
bic r0,r0, #0x00002300 @ clear bits 13, 9:8(--V- --RS)
bic r0,r0, #0x00000087 @ clear bits 7, 2:0(B--- -CAM)
orr r0,r0, #0x00000002 @ set bit 2 (A) Align
orr r0,r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15,0, r0, c1, c0, 0
首先来看看mcr和mrc这两个指令吧,查到如下:
MRC {条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,{协处理器操作码2}
MCR {条件}协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,{协处理器操作码2}
arm的cp15协处理器时只能够被mcr和mrc两个指令操作的,这里我们查看datasheet:
这里由于是cp15,所以8到11位是1111,各个位对应的意思也可以去datesheet查到。这里不赘述了。
mcr p15,0, r0, c7, c7, 0 /* flush v3/v4 cache*/
mcr p15,0, r0, c8, c7, 0 /* flush v4 TLB */
这里先对r0清零以后,再对c7和c8进行清零,而c7和c8分别对应什么呢,可以继续看下手册里面的寄存器编号:
寄存器编号 |
基本作用 |
在MMU中的作用 |
在PU中的作用 |
0 |
ID编码(只读) |
ID编码和cache类型 |
|
1 |
控制位(可读写) |
各种控制位 |
|
2 |
存储保护和控制 |
地址转换表基地址 |
Cachability的控制位 |
3 |
存储保护和控制 |
域访问控制位 |
Bufferablity控制位 |
4 |
存储保护和控制 |
保留 |
保留 |
5 |
存储保护和控制 |
内存失效状态 |
访问权限控制位 |
6 |
存储保护和控制 |
内存失效地址 |
保护区域控制 |
7 |
高速缓存和写缓存 |
高速缓存和写缓存控制 |
|
8 |
存储保护和控制 |
TLB控制 |
保留 |
9 |
高速缓存和写缓存 |
高速缓存锁定 |
|
10 |
存储保护和控制 |
TLB锁定 |
保留 |
11 |
保留 |
|
|
12 |
保留 |
|
|
13 |
进程标识符 |
进程标识符 |
|
14 |
保留 |
|
|
15 |
因不同设计而异 |
因不同设计而异 |
因不同设计而异 |
可以看到表格里面c7,c8在MMU中的作用是告诉缓存和写缓存控制以及TLB控制,其实c7和c8是两个只写寄存器,这里就是清除cache和写缓存,以及清除TLB就是这么做的。
mrc p15,0, r0, c1, c0, 0
bic r0,r0, #0x00002300 @ clear bits 13, 9:8(--V- --RS)
bic r0,r0, #0x00000087 @ clear bits 7, 2:0(B--- -CAM)
orr r0,r0, #0x00000002 @ set bit 2 (A) Align
orr r0,r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15,0, r0, c1, c0, 0
这段呢,其实就很明了了,首先将c0和c1的值读给r0,然后分别清除对应的位,分别是0到2位,7位,8,9,13位,这些位是干嘛的呢?为什么要清楚这些位。查找datasheet看到c1,下面有两个表格,清除的位和下面用orr指令设置的位我都标注出来了。
31 16 |
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
附加 |
L4 |
RR |
V |
I |
Z |
F |
R |
S |
B |
L |
D |
P |
W |
C |
A |
M |
位 |
说 明 |
M |
0:禁止MMU或者PU;1:使能MMU或者PU |
A |
0:禁止地址对齐检查;1:使能地址对齐检查 |
C |
0:禁止数据/整个cache;1:使能数据/整个cache |
W |
0:禁止写缓冲;1:使能写缓冲 |
P |
0:异常中断处理程序进入32位地址模式;1:异常中断处理程序进入26位地址模式 |
D |
0:禁止26位地址异常检查;1:使能26位地址异常检查 |
L |
0:选择早期中止模型;1:选择后期中止模型 |
B |
0:little endian;1:big endian |
S |
在基于MMU的存储系统中,本位用作系统保护 |
R |
在基于MMU的存储系统中,本位用作ROM保护 |
F |
0:由生产商定义 |
Z |
0:禁止跳转预测功能;1:使能跳转预测指令 |
I |
0:禁止指令cache;1:使能指令cache |
V |
0:选择低端异常中断向量0x0~0x1c;1:选择高端异常中断向量0xffff0000~ 0xffff001c |
RR |
0:常规的cache淘汰算法,如随机淘汰;1:预测性淘汰算法,如round-robin淘汰算法 |
L4 |
0:保持ARMv5以上版本的正常功能;1:将ARMv5以上版本与以前版本处理器兼容,不根据跳转地址的bit[0]进行ARM指令和Thumb状态切换:bit[0]等于0表示ARM指令,等于1表示Thumb指令 |
总结一下这一步具体做了哪些事情,首先禁止MMU,禁止对齐检查,禁止整个cache,选择为小尾数,取消系统保护,取消rom保护,禁止指令cache,选择低端的异常中断向量。然后再设置对齐检查,因为我们的ALIGN是四字节对齐的,然后再使能指令cache。看来简单的禁止MMU不是那么简单的。。。
四.lowlevel_init.S分析
第235行:
mov ip, lr
bl lowlevel_init
首先,ip是一个内部调用暂时寄存器,我们现在是在cpu_init_crit函数里面,而一开始从start_code跳转到cpu_init_crit里面的时候,lr保存的是从cpu_init_crit用于返回到start_code的地址,这里再跳转到lowlevel_init的时候我们需要将lr的值暂存到ip里面,待会儿从lowlevel_init回来的时候,再用ip把保存的地址还给lr,这样我们再进行跳转就回到start_code里面去了。说白了,就是函数的嵌套调用方法。
下面我们切换到lowlevel_init中去,这个文件在board/mini2440/mini2440下,打开。
第114行:
lowlevel_init:
/* memorycontrol configuration */
/* make r0relative the current location so that it */
/* readsSMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, =CONFIG_SYS_TEXT_BASE
sub r0, r0, r1
三段注释部分说的是这部分是内存控制的配置,请使r0相对当前位置从FLASH读出SMRDATA,而不是从存储器里面。(暂时不明白啥意思)
ldr r0, =SMRDATA
这里是一个伪指令,SMRDATA使用了后面的在low文件最后的.ltorg伪指令,将其作为了一个数据缓冲池,而SMRDATA就是数据缓冲池的名称。见文件最后第132行开始:
.ltorg
/* the literal pools origin */
SMRDATA:
.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
一般数据缓冲池都放在跳转指令最后或者子程序返回指令之后,这样处理器就不会把它当做指令来处理啦。但是由于LDR访问范围要在4KB之内,所以这个数据池也要放在距离这条LDR指令4KB之内的地方。
ldr r0,=SMRDATA
其实就是让r0等于SMRDATA代表的数据池的起始地址。
ldr r1,=CONFIG_SYS_TEXT_BASE
这是让r1等于CONFIG_SYS_TEXT_BASE代表的地址,有说是FLASH的0地址或者内部4K SRAM的0地址。这里我理解的是0x30008000,应该是SDRAM的起始地址。
sub r0, r0, r1
这句,计算r0和r1的差值,并且赋给r0,实际上,是计算SMRDATA代表的首地址和arm实际的地址空间的差值,也就是u-boot启动的时候,无论是从nor还是nand ,SMRDATA的绝对访问地址。
ldr r1, =BWSCON
将BWSCON代表的bank控制寄存器的地址赋给r1.
add r2, r0,#13*4
这一步是为下一步判断循环结束条件做准备,r2是用来存放这个地址的最大值,因为13个寄存器,每个偏移量为4。
0:
ldr r3, [r0], #4
将r0地址中的值赋给r3,并将r0+4,指向下一个内存池里的数据
str r3, [r1], #4
同样,把刚才r0给r3的值给r1,并且使r1+4,准备接收下一次的数据
cmp r2, r0
这是判断循环条件,因为有13个寄存器,所以刚才r0+13*4,每次偏移4个,正好13次结束,比较是否相等,不等继续跳转到0处执行,相等的话说明配置结束。下一条指令,lr把保存的执行地址给PC,跳回cpu_init_crit。
bne 0b
mov pc,lr
以上为配置结束以后,SDRAM就可以工作了,这样我们才能访问0x30008000开始的地址空间,同时要注意的是:SMRDATA的绝对地址(即相当于_start的地址)大于了4K,则这个代码就不能使用nand启动了。因为nand启动时只能映射前4k代码到SRAM。而nor启动则没有这个限制,因为nor启动在SDRAM运行代码前可支配的地址空间是0到norflash大小。
下面回到start.S文件,因为我们已经跳转回来了,接着第239行:
mov lr, ip
mov pc, lr
这里把刚才暂存的lr保存的下一条指令的运行地址还给lr,然后再由lr传给pc,也就是说继续跳转回去,到start_code,我们看第190行。
bl _main
跳转到这个函数里面去了,这个_main其实也做了很多事,因为我现在还未移植从nor或者nand启动,这个会留到以后具体分析。其实它是跳转到了arch/arm/lib/crt0.S里面去了,在那里进行了__TEXT_BASE部分,CONFIG_SYS_TEXT_BASE的定义,以及设定了将代码如何拷贝到sdram里面去的过程。之后再跳转arch/arm/lib/board.c里面的board_init_f等等。这里就不赘述了。
start.S里面剩下从244行以后的是和中断有关的一些处理,u-boot这里不使用中断,所以不做分析。
至此,总结一下第一启动阶段,start.S做了些什么。
第一:设置CPU为SVC模式
第二:关闭看门狗
第三:关中断和子中断
第四:设置时钟
第五:MMU关闭,清除cache和TLB,使能地址对齐检查等
第六:初始化SDRAM
就分析到这里吧,如有不正确的地方,还请指出,共同进步。