嵌入式ARM裸板程序——存储管理器驱动
2016-10-06 12:30
260 查看
本博文是根据百问网韦东山老师的嵌入式视频教程,基于jz2440进行嵌入式开发学习所记录的学习体会,如有雷同纯属巧合。
而存储管理器的作用,就是可以驱动相关硬件设置,使得程序可以不局限于只能在SRAM中运行,所以这个裸板程序的作用就是为了驱动SDRAM这个片外内存部件,使得我们的程序可以在更大的SDRAM中跑起来。
因此,CPU就是通过存储管理器的片选、bank设置和相应的寻址功能,可以访问片外的内存类部件:SDRAM、DM9000网卡、NandFlash、NorFlash等;在2440开发板上,单板使用了不同的地址来标识这些不同的外部内存类设备,比如0x30000000便是SDRAM的起始地址,而0x48000000则是代表了存储管理器的BUS WIDTH & WAIT CONTROL REGISTER (BWSCON)即位宽和等待状态控制寄存器的起始地址。
接下来我们直接分析程序的相关代码,详细理解一个程序的原理最好的入口就是源代码。由于这个存储管理器程序的最主要目的是为了驱动SDRAM,当SDRAM成功被设置启动后,我们便可以将前面使用过的点灯c程序放到SDRAM中运行。所以这个程序运行后的效果其实和GPIO中的没什么区别。但是实际上有着本质的区别:前面的GPIO点灯LED程序是在片内SRAM中运行的;而经过存储管理器的相关设置后,当SDRAM被成功设置启动后,我们就可以把更大更多的程序放在SDRAM中运行,并且,实际上SRAM中指令运行的速度要高于SDRAM,只是这里并未有很好的例子可以显示这两者之间的效果差异。但是实际上还是得详细区别好。
下面我们先看这个程序中最重要的head.S:
由于源码注释已经算是足够详细了,这里我们结合自身的理解对整个驱动过程做一个分析和探讨。
首先是前两个全局符号的定义:
.equ MEM_CTL_BASE, 0x48000000
.equ SDRAM_BASE, 0x30000000
这两个我们可以简单理解为c中的宏定义,其中MEM_CTL_BASE代表了存储管理器的设置寄存器地址,SDRAM_BASE代表了设置完毕后SDRAM的起始地址位置,这些都可以通过阅读芯片手册可以了解到,这里不再赘述。
我们直接看主要的调用汇编函数:
这个模块中一开始先调用关闭看门狗功能模块disable_watch_dog,程序会跳到disable_watch_dog:这个符号标志所在的入口地址执行里面的代码指令。关于这个模块简单的理解就是直接用str指令向看门狗寄存器地址0x53000000写入0,设置相关的寄存器位关闭看门狗功能。随后该模块会调整PC指针让其指向lr(即连接寄存器所保存的返回地址内容)成功返回到调用模块指令结束的位置继续下一个指令。
接着程序会执行bl memsetup跳转到设置存储管理器的模块位置执行设置步骤,我们先看看这个设置过程的代码:
首先程序会保存存储管理器的设置寄存器地址和用adrl指令将mem_cfg_val位置地址保存在r2寄存器中,mem_cfg_val所存储的设定值是一个按4字节对齐的13个寄存器的设置值。可以这么理解,这个mem_cfg_val标记位置处相当于有一个存放了13个数组元素的数组存在,而且是long型的数据,这个数组里面的值便是对应的存储管理器的设置寄存器的设定值,用以驱动SDRAM可以正常工作。
随后将用r3寄存器保存一个判断值,这个判断值的内容是MEM_CTL_BASE相对偏移地址52字节处的位置,用于后面在逐一设置13个寄存器的设定值时判断是否已经设置完毕全部的13个寄存器。
接着程序就进入一个循环体进行针对13个寄存器的逐一赋值设置,r1和r2每次设定完毕后都会将当前的指针自加4字节的偏移量代表完成当前寄存器的设置;当全部的13个寄存器都设定完成后程序便会调整PC返回。
当程序完成上述过程后就已经完成了SDRAM的初始化工作,此时SDRAM已经可以被使用,由于上述的过程原理特别是寄存器设定值需要结合芯片手册和视频教程进行理解。具体这边由于篇幅所限便不再展开。程序完成初始化工作后便开始调用copy_steppingstone_to_sdram模块将当前SRAM中的4K数据全部拷贝至SDRAM中,每次拷贝都按4字节的拷贝大小为基本单元。
这里需要注意的一点是在copy_steppingstone_to_sdram被调用之前程序一直都是在片内SRAM中运行的,当这条指令所调用的过程被完成后,此时片内SRAM中的所有程序数据都被成功转移至SDRAM中。随后程序执行ldr pc, =on_sdram调整PC指针使程序流转到on_sdram这个符号标记的地址入口处,之后程序会在SDRAM中设置函数堆栈为调用c的main函数做准备(注意:这时候的函数堆栈是设置在了SDRAM中,所以c函数此时已经可以从SDRAM中执行),堆栈sp设定在了64M大小的SDRAM的顶端。
这里需要说明一个问题:程序不是一开始上电时被拷贝至SRAM中的么?那么此时程序代码段text的起始地址应该是0,此后的程序代码部分所在的地址应该都是相对绝对地址0的地址,那么拷贝到SDRAM也就是0x30000000起始的地址后,整个程序的地址到底是更新了?还是做了什么特定的变化?
在解决这个疑虑之前,我们先看看makefile中关于链接部分的代码:
这条指令指定了程序的链接地址是0x30000000也就是SDRAM的起始地址。那么也就是说明程序的运行地址就是起始于0x30000000。可是为什么一开始程序被拷贝至SRAM中时,head.S中的代码可以被执行呢?
原因在于bl跳转指令,这个bl指令是一个位置无关代码,也就是说该段代码无论放在内存的哪个地址,都能正确运行。原因是因为代码里没有使用绝对地址,都是相对地址。这跟链接相关的概念有关,我们这里先做一点简单的解析和说明,程序都需要经过编译、汇编和链接后才可以变成可执行文件。在链接时,要对所有目标文件.o进行重定位(relocation),建立符号引用规则,同时为变量、函数等分配运行地址。这些运行地址其实是虚拟地址也就是相对地址,当程序执行时,系统必须把代码加载到链接时所指定的地址空间,以保证程序在执行过程中对变量、函数等符号的正确引用,使程序正常运行。一般来说中重定位过程由操作系统自动完成。而通常来说链接脚本中所规定的地址是虚拟地址,也就是指烧录完成后的映像文件执行时,各个不同的输出段所重定位到相应的存储地址空间,与映像文件烧写到的实际的地址无关(即映像的加载地址)。重定位的时候需要参考的地址就是相对地址。
举个栗子来看看:
我们直接先看这个SDRAM程序反汇编后所生成的文件内容:
需要指出的是ARM架构下的程序计数器PC指针的计算方式是当前地址值 + 8字节偏移量,此时我们注意到,反汇编文件中关于程序的地址信息其实是虚拟链接地址,也就是这个程序运行时应该位于的位置。但是实际上,程序在未调用完成copy_steppingstone_to_sdram之前地址其实一直都是相对于SRAM的起始地址0x0的相对地址。因此这里的
其实实际上真正的地址是
因为此刻其实真正执行的地方是片内内存SRAM,所以此刻的真正地址是这个部分。
这里我们再次重温下这个位置无关代码的基本特性:
位置无关代码在引用同一位置无关段或相对位置固定的另一位置无关段中的符号时,必须是基于PC的符号引用,即使用相对于当前PC的偏移量来实现跳转或进行常量访问。在本程序中就是使用了bl跳转指令进行相对地址的跳转,而且也是基于PC的偏移量。
基本特性是:
1.位置无关的程序跳转。使用相对跳转指令(如bl)实现程序跳转。指令中所跳转的目标地址用基于当前PC的偏移量来表示,与链接时分配给地址标号的绝对地址值(本程序中就是链接时指定的0x30000000)无关,所以在运行时可以在任何位置进行跳转,实现位置无关性。
2.位置无关的常量访问。在应用程序中,经常要读写相关寄存器以完成必要的硬件初始化。为增强程序的可读性,利用EQU伪指令对一些常量进行赋值,但在访问过程中,必须实现位置无关性。
3 使用绝对地址进行跳转(如jmp指令),一般是在不同的位置无关代码段之间跳转。也就是最好不要在位置无关代码段中使用绝对地址进行跳转。
最后,我们大致总结下关于位置无关代码段的优点:
1.简化设计,方便实现系统的快速引导。位置无关代码可以避免在引导时进行地址映射,并方便地跳转到RAM中实现快速引导
2.实现复位处理智能化。位置无关的代码可以被加载到任意地址空间运行,只是其中的运行地址都是相对地址。
3.便于调试。Bootloader的调试通常也是一个繁琐的过程,使用位置无关代码,则可以将映像文件加载到RAM中进行调试,这既能真实地反映程序从ROM中 进行系统引导的情况,又可以避免频繁烧写程序存储器。
至此,这个关于存储管理器设置驱动SDRAM的程序最重要的部分我们便算完成了学习分析过程。由于main程序还是原来GPIO中的点灯程序,所以我们便不再加以赘述。
存储管理器
ARM的存储管理器,其实是一个存储控制器的作用。通过前面的学习我们可以了解到,在2440开发板上,单板一上电开机,开发板会自动地从flash中拷贝4K字节的数据(无论有没有)到片内内存SRAM中然后程序指针PC会从0地址开始执行指令。所以,我们如果在单板上想运行裸板程序,那么程序一般是不可以超过4K字节大小的,而且就算不超过4K大小,程序执行到最后如果需要调用到函数堆栈,一旦堆栈增长的局限区域与text代码段区域有发生重叠(尤其是递归函数调用),那么此时整个程序都会崩溃。这是因为没有OS的保护机制以及段页式内存管理,因此裸板程序的缺陷和局限性可见一斑。而存储管理器的作用,就是可以驱动相关硬件设置,使得程序可以不局限于只能在SRAM中运行,所以这个裸板程序的作用就是为了驱动SDRAM这个片外内存部件,使得我们的程序可以在更大的SDRAM中跑起来。
因此,CPU就是通过存储管理器的片选、bank设置和相应的寻址功能,可以访问片外的内存类部件:SDRAM、DM9000网卡、NandFlash、NorFlash等;在2440开发板上,单板使用了不同的地址来标识这些不同的外部内存类设备,比如0x30000000便是SDRAM的起始地址,而0x48000000则是代表了存储管理器的BUS WIDTH & WAIT CONTROL REGISTER (BWSCON)即位宽和等待状态控制寄存器的起始地址。
接下来我们直接分析程序的相关代码,详细理解一个程序的原理最好的入口就是源代码。由于这个存储管理器程序的最主要目的是为了驱动SDRAM,当SDRAM成功被设置启动后,我们便可以将前面使用过的点灯c程序放到SDRAM中运行。所以这个程序运行后的效果其实和GPIO中的没什么区别。但是实际上有着本质的区别:前面的GPIO点灯LED程序是在片内SRAM中运行的;而经过存储管理器的相关设置后,当SDRAM被成功设置启动后,我们就可以把更大更多的程序放在SDRAM中运行,并且,实际上SRAM中指令运行的速度要高于SDRAM,只是这里并未有很好的例子可以显示这两者之间的效果差异。但是实际上还是得详细区别好。
下面我们先看这个程序中最重要的head.S:
@************************************************************************* @ File:head.S @ 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行 @************************************************************************* .equ MEM_CTL_BASE, 0x48000000 .equ SDRAM_BASE, 0x30000000 .text .global _start _start: bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 bl memsetup @ 设置存储控制器 bl copy_steppingstone_to_sdram @ 复制代码到SDRAM中 ldr pc, =on_sdram @ 跳到SDRAM中继续执行 on_sdram: ldr sp, =0x34000000 @ 设置堆栈 bl main halt_loop: b halt_loop disable_watch_dog: @ 往WATCHDOG寄存器写0即可 mov r1, #0x53000000 mov r2, #0x0 str r2, [r1] mov pc, lr @ 返回 copy_steppingstone_to_sdram: @ 将Steppingstone的4K数据全部复制到SDRAM中去 @ Steppingstone起始地址为0x00000000,SDRAM中起始地址为0x30000000 mov r1, #0 ldr r2, =SDRAM_BASE mov r3, #4*1024 1: ldr r4, [r1],#4 @ 从Steppingstone读取4字节的数据,并让源地址加4 str r4, [r2],#4 @ 将此4字节的数据复制到SDRAM中,并让目地地址加4 cmp r1, r3 @ 判断是否完成:源地址等于Steppingstone的未地址? bne 1b @ 若没有复制完,继续 mov pc, lr @ 返回 memsetup: @ 设置存储控制器以便使用SDRAM等外设 mov r1, #MEM_CTL_BASE @ 存储控制器的13个寄存器的开始地址 adrl r2, mem_cfg_val @ 这13个值的起始存储地址 add r3, r1, #52 @ 13*4 = 52 1: ldr r4, [r2], #4 @ 读取设置值,并让r2加4 str r4, [r1], #4 @ 将此值写入寄存器,并让r1加4 cmp r1, r3 @ 判断是否设置完所有13个寄存器 bne 1b @ 若没有写成,继续 mov pc, lr @ 返回 .align 4 mem_cfg_val: @ 存储控制器13个寄存器的设置值 .long 0x22011110 @ BWSCON .long 0x00000700 @ BANKCON0 .long 0x00000700 @ BANKCON1 .long 0x00000700 @ BANKCON2 .long 0x00000700 @ BANKCON3 .long 0x00000700 @ BANKCON4 .long 0x00000700 @ BANKCON5 .long 0x00018005 @ BANKCON6 .long 0x00018005 @ BANKCON7 .long 0x008C07A3 @ REFRESH .long 0x000000B1 @ BANKSIZE .long 0x00000030 @ MRSRB6 .long 0x00000030 @ MRSRB7
由于源码注释已经算是足够详细了,这里我们结合自身的理解对整个驱动过程做一个分析和探讨。
首先是前两个全局符号的定义:
.equ MEM_CTL_BASE, 0x48000000
.equ SDRAM_BASE, 0x30000000
这两个我们可以简单理解为c中的宏定义,其中MEM_CTL_BASE代表了存储管理器的设置寄存器地址,SDRAM_BASE代表了设置完毕后SDRAM的起始地址位置,这些都可以通过阅读芯片手册可以了解到,这里不再赘述。
我们直接看主要的调用汇编函数:
_start: bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启 bl memsetup @ 设置存储控制器 bl copy_steppingstone_to_sdram @ 复制代码到SDRAM中 ldr pc, =on_sdram @ 跳到SDRAM中继续执行 on_sdram: ldr sp, =0x34000000 @ 设置堆栈 bl main halt_loop: b halt_loop
这个模块中一开始先调用关闭看门狗功能模块disable_watch_dog,程序会跳到disable_watch_dog:这个符号标志所在的入口地址执行里面的代码指令。关于这个模块简单的理解就是直接用str指令向看门狗寄存器地址0x53000000写入0,设置相关的寄存器位关闭看门狗功能。随后该模块会调整PC指针让其指向lr(即连接寄存器所保存的返回地址内容)成功返回到调用模块指令结束的位置继续下一个指令。
接着程序会执行bl memsetup跳转到设置存储管理器的模块位置执行设置步骤,我们先看看这个设置过程的代码:
memsetup: @ 设置存储控制器以便使用SDRAM等外设 mov r1, #MEM_CTL_BASE @ 存储控制器的13个寄存器的开始地址 adrl r2, mem_cfg_val @ 这13个值的起始存储地址 add r3, r1, #52 @ 13*4 = 52 1: ldr r4, [r2], #4 @ 读取设置值,并让r2加4 str r4, [r1], #4 @ 将此值写入寄存器,并让r1加4 cmp r1, r3 @ 判断是否设置完所有13个寄存器 bne 1b @ 若没有写成,继续 mov pc, lr @ 返回
首先程序会保存存储管理器的设置寄存器地址和用adrl指令将mem_cfg_val位置地址保存在r2寄存器中,mem_cfg_val所存储的设定值是一个按4字节对齐的13个寄存器的设置值。可以这么理解,这个mem_cfg_val标记位置处相当于有一个存放了13个数组元素的数组存在,而且是long型的数据,这个数组里面的值便是对应的存储管理器的设置寄存器的设定值,用以驱动SDRAM可以正常工作。
随后将用r3寄存器保存一个判断值,这个判断值的内容是MEM_CTL_BASE相对偏移地址52字节处的位置,用于后面在逐一设置13个寄存器的设定值时判断是否已经设置完毕全部的13个寄存器。
接着程序就进入一个循环体进行针对13个寄存器的逐一赋值设置,r1和r2每次设定完毕后都会将当前的指针自加4字节的偏移量代表完成当前寄存器的设置;当全部的13个寄存器都设定完成后程序便会调整PC返回。
当程序完成上述过程后就已经完成了SDRAM的初始化工作,此时SDRAM已经可以被使用,由于上述的过程原理特别是寄存器设定值需要结合芯片手册和视频教程进行理解。具体这边由于篇幅所限便不再展开。程序完成初始化工作后便开始调用copy_steppingstone_to_sdram模块将当前SRAM中的4K数据全部拷贝至SDRAM中,每次拷贝都按4字节的拷贝大小为基本单元。
这里需要注意的一点是在copy_steppingstone_to_sdram被调用之前程序一直都是在片内SRAM中运行的,当这条指令所调用的过程被完成后,此时片内SRAM中的所有程序数据都被成功转移至SDRAM中。随后程序执行ldr pc, =on_sdram调整PC指针使程序流转到on_sdram这个符号标记的地址入口处,之后程序会在SDRAM中设置函数堆栈为调用c的main函数做准备(注意:这时候的函数堆栈是设置在了SDRAM中,所以c函数此时已经可以从SDRAM中执行),堆栈sp设定在了64M大小的SDRAM的顶端。
这里需要说明一个问题:程序不是一开始上电时被拷贝至SRAM中的么?那么此时程序代码段text的起始地址应该是0,此后的程序代码部分所在的地址应该都是相对绝对地址0的地址,那么拷贝到SDRAM也就是0x30000000起始的地址后,整个程序的地址到底是更新了?还是做了什么特定的变化?
在解决这个疑虑之前,我们先看看makefile中关于链接部分的代码:
arm-linux-ld -Ttext 0x30000000 head.o leds.o -o sdram_elf
这条指令指定了程序的链接地址是0x30000000也就是SDRAM的起始地址。那么也就是说明程序的运行地址就是起始于0x30000000。可是为什么一开始程序被拷贝至SRAM中时,head.S中的代码可以被执行呢?
原因在于bl跳转指令,这个bl指令是一个位置无关代码,也就是说该段代码无论放在内存的哪个地址,都能正确运行。原因是因为代码里没有使用绝对地址,都是相对地址。这跟链接相关的概念有关,我们这里先做一点简单的解析和说明,程序都需要经过编译、汇编和链接后才可以变成可执行文件。在链接时,要对所有目标文件.o进行重定位(relocation),建立符号引用规则,同时为变量、函数等分配运行地址。这些运行地址其实是虚拟地址也就是相对地址,当程序执行时,系统必须把代码加载到链接时所指定的地址空间,以保证程序在执行过程中对变量、函数等符号的正确引用,使程序正常运行。一般来说中重定位过程由操作系统自动完成。而通常来说链接脚本中所规定的地址是虚拟地址,也就是指烧录完成后的映像文件执行时,各个不同的输出段所重定位到相应的存储地址空间,与映像文件烧写到的实际的地址无关(即映像的加载地址)。重定位的时候需要参考的地址就是相对地址。
举个栗子来看看:
我们直接先看这个SDRAM程序反汇编后所生成的文件内容:
sdram_elf: file format elf32-littlearm Disassembly of section .text: 30000000 <_start>: 30000000: eb000005 bl 3000001c <disable_watch_dog> 30000004: eb000010 bl 3000004c <memsetup> 30000008: eb000007 bl 3000002c <copy_steppingstone_to_sdram> 3000000c: e59ff090 ldr pc, [pc, #144] ; 300000a4 <mem_cfg_val+0x34> 30000010 <on_sdram>: 30000010: e3a0d30d mov sp, #872415232 ; 0x34000000 30000014: eb000033 bl 300000e8 <main> 30000018 <halt_loop>: 30000018: eafffffe b 30000018 <halt_loop> 3000001c <disable_watch_dog>: 3000001c: e3a01453 mov r1, #1392508928 ; 0x53000000 30000020: e3a02000 mov r2, #0 30000024: e5812000 str r2, [r1] 30000028: e1a0f00e mov pc, lr 3000002c <copy_steppingstone_to_sdram>: 3000002c: e3a01000 mov r1, #0 30000030: e3a02203 mov r2, #805306368 ; 0x30000000 30000034: e3a03a01 mov r3, #4096 ; 0x1000 30000038: e4914004 ldr r4, [r1], #4 3000003c: e4824004 str r4, [r2], #4 30000040: e1510003 cmp r1, r3 30000044: 1afffffb bne 30000038 <copy_steppingstone_to_sdram+0xc> 30000048: e1a0f00e mov pc, lr 3000004c <memsetup>: 3000004c: e3a01312 mov r1, #1207959552 ; 0x48000000 30000050: e28f2018 add r2, pc, #24 30000054: e1a00000 nop ; (mov r0, r0) 30000058: e2813034 add r3, r1, #52 ; 0x34 3000005c: e4924004 ldr r4, [r2], #4 30000060: e4814004 str r4, [r1], #4 30000064: e1510003 cmp r1, r3 30000068: 1afffffb bne 3000005c <memsetup+0x10> 3000006c: e1a0f00e mov pc, lr 30000070 <mem_cfg_val>: 30000070: 22011110 andcs r1, r1, #4 30000074: 00000700 andeq r0, r0, r0, lsl #14 30000078: 00000700 andeq r0, r0, r0, lsl #14 3000007c: 00000700 andeq r0, r0, r0, lsl #14 30000080: 00000700 andeq r0, r0, r0, lsl #14 30000084: 00000700 andeq r0, r0, r0, lsl #14 30000088: 00000700 andeq r0, r0, r0, lsl #14 3000008c: 00018005 andeq r8, r1, r5 30000090: 00018005 andeq r8, r1, r5 30000094: 008c07a3 addeq r0, ip, r3, lsr #15 30000098: 000000b1 strheq r0, [r0], -r1 3000009c: 00000030 andeq r0, r0, r0, lsr r0 300000a0: 00000030 andeq r0, r0, r0, lsr r0 300000a4: 30000010 andcc r0, r0, r0, lsl r0 300000a8: e1a00000 nop ; (mov r0, r0) 300000ac: e1a00000 nop ; (mov r0, r0) 300000b0 <wait>: 300000b0: e52db004 push {fp} ; (str fp, [sp, #-4]!) 300000b4: e28db000 add fp, sp, #0 300000b8: e24dd00c sub sp, sp, #12 300000bc: e50b0008 str r0, [fp, #-8] 300000c0: ea000002 b 300000d0 <wait+0x20> 300000c4: e51b3008 ldr r3, [fp, #-8] 300000c8: e2433001 sub r3, r3, #1 300000cc: e50b3008 str r3, [fp, #-8] 300000d0: e51b3008 ldr r3, [fp, #-8] 300000d4: e3530000 cmp r3, #0 300000d8: 1afffff9 bne 300000c4 <wait+0x14> 300000dc: e28bd000 add sp, fp, #0 300000e0: e8bd0800 pop {fp} 300000e4: e12fff1e bx lr 300000e8 <main>: 300000e8: e92d4800 push {fp, lr} 300000ec: e28db004 add fp, sp, #4 300000f0: e24dd008 sub sp, sp, #8 300000f4: e3a03000 mov r3, #0 300000f8: e50b3008 str r3, [fp, #-8] 300000fc: e59f3030 ldr r3, [pc, #48] ; 30000134 <main+0x4c> 30000100: e3a02b55 mov r2, #87040 ; 0x15400 30000104: e5832000 str r2, [r3] 30000108: e59f0028 ldr r0, [pc, #40] ; 30000138 <main+0x50> 3000010c: ebffffe7 bl 300000b0 <wait> 30000110: e59f3024 ldr r3, [pc, #36] ; 3000013c <main+0x54> 30000114: e3a02000 mov r2, #0 30000118: e5832000 str r2, [r3] 3000011c: e59f0014 ldr r0, [pc, #20] ; 30000138 <main+0x50> 30000120: ebffffe2 bl 300000b0 <wait> 30000124: e59f3010 ldr r3, [pc, #16] ; 3000013c <main+0x54> 30000128: e3a02e1e mov r2, #480 ; 0x1e0 3000012c: e5832000 str r2, [r3] 30000130: eafffff4 b 30000108 <main+0x20> 30000134: 56000010 undefined instruction 0x56000010 30000138: 00007530 andeq r7, r0, r0, lsr r5 3000013c: 56000014 undefined instruction 0x56000014 Disassembly of section .ARM.attributes: 00000000 <.ARM.attributes>: 0: 00002541 andeq r2, r0, r1, asr #10 4: 61656100 cmnvs r5, r0, lsl #2 8: 01006962 tsteq r0, r2, ror #18 c: 0000001b andeq r0, r0, fp, lsl r0 10: 00543405 subseq r3, r4, r5, lsl #8 14: 01080206 tsteq r8, r6, lsl #4 18: 04120109 ldreq r0, [r2], #-265 ; 0x109 1c: 01150114 tsteq r5, r4, lsl r1 20: 01180317 tsteq r8, r7, lsl r3 24: Address 0x00000024 is out of bounds. Disassembly of section .comment: 00000000 <.comment>: 0: 3a434347 bcc 10d0d24 <SDRAM_BASE-0x2ef2f2dc> 4: 74632820 strbtvc r2, [r3], #-2080 ; 0x820 8: 312d676e teqcc sp, lr, ror #14 c: 312e362e teqcc lr, lr, lsr #12 10: 2e342029 cdpcs 0, 3, cr2, cr4, cr9, {1} 14: 00332e34 eorseq r2, r3, r4, lsr lr
需要指出的是ARM架构下的程序计数器PC指针的计算方式是当前地址值 + 8字节偏移量,此时我们注意到,反汇编文件中关于程序的地址信息其实是虚拟链接地址,也就是这个程序运行时应该位于的位置。但是实际上,程序在未调用完成copy_steppingstone_to_sdram之前地址其实一直都是相对于SRAM的起始地址0x0的相对地址。因此这里的
30000000: eb000005 bl 3000001c <disable_watch_dog> 30000004: eb000010 bl 3000004c <memsetup>
其实实际上真正的地址是
@实际上运行时真正的物理地址 00000000: eb000005 bl 3000001c <disable_watch_dog> 00000004: eb000010 bl 3000004c <memsetup>
因为此刻其实真正执行的地方是片内内存SRAM,所以此刻的真正地址是这个部分。
这里我们再次重温下这个位置无关代码的基本特性:
位置无关代码在引用同一位置无关段或相对位置固定的另一位置无关段中的符号时,必须是基于PC的符号引用,即使用相对于当前PC的偏移量来实现跳转或进行常量访问。在本程序中就是使用了bl跳转指令进行相对地址的跳转,而且也是基于PC的偏移量。
基本特性是:
1.位置无关的程序跳转。使用相对跳转指令(如bl)实现程序跳转。指令中所跳转的目标地址用基于当前PC的偏移量来表示,与链接时分配给地址标号的绝对地址值(本程序中就是链接时指定的0x30000000)无关,所以在运行时可以在任何位置进行跳转,实现位置无关性。
2.位置无关的常量访问。在应用程序中,经常要读写相关寄存器以完成必要的硬件初始化。为增强程序的可读性,利用EQU伪指令对一些常量进行赋值,但在访问过程中,必须实现位置无关性。
3 使用绝对地址进行跳转(如jmp指令),一般是在不同的位置无关代码段之间跳转。也就是最好不要在位置无关代码段中使用绝对地址进行跳转。
最后,我们大致总结下关于位置无关代码段的优点:
1.简化设计,方便实现系统的快速引导。位置无关代码可以避免在引导时进行地址映射,并方便地跳转到RAM中实现快速引导
2.实现复位处理智能化。位置无关的代码可以被加载到任意地址空间运行,只是其中的运行地址都是相对地址。
3.便于调试。Bootloader的调试通常也是一个繁琐的过程,使用位置无关代码,则可以将映像文件加载到RAM中进行调试,这既能真实地反映程序从ROM中 进行系统引导的情况,又可以避免频繁烧写程序存储器。
至此,这个关于存储管理器设置驱动SDRAM的程序最重要的部分我们便算完成了学习分析过程。由于main程序还是原来GPIO中的点灯程序,所以我们便不再加以赘述。
相关文章推荐
- ARM嵌入式学习--OK6410裸板程序--2.GPIO控制LED跑马灯(从ARM汇编跳转到C语言)
- ARM嵌入式学习--OK6410裸板程序--1.GPIO控制LED
- 嵌入式ARM裸板程序——GPIO
- 基于嵌入式linux的usb摄像头的驱动及采集程序的实现
- lpc2131 arm 驱动数码管 程序
- ARM嵌入式系统网络驱动中的重要数据结构
- ARM嵌入式系统网络驱动中的重要数据结构
- ARM的嵌入式Linux移植体验之设备驱动
- [ARM作品] 2012,最给力嵌入式项目资料!!(原创+图片+程序源码)
- 嵌入式 友善之臂ARM2440烧写程序注释
- Linux嵌入式驱动初体验(六)--- LED驱动测试程序
- 嵌入式Linux之我行——PWM在ARM Linux中的原理和蜂鸣器驱动实例开发
- 基于ARM的嵌入式Linux移植真实体验(4)――设备驱动
- 嵌入式第一个手写程序(关于KEIL-ARM)
- [ARM笔记]嵌入式Linux中断处理程序架构
- ARM的嵌入式Linux移植体验之设备驱动
- 基于ARM 的嵌入式系统程序开发要点--(四)异常处理机制的设计
- 基于ARM 的嵌入式系统程序开发要点-(二)系统的初始化过程
- ARM嵌入式Linux移植体验设备驱动(续1)
- 摄像头驱动程序的开发修改和移植(针对嵌入式mini2440开发板)—开发文档