SylixOS armv8 mmu

armv8 mmu 支持4K,16K,64K分页,在SylixOS目前实现了4K和6K分页。根据查看代码SylixOS目前使用的是Non-secure EL1、stage 1 translation、VA和PA的地址宽度都是48个bit。所以分析代码不包含arm提供的其他功能。

代码位于SylixOS/arch/arm64/mm/mmu 文件夹。

SylixOS armv8 mmu_第1张图片

在SylixOS封装了mmu操作函数集,这样虽然不同体系结构只需要实现对应的函数功能,不影响内核。

SylixOS armv8 mmu_第2张图片

arm64MmuMemInit函数是初始化,为pgd,pmd,pts,pte分配空间。在4K模式下使用四级分页。

/*********************************************************************************************************
** 函数名称: arm64MmuMemInit
** 功能描述: 初始化 MMU 页表内存区
** 输 入  : pmmuctx        mmu 上下文
** 输 出  : ERROR or OK
** 全局变量: 
** 调用模块: 
** 注  意  : ARM 体系结构要求: 一级页表基地址需要保持 16 KByte 对齐, 单条目映射 1 MByte 空间.
                               二级页表基地址需要保持  1 KByte 对齐, 单条目映射 4 KByte 空间.
*********************************************************************************************************/
static INT  arm64MmuMemInit (PLW_MMU_CONTEXT  pmmuctx)
{
#define PGD_BLOCK_SIZE  (4  * LW_CFG_KB_SIZE)
#define PMD_BLOCK_SIZE  (4  * LW_CFG_KB_SIZE)
#define PTS_BLOCK_SIZE  (4  * LW_CFG_KB_SIZE)
#define PTE_BLOCK_SIZE  (4  * LW_CFG_KB_SIZE)

    PVOID  pvPgdTable;
    PVOID  pvPmdTable;
    PVOID  pvPtsTable;
    PVOID  pvPteTable;
    
    ULONG  ulPgdNum = bspMmuPgdMaxNum();
    ULONG  ulPmdNum = bspMmuPmdMaxNum();
    ULONG  ulPtsNum = bspMmuPtsMaxNum();
    ULONG  ulPteNum = bspMmuPteMaxNum();
    
    pvPgdTable = __KHEAP_ALLOC_ALIGN((size_t)ulPgdNum * PGD_BLOCK_SIZE, PGD_BLOCK_SIZE);
    pvPmdTable = __KHEAP_ALLOC_ALIGN((size_t)ulPmdNum * PMD_BLOCK_SIZE, PMD_BLOCK_SIZE);
    pvPtsTable = __KHEAP_ALLOC_ALIGN((size_t)ulPtsNum * PTS_BLOCK_SIZE, PTS_BLOCK_SIZE);
    pvPteTable = __KHEAP_ALLOC_ALIGN((size_t)ulPteNum * PTE_BLOCK_SIZE, PTE_BLOCK_SIZE);
    
    if (!pvPgdTable || !pvPmdTable || !pvPtsTable || !pvPteTable) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "can not allocate page table.\r\n");
        return  (PX_ERROR);
    }
    
    _G_hPGDPartition = API_PartitionCreate("pgd_pool", pvPgdTable, ulPgdNum, PGD_BLOCK_SIZE,
                                           LW_OPTION_OBJECT_GLOBAL, LW_NULL);
    _G_hPMDPartition = API_PartitionCreate("pmd_pool", pvPmdTable, ulPmdNum, PMD_BLOCK_SIZE,
                                           LW_OPTION_OBJECT_GLOBAL, LW_NULL);
    _G_hPTSPartition = API_PartitionCreate("pts_pool", pvPtsTable, ulPtsNum, PTS_BLOCK_SIZE,
                                           LW_OPTION_OBJECT_GLOBAL, LW_NULL);
    _G_hPTEPartition = API_PartitionCreate("pte_pool", pvPteTable, ulPteNum, PTE_BLOCK_SIZE,
                                           LW_OPTION_OBJECT_GLOBAL, LW_NULL);
                                           
    if (!_G_hPGDPartition || !_G_hPMDPartition || !_G_hPTSPartition || !_G_hPTEPartition) {
        _DebugHandle(__ERRORMESSAGE_LEVEL, "can not allocate page pool.\r\n");
        return  (PX_ERROR);
    }
    
    return  (ERROR_NONE);
}

