MMU的初始化與開啓實驗

內存管理單元MMU負責虛擬地址到物理地址的映射,並提供硬件機制的內存訪問權限檢查。

     4種映射長度:段(1MB)、大頁(64KB)、小頁(4KB)、極小頁(1KB)。

     對每個段都可以設置訪問權限。

     大頁、小頁的每個子頁(sub-page,即被映射頁的1/4)都可以單獨設置訪問權限。

     沒有啓動MMU時,CPU核、cache、MMU、外設等所有部件使用的都是物理地址。

     理論知識我就不多寫了,畢竟這裏是做實驗而不是講原理的,但是代碼的註釋會很清楚

主要的c代碼:

 

  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開啓之後 就只認虛擬地址了

  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加的一點內容了

  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

最後就是連接腳本了

  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

  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了

發佈了249 篇原創文章 · 獲贊 9 · 訪問量 107萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章