环境
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,运行速度不止提高一点。
此代码整体映射流程图如下所示: