S3C2440裸机------代码重定位

发布者:Xiangsi最新更新时间:2021-09-07 来源: eefocus关键字:S3C2440  裸机  代码重定位 手机看文章 扫描二维码
随时随地手机看文章

1.段的概念

我们的cpu可以直接把地址通过内存控制器发送到norflash,sram,sdram,但是我们的cpu不能直接到达nandflash,只能发送到nandflash控制器,我们的程序可以放到norflash以及sdram上面,它可以直接运行,但是nandflash的程序是不能直接运行的,但是我们仍然可以把程序放到nandflash上面,因为一上电2440内部的硬件会把nandflash的前4K代码复制到片内的SRAM,这个是由硬件做的,然后cpu从0地址开始运行,此时的0地址对应的是sram。那么如果我的程序超过4K怎么办,如果bin文件在nandflash上面超过4K,那么我们不能只复制前面4K代码,所以显然我们前面4K代码应该把整个程序都复制到SDRAM上面。这就是重定位,重新确定程序的地址。


如果我是用norflash启动,此时的0地址在norflash,此时片内4Ksram的地址是0x4000,0000.norflash可以像内存一样读,但是不能像内存一样的写。如果程序中含有需要更改的全局变量或者静态变量,全局变量是包含在bin文件中,烧在norflash上面(局部变量是在栈中在SRAM上面,可读可写没有问题),直接修改变量无效。所以我们需要把这些全局变量和静态变量重定位放到SDRAM中。


我们修改之前的main.c,然后在里面定义一个全局的变量,可以发现串口一直打印A,并没有递增的打印ABCD。


#include "s3c2440_soc.h"

#include "uart.h"

#include "init.h"

 

char g_Char = 'A';

const char g_Char2 = 'B';

int g_A = 0;

int g_B;

 

int main(void)

{

uart0_init();

 

while (1)

{

putchar(g_Char);

g_Char++;         /* nor启动时, 此代码无效 */

delay(1000000);

}

 

return 0;

}


我们的程序主要分为:


代码段(text):代码 


数据段(data):一般的全局变量


只读数据段(rodata) :const全局变量。


bss段:初值为0或者无初始值的全局变量。BSS段不保存在bin文件中。


comment:注释:也不保存在bin文件中。


2.链接脚本的引入与简单测试

通过上一节的测试,我们发现当我们把程序烧写到norflash时,由于norflash是不可写的,因此不能修改g_Char,所以我们需要通过修改makefile,让g_Char放到SDRAM里面,我们的代码仍然放在NORFLASH里面,只是把变量保存到SDRAM里面,由于我们的SDRAM的地址是从0X30000000开始的,因此我们修改makefile为如下的形式。


all:

arm-linux-gcc -c -o led.o led.c

arm-linux-gcc -c -o uart.o uart.c

arm-linux-gcc -c -o init.o init.c

arm-linux-gcc -c -o main.o main.c

arm-linux-gcc -c -o start.o start.S

arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf

arm-linux-objcopy -O binary -S sdram.elf sdram.bin

arm-linux-objdump -D sdram.elf > sdram.dis

clean:

rm *.bin *.o *.elf *.dis

但是编译完之后,我们发现我们的bin文件达到了800M,这是因为我们把变量放在了0X30000000这里,但是我们显然不能这么改, 针对这个问题有两个解决方案。


2.1方案一

首先我们在bin文件里面,让代码段和全局变量靠在一起,然后把bin文件烧写到norflash上面,然后运行的时候,我们前面的代码需要把全局变量复制到0X30000000的地方, 这就是重定位。


2.2方案二

第二种方法是,我们在链接的时候,让代码段和全局变量中间没有那么大的空闲,我们的代码段也从0x30000000开始,然后烧写到norflash,然后运行的时候,我们前面的代码把代码段和全局变量整个程序复制到0x30000000。


这两种方案的差别在于,第一种方法只是重定位了全局变量,第二种方法是重定位了整个程序。


下面我们先来看方案一:我们通过引入链接脚本,将本来应该位于0X30000000的变量和代码段拼在一起,我们首先修改makefile为下面的形式:


all:

arm-linux-gcc -c -o led.o led.c

arm-linux-gcc -c -o uart.o uart.c

arm-linux-gcc -c -o init.o init.c

arm-linux-gcc -c -o main.o main.c

arm-linux-gcc -c -o start.o start.S

#arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf

arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf

arm-linux-objcopy -O binary -S sdram.elf sdram.bin

arm-linux-objdump -D sdram.elf > sdram.dis

clean:

rm *.bin *.o *.elf *.dis

然后我们来写出这个链接脚本sdram.lds 


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) { *(.data) }

   .bss  : { *(.bss) *(.COMMON) }

}


上面的{}里面的*表示所有文件的.text段,所有文件的.rodata段。其中的AT表示at,也就是全局变量运行的时候是在0x30000000,但是在bin文件中我们把它放在0x800这里。


我们把上面的程序烧写到板子上之后显示乱码,这是因为我们的bin文件中,全局变量是保存在0X800的地方,但是我们的main函数中是以0x30000000为地址去访问全局变量的。而我们的代码中并没有把0x30000000地址的地方用全局变量的值进行初始化,也就是我们的代码中缺少重定位相关的代码,我们需要修改代码,将变量从0x800的地方复制到0x30000000的地方,我们修改start.S,在执行main函数之前进行重定位。


.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 /*首先要初始化SDRAM,要不然下面的重定位代码不起作用,没法写SDRAM*/

 

/* 重定位data段 */

mov r1, #0x800

ldr r0, [r1]

mov r1, #0x30000000

str r0, [r1]

 

bl main

 

halt:

b halt


上面的重定位data的代码中,我们简单粗暴的从0x800复制了一个字节到0x30000000这里,这种方法并不通用,首先我们程序中有可能有很多个全局变量,另外我们这里的地址0X800和0x30000000都是写死的,这是因为肉眼从链接脚本中观察到的,下面我们写一个通用的程序,首先要在链接脚本中添加一些变量。


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }

   .bss  : { *(.bss) *(.COMMON) }

}

 

其中data_start后面的.表示当前地址,也就是0x30000000, data_end-data_start就等于数据段的长度,LOADADDR(.data)表示data段在bin文件中的地址,也就是0x800.


然后我们修改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

 

/* 重定位data段 */

ldr r1, =data_load_addr  /* data段在bin文件中的地址, 加载地址 */

ldr r2, =data_start /* data段在重定位地址, 运行时的地址 */

ldr r3, =data_end      /* data段结束地址 */

 

cpy:

ldrb r4, [r1]

strb r4, [r2]

add r1, r1, #1

add r2, r2, #1

cmp r2, r3

bne cpy

 

bl main

 

halt:

b halt


3.链接脚本的解析

我们上一节用到了链接脚本,现在我们来解析下前面用到的连接脚本。


首先通过文档 http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html,我们知道链接脚本的格式是这样的:


SECTIONS {

...

secname start BLOCK(align) (NOLOAD) : AT ( ldadr )

  { contents } >region :phdr =fill

...

}


在链接脚本格式中,BLOCK(align)很少用到,NOLOAD也很少用到,region:phdr=fill也很少用到。里面的secname名字我们可以随便写,也可以不叫.text .rodata,叫什么名字都可以,start表示运行时的起始地址runtime address,也叫重定位地址relocate address,AT可写也可以不写,表示加载地址,LOADaddress不写时,就等于runtime address.


然后就是contents的格式,contents格式有下面3种:


1:start.o:你可以 指定start.o,把整个start.o放到这个段里面。


2:*(.text):指定所有.o的text段放到这里面。


3:start.o


*(.text):指定start.o放到前面,然后剩下所有文件的text段放到


elf文件地址问题:


我们的makefile中通过链接脚本将一些.o文件生成了elf格式的文件,elf文件中是含有地址信息的,比如加载地址。


我们可以使用加载器把elf文件加载到内存的加载地址load address,对于裸板来说,JTAG调试器就是加载器,对于应用程序来说,加载器本身也是一个应用程序,

然后运行程序。


如果链接脚本中指定了load address ,然后load address不等于runtime address,那么程序本身还需要重定位代码。


裸板bin文件:


elf格式文件生成bin文件。

硬件机制的启动

如果bin文件所在位置不等于运行地址,那么需要程序本身实现重定位。

下面看一下我们前面用到的链接脚本: 


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }

   .bss  : { *(.bss) *(.COMMON) }

}

 

