曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 5066|回复: 1
打印 上一主题 下一主题

[2410] s3c2410 MMU

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-10-29 18:16:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
作者:蔡于清
MMU,全称Memory Manage Unit, 中文名——存储器管理单元。
   许多年以前,当人们还在使用DOS或是更古老的操作系统的时候,计算机的内存还非常小,一般都是以K为单位进行计算,相应的,当时的程序规模也不大,所以内存容量虽然小,但还是可以容纳当时的程序。但随着图形界面的兴起还用用户需求的不断增大,应用程序的规模也随之膨胀起来,终于一个难题出现在程序员的面前,那就是应用程序太大以至于内存容纳不下该程序,通常解决的办法是把程序分割成许多称为覆盖块(overlay)的片段。覆盖块0首先运行,结束时他将调用另一个覆盖块。虽然覆盖块的交换是由OS完成的,但是必须先由程序员把程序先进行分割,这是一个费时费力的工作,而且相当枯燥。人们必须找到更好的办法从根本上解决这个问题。不久人们找到了一个办法,这就是虚拟存储器(virtualmemory).虚拟存储器的基本思想是程序,数据,堆栈的总的大小可以超过物理存储器的大小,操作系统把当前使用的部分保留在内存中,而把其他未被使用的部分保存在磁盘上。比如对一个16MB的程序和一个内存只有4MB的机器,OS通过选择,可以决定各个时刻将哪4M的内容保留在内存中,并在需要时在内存和磁盘间交换程序片段,这样就可以把这个16M的程序运行在一个只具有4M内存机器上了。而这个16M的程序在运行前不必由程序员进行分割。
   任何时候,计算机上都存在一个程序能够产生的地址集合,我们称之为地址范围。这个范围的大小由CPU的位数决定,例如一个32位的CPU,它的地址范围是0~0xFFFFFFFF (4G),而对于一个64位的CPU,它的地址范围为0~0xFFFFFFFFFFFFFFFF(64T).这个范围就是我们的程序能够产生的地址范围,我们把这个地址范围称为虚拟地址空间,该空间中的某一个地址我们称之为虚拟地址。与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址,大多数时候我们的系统所具备的物理地址空间只是虚拟地址空间的一个子集,这里举一个最简单的例子直观地说明这两者,对于一台内存为256MB的32bitx86主机来说,它的虚拟地址空间范围是0~0xFFFFFFFF(4G),而物理地址空间范围是0x000000000~0x0FFFFFFF(256MB)。
   在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写。而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到内存管理单元——MMU(主角终于出现了:])。他由一个或一组芯片组成,一般存在与协处理器中,其功能是把虚拟地址映射为物理地址。
    大多数使用虚拟存储器的系统都使用一种称为分页(paging)。虚拟地址空间划分成称为页(page)的单位,而相应的物理地址空间也被进行划分,单位是页框(frame).页和页框的大小必须相同。接下来配合图片我以一个例子说明页与页框之间在MMU的调度下是如何进行映射的

在这个例子中我们有一台可以生成16位地址的机器,它的虚拟地址范围从0x0000~0xFFFF(64K),而这台机器只有32K的物理地址,因此他可以运行64K的程序,但该程序不能一次性调入内存运行。这台机器必须有一个达到可以存放64K程序的外部存储器(例如磁盘或是FLASH),以保证程序片段在需要时可以被调用。在这个例子中,页的大小为4K,页框大小与页相同(这点是必须保证的,内存和外围存储器之间的传输总是以页为单位的),对应64K的虚拟地址和32K的物理存储器,他们分别包含了16个页和8个页框。
    我们先根据上图解释一下分页后要用到的几个术语,在上面我们已经接触了页和页框,上图中绿色部分是物理空间,其中每一格表示一个物理页框。橘黄色部分是虚拟空间,每一格表示一个页,它由两部分组成,分别是Frame Index(页框索引)和位p(present 存在位),FrameIndex的意义很明显,它指出本页是往哪个物理页框进行映射的,位p的意义则是指出本页的映射是否有效,如上图,当某个页并没有被映射时(或称“映射无效”,Frame Index部分为X),该位为0,映射有效则该位为1。
