首先直接看第一段代码:
.globl _start
_start: b reset
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
.globl 和.globol是一样的,它的作用是声明一个符号,而这个符号可以被其他文档引用,相当于声明了一个全局变量。
那么这里就是声名_start,_start可以被其他文档使用。
_start就是整个start.S的最开始,即整个uboot的代码的开始。后面的冒号,表示_start是一个标号,它的值,就是代码的位置,这里,是代码的最开始,所以其值=0。
b reset :表示跳转到 reset 执行,并且不返回
ldr: LDR伪指令—–大范围的地址读取
详情请看:1,http://blog.chinaunix.net/uid-28458801-id-3753775.html
代码是:
ldr pc, _undefined_instruction
我们先看大体一下_undefined_instruction是什么:
_undefined_instruction: .word undefined_instruction
.word: word是字的意思,.word就是分配一段字内存单元,这里相当于分配了一个4字节的内存单元,里面放的是undefined_instruction的值,这个undefined_instruction跟 _start一样,是标号,它的值就是代码所在的位置,所以ldr pc, _undefined_instruction
就是把,undefined_instruction代码的地址放到PC中,那样,发生未定义异常的时候,就可以通过PC进入undefined_instruction对应的代码段处理了。
接下去的代码:.balignl 16,0xdeadbeef
意思:接下的代码,也就是
_TEXT_BASE:
.word TEXT_BASE
它是需要16字节对齐的,如果不满足16个字节,那么就用0xdeadbeef补充。
TEXT_BASE是一个word类型的变量,它代表的是整个u-boot的基地址,是在\board\xx\config.mk中定义的,makefile中会用到,后面再分析,我们假设 TEXT_BASE = 0x33D00000
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
_bss_start和_bss_end都叧是两个标号,对应着此处的地址。
而两个地址里面分别存放的值是__bss_start和_end,这两个的值,定义在 \board\xx\u-boot.lds 中这是一个连接脚本。
而关于_bss_start和_bss_end定义为.glogl即全局变量,是因为uboot的其他源码中要用到这两个变量。
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
IRQ_STACK_START跟FIQ_STACK_START两个全局变量,当前的值是没有意义的,因为一开始初始化,并不知道堆栈的准确地址,这是留给以后再修改的。
/* IRQ stack memory (calculated at run-time) */
/*
* the actual reset code
*/
/*
对 mrs 和msr 有疑问的请看:http://blog.chinaunix.net/uid-28458801-id-4083956.html
*/
reset:
/*
* set the cpu to SVC32 mode.设置cpu为 SVC模式。
* 如果对arm的个模式有疑问的请看:1,http://blog.chinaunix.net/uid-28458801-id-3788554.html
* 2,http://blog.chinaunix.net/uid-28458801-id-3494646.html
*/
mrs r0,cpsr /* 传送CPSR的内容到R0 */
bic r0,r0,#0x1f /* 清除r0的bit[4:0]位 */
orr r0,r0,#0xd3 /* 把r0的bit[7:6]和bit[4]和bit[2:0]置为1 */
msr cpsr,r0 /* 将r0的值赋给CPSR */
接下来是关看门狗的操作:
* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif
首先,定义一些看门狗相关的寄存器地址。
关看门狗操作很简单,就是往pWTCON寄存器里写0
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
然后就是关闭所有中断,方法:设置INTMSK寄存器
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
#if 0 /* 不编译*/
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif
接下来:
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
// B 转移指令,跳转到指令中指定的目的地址,即不返回
// BL 带链接的转移指令,像B 相同跳转并把转移后面紧接的一条指令地址保存到链接寄存器LR(R14)中,以此来完成子程式的调用,即能返回。
// 该语句首先调用cpu_init_crit 进行CPU 的初始化
那我们在看一下,#ifndef CONFIG_SKIP_LOWLEVEL_INIT
这句话的作用是什么,其实,注释写了,因为bl cpu_init_crit是底层操作
用这句话,来使得bl这句代码,只在u-boot在ram中运行的时候,才能执行,在外部的sdram就不执行了。
接下来分析cpu_init_crit 这部分代码:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* 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
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
第一个CONFIG_SKIP_LOWLEVEL_INIT 宏定义,在2440上是没用定义的,因此会执行这部分代码。
前面两部分很简单,就是操作cp15协处理器,invalidate icache, dcache,这里有个我的理解,看第二部份的代码注释
@set bit 12 (I) I-Cache
即,使能了icache,我一开始觉得跟之前一段代码invalidate cache是冲突的,但后来想想可能是这样:invalidate跟使能是可以同时的,使能是让cache可以用,以后需要使用cache时,不需要再在使能。invalidate只是让cache里的数据无效,好像是让cache每一段的最后一位都为0。
然后看第三部分:
这一部分,一开始mov ip, lr
这一句,是将lr保存起来,ip好像就是r11。为什么需要保存呢?因为在下面调用的lowlevel_init的子程序里,还需要使用lr,即还需要再调用子程序,所以将lr先保存,防止后面被修改。那这个lowlevel_init是干什么用的呢?
我们知道,u-boot很大,而2440内部只用4k的sram,所以,在这4k的代码中,你就要为后面的u-boot的代码存放的位置做好准备,这里,我们是将后面的代码放到外部的sdram中,那就需要把sdram初始化,初始化的意思就是让我2440的芯片可以管理外部的sdram。在直白点,就是配置2440的存储管理寄存器。
好,那我们就来看看这部分的代码:
首先第一部分是一些宏定义(看2440手册的第5章):
#define BWSCON 0x48000000
上面BWSCON 寄存器的的地址
#define DW8 (0x0)
#define DW16 (0x1)
#define DW32 (0x2)
#define WAIT (0x1<<2)
#define UBLB (0x1<<3)
#define B1_BWSCON (DW32)
#define B2_BWSCON (DW16)
#define B3_BWSCON (DW16 + WAIT + UBLB)
#define B4_BWSCON (DW16)
#define B5_BWSCON (DW16)
#define B6_BWSCON (DW32)
#define B7_BWSCON (DW32)
这上面一开始是BWSCON 里面一些位设置的宏,接下来是对BANKn的数据总线宽度与功能的宏定义,对于BANK3因为要接网卡,网卡有wait信号和没有wait信号两种,所以这里有些特别。
/* BANK0CON */
#define B0_Tacs 0x0 /* 0clk */
#define B0_Tcos 0x0 /* 0clk */
#define B0_Tacc 0x7 /* 14clk */
#define B0_Tcoh 0x0 /* 0clk */
#define B0_Tah 0x0 /* 0clk */
#define B0_Tacp 0x0
#define B0_PMC 0x0 /* normal */
/* BANK1CON */
#define B1_Tacs 0x0 /* 0clk */
#define B1_Tcos 0x0 /* 0clk */
#define B1_Tacc 0x7 /* 14clk */
#define B1_Tcoh 0x0 /* 0clk */
#define B1_Tah 0x0 /* 0clk */
#define B1_Tacp 0x0
#define B1_PMC 0x0
#define B2_Tacs 0x0
#define B2_Tcos 0x0
#define B2_Tacc 0x7
#define B2_Tcoh 0x0
#define B2_Tah 0x0
#define B2_Tacp 0x0
#define B2_PMC 0x0
#define B3_Tacs 0x0 /* 0clk */
#define B3_Tcos 0x3 /* 4clk */
#define B3_Tacc 0x7 /* 14clk */
#define B3_Tcoh 0x1 /* 1clk */
#define B3_Tah 0x0 /* 0clk */
#define B3_Tacp 0x3 /* 6clk */
#define B3_PMC 0x0 /* normal */
#define B4_Tacs 0x0 /* 0clk */
#define B4_Tcos 0x0 /* 0clk */
#define B4_Tacc 0x7 /* 14clk */
#define B4_Tcoh 0x0 /* 0clk */
#define B4_Tah 0x0 /* 0clk */
#define B4_Tacp 0x0
#define B4_PMC 0x0 /* normal */
#define B5_Tacs 0x0 /* 0clk */
#define B5_Tcos 0x0 /* 0clk */
#define B5_Tacc 0x7 /* 14clk */
#define B5_Tcoh 0x0 /* 0clk */
#define B5_Tah 0x0 /* 0clk */
#define B5_Tacp 0x0
#define B5_PMC 0x0 /* normal */
这上面的这段代码是对每一个BANK的BANKCON进行配置,这里要根据SRAM手册来配置对应的值,否则不能正常进行使用,在0~5中大多是对一些时序与Page模式的配置,只对应SRAM的要求就可以了。
#define B6_MT 0x3 /* SDRAM */
#define B6_Trcd 0x1
#define B6_SCAN 0x1 /* 9bit */
#define B7_MT 0x3 /* SDRAM */
#define B7_Trcd 0x1 /* 3clk */
#define B7_SCAN 0x1 /* 9bit */
/* REFRESH parameter */
#define REFEN 0x1 /* Refresh enable */
#define TREFMD 0x0 /* CBR(CAS before RAS)/Auto refresh */
#define Trp 0x0 /* 2clk */
#define Trc 0x3 /* 7clk */
#define Tchr 0x2 /* 3clk */
#define REFCNT 1113 /* period=15.6us, HCLK=60Mhz, (2048+1-15.6*60) */
然后,上面的代码,就是配置B6,B7,这两个块是可以作为ROM,SRAM,SDRAM等存储器的。
还有刷新控制寄存器的配置,要根据SDRAM的具体参数来配置,移植需要注意。
好了,就下来就是lowlevel_init了:
_TEXT_BASE:
.word TEXT_BASE
这里是获得代码段的起始地址,我的是0x33F80000(在board/xxx/config.mk中)
接下来是这段代码:
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
首先看第一句:ldr r0, =SMRDATA
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))
.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
SMRDATA表示的是这13个寄存器的值存放的开始地址,值为0x33F8xxxx,处于内存中,这一句就是将其值加载到r0中。
接下来的分析,我转的一个人的:
_TEXT_BASE:
.word TEXT_BASE //这里是获得代码段的起始地址,我的是0x33F80000(在board/xxx/config.mk中
//可到找到“TEXT_BASE=0x33F80000”
.globl lowlevel_init //这里相当于定义一个全局的lowlevel_init以方便调用
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA //SMDATA表示这13个寄存器的值存放的开始地址,值为0x33F8xxxx,处于内
//存中,这一句的作用是把其值加载到r0中
ldr r1, _TEXT_BASE //把代码的起始地址(0x33F80000)加载到r1中
sub r0, r0, r1 //r0减去r1其结果存入r0,也即SMDATA中的起始地址0x33F8xxxx减去
//0x33F80000,其结果就是13个寄存器的值在NOR Flash存放的开始地址
ldr r1, =BWSCON /* Bus Width Status Controller */ //存储控制器的基地址
add r2, r0, #13*4 //在计算出来的存放地址加上#13*4,然后其结果保存在r2中
//13个寄存器,每个寄存器占4个字节
0:
ldr r3, [r0], #4 //内存中r0的值加载到r3中,然后r0加4,即下一个寄存器的
str r3, [r1], #4 //读出寄存器的值保存到r1中,然后r1也偏移4
cmp r2, r0 //比较r0与r2的值,如果不等继续返回0:执行,也即13个寄存器的值
//是否读完
bne 0b
/* everything is fine now */
mov pc, lr //程序跳转,返回到cpu_init_crit中
到这里,lowlevel_init就结束了。
上面结束后,程序就回到之前调用cpu_init_crit的地方,继续往下运行。
代码如下:
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
/* 这一段程序的目的是判断 _start 和 _TEXT_BASE 是否相同,如果程序开始执行的地方就位于程序的链接地址的话,就不用重定位了。如果是nor启动,_start即为代码的最开始,相对的0的位置;如果重新relocate代码之后,_start就是在/board/smdk2410/config.mk中定义的TEXT_BASE = 0x33F80000位置,即_start = TEXT_BASE = 0x33F80000,这样的话就不用重定位代码了。 */
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
能执行到这,就说明_start 和 _TEXT_BASE 是不相同的。重定位代码就是把代码从源地址拷贝到目的地址,拷贝多大的问题。源就是r0中的值,为_start,目的就是r1中的值,为_TEXT_BASE,程序拷贝多大呢?从链接脚本里面可以看出来,用(_bss_start - _armboot_start)即为程序的大小。 为什么这么大,待思考。
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
设置栈,在经过上面的重定位代码以后, r0 = TEXT_BASE = 0x33F80000,去除分给malloc堆的空间以后,再取出一部分空间给bdinfo结构体,再留出12字节给终止异常的空间,然后将这个值赋给sp,即设置好了栈。
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
清BSS段,BSS段的大小为(_bss_end - _bss_start),将这个空间都清为0。
ldr pc, _start_armboot
_start_armboot: .word start_armboot
将_start_armboot的值赋给pc,即调用start_armboot函数,即跳到uboot的第二阶段。
总结一下流程:
1,设置svc模式;
2,关看门狗;
3,屏蔽中断;
4,invalidate cache,关mmu
5,初始化存储管理器
6,代码重定位
7,设置栈
8,启动第二段代码