可看到首先重bsp函数中获得需要分配的pgd,pmd,pte,pts数量。根据不同的硬件可以指定当前使用的数量,但是都是以4K空间为单位。在armv8A手册中规定了采用4级后各使用地址位数。

SylixOS armv8 mmu_第3张图片

上图是手册中VA48位时4K分页传输时的结构。在armv8 可以指定地址线位数,SylixOS目前使用的是虚拟地址48位。

SylixOS armv8 mmu_第4张图片

 上图来自蜗窝科技 的ARM64的启动过程之(二),很好的解释了虚拟地址的翻译过程。

在4k分页对应关系是下表

SylixOS armv8 mmu_第5张图片

地址翻译过程如下图:

 SylixOS armv8 mmu_第6张图片

 在arm64MmuMemInit函数中创建了pgd,pmd,pts,pte的池,这个池就是为四级提前在堆栈中分配的空间。每使用一个entry就是从池中分配出来。在arm64中每个entry是8个字节。

arm64MmuGlobalInit

主要是做了与硬件相关的初始化。

/*********************************************************************************************************
** 函数名称: arm64MmuGlobalInit
** 功能描述: 调用 BSP 对 MMU 初始化
** 输 入  : pcMachineName     使用的机器名称
** 输 出  : ERROR or OK
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static INT  arm64MmuGlobalInit (CPCHAR  pcMachineName)
{
    archCacheReset(pcMachineName);

    arm64MmuInvalidateTLB();

    arm64MmuSetTCR(0x580823510);                                        /*  T0SZ = 2 ^ 48               */

    arm64MmuSetMAIR();

    return  (ERROR_NONE);
}

archCacheRest函数是无效cache清除cache。实现调用汇编参考SylixOS armv8启动分析

arm64MmuInvalidateTlB函数是无效mmu的TLB表。在mmu中存在一个类似于cache的快表。


FUNC_DEF(arm64MmuInvalidateTLB)                                         ;/* 快表中的所有项无效           */
    TLBI    VMALLE1IS                                                   ;/* TLBIAll, EL1, IS            */
    ARM_DSB()
    ARM_ISB()
    RET
    FUNC_END()

 TLBI    VMALLE1IS 指令在armv8手册中定义如图。ARM_DSB(),ARM_ISB()是arm dsb,isb指令,是指令屏障,保证前面指令执行完,后面执行。 

SylixOS armv8 mmu_第7张图片

 arm64MmuSetTCR函数是直接将值赋值给TCR_EL1寄存器。TCR_ELx 是mmu控制相关的寄存器。

FUNC_DEF(arm64MmuSetTCR)
    MSR     TCR_EL1 , X0
    RET
    FUNC_END()

TCR控制寄存器包含很多系统相关的内容:

T0SZ, bits [5:0]定义了虚拟地址的宽度。

EPD0 控制当虚拟地址使用TLB块表查找错误时,是否使用TTBR0_EL1 进行查找。Translation table walk 指的是从虚拟地址查找到物理地址的过程。TTBR0_EL1 存放的的pgd的基地址。当从TLB查找失败时需要使用pgd->pmd->pts->pts查找过程。

SylixOS armv8 mmu_第8张图片

 

 IRGN1, bits [25:24]和IRGN0, bits [9:8]用来控制页表所在memory的inner cachebility attribute的。

ORGN1, bits [27:26]和ORGN0, bits [11:10]用来控制页表所在memory的outercachebility attribute的。

SH1, bits [29:28]和SH0, bits [13:12]是用来控制页表所在memory的Shareability attribute。

TG1,bits [31:30]和TG0,bits [15:14]是用来控制page size的,可以是4K,16K或者64K。