我们执行下面这些指令(本例子的指令不针对任何特定机型,都是伪指令)
例1:
MOVE REG,0 //将0号地址的值传递进寄存器REG.
虚拟地址0将被送往MMU,MMU看到该虚地址落在页0范围内(页0范围是0到4095),从上图我们看到页0所对应(映射)的页框为2(页框2的地址范围是8192到12287),因此MMU将该虚拟地址转化为物理地址8192,并把地址8192送到地址总线上。内存对MMU的映射一无所知,它只看到一个对地址8192的读请求并执行它。MMU从而把0到4096的虚拟地址映射到8192到12287的物理地址。
例2:
MOVE REG,8192
被转换为
MOVE REG,24576
因为虚拟地址8192在页2中,而页2被映射到页框6(物理地址从24576到28671)
例3:
MOVE REG,20500
被转换为
MOVE REG,12308
虚拟地址20500在虚页5(虚拟地址范围是20480到24575)距开头20个字节处,虚页5映射到页框3(页框3的地址范围是 12288到16383),于是被映射到物理地址12288+20=12308。   通过适当的设置MMU,可以把16个虚页隐射到8个页框中的任何一个,但是这个方法并没有有效的解决虚拟地址空间比物理地址空间大的问题。从上图中我们可以看到,我们只有8个页框(物理地址),但我们有16个页(虚拟地址),所以我们只能把16个页中的8个进行有效的映射。我们看看例4会发生什么情况
MOV REG,32780
   虚拟地址32780落在页8的范围内,从上图总我们看到页8没有被有效的进行映射(该页被打上X),这是又会发生什么?MMU注意到这个页没有被映射,于是通知CPU发生一个缺页故障(pagefault).这种情况下操作系统必须处理这个页故障,它必须从8个物理页框中找到1个当前很少被使用的页框并把该页框的内容写入外围存储器(这个动作被称为pagecopy),随后把需要引用的页(例4中是页8)映射到刚才释放的页框中(这个动作称为修改映射关系),然后从新执行产生故障的指令(MOVREG,32780)。假设操作系统决定释放页框1,那么它将把虚页8装入物理地址的4-8K,并做两处修改:首先把标记虚页1未被映射(原来虚页1是被影射到页框1的),以使以后任何对虚拟地址4K到8K的访问都引起页故障而使操作系统做出适当的动作(这个动作正是我们现在在讨论的),其次他把虚页8对应的页框号由X变为1,因此重新执行MOV REG,32780时,MMU将把32780映射为4108。
我们大致了解了MMU在我们的机器中扮演了什么角色以及它基本的工作内容是什么,下面我们将举例子说明它究竟是如何工作的(注意,本例中的MMU并无针对某种特定的机型,它是所有MMU工作的一个抽象)。
    首先明确一点,MMU的主要工作只有一个,就是把虚拟地址映射到物理地址。
    我们已经知道,大多数使用虚拟存储器的系统都使用一种称为分页(paging)的技术,就象我们刚才所举的例子,虚拟地址空间被分成大小相同的一组页,每个页有一个用来标示它的页号(这个页号一般是它在该组中的索引,这点和C/C++中的数组相似)。在上面的例子中0~4K的页号为0,4~8K的页号为1,8~12K的页号为2,以此类推。而虚拟地址(注意:是一个确定的地址,不是一个空间)被MMU分为2个部分,第一部分是页号索引(pageIndex),第二部分则是相对该页首地址的偏移量(offset)。我们还是以刚才那个16位机器结合下图进行一个实例说明,该实例中,虚拟地址8196被送进MMU,MMU把它映射成物理地址。16位的CPU总共能产生的地址范围是0~64K,按每页4K的大小计算,该空间必须被分成16个页。而我们的虚拟地址第一部分所能够表达的范围也必须等于16(这样才能索引到该页组中的每一个页),也就是说这个部分至少需要4个bit。一个页的大小是4K(4096),也就是说偏移部分必须使用12个bit来表示(2^12=4096,这样才能访问到一个页中的所有地址),8196的二进制码如下图所示:

该地址的页号索引为0010(二进制码),既索引的页为页2,第二部分为000000000100(二进制),偏移量为4。页2中的页框号为6(页2映射在页框6,见上图),我们看到页框6的物理地址是24~28K。于是MMU计算出虚拟地址8196应该被映射成物理地址24580(页框首地址+偏移量= 24576+4=24580)。同样的,若我们对虚拟地址1026进行读取,1026的二进制码为0000010000000010,pageindex="0000"=0,offset=010000000010=1026。页号为0,该页映射的页框号为2,页框2的物理地址范围是8192~12287,故MMU将虚拟地址1026映射为物理地址9218(页框首地址+偏移量=8192+1026=9218)
    以上就是MMU的工作过程。
