之前跑裸機時搞到MMU那一塊的時候只進行了原理上的學習,沒有進行s3c2440的MMU實例操作,今天分析linux內核啓動時創建臨時頁表那部分有些地方還是卡住了不理解,就比如說爲什麼要創建臨時頁表這個問題,雖說代碼實現過程可以分析明白,就是不明白爲什麼要建立1:1的映射關係?這裏好是糾結。決定在裸機MMU那部分再進行實例分析。
代碼要實現的效果:
將第二部分代碼,即led控制那部分程序運行在虛擬地址空間,並且通過虛擬地址來控制4顆led的循環顯示。
代碼具體完成的任務:
1.建立物理地址空間第一個1M映射:(VA)0x00000000-0x00100000---->(PA)0x00000000-0x00100000【實際用到的物理地址0x00000000-0x00000800】
2.建立寄存器GPBCON和GPBDAT的地址映射:(VA)0xA0000000-0xA0100000---->(PA)0x56000000-0x56100000【實際中用到的實際物理地址0x56000010、0x56000014】
3.建立第二部分代碼地址空間leds.o的映射:(VA)0xB0000000-0xB0100000---->(PA)0x30000000-0x30100000【實際用到的物理地址0x30004000-0x30004800(2048-4096拷貝過去的內容)】
注:每次映射都是要完整1M的地址映射。
鏈接腳本文件mmu.lds:
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0xB0004000 : AT(2048) { leds.o }
}
這個鏈接腳本將三個目標文件分成兩塊來構建elf格式的可執行文件。意思是:將head.o和init.o目標文件依次連在一起放到物理運行空間的0地址處,作爲可執行文件的第一部分“first”,並且這cpu運行這部分代碼時是從地址0處開始取指令執行;接着將leds.o目標文件強行放到物理運行空間的2048地址處,從這裏開始存放leds.o文件,即leds.o文件在elf可執行文件中的偏移是2048,cpu取指令時要讓他“自以爲”看到這段代碼存放到0xB0004000處。忽悠cpu這種事情是linux內核中常有的事情,不過這是從我們敲代碼的人的角度來看待這一種事實的。要是cpu會跟人一樣說話,說不定還會不屑的說:“我的世界你不懂,說句實在話,我才懶得管去哪裏取‘材料’呢,我有的是錢,我僱了個MMU,讓她專門負責把‘材料’的存放位置告訴那些運輸部門,我想要什麼‘材料’只管看看我自己的大腦空間裏邊找找看,找到了跟MMU說就行了。”
啓動引導文件head.S:
.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中繼續執行第二部分代碼
@ ldr pc, =main @ 完全可以使用這一句來代替上面的一句話
halt_loop:
b halt_loop
調用mmu_init函數之後,頁表也創建好了mmu也啓動完成,但是下一句。看下一個代碼。
init.c文件:主要是關於看門狗、SDRAM和MMU的函數
#define WTCON (*(volatile unsigned long *)0x53000000)
/* 存儲控制器的寄存器起始地址 */
#define MEM_CTL_BASE 0x48000000
void disable_watch_dog(void)
{
WTCON = 0; // 關閉WATCHDOG很簡單,往這個寄存器寫0即可
}
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];
}
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++;
}
}
//下邊是創建頁表,有三部分的映射
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後仍能運行第一部分的程序,之所以還要運行第一部分的程序是因爲在跳轉到第二部分main函數之時是非1:1的虛實地址映射,
* 若此時進行跳轉c函數main中,pc指針只識得映射後的0xBxxxxxxx之類的地址,那sp指針該怎麼辦,他還在指着0x0-0x40000的某一處,這些地址cpu理都不理啊
* 爲了避免這樣的事情發生,重新設置cpu認識的sp,而這個設置過程就是要可執行的,因此將0~1M的虛擬地址映射到同樣的物理地址,這裏的物理地址實際上沒有1M,而只有Steppingstone的4K大小
* 看透了之後發現,這一頁的映射就爲了執行ldr sp, =0xB4000000和ldr pc, =0xB0004000兩條語句,就兩條,就·······
* 文章開頭提出來的問題到這裏得到了解決!
*/
virtuladdr = 0;
physicaladdr = 0;
*(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
MMU_SECDESC_WB;
/*
* 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個段描述符
*/
virtuladdr = 0xB0000000;
physicaladdr = 0x30000000;
while (virtuladdr < 0xB4000000)
{
*(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) | \
MMU_SECDESC_WB;
virtuladdr += 0x100000;
physicaladdr += 0x100000;
}
}
/*
* 將頁表基地址告訴MMU,並啓動MMU
*/
void mmu_init(void)
{
unsigned long ttb = 0x30000000;
// ARM休系架構與編程
// 嵌入彙編:LINUX內核完全註釋
__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) ); //變量ttb通過通用寄存器R0-R15中的某一個傳遞,這裏是c向彙編輸入的第一個變量,彙編裏邊用“%0”表示
}
這一段代碼已經在代碼中進行了比較詳細的註釋,就不在獨立出來分析。
led控制主程序leds.c:
#define GPBCON (*(volatile unsigned long *)0xA0000010) // 物理地址0x56000010
#define GPBDAT (*(volatile unsigned long *)0xA0000014) // 物理地址0x56000014
/*
* LED1,LED2,LED4對應GPB5、GPB6、GPB7、GPB8
*/
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*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(volatile unsigned long dly)
{
for(; dly > 0; dly--);
}
int main(void)
{
unsigned long i = 0;
// LED1,LED2,LED3,LED4對應的4根引腳設爲輸出
GPBCON = GPB5_out | GPB6_out | GPB7_out | GPB8_out;
while(1){
wait(30000);
GPBDAT = (~(i<<5)); // 根據i的值,點亮LED1,2,3,4
if(++i == 16)
i = 0;
}
return 0;
}
講述完畢!
另附反彙編文件mmu.dis
mmu_elf: file format elf32-littlearm
Disassembly of section firtst:
寫程序的時候我們說的地址是沒有虛擬地址和物理地址之分的,是單純的地址,是cpu看到的
(運行地址)
鏈接地址 機器碼
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000
4: eb00000e bl 44 <disable_watch_dog>
8: eb000011 bl 54 <memsetup>
c: eb00001c bl 84 <copy_2th_to_sdram>
10: eb000024 bl a8 <create_page_table>
14: eb000039 bl 100 <mmu_init> ; 在這個函數裏邊就包含了頁表的創建和之後的MMU啓動
18: e3a0d32d mov sp, #-1275068416 ; 0xb4000000 運行到這一條指令mmu已經啓動,但是另一個映射區的c運行環境還沒有設置好,此時還不能跳轉到非1:1的映射區
1c: e59ff000 ldr pc, [pc, #0] ; 24 <halt_loop+0x4>
00000020 <halt_loop>:
20: eafffffe b 20 <halt_loop>
24: b0004000 .word 0xb0004000
28: 00001941 .word 0x00001941
2c: 61656100 .word 0x61656100
30: 01006962 .word 0x01006962
34: 0000000f .word 0x0000000f
38: 00543405 .word 0x00543405
3c: 01080206 .word 0x01080206
40: 00000109 .word 0x00000109
00000044 <disable_watch_dog>:
44: e3a02000 mov r2, #0 ; 0x0
48: e3a03453 mov r3, #1392508928 ; 0x53000000
4c: e5832000 str r2, [r3]
50: e12fff1e bx lr
00000054 <memsetup>:
54: e3a00312 mov r0, #1207959552 ; 0x48000000
58: e59fc020 ldr ip, [pc, #32] ; 80 <memsetup+0x2c>
5c: e2800034 add r0, r0, #52 ; 0x34
60: e3a01312 mov r1, #1207959552 ; 0x48000000
64: e08c3001 add r3, ip, r1
68: e283332e add r3, r3, #-1207959552 ; 0xb8000000
6c: e5932000 ldr r2, [r3]
70: e4812004 str r2, [r1], #4
74: e1510000 cmp r1, r0
78: 1afffff9 bne 64 <memsetup+0x10>
7c: e12fff1e bx lr
80: 0000014c .word 0x0000014c
00000084 <copy_2th_to_sdram>:
84: e3a01000 mov r1, #0 ; 0x0
88: e2813203 add r3, r1, #805306368 ; 0x30000000
8c: e5912800 ldr r2, [r1, #2048]
90: e2811004 add r1, r1, #4 ; 0x4
94: e2833901 add r3, r3, #16384 ; 0x4000
98: e3510b02 cmp r1, #2048 ; 0x800
9c: e5832000 str r2, [r3]
a0: 1afffff8 bne 88 <copy_2th_to_sdram+0x4>
a4: e12fff1e bx lr
000000a8 <create_page_table>:
a8: e3a03456 mov r3, #1442840576 ; 0x56000000
ac: e2833ec1 add r3, r3, #3088 ; 0xc10
b0: e3a02ec1 mov r2, #3088 ; 0xc10
b4: e3a01203 mov r1, #805306368 ; 0x30000000
b8: e2811a02 add r1, r1, #8192 ; 0x2000
bc: e282200e add r2, r2, #14 ; 0xe
c0: e2833002 add r3, r3, #2 ; 0x2
c4: e3a00203 mov r0, #805306368 ; 0x30000000
c8: e5802000 str r2, [r0]
cc: e5813800 str r3, [r1, #2048]
d0: e3a0120b mov r1, #-1342177280 ; 0xb0000000
d4: e2813102 add r3, r1, #-2147483648 ; 0x80000000
d8: e1a03a23 lsr r3, r3, #20
dc: e1a03a03 lsl r3, r3, #20
e0: e1a02a21 lsr r2, r1, #20
e4: e3833ec1 orr r3, r3, #3088 ; 0xc10
e8: e2811601 add r1, r1, #1048576 ; 0x100000
ec: e383300e orr r3, r3, #14 ; 0xe
f0: e351032d cmp r1, #-1275068416 ; 0xb4000000
f4: e7803102 str r3, [r0, r2, lsl #2]
f8: 1afffff5 bne d4 <create_page_table+0x2c>
fc: e12fff1e bx lr
00000100 <mmu_init>:
100: e3a03203 mov r3, #805306368 ; 0x30000000
104: e3a00000 mov r0, #0 ; 0x0
108: ee070f17 mcr 15, 0, r0, cr7, cr7, {0}
10c: ee070f9a mcr 15, 0, r0, cr7, cr10, {4}
110: ee080f17 mcr 15, 0, r0, cr8, cr7, {0}
114: e1a04003 mov r4, r3
118: ee024f10 mcr 15, 0, r4, cr2, cr0, {0}
11c: e3e00000 mvn r0, #0 ; 0x0
120: ee030f10 mcr 15, 0, r0, cr3, cr0, {0}
124: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
128: e3c00a03 bic r0, r0, #12288 ; 0x3000
12c: e3c00c03 bic r0, r0, #768 ; 0x300
130: e3c00087 bic r0, r0, #135 ; 0x87
134: e3800002 orr r0, r0, #2 ; 0x2
138: e3800004 orr r0, r0, #4 ; 0x4
13c: e3800a01 orr r0, r0, #4096 ; 0x1000
140: e3800001 orr r0, r0, #1 ; 0x1
144: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
148: e12fff1e bx lr
0000014c <mem_cfg_val.1252>:
14c: 22011110 .word 0x22011110
150: 00000700 .word 0x00000700
154: 00000700 .word 0x00000700
158: 00000700 .word 0x00000700
15c: 00000700 .word 0x00000700
160: 00000700 .word 0x00000700
164: 00000700 .word 0x00000700
168: 00018005 .word 0x00018005
16c: 00018005 .word 0x00018005
170: 008c07a3 .word 0x008c07a3
174: 000000b1 .word 0x000000b1
178: 00000030 .word 0x00000030
17c: 00000030 .word 0x00000030
180: 43434700 .word 0x43434700
184: 5328203a .word 0x5328203a
188: 6372756f .word 0x6372756f
18c: 20797265 .word 0x20797265
190: 202b2b47 .word 0x202b2b47
194: 6574694c .word 0x6574694c
198: 30303220 .word 0x30303220
19c: 2d317139 .word 0x2d317139
1a0: 29333032 .word 0x29333032
1a4: 332e3420 .word 0x332e3420
1a8: 4100332e .word 0x4100332e
1ac: 00000029 .word 0x00000029
1b0: 62616561 .word 0x62616561
1b4: 1f010069 .word 0x1f010069
1b8: 05000000 .word 0x05000000
1bc: 06005434 .word 0x06005434
1c0: 09010802 .word 0x09010802
1c4: 14041201 .word 0x14041201
1c8: 17011501 .word 0x17011501
1cc: 19011803 .word 0x19011803
1d0: 1e021a01 .word 0x1e021a01
1d4: Address 0x000001d4 is out of bounds.
Disassembly of section second:
;第二段代碼就是從這裏開始的,發現木有,程序的運行地址是0xb開頭的
b0004000 <main>:
b0004000: e3a0220a mov r2, #-1610612736 ; 0xa0000000
b0004004: e3a03b55 mov r3, #87040 ; 0x15400
b0004008: e5823010 str r3, [r2, #16]
b000400c: e3a01000 mov r1, #0 ; 0x0
b0004010: e3a03c75 mov r3, #29952 ; 0x7500
b0004014: e2833030 add r3, r3, #48 ; 0x30
b0004018: e2533001 subs r3, r3, #1 ; 0x1
b000401c: 1afffffd bne b0004018 <main+0x18>
b0004020: e1e03281 mvn r3, r1, lsl #5
b0004024: e351000f cmp r1, #15 ; 0xf
b0004028: e5823014 str r3, [r2, #20]
b000402c: 12811001 addne r1, r1, #1 ; 0x1
b0004030: 03a01000 moveq r1, #0 ; 0x0
b0004034: eafffff5 b b0004010 <main+0x10>
b0004038: 43434700 movtmi r4, #14080 ; 0x3700
b000403c: 5328203a .word 0x5328203a
b0004040: 6372756f .word 0x6372756f
b0004044: 20797265 .word 0x20797265
b0004048: 202b2b47 .word 0x202b2b47
b000404c: 6574694c .word 0x6574694c
b0004050: 30303220 .word 0x30303220
b0004054: 2d317139 .word 0x2d317139
b0004058: 29333032 .word 0x29333032
b000405c: 332e3420 .word 0x332e3420
b0004060: 4100332e .word 0x4100332e
b0004064: 00000029 .word 0x00000029
b0004068: 62616561 .word 0x62616561
b000406c: 1f010069 .word 0x1f010069
b0004070: 05000000 .word 0x05000000
b0004074: 06005434 .word 0x06005434
b0004078: 09010802 .word 0x09010802
b000407c: 14041201 .word 0x14041201
b0004080: 17011501 .word 0x17011501
b0004084: 19011803 .word 0x19011803
b0004088: 1e021a01 .word 0x1e021a01
b000408c: Address 0xb000408c is out of bounds.