MMU的初始化与开启实验

来源:互联网 发布:答题软件单机版 编辑:程序博客网 时间:2024/06/05 08:34

内存管理单元MMU负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。

     4种映射长度:段(1MB)、大页(64KB)、小页(4KB)、极小页(1KB)。

     对每个段都可以设置访问权限。

     大页、小页的每个子页(sub-page,即被映射页的1/4)都可以单独设置访问权限。

     没有启动MMU时,CPU核、cache、MMU、外设等所有部件使用的都是物理地址。

     理论知识我就不多写了,毕竟这里是做实验而不是讲原理的,但是代码的注释会很清楚

主要的c代码:

 

view plaincopy to clipboardprint?
  1. #include"memmap.h"  
  2.   
  3. void creat_page_table()  
  4. {  
  5. //我们建立段描述符  相对简单些 便于理解  
  6. //权限设置 它们占描述符的低12bit【11-0】  
  7. #define MMU_AP      (3<<10)       //ap 位111 访问权限  
  8. #define MMU_DOMAIN  (0<<5)        //选择0域  
  9. #define MMU_SPECIAL (1<<4)        // 必须1  why?  
  10. #define MMU_CACHEEN (1<<3)        //cache使能  
  11. #define MMU_BUFFEN  (1<<2)        //buffer 使能  
  12. #define MMU_SECTION 2           //0b10 表示这个是段描述符  
  13. //段描述符低12bit 没有开启cache和buff,因为它 将用于前1M的映射,不能开启cache  
  14. #define MMU_SEC_DESC    MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION  
  15. //段描述符低12bit  开启cache和buff,它将用于 后面sdram的映射,开启cache  buffer  
  16. #define MMU_SEC_DESC_WB MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION|MMU_CACHEEN|MMU_BUFFEN  
  17.   
  18.   
  19. unsigned long vir_address,phy_address;  
  20. unsigned long *mmu_table_base=(unsigned long *)0x30000000;//地址转化为指针  
  21. //映射前1M的物理地址到虚拟地址的前1M,为了能够在开启mmu 之后0地址的前4k内的程序依然可以执行,所以它们是一一对应的关系  
  22. /* 
  23. (vir_address>>20可以理解取虚拟地址高 12bit 也可以理解为虚拟地址除于1MB,就得到了段地址,所以要映射的虚拟地址必须是1M的倍数。学过汇编的话你应该很容易知道段地址是怎么一回 事,比如这里我们的是按照1M分段的,那么段1就是1M=0x100000。段大页小页极小页地址,也就是单位不一样。  
  24. phy_address&0xfff00000也是保存高 12bit  这样做取来的结果是1M的倍数,也是段对应的物理地址,低12bit保存权限,中间的8bit保留 
  25. */  
  26. vir_address=0;  
  27. phy_address=0;  
  28. *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC);  
  29.   
  30. /* 
  31. mmu_table_base+(vir_address>>20)取得段描述符的地址(指针),这4个字 节保存着一个32bit的段描述符(((phy_address&0xfff00000) | MMU_SEC_DESC)),段描述符的 【31-20】bit保存着段的物理地址(带上单位就是实实在在的地址了,比如12M,12M=0x1200000) 
  32. MMU_SEC_DESC即是上面定义的权限了 
  33. */  
  34.   
  35.   
  36.   
  37.   
  38.   
  39. /* 将0x56000000 之后1M映射到0xA0000000  
  40. 外部 IO存储设备也是不能开启cache和buff的  
  41. */  
  42. vir_address=0xa0000000;  
  43. phy_address=0x56000000;  
  44. *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC);  
  45.   
  46. /*将0x30000000 之后64M映射到0xB0000000*/  
  47. vir_address=0xb0000000;  
  48. phy_address=0x30000000;  
  49. while(phy_address<0x34000000)  
  50. {  
  51.     *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC_WB);  
  52.     vir_address+=0x100000;            //累加1m,到下一个段描述符  
  53.     phy_address+=0x100000;  
  54.       
  55. }  
  56. }  
  57. /*以上就是我们建的描述符表了,但是cpu还不知道这是怎么回事, 下面我们就需要告诉cpu这样一个虚拟地址映射物理地址的规则了,由于处理开启mmu以后的所有的虚拟地址转换*/  
  58.   
  59. void mmu_init()  
  60. {  
  61. unsigned long ttb=0x30000000;     //描述符表table的表头地址  
  62. __asm__(  
  63.     "mov r0,#0/n"                  //操作协处理器得仔细看其结构了  
  64.     "mcr p15,0,r0,c7,c7,0/n"      //mcr是写  将r0 写入c7    
  65.     "mcr p15,0,r0,c7,c10,4/n"      
  66.     "mcr p15,0,r0,c8,c7,0/n"  
  67.       
  68.     "mov r4,%0/n"  
  69.     "mcr p15,0,r4,c2,c0,0/n"  
  70.   
  71.     "mvn r0,#0/n"  
  72.     "mcr p15,0,r0,c3,c0,0/n"  
  73.   
  74. /*对于控制寄存器  先读出控制寄存器的值  修改之  再保存进去*/  
  75.     "mrc p15,0,r0,c1,c0,0/n"  
  76.   
  77.     /*先清除不需要的位 如果需要用到 之后再设置*/  
  78.     "bic r0,r0,#0x3000/n"  
  79.     "bic r0,r0,#0x0300/n"  
  80.     "bic r0,r0,#0x0087/n"            //清除1 2 3 7bit  
  81.   
  82. /* 设置需要的位*/  
  83.     "orr r0,r0,#0x2/n"              //开启对齐检查  
  84.     "orr r0,r0,#0x4/n"              //开启dcache  
  85.     "orr r0,r0,#0x1000/n"           //开启icache  
  86.     "orr r0,r0,#0x1/n"             //使能mmu  
  87.   
  88.     "mcr p15,0,r0,c1,c0,0/n"  
  89.   
  90.     :  
  91.     :"r" (ttb)  
  92.          );  
  93.   
  94. }  