下面我们针对s3c2410的MMU(注1)进行讲解。
S3c2410总共有4种内存映射方式,分别是:
Fault (无映射)
Coarse Page (粗表)
Section (段)
Fine Page (细表)
    我们以Section(段)进行说明。
   ARM920T是一个32bit的CPU,它的虚拟地址空间为2^32=4G。而在Section模式,这4G的虚拟空间被分成一个一个称为段(Section)的单位(与我们上面讲的页在本质上其实是一致的),每个段的长度是1M(而我们之前所使用的页的长度是4K)。4G的虚拟内存总共可以被分成4096个段(1M*4096=4G),因此我们必须用4096个描述符来对这组段进行描述,每个描述符占用4个Byte,故这组描述符的大小为16KB(4K*4096),这4096个描述符构为一个表格,我们称其为Tralaton Table.


上图是描述符的结构
Section base address:段基地址(相当于页框号首地址)
AP: 访问控制位Access Permission
Domain: 访问控制寄存器的索引。Domain与AP配合使用,对访问权限进行检查
C:当C被置1时为write-through (WT)模式 (write_through 与 write-back 概念介绍:http://www.groad.net/bbs/read.php?tid-1316.html)
B: 当B被置1时为write-back (WB)模式
(C,B两个位在同一时刻只能有一个被置1)
    下面是s3c2410内存映射后的一个示意图:

我的s3c2410上配置的SDRSAM大小为64M,该SDRAM的物理地址范围是0x3000 0000~0x33FF FFFF(属于Bank 6),由于1个Section的大小是1M,所以该物理空间可以被分成64个物理段(页框).
   在Section模式下,送进MMU的虚拟地址(注1)被分为两部分(这点和我们上面举的例子是一样的),这两部分为 DescriptorIndex(相当于上面例子的Page Index)和 Offset,descriptindex长度为12bit(2^12=4096,从这个关系式你能看出什么?:)),Offset长度为20bit(2^20=1M,你又能看出什么?:)).观察一下一个描述符(Descriptor)中的SectionBase Address部分,它长度为12bit,里面的值是该虚拟段(页)映射成的物理段(页框)的物理地址前12bit,由于每一个物理段的长度都是1M,所以物理段首地址的后20bit总是为0x00000(每个Section都是以1M对齐),确定一个物理地址的方法是 物理页框基地址+虚拟地址中的偏移部分=Section BaseAddress<<20+Offset ,呵呵,可能你有点糊涂了,还是举一个实际例子说明吧。假设现在执行指令
MOV REG, 0x30000012
    虚拟地址的二进制码为00110000 00000000 00000000 00010010
   前12位是Descriptor Index= 00110000 0000=768,故在TranslationTable里面找到第768号描述符,该描述的Section BaseAddress="0x0300",也就是说描述符所描述的虚拟段(页)所映射的物理段(页框)的首地址为0x30000000(物理段(页框)的基地址=Section Base Address左移20bit=0x0300<<20=0x30000000),而Offset=000000 0000000000010010=0x12,故虚拟地址0x30000012映射成的物理地址=0x3000 0000+0x12=0x30000012(物理页框基地址+虚拟地址中的偏移)。你可能会问怎么这个虚拟地址和映射后的物理地址一样?这是由我们定义的映射规则所决定的。在这个例子中我们定义的映射规则是把虚拟地址映射成和他相等的物理地址。我们这样书写映射关系的代码:代码
