一、MMU介绍
1.1 虚拟地址与物理地址
建立两个应用程序,hello1.c和hello2.c,然后运行:
hello1.c
hello2.c
运行结果如下:
可以看到两个结果打印的地址是一样的,都为 0x601040,这说明两段程序都运行于同一个地址中。我们的死循环程序又保证了两个程序在同时运行。
对于2440来说,如下图:
CPU只管发出地址,读写数据。不管地址是否是物理地址还是虚拟地址。
写程序的时候,链接地址也没有物理地址和虚拟地址。链接地址是CPU看到的,是从CPU的角度来说的。
1.2 虚拟地址转换成物理地址
对与ARM来说,虚拟地址(VA)转换成物理地址(PA)是通过表格来转换的。这个表格就是页表。
建立映射的简单步骤是:
建立表格:虚拟地址到物理地址的映射
表格地址告诉给MMU:表格在内存中,将表格的首地址告诉MMU
启动MMU
二、代码
2.1 start.S (archarmcpuarm920t)
start.S中的cpu_init_crit进行了MMU的使能设置,u-boot启动的时候的是不使能MMU的。MMU的使能涉及到CP15协处理器。
代码段如下:
1 /*
2 * disable MMU stuff and caches
3 */
4 mrc p15, 0, r0, c1, c0, 0 /* 将 CP15 的寄存器 C1 的值读到 r0 中 */
5 /* C1寄存器的8,9,13位被清除
6 * S(bit[8])在基于 MMU 的存储系统中,本位用作系统保护
7 * R(bit[9])在基于 MMU 的存储系统中,本位用作 ROM 保护
8 * V(bit[13])
9 * 对于支持高端异常向量表的系统,本控制位控制向量表的位置
10 * 0 :选择低端异常中断向量 0x0~0x1c
11 * 1 :选择高端异常中断向量0xffff0000~ 0xffff001c
12 * 对于不支持高端异常向量表的系统,读取时该位返回0,写入时忽略
13 */
14 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
15 /* C1寄存器的0,1,2,7位被清除
16 * M(bit[0])
17 * 0 :禁止 MMU 或者 PU
18 * 1 :使能 MMU 或者 PU
19 * 如果系统中没有MMU及PU,读取时该位返回0,写入时忽略该位
20 * A(bit[1])
21 * 0 :禁止地址对齐检查
22 * 1 :使能地址对齐检查
23 * C(bit[2])
24 * 当数据cache和指令cache分开时,本控制位禁止/使能数据cache。
25 * 当数据cache和指令cache统一时,该控制位禁止/使能整个cache。
26 * 0 :禁止数据 / 整个 cache
27 * 1 :使能数据 / 整个 cache
28 * 如果系统中不含cache,读取时该位返回0.写入时忽略
29 * 当系统中不能禁止cache 时,读取时返回1.写入时忽略
30 * B(bit[7])
31 * 对于存储系统同时支持big-endian和little-endian的ARM系统,本控制位配置系统的存储模式
32 * 0 : little endian
33 * 1 : big endian
34 * 对于只支持little-endian的系统,读取时该位返回0,写入时忽略
35 * 对于只支持big-endian的系统,读取时该位返回1,写入时忽略
36 */
37 bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
38 /* C1寄存器的bit1置1,即使能地址对齐 */
39 orr r0, r0, #0x00000002 @ set bit 1 (A) Align
40 /* C1寄存器的bit12置1,即使能指令 cache */
41 /* I(bit[12])
42 * 当数据cache和指令cache是分开的,本控制位禁止/使能指令cache
43 * 0 :禁止指令 cache
44 * 1 :使能指令 cache
45 * 如果系统中使用统一的指令cache和数据cache或者系统中不含cache,读取该位时返回0,写入时忽略。
46 * 当系统中的指令cache不能禁止时,读取时该位返回1,写入时忽略
47 */
48 orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
49 mcr p15, 0, r0, c1, c0, 0 /* 将 r0 的值写到 CP15 的寄存器 C1 中 */
50
51 /*
52 * before relocating, we have to setup RAM timing
53 * because memory timing is board-dependend, you will
54 * find a lowlevel_init.S in your board directory.
55 */
56 mov ip, lr /* 保存函数地址,用于返回 */
之后start.S 中的代码会进入 _main(crt0.S (archarmlib))中运行。
2.2 crt0.S (archarmlib)
ctr0.S中运行 _main 函数,再此函数中,运行到 board_init_f(单板启动前初始化),然后执行到链表 init_sequence_f,在其中对MMU进行初始化 reserve_mmu。
2.2.1 reserve_mmu
1 static int reserve_mmu(void)
2 {
3 /* reserve TLB table */
4 gd->arch.tlb_size = PGTABLE_SIZE; //PGTABLE_SIZE = 4096 *4 预留16kb的MMU页表
5 gd->relocaddr -= gd->arch.tlb_size;//gd->relocaddr = gd->relocaddr - 16K = 0x33ffc000
6
7 /* round down to next 64 kB limit */
8 gd->relocaddr &= ~(0x10000 - 1);//64kb对齐 gd->relocaddr = 0x33ff0000
9
10 gd->arch.tlb_addr = gd->relocaddr;//gd->arch.tlb_addr = 0x33ff0000
11 debug("TLB table from %08lx to %08lxn", gd->arch.tlb_addr,
12 gd->arch.tlb_addr + gd->arch.tlb_size);
13 return 0;
14 }
PGTABLE_SIZE的大小我们可以看 u-boot.dis中得到:
第一行就是 PGTABLE_SIZE 得大小。
在执行完链表以后,跳回_main中继续执行,然后执行到board_init_r(Board_r.c (common)板启动后初始化代码)。
在board_init_r同样会执行一个链表,init_sequence_r。在链表中执行各种初始化函数,然后在执行到 board_init 中,在board_init中,进行icache_enable和dcache_enable。
这两个函数都会调用 cache_enable 函数。在其中就有MMU的设置。
2.2.2 cache_enable
cache_enable
1 static void cache_enable(uint32_t cache_bit)
2 {
3 uint32_t reg;
4
5 /* The data cache is not active unless the mmu is enabled too */
6 if ((cache_bit == CR_C) && !mmu_enabled())
7 mmu_setup();
8 reg = get_cr(); /* get control reg. */
9 cp_delay();
10 set_cr(reg | cache_bit);
11 }
现在来看mmu_setup 函数
1 /* to activate the MMU we need to set up virtual memory: use 1M areas */
2 static inline void mmu_setup(void)
3 {
4 int i;
5 u32 reg;
6
7 arm_init_before_mmu(); //空函数
8 /* Set up an identity-mapping for all 4GB, rw for everyone */
9 for (i = 0; i < 4096; i++)
10 set_section_dcache(i, DCACHE_OFF); //传入DCACHE_OFF参数,建立页表地址
11
12 for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
13 dram_bank_mmu_setup(i); //设置DRAM的页表大小和地址
14 }
15
16 /* Set the access control to all-supervisor */
17 asm volatile("mcr p15, 0, %0, c3, c0, 0"
18 : : "r" (~0)); //MMU进行初始化
19
20 arm_init_domains(); //空函数
21
22 /* and enable the mmu */
23 reg = get_cr(); /* get control reg. */
24 cp_delay();
25 set_cr(reg | CR_M);
26 }
27
28 arch/arm/include/asm/system.h
29 /* Size of an MMU section */
30 enum {
31 MMU_SECTION_SHIFT = 20,
32 MMU_SECTION_SIZE = 1 << MMU_SECTION_SHIFT,
33 };
34
35 /* 页表建立函数,每一页为1M,页的起始地址为section */
36 void set_section_dcache(int section, enum dcache_option option)
37 {
38 u32 *page_table = (u32 *)gd->arch.tlb_addr; //page_table取出来,16K
39 u32 value;
40
41 value = (section << MMU_SECTION_SHIFT) | (3 << 10); //2的20次方为1M
42 value |= option;
43 page_table[section] = value;
44 }
45
46 __weak void dram_bank_mmu_setup(int bank)
47 {
48 bd_t *bd = gd->bd;
49 int i;
50
51 debug("%s: bank: %dn", __func__, bank);
52 for (i = bd->bi_dram[bank].start >> 20;
53 i < (bd->bi_dram[bank].start >> 20) + (bd->bi_dram[bank].size >> 20);
54 i++) {
55
56 set_section_dcache(i, DCACHE_WRITETHROUGH);
57 }
58 }