diff --git a/programing-manual/port/riscv/figures/pte-rwx.png b/programing-manual/port/riscv/figures/pte-rwx.png new file mode 100644 index 0000000000000000000000000000000000000000..e2c20df53c4233b111d6066411f8aeaa2d057649 Binary files /dev/null and b/programing-manual/port/riscv/figures/pte-rwx.png differ diff --git a/programing-manual/port/riscv/figures/sv39-full.png b/programing-manual/port/riscv/figures/sv39-full.png new file mode 100644 index 0000000000000000000000000000000000000000..5678542eb3eefe7f421d84f8f5ba2ecf1e7496ec Binary files /dev/null and b/programing-manual/port/riscv/figures/sv39-full.png differ diff --git a/programing-manual/port/riscv/figures/sv39-pte.png b/programing-manual/port/riscv/figures/sv39-pte.png new file mode 100644 index 0000000000000000000000000000000000000000..7f693907429c67ad64a0f1e731fa76c7aa0348eb Binary files /dev/null and b/programing-manual/port/riscv/figures/sv39-pte.png differ diff --git a/programing-manual/port/riscv/mmu-sv39.md b/programing-manual/port/riscv/mmu-sv39.md new file mode 100644 index 0000000000000000000000000000000000000000..47c301c2ba4654773086c927b56f4a7aaa78814e --- /dev/null +++ b/programing-manual/port/riscv/mmu-sv39.md @@ -0,0 +1,527 @@ +# MMU-sv39 映射的实现 + +`NX_HalMapPage` 是映射虚拟地址,会自动分配物理地址并映射页面。 + +`NX_HalMapPageWithPhy` 映射虚拟地址,但是会指定物理地址,就不用自动分配物理地址了。 + +`NX_HalUnmapPage` 解除地址映射,解除后就不能访问了。 + +`NX_HalVir2Phy` 可以通过虚拟地址找到其映射的物理地址。 + +`NX_HalSetPageTable` 设置页表的地址到硬件寄存器中,当访问虚拟地址的时候,会根据设置的页表进行地址转换。 + +`NX_HalGetPageTable` 可以获取页表的地址。 + +`NX_HalEnable` 是使能 `MMU` ,所有地址都变成虚拟地址了。 + +* 文件:src/arch/riscv64/port/mmu.c + +```c +NX_PRIVATE void *NX_HalMapPage(NX_Mmu *mmu, NX_Addr virAddr, NX_Size size, NX_UArch attr) +{ + NX_ASSERT(mmu); + if (!attr) + { + return NX_NULL; + } + + virAddr = virAddr & NX_PAGE_ADDR_MASK; + size = NX_PAGE_ALIGNUP(size); + + NX_UArch level = NX_IRQ_SaveLevel(); + void *addr = __MapPage(mmu, virAddr, size, attr); + NX_IRQ_RestoreLevel(level); + return addr; +} + +NX_PRIVATE void *NX_HalMapPageWithPhy(NX_Mmu *mmu, NX_Addr virAddr, NX_Addr phyAddr, NX_Size size, NX_UArch attr) +{ + NX_ASSERT(mmu); + if (!attr) + { + return NX_NULL; + } + + virAddr = virAddr & NX_PAGE_ADDR_MASK; + phyAddr = phyAddr & NX_PAGE_ADDR_MASK; + size = NX_PAGE_ALIGNUP(size); + + NX_UArch level = NX_IRQ_SaveLevel(); + void *addr = __MapPageWithPhy(mmu, virAddr, phyAddr, size, attr); + NX_IRQ_RestoreLevel(level); + return addr; +} + +NX_PRIVATE NX_Error NX_HalUnmapPage(NX_Mmu *mmu, NX_Addr virAddr, NX_Size size) +{ + NX_ASSERT(mmu); + + virAddr = virAddr & NX_PAGE_ADDR_MASK; + size = NX_PAGE_ALIGNUP(size); + + NX_Addr addrStart = virAddr; + NX_Addr addrEnd = virAddr + size - 1; + NX_Size pages = GET_PF_ID(addrEnd) - GET_PF_ID(addrStart) + 1; + + NX_UArch level = NX_IRQ_SaveLevel(); + NX_Error err = __UnmapPage(mmu, virAddr, pages); + NX_IRQ_RestoreLevel(level); + return err; +} + +NX_PRIVATE void *NX_HalVir2Phy(NX_Mmu *mmu, NX_Addr virAddr) +{ + NX_ASSERT(mmu); + + NX_Addr pagePhy; + NX_Addr pageOffset; + + MMU_PDE *pageTable = (MMU_PDE *)mmu->table; + + MMU_PTE *pte = PageWalk(pageTable, virAddr, NX_False); + if (pte == NX_NULL) + { + NX_PANIC("vir2phy walk fault!"); + } + + if (!PTE_USED(*pte)) + { + NX_PANIC("vir2phy pte not used!"); + } + + pagePhy = PTE2PADDR(*pte); + pageOffset = virAddr % NX_PAGE_SIZE; + return (void *)(pagePhy + pageOffset); +} + +NX_PRIVATE void NX_HalSetPageTable(NX_Addr addr) +{ + WriteCSR(satp, MAKE_SATP(addr)); + MMU_FlushTLB(); +} + +NX_PRIVATE NX_Addr NX_HalGetPageTable(void) +{ + NX_Addr addr = ReadCSR(satp); + return (NX_Addr)GET_ADDR_FROM_SATP(addr); +} + +NX_PRIVATE void NX_HalEnable(void) +{ + MMU_FlushTLB(); +} + +NX_INTERFACE struct NX_MmuOps NX_MmuOpsInterface = +{ + .setPageTable = NX_HalSetPageTable, + .getPageTable = NX_HalGetPageTable, + .enable = NX_HalEnable, + .mapPage = NX_HalMapPage, + .mapPageWithPhy = NX_HalMapPageWithPhy, + .unmapPage = NX_HalUnmapPage, + .vir2Phy = NX_HalVir2Phy, +}; +``` + +监管者模式的页表的地址是由地址转换寄存器 `SATP` 保存的,其格式如下: +![地址转换](figures/satp.png) + +具体字段的解析如下: + +* Mode - MMU 地址翻译模式 + +|Value |Name |Description| +| ------- | ---------- | ---------- | +|0 |Bare |No translation or protection| +|1-7| - |Reserved| +|8 |Sv39 |Page-based 39-bit virtual addressing| +|9 |Sv48 |Page-based 48-bit virtual addressing| +|10 |Sv57 |Reserved for page-based 57-bit virtual addressing| +|11 |Sv64 |Reserved for page-based 64-bit virtual addressing| +|12-15| - |Reserved| + +当 Mode 为 0 时,MMU 关闭。 + +* ASID – 当前 ASID。表示当前程序的 ASID 号。 +* PPN – 硬件回填根 PPN。第一级硬件回填使用的 PPN (Phsical Page Number)。 + +接下来,我们给出 SV39 地址转换的全过程图示(来源于 MIT 6.828 课程)来介绍多级页表原理的介绍: +![sv39-full](figures/sv39-full.png) + +在 `SV39` 模式中我们采用三级页表,即将 `27` 位的虚拟页号分为三个等长的部分, +第 `26-18` 位为三级索引 `VPN2`,第 `17-9` 位为二级索引 `VPN1`,第 `8-0` 位为一级索引 `VPN0`。 + +我们也将页表分为三级页表,二级页表,一级页表。 +每个页表都用 `9` 位索引的,因此有 `2的9次方=512` 个页表项, +而每个页表项都是 `8` 字节, +因此每个页表大小都为 `512 x 8 = 4KiB` 。 +正好是一个物理页的大小。 +我们可以把一个页表放到一个物理页中,并用一个物理页号来描述它。 +事实上,三级页表的每个页表项中的物理页号可描述一个二级页表; +二级页表的每个页表项中的物理页号可描述一个一级页表; +一级页表中的页表项内容则和我们刚才提到的页表项一样,其内容包含物理页号,即描述一个要映射到的物理页。 + +具体来说,假设我们有虚拟地址 `(VPN2,VPN1,VPN0,offset)` : + +* 我们首先会记录装载「当前所用的三级页表的物理页」的页号到 `satp` 寄存器中; + +* 把 `VPN2` 作为偏移在第三级页表的物理页中找到第二级页表的物理页号; + +* 把 `VPN1` 作为偏移在第二级页表的物理页中找到第一级页表的物理页号; + +* 把 `VPN0` 作为偏移在第一级页表的物理页中找到要访问位置的物理页号; + +* 物理页号对应的物理页基址(即物理页号左移12位)加上 就是虚拟地址对应的物理地址。 + +这样处理器通过这种多次转换,终于从虚拟页号找到了一级页表项, +从而得出了物理页号和虚拟地址所对应的物理地址。 +刚才我们提到若页表项满足 `R,W,X` 都为 0,表明这个页表项指向下一级页表。 +在这里三级和二级页表项的 `R,W,X` 为 0 应该成立,因为它们指向了下一级页表。 + +页面属性的定义如下: +* 文件:src/arch/riscv64/include/arch/mmu.h + +```c +// page table entry (PTE) fields +#define PTE_V 0x001 // Valid +#define PTE_R 0x002 // Read +#define PTE_W 0x004 // Write +#define PTE_X 0x008 // Execute +#define PTE_U 0x010 // User +#define PTE_G 0x020 // Global +#define PTE_A 0x040 // Accessed +#define PTE_D 0x080 // Dirty +#define PTE_SOFT 0x300 // Reserved for Software +#define PTE_S 0x000 // system + +#define NX_PAGE_ATTR_RWX (PTE_X | PTE_W | PTE_R) + +#define NX_PAGE_ATTR_KERNEL (PTE_V | NX_PAGE_ATTR_RWX | PTE_S | PTE_G) +#define NX_PAGE_ATTR_USER (PTE_V | NX_PAGE_ATTR_RWX | PTE_U | PTE_G) +``` + +[以上描述摘自rcore book](https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter4/3sv39-implementation-1.html) + +在 `NX_HalMapPage` 中调用了 `__MapPage` 。`__MapPage` 会先计算需要映射的页面数量,然后循环映射页面。 + +由于每个虚拟地址都需要有一个对应的物理地址,因此调用物理页面分配函数 `NX_PageAlloc` 来分配页面。然后调用 `MapOnePage` 来映射一个页面。 + +```c +NX_PRIVATE void *__MapPage(NX_Mmu *mmu, NX_Addr virAddr, NX_Size size, NX_UArch attr) +{ + NX_Addr addrStart = virAddr; + NX_Addr addrEnd = virAddr + size - 1; + + NX_SSize pages = GET_PF_ID(addrEnd) - GET_PF_ID(addrStart) + 1; + NX_Size mappedPages = 0; + void *phyAddr; + + while (pages > 0) + { + phyAddr = NX_PageAlloc(1); + if (phyAddr == NX_NULL) + { + NX_LOG_E("map page: alloc page failed!"); + goto err; + } + + if (MapOnePage(mmu, virAddr, (NX_Addr)phyAddr, attr) != NX_EOK) + { + NX_LOG_E("map page: vir:%p phy:%p attr:%x failed!", virAddr, phyAddr, attr); + goto err; + } + virAddr += NX_PAGE_SIZE; + phyAddr += NX_PAGE_SIZE; + pages--; + mappedPages++; + } + return (void *)addrStart; +err: + if (phyAddr != NX_NULL) + { + NX_PageFree(phyAddr); + } + __UnmapPage(mmu, addrStart, mappedPages); + return NX_NULL; +} +``` + +在 `MapOnePage` 中,会先检测该地址是否已经映射,没有映射才能映射。 + +需要调用一个最为核心的函数,就是 `PageWalk`。 +它会根据虚拟地址来做页表遍历,如果 `allocPage` 为假,那么在遍历过程中,某个等级的页表不存在就会返回空。 +这就意味着这个地址是没有映射。 + +如果 `allocPage` 为真,那么就会在遍历过程中分配不存在的页表。 +例如在页目录表中,会有页表项,如果发现该页表项为空,则会分配一个物理页,作为下一级的页表。 + +该函数最后返回的是 `VPN0` 的页表项,页表项存放了物理页的地址。 + +在 `MapOnePage` 中,`allocPage` 为真,那就意味着如果虚拟地址没有映射物理地址,就会一级一级地分配页表。 + +返回 `pte` 后,需要填写也表项,其内容是物理地址和页面的属性`R,W,X`等。 + +页表项格式如下: +![pte格式](figures/sv39-pte.png) + +物理页的属性如下: +![pte格式rwx属性](figures/pte-rwx.png) + + + +```c +NX_PRIVATE MMU_PTE *PageWalk(MMU_PDE *pageTable, NX_Addr virAddr, NX_Bool allocPage) +{ + NX_ASSERT(pageTable); + + /* The page table in sv39 mode has 3 levels */ + int level; + for (level = 2; level > 0; level--) + { + MMU_PTE *pte = &pageTable[GET_LEVEL_OFF(level, virAddr)]; + + if (PTE_USED(*pte)) + { + pageTable = (MMU_PDE *)PTE2PADDR(*pte); + NX_ASSERT(pageTable); + } + else + { + if (allocPage == NX_False) + { + return NX_NULL; + } + pageTable = (MMU_PDE *)NX_PageAlloc(1); + if (pageTable == NX_NULL) + { + NX_LOG_E("riscv64 mmu-sv39: page walk with no enough memory!"); + return NX_NULL; + } + NX_MemZero(NX_Phy2Virt(pageTable), NX_PAGE_SIZE); + + /* increase last level page table reference */ + void *levelPageTable = (void *)(NX_Virt2Phy((NX_Addr)pte) & NX_PAGE_ADDR_MASK); + NX_ASSERT(pageTable); + NX_PageIncrease(levelPageTable); + + *pte = PADDR2PTE(pageTable) | PTE_V; + } + pageTable = (MMU_PDE *)NX_Phy2Virt(pageTable); + } + return &pageTable[GET_LEVEL_OFF(0, virAddr)]; +} + +NX_PRIVATE NX_Error MapOnePage(NX_Mmu *mmu, NX_Addr virAddr, NX_Addr phyAddr, NX_UArch attr) +{ + if (IsVirAddrMapped(mmu, virAddr) == NX_True) + { + NX_LOG_E("map page: vir:%p was mapped!", virAddr); + return NX_EINVAL; + } + + MMU_PDE *pageTable = (MMU_PDE *)mmu->table; + + MMU_PTE *pte = PageWalk(pageTable, virAddr, NX_True); + if (pte == NX_NULL) + { + NX_LOG_E("map page: walk page vir:%p failed!", virAddr); + return NX_EFAULT; + } + if (PTE_USED(*pte)) + { + NX_PANIC("Map one page but PTE had used!"); + } + /* increase last level page table reference */ + void *levelPageTable = (void *)(NX_Virt2Phy((NX_Addr)pte) & NX_PAGE_ADDR_MASK); + NX_PageIncrease(levelPageTable); + + *pte = PADDR2PTE(phyAddr) | attr; + + return NX_EOK; +} +``` + +在 `NX_HalMapPageWithPhy` 中,会调用 `__MapPageWithPhy`。 + +只是现在不需要在分配物理地址而已,因为已经指定了物理地址了。 + +```c +NX_PRIVATE void *__MapPageWithPhy(NX_Mmu *mmu, NX_Addr virAddr, NX_Addr phyAddr, NX_Size size, NX_UArch attr) +{ + NX_Addr addrStart = virAddr; + NX_Addr addrEnd = virAddr + size - 1; + + NX_SSize pages = GET_PF_ID(addrEnd) - GET_PF_ID(addrStart) + 1; + NX_Size mappedPages = 0; + + while (pages > 0) + { + if (MapOnePage(mmu, virAddr, phyAddr, attr) != NX_EOK) + { + NX_LOG_E("map page: vir:%p phy:%p attr:%x failed!", virAddr, phyAddr, attr); + __UnmapPage(mmu, addrStart, mappedPages); + return NX_NULL; + } + virAddr += NX_PAGE_SIZE; + phyAddr += NX_PAGE_SIZE; + pages--; + mappedPages++; + } + return (void *)addrStart; +} +``` + +在 `NX_HalUnmapPage` 中,会调用 `__UnmapPage` 来解除页面的映射。 + +在解除页面映射的时候,循环调用 `UnmapOnePage` 解除每一个页面。 + +在 `UnmapOnePage` 中,会通过 `PageWalkPTE` 去遍历3级页表,获取虚拟地址 `virAddr` 对应的页表项。将结果保存到 +`pteArray[]` 数组中,`pteArray[0]` 保存了 `VPN0` 的页表项,`pteArray[1]` 保存了 `VPN1` 的页表项,`pteArray[2]` 保存了 `VPN2` 的页表项。 + +首先会释放叶子页面,也就是虚拟地址最终对应的物理地址,而页表是非叶子页面。 + +非叶子页面,就是页表,在释放的时候调用 `NX_PageFree` 会减少页面的引用计数,为0的时候才真正释放。 + +```c +/** + * walk addr for get pte level 0, 1, 2 + */ +NX_PRIVATE NX_Error PageWalkPTE(MMU_PDE *pageTable, NX_Addr virAddr, MMU_PTE *pteArray[3]) +{ + /* The page table in sv39 mode has 3 levels */ + int level; + for (level = 2; level > 0; level--) + { + MMU_PTE *pte = &pageTable[GET_LEVEL_OFF(level, virAddr)]; + pteArray[level] = pte; + + if (PTE_USED(*pte)) + { + pageTable = (MMU_PDE *)PTE2PADDR(*pte); + pageTable = (MMU_PDE *)NX_Phy2Virt(pageTable); + } + else + { + NX_LOG_E("map walk pte: pte on vir:%p not used!", virAddr); + return NX_EFAULT; + } + } + pteArray[0] = &pageTable[GET_LEVEL_OFF(0, virAddr)]; + return NX_EOK; +} + +NX_PRIVATE NX_Error UnmapOnePage(NX_Mmu *mmu, NX_Addr virAddr) +{ + MMU_PDE *pageTable = (MMU_PDE *)mmu->table; + MMU_PTE *pte; + NX_Addr phyPage; + void *levelPageTable; + + MMU_PTE *pteArray[3] = {NX_NULL, NX_NULL, NX_NULL}; + NX_ASSERT(PageWalkPTE(pageTable, virAddr, pteArray) == NX_EOK); + + pte = pteArray[0]; + NX_ASSERT(pte != NX_NULL); + NX_ASSERT(PTE_USED(*pte)); + NX_ASSERT(PAGE_IS_LEAF(*pte)); + phyPage = PTE2PADDR(*pte); + NX_ASSERT(phyPage); + NX_PageFree((void *)phyPage); /* free leaf page*/ + *pte = 0; /* clear pte in level 0 */ + + /* free none-leaf page */ + levelPageTable = (void *)(((NX_Addr)pte) & NX_PAGE_ADDR_MASK); /* get level page table by pte */ + if (NX_PageFree(levelPageTable) == NX_EOK) + { + pte = pteArray[1]; + NX_ASSERT(PTE_USED(*pte)); + NX_ASSERT(!PAGE_IS_LEAF(*pte)); + NX_ASSERT((NX_Addr)levelPageTable == PTE2PADDR(*pte)); + *pte = 0; /* clear pte in level 1 */ + + levelPageTable = (void *)(((NX_Addr)pte) & NX_PAGE_ADDR_MASK); + if (NX_PageFree(levelPageTable) == NX_EOK) + { + pte = pteArray[2]; + NX_ASSERT(PTE_USED(*pte)); + NX_ASSERT(!PAGE_IS_LEAF(*pte)); + NX_ASSERT((NX_Addr)levelPageTable == PTE2PADDR(*pte)); + *pte = 0; /* clear pte in level 2 */ + } + } + return NX_EOK; +} + +NX_INLINE NX_Error __UnmapPage(NX_Mmu *mmu, NX_Addr virAddr, NX_Size pages) +{ + while (pages > 0) + { + UnmapOnePage(mmu, virAddr); + virAddr += NX_PAGE_SIZE; + pages--; + } + return NX_EOK; +} +``` + +在 `NX_HalVir2Phy` 中,根据虚拟地址 `virAddr`,转换为物理地址。 +之前介绍过 `PageWalk` 这个函数,如果 `allocPage` 为假,那么在遍历过程中,不存在就会出错,存在就会返回页表项,里面存放了物理页的地址。 + +通过 `PTE2PADDR(*pte)` 取出物理页面的地址,返回的时候加上页内偏移 `(pagePhy + pageOffset)`。 + +```c + +NX_PRIVATE void *NX_HalVir2Phy(NX_Mmu *mmu, NX_Addr virAddr) +{ + NX_ASSERT(mmu); + + NX_Addr pagePhy; + NX_Addr pageOffset; + + MMU_PDE *pageTable = (MMU_PDE *)mmu->table; + + MMU_PTE *pte = PageWalk(pageTable, virAddr, NX_False); + if (pte == NX_NULL) + { + NX_PANIC("vir2phy walk fault!"); + } + + if (!PTE_USED(*pte)) + { + NX_PANIC("vir2phy pte not used!"); + } + + pagePhy = PTE2PADDR(*pte); + pageOffset = virAddr % NX_PAGE_SIZE; + return (void *)(pagePhy + pageOffset); +} +``` + +设置页表的时候,需要将页表基地址写入 `satp` 寄存器。然后刷新快表。 +这里的快表可以理解为缓存,可以加快虚拟地址到物理地址的转换。 + +```c +NX_INLINE void SFenceVMA() +{ + NX_CASM("sfence.vma"); +} + +#define MMU_FlushTLB() SFenceVMA() + +NX_PRIVATE void NX_HalSetPageTable(NX_Addr addr) +{ + WriteCSR(satp, MAKE_SATP(addr)); + MMU_FlushTLB(); +} +``` + +获取也表的时候,需要读取 `satp` 寄存器,并转换出页表的地址。 + +```c +NX_PRIVATE NX_Addr NX_HalGetPageTable(void) +{ + NX_Addr addr = ReadCSR(satp); + return (NX_Addr)GET_ADDR_FROM_SATP(addr); +} +``` + diff --git a/programing-manual/port/riscv/riscv.md b/programing-manual/port/riscv/riscv.md index a065fbca07602e9986989993c8533909de78cba1..868efaaa28f163572f0ec8748e0519c1c3fb0442 100644 --- a/programing-manual/port/riscv/riscv.md +++ b/programing-manual/port/riscv/riscv.md @@ -66,28 +66,6 @@ RISC-V的中断管理由处理器核局部中断CLINT(CoreLocalInterrupt)和 RISC-V的MMU支持多种模式,有Sv32/Sv39/Sv48/Sv57/Sv64等。不同的模式映射的页面等级,页面大小是有差异的。在64位处理器种最常用的是Sv39,它是3级4KB页面大小映射。 -监管者模式的页表的地址是由地址转换寄存器 `SATP` 保存的,其格式如下: -![地址转换](figures/satp.png) - -具体字段的解析如下: - -* Mode - MMU 地址翻译模式 - -|Value |Name |Description| -| ------- | ---------- | ---------- | -|0 |Bare |No translation or protection| -|1-7| - |Reserved| -|8 |Sv39 |Page-based 39-bit virtual addressing| -|9 |Sv48 |Page-based 48-bit virtual addressing| -|10 |Sv57 |Reserved for page-based 57-bit virtual addressing| -|11 |Sv64 |Reserved for page-based 64-bit virtual addressing| -|12-15| - |Reserved| - -当 Mode 为 0 时,MMU 关闭。 - -* ASID – 当前 ASID。表示当前程序的 ASID 号。 -* PPN – 硬件回填根 PPN。第一级硬件回填使用的 PPN (Phsical Page Number)。 - ## 三、代码移植 移植一个新的平台需要实现如下内容: @@ -862,128 +840,7 @@ NX_INTERFACE NX_IRQ_Controller NX_IRQ_ControllerInterface = 目前支持的是mmu-sv39,3级页表,页表和物理页都是4kb大小。 -`NX_HalMapPage` 是映射虚拟地址,会自动分配物理地址并映射页面。 - -`NX_HalMapPageWithPhy` 映射虚拟地址,但是会指定物理地址,就不用自动分配物理地址了。 - -`NX_HalUnmapPage` 解除地址映射,解除后就不能访问了。 - -`NX_HalVir2Phy` 可以通过虚拟地址找到其映射的物理地址。 - -`NX_HalSetPageTable` 设置页表的地址到硬件寄存器中,当访问虚拟地址的时候,会根据设置的页表进行地址转换。 - -`NX_HalGetPageTable` 可以获取页表的地址。 - -`NX_HalEnable` 是使能 `MMU` ,所有地址都变成虚拟地址了。 - -* 文件:src/arch/riscv64/port/mmu.c - -```c -NX_PRIVATE void *NX_HalMapPage(NX_Mmu *mmu, NX_Addr virAddr, NX_Size size, NX_UArch attr) -{ - NX_ASSERT(mmu); - if (!attr) - { - return NX_NULL; - } - - virAddr = virAddr & NX_PAGE_ADDR_MASK; - size = NX_PAGE_ALIGNUP(size); - - NX_UArch level = NX_IRQ_SaveLevel(); - void *addr = __MapPage(mmu, virAddr, size, attr); - NX_IRQ_RestoreLevel(level); - return addr; -} - -NX_PRIVATE void *NX_HalMapPageWithPhy(NX_Mmu *mmu, NX_Addr virAddr, NX_Addr phyAddr, NX_Size size, NX_UArch attr) -{ - NX_ASSERT(mmu); - if (!attr) - { - return NX_NULL; - } - - virAddr = virAddr & NX_PAGE_ADDR_MASK; - phyAddr = phyAddr & NX_PAGE_ADDR_MASK; - size = NX_PAGE_ALIGNUP(size); - - NX_UArch level = NX_IRQ_SaveLevel(); - void *addr = __MapPageWithPhy(mmu, virAddr, phyAddr, size, attr); - NX_IRQ_RestoreLevel(level); - return addr; -} - -NX_PRIVATE NX_Error NX_HalUnmapPage(NX_Mmu *mmu, NX_Addr virAddr, NX_Size size) -{ - NX_ASSERT(mmu); - - virAddr = virAddr & NX_PAGE_ADDR_MASK; - size = NX_PAGE_ALIGNUP(size); - - NX_Addr addrStart = virAddr; - NX_Addr addrEnd = virAddr + size - 1; - NX_Size pages = GET_PF_ID(addrEnd) - GET_PF_ID(addrStart) + 1; - - NX_UArch level = NX_IRQ_SaveLevel(); - NX_Error err = __UnmapPage(mmu, virAddr, pages); - NX_IRQ_RestoreLevel(level); - return err; -} - -NX_PRIVATE void *NX_HalVir2Phy(NX_Mmu *mmu, NX_Addr virAddr) -{ - NX_ASSERT(mmu); - - NX_Addr pagePhy; - NX_Addr pageOffset; - - MMU_PDE *pageTable = (MMU_PDE *)mmu->table; - - MMU_PTE *pte = PageWalk(pageTable, virAddr, NX_False); - if (pte == NX_NULL) - { - NX_PANIC("vir2phy walk fault!"); - } - - if (!PTE_USED(*pte)) - { - NX_PANIC("vir2phy pte not used!"); - } - - pagePhy = PTE2PADDR(*pte); - pageOffset = virAddr % NX_PAGE_SIZE; - return (void *)(pagePhy + pageOffset); -} - -NX_PRIVATE void NX_HalSetPageTable(NX_Addr addr) -{ - WriteCSR(satp, MAKE_SATP(addr)); - MMU_FlushTLB(); -} - -NX_PRIVATE NX_Addr NX_HalGetPageTable(void) -{ - NX_Addr addr = ReadCSR(satp); - return (NX_Addr)GET_ADDR_FROM_SATP(addr); -} - -NX_PRIVATE void NX_HalEnable(void) -{ - MMU_FlushTLB(); -} - -NX_INTERFACE struct NX_MmuOps NX_MmuOpsInterface = -{ - .setPageTable = NX_HalSetPageTable, - .getPageTable = NX_HalGetPageTable, - .enable = NX_HalEnable, - .mapPage = NX_HalMapPage, - .mapPageWithPhy = NX_HalMapPageWithPhy, - .unmapPage = NX_HalUnmapPage, - .vir2Phy = NX_HalVir2Phy, -}; -``` +由于篇幅较大,故单独提出来讲解:[点击访问](mmu-sv39.md) ### 9. 进程管理