void mem_mapping_linear(void)   
{   
     unsigned long descriptor_index, section_base, sdram_base, sdram_size;   
     sdram_base=0x30000000;   
     sdram_size=0x 4000000;   
    for (section _base= sdram_base,descriptor_index = section _base>>20;   
          section _base < sdram_base+ sdram_size;   
          descriptor_index+=1;section _base +=0x100000)   
     {   
          *(mmu_tlb_base + (descriptor_index)) = (section _base>>20) | MMU_OTHER_SECDESC;   
     }   
}  
    上面的这段段代码把虚拟空间0x3000 0000~0x33FF FFFF映射到物理空间0x3000 0000~0x33FFFFFF,由于虚拟空间与物理空间空间相吻合,所以虚拟地址与他们各自对应的物理地址在值上是一致的。当初始完TranslationTable之后,记得要把Translation Table的首地址(第0号描述符的地址)加载进协处理器CP15的ControlRegister2(2号控制寄存器)中,该控制寄存器的名称叫做Translation table base (TTB) register。
    以上讨论的是descriptor中的Section Base Address以及虚拟地址和物理地址的映射关系,然而MMU还有一个重要的功能,那就是访问控制机制(Access Permission )。
   简单说访问控制机制就是CPU通过某种方法判断当前程序对内存的访问是否合法(是否有权限对该内存进行访问),如果当前的程序并没有权限对即将访问的内存区域进行操作,则CPU将引发一个异常,s3c2410称该异常为Permissionfault,x86架构则把这种异常称之为通用保护异常(General Protection),什么情况会引起Permissionfault呢?比如处于User级别的程序要对一个System级别的内存区域进行写操作,这种操作是越权的,应该引起一个Permissionfault,搞过x86架构的朋友应该听过保护模式(ProtectionMode),保护模式就是基于这种思想进行工作的,于是我们也可以这么说:s3c2410的访问控制机制其实就是一种保护机制。那s3c2410的访问控制机制到底是由什么元素去参与完成的呢?它们间是怎么协调工作的呢?这些元素总共有:
协处理器CP15中Control Register3:DOMAIN ACCESS CONTROL REGISTER
段描述符中的AP位和Domain位
协处理器CP15中Control Register1(控制寄存器1)中的S bit和R bit
协处理器CP15中Control Register5(控制寄存器5)
协处理器CP15中Control Register6(控制寄存器6)
    DOMAIN ACCESS CONTROL REGISTER 是访问控制寄存器,该寄存器有效位为32,被分成16个区域,每个区域由两个位组成,他们说明了当前内存的访问权限检查的级别,如下图所示:



每区域可以填写的值有4个,分别为00,01,10,11(二进制),他们的意义如下所示:

00:当前级别下,该内存区域不允许被访问,任何的访问都会引起一个domain fault
01:当前级别下,该内存区域的访问必须配合该内存区域的段描述符中AP位进行权检查
10:保留状态(我们最好不要填写该值,以免引起不能确定的问题)
11:当前级别下,对该内存区域的访问都不进行权限检查。
   我们再来看看discriptor中的Domain区域,该区域总共有4个bit,里面的值是对DOMAIN ACCESS CONTROLREGISTER中16个区域的索引.而AP位配合S bit和A bit对当前描述符描述的内存区域被访问权限的说明,他们的配合关系如下图所示:

AP位也是有四个值,我结合实例对其进行说明.
在下面的例子中,我们的DOMAIN ACCESS CONTROL REGISTER都被初始化成0xFFFF BDCF,如下图所示:

例1:Discriptor 中的domain=4,AP=10(这种情况下S bit ,A bit 被忽略)
假设现在我要对该描述符描述的内存区域进行访问:
由于domain=4,而DOMAIN ACCESS CONTROL REGISTER中field 4的值是01,系统会对该访问进行访问权限的检查。
假设当前CPU处于Supervisor模式下,则程序可以对该描述符描述的内存区域进行读写操作。
假设当前CPU处于User模式下,则程序可以对该描述符描述的内存进行读访问,若对其进行写操作则引起一个permission fault.例2:Discriptor 中的domain=0,AP=10(这种情况下S bit ,A bit 被忽略)
domain=0,而DOMAIN ACCESS CONTROL REGISTER中field 0的值是11,系统对任何内存区域的访问都不进行访问权限的检查。
由于统对任何内存区域的访问都不进行访问权限的检查,所以无论CPU处于合种模式下(Supervisor模式或是User模式),程序对该描述符描述的内存都可以顺利地进行读写操作
例3:Discriptor 中的domain=4,AP=11(这种情况下S bit ,A bit 被忽略)
由于domain=4,而DOMAIN ACCESS CONTROL REGISTER中field 4的值是01,系统会对该访问进行访问权限的检查。
由于AP=11,所以无论CPU处于合种模式下(Supervisor模式或是User模式),程序对该描述符描述的内存都可以顺利地进行读写操作
例4:Discriptor 中的domain=4,AP=00, S bit="0",A bit="0"
由于domain=4,而DOMAIN ACCESS CONTROL REGISTER中field 4的值是01,系统会对该访问进行访问权限的检查。
由于AP=00,S bit="0",A bit="0",所以无论CPU处于合种模式下(Supervisor模式或是User模式),程序对该描述符描述的内存都只能进行读操作,否则引起permission fault.
通过以上4个例子我们得出两个结论:
1.对某个内存区域的访问是否需要进行权限检查是由该内存区域的描述符中的Domain域决定的。
2.某个内存区域的访问权限是由该内存区域的描述符中的AP位和协处理器CP15中Control Register1(控制寄存器1)中的S bit和R bit所决定的。
关于访问控制机制我们就讲到这里.
注1:对于s3c2410来说,送进MMU的地址准确讲是一个Modify Visual Address(MVA),这个地址是Virtual Address的一个变换,我将在以后谈论到进程切换的时候中向大家介绍MVA

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-10-29 18:29:49 | 只看该作者

