前言,这节课会涉及到重定位、位置无关码,不是很了解的请先看我的这篇文章:
链接地址、运行地址、加载地址、存储地址、位置相关与位置无关
一、重定位引入
首先我们知道我们的代码是烧写在flash中的,而且我们的设备是区分nor flash与nand flash;
nor启动:
如果我们使用nor启动,这时nor flash就会被映射成0地址,但是由于nor flash特性,我们不能按照SDRAM对nor flash进行写操作,所以需要将全局变量及静态变量放到SDRAM中。
nand启动:
如果我们使用nand启动,由于cpu不能对nand flash进行跟SDRAM一样的数据读写,所以启动的时候,硬件会实现一个复制的功能,既将nand flash前4K的内容复制到片内的4Ksram,这个时候,片内的4Ksram会被映射到0地址。所以nand 启动我们可以实现像读写SDRAM一样的功能。但是当文件大于4K,我们就需要将整体复制到SDRAM,复制的实现部分就应该放到前4K的代码中。
以上两种启动方法,在文件大于4K的情况下,都需要将代码或数据复制到SDRAM,这就叫做重定位。
二、链接脚本的介绍
我们知道程序中分代码段、只读数据段、数据段、.bss段、.common。
链接脚本的语法:
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
解释:
secname :段名
start :起始地址:运行时的地址(runtime addr);重定位地址(relocate addr)
AT ( ldadr ) :可有可无(load addr:加载地址) 不写时LoadAddr = runtime addr
{ contents } 的内容:
start.o //内容为start.o文件
*(.text)所有的代码段文件
start.o *(.text)文件
如果loadaddr != runtimeaddr程序本身要重定位
如果bin文件的位置不等于runtimeaddr程序本身要重定位
下面列出uboot中的一个一体式链接脚本(一体式即代码段、只读数据段、数据段、.bss段 .common连在一起)
SECTIONS
{
. = 0x30000000; //链接地址,SDRAM的地址
. = ALIGN(4); //向4字节对齐
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
_end = .;
}
有上面的脚本可知,我们的程序加载地址跟运行地址都是0x30000000,但是我们把它烧写到nor flash 中,因为nor启动是,nor flash的地址是0地址,所以也需要重定位,将nor flash中的代码复制到0x30000000处。
三、代码编写
修改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段整个程序 */
mov r1, #0
ldr r2, =_start /* 第1条指令运行时的地址 */
ldr r3, =__bss_start /* bss段的起始地址 */
cpy:
ldr r4, [r1]
str r4, [r2]
add r1, r1, #4
add r2, r2, #4
cmp r2, r3
ble cpy
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
ble clean
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
四、编译烧写
编译文件之后,在生成的bin文件里,代码保存的位置是0x30000000。随后烧写到NOR Flash的0地址,但代码的结构没有变化。之后再重定位到SDRAM。