#include"memmap.h" void creat_page_table() { //我们建立段描述符 相对简单些 便于理解 //权限设置 它们占描述符的低12bit【11-0】 #define MMU_AP (3<<10) //ap位111 访问权限 #define MMU_DOMAIN (0<<5) //选择0域 #define MMU_SPECIAL (1<<4) //必须1 why? #define MMU_CACHEEN (1<<3) //cache使能 #define MMU_BUFFEN (1<<2) //buffer使能 #define MMU_SECTION 2 //0b10 表示这个是段描述符 //段描述符低12bit 没有开启cache和buff,因为它将用于前1M的映射,不能开启cache #define MMU_SEC_DESC MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION //段描述符低12bit 开启cache和buff,它将用于后面sdram的映射,开启cache buffer #define MMU_SEC_DESC_WB MMU_AP|MMU_DOMAIN|MMU_SPECIAL|MMU_SECTION|MMU_CACHEEN|MMU_BUFFEN unsigned long vir_address,phy_address; unsigned long *mmu_table_base=(unsigned long *)0x30000000;//地址转化为指针 //映射前1M的物理地址到虚拟地址的前1M,为了能够在开启mmu之后0地址的前4k内的程序依然可以执行,所以它们是一一对应的关系 /* (vir_address>>20可以理解取虚拟地址高12bit 也可以理解为虚拟地址除于1MB,就得到了段地址,所以要映射的虚拟地址必须是1M的倍数。学过汇编的话你应该很容易知道段地址是怎么一回事,比如这里我 们的是按照1M分段的,那么段1就是1M=0x100000。段大页小页极小页地址,也就是单位不一样。 phy_address&0xfff00000也是保存高12bit 这样做取来的结果是1M的倍数,也是段对应的物理地址,低12bit保存权限,中间的8bit保留 */ vir_address=0; phy_address=0; *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC); /* mmu_table_base+(vir_address>>20)取得段描述符的地址(指针),这4个字节保存着一个32bit的段描述符 (((phy_address&0xfff00000) | MMU_SEC_DESC)),段描述符的【31-20】bit保存着段的物理地址(带上单位就是实实在在的地址了,比如 12M,12M=0x1200000) MMU_SEC_DESC即是上面定义的权限了 */ /*将0x56000000 之后1M映射到0xA0000000 外部IO存储设备也是不能开启cache和buff的 */ vir_address=0xa0000000; phy_address=0x56000000; *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC); /*将0x30000000 之后64M映射到0xB0000000*/ vir_address=0xb0000000; phy_address=0x30000000; while(phy_address<0x34000000) { *(mmu_table_base+(vir_address>>20))=((phy_address&0xfff00000) | MMU_SEC_DESC_WB); vir_address+=0x100000; //累加1m,到下一个段描述符 phy_address+=0x100000; } } /*以上就是我们建的描述符表了,但是cpu还不知道这是怎么回事,下面我们就需要告诉cpu这样一个虚拟地址映射物理地址的规则了,由于处理开启mmu 以后的所有的虚拟地址转换*/ void mmu_init() { unsigned long ttb=0x30000000; //描述符表table的表头地址 __asm__( "mov r0,#0/n" //操作协处理器得仔细看其结构了 "mcr p15,0,r0,c7,c7,0/n" //mcr是写 将r0 写入c7 "mcr p15,0,r0,c7,c10,4/n" "mcr p15,0,r0,c8,c7,0/n" "mov r4,%0/n" "mcr p15,0,r4,c2,c0,0/n" "mvn r0,#0/n" "mcr p15,0,r0,c3,c0,0/n" /*对于控制寄存器 先读出控制寄存器的值 修改之 再保存进去*/ "mrc p15,0,r0,c1,c0,0/n" /*先清除不需要的位 如果需要用到 之后再设置*/ "bic r0,r0,#0x3000/n" "bic r0,r0,#0x0300/n" "bic r0,r0,#0x0087/n" //清除1 2 3 7bit /*设置需要的位*/ "orr r0,r0,#0x2/n" //开启对齐检查 "orr r0,r0,#0x4/n" //开启dcache "orr r0,r0,#0x1000/n" //开启icache "orr r0,r0,#0x1/n" //使能mmu "mcr p15,0,r0,c1,c0,0/n" : :"r" (ttb) ); }

