diff --git a/paw/stos/arch/amd64/cpu/mmu.c b/paw/stos/arch/amd64/cpu/mmu.c index aac78ea..3abb2e9 100644 --- a/paw/stos/arch/amd64/cpu/mmu.c +++ b/paw/stos/arch/amd64/cpu/mmu.c @@ -7,9 +7,186 @@ */ #include +#include #include +#include +#include #include +/* + * Page-Table Entry (PTE) flags + * + * See Intel SDM Vol 3A, Section 4.5, Table 4-19 + */ +#define PTE_ADDR_MASK 0x000FFFFFFFFFF000 +#define PTE_P BIT(0) /* Present */ +#define PTE_RW BIT(1) /* Writable */ +#define PTE_US BIT(2) /* User r/w allowed */ +#define PTE_PWT BIT(3) /* Page-level write-through */ +#define PTE_PCD BIT(4) /* Page-level cache disable */ +#define PTE_ACC BIT(5) /* Accessed */ +#define PTE_DIRTY BIT(6) /* Dirty (written-to page) */ +#define PTE_PS BIT(7) /* Page size */ +#define PTE_GLOBAL BIT(8) /* Global; sticky */ +#define PTE_NX BIT(63) /* Execute-disable */ + +/* 57-bit linear addresses */ +#define CR4_LA57 BIT(12) + +/* + * Valid page levels + */ +typedef enum { + PAGE_LEVEL_PML1, + PAGE_LEVEL_PML2, + PAGE_LEVEL_PML3, + PAGE_LEVEL_PML4, + PAGE_LEVEL_PML5 +} PAGE_LEVEL; + +/* + * Convert protection flags to PTE flags + */ +static inline UQUAD +MmuProtToPte(UCHAR Prot) +{ + switch (Prot) { + case PAGE_READONLY: + return PTE_P | PTE_NX; + case PAGE_READWRITE: + return PTE_P | PTE_RW | PTE_NX; + case PAGE_READWRITE_X: + return PTE_P | PTE_RW; + case PAGE_READONLY_X: + return PTE_P; + } + + return 0; +} + +/* + * Returns true if the given pagesize is valid + * + * @PageSize: Pagesize to check + */ +static inline BOOLEAN +MmuPageSizeValid(MMU_PAGESIZE PageSize) +{ + switch (PageSize) { + case PAGESIZE_4K: + return true; + default: + return false; + } +} + +/* + * Obtain the toplevel index + */ +static inline PAGE_LEVEL +MmuTopLevel(VOID) +{ + UPTR Cr4; + + ASMV( + "mov %%cr4, %0" + : "=r" (Cr4) + : + : "memory" + ); + + return ISSET(Cr4, CR4_LA57) + ? PAGE_LEVEL_PML5 + : PAGE_LEVEL_PML4; +} + +/* + * Invalidate an entry from the TLB + * + * @Vma: Virtual memory address to flush + */ +static inline VOID +MmuInvlpg(UPTR Vma) +{ + ASMV( + "invlpg (%0)" + : + : "r" (Vma) + : "memory" + ); +} + +/* + * Obtain the page level index + * + * @Vma: Virtual memory address + * @Level: Page level + */ +static inline UQUAD +MmuLevelIndex(UPTR Vma, PAGE_LEVEL Level) +{ + switch (Level) { + case PAGE_LEVEL_PML1: return (Vma >> 12) & 0x1FF; + case PAGE_LEVEL_PML2: return (Vma >> 21) & 0x1FF; + case PAGE_LEVEL_PML3: return (Vma >> 30) & 0x1FF; + case PAGE_LEVEL_PML4: return (Vma >> 39) & 0x1FF; + case PAGE_LEVEL_PML5: return (Vma >> 48) & 0x1FF; + default: return 0; + } +} + +/* + * Extract a page table entry at a specific level + * + * @Vas: Virtual address space containing top-level + * @Vma: Virtual memory address used as key + * @Level: Level to extract + * @Alloc: If true, allocate unset entries + */ +static UPTR * +MmuExtractLevel(MMU_VAS *Vas, UPTR Vma, PAGE_LEVEL Level, BOOLEAN Alloc) +{ + MM_PFN AllocPfn; + UPTR Pma, *PageMap; + PAGE_LEVEL CurrentLevel; + USHORT Index; + + if (Vas == NULL) { + return NULL; + } + + CurrentLevel = MmuTopLevel(); + PageMap = PMA_TO_VMA((Vas->Cr3 & PTE_ADDR_MASK)); + + while (CurrentLevel > Level) { + Index = MmuLevelIndex(Vma, CurrentLevel); + + if (ISSET(PageMap[Index], PTE_P)) { + Pma = PageMap[Index] & PTE_ADDR_MASK; + PageMap = PMA_TO_VMA(Pma); + --CurrentLevel; + continue; + } + + if (!Alloc) { + return NULL; + } + + AllocPfn = MmRequestFrame(); + if (AllocPfn == 0) { + return NULL; + } + + Pma = AllocPfn << LOG2_PAGESIZE; + PageMap[Index] = Pma | (PTE_P | PTE_RW | PTE_US); + + PageMap = PMA_TO_VMA(Pma); + --CurrentLevel; + } + + return PageMap; +} + VOID HalMmuReadVas(MMU_VAS *Result) { @@ -39,3 +216,28 @@ HalMmuWriteVas(MMU_VAS *Vas) : "memory" ); } + +ST_STATUS +HalMmuMapSingle(MMU_VAS *Vas, UPTR Vma, UPTR Pma, USHORT Flags, MMU_PAGESIZE PagSize) +{ + UPTR *PageTable; + USIZE PteFlags; + USHORT Index; + + if (Vas == NULL) { + return STATUS_INVALID_PARAM; + } + + PageTable = MmuExtractLevel(Vas, Vma, PAGE_LEVEL_PML1, true); + if (PageTable == NULL) { + return STATUS_NO_MEMORY; + } + + PteFlags = MmuProtToPte(Flags); + Index = MmuLevelIndex(Vma, PAGE_LEVEL_PML1); + + /* Map the entry and flush the TLB */ + PageTable[Index] = Pma | PteFlags; + MmuInvlpg(Vma); + return STATUS_SUCCESS; +} diff --git a/paw/stos/head/hal/mmu.h b/paw/stos/head/hal/mmu.h index 73b22d8..3ed797b 100644 --- a/paw/stos/head/hal/mmu.h +++ b/paw/stos/head/hal/mmu.h @@ -10,8 +10,18 @@ #define _HAL_MMU_H_ 1 #include +#include #include +#define PAGE_READONLY 0x00 /* Readonly */ +#define PAGE_READONLY_X 0x02 /* Read/execute */ +#define PAGE_READWRITE 0x03 /* Read/write */ +#define PAGE_READWRITE_X 0x04 /* Read/write/execute */ + +typedef enum { + PAGESIZE_4K +} MMU_PAGESIZE; + /* * Read the current virtual address space * @@ -26,4 +36,18 @@ VOID HalMmuReadVas(MMU_VAS *Result); */ VOID HalMmuWriteVas(MMU_VAS *Vas); +/* + * Map a single page of memory + * + * @Vas: Virtual address space to map within + * @Vma: Virtual memory address to map + * @Pma: Physical memory address to map + * @Flags: Flags to map with + * @PageSize: Pagesize to map + */ +ST_STATUS HalMmuMapSingle( + MMU_VAS *Vas, UPTR Vma, UPTR Pma, + USHORT Flags, MMU_PAGESIZE PageSize +); + #endif /* !_HAL_MMU_H_ */