想关代码

////////////////////////////////////head.S//////////////////////////////////////////////////////////////////////
@*************************************************************************
@ Filehead.S
@ 功能:设置SDRAM,将第二部分代码复制到SDRAM,设置页表,启动MMU
@       然后跳到SDRAM继续执行
@*************************************************************************     

.text
.global _start
_start:
    ldr sp, =4096                       @ 设置栈指针,以下都是C函数,调用前需要设好栈
    bl disable_watch_dog               @ 关闭WATCHDOG,否则CPU会不断重启
    bl memsetup                        @ 设置存储控制器以使用SDRAM
    bl copy_2th_to_sdram               @ 将第二部分代码复制到SDRAM
    bl create_page_table               @ 设置页表
    bl mmu_init                        @ 启动MMU
    ldr sp, =0xB4000000                 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址)
    ldr pc, =0xB0004000                 @ 跳到SDRAM中继续执行第二部分代码
halt_loop:
    b   halt_loop

//////////////////////////////////init.c//////////////////////////////////////////////
/*
* init.c: 进行一些初始化,在Steppingstone中运行
* 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址
*/

/* WATCHDOG寄存器 */
#define WTCON           (*(volatile unsigned long *)0x53000000)
/* 存储控制器的寄存器起始地址 */
#define MEM_CTL_BASE    0x48000000


/*
* 关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
    WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}

/*
* 设置存储控制器以使用SDRAM
*/
void memsetup(void)
{
    /* SDRAM 13个寄存器的值 */
    unsigned long const    mem_cfg_val[]={ 0x22011110,     //BWSCON
                                            0x00000700,     //BANKCON0
                                            0x00000700,     //BANKCON1
                                            0x00000700,     //BANKCON2
                                            0x00000700,     //BANKCON3
                                            0x00000700,     //BANKCON4
                                            0x00000700,     //BANKCON5
                                            0x00018005,     //BANKCON6
                                            0x00018005,     //BANKCON7
                                            0x008C07A3,     //REFRESH
                                            0x000000B1,     //BANKSIZE
                                            0x00000030,     //MRSRB6
                                            0x00000030,     //MRSRB7
                                    };
    int     i = 0;
    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
    for(; i < 13; i++)
        p[i] = mem_cfg_val[i];
}

/*
*将第二部分代码复制到SDRAM,32位CPU的虚拟地址空间达到4GB。一级页表中使用4096个描述服来表示4GB空间,则每个描述符对应1MB的虚拟地址,每个描述符占用4字节,所以一级页表占16KB。所以SDRAM的16k来存放一级页表,所以剩下的内存开始物理地址为0x30004000.
*/
void copy_2th_to_sdram(void)
{
    unsigned int *pdwSrc = (unsigned int *)2048;
    unsigned int *pdwDest = (unsigned int *)0x30004000;
  
    while (pdwSrc < (unsigned int *)4096)
    {
        *pdwDest = *pdwSrc;
        pdwDest++;
        pdwSrc++;
    }
}

