裸机中代码书写的细节总结
2017-12-31 19:07
393 查看
1、用汇编写的函数,末尾应该添加mov pc,lr语句。
2、裸机程序的代码编写流程、文件的引用关系
3、关于链接地址和链接脚本的一些符号标识的理解
4、关于重定位的理解
(1)在sram内部重定位,因此不需要初始化DDR
链接脚本中链接地址是0xd0024000 在SRAM中
(2)重定位至DDR,因此需要初始化DDR
链接脚本中的链接地址是0x2000 0000 在DDR中
(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的顺序,决定了第二阶段众多程序文件中,先执行哪些文件。
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的顺序,决定了第二阶段众多程序文件中,先执行哪些文件。
相关文章推荐
- quick 2.23 它们的定义c++代码lua与总结的一些细节
- 标准代码书写 C++ 的string类的用法总结
- 代码编写细节问题总结
- 嵌入式中代码书写方法的一点总结
- iOS每日总结问题版2:代码书写方面
- 黑马程序员--Java基础加强--04.代码简化 书写规律III_数组参数【重载】【数组】【可变参数数组】【泛型可变参数数组】【个人总结】
- firefox 和 ie 事件处理的细节,研究,再研究 书写同时兼容ie和ff的事件处理代码
- JavaScript常用代码书写规范的超全面总结
- java代码开发细节(根据Sonar提示的问题总结归纳的)
- JavaScript-总结常用代码书写规范
- 代码的书写规范的小总结
- 编写高质量OC代码52建议总结:9.以“族类模式“隐藏实现细节
- firefox 和 ie 事件处理的细节,研究,再研究-----书写同时兼容ie和ff的事件处理代码
- 黑马程序员--Java基础加强--03.代码简化 书写规律II_参数化数据类型【重载】【多态】【泛型】【泛型限定】【个人总结】
- JavaScript-总结常用代码书写规范
- 提高ios代码质量 细节总结1
- firefox 和 ie 事件处理的细节-----书写同时兼容ie和ff的事件处理代码
- 编写高质量OC代码52建议总结:27.使用“class-continuation 分类” 隐藏实现细节
- firefox 和 ie 事件处理的细节,研究,再研究-----书写同时兼容ie和ff的事件处理代码 (转)