您的位置:首页 > 运维架构 > Linux

嵌入式ARM裸板程序——存储管理器驱动

2016-10-06 12:30 260 查看
本博文是根据百问网韦东山老师的嵌入式视频教程,基于jz2440进行嵌入式开发学习所记录的学习体会,如有雷同纯属巧合。

存储管理器

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中的点灯程序,所以我们便不再加以赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息