对于协处理器的结构,我也很模糊,以后专门研究下

其次就是定义那些寄存器的物理地址需要修改一下,因为mmu开启之后 就只认虚拟地址了

view plaincopy to clipboardprint?
  1. //led fanction  
  2. #define GPBCON  (* (volatile unsigned long *)0xa0000010) //先将地址值其转化为指针,GPBCON其实就变成了这个地址储存的值 鸟  哈哈   
  3. #define GPBDAT  (*(volatile unsigned long *)0xa0000014)  
  4. #define GPBUP   (*(volatile unsigned long *)0xa0000018)  
  5. //key addr  
  6. #define GPFCON  (*(volatile unsigned long *)0xa0000050)  
  7. #define GPFDAT  (*(volatile unsigned long *)0xa0000054)  
  8. #define GPFUP   (*(volatile unsigned long *)0xa0000058)  
//led fanction #define GPBCON (*(volatile unsigned long *)0xa0000010) //先将地址值其转化为指针,GPBCON其实就变成了这个地址储存的值鸟 哈哈 #define GPBDAT (*(volatile unsigned long *)0xa0000014) #define GPBUP (*(volatile unsigned long *)0xa0000018) //key addr #define GPFCON (*(volatile unsigned long *)0xa0000050) #define GPFDAT (*(volatile unsigned long *)0xa0000054) #define GPFUP (*(volatile unsigned long *)0xa0000058)

然后就是start.s加的一点内容了

view plaincopy to clipboardprint?
  1. @led start  
  2. @2010-01-12  
  3. @jay  
  4.   
  5. .globl _start  
  6. _start:  
  7.     b reset  
  8.     @预留着以后扩展中断向量表  
  9.   
  10.   
  11. reset:  
  12.           /*因为下面有c函数 要先设置个sp*/  
  13.     ldr sp,=4096  
  14.     @disable watchdog  
  15.     ldr r0,=0x53000000  
  16.     mov r1,#0  
  17.     str r1,[r0]  
  18.   
  19.     @init SDRAM  
  20.     bl memsetup  
  21.       
  22.     @cope the code to sdram  
  23.     bl cp_to_SDRAM  
  24.       
  25.     @setup pagetable  
  26.     bl creat_page_table  
  27.       
  28.     @init and enable mmu  
  29.     bl mmu_init   
  30.       
  31.     @reset stack   开启了mmu,我们要重设下堆栈指针,指向虚拟地址映射的顶部  
  32.     ldr sp,=0xb4000000  
  33.       
  34.     @跳到虚拟地址   
  35.  /*这2句指令是从uboot学来的 注意这一点的理解,_led_on是个标签,标签的实质就是个地址,这个标签存储 的内容就是另外一个标签led_test(是不是很熟悉的感觉?对了,就是c语言的指针的指针),其实就是将_led_on的内容led_text赋给了 pc*/  
  36.     ldr pc,_led_on  
  37. _led_on:.word led_test   
  38.   
  39.   
  40.   
  41. load_sd:  
  42.     ldr sp,=0x34000000  
  43.     bl led_test  
  44.   
  45.       
  46.     @copy to sdram  
  47. cp_to_SDRAM:  
  48. @   adr r0,_start  
  49.     mov r0,#0  
  50.     add r1,r0,#4096  
  51.     ldr r2,=0x30004000   @注意这里不要指定到0x30000000了  
  52. lp:  
  53.     ldmia r0!,{r3-r11}  
  54.     stmia r2!,{r3-r11}  
  55.     cmp r0,r1  
  56.     ble lp    
  57.     mov pc,lr  