TBI1,bit[38]和TBI0,bit[37]用来控制是否忽略地址的高8位(TBI就是Top Byte ignored的意思),如果允许忽略地址的高8位,那么MMU的硬件在进行地址比对,匹配的时候忽略高八位,这样软件可以自由的使用这个byte,例如对于一个指向动态分配内存的对象指针,可以通过高8位来表示reference counter,从而可以跟踪其使用情况,reference count等于0的时候,可以释放内存。AS bit[36]用来定义ASID(address space ID)的size,A1, bit [22]用来控制是kernel space还是user space使用ASID。ASID是和TLB操作相关,一般而言,地址翻译的时候并不是直接查找页表,而是先看TLB是否命中,具体判断的标准是虚拟地址+ASID,ASID是每一个进程分配一个,标识自己的进程地址空间。这样在切换进程的时候不需要flush TLB,从而有助于performance。 这里参考了ARM64的启动过程之(三):为打开MMU而进行的CPU初始化。

在SylixOS中在TCR寄存器设置的值为0x580823510 所以bit[38]和bit[37]都为0,不忽略高8位。控制选择4K,虚拟地址长度是48位。IPS设置为48bit,IPS(Intermediate Physical Address Size)查资料这里和虚拟化有关,具体还没搞懂。

最后是arm64MmuSetMAIR函数,MAIR_EL1是内存属性的寄存器

;/*********************************************************************************************************
;  设置域属性
;*********************************************************************************************************/

#define MAIR(attr, mt)      ((attr) << ((mt) * 8))
#define MT_DEVICE_nGnRnE    0
#define MT_DEVICE_nGnRE     1
#define MT_DEVICE_nGRE      2
#define MT_DEVICE_GRE       3
#define MT_NORMAL_NC        4
#define MT_NORMAL           5
#define MT_NORMAL_WT        6

FUNC_DEF(arm64MmuSetMAIR)
    LDR     X0 , =MAIR(0x00, MT_DEVICE_nGnRnE) | \
                  MAIR(0x04, MT_DEVICE_nGnRE)  | \
                  MAIR(0x08, MT_DEVICE_nGRE)   | \
                  MAIR(0x0c, MT_DEVICE_GRE)    | \
                  MAIR(0x44, MT_NORMAL_NC)     | \
                  MAIR(0xff, MT_NORMAL)        | \
                  MAIR(0xbb, MT_NORMAL_WT)
    MSR     MAIR_EL1 , X0
    RET
    FUNC_END()

 下面在armv8A手册中对MAIR_EL寄存的介绍

 

SylixOS armv8 mmu_第9张图片

根据arm官方介绍内存分为两种类型,分别是device和normal,也就是设备使用的内存和正常的内存。在arm中将寄存器操作映射到内存当中这部分内存,由于对寄存器读些映射关系呗称为设备内存。

 “我们知道,ARMv8采用了weakly-order内存模型,也就是说,通俗的讲就是处理器实际对内存访问(load and store)的执行序列和program order不一定保持严格的一致,处理器可以对内存访问进行reorder。例如:对于写操作,processor可能会合并两个写的请求。处理器这么任性当然是从性能考虑,不过这大大加大了软件的复杂度(软件工程师需要理解各种memory barrier操作,例如ISB/DSB/DMB,以便控制自己程序的内存访问的order)。

地址空间那么大,是否都任由processor胡作非为呢?当然不是,例如对于外设的IO地址,处理必须要保持其order。因此memory被分成两个基本的类型:normal memory和devicememory。除了基本的memory type,还有memory attribute(例如:cacheability,shareability)来进一步进行描述,我们在下一节描述。

标识为normal memory type的memory就是我们常说的内存而已,对其访问没有副作用(side effect),也就是说第n次和第n+1次访问没有什么差别。device memory就不会这样,对一些状态寄存器有可能会read clear,因此n和n+1的内存访问结果是不一样的。正因为如此,processor可以对这些内存操作进行reorder、repeat或者merge。我们可以把程序代码和数据所在的memory设定为normal memory type,这样可以获取更高的性能。例如,在代码执行过程中,processor可能进行分支预测,从而提前加载某些代码进入pipeline(而实际上,program不一定会fetch那些指令),如果设定了不正确的memory type,那么会阻止processor进行reorder的动作,从而阻止了分支预测,进而影响性能。

