野火论坛

 找回密码
 注册

扫一扫,访问微社区

查看: 94|回复: 2

[经验\资料分享] stm32之OK6410mmu初始化

[复制链接]
发表于 2018-4-16 16:42:06 | 显示全部楼层 |阅读模式
1.mmu的功能

mmu中文名内存管理单元,主要功能是实现虚拟地址到物理地址的转换,同时为内存保护提供硬件上的保证。这里提到了3个概念:虚拟地址、物理地址和内存保护。首先看看什么是虚拟地址(VA)和物理地址(PA)?物理地址是存储单元对应的实际地址,而虚拟地址顾名思义就是软件程序能表达的非物理的实体地址。没有启用mmu之前,访问的地址都是物理地址,我们访问0地址,这么写(int *)0x0 ,启用mmu将物虚拟地址0x5000_0000映射到物理地址0x0,则访问0地址可以这样写(int *)0x5000_0000。那么这么做有什么意义呢?因为程序是运行在固定的地址空间上,那么同一个程序运行两次的话,必然会造成地址空间冲突,这个工作就由mmu来做。我们打开qq程序,调用的都是同一个地址(虚拟地址),而第二次运行时却被定为到另一个物理地址,避免地址冲突,也就是mmu能对虚拟地址进行重映射。多个程序正常运行在不同的进程空间,然后发生异常后就有可能影响其他进程空间,这时候就需要一种硬件上的内存保护机制,mmu实现进程空间的隔离。

2.虚拟地址到物理地址的转换过程

怎么实现一个映射关系呢?如果是个数学题我想你会这么做,VA=f(PA)。在ARM平台我们定义了一张表来描述VA与PA 的映射关系,也就是页表。   

这里只介绍段页表(1M的粒度)。用一个32位的数据来描述页表,这就是页表描述符。程序中定义了gen_pte函数来生成一个页表描述符,base为段地址,如内存的起始段地址为0x500,左移20位(增加5个0)得到0x50000000,d为域设置位,c为cache设置位,b为write buffer设置位。


unsigned int gen_pte(unsigned int base,unsigned int ap,unsigned int d,unsigned int c,unsigned int b)
{
    return ((base<<20)|(ap<<10)|(d<<5)|(1<<4)|(c<<3)|(b<<2)|(1<<1));
}


4G的虚拟地址空间理论上可以分为4G/1M=2^10=4096个段,要查找某个虚拟地址属于哪个段,只需将虚拟地址右移20位。比如要找0x5000_0000的所属段,0x5000_0000/1M=0x5000_0000/2^10=0x500,这里就得到个有用的信息:虚拟地址的前12位表示的是段基址。而后20位刚好可以描述1M的内存。


3.创建段页表

这里得解决两个问题。一、段页表存放在哪里。二、段页表的构建。

首先段页表存在内存空间,放在内存哪里呢?程序员来指定存放地址。这里段页表存放的起始地址为0x5070_0000。映射页表有两段:从0地址开始的2.5G地址空间和内存区(0x5000_0000开始的256M地址空间)。好,存放页表的起始地址有了,那么具体怎么存放呢?当然是起始地址+偏移地址。

可以这样做,分三步:

3.1 用gen_pte函数生成一个页表项。

3.2 找到页表项存放的地址,就是页表起始地址+偏移地址。

偏移地址怎么计算呢?这里定义了一个宏#define PTE_INDEX(va) ((va<<20)>>18)

首先得清楚,偏移地址是根据虚拟地址得到的。将虚拟地址的段地址如0x500左移20位,得到0x5000_0000,再右移18位得到偏移量0x1400。也就是说段地址左移2位(或者说乘以4)就得到了偏移地址。页表起始地址与偏移地址与运算就可得到页表项存放地址。这里定义了一个函数生成页表项存放地址

unsigned int gen_pte_addr(unsigned int pte_base,unsigned int va)
{
    return ((pte_base&0xffffc000)|PTE_INDEX(va));
}

3.3 将页表项存进页表项地址

*(volatile unsigned int *)pte_addr = pte;

4.把要映射的地址空间进行mmu初始化

void init_mmu(void)
{
    unsigned int pte;
    unsigned int pte_addr;
    int j;
    for(j=0;j<0xa00;j++)  //0xa00表示2.5G的地址空间
    {
        pte = gen_pte(0x0+j,3,0,0,0);  
        pte_addr = gen_pte_addr(0x50700000,0x0+j);
        *(volatile unsigned int *)pte_addr = pte;
    }
    for(j=0;j<0x100;j++)    //0x100即256,内存为256M,一个页表项表示1M的空间故循环256次
    {
        pte = gen_pte(0x500+j,3,0,1,1);
        pte_addr = gen_pte_addr(0x50700000,0x500+j);
        *(volatile unsigned int *)pte_addr = pte;
    }
}
5.启动mmu

void start_mmu(void)
{
    unsigned int ttb = 0x50700000;  //页表起始地址
    asm(
        "mcr p15,0,%0,c2,c0,0\n"   //通过cp15协处理器,写入页表起始地址到c2
        "ldr r0,=0x0000ffff\n"     
        "mcr p15,0,r0,c3,c0,0\n"    //通过c3进行域设置
        "mov r0,#1\n"
        "mcr p15,0,r0,c1,c0,0\n"     //设置c1开启mmu
        "mov r0,r0\n"
        "mov r0,r0\n"
        "mov r0,r0\n"
        "mov r0,r0\n"
        :
        : "r"(ttb)
        : "r0"
    );
}


下面是具体的代码实现:


[plain] view plain copy


  • #define rGPMCON ((volatile unsigned long *)0x7f008820)  
  • #define rGPMDAT ((volatile unsigned long *)0x7f008824)  
  • #define PTE_INDEX(va)  ((va<<20)>>18)  
  • void light_led(void);  
  • unsigned int gen_pte(unsigned int base,unsigned int ap,unsigned int d,unsigned int c,unsigned int b);  
  • unsigned int gen_pte_addr(unsigned int pte_base,unsigned int va);  
  •   
  • void init_mmu(void);  
  • void start_mmu(void);  
  • int gboot_main()  
  • {  
  •     init_mmu();   
  •     start_mmu();  
  •     light_led();  
  •     return 0;  
  • }  
  • void light_led(void)  
  • {  
  •     *rGPMCON = 0x1111;  
  •     *rGPMDAT = 0xe;  
  • }  
  • //鐢熸垚涓
回复

使用道具 举报

发表于 2018-4-16 16:59:56 | 显示全部楼层
帮顶         
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-4-17 09:46:47 | 显示全部楼层
                                            
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|野火论坛 ( 粤ICP备14069197号-2

GMT+8, 2018-4-27 04:54 , Processed in 0.053916 second(s), 21 queries , Gzip On.

野火电子论坛

© 2014-2016 www.firebbs.cn

快速回复 返回顶部 返回列表