按照gooogleman老兄的建议,从优龙2440板子的bootloader学起。首先先将程序运行起来,step执行,搞清楚每步做了什么,为什么这么做。将整体的流程学习一遍。等1个月,2个月,无论多长时间,当自己真正掌握后,再将整体的代码加上注释,做成PDF文档。分给后来者,去帮助像我现在这样水平的小小鸟。
OK,第一话开始:
程序入口点:
程序最开始跳到2440init.s文件,执行如下代码:b ResetHandler 跳转到复位异常处理程序。
ResetHandler
ldr r0,=WTCON ;watch dog disable
ldr r1,=0x0
str r1,[r0]
(将看门狗定时器清零,查看datasheet,WTCON的第0位是看门狗定时器复位输出信号开关。为0,则程序关闭了S3C2440A的看门狗复位功能。)
ldr r0,=INTMSK
ldr r1,=0xffffffff ;all interrupt disable
str r1,[r0]
(将中断屏蔽寄存器全部置1,将对应的中断全部关闭。)
ldr r0,=INTSUBMSK
ldr r1,=0x7fff ;all sub interrupt disable `
str r1,[r0]
(将子中断屏蔽寄存器全不置1,将子中断屏蔽寄存器对应的中断全部关闭。)
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
(LOCKTIME是锁定时间计数寄存器,分别设定了UPLL 对于UCLK 的锁定时间计数值和
MPLL对于FCLK、HCLK、PCLK的锁定时间计数值。具体查看datasheet第7章时钟部分。)
[ PLL_ON_START
; Added for confirm clock divide. for 2440.
; Setting value Fclk:Hclk:Pclk
ldr r0,=CLKDIVN
ldr r1,=CLKDIV_VAL ; 0=1:1:1, 1=1:1:2, 2=1:2:2, 3=1:2:4, 4=1:4:4, 5=1:4:8, 6=1:3:3, 7=1:3:6.
str r1,[r0]
(这里先明确一下,在ADS下的bootloader代码常看到
[
|
]
他们其实就相当于c语言里面的
Ifdef
Else
Endif)
(CLKDIVN是时钟分频器控制寄存器,设置的值为CLKDIV_VAL宏。我这里设置的是CLKDIV_VAL EQU 5 ;1:4:8)
继续执行:
[ CLKDIV_VAL>1 ; means Fclk:Hclk is not 1:1.
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
|
mrc p15,0,r0,c1,c0,0
bic r0,r0,#0xc0000000;R1_iA:OR:R1_nF
mcr p15,0,r0,c1,c0,0
]
由于我这里CLKDIV_VAL的值为5,所以执行
mrc p15,0,r0,c1,c0,0 <1>
orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA <2>
mcr p15,0,r0,c1,c0,0 <3>
<1> mrc是协处理器命令。用于读取协处理器中的寄存器的数据到ARM处理器的寄存器里面。这句话的意思应该是读协处理器中的寄存器数据到ARM处理器的r0里面。应该是ARM访问MMU,一般cp15就是MMU。这三条指令的目的是将MMU的30,31位置1。
功能如下:31位(iA bit)Function:Asynchronous clock select (选择异步时钟)
30位(nF bit)Function: notFastBus select
ldr r0,=UPLLCON
ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)
str r1,[r0]
(配置UPLL,一般UPLL的值设置为48MHZ,或者96MHZ,因为一般较难找到一个合适的PLL值,所以手册上推荐了一些。本程序中有如下宏定义:#define UCLK 48000000(在option.h文件中),则对应手册上的推荐值,MDIV=56,PDIV=2,SDIV=2)
(在option.inc文件中又有如下代码,通过这段代码已经确定了U_MDIV,U_PDIV ,U_SDIV值,那么上面的代码就是将相应分频值写入到UPLLCON。详细的内容去看数据手册的第7章,时钟与电源管理部分。)
[ UCLK = 48000000
U_MDIV EQU 56 ;Fin=12.0MHz Fout=48MHz
U_PDIV EQU 2
[ CPU_SEL = 32440001
U_SDIV EQU 2 ; 2440A(我们设置的是这个值,因为用的芯片是S3C2440A)
|
U_SDIV EQU 1 ; 2440X
]
]
程序继续执行:
nop
nop
nop
nop
nop
nop
nop
这里面插入了7个nop,在设置完UPLLCON寄存器后,需要至少延时7-clocks,等待硬件的配置完成。
;Configure MPLL
ldr r0,=MPLLCON
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin=16.9344MHz
str r1,[r0]
接下来配置MPLLCON寄存器,与UPLLCON部分相同,优龙2440的板子主频是400MHZ。
[ FCLK = 400000000
CLKDIV_VAL EQU 5 ;1:4:8
M_MDIV EQU 92 ;Fin=12.0MHz Fout=400MHz
M_PDIV EQU 1
[ CPU_SEL = 32440001
M_SDIV EQU 1 ; 2440A
|
M_SDIV EQU 0 ; 2440X
]
]
则M_MDIV=92,M_PDIV=1,M_SDIV=1.本程序段将这几个值写入到了MPLLCON寄存器。
;Check if the boot is caused by the wake-up from SLEEP mode.
ldr r1,=GSTATUS2
ldr r0,[r1]
tst r0,#0x2
;In case of the wake-up from SLEEP mode, go to SLEEP_WAKEUP handler.
bne WAKEUP_SLEEP
检查引导程序是否是从休眠模式被唤醒的,如果是的话,就跳转到相应的程序段进行处理。
这里面用到了一个tst指令。首先了解一下这个指令。
tst是位测试指令,根据测试结果更新CPSR中的相应条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。
再来看GSTATUS2这个寄存器,查询芯片手册,GSTATUS2是复位状态寄存器,本程序测试的是它的第1位(0010),“Boot is caused by wakeup reset in sleep mode”,这个标志代表引导程序是从休眠模式被唤醒的。由于我这里并不是从休眠模式唤醒的,所以程序没有跳转,继续顺序执行。
;Set memory control registers
;ldr r0,=SMRDATA
adrl r0, SMRDATA ;be careful!, hzh
ldr r1,=BWSCON ;BWSCON Address
add r2, r0, #52 ;End address of SMRDATA
0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0
mov r0, #&1000
1
subs r0, r0, #1
bne %B1
这里是设置存储器控制寄存器。首先还是了解一下adrl指令,它是中等范围的地址读取指令,比ADR伪指令可以读取更大范围的地址。SMRDATA是一个标号,将这个标号处的地址加载到了r0。BWSCON是总线宽度与等待控制寄存器,将这个寄存器的地址加载到了r1。然后将SMRDATA+52地址值加载到r2,这个地址是SMRDATA的尾地址。在SMRDATA地址处存放的是用DCD指令分配的一定大小的字内存单元,这段内存存放的就是与存储器相关的控制寄存器的配置参数。再来看BWSCON的地址是0x48000000,然后是BANKCON0(Bank0 控制寄存器)的地址是0x48000004,BANKCON1(Bank0 控制寄存器)的地址是0x48000008,以此类推,一直到BANKCON7的地址是0x48000020。接着是REFRESH(SDRAM 刷新控制寄存)地址是:0x48000024,BANKSIZE(可调的bank 大小寄存器)地址是:0x48000028,MRSRB6(模式寄存器集寄存器bank6)地址是:0x4800002C,MRSRB7(模式寄存器集寄存器bank7)地址是:0x48000030。上面这些寄存器都是存储器控制的相关寄存器,地址全部连续,相差0x04. 那么现在知道了为什么ldr r3, [r0], #4,str r3, [r1], #4,这两段代码,先将SMRDATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;; When EINT0 is pressed, Clear SDRAM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; check if EIN0 button is pressed
ldr r0,=GPFCON
ldr r1,=0x0
str r1,[r0]
ldr r0,=GPFUP
ldr r1,=0xff
str r1,[r0]
ldr r1,=GPFDAT
ldr r0,[r1]
bic r0,r0,#(0x1e<<1) ; bit clear
tst r0,#0x1
bne %F1
; Clear SDRAM Start
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]
; ldr r0,=GPFUP
; ldr r1,=0xff
; str r1,[r0]
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0] ;LED=****
mov r1,#0
mov r2,#0
mov r3,#0
mov r4,#0
mov r5,#0
mov r6,#0
mov r7,#0
mov r8,#0
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
;Clear SDRAM End
1
;Initialize stacks
bl InitStacks
接下来是这段比较长的代码,总体的作用就是,如果 EINT0 产生(这中断就是我们按键产生的), 就清除SDRAM。分析之前先搞清一个知识点,这段代码里面有一个bne %B0,还有一个bne %F1,这两句代码是什么意思呢?%B代表向前搜索,%F代表向后搜索,“0”“1”只是标号而已。也就是不等于则向前后者向后跳转到标号0或者标号1的位置。
ldr r0,=GPFCON
ldr r1,=0x0
str r1,[r0]
GPF0-GPF7都被设置成了input。
ldr r0,=GPFUP
ldr r1,=0xff
str r1,[r0]
GPF0-GPF7都被禁止上拉。
ldr r1,=GPFDAT
ldr r0,[r1]
bic r0,r0,#(0x1e<<1) ; bit clear
tst r0,#0x1
bne %F1
这段代码则是检测EINT0的输入。有输入则清空SDRAM,没输入就直接向下跳到标号1
处。再来看bic指令。举个例子,BIC R1,R2,R3则是将R3的反码和R2逻辑与,结果保存到R1。这里为什么是0x1e,我也不是很理解。反正0x1e倒是可以。一会到CSDN去发帖。弄清楚了,再补充进来。tst r0,#0x1这段指令就是测试最低位是否为0.
; Clear SDRAM Start
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]
; ldr r0,=GPFUP
; ldr r1,=0xff
; str r1,[r0]
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0] ;LED=****
mov r1,#0
mov r2,#0
mov r3,#0
mov r4,#0
mov r5,#0
mov r6,#0
mov r7,#0
mov r8,#0
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
;Clear SDRAM End
这段代码则是清空SDRAM。
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]
GPF0-GPF3设置为中断EINT0-EINT3,GPF4-GPF7设置为输出。
; ldr r0,=GPFUP
; ldr r1,=0xff
; str r1,[r0]
全部禁止上拉。
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0]
将GPFDAT全部置0.GPF4-GPF7输出低电平。
mov r1,#0
mov r2,#0
mov r3,#0
mov r4,#0
mov r5,#0
mov r6,#0
mov r7,#0
mov r8,#0
将r1-r8寄存器内容设置为0,用来做清空SDRAM用的。
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
r9中装入的值是SDRAM的容量大小,r0中装入的值是SDRAM的首地址。
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
这段代码用来循环清零,直到SDRAM全部清空为止。
1
;Initialize stacks
bl InitStacks
当然如果在代码前段,没有按下按键的话,就直接跳到标号1处的这段代码,跳转到初始化堆栈的地方了。事实上,基本不会在此处按下按键,所以清空SDRAM的代码基本不会执行。
接着向下看:
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1 ;UndefMode
ldr sp,=UndefStack ; UndefStack=0x33FF_5C00
orr r1,r0,#ABORTMODE|NOINT
msr cpsr_cxsf,r1 ;AbortMode
ldr sp,=AbortStack ; AbortStack=0x33FF_6000
orr r1,r0,#IRQMODE|NOINT
msr cpsr_cxsf,r1 ;IRQMode
ldr sp,=IRQStack ; IRQStack=0x33FF_7000
orr r1,r0,#FIQMODE|NOINT
msr cpsr_cxsf,r1 ;FIQMode
ldr sp,=FIQStack ; FIQStack=0x33FF_8000
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msr cpsr_cxsf,r1 ;SVCMode
ldr sp,=SVCStack ; SVCStack=0x33FF_5800
这段代码则是分别初始化各种模式的堆栈。以初始化未定义模式堆栈为例。先将CPSR读取到r0,然后利用bic指令,将控制模式的低5位清零。之后设置(或上)为未定义模式,禁止中断,将结果保存到r1,然后将r1保存到CPSR。接着ldr sp,=UndefStack 设置堆栈。堆栈的地址值,为UndefStack宏,具体是多少查看宏定义即可。程序注释中也已经标注了。
需要知道orr r1,r0,#UNDEFMODE|NOINT,这条指令执行之后,r0寄存器中的值是无变化的,所以以后初始化其他堆栈的时候,可以接着拿r0来用。初始化其他模式堆栈,与此相同,不再赘述。
mov pc,lr 程序跳转回去。(转)


txgc_wm 于 2011-03-27 00:13:51发表:
不错