利用MMU通过访问虚拟内存来实现流水灯

实验目的:

              利用MMU通过访问虚拟内存来实现流水灯

预备知识:

           (1):熟悉协处理器的作用,CP15中的十六个寄存器,以及寄存器中的位格式和含义,相关的指令(MRC和MCR);

                         (关于协处理器的指令,在《arm体系结构与编程》这本书上说的比较好)  

           (2):  熟悉嵌入式汇编语法  (这方面的知识,在我上面的博客里已经有过介绍,这里不再赘述);

                     

实验原理:

             内存管理单元(Memory Management Unit)简称MMU。它的功能是:(1)将虚拟内存转化为物理内存;(2)设置访问权限;(3)设置Cache和Write Buffer。 若没有MMU,即程序只能直接使用物理内存,若几个应用程序同时运行,这样会导致内存泄漏,造成程序的混乱,同时有了MMU,对于CPU,其发出的虚拟地址将 会变大(对于arm9,CPU可以发出4G的虚拟内存地址)  。

  在基于ARM的嵌入式应用系统中,存储系统通常是通过系统控制协处理器CP15完成的,在具体的各种存储管理机制中,可能还会用到其他的一些技术,如在MMU中除了CP15,还有使用页表和高速缓存(很重要,注意理解)等


 在MMU中的地址变换中,有四种地址映射方法:段(section)、大页(large page)、小页(small page)、极小页(tiny page)。

虚拟地址到物理地址的索引,涉及到两种索引:一级页表索引、二级页表索引。

 其中段用的一级页表索引,大页、小页和极小页用的的二级索引。

虚拟地址到物理地址的大概转换过程如下:

                   a. 根据给定的虚拟地址找到一级页表中的条目;

           b.如果此条目是段描述符,则返回物理地址,转换结束;

   c,否则如果此条目是二级页表描述符,继续利用虚拟地址在此二级页表中找到下一个条目;

   d,如果这第二个条目是页描述符,则返回物理地址,转换结束;

                   e,其他情况出错。

 补充:这里地址转换图我不在此补充,希望大家自己参考arm体系结构与编程,上面的图解已经很详细了。

 注意:条目中的每个位的含义:

                   例如 在一级页表中描述符的格式(段的条目)

                                                      31 12 11 10 9 8 5 4  3   2   1    0 

                                                      [section base adress| 00  00 0 0 0         ]   [  AP  ]    1     [ DOMAIN ]     1       C      B      1    0           

                                                       段的物理地址的基址                                       AP 规定怎样进行检查     domain规定对某个内存是否检查            C规定是否启动cache     B规定是否启动Write Buffer ,最后两位(10)

                       代表的是段映射。  在一级页表中,最后两位01代表的是粗页条目,   11代表的是细页条目。


   例如 在一级页表中描述符的格式(粗页的条目)

                                                      31 12 11 10  9 8 5      4   3    2     1    0 

                                                      [Corse page table base address           ]   [  AP  ]     1      [ DOMAIN ]     1       C      B      0     1           

                                                       二级页表的虚拟页表的基址                  


  例如 在一级页表中描述符的格式(细页的条目)

                                                      31 12 11 10  9 8 5      4   3    2     1    0 

                                                      [Fine page table base address           ]   [  AP  ]     1      [ DOMAIN ]     1       C      B      0     1           

                                                       二级虚拟页表的基址                  

注意:在地址转换工程中,如有二级页表转换(粗页和细页中的条目是跟一级页表是差不多的)。

通过MMU来实现虚拟内存的访问一般步骤:

(1)建表格;(2)将表格的基址(TTB)告诉MMU (即将TTP保存到CP15的寄存器C2中) (2)启动MMU


实验代码:第一部分代码:head.S 和 init.c主要是完成一些初始化化工作,第二部分代码:leds.c 是通过虚拟内存的访问,来实现点亮一个灯

head.S:

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

.text
.global _start
_start:
    ldr sp, =4096                       @ 设置栈指针,,以下都是C函数,调用前需要设好栈,将sp指向stepping的顶部,
    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中继续执行第二部分代码
    @ ldr pc, =main
halt_loop:
    b   halt_loop

@问题一:这里sp为什么是0xB4000000
@分析:SDRAM的物理内存地址范围是0x30000000~0x33ffffff,要将虚拟地址0xB0000000~0xBffffff映射到物理地址0x30000000~0x33ffffff
@CPU在执行第二块代码的时候,pc就在SDRAM内了,故将sp重设为指向SDRAM的顶部

@问题二:ldr pc, =0xB0004000  ,这里为什么是0xB0004000  
@分析:本程序是使用段映射,只用到一级页表。32位CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间
@(每个描述符对应1MB的虚拟地址空间)每个描述符占用4个字节,所以一级页表占16KB。本例使用SDRAM的开始16KB来存放一级页表。
@所以剩下的内存开始物理地址为0x30004000,然而在支持第二部分代码(即leds.c)的时候MMU已经启动,故应该使用虚拟内存.由于
@虚拟地址0xB0000000~0xBffffff映射到物理地址0x30000000~0x33ffffff,虚拟内存0xB0004000对应的物理内存是0x30004000.所以
@PC这个时候应该指向0xB0004000

 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
 *  将steppingstone中2048到4096空间中(leds.c就存在这一部分中)
 *从mmu.lds中的AT(2048)中可以知道,第二部分代码leds.c从steppingstone处开始存放
 *应该运行的位置从0xB0004000开始
 */