对于那些外设使用的IO memory,对其的访问是有side effect的,很简单的例子就是设备的FIFO,其地址是固定不变的,但是每次访问,内部的移位寄存器就会将下一个数据移出来,因此每次访问同一个地址实际上返回的数据是不一样的。device不存在cache的设定,总是no cache的,处理器访问device memory的时候,限制会比普通memory多,例如不能进行Speculative data accesses(所谓不能进行Speculative data accesses就是说cpu对memory的访问必须由顺序执行的执行产生,不能由于自己想加快性能而投机的,提前进行某些数据访问)。” ---引用自蜗窝科技

在device和normal内存中又进行了细分,在MAIR寄存器被分为了8组,每组8位,分别代表了不同属性的内存。看手册中MAIR寄存器的定义

SylixOS armv8 mmu_第10张图片

SylixOS armv8 mmu_第11张图片

 可以看到每一组中当4-7bit是0时为设备内存。

以下来自蜗窝科技:“

对于device type,其总是non cacheable的,而且是outer shareable,因此它的attribute不多,主要有下面几种附加的特性:

(1)Gathering 或者non Gathering (G or nG)。这个特性表示对多个memory的访问是否可以合并,如果是nG,表示处理器必须严格按照代码中内存访问来进行,不能把两次访问合并成一次。例如:代码中有2次对同样的一个地址的读访问,那么处理器必须严格进行两次read transaction。

(2)Re-ordering (R or nR)。这个特性用来表示是否允许处理器对内存访问指令进行重排。nR表示必须严格执行program order。

(3)Early Write Acknowledgement (E or nE)。PE访问memory是有问有答的(更专业的术语叫做transaction),对于write而言,PE需要write ack操作以便确定完成一个write transaction。为了加快写的速度,系统的中间环节可能会设定一些write buffer。nE表示写操作的ack必须来自最终的目的地而不是中间的write buffer。

对于normal memory,可以是non-cacheable的,也可以是cacheable的,这样就需要进一步了解Cacheable和shareable atrribute,具体如下:

(1)是否cacheable

(2)write through or write back

(3)Read allocate or write allocate

(4)transient or non-transient cache

最后一点要说明的是由于cache hierararchy的存在,memory的属性可以针对inner和outer cache分别设定,具体如何区分inner和outer cache是和具体实现相关,但通俗的讲,build in在processor内的cache是inner的,而outer cache是processor通过bus访问的。NC是no cache,也就是说MT_NORMAL_NC的memory是normal memory,但是对于这种类型的memory的访问不需要通过cache系统”

write through or write back解释如下(来自链接):

  Write Back:Cache Line中的数据被CPU核修改时并不立刻写回内存,Cache Line和内存中的数据会暂时不一致,在Cache Line中有一个Dirty位标记这一情况。当一条Cache Line要被其它VA的数据替换时,如果不是Dirty的就直接替换掉,如果是Dirty的就先写回内存再替换。

   Write Through:每当CPU核修改Cache Line中的数据时就立刻写回内存,Cache Line和内存中的数据总是一致的。如果有多个CPU或设备同时访问内存,例如采用双口RAM,那么Cache中的数据和内存保持一致就非常重要了,这时相关的内存页面通常配置为Write Through模式

Read allocate or write allocate 解释(来自链接):

在有cache的单机系统中,通常有两种写策略:write through和write back。这两种写策略都是针对写命中(write hit)情况而言的:write through是既写cache也写main memory;write back是只写cache,并使用dirty标志位记录cache的修改,直到被修改的cache 块被替换时,才把修改的内容写回main memory。那么在写失效(write miss)时,即所要写的地址不在cache中,该怎么办呢?一种办法就是把要写的内容直接写回main memory,这种办法叫做no write allocate policy;另一种办法就是把要写的地址所在的块先从main memory调入cache中,然后写cache,这种办法叫做write allocate policy
 

可以看到SylixOS给MAIR_EL1中七个条目设置属性。

第一个是MT_DEVICE_nGnRnE 代表是设备内存,必须保证严格按照代码中的访问顺序来,不允许合并对内存的访问,不允许对指令重排,写操作的ack必须来自最终的目的地而不是中间的write buffer。

