重定位二:
上一节说了:
为什么需要重定位?
重定位是什么?
怎么实现重定位?(只是实现了某个全局变量或者说数据段的一个字节重定位)
本节目标:
实现整个代码的重定位:
这里只列举nor启动重定位全部代码,因为nor可以想内存一样直接读取;
nand 读取方式是通过io外接接口和内存读写方式不同,之后介绍了nand读写方式后再补充;
ok分析思路:
首先:
1、我们要重定位代码那这个函数的参数肯定要先确定:
整个代码的起始位置(src)和重定位后的位置(dest) 还有就是拷贝多长的数据(length);于是我们得到了函数的原型如下:
define vui (volatile unsigned int )
void relacate_to_sdram(vui * src, vui * dest, int length);
2、得到了函数原型后函数很容易实现:
void relacate_to_sdram(vui * src, vui * dest, int length)
{
int i =0;
for (i=0; i<length; i+=sizeof(vui *))
{
*dest++ = *src ++;
}
}
3、参数从哪里来呢??
src:代码起始位置,这个我们知道,arm起始地址都是0
dest:目标位置是sdram的起始位置0x30000000
length:长度如何确认呢?
上一节我们提到过连接脚本:
如下来自上一节的连接脚本:
SECTIONS {
.text 0 : {*(.text)} //指定地址为0 开始存放代码段,所有文件代码段放在这里
.rodata : {*(.rodata)} //所有文件只读段放在代码段之后
.data 0x30000020 : AT(0xf80){*(.data)} //指定所有文件数据段 加载地址为0xf80(即烧写到0xf80的位置, 运行的时候在0x30000020处操作)
.bss : {*(.bss) *.commen}
}
现在我们修改连接脚本使用变量:
SECTIONS {
. = 0x30000000; //指定当前地址为sdram的起始地址
_sdram_start = . //定义一个变量_sdram_start 赋值为当前地址0x30000000
.text : {*(.text)} //所有文件代码段放在这里
.rodata : {*(.rodata)} //所有文件只读段放在代码段之后
.data : {*(.data)} //指定所有文件数据段
_bss_start = . // 同理定义变量_bss_start 为bss段的起始地址
.bss : {*(.bss) *.commen}
_bss_end = . //定义——bss——end为bss段的结束地址
}
引入了一个问题: 如何使用这些变量?
有两种方式: (这个知识点不解释了,比较繁琐还挺多可以查询sysbol table 相关知识)
a) 在汇编文件中可以直接访问变量
如: ldr r0, =_sdram_start //r0=0x30000000
b)在c语言文件中使用:
1、使用extern 外部声明变量 (存放在 sysbol table中)不占据 c文件空间
如: extern _bss_end, _bss_start;
2、怎么使用这些外部声明变量: 使用取地址符
volatile unsigned int *src = (volatile unsigned int * ) _bss_end;
volatile unsigned int *dest = (volatile unsigned int * )& _bss_start;
这样一来我们定义的函数可以优化了:变量直接定义在连接脚本,然后c语言里使用就好了;
ok下面是代码实现:
连接脚本:
//关闭看门狗:
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
//设置时钟: 设置锁相环 MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m
//LOCKTIME(0x4C000000) = 0xFFFFFFFF
//设置CPU工作于异步模式
//设置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
//设置 栈 方便调用c函数
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 //sdram的初始化 这里调用,不然重定位到sdram后也没办法访问
bl copy2sdram
bl clean_bss
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM 这个很重要,我在写的时候就找了很久错误 */
copy2sdram
clean_bss
sdram_init 三个函数的实现:
void clear_bss () // start length
{
extern _bss_start, _bss_end;
volatile unsigned int *start = (volatile unsigned int *)&_bss_start;
volatile unsigned int *end = (volatile unsigned int *)&_bss_end;
while(start != end)
{
*start = 0;
start++;
}
}
void relacate_to_sdram(void) // src dest length
{
extern _run_start;
extern _bss_start;
extern _start;
//使用连接脚本中的变量 通过取地址获取;
volatile unsigned int *src = (volatile unsigned int * )0;
volatile unsigned int *dest = (volatile unsigned int * )&_run_start;
volatile unsigned int *bss_start = (volatile unsigned int *)&_bss_start;
while (dest <= bss_start )
{
*dest++ = *src++;
}
}
void sdram_init()
{
BWSCON= 0x02000000;
BANKCON6 = 0x00018001;
REFRESH |= ((0x4f5<<0)|(01<<18)|(1<<23));
BANKSIZE = 0xb0;
MRSRB6 = 0x20;
}
之后烧写至开发板:设置为nor启动,速度挺快;
这里重点说下这个语句:
汇编文件中最后跳转到main函数执行:
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main 绝对跳转, 跳到SDRAM 这个很重要
个人理解:
因为代码已经重定位了,所以执行的顺序是:
代码开始是在0地址启动运行,之后调用了 relacate_to_sdram 这个只是把nor中烧写的数据拷贝到了sdram,但是运行还是在nor中运行;
因为我们代码可能涉及对全局变量、静态变量值修改,所以要跳转到内存sdran中执行;
bl main ;执行这个指令时还是在nor中,跳转到pc+(代码执行的0 到 当前地址的偏移量) 实际上还是在 nor中执行;没有按我们想的跳转到内存中执行
ldr pc, =mian //绝对跳转,代码转到main的运行地址;就是在连接脚本中指定起始地址为0x30000000;