对 S3C2410 启动代码内数据复制过程的分析
本文若有错误之处,欢迎来信指正。
S3C2410 启动后先进行一些必要的设置,如关 WatchDog,设置 PLL 与时钟,配置 SDRAM,初始化堆栈等,
网上有很多分析启动代码的文章,本文不打算再进行说明。
本文分从 NOR Flash 与 NAND Flash 启动两种情况分别进行分析。首先要知道 NOR Flash 是 XIP 的,如
果从 NOR 启动,代码首先在 NOR Flash 内执行,考虑到运行速度,我们要把代码要复制到 SDRAM 内去执行。
当系统被设置成从 NAND FALSH 启动(使用 OM[1:0]引脚的电平来控制)时,由于其自身的特点,NAND
Flash 不具备运行程序的功能,程序不能在 NAND Flash 内执行,CPU 会自动把 NAND 内头 4K 代码加载到 CPU
的内部 SRAM(StepingStone 垫脚石)中,然后把 StepingStone 映射到 CPU 地址空间的 0 地址处(BANK0),因
此 CPU 会从 StepingStone 的 0 地址处取指令并执行。因为程序一般都会大于 4K,我们必须把代码复制到
SDRAM 中去执行。综上所述,不管是从 NOR 的启动还是从 NAND 启动,都要把代码复制到 SDRAM 中去执行,
这个复制过程要在头 4K 代码中完成。此后这 4KB 的 SRAM 还可以用作其它用途。
另外必须注意到的是 NOR 的启动与 NAND 启动的程序入口点 ResetEntry 都为 0,CPU 在加电后会去 0x0
处取指执行,如下图所示。
一、我们先看从 NOR Flash 启动的情况:
下面这段代码进行复制前的判断,因为从 NAND Flash 与从 NOR Flash 启动的复制过程是大不相同的。
;/**************************************************************************************
;BWSCON 的[2:1]反映了外部引脚 OM[1:0]:若 OM[1:0] != 00, 从 NOR FLash 启动或直接在内存运行;
;若 OM[1:0]==00,则为从 Nand Flash 启动
;**************************************************************************************/
ldr r0, =BWSCON ldr r0, [r0]
ands r0, r0, #6
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg
adr r0, ResetEntry ;从 NOR 或 NAND 启动,ResetEntry 都为 0, 但上面已经排除了 NOR 启动
cmp r0, #0 ;所以入口是 0 地址表示是从 NAND 的 StepingStone 启动
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg。否则往下执行
若判断是从 NOR Flash 启动,程序直接跳转到标号 copy_proc_beg 处执行:
copy_proc_beg
adr r0, ResetEntry ;r0= ResetEntry=0
ldr r2, BaseOfROM ;r2= |Image$$RO$$Base| = 0x30000000
cmp r0, r2 ;//如果从 norflash 启动,那么 ResetEntry=0 BaseOfROM=0x3000000,显然不相同
ldreq r0, TopOfROM
beq InitRam ;同时跳到 InitRam
因为现在程序仍然在 NOR Flash 中执行,代码的执行基址还是 0,所以“adr r0, ResetEntry”取得的
ResetEntry 相对地址是 0。则 r0≠r2,先执行下面的代码从 NOR 复制 RO 数据段到 SDRAM,不直接跳转到
InitRam 进行 RW 段复制。
ldr r3, TopOfROM
0 ;循环复制 Code 数据
ldmia r0!, {r4-r7} ;r0 的初始值 ResetEntry=0
stmia r2!, {r4-r7} ;r2 的初始值 RO Base(0x30000000)
;上面的 2 行代码将从 0 开始的地址内的数据保存到 SDRAM 内以 RO Base 开始的地址内
cmp r2, r3 ;复制的终止条件:复制了(RO Limit - RO Base) 大小,即整个 RO 数据段
bcc %B0 ;若 r2<r3,往回跳转到标号 0
sub r2, r2, r3 ;r0+16,r2+16 every time,if r2>r3
sub r0, r0, r2 ;将可能多加的部分减回来,让 r0 对准 ROM 内 RO 段的结束点,也是 RW 数据段的开始点
复制过程及结果如下图:
代码段复制完紧接着就是复制 RW 段:
RW Data
RO Data
Total ROM Size
ROM
ResetEntry
0x00000000
|Image$$RO$$Base|(0x30000000)
RO Data
SDRAM 加载域
|Image$$RO$$Limit|
从NOR Flash的0地址开始复制长度为(RO Limit – RO Base)的数据到SDRAM的RO Base处
r0(复制完后)
r0(复制前) InitRam ;从 NOR Flash 复制 RW 段到 SDRM 中预设的 RW BASE 位置
ldr r2, BaseOfBSS ;r2= |Image$$RW$$Base|=|Image$$RO$$Limit|
ldr r3, BaseOfZero ;r3= |Image$$ZI$$Base|
0 ;复制 RW 数据到 BaseOfBSS
cmp r2, r3 ;比较 r2,r3;看 RW 数据段有没有复制完
ldrcc r1, [r0], #4 ;R2<R3, [r0]->r1,然后 r0=r0+4, r0 是 RW 数据段在 ROM 内的起始地址
strcc r1, [r2], #4 ;r2 为|Image$$RW$$Base|地址,由编译器指定
bcc %B0 ;若 r2<r3,RW 段还没有复制完
从 NOR Flash 的 RO 段结束点开始复制 RW 数据到 SDRAM 的 RW Base 处,数据长度为 RW Limit - RW Base
还是有必要交代一下 ADS 中预定义的几个变量以及它们的作用。|Image$$RO$$Base|表示连接完成后程序映
像 RO 段的起始地址;|Image$$RO$$Limit|表示连接完成后程序映像 RO 段的结束地址;|Image$$RW$$Base|
表示连接完成后程序映像 RW 段的起始地址;|Image$$ZI$$Base|表示连接完成后程序映像 ZI 段的起始地址,
注意由于 RW 段与 ZI 段在运行时是紧密挨着的哦,所以这个值也就代表了 RW 段的结束地址;
|Image$$ZI$$Limit|表示连接完成后程序映像 ZI 段的结束地址。为了书写和阅读的方便,我们分别把上面
几个值赋给变量 BaseOfROM,TopOfROM,BaseOfBSS,BaseOfZero,EndOfBSS 这样看起来就很简结了:
BaseOfROM DCD |Image$$RO$$Base| ;BaseOfROM 地址内保存|Image$$RO$$Base|的值
TopOfROM DCD |Image$$RO$$Limit| ;
BaseOfBSS DCD |Image$$RW$$Base|
BaseOfZero DCD |Image$$ZI$$Base| ;ZI Base = RW Limit
EndOfBSS DCD |Image$$ZI$$Limit|
这样“ldr r2, BaseOfBSS”实际是加载了|Image$$RW$$Base|到 r2。
实际上也可以直接用 ldr r2,= |Image$$RW$$Base| 。
复制完 RW 数据段,需要将 ZI 段清 0。ZI 段在 SDRAM 内是紧跟着 RW 段的,所以其起始地址|Image$$ZI$$Base|
同时也是 RW 段的结束地址。
下面的代码很简单明了:
mov r0, #0 ;用 0 初始化 ZI 区
ldr r3, EndOfBSS ;r3 = |Image$$ZI$$Limit| ; ZI 段的结束地址
1
cmp r2, r3 ;r2 是 RW limit,也就是 ZI 段的起始地址
strcc r0, [r2], #4 ;ZI 数据段清 0
bcc %B1
RW Data
RO Data
Total ROM Size
ROM
ResetEntry
0x00000000
|Image$$RO$$Base|(0x30000000)
RO Data
SDRAM 加载域
|Image$$RO$$Limit|
从NOR Flash的(RO Limit – RO Base) 地址开始复制(RW Limit – RW Base)的数据到SDRAM的RW Base处
|Image$$RW$$Base|(0x30100000)
RO Limit-RO Base
|Image$$RW$$Limit|
RW Data
r0(复制后)
r0(复制前)
r2(复制后)
r2(复制前) 此后,程序就可以跳转到 Main 主函数处执行了。
[ :LNOT:THUMBCODE
ldr pc, GotoMain ;//跳转到 Main()主函数; 它把$main_entry 的绝对地址赋给 PC,由于程序
已经复制到 SDRAM 中,而且$main_entry 的绝对地址是在 SDRAM 范围内,所以从这句代码以后,程序就开
始从 ROM 跳到 SDRAM 内执行了。
b . ;就是仍跳回这一指令...不做任何事情的死循环
]
关于 GotoMain 标号,是在文件的最后定义的:
GBLS main_entry
main_entry SETS "Main"
IMPORT $main_entry
GotoMain DCD $main_entry
因为“ldr pc, GotoMain”把$main_entry 的绝对地址(即 Main 函数的入口地址)赋给 PC,由于程序已经复
制到 SDRAM 中,而且$main_entry 的绝对地址是在 SDRAM 范围内,所以从这句代码以后,程序就开始从 ROM
跳到 SDRAM 内执行了。
二、从 NAND 启动的分析
从 NAND 启动,一样是用下面这段代码做判断:
;/**************************************************************************************
;BWSCON 的[2:1]反映了外部引脚 OM[1:0]:若 OM[1:0] != 00, 从 NOR FLash 启动或直接在内存运行;
;若 OM[1:0]==00,则为从 Nand Flash 启动
;**************************************************************************************/
ldr r0, =BWSCON
ldr r0, [r0]
ands r0, r0, #6
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg
adr r0, ResetEntry ;从 NOR 或 NAND 启动,ResetEntry 都为 0, 但上面已经排除了 NOR 启动
RW Data
RO Data
SDRAM
|Image$$RW$$Base|(0x30100000)
|Image$$RO$$Base|(0x30000000)
ZI Data
|Image$$RO$$Limit|
|Image$$ZI$$Base|(|Image$$RW$$Limit|)
|Image$$ZI$$Limit|
ZI数据段清0 cmp r0, #0 ;所以入口是 0 地址表示是从 NAND 的 StepingStone 启动
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg。否则往下执行
若判断是从 NAND 启动,不会跳转到 copy_proc_beg,而是紧接着执行从 NAND 复制数据到 SDRAM 的代码,从
NAND 的开始处(第一个 Block 的头一页)复制总共 256 页即 128Kbytes 数据。过程与结果如下图所示,代码
如下。
nand_boot_beg
mov r5, #NFCONF ;首先设定 NAND 的控制寄存器;NFCONF 配置寄存器 地址 0x4E000000
ldr r0, =(1<<15)|(1<<12)|(1<<11)|(7<<8)|(7<<4)|(7)
str r0, [r5] ;
bl ReadNandID ;接着读取 NAND 的 ID 号,结果保存在 r5 里
mov r6, #0 ;r6 设初值 0.
ldr r0, =0xec73 ;期望的 NAND ID 号
cmp r5, r0 ;这里进行比较,r5 是读取到的 ID
beq %F1 ;相等的话就跳到下一个 1 标号处
ldr r0, =0xec75 ;这是另一个期望值
cmp r5, r0 ;这里进行比较
beq %F1 ;相等的话就跳到下一个 1 标号处
mov r6, #1 ;若 ID 不是 0xec73 或 0xec75, 设置 r6=1,否则 r6=0
1
bl ReadNandStatus ;读取 NAND 状态,结果放在 r1 里
mov r8, #0 ;r8 设初值 0,意义为页号(start page address)
ldr r9, =ResetEntry ;r9 设初值为 ResetEntry 的绝对地址 |Image$$RO$$Base|=0x30000000,
2
ands r0, r8, #0x1f ;若 r8 为 32 的整数倍(0,32,64...),eq 有效,ne 无效
bne %F3 ;若不是 32 的整数倍,不是当前块的头一页跳转到标号 3 处,不用检查坏块。
mov r0, r8 ;若 ands 结果所有位为 0,即 r8 是 32 的整数倍或为 0,是每个 Block 的头一页
bl CheckBadBlk ;检查 NAND 的坏区,结果保存到 r0,r0 是 0 说明当前块不是坏块
cmp r0, #0 ;比较 r0 和 0, r0==0, not bad block
addne r8, r8, #32 ;存在坏块的话就跳过这个坏块: +32 得到下一块.(1 block=32 page);
|Image$$RO$$Base|(0x30000000)
RW Data
RO Data
SDRAM
|Image$$RO$$Limit|
对于从NAND Flash启动,首先程序在4KB的StepingStone中运行,会复制128K的数据到SDRAM的RO
Base处,其中包含了RO Data与RW Data。
RW Data
RO Data
NAND Flash bne %F4 ;若有坏块就跳到 4 进行循环条件判断。没有坏块的话就跳到标号 3 处 copy 当前页
3
mov r0, r8 ;加载当前页号到 r0
mov r1, r9 ;加载复制数据的目标地址到 r1 (0x30000000)
bl ReadNandPage ;读取该页的 NAND 数据到 SDRAM 中由 r1 指定的地址
add r9, r9, #512 ;目标地址+512(每一页的大小是 512Bytes) 用于复制下一页数据
add r8, r8, #1 ;r8 指向下一页
4
cmp r8, #256 ;比较是否读完 256 页即 128KBytes 共复制 8blocks 128KB 到 SDRAM
bcc %B2 ;如果 r8 小于 256(没读完 256 页),就返回前面的标号 2 处读取下一页
;复制完成后要关闭 NAND 控制器
mov r5, #NFCONF ;Disable NandFlash
ldr r0, [r5]
and r0, r0, #~0x8000 ;clear bit15, Disable NAND Controller
str r0, [r5]
ldr pc, =copy_proc_beg
执行完 NAND 到 SDRAM 的复制,最后一句“ldr pc, =copy_proc_beg”,是重点。它把 copy_proc_beg
的绝对地址赋给 PC,由于程序的 RO Base 是 SDRAM 的起始地址空间,copy_proc_beg 的绝对地址肯定是在
SDRAM 范围内,所以从这句代码以后,程序就开始从 StepingStone 跳到 SDRAM 内执行了。程序中所有标号
的地址也相应要加上运行域的基址 RO Base(0x30000000)。另外在这句之前的程序大小要控制在 4K 以内。
然后执行 copy_proc_beg 子程序:
copy_proc_beg
adr r0, ResetEntry ;从这里开始 ResetEntry 变成了 0x30000000
ldr r2, BaseOfROM ;加载 BaseOfROM 内的数据即|Image$$RO$$Base|=0x30000000
cmp r0, r2 ;//如果从 norflash 启动,那么 ResetEntry=0 BaseOfROM=0x3000000,显然不相同
ldreq r0, TopOfROM ;如果相等的话,加载|Image$$RO$$Limit|到 r0,作为复制 RW 数据段的起始地址
beq InitRam ;同时跳到 InitRam 进行 RW 数据段的复制
对于“adr r0, ResetEntry”要特别注意, 这句代码大致会被编译成如下格式:
sub r0,PC, #offset to ResetEntry
其中“offset to ResetEntry”指编译后 adr r0, ResetEntry 语句与 ResetEntry 标号之间的地址偏移,
是固定不变的。
在执行“ldr pc, =copy_proc_beg”之前,程序还在 StepingStone 内运行,也就是从 0x0 地址开始运
行的,而 ResetEntry 本来就是程序入口,地址偏移值就为当前 PC( 假设为 0x00001000),计算结果显然为
0,则取到的 ResetEntry 值为 0x0。而在这里因为程序已经跳转到 SDRAM 内运行,起始地址为 RO
Base(0x30000000),当前语句的执行地址与 PC 值相应增加了 0x30000000(变为 0x30001000)而偏移量是固
定不变的,则取到的 ResetEntry 值为 0x30000000。
其实不用想那么复杂,只要知道因为已经完成了代码的复制,程序已经在 SDRAM 中执行,代码的执行
基址是 RO Base(0x30000000),所以“adr r0, ResetEntry”取得的 ResetEntry 相对地址是 0x30000000
就行了。
好了,下面该跳转到 InitRam 了:
InitRam
ldr r2, BaseOfBSS ;这里的 ldr 是 ARM 指令,加载 BaseOfBSS 内的数据|Image$$RW$$Base|到 r2
;也可以用 ldr r2,= |Image$$RW$$Base| ,但不太方便阅读
ldr r3, BaseOfZero ; r3= |Image$$ZI$$Base|
0 ;//复制 RW 数据到 BaseOfBSS
cmp r2, r3 ;比较 r2,r3 ldrcc r1, [r0], #4 ;R2<R3, [r0]->r1,然后 r0=r0+4, r0 初始值是 TopOfROM(|Image$$RO$$Limit|),
;也是 RW 段的起始地址,此处即 0x30000000+RO Size
strcc r1, [r2], #4 ;r2 为|Image$$RW$$Base|地址,由编译器指定
bcc %B0 ;若 r2<r3,RW 段还没有复制完
前面分析过从 NOR 启动的过程,是从 NOR 复制 RW 数据段到 SDRAM 的 RW Base 处,而对于从 NAND 启动,是
从 SDRAM 的 RO Limit 处复制 RW 数据段到 SDRAM 的 RW Base 处,因为 RO 段与 RW 段都已经从 NAND 复制到了
SDRAM。
后面将 ZI 段清 0 以及跳转到 Main 主函数的过程与前面所分析的从 NOR 启动的是一样的,不再赘述。
最后要强调的是,一定要清楚一旦程序跳转到 SDRAM 中运行,程序所有语句的运行地址都变了,要加上基
址 RO Base(本例设定为 0x30000000)。
RW Data
RO Data
SDRAM
|Image$$RW$$Base|(0x30100000)
|Image$$RO$$Base|(0x30000000)
RW Data
RO Data
SDRAM
|Image$$RO$$Limit|
从SDRAM的|Image$$RO$$Limit|处开始复制长度为RW Limit-RW Base的数据段到|Image$$RW$$Base|(0x30100000)处。
|Image$$ZI$$Base|(|Image$$RW$$Limit|)
本文若有错误之处,欢迎来信指正。
S3C2410 启动后先进行一些必要的设置,如关 WatchDog,设置 PLL 与时钟,配置 SDRAM,初始化堆栈等,
网上有很多分析启动代码的文章,本文不打算再进行说明。
本文分从 NOR Flash 与 NAND Flash 启动两种情况分别进行分析。首先要知道 NOR Flash 是 XIP 的,如
果从 NOR 启动,代码首先在 NOR Flash 内执行,考虑到运行速度,我们要把代码要复制到 SDRAM 内去执行。
当系统被设置成从 NAND FALSH 启动(使用 OM[1:0]引脚的电平来控制)时,由于其自身的特点,NAND
Flash 不具备运行程序的功能,程序不能在 NAND Flash 内执行,CPU 会自动把 NAND 内头 4K 代码加载到 CPU
的内部 SRAM(StepingStone 垫脚石)中,然后把 StepingStone 映射到 CPU 地址空间的 0 地址处(BANK0),因
此 CPU 会从 StepingStone 的 0 地址处取指令并执行。因为程序一般都会大于 4K,我们必须把代码复制到
SDRAM 中去执行。综上所述,不管是从 NOR 的启动还是从 NAND 启动,都要把代码复制到 SDRAM 中去执行,
这个复制过程要在头 4K 代码中完成。此后这 4KB 的 SRAM 还可以用作其它用途。
另外必须注意到的是 NOR 的启动与 NAND 启动的程序入口点 ResetEntry 都为 0,CPU 在加电后会去 0x0
处取指执行,如下图所示。
一、我们先看从 NOR Flash 启动的情况:
下面这段代码进行复制前的判断,因为从 NAND Flash 与从 NOR Flash 启动的复制过程是大不相同的。
;/**************************************************************************************
;BWSCON 的[2:1]反映了外部引脚 OM[1:0]:若 OM[1:0] != 00, 从 NOR FLash 启动或直接在内存运行;
;若 OM[1:0]==00,则为从 Nand Flash 启动
;**************************************************************************************/
ldr r0, =BWSCON ldr r0, [r0]
ands r0, r0, #6
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg
adr r0, ResetEntry ;从 NOR 或 NAND 启动,ResetEntry 都为 0, 但上面已经排除了 NOR 启动
cmp r0, #0 ;所以入口是 0 地址表示是从 NAND 的 StepingStone 启动
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg。否则往下执行
若判断是从 NOR Flash 启动,程序直接跳转到标号 copy_proc_beg 处执行:
copy_proc_beg
adr r0, ResetEntry ;r0= ResetEntry=0
ldr r2, BaseOfROM ;r2= |Image$$RO$$Base| = 0x30000000
cmp r0, r2 ;//如果从 norflash 启动,那么 ResetEntry=0 BaseOfROM=0x3000000,显然不相同
ldreq r0, TopOfROM
beq InitRam ;同时跳到 InitRam
因为现在程序仍然在 NOR Flash 中执行,代码的执行基址还是 0,所以“adr r0, ResetEntry”取得的
ResetEntry 相对地址是 0。则 r0≠r2,先执行下面的代码从 NOR 复制 RO 数据段到 SDRAM,不直接跳转到
InitRam 进行 RW 段复制。
ldr r3, TopOfROM
0 ;循环复制 Code 数据
ldmia r0!, {r4-r7} ;r0 的初始值 ResetEntry=0
stmia r2!, {r4-r7} ;r2 的初始值 RO Base(0x30000000)
;上面的 2 行代码将从 0 开始的地址内的数据保存到 SDRAM 内以 RO Base 开始的地址内
cmp r2, r3 ;复制的终止条件:复制了(RO Limit - RO Base) 大小,即整个 RO 数据段
bcc %B0 ;若 r2<r3,往回跳转到标号 0
sub r2, r2, r3 ;r0+16,r2+16 every time,if r2>r3
sub r0, r0, r2 ;将可能多加的部分减回来,让 r0 对准 ROM 内 RO 段的结束点,也是 RW 数据段的开始点
复制过程及结果如下图:
代码段复制完紧接着就是复制 RW 段:
RW Data
RO Data
Total ROM Size
ROM
ResetEntry
0x00000000
|Image$$RO$$Base|(0x30000000)
RO Data
SDRAM 加载域
|Image$$RO$$Limit|
从NOR Flash的0地址开始复制长度为(RO Limit – RO Base)的数据到SDRAM的RO Base处
r0(复制完后)
r0(复制前) InitRam ;从 NOR Flash 复制 RW 段到 SDRM 中预设的 RW BASE 位置
ldr r2, BaseOfBSS ;r2= |Image$$RW$$Base|=|Image$$RO$$Limit|
ldr r3, BaseOfZero ;r3= |Image$$ZI$$Base|
0 ;复制 RW 数据到 BaseOfBSS
cmp r2, r3 ;比较 r2,r3;看 RW 数据段有没有复制完
ldrcc r1, [r0], #4 ;R2<R3, [r0]->r1,然后 r0=r0+4, r0 是 RW 数据段在 ROM 内的起始地址
strcc r1, [r2], #4 ;r2 为|Image$$RW$$Base|地址,由编译器指定
bcc %B0 ;若 r2<r3,RW 段还没有复制完
从 NOR Flash 的 RO 段结束点开始复制 RW 数据到 SDRAM 的 RW Base 处,数据长度为 RW Limit - RW Base
还是有必要交代一下 ADS 中预定义的几个变量以及它们的作用。|Image$$RO$$Base|表示连接完成后程序映
像 RO 段的起始地址;|Image$$RO$$Limit|表示连接完成后程序映像 RO 段的结束地址;|Image$$RW$$Base|
表示连接完成后程序映像 RW 段的起始地址;|Image$$ZI$$Base|表示连接完成后程序映像 ZI 段的起始地址,
注意由于 RW 段与 ZI 段在运行时是紧密挨着的哦,所以这个值也就代表了 RW 段的结束地址;
|Image$$ZI$$Limit|表示连接完成后程序映像 ZI 段的结束地址。为了书写和阅读的方便,我们分别把上面
几个值赋给变量 BaseOfROM,TopOfROM,BaseOfBSS,BaseOfZero,EndOfBSS 这样看起来就很简结了:
BaseOfROM DCD |Image$$RO$$Base| ;BaseOfROM 地址内保存|Image$$RO$$Base|的值
TopOfROM DCD |Image$$RO$$Limit| ;
BaseOfBSS DCD |Image$$RW$$Base|
BaseOfZero DCD |Image$$ZI$$Base| ;ZI Base = RW Limit
EndOfBSS DCD |Image$$ZI$$Limit|
这样“ldr r2, BaseOfBSS”实际是加载了|Image$$RW$$Base|到 r2。
实际上也可以直接用 ldr r2,= |Image$$RW$$Base| 。
复制完 RW 数据段,需要将 ZI 段清 0。ZI 段在 SDRAM 内是紧跟着 RW 段的,所以其起始地址|Image$$ZI$$Base|
同时也是 RW 段的结束地址。
下面的代码很简单明了:
mov r0, #0 ;用 0 初始化 ZI 区
ldr r3, EndOfBSS ;r3 = |Image$$ZI$$Limit| ; ZI 段的结束地址
1
cmp r2, r3 ;r2 是 RW limit,也就是 ZI 段的起始地址
strcc r0, [r2], #4 ;ZI 数据段清 0
bcc %B1
RW Data
RO Data
Total ROM Size
ROM
ResetEntry
0x00000000
|Image$$RO$$Base|(0x30000000)
RO Data
SDRAM 加载域
|Image$$RO$$Limit|
从NOR Flash的(RO Limit – RO Base) 地址开始复制(RW Limit – RW Base)的数据到SDRAM的RW Base处
|Image$$RW$$Base|(0x30100000)
RO Limit-RO Base
|Image$$RW$$Limit|
RW Data
r0(复制后)
r0(复制前)
r2(复制后)
r2(复制前) 此后,程序就可以跳转到 Main 主函数处执行了。
[ :LNOT:THUMBCODE
ldr pc, GotoMain ;//跳转到 Main()主函数; 它把$main_entry 的绝对地址赋给 PC,由于程序
已经复制到 SDRAM 中,而且$main_entry 的绝对地址是在 SDRAM 范围内,所以从这句代码以后,程序就开
始从 ROM 跳到 SDRAM 内执行了。
b . ;就是仍跳回这一指令...不做任何事情的死循环
]
关于 GotoMain 标号,是在文件的最后定义的:
GBLS main_entry
main_entry SETS "Main"
IMPORT $main_entry
GotoMain DCD $main_entry
因为“ldr pc, GotoMain”把$main_entry 的绝对地址(即 Main 函数的入口地址)赋给 PC,由于程序已经复
制到 SDRAM 中,而且$main_entry 的绝对地址是在 SDRAM 范围内,所以从这句代码以后,程序就开始从 ROM
跳到 SDRAM 内执行了。
二、从 NAND 启动的分析
从 NAND 启动,一样是用下面这段代码做判断:
;/**************************************************************************************
;BWSCON 的[2:1]反映了外部引脚 OM[1:0]:若 OM[1:0] != 00, 从 NOR FLash 启动或直接在内存运行;
;若 OM[1:0]==00,则为从 Nand Flash 启动
;**************************************************************************************/
ldr r0, =BWSCON
ldr r0, [r0]
ands r0, r0, #6
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg
adr r0, ResetEntry ;从 NOR 或 NAND 启动,ResetEntry 都为 0, 但上面已经排除了 NOR 启动
RW Data
RO Data
SDRAM
|Image$$RW$$Base|(0x30100000)
|Image$$RO$$Base|(0x30000000)
ZI Data
|Image$$RO$$Limit|
|Image$$ZI$$Base|(|Image$$RW$$Limit|)
|Image$$ZI$$Limit|
ZI数据段清0 cmp r0, #0 ;所以入口是 0 地址表示是从 NAND 的 StepingStone 启动
bne copy_proc_beg ;若从 NOR 启动,跳转到标号 copy_proc_beg。否则往下执行
若判断是从 NAND 启动,不会跳转到 copy_proc_beg,而是紧接着执行从 NAND 复制数据到 SDRAM 的代码,从
NAND 的开始处(第一个 Block 的头一页)复制总共 256 页即 128Kbytes 数据。过程与结果如下图所示,代码
如下。
nand_boot_beg
mov r5, #NFCONF ;首先设定 NAND 的控制寄存器;NFCONF 配置寄存器 地址 0x4E000000
ldr r0, =(1<<15)|(1<<12)|(1<<11)|(7<<8)|(7<<4)|(7)
str r0, [r5] ;
bl ReadNandID ;接着读取 NAND 的 ID 号,结果保存在 r5 里
mov r6, #0 ;r6 设初值 0.
ldr r0, =0xec73 ;期望的 NAND ID 号
cmp r5, r0 ;这里进行比较,r5 是读取到的 ID
beq %F1 ;相等的话就跳到下一个 1 标号处
ldr r0, =0xec75 ;这是另一个期望值
cmp r5, r0 ;这里进行比较
beq %F1 ;相等的话就跳到下一个 1 标号处
mov r6, #1 ;若 ID 不是 0xec73 或 0xec75, 设置 r6=1,否则 r6=0
1
bl ReadNandStatus ;读取 NAND 状态,结果放在 r1 里
mov r8, #0 ;r8 设初值 0,意义为页号(start page address)
ldr r9, =ResetEntry ;r9 设初值为 ResetEntry 的绝对地址 |Image$$RO$$Base|=0x30000000,
2
ands r0, r8, #0x1f ;若 r8 为 32 的整数倍(0,32,64...),eq 有效,ne 无效
bne %F3 ;若不是 32 的整数倍,不是当前块的头一页跳转到标号 3 处,不用检查坏块。
mov r0, r8 ;若 ands 结果所有位为 0,即 r8 是 32 的整数倍或为 0,是每个 Block 的头一页
bl CheckBadBlk ;检查 NAND 的坏区,结果保存到 r0,r0 是 0 说明当前块不是坏块
cmp r0, #0 ;比较 r0 和 0, r0==0, not bad block
addne r8, r8, #32 ;存在坏块的话就跳过这个坏块: +32 得到下一块.(1 block=32 page);
|Image$$RO$$Base|(0x30000000)
RW Data
RO Data
SDRAM
|Image$$RO$$Limit|
对于从NAND Flash启动,首先程序在4KB的StepingStone中运行,会复制128K的数据到SDRAM的RO
Base处,其中包含了RO Data与RW Data。
RW Data
RO Data
NAND Flash bne %F4 ;若有坏块就跳到 4 进行循环条件判断。没有坏块的话就跳到标号 3 处 copy 当前页
3
mov r0, r8 ;加载当前页号到 r0
mov r1, r9 ;加载复制数据的目标地址到 r1 (0x30000000)
bl ReadNandPage ;读取该页的 NAND 数据到 SDRAM 中由 r1 指定的地址
add r9, r9, #512 ;目标地址+512(每一页的大小是 512Bytes) 用于复制下一页数据
add r8, r8, #1 ;r8 指向下一页
4
cmp r8, #256 ;比较是否读完 256 页即 128KBytes 共复制 8blocks 128KB 到 SDRAM
bcc %B2 ;如果 r8 小于 256(没读完 256 页),就返回前面的标号 2 处读取下一页
;复制完成后要关闭 NAND 控制器
mov r5, #NFCONF ;Disable NandFlash
ldr r0, [r5]
and r0, r0, #~0x8000 ;clear bit15, Disable NAND Controller
str r0, [r5]
ldr pc, =copy_proc_beg
执行完 NAND 到 SDRAM 的复制,最后一句“ldr pc, =copy_proc_beg”,是重点。它把 copy_proc_beg
的绝对地址赋给 PC,由于程序的 RO Base 是 SDRAM 的起始地址空间,copy_proc_beg 的绝对地址肯定是在
SDRAM 范围内,所以从这句代码以后,程序就开始从 StepingStone 跳到 SDRAM 内执行了。程序中所有标号
的地址也相应要加上运行域的基址 RO Base(0x30000000)。另外在这句之前的程序大小要控制在 4K 以内。
然后执行 copy_proc_beg 子程序:
copy_proc_beg
adr r0, ResetEntry ;从这里开始 ResetEntry 变成了 0x30000000
ldr r2, BaseOfROM ;加载 BaseOfROM 内的数据即|Image$$RO$$Base|=0x30000000
cmp r0, r2 ;//如果从 norflash 启动,那么 ResetEntry=0 BaseOfROM=0x3000000,显然不相同
ldreq r0, TopOfROM ;如果相等的话,加载|Image$$RO$$Limit|到 r0,作为复制 RW 数据段的起始地址
beq InitRam ;同时跳到 InitRam 进行 RW 数据段的复制
对于“adr r0, ResetEntry”要特别注意, 这句代码大致会被编译成如下格式:
sub r0,PC, #offset to ResetEntry
其中“offset to ResetEntry”指编译后 adr r0, ResetEntry 语句与 ResetEntry 标号之间的地址偏移,
是固定不变的。
在执行“ldr pc, =copy_proc_beg”之前,程序还在 StepingStone 内运行,也就是从 0x0 地址开始运
行的,而 ResetEntry 本来就是程序入口,地址偏移值就为当前 PC( 假设为 0x00001000),计算结果显然为
0,则取到的 ResetEntry 值为 0x0。而在这里因为程序已经跳转到 SDRAM 内运行,起始地址为 RO
Base(0x30000000),当前语句的执行地址与 PC 值相应增加了 0x30000000(变为 0x30001000)而偏移量是固
定不变的,则取到的 ResetEntry 值为 0x30000000。
其实不用想那么复杂,只要知道因为已经完成了代码的复制,程序已经在 SDRAM 中执行,代码的执行
基址是 RO Base(0x30000000),所以“adr r0, ResetEntry”取得的 ResetEntry 相对地址是 0x30000000
就行了。
好了,下面该跳转到 InitRam 了:
InitRam
ldr r2, BaseOfBSS ;这里的 ldr 是 ARM 指令,加载 BaseOfBSS 内的数据|Image$$RW$$Base|到 r2
;也可以用 ldr r2,= |Image$$RW$$Base| ,但不太方便阅读
ldr r3, BaseOfZero ; r3= |Image$$ZI$$Base|
0 ;//复制 RW 数据到 BaseOfBSS
cmp r2, r3 ;比较 r2,r3 ldrcc r1, [r0], #4 ;R2<R3, [r0]->r1,然后 r0=r0+4, r0 初始值是 TopOfROM(|Image$$RO$$Limit|),
;也是 RW 段的起始地址,此处即 0x30000000+RO Size
strcc r1, [r2], #4 ;r2 为|Image$$RW$$Base|地址,由编译器指定
bcc %B0 ;若 r2<r3,RW 段还没有复制完
前面分析过从 NOR 启动的过程,是从 NOR 复制 RW 数据段到 SDRAM 的 RW Base 处,而对于从 NAND 启动,是
从 SDRAM 的 RO Limit 处复制 RW 数据段到 SDRAM 的 RW Base 处,因为 RO 段与 RW 段都已经从 NAND 复制到了
SDRAM。
后面将 ZI 段清 0 以及跳转到 Main 主函数的过程与前面所分析的从 NOR 启动的是一样的,不再赘述。
最后要强调的是,一定要清楚一旦程序跳转到 SDRAM 中运行,程序所有语句的运行地址都变了,要加上基
址 RO Base(本例设定为 0x30000000)。
RW Data
RO Data
SDRAM
|Image$$RW$$Base|(0x30100000)
|Image$$RO$$Base|(0x30000000)
RW Data
RO Data
SDRAM
|Image$$RO$$Limit|
从SDRAM的|Image$$RO$$Limit|处开始复制长度为RW Limit-RW Base的数据段到|Image$$RW$$Base|(0x30100000)处。
|Image$$ZI$$Base|(|Image$$RW$$Limit|)