您的位置:首页 > 编程语言

裸机中代码书写的细节总结

2017-12-31 19:07 393 查看
1、用汇编写的函数,末尾应该添加mov pc,lr语句。

2、裸机程序的代码编写流程、文件的引用关系





3、关于链接地址和链接脚本的一些符号标识的理解



4、关于重定位的理解

(1)在sram内部重定位,因此不需要初始化DDR

链接脚本中链接地址是0xd0024000 在SRAM中

/*
* 文件名:	led.s
* 作者:	朱老师
* 描述:	演示重定位(在SRAM内部重定位)
*/

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start	// 把_start链接属性改为外部,这样其他文件就可以看见_start了

_start:

// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]

// 第2步:设置SVC栈
ldr sp, =SVC_STACK

// 第3步:开/关icache
mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
//bic r0, r0, #(1<<12)			// bit12 置0  关icache
orr r0, r0, #(1<<12)			// bit12 置1  开icache
mcr p15,0,r0,c1,c0,0;

// 第4步:重定位。(这里的代码细节说明adr是与运行相关的,ldr是与链接相关的。)
adr r0, _start     // adr指令用于加载_start当前运行地址             // adr加载时就叫短加载
ldr r1, =_start    // ldr指令用于加载_start的链接地址:0xd0024000    // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载

// bss段的起始地址
ldr r2, =bss_start	// 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可

cmp r0, r1			// 比较_start的运行时地址和链接地址是否相等
beq clean_bss		// 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
// 重定位完成后继续执行clean_bss。

// 用汇编来实现的一个while循环
copy_loop:
ldr r3, [r0], #4    // 源
str r3, [r1], #4	// 目的   这两句代码就完成了4个字节内容的拷贝
cmp r1, r2			// r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
bne copy_loop

// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end

cmp r0, r1				// 如果r0等于r1,说明bss段为空(即不存在bss段),直接下去
beq run_on_dram			// 清除bss完之后的地址

mov r2, #0

clear_loop:
str r2, [r0], #4		// 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
cmp r0, r1				// 然后r0 = r0 + 4
bne clear_loop

run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink				// ldr指令实现长跳转

/
4000
/bl led_blink					// bl指令实现短跳转

// 汇编最后的这个死循环不能丢
b .


(2)重定位至DDR,因此需要初始化DDR

链接脚本中的链接地址是0x2000 0000 在DDR中

/*
* 文件名:	led.s
* 作者:	朱老师
* 描述:	演示重定位
*/

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start
_start:
//..............

// 第4步:初始化ddr
bl sdram_asm_init //此函数末尾记得添加mov pc,lr

// 第5步:重定位,后面的代码和之前的完全一样。故不写。


(3)由此可以看出,其实两者没有什么区别,只是多了一个内存初始化操作而已。

5、Makefile中用 -Ttext 0x0 来指定链接地址是0x0。这意味着我们认为这个程序将来会放在0x0这个内存地址去运行。但是实际上我们运行时的地址是0xd0020010(我们用dnw下载时指定的下载地址)。这两个地址看似不同,但是实际相同。这是因为S5PV210内部做了映射,把SRAM映射到了0x0地址去。

这段话的意思是,SRAM的0xd0020010地址,映射到了0x0地址,因此我们可以把程序的链接地址设为0x0。而0xd0020010这个地址,是三星这个开发板启动时,BL0执行完后自动运行开始的地址,它是由CPU的设计决定的。

6、问题,BL0判断启动介质为SD卡后,怎么知道拷贝SD卡的第几扇区?怎么知道拷贝到哪个地方?

我的猜想是,BL0内部的拷贝函数,它的参数之一肯定是SD卡的第一扇区,参数之二肯定是拷贝到哪个地方(这里肯定是0xd0020000),参数之三是拷贝的大小。

7、问题,如果文件太大,分为两部分了,这个拷贝的过程是如何的。

首先,两部分烧录到SD卡的哪个扇区位置是已知的,第一部分肯定是在第一扇区开始的位置,至于第二部分,合理即可,但必须知道是哪里(后面这个位置当作参数,传给第一部分中的拷贝函数)。