第二个是MT_DEVICE_nGnRE 必须保证严格按照代码中的访问顺序来,不允许合并对内存的访问,不允许对指令重排,但是写操作的ack不必须来自最终的目的地而不是中间的write buffer。

第三个 MT_DEVICE_nGRE是不能对内存访问合并,但是指令可以重排,ack不必须来自最终的目的地。

第四个MT_DEVICE_GRE 是对内存访问可以合并,指令可以重排,ack不必须来自最终的目的地。

第五个 MT_NORMAL_NC是不带cache的内存。

第六个MT_NORMAL 是正常的内存。

第七个MT_NORMAL_WT 是带 write Through的。

MAIR寄存器含有8组,最后使用时在描述符属性里的AttrIndx[2:0]里指定使用MAIR寄存器中那一组属性。

SylixOS armv8 mmu_第12张图片

SylixOS armv8 mmu_第13张图片上图是armv8手册中定义属性位置, 但是SylixOS有一层自己封装的属性,在最后pte和物理地址绑定时将SylixOS属性转化为上图所示的属性。

arm64MmuPgdAlloc

此函数是分配一个pgd 基地址。也就是从pgd池中分配出一个表的首地址。然后将表清空。pgd比较特殊因为它是第一个,所以没有去把地址填入过程。并且直接返回相应的entry。

/*********************************************************************************************************
** 函数名称: arm64MmuPgdAlloc
** 功能描述: 分配 PGD 项
** 输 入  : pmmuctx        mmu 上下文
**           ulAddr         虚拟地址 (参数 0 即偏移量为 0 , 需要返回页表基地址)
** 输 出  : 分配 PGD 地址
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static LW_PGD_TRANSENTRY *arm64MmuPgdAlloc (PLW_MMU_CONTEXT  pmmuctx, addr_t  ulAddr)
{
    REGISTER LW_PGD_TRANSENTRY  *p_pgdentry;
    REGISTER ULONG               ulPgdNum;
    
    p_pgdentry = (LW_PGD_TRANSENTRY *)API_PartitionGet(_G_hPGDPartition);
    if (!p_pgdentry) {
        return  (LW_NULL);
    }

    lib_bzero(p_pgdentry, PGD_BLOCK_SIZE);                              /*  新的 PGD 无有效的页表项     */

    ulAddr    &= LW_CFG_VMM_PGD_MASK;
    ulPgdNum   = ulAddr >> LW_CFG_VMM_PGD_SHIFT;

    p_pgdentry = (LW_PGD_TRANSENTRY *)((addr_t)p_pgdentry |
                 (ulPgdNum * sizeof(LW_PGD_TRANSENTRY)));               /*  获得一级页表描述符地址      */

    return  (p_pgdentry);
}

首先获取pgd池的基地址,然后保留ulAddr 地址的39到47位。通过右移39位,求出当前地址在pgd页表中的的位置。求出一个entry的地址。

arm64MmuPmdAlloc

主要是分配一个pmd表的首地址,将这个首地址填入到pgd 条目中。然后计算当前地址在pmd表中的entry,返回当前的pmd条目

/*********************************************************************************************************
** 函数名称: arm64MmuPmdAlloc
** 功能描述: 分配 PMD 项
** 输 入  : pmmuctx        mmu 上下文
**           p_pgdentry     pgd 入口地址
**           ulAddr         虚拟地址
** 输 出  : 分配 PMD 地址
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static LW_PMD_TRANSENTRY *arm64MmuPmdAlloc (PLW_MMU_CONTEXT     pmmuctx, 
                                            LW_PGD_TRANSENTRY  *p_pgdentry,
                                            addr_t              ulAddr)
{
#if LW_CFG_CACHE_EN > 0
    INTREG  iregInterLevel;
#endif                                                                  /*  LW_CFG_CACHE_EN > 0         */

    LW_PMD_TRANSENTRY  *p_pmdentry = (LW_PMD_TRANSENTRY *)API_PartitionGet(_G_hPMDPartition);

    if (!p_pmdentry) {
        return  (LW_NULL);
    }
    
    lib_bzero(p_pmdentry, PMD_BLOCK_SIZE);

    *p_pgdentry = arm64MmuBuildPgdEntry((addr_t)p_pmdentry,             /*  设置一级页表描述符          */
                                        ARM64_MMU_NS_SECURE,
                                        ARM64_MMU_AP_NO_EFFECT,
                                        ARM64_MMU_XN_NO_EFFECT,
                                        ARM64_MMU_PXN_NO_EFFECT,
                                        ARM64_PGD_TYPE_TABLE);

