uboot120启动代码分析Word文档格式.docx
《uboot120启动代码分析Word文档格式.docx》由会员分享,可在线阅读,更多相关《uboot120启动代码分析Word文档格式.docx(13页珍藏版)》请在冰豆网上搜索。
_irq:
.wordirq
_fiq:
.wordfiq
.balignl16,0xdeadbeef
在start.S文件的开头,就定义了一个全局变量_start(地址为0x0),能被其他文件所引用。
中断向量表是放在地址为0x0开始的地方,其中,每个异常中断的排序是事先规定好的,比如第一个必须是reset异常,第二个必须是未定义指令异常。
系统上电时会从地址为0x0地方取指令,而地址0x0处放置的是reset标签,故会直接跳去reset标签处去启动系统了。
这里使用了ldr指令,ldr指令的label,都用一个.word伪操作来定义的。
.word伪操作符分配了一段字内存单元(字对齐),并用伪操作中的值进行初始化,表示把该label的编译地址写入当前地址,label是不占用任何指令的。
当发生异常时,都将执行u-boot-1.2.0/cpu/arm920t/interrupts.c中定义的中断函数,即start.S中要跳转的这些中断子程序的代码,均在u-boot-1.2.0/cpu/arm920t/interrupts.c中定义。
2.2u-boot存储器映射的定义
该段代码段主要是定义了u-boot需要使用的一些映射区的label,比如用户堆区、用户栈区、全局数据结构区等。
如下图所示:
(非常经典的u-boot映射图)
_TEXT_BASE:
.wordTEXT_BASE
.globl_armboot_start
_armboot_start:
.word_start
.globl_bss_start
_bss_start:
.word__bss_start
.globl_bss_end
_bss_end:
.word_end
#ifdefCONFIG_USE_IRQ
/*IRQstackmemory(calculatedatrun-time)中断的堆栈设置*/
.globlIRQ_STACK_START
IRQ_STACK_START:
.word0x0badc0de
/*IRQstackmemory(calculatedatrun-time)*/
.globlFIQ_STACK_START
FIQ_STACK_START:
#endif
代码的解析:
1)声明_TEXT_BASE标号并用TEXT_BASE来初始化(把TEXT_BASE的值存储在当前位置即标号的位置),TEXT_BASE在u-boot-1.2.0/board/smdk2410/config.mk中定义,它的值为0x33F80000。
2)声明_armboot_start为全局变量,并用_start来初始化,_start定义在board/smdk2410/u-boot.lds中,在FLASH中运行时,_start的地址为0x0;
在SDRAM中运行时,_start的地址为0x33F80000。
3)__bss_start和_end是在链接脚本board/smdk2410/u-boot.lds中给出定义的,在编译u-boot的时候产生的,声明_bss_start和_bss_end为全局变量,并用_bss_start和_bss_end来初始化。
4)声明IRQ_STACK_START和FIQ_STACK_START为全局变量,如果宏定义了CONFIG_USE_IRQ在cpu/arm920t/cpu.c中的cpu_init()函数将用到这两个全局变量。
2.3上电后设CPU为SVC模式
reset:
/*
*setthecputoSVC32mode
*设置CPU的状态类型为特权模式,该模式主要用来处理软件中断(SWI)
*/
mrsr0,cpsr
bicr0,r0,#0x1f
orrr0,r0,#0xd3
msrcpsr,r0
CPU复位后,系统会立即被设置为SVC模式,首先用mrs指令把CPSR寄存器中的值传送到通用寄存器r0中,用bic指令清除想修改的bit位,然后用orr指令来保证其他bit位不被改动,以达到修改低5位值的目的,最后用msr指令把r0的值给CPSR寄存器,让ARM进入SVC特权模式。
2.4关闭看门狗
1)首先定义寄存器的基地址:
#ifdefined(CONFIG_S3C2400)
#definepWTCON0x15300000
#defineINTMSK0x14400008/*Interupt-Controllerbaseaddresses*/
#defineCLKDIVN0x14800014/*clockdivisorregister*/
#elifdefined(CONFIG_S3C2410)
#definepWTCON0x53000000/*看门狗定时器控制寄存器的基地址*/
#defineINTMSK0x4A000008/*中断屏蔽寄存器的基地址*/
#defineINTSUBMSK0x4A00001C/*中断次级屏蔽寄存器的基地址*/
#defineCLKDIVN0x4C000014/*时钟分频控制寄存器的基地址*/
2)关闭看门狗
#ifdefined(CONFIG_S3C2400)||defined(CONFIG_S3C2410)
ldrr0,=pWTCON
movr1,#0x0
strr1,[r0]
由S3C2440的手册可知,系统启动后,看门狗寄存器是被使能的,如果不在预计的时间内“喂狗”,则系统会发生复位,故要关闭看门狗。
上述代码是将数据0写进看门狗定时器控制寄存器(WTCON),即禁止看门狗定时器的复位功能。
2.5禁止所有中断
/*maskallIRQsbysettingallbitsintheINTMR-default
*禁止所有中断
movr1,#0xffffffff
ldrr0,=INTMSK
strr1,[r0]/*屏蔽所有中断源*/
#ifdefined(CONFIG_S3C2410)
ldrr1,=0x3ff
ldrr0,=INTSUBMSK/*屏蔽有关中断源*/
strr1,[r0]
#endif
作用:
屏蔽所有的irq中断源,要屏蔽中断源只要把中断屏蔽寄存器(INTMSK)的所有bit位置1即可;
这个代码貌似有点多余,因为CPU复位时,这个寄存器的值就是0xFFFFFFFF,以防止发生异常中断。
2.6设置时钟分频控制寄存器(CLKDIVN)
/*FCLK:
HCLK:
PCLK=1:
2:
4*/
/*defaultFCLKis120MHz!
ldrr0,=CLKDIVN
movr1,#3
将0x3写入CLKDIVN寄存器,则分频比FCLK:
4
2.7调用cpu_init_crit
#ifndefCONFIG_SKIP_LOWLEVEL_INIT
blcpu_init_crit
如果未定义CONFIG_SKIP_LOWLEVEL_INIT就执行blcpu_init_crit,该语句首先调用cpu_init_crit进行CPU的初始化,并把下一条指令的地址保存在lr寄存器中,以使得执行完后能够正常返回。
#ifndefCONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*flushv4I/Dcaches*/
movr0,#0
mcrp15,0,r0,c7,c7,0
mcrp15,0,r0,c8,c7,0
/*disableMMUstuffandcaches*/
mrcp15,0,r0,c1,c0,0
bicr0,r0,#0x00002300@clearbits13,9:
8(--V---RS)
bicr0,r0,#0x00000087@clearbits7,2:
0(B----CAM)
orrr0,r0,#0x00000002@setbit2(A)Align
orrr0,r0,#0x00001000@setbit12(I)I-Cache
mcrp15,0,r0,c1,c0,0
*beforerelocating,wehavetosetupRAMtiming
*becausememorytimingisboard-dependend,youwill
*findalowlevel_init.Sinyourboarddirectory.
movip,lrbllowlevel_init
movlr,ip
movpc,lr
#endif/*CONFIG_SKIP_LOWLEVEL_INIT*/
代码分析:
1)设置CP15寄存器使失效Icache(指令cache)和Dcache(数据cache),然后禁止MMU和cache。
这样设置的原因主要是:
如果只按复位键而不关掉电源重新上电,就会造成cache中可能残留之前对cache操作的数据,称之为“脏数据”,它会映像我们的调试结果,造成假象。
还有就是在系统初始化阶段,只有一个任务在运行,程序看到的地址都是物理地址,没有必要也不允许使用地址变换,因此最好关闭MMU。
2)调用子程序lowlevel_init,在调用之前把lr寄存器中存储的返回地址保存在scratch寄存器(ip)中,当程序返回时再恢复它。
2.8调用lowlevel_init子程序
这个函数在board/smdk2410/lowlevel_init.S文件中,主要完成对存储控制器的配置,特别对SDRAM的初始化,为后面搬运代码至SDRAM作准备。
.globllowlevel_init//全局变量lowlevel_init被start.s文件调用
lowlevel_init:
/*memorycontrolconfiguration*/
/*maker0relativethecurrentlocationsothatit*/
/*readsSMRDATAoutofFLASHratherthanmemory!
ldrr0,=SMRDATA
ldrr1,_TEXT_BASE
subr0,r0,r1
ldrr1,=BWSCON
addr2,r0,#13*4
0:
ldrr3,[r0],#4
strr3,[r1],#4
cmpr2,r0
bne0b
/*everythingisfinenow*/
.ltorg
SMRDATA:
//变量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))
/*以上是对BANKCON0~BANKCON5的时钟周期进行设置*/
.word((B6_MT<
15)+(B6_Trcd<
2)+(B6_SCAN))
.word((B7_MT<
15)+(B7_Trcd<
2)+(B7_SCAN))
/*以上是对BANKCON6~BANKCON7的设置*/
.word((REFEN<
23)+(TREFMD<
22)+(Trp<
20)+(Trc<
18)+(Tchr<
16)+REFCNT)
.word0x32/*BANKSIZE寄存器的设置*/
.word0x30/*MRSRB6寄存器的设置*/
.word0x30/*MRSRB7寄存器的设置*/
1)使用伪指令ltorg,用于声明一个数据文字池的开始,把SDRAM控制器初始化需要用到的13个寄存器的值先保存至文字池(literalpools)中,然后通过ldr指令来访问这个文字池,以获取寄存器的值赋值到对应的寄存器地址中去。
2)SMRDATA标号表示存放存储控制器13个寄存器所需值的开始地址,SMRDATA标号的地址是u-boot编译时确定下来的内存文字池的地址,在内存中的地址为0x33F8xxxx,用SMRDATA减去_TEXT_BASE的值就是要计算出地址SMRDATA相对于地址0x0的大小,即计算出SMRDATA相对地址;
因为u-boot编译时所有地址都是相对于内存地址TEXT_BASE(0x33F80000)计算出来的,而程序存放的实际地址应该NORFLASH的0x0地址(或者内部4KSRAM的0x0地址),ARM一上电总是从0x0的地址开始运行程序,程序代码还没有搬运到TEXT_BASE(0x33F80000)这个位置,所以不能使用这些label的,只能找对一个相对与0x0的地址,才能得到真正的数据,这样SMRDATA在NORFLASH(或者内部4KSRAM)的地址就可以确定下来了。
3)在计算出NORFLASH(或者内部4KSRAM)的存放地址SMRDATA后加上13*4,然后将结果保存在r2中,13个寄存器,每个寄存器占4个字节。
并读取地址SMRDATA后文字池中的数据初始化BWSCON寄存器,这样就完成了SDRAM的初始化工作,SDRAM就开始工作了,以后就可以正常访问0x30000000开始的SDRAM空间。
2.9代码的搬运工作
#ifndefCONFIG_SKIP_RELOCATE_UBOOT
relocate:
/*relocateU-BoottoRAM*/
adrr0,_start/*r0<
-currentpositionofcode_*/
ldrr1,_TEXT_BASE/*testifwerunfromflashorRAM*/
cmpr0,r1/*don'
trelocduringdebug*/
beqstack_setup
ldrr2,_armboot_start
ldrr3,_bss_start
subr2,r3,r2/*r2<
-sizeofarmboot*/
addr2,r0,r2/*r2<
-sourceendaddress*/
copy_loop:
ldmiar0!
{r3-r10}/*copyfromsourceaddress[r0]*/
stmiar1!
{r3-r10}/*copytotargetaddress[r1]*/
cmpr0,r2/*untilsourceendaddreee[r2]*/
blecopy_loop
#endif/*CONFIG_SKIP_RELOCATE_UBOOT*/
1)首先取得标号_start的地址存储到r0,当代码在NORFLASH(或内部4KSRAM)中执行时,r0=_start=0x0;
当在SDRAM中执行时,r0=_start=_TEXT_BASE,测试是从FLASH启动,还是从RAM中启动,若在SDRAM中运行时,则不需要搬运代码直接跳转至stack_setup标号处执行,进行堆栈设置。
若u-boot从flash启动时,则搬运代码至SDRAM中,并计算出u-boot镜像大小的结束地址并保存在寄存器r2,循环搬运代码时使用多寄存器寻址方式。
2)搬运代码的原因:
flash的读写速度远小于SDRAM的读写速度,搬运至SDRAM后,可大幅提高程序的运行效率;
如果是nandflash启动模式,那么只有4KB的空间可供用户使用,实际的代码远大于4KB,因此需要重新开辟空间进行代码的运行工作;
还有就是考虑到价钱的原因,(nandflash和norflash每兆价格相差悬殊),把boot代码放在norflash里面(为什么不放在nandflash里面,因为nandflash读需要驱动支持,norflash可以直接读),boot通常很小,只需要占用几十k的空间,所以只需要很小的norflash芯片,这样很便宜,而应用程序通常很大,所以用价格低廉nandflash来储存。
2.10堆栈空间的设置
stack_setup:
ldrr0,_TEXT_BASE/*upper128KiB:
relocateduboot*/
subr0,r0,#CFG_MALLOC_LEN/*mallocarea*/
subr0,r0,#CFG_GBL_DATA_SIZE/*bdinfo*/
#ifdefCONFIG_USE_IRQ
subr0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
subsp,r0,#12
这段代码的作用是用来分配各个栈空间,包括分配动态内存区,全局数据区,IRQ和FIQ的栈空间等。
2.11BSS段的清零
clear_bss:
/*BSS段清零*/
ldrr0,_bss_start/*findstartofbsssegment找*/
ldrr1,_bss_end/*stopherebss段的结束地址存放r1中*/
movr2,#0x00000000/*clear把0传送给r2寄存器*/
clbss_l:
strr2,[r0]/*clearloop...将bss段循环清零*/
addr0,r0,#4
cmpr0,r1
bleclbss_l
这段代码先设置了BSS段的起始地址与结束地址,然后循环清除所有的BSS段。
至此,所有的CPU初始化工作(stage1阶段)已经全部结束了。
后面的代码,将通过ldrpc,_start_armboot,进入C程序代码执行,启动C语言程序,同时也是整个u-boot的主函数。
3、stage2:
C代码的分析
3.1为gd_t全局数据结构的指针gd与bd分配空间
gd=(gd_t*)(_armboot_start-CFG_MALLOC_LEN-sizeof(gd_t));
__asm____volatile__("
"
:
:
memory"
);
memset((void*)gd,0,sizeof(gd_t));
gd->
bd=(bd_t*)((char*)gd-sizeof(bd_t));
memset(gd->
bd,0,sizeof(bd_t));
代码解析:
把gd所指的内存区域清零,大小为sizeof(gd_t),初始化数据结构体指针bd_t,即为开发板数据变量bd分配内存空间,计算gd->
bd的起始地址,把bd所指的内存区域清零,大小为sizeof(bd_t)。
代码中的这句话:
目的就是告诉编译器内存被修改过了。
3.2初始化列表函数
for(init_fnc_ptr=init_sequence;
*init_fnc_ptr;
++init_fnc_ptr)