然后,第一部分被BL0拷贝到SRAM中运行,运行到第一部分中的拷贝函数时,拷贝函数从SD中拷贝第二部分内容。此函数之一肯定是第二部分在SD卡中的存储位置,参数二肯定是第二部分的大小,参数三肯定是拷贝到哪个位置。

最后,第一部分代码中有一个跳转语句,跳转到刚才拷贝的第二部分的位置,执行第二部分的代码。

8、关于7中的问题验证。

(1)此时write2sd中代码

可知,两部分分别烧录至第1扇区、第45扇区开始的地方。

#!/bin/sh
sudo dd iflag=dsync oflag=dsync if=./BL1/BL1.bin of=/dev/sdb seek=1
sudo dd iflag=dsync oflag=dsync if=./BL2/BL2.bin of=/dev/sdb seek=45

(2)第一部分的逻辑关系
首先,链接脚本的链接地址是0xd0020010。很好,这个地址就是BL1该呆在的地方,因为由CPU设计时规定的一开始运行的地址就是0xd0020010。

SECTIONS
{
. = 0xd0020010;

.text : {
start.o
sdram_init.o
* (.text)
}

.data : {
* (.data)
}

bss_start = .;
.bss : {
* (.bss)
}

bss_end = .;
}


其次,BL1的makefile中,还是要把BL1做16字节填充的,这也很合理。

接着,start.S中初始化DDR后,使用copy_bl2_2_ddr函数,把BL2复制到DDR中的某个位置,并跳转到该位置执行。

#define WTCON 0xE2700000

#define SVC_STACK 0xd0037d80

.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]

// 第2步:设置SVC栈
ldr sp, =SVC_STACK

// 第3步:开/关icache
mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中
//bic r0, r0, #(1<<12) // bit12 置0 关icache
orr r0, r0, #(1<<12) // bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;

// 第4步:初始化ddr
bl sdram_asm_init

// 第5步:重定位,从SD卡第45扇区开始,复制32个扇区内容到DDR的0x23E00000
bl copy_bl2_2_ddr

// 汇编最后的这个死循环不能丢
b .

copy_bl2_2_ddr函数。
关注下0xD0037F98,这个地址是BL0内置的SD卡拷贝函数地址。

#define SD_START_BLOCK 45
#define SD_BLOCK_CNT 32
#define DDR_START_ADDR 0x23E00000

typedef unsigned int bool;

// 通道号:0,或者2
// 开始扇区号:45
// 读取扇区个数:32
// 读取后放入内存地址:0x23E00000
// with_init:0
typedef bool(*pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int*, bool);

typedef void (*pBL2Type)(void);

// 从SD卡第45扇区开始,复制32个扇区内容到DDR的0x23E00000,然后跳转到23E00000去执行
void copy_bl2_2_ddr(void)
{
// 第一步,读取SD卡扇区到DDR中
pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)0xD0037F98);

p1(2, SD_START_BLOCK, SD_BLOCK_CNT, (unsigned int *)DDR_START_ADDR, 0); // 读取SD卡到DDR中
// 第二步,跳转到DDR中的BL2去执行
pBL2Type p2 = (pBL2Type)DDR_START_ADDR;
p2();
}


(3)第二部分的逻辑关系

首先,链接地址应该由第一部分的拷贝函数拷贝将第二部分到哪里决定。由上面可知拷贝到0x23E00000,那么第二部分的连接地址也应该是0x23E00000。果真如此。

SECTIONS
{
. = 0x23E00000;

.text : {
start.o
* (.text)
}

.data : {
* (.data)
}

bss_start = .;
.bss : {
* (.bss)
}

bss_end = .;
}

其次,BL2的makefile中,不会再添加16字节填充的操作。果真如此。

然后,至于为什么会先执行start.S,是因为链接脚本中的.o文件的顺序决定的。start.S中的内容如下。

#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80

.global _start

_start:
ldr pc, =main // ldr指令实现长跳转

// 汇编最后的这个死循环不能丢
b .

此时,会到有main函数的文件中,执行main函数。

总结:

(1)拷贝函数还是使用BL0中的拷贝函数。

(2)第二阶段的运行入口,是第二阶段的链接脚本指定的位置(该位置由第一阶段拷贝函数将内容拷贝到哪里决定)。

(3)第二阶段的脚本中.o的顺序,决定了第二阶段众多程序文件中,先执行哪些文件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: