mmu工作原理【3】代码实战

环境

1.硬件平台:s3c2440 (arm920t核)

2.软件:裸机代码。目前已经支持sdram,nandflash,norflash,lcd与触摸屏校准。

s3c2440 MMU相关知识(datesheet)

       首先了解下cpu的内部构成。我们关于mmu的基本操作都是协处理器CP15。经过指令mmu或者数据mmu将物理地址发送给AMBA总线。关于icache和dcache的操作都是在R13协处理器(可以enable cache前后比较代码执行速度)。

        cpu发出一个虚拟地址后,首先要转换为MVA(Modified virtual address)。根据每个进程的PID转换为MVA,因为每个进程的虚拟地址都是一样的,两个进程虚拟地址必然有重叠。此时根据PID转换成MVA,这样减少进程上下文的代价,不用再映射页表等等。转换如下图:通过读取CP15的C13寄存器获取PID,根据PID进行转换。

下面重点关注下cp15协处理器:

       看下图,首先从TTB中获取页基址值,然后根据MVA找到一级页表(4096个entry)

       a.如果是段描述符,直接返回物理地址,转换结束。每个页框大小为1M

       b.如果是二级页表描述符,继续利用虚拟地址找一个entry。

       c.如果是第二个条目是页描述符,返回物理地址。

那么问题来了,如何区分是页表描述符,页描述符,段描述符呢?来看下图:

1.主要是靠bit[1:0]来区分是那种模式。

0b00:无效

0b01:粗页表。可以表示一个二级页表。看上图coarse page table base,指示256个entry。每页entry指向4k页框。

0b10: 段描述符。看下图,首先ttb的段基址是bit[20:31](2^12 = 4096个entry),bit[0:19]填充0,就可以表示1M的页框的起始地                址。根据页表基地址和MVA的table index组成一个30位地址(低2位0),然后找到一个段描述符(entry)。然后取出根据           这个段描述符和偏移地址就可以找到物理地址了。

0b11:细页表。也是二级页表。里面有1024个entry。

2.内存访问权限。

AP(access permission):权限位。决定如何对权限位进行检查。

Domain:域。决定是否对某块内存进行检查。

C:指令cache。

B: write buffer。data cache和write buffer。data cache里面数据更改后,需要write buffer回写。

代码实战

1.进行地址映射。包含sram,sdram,IO regs,framebuffer。设置是否开dcache和icache。此次简单,我们就建立一级页表。

页基址放在sdram的0x32000000。为了代码简单,我们只更改了链接地址va(0x0b000000)到pa(0x30000000)的转换,别的都是va == pa.

#define MMU_SECDESC_AP      (3<<10) //0x3,权限
#define MMU_SECDESC_DOMAIN  (0<<5)
#define MMU_SECDESC_NCNB    (0<<2)  //cache and buffer
#define MMU_SECDESC_WB      (3<<2) 
#define MMU_SECDESC_TYPE    ((1<<4)|(1<<1))
#define MMU_SECDESC_FOR_IO  (MMU_SECDESC_AP|MMU_SECDESC_DOMAIN|MMU_SECDESC_NCNB|MMU_SECDESC_TYPE)  
#define MMU_SECDESC_FOR_MEM (MMU_SECDESC_AP|MMU_SECDESC_DOMAIN|MMU_SECDESC_WB|MMU_SECDESC_TYPE)  

#define IO  1
#define MEM 0
void create_secdesc(unsigned int * ttb,unsigned int va,unsigned int pa,int io)
{
	int index;
	index = va /0x100000;
	if(io) {
		ttb[index] = (pa&0xfff00000)|MMU_SECDESC_FOR_IO;
	}else {
		ttb[index] = (pa&0xfff00000)|MMU_SECDESC_FOR_MEM;
	}
}
//创建一级页表
// VA ------------PA----------------CB
// 0              0                11
// 0x40000000     0x40000000       11
// 64M sdram:
// 0x30000000     0x30000000       11
// ---------      ----------
// 0x33f00000     0x33f00000     
// register:
// 0x48000000     0x48000000       00
//-----------    -----------       --
// 0x5B00001C     0x5B00001C       --
// Framebuffer:
// 0x33c00000     0x33c00000       00
// 链接地址       
// 0xB0000000     0x30000000       11
void create_page_table(void) 
{
	unsigned int va,pa;
	/*
	1.页表位置在哪?0x32000000
	2.页表多大? 4g/1M = 4096,4K*4byte = 16kb
	*/
	unsigned int * ttb = (unsigned int*)0x32000000;
	//根据VA,PA设置页表entry
	//for sram,norflash
	create_secdesc(ttb,0,0,IO);
	//for sram when nor boot
	//for sram when nor boot
	create_secdesc(ttb,0x40000000,0x40000000,MEM);

	//for 64M sdram when nor boot
	va = 0x30000000;
	pa = 0x30000000;
	for (; va < 0x34000000;)
	{
		create_secdesc(ttb,va,pa,MEM);
		va += 0x100000;
		pa += 0x100000;
	}
	//for register: 0x48000000~0x5b00001c
	va = 0x48000000;
	pa = 0x48000000;
	for (; va <= 0x5B000000;)
	{
		create_secdesc(ttb,va,pa,IO);
		va += 0x100000;
		pa += 0x100000;
	}
	//for framebuffer
	create_secdesc(ttb,0x33c00000,0x33c00000,IO);
	//link address 
	create_secdesc(ttb,0xB0000000,0x30000000,MEM);	
}

2.更改链接地址。lds

SECTIONS {
	. = 0xB0000000; //不启动mmu时是物理地址0x30000000
	__code_start = .;
	. = ALIGN(4);
	.text      :
	{
	  *(.text)
	}

	. = ALIGN(4);
	.rodata : { *(.rodata) }

	. = ALIGN(4);
	.data : { *(.data) }

	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) *(.COMMON) }
	__end = .;
}

3.更改start.s(类似于uboot),开始实现了:

设置中断向量表,关看门狗,设置时钟(fclk,hclk,pclk),sdram初始化的位置无关码。然后就实现使能mmu。

	bl sdram_init         //初始化sdram
	bl create_page_table //创建页表
	bl mmu_enable 	     //启动mmu
	bl copy2sdram        //代码重定位

mmu_enable:
	//把页表基地址告诉cp15(也就是页表所存放的地址)
	ldr r0, =0x32000000
	mcr p15,0,r0,c2,c0,0 //mcr 将r0的值写入cp15协处理器
	
	//设置域为0xffffffff,不进行权限检查
	ldr r0, = 0xffffffff
	mcr p15, 0, r0, c3, c0, 0 
	
	//使能icache,dcache,mmu
	mrc p15, 0, r0, c1, c0, 0 //读cp15到r0
	orr r0, r0, #(1<<12)      //bit 12:enable icache
	orr r0, r0, #(1<<2)       //bit 2:enable dcache
	orr r0, r0, #(1<<0)       //bit 0:enable mmu
	mcr p15, 0, r0, c1, c0, 0  //写r0到cp15
	
	mov pc,lr //返回

       最后make完成后,看反汇编文件。运行地址已经是从0xb000000开始了。此时cpu再从0xb0000000开始执行时候,就是虚拟地址了,sdram真正的物理地址是0x30000000.此时就需要0xb0000000--->0x30000000之间的mmu转换了。

Disassembly of section .text:

b0000000 <_start>:
b0000000:	ea000032 	b	b00000d0 <reset>
b0000004:	e59ff014 	ldr	pc, [pc, #20]	; b0000020 <und_addr>
b0000008:	e59ff014 	ldr	pc, [pc, #20]	; b0000024 <swi_addr>
b000000c:	ea000065 	b	b00001a8 <halt>
b0000010:	ea000064 	b	b00001a8 <halt>
b0000014:	ea000063 	b	b00001a8 <halt>
b0000018:	e59ff008 	ldr	pc, [pc, #8]	; b0000028 <irq_addr>
b000001c:	ea000061 	b	b00001a8 <halt>

烧录到nandflash,并从nandflash启动。发现开了icache和dache,运行速度不止提高一点。

此代码整体映射流程图如下所示:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现了对ARM920TMMU的启用 地址转换过程 1. 从CP15寄存器C2得到一级页表的基地址 2. 将虚拟地址[31:20]作为页表的索引,得到页表该虚拟地址的描述符。 3. 判断该描述符是否为段描述符,如为段描述符,将该描述符[31:20]和虚拟地址[19:0]作为偏移量组成一个32位的物理地址进行访问。 4. 如为粗页表描述符,则将该粗页表描述符[31:10]作为二级页表的基地址,并将虚拟地址[19:12]位作为索引得到在二级页表该虚拟地址的描述符。 判断二级页表符的类型 ① 为极大页描述符表将该描述符[31:16]作为基地和虚拟地址[15:0]作为偏移量得到该虚拟地址的32位物理地址进行访问。 ② 为小页描述符表将描述符[31:12]作为基地和虚拟地址[11:0]作为偏移量得到该虚拟地址的32位物理地址进行访问。 5. 如为细页表描述符,将该组页表描述符[31:12]作为二级页表的基地址,并将虚拟地址[19:10]位作为索引得到在二级页表该虚拟地址的描述符。 判断二级页表符的类型 ① 为大页描述符表将该描述符[31:16]作为基地和虚拟地址[15:0]作为偏移量得到该虚拟地址的32位物理地址进行访问。 ②为小页描述符表将该描述符[31:12]作为基地和虚拟地址[11:0]作为偏移量得到该虚拟地址的32位物理地址进行访问。 ③为小极页描述符表将描述符[31:10]作为基地和虚拟地址[9:0]作为偏移量得到该虚拟地址的32位物理地址进行访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值