之前跑裸机时搞到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.