最近在阅读 vivi,顺便将其中的针对 2410 有用的部分精简出来,构成自己的代码。
刚刚完成了汇编部分,head.S 终于顺利的跳入到了 main.c 中。这个过程我是逐个功能添加的。每添加一个功能,直到测试成功再添加下一功能。这样的累积我觉得也是有好处的,可能对每个功能模块、每个步骤都详细了解出了问题也能很快的定位。当然,最重要的是深入细节,其中可以学到很多的东西,掌握很多东西。(“学会”与“掌握”是不同的)
从 head.S 跳入 main.C 中,这个过程我出了一点差错。其实之前对运行域与加载域也有所了解,但在这里还是卡了一下。这里分析一下:
首先,要明确的是这个跳转用 B/BL 指令肯定是不行的。因为 2410 的 sdram 一般都放在 bank6(0x30000000),我们要从 bank0 跳到 bank6,而 B/BL 的范围只有 +/-32M (这在另一篇里分析过)。那么这个跳转只能由向 pc 直接写值来实现。
vivi 中的这个跳转过程采用了“弹簧床”的技巧。让我们来看看在 vivi 中它是如何实现的:
@ 10 jump to ram
ldr r1, =on_the_ram // 将 on_the_ram 的地址保存到 r1 中
add pc, r1, #0 // 将地址加载到pc中,跳入 on_the_ram
nop
nop
1:
b .
// 链接之后,on_the_ram 之后的代码都会被链接到 sdram 里的某个位置 on_the_ram: mov r1, #GPIO_CTL_BASE add r1, r1, #oGPIO_F mov r2, #0xff str r2, [r1, #oGPIO_DAT] @ print prompt information ldr r1, SerBase ldr r0, STR_STACK bl PrintWord ldr r0, DW_STACK_START bl PrintHexWord mov r0, pc bl PrintHexWord
ldr sp, DW_STACK_START mov fp, #0 mov a2, #0 // 前面这几句是我测试用的,熄灯、向串口打印堆栈地址和当前pc地址。
bl main // 这里在main.c中写了一个流水灯,便于看到效果。
mov pc, #FLASH_BASE
|
从上面可以看到,add pc, r1, #0 这一句之后程序已经开始运行在 sdram 中了。
其次,要注意一个细节:“位置无关代码”与“位置相关代码”。
位置无关代码,即代码在链接之后无论放在哪里,都能正确的运行。程序的运行是顺序的,但跳转的时候,位置的问题就来了。跳转的目标地址是如何计算来的呢?在指令 b/bl 中,目标地址是通过当前 pc 来计算的,它是目标地址是一个相对量(相对当前pc)。
位置相关代码,链接之后,代码需要放在指定的地方,才能正确的运行。链接的时候,主要是链接程序中的symbol,给其一个地址。ldr r1, =on_the_ram 这条指令中,on_the_ram 的地址值在链接后即是确定的。也就是说cpu在其后,即会跳到指定的地址去,我们必须把我们的代码也放到这个指定的地址,那么cpu才能运行正确的指令。否则就会出错。
具体来看一下这些程序的编译过程:
vivi:head.S nand_read.c
arm-linux-gcc -c head.S -I ../../include/ -o head.o
arm-linux-gcc -c nand_read.c -I ../../include/ -o nand_read.o
arm-linux-gcc -c main.c -o main.o
arm-linux-ld -Ttext 0x33f00000 head.o nand_read.o main.o -o tmp.o
arm-linux-objcopy -O binary -S tmp.o vivi
arm-linux-objdump -D -b binary -m arm vivi > aaa
clean:
rm *~ -f
rm *.o -f
rm vivi -f
|
Makefile 写的很初级。其中可以看到,程序的链接地址是 0x33f00000 。即链接之后,on_the_ram的地址值一定在0x33f00000之后(bank6中),而不是bank0中的地址。
还可以直接写一个链接脚本,这样可以看得更直观:
SECTIONS {
first 0x33f00000 : { head.o nand_read.o main.o }
}
这里也写的很简单,直接都链接到了0x33f00000。在ldr r1, =on_the_ram这一句之前的所有操作都是位置无关代码,复制nand的nand_read.c也自然是位置无关代码,这些无论放在0地址开始还是放在0x33f00000 都可以正确的被执行。
再次,这里为什么链接到 0x33f00000 呢?
其实很简单,在 head.S 里我把整个 代码都复制到了 0x33f00000 开始的地方。如果链接到了 0x30000000,那么cpu就是从0x30000000后面开始的相应位置支找 on_the_ram,自然是找不到的。
我开始犯了一个错误,链接到了0x30000000。
最后,来看一下反汇编代码。
dc: e59f13e8 ldr r1, [pc, #1000] ; 0x4cc // 这一句就是 ldr r1, =on_the_ram 把0x4cc处的值赋给r1,即r1 = 0x 33f000f0
e0: e281f000 add pc, r1, #0 ; 0x0
e4: e1a00000 nop (mov r0,r0)
e8: e1a00000 nop (mov r0,r0)
ec: eafffffe b 0xec
f0: e3a01456 mov r1, #1442840576 ; 0x56000000 //
此处就到了 on_the_ram,从这以下都在sdram里
f4: e2811050 add r1, r1, #80 ; 0x50
f8: e3a020ff mov r2, #255 ; 0xff
fc: e5812004 str r2, [r1, #4] // 熄灯
100: e59f138c ldr r1, [pc, #908] ; 0x494
104: e59f03b2 ldr r0, [pc, #946] ; 0x4be
108: eb0000c0 bl 0x410 // 调用打印信息的子程序
10c: e59f038c ldr r0, [pc, #908] ; 0x4a0
110: eb0000a3 bl 0x3a4 // 调用打印信息的子程序
114: e1a0000f mov r0, pc
118: eb0000a1 bl 0x3a4 // 调用打印信息的子程序
11c: e59fd37c ldr sp, [pc, #892] ; 0x4a0
120: e3a0b000 mov fp, #0 ; 0x0
124: e3a01000 mov r1, #0 ; 0x0
128: eb000174 bl 0x700 // 0x700后面就是main里的内容了
12c: e3a0f000 mov pc, #0 ; 0x0
4cc: 33f000f0 mvnccs r0, #240 ; 0xf0 //这条指令的机器码是33f000f0,实际上是存储了这么一个值
4d0: 0000f830 andeq pc, r0, r0, lsr r8 //这里也是同一个道理,存储了0x0000f830这么一个值,这个值看起来眼熟吧,是配置nand flash用的
|
最后,贴一下打印出来的信息:
@000000D4 //初始化UART好了之后,第一次打印pc值
MTST-OK // 内存检测
OK // nand复制
NAND-OK // 复制后检测通过
ST
33DEFFFC // 栈指针
33F0011C // 当前pc
|
阅读(1827) | 评论(0) | 转发(0) |