第一行没有写加载地址,那么我们的text的加载地址就等于运行地址,也就是0.不要进行重定位。另外这里text是保存的是所有文件的text段,这里的所有文件的顺序就是按照makefile里面写的先后顺序arm-linux-ld -Ttext 0 -Tdata 0x30000000  start.o led.o uart.o init.o main.o -o sdram.elf,然后看data段,这里加载地址是0x800,然后运行地址是0x30000000.那么我们程序启动之后需要把数据段从0x800拷贝到0x30000000,这就是重定位。另外,在elf或者bin文件中都不会存bss段,因为bss中是初始值为零的或者没有初始化的全局变量,假如我们有很多初始化为零的全局变脸,那么我们保存这么多零浪费空间。


我们通过编写程序,将初始化为零的全局变量打印出来,发现它并不是零,这是因为我们没有把他清零,我们需要写程序 将BSS段对应的内存清零, 要想清零,需要知道BSS空间的地址是什么,首先要修改链接脚本,


SECTIONS {

   .text   0  : { *(.text) }

   .rodata  : { *(.rodata) }

   .data 0x30000000 : AT(0x800) 

   { 

      data_load_addr = LOADADDR(.data);

      data_start = . ;

      *(.data) 

      data_end = . ;

   }

   

   bss_start = .;

   .bss  : { *(.bss) *(.COMMON) }

   bss_end = .;

}

 

然后修改start.S清除BSS段。


 

.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] /* 读出原来的值备份 */

[1] [2] [3]
关键字:S3C2440  裸机  代码重定位 引用地址:S3C2440裸机------代码重定位

上一篇:ARM64异常
下一篇:s3c2440的Memory Controller与外设地址线错位连接分析

推荐阅读最新更新时间:2024-11-17 14:02

LTC2207在S3C2440和EP3C25控制下的采集应用
本设计中数据采集系统的核心器件是凌力尔特公司的A/D转换芯片LTC2207。本文研究了在ARM核S3C2440芯片和FPGA的控制下对直流数据和正弦信号的采集应用,并进行了相关的仿真验证。 1 LTC2207芯片介绍 1.1 LTC2207的功能特性 LTC2207是16位A/D转换器,它的采样速率为105Msps。LTC2207是针对输入频率为700 MHz的高频、宽动态范围信号进行数字化处理而设计的。它可以利用PGA前端(输入范围为1.5Vp-p或2.25Vp-p)对该ADC的输入范围进行优化。 LTC2207非常适合于要求苛刻的通信应用。它的AC性能包括78.2 dB噪声层和100 dB无杂散动态范围(SFDR);25
[模拟电子]
LTC2207在<font color='red'>S3C2440</font>和EP3C25控制下的采集应用
S3C2440-时钟计算
S3C2440有两个PLL(phase locked loop)一个是MPLL,一个是UPLL。MPLL用于CPU及其他外围器件,UPLL用于USB。 S3C2440A 中的时钟控制逻辑可以产生必须的时钟信号,包括CPU 的FCLK,AHB 总线外设的HCLK 以及APB 总线外设的PCLK。S3C2440A 包含两个锁相环(PLL):一个提供给FCLK、HCLK 和PCLK,另一个专用于USB 模块(48MHz)。时钟控制逻辑可以不使用PLL来减慢时钟,并且可以由软件连接或断开各外设模块的时钟, 从S3C2440的DataSheet里可以看到,S3C2440最大支持400MHz的主频,但是这并不意味着一定工作在400MH
[单片机]
s3c2440裸机-异常中断3-swi软中断
swi(软中断) 我们知道arm有7中工作模式,除了usr模式,其他6种都是特权模式。我们知道usr模式无法修改CPSR直接进入其他特权模式,但linux应用程序一般运行在usr模式,既然usr模式权限非常低,是无法直接访问硬件寄存器的,那么它是如何访问硬件的呢? linux应用程序是通过系统调用,从而进入内核态,运行驱动程序来访问的硬件,那么系统调用又是如何实现的呢,就是通过软中断swi指令来进入svc模式,进入到svc模式后当然就能访问硬件啦。 所以我们的应用程序在usr模式想访问硬件,必须切换模式,怎么切换? 有以下两种方式: 1.发生异常或中断(被动的) 2.swi + 某个值(主动的) 现在介绍如何进
[单片机]
基于S3C2440处理器拳击娱乐系统设计
引 言 拳击娱乐系统是为满足广大群众的需要而设计,目前市场上还没有模拟真人的对抗性的拳击娱乐设备,该系统的出现使人们从枯燥的锻炼中摆脱出来,能够主动地投入到娱乐中。在人形靶的头部正面和胸部正面各安装一个击打气袋和压力传感器,当语音提示可以开始击打时,使用者可以全力击打头、胸部,同时ARM9处理器采集相应的击打数据,在人形靶上安装有自动出击拳装置。 该系统选用了Samsung公司的16/32位RISC处理器S3C2440,由于该处理器价格低、功耗小、性能高,从而降低了整个系统的成本,便于市场的推广。 在用户界面的设计上,采用目前流行的Qt/Embedded,使显示界面更直观,操作更方便。Qt/Embedded丰富的控件资源和较好的
[单片机]
基于<font color='red'>S3C2440</font>处理器拳击娱乐系统设计
华为GX8登陆美国市场 裸机售价350美元
     在年初的CES(国际消费电子展)期间,华为方面就公开表示将于今年某个时候在美国市场推出——华为GX8。如今,华为GX8正式登陆美国市场,裸机售价为350美元(约合人民币2300元)。华为GX8的上市,无疑将会进一步拓展华为手机的海外市场,为其2016年的海外市场贡献一份力。 华为GX8登陆美国市场   外观方面,华为GX8采用金属机身设计,极具档次和品味,如图所示。配置方面,它拥有5.5英寸大屏,视野开阔,分辨率则达到了1080p级别,搭载 64位高通骁龙八核处理器,配备2GB RAM和16GB机身内存,运行Android 5.1 Lollipop操作系统,同时还拥有1300万像素主摄像头,整体配置属于时下中端水准。
[手机便携]
基于S3C2440的嵌入式Linux根文件系统构建
嵌入式Linux早已成为IT界家喻户晓的一个名字,使用Linux进行嵌入式产品开发有一个很大的优势,就是开发资源丰富,且成本低廉,嵌入式Linux操作系统越来越受到重视,其应用也越来越广泛。而文件系统作为操作系统的重要组成部分,用于控制对数据文件及设备的存取,提供对文件和目录的分层组织形式,数据缓冲以及对文件存取权限的控制。根文件系统一直是Linux系统不可或缺的组件,在嵌入式Lin-ux中,内核在启动期间进行的最后操作之一就是安装根文件系统。Busybox是构建嵌入式Linux根文件系统的软件,用它制作根文件系统简单、方便,而且设置灵活。 1 根文件 Linux要在一个分区上存放系统启动所必需的文件,如内核映像文件、内核
[单片机]
基于<font color='red'>S3C2440</font>的嵌入式Linux根文件系统构建
Tiny4412之C语言实现流水灯,Tiny4412裸机程序[3]
在前边我们使用汇编完成了一个流水灯实验: Tiny4412汇编流水灯代码,Tiny4412裸机LED操作 ---- - -- -- -- - -- -- 修改: # ${MKBL2} ${SOURCE_FILE} bl2.bin 14336 ./${MKBL2} ${SOURCE_FILE} bl2.bin 14336 或者: MKBL2=my_mkbl2改成MKBL2=./my_mkbl2 必须有:chmod +x my_mkbl2 chmod 777 my_mkbl2 然后 查看下权限 ls -l my_mkbl2 变黄即可 后编译成功 r
[单片机]
Tiny4412之C语言实现流水灯,Tiny4412<font color='red'>裸机</font>程序[3]
s3c2440 移值u-boot-2016.03 第1篇 新建单板
目前除RC版外,最新的就是 u-boot-2016.03.tar.bz2 ,大概看了几个年份的u-boot 发现,现在 更像是 linux kernel 。有 menuconfig 。 对比2012年的版本,发现 原来在 start.S 中做的一些事情,被拆分了。 board 分的更加详细。 之前没有 J-LINK 根本无法烧写 NOR FLASH ,导致 u-boot 只能在 NAND FLASH 上,但是卡在 重定向这一处。用了点灯大法,串口调试,都不是很理想。 买了个J-LINKV9 ,一试,原来烧写,NOR FLASH 这么简单。 因为 u-boot 自带的 只有 2410 的 单板,而且是不支持 NAND FLASH 的
[单片机]
<font color='red'>s3c2440</font> 移值u-boot-2016.03 第1篇 新建单板
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved