北京蓝海微芯S3C2410开发板启动源码中MMU初始化部分分析
void MMU_Init(MMU_Table table[])
{
//========================== IMPORTANT NOTE =========================
//The current stack and code area can't be re-mapped in this routine.
//If you want memory map mapped freely, your own sophiscated MMU
//initialization code is needed.
//===================================================================
上面部分的中文意思是:这段程序不能实现当前堆栈和代码区域的重新映射,假如你想让存储区域自由映射,需要你自己编写一个细致入微的MMU初始化代码。
MMU_DisableDCache();
MMU_DisableICache();
MMU_InvalidateDCache(); //invalidate data cache all
MMU_DCacheCleanInvalidateAll();
MMU_InvalidateICache();
#if 0
//To complete MMU_Init() fast, Icache may be turned on here.
MMU_EnableICache();
#endif
MMU_DisableMMU();
MMU_InvalidateTLB();
for(; table->vEnd; table++)
MMU_SetMTT(table->vStart, table->vEnd, table->pStart, table->attr);
MMU_SetTTBase(_MMUTT_STARTADDRESS);
MMU_SetDomain(0x55555550|DOMAIN1_ATTR|DOMAIN0_ATTR);
//DOMAIN1: no_access, DOMAIN0,2~15=client(AP is checked)
MMU_SetProcessId(0x0);
MMU_EnableAlignFault();
MMU_EnableMMU();
MMU_EnableICache();
MMU_EnableDCache(); //DCache should be turned on after MMU is turned on.
}
从上至下一条一条分析
首先执行MMU_DisableDCache();程序跳转到2410slib.s文件的函数体
MMU_DisableDCache
mrc p15,0,r0,c1,c0,0
bic r0,r0,#R1_C
mcr p15,0,r0,c1,c0,0
MOV_PC_LR
其中mrc是协处理器命令。用于读取协处理器中的寄存器的数据到ARM处理器的寄存器里面。
mrc p15,0,r0,c1,c0,0 这句话的意思应该是读协处理器中的寄存器到ARM处理器的r0里面。应该是ARM访问MMU,因为一般cp15就是MMU。
bic r0,r0,#R1_C 其中有如下定义R1_C EQU (1<<2)
则此句话是将MMU寄存器中的第2位清零,用于禁止数据cache
mcr p15,0,r0,c1,c0,0写回到协处理器MMU寄存器
MOV_PC_LR 程序返回
继续执行MMU_DisableICache();这段代码用来禁止指令cache
MMU_InvalidateDCache();无效数据缓存
顺序执行MMU_DCacheCleanInvalidateAll();这个函数如下:
void MMU_DCacheCleanInvalidateAll(void)
{
int i, j;
//If write-back is used,the DCache should be cleared.
for(i=0; i<64; i++)
for(j=0; j<8; j++)
MMU_CleanInvalidateDCacheIndex((i<<26)|(j<<5));
__asm {
mov r0, #0
mcr p15, 0, r0, c7, c10, 4 // drain WB
}
}
清除无效的数据cache内的列表,r0=index。
其中MMU_CleanInvalidateDCacheIndex函数原型如下
MMU_CleanInvalidateDCacheIndex
;r0=index
mcr p15,0,r0,c7,c14,2
MOV_PC_LR
接着执行MMU_InvalidateICache,该函数作用是无效指令cache
该函数源码如下:
MMU_InvalidateICache
mov r0, #0 ;add by hzh
mcr p15,0,r0,c7,c5,0
MOV_PC_LR
继续执行MMU_DisableMMU(); 关闭MMU
其函数原型如下:
EXPORT MMU_DisableMMU
MMU_DisableMMU
mrc p15,0,r0,c1,c0,0
bic r0,r0,#R1_M
mcr p15,0,r0,c1,c0,0
MOV_PC_LR
其中R1_M EQU (1)
看来应该是CP15协处理器(MMU)的第1位清零,则关闭了MMU继续执行MMU_InvalidateTLB();该函数的作用是无效整个TLB
其中关于TLB的理解
MMU是内存管理单元,该单元通常是属于处理器的硬件,用于从虚拟地址到物理地址的映射。在典型的二级页表内存管理的系统中,映射过程为:系统(OS)为MMU依次准备好页目录表地址,页表地质,MMU通过虚拟地址的各个段作为索引寻找到物理页面地址,与页内偏移地址一起构成最终物理地址。
从上可见,从MMU映射获得最终数据,需要访问三次内存(页目录表,页表,取数据),在分页级别更高的系统中,访问一次数据要更多的内存访问。为了提高访问速度,MMU中设置了一个叫TLB的高速缓存,存储了CPU最近访问内存的虚拟地址和物理地址。每当MMU得到一个要访问的虚拟地址时,先从TLB中检查有没有对应的项,如果有,则直接取出其物理地址,如果没有,则产生一个中断,由上述的MMU映射过程计算出物理地址,并将这一个新的(虚拟地址,物理地址)对替换掉TLB中某一项。
分页式管理可以给每个应用程序虚拟的分配4G的内存空间;程序运行时物理内存不足时可以将不常用的内存页交换到硬盘上,当需要交换到硬盘上的页时,会触发处理器的中断将硬盘上的页读到内存里。
MMU是虚地址转换到物理地址的一个机制。大部分的CPU都支持虚地址。当然也有一些没有虚地址的CPU。TLB是虚地址到物理地址映射表的一个缓存,一个高速的cache,用于快速地实现地址转换。完整的映射表在内存里面,CPU会把最近访问的映射表的一部分放到TLB里面。TLB是和CPU在一起的,所以访问速度高。TLB可以自动更新,也可以手动更新。具体要看CPU的实现。
函数原型如下:
MMU_InvalidateTLB
mov r0, #0 ;add by hzh
mcr p15,0,r0,c8,c7,0
MOV_PC_LR
继续执行
for(; table->vEnd; table++)
MMU_SetMTT(table->vStart, table->vEnd, table->pStart, table->attr);
/****虚拟存储空间到物理存储空间的映射是以内存块为单位的:分为1M/64Lb/4Kb/1kB
虚拟存储空间中的一块连续的存储空间被映射成物理存储空间中同样大小的一块连续
存储空间页表中,每一个地址变换条目实际上就记录了一个虚拟存储空间的存储块的
基地址与物理存储空间相应的一个存储块的基地址的对应关系C2:存放的是内存中页
表的基地址[31:14]--查找页表 而C2寄存器的位[31:14]和虚拟地址的位[31:20]
(页表内的序号)结合作为一个32位数的高30位作为合成建立的页表中相应的地址转换
条目的地址,低2位为00
此设置详细查看 <ARM结构和编程>的page 193,基于段的****/
MMU_SetMTT函数原型如下:
void MMU_SetMTT(U32 vaddrStart, U32 vaddrEnd, U32 paddrStart, int attr)
{
U32 *pTT;
int i, nSec;
pTT = (U32 *)_MMUTT_STARTADDRESS+(vaddrStart>>20);
nSec = (vaddrEnd>>20)-(vaddrStart>>20);
for(i=0; i<=nSec; i++)
*pTT++ = attr|(((paddrStart>>20)+i)<<20);
}
设置页表函数
vaddrStart:虚拟起始地址
vaddrEnd:虚拟结束地址
paddrStart:物理起始地址
attr:访问属性
void MMU_SetMTT(int vaddrStart,int vaddrEnd,int paddrStart,int attr)
{
volatile U32 *pTT; 定义了页表的指针
volatile int i,nSec;
pTT=(U32 *)_MMUTT_STARTADDRESS+(vaddrStart>>20); //由于内存块是1M的,写
//页表的基地址 这里使用一级描述符,段映射(1M)
MMUTT_STARTADDRESS 这是转换表开始地址
(vaddrStart>>20); 虚拟地址开始地址在转换表中的偏移地址pTT 段描述符的地址
//简单讲一下段映射 比如 比如0x80000000 映射到0x30000000
//直接可以通过
//*(mmu_tlb_base+(virtuladdr>>20))=(physicaladdr&0xfff00000) | MMU_SECDESC_WB
//其中 MMU_SECDESC_WB设置段的访问权限 。
//mmu_tlb_base代表一级页表地址,将它写入协处理器CP15的寄存器C2(称为页表基址寄存器)
//当虚拟地址 0x80000001映射到30000001的过程如下
//根据页表基址位[31:14]和 虚拟地址[31:20]组成一个低2位 为0 的32位地址(0x30000800),根据这个地址找到段描述符(0x30000000 | MMU_SECDESC_WB)
//取出段描述符的位[31:20]即段基址,和虚拟地址[19:0]组成一个32位的物理地址。
nSec=(vaddrEnd>>20)-(vaddrStart>>20); // nSec:段大小
for(i=0;i<=nSec;i++)*pTT++=attr |(((paddrStart>>20)+i)<<20);
//页表存储访问信息和存储块的基地址
//(((paddrStart>>20)+i)<<20) :对应的物理内存页的地址
// attr:访问权限和缓冲属性
这些都是些地址映射方面的代码。
唉。先搞个大概的流程吧。要把这些都弄懂,看来得好好看看ARM体系结构了。
杜春雷的书这回该派上用场了。
接下来是MMU_SetTTBase(_MMUTT_STARTADDRESS);
;//C2用于保存页表的在内存中的基地址
;//页表中每一行对应一个虚地址页对应的实地址页的地址、该位的方位权限和该页的缓冲特性
;//通常把部分页表放在页表缓冲器TLB(translation lookaside buffer)中,换页表时,TLB要清空,因为。。。。
;//C8控制清空TLB
;//C10用于控制TLB中内容的锁定
;=========================
; Set TTBase
;=========================
;void MMU_SetTTBase(int base)
EXPORT MMU_SetTTBase
MMU_SetTTBase
;ro=TTBase
mcr p15,0,r0,c2,c0,0
MOV_PC_LR
顺序执行MMU_SetDomain(0x55555550|DOMAIN1_ATTR|DOMAIN0_ATTR);
;//MMU将整个存储空间分成16个域(domain),每个域具有相同的访问属性
;//C3用于设置域的属性
;=========================
; Set Domain
;=========================
;void MMU_SetDomain(int domain)
EXPORT MMU_SetDomain
MMU_SetDomain
;ro=domain
mcr p15,0,r0,c3,c0,0
MOV_PC_LR
接下来
MMU_SetProcessId(0x0); //快速上下文切换,禁止 MVA="VA" 《ARM结构和编程》 page 220
MMU_EnableAlignFault(); //是否开启地址对齐检查功能 《ARM结构和编程》page 222
MMU_EnableMMU();
MMU_EnableICache();
MMU_EnableDCache(); //DCache should be turned on after MMU is turned on.
然后先后时能了MMU,指令缓存,数据缓存。
其中数据缓存必须在MMU打开之后打开