armv8 mmu 操作函数集已经介绍了操作函数集。SylixOS在启动时会调用__vmmLibPrimaryInit函数进行页表的初始化。
/*********************************************************************************************************
** 函数名称: __vmmLibPrimaryInit
** 功能描述: 初始化 MMU 功能, CPU 构架相关。(多核模式下, 为主核 MMU 初始化)
** 输 入 : pphydesc 物理内存区描述表
** pcMachineName 正在运行的机器名称
** 输 出 : BSP 函数返回值
** 全局变量:
** 调用模块:
*********************************************************************************************************/
ULONG __vmmLibPrimaryInit (LW_MMU_PHYSICAL_DESC pphydesc[], CPCHAR pcMachineName)
此函数需要传入一个物理地址描述符,包含需要将物理地址映射到虚拟地址中位置。
上图是物理地址描述符结构体。 在全志h6中映射关系的一部分如下图
将物理地址的BSP_CFG_RAM_BASE 映射到了虚拟地址另。在链接脚本中将 BSP_CFG_RAM_BASE地址是异常入口,所以这里需要映射成虚拟地址零。SylixOS 系统中使用的是平映射,代码段在使用虚拟地址后并没有在高地址运行。linux是在虚拟地址的高地址运行。所以这里物理地址和虚拟地址都是BSP_CFG_RAM_BASE。
__vmmLibPrimaryInit 首席获取mmu上下文,mmu上下文在SylixOS目前是一个全部变量。然后调用__VMM_MMU_MEM_INIT宏初始化mmu。
在armv8中这个调用的函数对应的是 在mmu实现的操作函数集。
此函数功能是初始化当前pgd,pmd,pts,pte的内存池。在分配完成缓冲区后,调用__vmm_pgd_alloc 分配一个pgd,此函数首先判断是否存在,如果不存在直接分配。
static LW_INLINE LW_PGD_TRANSENTRY *__vmm_pgd_alloc (PLW_MMU_CONTEXT pmmuctx, addr_t ulAddr)
{
if (pmmuctx->MMUCTX_pgdEntry == LW_NULL) {
return (__VMM_MMU_PGD_ALLOC(pmmuctx, ulAddr));
} else {
return (__VMM_MMU_PGD_OFFSET(pmmuctx, ulAddr));
}
}
如果存在查找对应的entry,返回这个entry。在初始化时传入的地址是0,所以这里无论有没有都会返回pgd表的首地址。
__VMM_MMU_GLOBAL_INIT 宏是对 arm64MmuGlobalInit 函数的宏定义,此时初始化硬件相关部分。包含关闭TLB,关cache等等。然后调用__vmmLibGlobalMap函数,此函数将物理地址描述符的物理地址和虚拟地址一一映射。
/*********************************************************************************************************
** 函数名称: __vmmLibGlobalMap
** 功能描述: 全局页面映射关系设置
** 输 入 : pmmuctx MMU 上下文
** pphydesc 物理内存区描述表
** 输 出 : ERROR CODE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static INT __vmmLibGlobalMap (PLW_MMU_CONTEXT pmmuctx, LW_MMU_PHYSICAL_DESC pphydesc[])
{
INT i;
ULONG ulError;
ULONG ulPageNum;
for (i = 0; pphydesc[i].PHYD_stSize; i++) {
if ((pphydesc[i].PHYD_uiType == LW_PHYSICAL_MEM_BUSPOOL) ||
(pphydesc[i].PHYD_uiType == LW_PHYSICAL_MEM_APP) ||
(pphydesc[i].PHYD_uiType == LW_PHYSICAL_MEM_DMA)) {
continue;
}
ulPageNum = (ULONG)(pphydesc[i].PHYD_stSize >> LW_CFG_VMM_PAGE_SHIFT);
if (pphydesc[i].PHYD_stSize & ~LW_CFG_VMM_PAGE_MASK) {
ulPageNum++;
}
_BugFormat(__vmmLibVirtualOverlap(pphydesc[i].PHYD_ulVirMap,
pphydesc[i].PHYD_stSize), LW_TRUE,
"global map vaddr 0x%08lx size: 0x%08zx overlap with virtual space.\r\n",
pphydesc[i].PHYD_ulVirMap, pphydesc[i].PHYD_stSize);
switch (pphydesc[i].PHYD_uiType) {
case LW_PHYSICAL_MEM_TEXT:
ulError = __vmmLibPageMap(pphydesc[i].PHYD_ulPhyAddr,
pphydesc[i].PHYD_ulVirMap,
ulPageNum, LW_VMM_FLAG_EXEC | LW_VMM_FLAG_RDWR);
break;
case LW_PHYSICAL_MEM_DATA:
ulError = __vmmLibPageMap(pphydesc[i].PHYD_ulPhyAddr,
pphydesc[i].PHYD_ulVirMap,
ulPageNum, LW_VMM_FLAG_RDWR);
break;
case LW_PHYSICAL_MEM_VECTOR:
ulError = __vmmLibPageMap(pphydesc[i].PHYD_ulPhyAddr,
pphydesc[i].PHYD_ulVirMap,
ulPageNum, LW_VMM_FLAG_EXEC);
break;
case LW_PHYSICAL_MEM_BOOTSFR:
ulError = __vmmLibPageMap(pphydesc[i].PHYD_ulPhyAddr,
pphydesc[i].PHYD_ulVirMap,
ulPageNum, LW_VMM_FLAG_DMA);
break;
default:
ulError = ERROR_NONE;
break;
}
if (ulError) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "vmm global map fail.\r\n");
return (PX_ERROR);
}
}
return (ERROR_NONE);
}
首先bus pool ,DMA,APP空间不映射。app空间在使用时才映射。 映射时按照4K对齐方式,所以以下代码将大小以4K对齐统计有多少页。也就是4K页面有多少个。不足4K页面的,按照4K计算,所以出现ulPageNum++。
ulPageNum = (ULONG)(pphydesc[i].PHYD_stSize >> LW_CFG_VMM_PAGE_SHIFT);
if (pphydesc[i].PHYD_stSize & ~LW_CFG_VMM_PAGE_MASK) {
ulPageNum++;
}
__vmmLibPageMap函数最终会调用__vmmLibPageMap2,TEXT,DATA,VECTOR,启动时相关的寄存器映射都会调用此函数,只是是使用的标志不同,这里使用的是SylixOS定义的标志,在mmu操作函数集中会将系统标志属性,最后转化为对应的体系结构硬件的页面属性。
/*********************************************************************************************************
** 函数名称: __vmmLibPageMap2
** 功能描述: 将物理页面重新映射 (for ioremap2 )
** 输 入 : paPhysicalAddr 物理页面地址
** ulVirtualAddr 需要映射的虚拟地址
** ulPageNum 需要映射的页面个数
** ulFlag 页面标志
** 输 出 : ERROR CODE
** 全局变量:
** 调用模块:
*********************************************************************************************************/
ULONG __vmmLibPageMap2 (phys_addr_t paPhysicalAddr, addr_t ulVirtualAddr, ULONG ulPageNum, ULONG ulFlag)
{
ULONG i;
PLW_MMU_CONTEXT pmmuctx = __vmmGetCurCtx();
#if LW_CFG_VMM_L4_HYPERVISOR_EN == 0
INTREG iregInterLevel;
addr_t ulVirtualTlb = ulVirtualAddr;
LW_PGD_TRANSENTRY *p_pgdentry;
LW_PMD_TRANSENTRY *p_pmdentry;
#if LW_CFG_VMM_PAGE_4L_EN > 0
LW_PTS_TRANSENTRY *p_ptsentry;
#endif /* LW_CFG_VMM_PAGE_4L_EN > 0 */
LW_PTE_TRANSENTRY *p_pteentry;
#endif /* !LW_CFG_VMM_L4_HYPERVISOR_EN */
#if LW_CFG_VMM_L4_HYPERVISOR_EN > 0
if (ulFlag & LW_VMM_FLAG_ACCESS) {
for (i = 0; i < ulPageNum; i++) {
if (__VMM_MMU_PAGE_MAP(pmmuctx, paPhysicalAddr,
ulVirtualAddr, ulFlag) < ERROR_NONE) {
return (ERROR_VMM_LOW_LEVEL);
}
paPhysicalAddr += LW_CFG_VMM_PAGE_SIZE;
ulVirtualAddr += LW_CFG_VMM_PAGE_SIZE;
}
} else {
for (i = 0; i < ulPageNum; i++) {
if (__VMM_MMU_PAGE_UNMAP(pmmuctx, ulVirtualAddr) < ERROR_NONE) {
return (ERROR_VMM_LOW_LEVEL);
}
ulVirtualAddr += LW_CFG_VMM_PAGE_SIZE;
}
}
#else
for (i = 0; i < ulPageNum; i++) {
p_pgdentry = __vmm_pgd_alloc(pmmuctx, ulVirtualAddr);
if (p_pgdentry == LW_NULL) {
return (ERROR_VMM_LOW_LEVEL);
}
p_pmdentry = __vmm_pmd_alloc(pmmuctx, p_pgdentry, ulVirtualAddr);
if (p_pmdentry == LW_NULL) {
return (ERROR_VMM_LOW_LEVEL);
}
#if LW_CFG_VMM_PAGE_4L_EN > 0
p_ptsentry = __vmm_pts_alloc(pmmuctx, p_pmdentry, ulVirtualAddr);
if (p_ptsentry == LW_NULL) {
return (ERROR_VMM_LOW_LEVEL);
}
p_pteentry = __vmm_pte_alloc(pmmuctx, p_ptsentry, ulVirtualAddr);
#else
p_pteentry = __vmm_pte_alloc(pmmuctx, p_pmdentry, ulVirtualAddr);
#endif /* LW_CFG_VMM_PAGE_4L_EN > 0 */
if (p_pteentry == LW_NULL) {
return (ERROR_VMM_LOW_LEVEL);
}
iregInterLevel = KN_INT_DISABLE(); /* 关闭中断 */
__VMM_MMU_MAKE_TRANS(pmmuctx, p_pteentry,
ulVirtualAddr,
paPhysicalAddr, ulFlag); /* 创建映射关系 */
KN_INT_ENABLE(iregInterLevel); /* 打开中断 */
paPhysicalAddr += LW_CFG_VMM_PAGE_SIZE;
ulVirtualAddr += LW_CFG_VMM_PAGE_SIZE;
}
__vmmLibFlushTlb(pmmuctx, ulVirtualTlb, ulPageNum); /* 同步刷新所有 CPU TLB */
#endif /* !LW_CFG_VMM_L4_HYPERVISOR_EN */
return (ERROR_NONE);
}
根据需要的4K页数,不断做pgd->pmd->pts->pte->物理地址 的映射过程。首先获取的pgd,然后在看pgd中是否填充了pmd。检测是否填充是调用arm64MmuPgdIsOk 函数,这个函数在mmu函数集中。是在首次往pgd中填写pmd地址时将第三位设置为一个标志位。通过检测这个标志位判断是否有填写数据。如果有pmd就算出当前地址在pmd表中的偏移,获得pmd entry。pts,pte都是一样的原理,然后到pte时调用__VMM_MMU_MAKE_TRAN 这个宏实现将SylixOS标志位 转换为物理页属性,将pte地址和物理地址绑定。是通过调用mmu功能函数集中的arm64MmuMakeTrans函数实现的,映射完成后刷新TLB块表。循环这样过程将物理地址描述符中的所有的页映射完成后。代用__VMM_MMU_MAKE_CURCTX 这个宏,调用mmu函数中的arm64MmuMakeCurCtx 函数,将pgd的基地址填写到TTBR1寄存器中。启动时的物理页面的整个映射就结束了。此时还需要mmu使能工作,全志h6是等cache和vmm子系统都完成后启动muu。