void copy_2th_to_sdram(void)        //将steppingstone的2048~4096(不包括4096)的代码复制到SDRAM
{
//因为前64MB的内存被一级页表占用了
//段的一个条目是1M 对于cpu的4GB寻址范围,需要4GB/1M=4096个一级页表条目。 一个条目占4B 所以4096*4B=16KB。
//所以剩下的内存开始地址是0x30004000
    unsigned int *pdwSrc  = (unsigned int *)2048;
    unsigned int *pdwDest = (unsigned int *)0x30004000;
    
    while (pdwSrc < (unsigned int *)4096)       //
    {
        *pdwDest = *pdwSrc;
        pdwDest++;
        pdwSrc++;
    }
}

/*
 * 设置页表
 */
void create_page_table(void)
{

/* 
 * 用于段描述符的一些宏定义
 */ 
 
 /*设置  例如 在一级页表中描述符的格式(段的条目)
  31			12	11	10   9   8		5 	4	 3	  2	   1   0
[section base adress     ]      [    AP  ]   1   [    DOMAIN    ]       1        C        B        1   0
段的物理地址的基址               AP 规定怎样进行检查
 domain规定对某个内存是否检查            C规定是否启动cache     B规定是否启动Write Buffer ,最后两位(10)
 */
#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后仍能运行第一部分的程序,(同时也是保证在启动MMU之前和启动MMU瞬间,虚拟地址连续并一致)
     * 将0~1M的虚拟地址映射到同样的物理地址
     */
     
     //建立第一个段条目:将虚拟地址映射到物理地址,这里虚拟和物理地址都是0开始,一样
//    virtuladdr = 0;
    physicaladdr = 0;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                            MMU_SECDESC_WB;

    /*
     * 0x56000000是GPIO寄存器的起始物理地址,
     * GPFCON和GPFDAT这两个寄存器的物理地址0x56000050、0x56000054,
     * 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT,
     * 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间
     */
     
     //建立第二个段条目:将虚拟地址0xA0000000映射到物理地址0x56000000
//不设置cache和write Buffer
    virtuladdr = 0xA0000000;
    physicaladdr = 0x56000000;
    *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                            MMU_SECDESC;

    /*
     * SDRAM的物理地址范围是0x30000000~0x33FFFFFF,
     * 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,
     * 总共64M,涉及64个段描述符
     */
     
    / /建立若干个段条目:将虚拟地址0xA0000000映射到物理地址0x56000000
//设置cache和write Buffer
    virtuladdr = 0xB0000000;
    physicaladdr = 0x30000000;
    while (virtuladdr < 0xB4000000)
    {
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
                                                MMU_SECDESC_WB;
        virtuladdr += 0x100000;
        physicaladdr += 0x100000;
    }
}

/*
 * 启动MMU
 */
void mmu_init(void)
{
    unsigned long ttb = 0x30000000;

// MRC和MCR中的讲解:在《ARM休系架构与编程》中
// 在我的博客中,嵌入式汇编已经讲解了

/*


*/
__asm__(
    "mov    r0, #0\n"
    "mcr    p15, 0, r0, c7, c7, 0\n"    /* 使无效ICaches和DCaches */
    /*在CP15中,第一个常数都是0,最后一个协处理寄存器和最后一个参数(option2)的组合决定了该命令的具体功能,
      不过默认情况下是C0,0*/
    
    "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 = 页表基址 */
    /*这里的%0是引用第0个“占位符”  “占位符”是输入变量和输出变量,其是这些变量时第几个由他们在输入和输出变量中的位置决定*/
    /*入式汇编程序规定把输出和输入寄存器按统一顺序编号,顺序是从输出寄存器序列从左到右从上到下以“%0”开始,分别记为%0、%1···%9
    /*从下面两个冒号开始,由于输出变量没有,按照从输出寄存器序列开始从左到右从上到下的原则,TTB是第0个参数*/
    /*所以这里%0代表的就是0x30000000*/
    "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) );
}

leds.c:

#define GPFCON	(*(volatile unsigned long*)0xA0000050)
#define GPFDAT  (*(volatile unsigned long*)0xA0000054)

/*

上面是要使用虚拟地址的
在执行led.c时,就启动MMU了,使用了虚拟地址,所以在对GPFCON和GPFDAT访问的时候,CPU要通过MMU。
*/


/*
 * delay函数加上“static inline”是有原因的,
 * 这样可以使得编译leds.c时,wait嵌入main中,编译结果中只有main一个函数。
 * 于是在连接时,main函数的地址就是由连接文件指定的运行时装载地址。
 * 而连接文件mmu.lds中,指定了leds.o的运行时装载地址为0xB4004000,
 * 这样,head.S中的“ldr pc, =0xB4004000”就是跳去执行main函数。
 * volatile 是防止该代码被优化
 */

static inline void delay (unsigned long loops)

{

    __asm__ volatile (

            "1:\n"
            "subs %0, %1, #1\n"          @loops=loops-1;

            "bne 1b":"=r" (loops):"0" (loops));

}

/*
上面是个延时函数,再次涉及到嵌入式汇编的语法知识
即loops=loops
 1:
    subs loops,,loops,#1
    bne 1b
  //  :"=r" (loops)
  //  :"0" (loops)
*/

int main()
{
        unsigned long i=0;



	while(1)
	{
        	GPFCON=0x00000100;   

        	for(i=0;i<3;i++)
        	{
		  delay(300000);
                  delay(300000);
 	          GPFCON=GPFCON<<2;
	          GPFDAT=0x00000000;
        	}
	}
    return 0;
}


                                                          在未经本人允许的情况下,拒绝转载!!




                

                     

  


  

 

     

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值