/*
* 设置页表,这个函数就是创建页表的过程。页表里存放是虚拟地址对应的物理地址,CPU处理
* 一个虚拟地址,是通过MMU来进行转换,也就是在相应的页表里找到对应的物理地址,我们就是需要创建 * 这个页表,具体参看S3C2410 MMU这篇文章。很详细!!!
*/
void create_page_table(void)
{

/*
* 用于段描述符的一些宏定义
*/
#define MMU_FULL_ACCESS     (3 << 10)   /* 访问权限 */
#define MMU_DOMAIN          (0 << 5)    /* 属于哪个域 */
#define MMU_SPECIAL         (1 << 4)    /* 必须是1 */
#define MMU_CACHEABLE       (1 << 3)    /* cacheable */
#define MMU_BUFFERABLE      (1 << 2)    /* bufferable */
#define MMU_SECTION         (2)         /* 表示这是段描述符 */
#define MMU_SECDESC         (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \\
                             MMU_SECTION)
#define MMU_SECDESC_WB      (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | \\
                             MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION)
#define MMU_SECTION_SIZE    0x00100000

    unsigned long virtuladdr, physicaladdr;
    unsigned long *mmu_tlb_base = (unsigned long *)0x30000000;
  
    /*
     * Steppingstone的起始物理地址为0,第一部分程序的起始运行地址也是0,
     * 为了在开启MMU后仍能运行第一部分的程序,
     * 将0~1M的虚拟地址映射到同样的物理地址
     */
    virtuladdr = 0;
    physicaladdr = 0;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \\
                                            MMU_SECDESC_WB;

//从SDRAM的开始存放页表,将虚拟地址0对应的物理地址0的页表创建好,当我们以后对这个虚拟地址操作的时候,MMU可以为我们在这个页表也找到相应的物理地址。
    /*
     * 0x56000000是GPIO寄存器的起始物理地址,
     * GPBCON和GPBDAT这两个寄存器的物理地址0x56000010、0x56000014,
     * 为了在第二部分程序中能以地址0xA0000010、0xA0000014来操作GPBCON、GPBDAT,
     * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间
     */
    virtuladdr = 0xA0000000;
    physicaladdr = 0x56000000;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \\
                                            MMU_SECDESC;

    /*
     * SDRAM的物理地址范围是0x30000000~0x33FFFFFF,
     * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,
     * 总共64M,涉及64个段描述符
    * 虚拟地址的位[31:20]用于索引一级页表,找到它所对应的描述符,对应于virtuladdr >>20
* 段描述符中位[31:20]中保存段的物理地址,对应于physicaladdr & 0xfff00000
     */
    virtuladdr = 0xB0000000;
    physicaladdr = 0x30000000;
    while (virtuladdr < 0xB4000000)
    {创建好了页表以后
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \\
                                                MMU_SECDESC_WB;
        virtuladdr += 0x100000;
        physicaladdr += 0x100000;
    }
}

/*
* 启动MMU,创建好了页表以后。还需要把页表地址告诉CPU,并在开启MMU之前做好一些准备工作,
* 比如使无效ICache,DCache,设置访问控制寄存器等
*/
void mmu_init(void)
{
    unsigned long ttb = 0x30000000;

__asm__(
    "mov    r0, #0\\n"
    "mcr    p15, 0, r0, c7, c7, 0\\n"    /* 使无效ICaches和DCaches */
  
    "mcr    p15, 0, r0, c7, c10, 4\\n"   /* drain write buffer on v4 */
    "mcr    p15, 0, r0, c8, c7, 0\\n"    /* 使无效指令、数据TLB */
  
    "mov    r4, %0\\n"                   /* r4 = 页表基址 */
    "mcr    p15, 0, r4, c2, c0, 0\\n"    /* 设置页表基址寄存器 */
  
    "mvn    r0, #0\\n"                 
    "mcr    p15, 0, r0, c3, c0, 0\\n"    /* 域访问控制寄存器设为0xFFFFFFFF,
                                         * 不进行权限检查
                                         */  
    /*
     * 对于控制寄存器,先读出其值,在这基础上修改感兴趣的位,
     * 然后再写入
     */
    "mrc    p15, 0, r0, c1, c0, 0\\n"    /* 读出控制寄存器的值 */
  
    /* 控制寄存器的低16位含义为:.RVI ..RS B... .CAM
     * R : 表示换出Cache中的条目时使用的算法,
     *     0 = Random replacement;1 = Round robin replacement
     * V : 表示异常向量表所在的位置,
     *     0 = Low addresses = 0x00000000;1 = High addresses = 0xFFFF0000
     * I : 0 = 关闭ICaches;1 = 开启ICaches
     * R、S : 用来与页表中的描述符一起确定内存的访问权限
     * B : 0 = CPU为小字节序;1 = CPU为大字节序
     * C : 0 = 关闭DCaches;1 = 开启DCaches
     * A : 0 = 数据访问时不进行地址对齐检查;1 = 数据访问时进行地址对齐检查
     * M : 0 = 关闭MMU;1 = 开启MMU
     */
  
    /*
     * 先清除不需要的位,往下若需要则重新设置它们  
     */
                                        /* .RVI ..RS B... .CAM */
    "bic    r0, r0, #0x3000\\n"          /* ..11 .... .... .... 清除V、I位 */
    "bic    r0, r0, #0x0300\\n"          /* .... ..11 .... .... 清除R、S位 */
    "bic    r0, r0, #0x0087\\n"          /* .... .... 1... .111 清除B/C/A/M */

    /*
     * 设置需要的位
     */
    "orr    r0, r0, #0x0002\\n"          /* .... .... .... ..1. 开启对齐检查 */
    "orr    r0, r0, #0x0004\\n"          /* .... .... .... .1.. 开启DCaches */
    "orr    r0, r0, #0x1000\\n"          /* ...1 .... .... .... 开启ICaches */
    "orr    r0, r0, #0x0001\\n"          /* .... .... .... ...1 使能MMU */
  
    "mcr    p15, 0, r0, c1, c0, 0\\n"    /* 将修改的值写入控制寄存器 */
    : /* 无输出 */
    : "r" (ttb) );
}