#if LW_CFG_CACHE_EN > 0
    iregInterLevel = KN_INT_DISABLE();
    arm64DCacheFlush((PVOID)p_pgdentry, (PVOID)p_pgdentry, 32);         /*  第三个参数无影响            */
    KN_INT_ENABLE(iregInterLevel);
#endif                                                                  /*  LW_CFG_CACHE_EN > 0         */

    return  (arm64MmuPmdOffset(p_pgdentry, ulAddr));
}

pts,pte分配和pmd分配相同。arm64MmuBuildPgdEntry函数在这里保留了地址的12-47位,其他位加入了系统需要的标志信息。

arm64MmuPgdIsOk  判断是否pgd当前 entry有效

/*********************************************************************************************************
** 函数名称: arm64MmuPgdIsOk
** 功能描述: 判断 PGD 项的描述符是否正确
** 输 入  : pgdentry       PGD 项描述符
** 输 出  : 是否正确
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static BOOL  arm64MmuPgdIsOk (LW_PGD_TRANSENTRY  pgdentry)
{
    return  (((pgdentry & ARM64_PGD_TYPE_MASK) == ARM64_PGD_TYPE_TABLE) ? LW_TRUE : LW_FALSE);
}

判断方式很简单 就是检测ARM64_PMD_TYPE_MASK 这个掩码,这个掩码上面pmd分配函数里,在把pmd地址写入到pgd entry时加入一些标志位,检测到这个存在说明当前pgd被填写了pmd数据。pmdOk等函数与这原理相同。

/*********************************************************************************************************
** 函数名称: arm64MmuPgdOffset
** 功能描述: 通过虚拟地址计算 PGD 项
** 输 入  : pmmuctx        mmu 上下文
**           ulAddr         虚拟地址
** 输 出  : 对应的 PGD 表项地址
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static LW_PGD_TRANSENTRY *arm64MmuPgdOffset (PLW_MMU_CONTEXT  pmmuctx, addr_t  ulAddr)
{
    REGISTER LW_PGD_TRANSENTRY  *p_pgdentry = pmmuctx->MMUCTX_pgdEntry;
    REGISTER ULONG               ulPgdNum;

    ulAddr    &= LW_CFG_VMM_PGD_MASK;
    ulPgdNum   = ulAddr >> LW_CFG_VMM_PGD_SHIFT;                        /*  计算 PGD 号                 */

    p_pgdentry = (LW_PGD_TRANSENTRY *)((addr_t)p_pgdentry |
                 (ulPgdNum * sizeof(LW_PGD_TRANSENTRY)));               /*  获得一级页表描述符地址      */
               
    return  (p_pgdentry);
}

全部的偏移函数都是首先获得当前自己的表的基地址,上面函数是首先获得 自己表的首地址,然后保留39-47数值,通过

 (ulPgdNum * sizeof(LW_PGD_TRANSENTRY)) 这句话算出在表内的偏移值。返回对应的entry。 

arm64MmuPtePhysGet  是将当前的pte存放地址丢掉低12bit,返回。

arm64MmuMakeTrans 

此函数的主要作用是将物理地址和虚拟地址表中的pte建立联系。也就是需要填充pte表。上面的alloc函数最终只把pts填充了pte的地址。所以在这里填充pte。