@led start @2010-01-12 @jay .globl _start _start: b reset @预留着以后扩展中断向量表 reset: /*因为下面有c函数 要先设置个sp*/ ldr sp,=4096 @disable watchdog ldr r0,=0x53000000 mov r1,#0 str r1,[r0] @init SDRAM bl memsetup @cope the code to sdram bl cp_to_SDRAM @setup pagetable bl creat_page_table @init and enable mmu bl mmu_init @reset stack 开启了mmu,我们要重设下堆栈指针,指向虚拟地址映射的顶部 ldr sp,=0xb4000000 @跳到虚拟地址 /*这2句指令是从uboot学来的 注意这一点的理解,_led_on是个标签,标签的实质就是个地址,这个标签存储的内容就是另外一个标签led_test(是不是很熟悉的感觉?对了,就 是c语言的指针的指针),其实就是将_led_on的内容led_text赋给了pc*/ ldr pc,_led_on _led_on:.word led_test load_sd: ldr sp,=0x34000000 bl led_test @copy to sdram cp_to_SDRAM: @ adr r0,_start mov r0,#0 add r1,r0,#4096 ldr r2,=0x30004000 @注意这里不要指定到0x30000000了 lp: ldmia r0!,{r3-r11} stmia r2!,{r3-r11} cmp r0,r1 ble lp mov pc,lr

最后就是连接脚本了

view plaincopy to clipboardprint?
  1. OUTPUT_FORMAT("elf32-littlearm""elf32-littlearm""elf32-littlearm")  
  2. OUTPUT_ARCH(arm)  
  3. ENTRY(_start)  
  4. SECTIONS   
  5. {  
  6.     . =0x30004000;  
  7.     . =ALIGN(4);  
  8.     .text :   
  9.     {  
  10.             start.o (.text)   
  11.             low_init.o (.text)   
  12.             *(.text)   
  13.     }  
  14.     . = ALIGN(4);  
  15.     .data :{ *(.data) }  
  16.     . =ALIGN(4);  
  17.     .rodata : { *(.rodata) }  
  18.     . =ALIGN(4);  
  19.     __bss_start = .;  
  20.     .bss :{*(.bss)}  
  21.     _end = . ;  
  22. }  
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . =0x30004000; . =ALIGN(4); .text : { start.o (.text) low_init.o (.text) *(.text) } . = ALIGN(4); .data :{ *(.data) } . =ALIGN(4); .rodata : { *(.rodata) } . =ALIGN(4); __bss_start = .; .bss :{*(.bss)} _end = . ; }

这个也是从uboot学来的

ALIGN(4)是4字节对齐,32bitcpu一次处理32bit的嘛

.是指当前地址   这里指定的知识编译地址,跟运行地址会不一样的。

两个.指令要用;隔开的,注意. =0x30004000 这里有空格的啊 否则会出错

makefile

view plaincopy to clipboardprint?
  1. CC=arm-linux-gcc  
  2. LD=arm-linux-ld  
  3. CP=arm-linux-objcopy  
  4. DP=arm-linux-objdump  
  5.   
  6. objs:=start.o low_init.o memmap.o led.o   
  7.   
  8. mmu.bin:$(objs)  
  9.     $(LD) -Tboot.lds -g -o mmu_elf $^  
  10.     $(CP) -O binary -S mmu_elf $@  
  11.     $(DP) -D -m arm mmu_elf > mmu.asm  
  12. #makefile 注意 .s不可以大写  -T不可以少-  
  13. %.o:%.c  
  14.     $(CC)  -g -Wall -c -o $@  $<  
  15. %.o:%.s  
  16.     $(CC)  -g -Wall -c -o $@  S<  
  17.       
  18.   
  19. clean:  
  20.     rm -f   *.asm *.bin *_elf *.o  
CC=arm-linux-gcc LD=arm-linux-ld CP=arm-linux-objcopy DP=arm-linux-objdump objs:=start.o low_init.o memmap.o led.o mmu.bin:$(objs) $(LD) -Tboot.lds -g -o mmu_elf $^ $(CP) -O binary -S mmu_elf $@ $(DP) -D -m arm mmu_elf > mmu.asm #makefile 注意 .s不可以大写 -T不可以少- %.o:%.c $(CC) -g -Wall -c -o $@ $< %.o:%.s $(CC) -g -Wall -c -o $@ S< clean: rm -f *.asm *.bin *_elf *.o

内容虽然不多,却花费了我很多时间去理解,其他的代码与之前的一样 没有改动

终于是可以成功开启mmu了