/////////////////////////////mmu.lds/////////////////////////////////
SECTIONS
{
        first 0x00000000 : {head.o init.o} #first
        second 0xB0004000 : AT(2048) {leds.o} #指定这个段在编译出来的映像文件中的地址-加载地址。如果不使用这个选项,并且不指定 0xB0004000,则加载地址等于运行地址,否则指定了 0xB0004000 是不相同的。通过这个选项。可以控制各段分别保存输出文件中不同的位置。这里指定leds.o保存在映像文件的2048这个起始地址,而 0xB0000000这个是段重定位地址,也称为运行地址,它是个虚拟地址。
}

///////////////////////////////////Makefile/////////////////////////////
objs := head.o init.o leds.o

mmu.bin : $(objs)
    arm-softfloat-linux-gnu-ld -Tmmu.lds -o mmu_elf $^
    arm-softfloat-linux-gnu-objcopy -O binary -S mmu_elf $@
    arm-softfloat-linux-gnu-objdump -D -m arm mmu_elf > mmu.dis
  
%.o:%.c
    arm-softfloat-linux-gnu-gcc -Wall -O2 -c -o $@ $<

%.o:%.S
    arm-softfloat-linux-gnu-gcc -Wall -O2 -c -o $@ $<

clean:
    rm -f mmu.bin mmu_elf mmu.dis *.o      

////////////////////////////leds.c///////////////////////////////////
/*
* leds.c: 循环点亮4个LED
* 属于第二部分程序,此时MMU已开启,使用虚拟地址
*/

#define GPFCON      (*(volatile unsigned long *)0xA0000050)     // 物理地址0x56000050
#define GPFDAT      (*(volatile unsigned long *)0xA0000054)     // 物理地址0x56000054

#define GPF4_out    (1<<(4*2))
#define GPF5_out    (1<<(5*2))
#define GPF6_out    (1<<(6*2))
#define GPF7_out    (1<<(7*2))

/*
* wait函数加上“static inline”是有原因的,
* 这样可以使得编译leds.c时,wait嵌入main中,编译结果中只有main一个函数。
* 于是在连接时,main函数的地址就是由连接文件指定的运行时装载地址。
* 而连接文件mmu.lds中,指定了leds.o的运行时装载地址为0xB4004000,
* 这样,head.S中的“ldr pc, =0xB4004000”就是跳去执行main函数。
*/
static inline void wait(unsigned long dly)
{
    for(; dly > 0; dly--);
}

int main(void)
{
    unsigned long i = 0;
  
    // 将LED1-4对应的GPF4/5/6/7四个引脚设为输出
    GPBCON = GPF4_out|GPF5_out|GPF6_out|GPF7_out;     

    while(1){
        wait(300000);
        GPBDAT = (~(i<<4));     // 根据i的值,点亮LED1-4
        if(++i == 16)
            i = 0;
    }

    return 0;
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 12:47 , Processed in 0.103336 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表