/*********************************************************************************************************
** 函数名称: arm64MmuMakeTrans
** 功能描述: 设置页面映射关系
** 输 入  : pmmuctx        mmu 上下文
**           p_pteentry     对应的页表项
**           ulVirtualAddr  虚拟地址
**           paPhysicalAddr 物理地址
**           ulFlag         对应的类型
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
** 注  意  : 这里不需要清除快表 TLB, 因为 VMM 自身会作此操作.
*********************************************************************************************************/
static VOID  arm64MmuMakeTrans (PLW_MMU_CONTEXT     pmmuctx,
                                LW_PTE_TRANSENTRY  *p_pteentry,
                                addr_t              ulVirtualAddr,
                                phys_addr_t         paPhysicalAddr,
                                ULONG               ulFlag)
{
    UINT8               ucGuard;                                        /*  严格的权限检查              */
    UINT8               ucXN;                                           /*  存储权限                    */
    UINT8               ucPXN;                                          /*  域                          */
    UINT8               ucContiguous;                                   /*  CACHE 与缓冲区控制          */
    UINT8               ucnG;                                           /*  存储权限                    */
    UINT8               ucAF;                                           /*  CACHE 与缓冲区控制          */
    UINT8               ucSH;                                           /*  永不执行位                  */
    UINT8               ucAP;
    UINT8               ucNS;
    UINT8               ucAttrIndx;
    UINT8               ucType;

    if (ulFlag & LW_VMM_FLAG_ACCESS) {
        ucType = ARM64_PTE_TYPE_PAGE;
    } else {
        ucType = ARM64_PTE_TYPE_FAULT;                                  /*  访问将失效                  */
    }

    if (arm64MmuFlags2Attr(ulFlag,
                           &ucGuard,
                           &ucXN, &ucPXN,
                           &ucContiguous,
                           &ucnG, &ucAF,
                           &ucSH, &ucAP,
                           &ucNS, &ucAttrIndx) < 0) {                   /*  无效的映射关系              */
        return;
    }

    *p_pteentry = arm64MmuBuildPtentry((addr_t)paPhysicalAddr,
                                       ucGuard,
                                       ucXN, ucPXN,
                                       ucContiguous,
                                       ucnG, ucAF,
                                       ucSH, ucAP,
                                       ucNS, ucAttrIndx,
                                       ucType);

#if LW_CFG_CACHE_EN > 0
    arm64DCacheFlush((PVOID)p_pteentry, (PVOID)p_pteentry, 32);         /*  第三个参数无影响            */
#endif                                                                  /*  LW_CFG_CACHE_EN > 0         */
}

首先将SylixoS标志位转换为物理地址的标志位。这里的ucAttrIndx 就是上面MAIR寄存器讲到有8个属性条目,其他可以在上面的图中看到。物理地址因为是4K页对齐所以也是保留 12-47位。最后根据虚拟地址中的offset计算出当前实际访问物理地址。

 arm64MmuMakeCurCtx

此函数用来设置TTBR0_EL1寄存器。TTBR0_EL1 寄存器器用来保存pgd表的基地址。

arm64MmuInvTLB 

函数使用来实现无线Tlb块表的,通过传入地址和无效的数量,当无效数量大于16时直接使用arm64MmuInvalidateTLB函数无效全部TLB。当无效数量小于16时调用arm64MmuInvalidateTLBMVA函数,将虚拟地址低12位抛弃,然后无效当前地址的。

FUNC_DEF(arm64MmuInvalidateTLBMVA)
    LSR     X0 , X0 , #12                                               ;/* 虚拟地址移出取低 12 位       */
    TLBI    VAE1IS , X0                                                 ;/* TLBIVA, All ASID, EL1, IS   */
    ARM_DSB()
    ARM_ISB()
    RET
    FUNC_END()
    

使能和关闭mmu都是通过设置SCTLR_EL1系统控制寄存器。

arm64MmuFlagGet 是通过虚拟地址四级查表获得pte存放的物理地址值,然后解析当前值对应的属性位。

arm64MmuFlagSet 函数是通过四级页表查找获得pte存放的物理地址,然后将SylixOS系统内存属性解析为硬件mmu的属性。然后将新设置的值写入到pte当中。

上面是mmu操作函数集,封装好了后还需要调用来建立mmu映射,在SylixOS中操作函数使用了宏定义,类似于下图

SylixOS armv8 mmu_第14张图片

你可能感兴趣的:(SylixOS)