nand启动,需要重定位
S3C2440的CPU可以直接给SDRAM发送命令、给Nor Flash发送命令、给4K的片上SDRAM发送命令,但是不能直接给Nand Flash发送命令,CPU无法直接访问Nand Flash,因为之间还隔了个Nand Flash控制器。
问:那为什么还可以nand启动?
答:1.nand启动时,前4k代码由硬件自动复制到SRAM。2.此时SRAM的基地址为0地址,CPU从SRAM的0地址开始运行。
问:那么问题来了,烧录至Nand Flash的bin文件大于4k怎么办?
答:前4k代码需要把整个程序读出,复制到SDRAM。然后去SDRAM执行程序。
把程序从nand读出复制到SDRAM的过程就叫做重定位。
注:1.nand启动时,SRAM地址为0。
2.nor启动时,SRAM地址为0x40000000。
nor启动,也需要重定位
CPU可以像读内存一样直接读Nor Flash,但是不能像写内存似的直接写Nor Flash。
程序中的全局变量、静态变量,它们在bin文件中,写在Nor Flash中,若直接修改它们,则无效。所以,也需要重定位来把程序读出,复制到SDRAM。就可以直接读和写了。
综上所述,无论是nand启动还是nor启动,都需要代码重定位。
段的概念:
一个程序里面有
- .text 代码段
- .data 数据段
- rodata 只读数据段(const全局变量)
- bss段 (初始值为0,无初始值的全局变量)
- commen 注释
其中bss段和commen 注释不保存在bin文件中。
链接脚本
作用:将若干个输入文件(.o文件)按照一定的规则合并为一个输出文件。
SECTIONS
{
. = 0x30000000;
__code_start = .;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
上面链接脚本就把程序的首地址定位0x30000000,0x30000000就是SDRAM的基地址。
编程
目的:把整个程序从NOR Flash 重定位至SDRAM。
把整个程序复制到SDRAM需要哪些技术细节:
- 把程序从Flash复制到运行地址,链接脚本中就要指定运行地址为SDRAM地址;
- 编译链接生成的bin文件,需要在SDRAM地址上运行,但上电后却必须先在0地址运行,这就要求重定位之前的代码与位置无关(是位置无关码);
将修改后的代码重新编译烧写在Nor Flash上,上电运行。 对本代码的启动情况进行分析:
在生成的bin文件里,代码保存的位置是0x30000000。随后烧写到NOR Flash的0地址,但代码的结构没有变化。之后再重定位到SDRAM。
怎么写位置无关码?
使用相对跳转命令 b或bl;
- 重定位之前,不可使用绝对地址,不可访问全局变量/静态变量,也不可访问有初始值的数组(因为初始值放在rodata里,使用绝对地址来访问);
- 重定位之后,使用ldr pc = xxx,跳转到/runtime地址;
- 写位置无关码,其实就是不使用绝对地址,判断有没有使用绝对地址,除了前面的几个规则,最根本的办法看反汇编。
重定位需要的几个函数:
sdram_init(); //初始化SDRAM,为后面复制代码过来做准备
copy2sdram(); //把Nor Flash中的代码复制到SDRAM
clean_bss(); //清除bss段
代码如下:
void sdram_init(void)
{
BWSCON = 0x22000000;
BANKCON6 = 0x18001;
BANKCON7 = 0x18001;
REFRESH = 0x8404f5;
BANKSIZE = 0xb1;
MRSRB6 = 0x20;
MRSRB7 = 0x20;
}
void copy2sdram(void)
{
/* 要从lds文件中获得 __code_start, __bss_start
* 然后从0地址把数据复制到__code_start
*/
extern int __code_start, __bss_start;
volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
volatile unsigned int *src = (volatile unsigned int *)0;
while (dest < end)
{
*dest++ = *src++;
}
}
void clean_bss(void)
{
/* 要从lds文件中获得 __bss_start, _end
*/
extern int _end, __bss_start;
volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&_end;
while (start <= end)
{
*start++ = 0;
}
}
这些函数要在程序运行开始的时候调用(就是调用main之前调用),在start.S调用。
所以start.S要增加代码为:
/*
*文件:start.S
*/
.text
.global _start
_start:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
ldr pc, =sdram /* 跳转到SDRAM执行 */
sdram:
bl main
halt:
b halt