From 8cbe050f35e862bdc59725ac45e1b9c374fc1868 Mon Sep 17 00:00:00 2001 From: Tom-on64 Date: Sun, 22 Mar 2026 14:27:48 +0100 Subject: [PATCH] Implemented 64-bit paging --- docs/paging.txt | 63 +++++++++++++ kernel/include/paging.h | 58 ++++++++---- kernel/src/kernel.c | 5 +- kernel/src/paging.c | 199 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 282 insertions(+), 43 deletions(-) create mode 100644 docs/paging.txt diff --git a/docs/paging.txt b/docs/paging.txt new file mode 100644 index 0000000..d940b12 --- /dev/null +++ b/docs/paging.txt @@ -0,0 +1,63 @@ +How paging works +================ +NOTE: This is mainly so I can wrap my head around paging. + +Table levels +------------ +Since 2^64 is quite a large number, paging uses a bunch of tables to organize +& navigate pages. These are also called layers & 4-layer paging is most common. +* Level 1 - Page Table (PT) +* Level 2 - Page Directory (PD) +* Level 3 - Page Directory Pointer Table (PDPT) +* Level 4 - Page Map Layer 4 (PML4) +* Level 5 - Page Map Layer 5 (PML5) (optional) + +Virtual address +--------------- +A 64-bit virtual address has four parts: +* 47-39 - PML4 index +* 38-30 - PDPT index +* 29-21 - PD index +* 20-12 - PT index +* 11-0 - 4 kiB page offset + +Note that the top 16 bits are unused. These must always be all 0s or all 1s. + +Walking the tables +------------------ +Here are the steps that the MMU will take to translate an address: +1. Find the physical address of the PML4 in the CR3 register +2. Index the PML4 with bits 47-39 to find the paddr of the PDPT +3. Index the PDPT with bits 38-30 to find the paddr of the PD +4. Index the PD with bits 29-21 to find the paddr of the PT +5. Index the PT with bits 20-12 to find the paddr of the Page +6. Offset the Page address by bits 11-0 to find the actual byte + +The flags +--------- +Since all physical addresses in these tables MUST be page (4 kiB) aligned, the +bottom 12 bits will always be zero. These bits are used to store Page Table +Entry (PTE) flags. Since the 64-bit address space is SO large, bits 52-63 are +also used for flags. Here's how a PTE entry looks: +* 0 - Present, 0 - page fault, 1 - page is in RAM +* 1 - Writable, 0 - read-only, 1 - read & write +* 2 - Userspace, 0 - kernel only, 1 - user & kernel +* 3 - Page-level write-through caching +* 4 - Page-level cache disable +* 5 - Accesed, set by CPU when page is read or written to +* 6 - Dirty, set by CPU when page is written to (level 1 only) +* 7 - Page size, 0 - 4 kiB, 1 - 2 MiB or 1 GiB +* 8-11 and 52-62 - Available to be used by kernel +* 63 - No execute, 0 - executable, 1 - not executable + +There are some flags that only apply to certain levels and page sizes, Please +see the diagram at https://wiki.osdev.org/Paging for more info. + +Large pages +----------- +I don't know man. + +Caching +------- +I couldn't tell ya. + diff --git a/kernel/include/paging.h b/kernel/include/paging.h index 0cbd04e..ec1f35c 100644 --- a/kernel/include/paging.h +++ b/kernel/include/paging.h @@ -1,37 +1,55 @@ #ifndef _PAGING_H #define _PAGING_H +#include #include -#define PTE_FLAG_PRESENT (1ULL << 0) // Page is present in the table -#define PTE_FLAG_WRITE (1ULL << 1) // Read-write -#define PTE_FLAG_USER (1ULL << 2) // User-mode (DPL3) access allowed -#define PTE_FLAG_PWT (1ULL << 3) // Page write-thru -#define PTE_FLAG_NOCACHE (1ULL << 4) // Cache disable -#define PTE_FLAG_ACCESSED (1ULL << 5) // Indicates whether page was accessed -#define PTE_FLAG_DIRTY (1ULL << 6) // Page size (valid for PD and PDPT only) -#define PTE_FLAG_PAT (1ULL << 7) // Page Attribute Table (valid for PT only) -#define PTE_FLAG_GLOBAL (1ULL << 8) // Indicates the page is globally cached -#define PTE_FLAG_SHARED (1ULL << 9) // Userland page is shared -#define PTE_FLAG_NOEXEC (1ULL << 63) // No execute +#define PTE_FLAG_PRESENT (1UL << 0) // Page is present in the table +#define PTE_FLAG_WRITE (1UL << 1) // Read-write +#define PTE_FLAG_USER (1UL << 2) // User-mode (DPL3) access allowed +#define PTE_FLAG_PWT (1UL << 3) // Page write-thru +#define PTE_FLAG_NOCACHE (1UL << 4) // Cache disable +#define PTE_FLAG_ACCESSED (1UL << 5) // Indicates whether page was read/written +#define PTE_FLAG_DIRTY (1UL << 6) // Indicates whether page was written to (valid for PT only) +#define PTE_FLAG_PAT (1UL << 7) // Page Attribute Table (valid for PT only) +#define PTE_FLAG_PAGESIZE (1UL << 7) // Page size (valid for PD and PDPT only) +#define PTE_FLAG_RSVD (1UL << 7) // No idea (valid for PML4 and PML5 only) +#define PTE_FLAG_GLOBAL (1UL << 8) // Global caching or sum (valid for PT only) +#define PTE_FLAG_NOEXEC (1UL << 63) // No execute (the XD bit) // Region caching according to the Limine protocol #define PTE_FLAG_CACHE (PTE_FLAG_PWT | PTE_FLAG_PAT) -#define PTE_ADDR_MASK 0x000FFFFFFFFFFFFFF000ULL -#define PTE_ADDR(_pte) ((_pte) & PAGE_ADDR_MASK) +#define PTE_ADDR_MASK 0x000FFFFFFFFFF000UL +#define PTE_ADDR(_pte) ((_pte) & PTE_ADDR_MASK) + +#define PML4_IDX(_ptr) (((_ptr) >> 39) & 0x1FFUL) +#define PDPT_IDX(_ptr) (((_ptr) >> 30) & 0x1FFUL) +#define PD_IDX(_ptr) (((_ptr) >> 21) & 0x1FFUL) +#define PT_IDX(_ptr) (((_ptr) >> 12) & 0x1FFUL) + +#define TO_VIRT(_ptr) ((void*)((uintptr_t)(_ptr) + bootloader.hhdm_offset)) +#define TO_PHYS(_ptr) ((void*)((uintptr_t)(_ptr) - bootloader.hhdm_offset)) #define PAGE_SIZE 0x1000 #define PAGE_SIZE_LARGE 0x200000 #define PAGE_SIZE_HUGE 0x40000000 -// From the Linux kernel source // WARN: This will only invalidate on one core -static inline void pag_invalidate(size_t vaddr) { __asm__ volatile("invlpg (%0)" :: "r" (vaddr) : "memory"); } +static inline void pag_invalidate(void* vaddr) { __asm__ volatile("invlpg (%0)" :: "r" (vaddr) : "memory"); } +static inline bool pag_is_user_addr(void* vaddr) { return (uintptr_t)vaddr < 0x0000800000000000UL; } -int pag_init(void); -void pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags); -void pag_unmap(uint64_t* pml4, void* vaddr); -void pag_switch(uint64_t* pml4); -uint64_t* pag_new_pml(void); +// NOTE: All uint64_t* are VIRTUAL addresses +int pag_init(void); +uint64_t* pag_get_pte(uint64_t* pml4, void* vaddr); +void pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags); +void pag_map_region(uint64_t* pml4, void* vaddr, void* paddr, size_t count, uint64_t flags); +void pag_unmap(uint64_t* pml4, void* vaddr); +void pag_unmap_region(uint64_t* pml4, void* vaddr, size_t count); +void pag_protect(uint64_t* pml4, void* vaddr, uint64_t flags); +void* pag_get_phys(uint64_t* pml4, void* vaddr); +void pag_set_pml4(uint64_t* pml4); +uint64_t* pag_get_pml4(void); +uint64_t* pag_get_next_tab(uint64_t* tab, offset_t index); +uint64_t* pag_new_next_tab(uint64_t* tab, offset_t index); #endif diff --git a/kernel/src/kernel.c b/kernel/src/kernel.c index 89d850d..b759d5f 100644 --- a/kernel/src/kernel.c +++ b/kernel/src/kernel.c @@ -34,10 +34,7 @@ void _start(void) { // Memory management if (pmm_init() != 0) panic("Failed to initalize Physical Memory Manager."); - void* paddr = pmm_alloc(); - pmm_free(paddr); - - //if (pag_init() != 0) panic("Failed to initalize Paging."); + if (pag_init() != 0) panic("Failed to initalize Paging."); //if (kmalloc_init() != 0) panic("Failed to initialize kernel heap."); // Tasking diff --git a/kernel/src/paging.c b/kernel/src/paging.c index 6920092..183d868 100644 --- a/kernel/src/paging.c +++ b/kernel/src/paging.c @@ -1,35 +1,196 @@ -#include "serial.h" #include #include +#include #include +#include #include #include -size_t* pag_globalpd = NULL; +// VIRTUAL address of the kernels PML4 +uint64_t* pag_kernel_pml4 = NULL; -void pag_invalidate(size_t vaddr) { __asm__ volatile ("invlpg %0" ::"m"(vaddr)); } +int pag_init(void) { + pag_kernel_pml4 = TO_VIRT(pmm_alloc()); + if (pag_kernel_pml4 == NULL) { + debugf("[pag] Failed to allocate kernel PML4. Not enough memory.\n"); + return 1; + } + memset(pag_kernel_pml4, 0, PAGE_SIZE); -int pag_init() { - size_t pd_phys = 0; - __asm__ volatile ("movq %%cr3, %0" : "=r"(pd_phys)); - if (!pd_phys) return 1; + pag_map_region( + pag_kernel_pml4, + (void*)bootloader.kernel_virt_base, + (void*)bootloader.kernel_phys_base, + dceil(bootloader.executable->size, PAGE_SIZE), + PTE_FLAG_WRITE | PTE_FLAG_PRESENT + ); - size_t pd_virt = pd_phys + bootloader.hhdm_offset; - pag_globalpd = (size_t*)pd_virt; + for (size_t i = 0; i < bootloader.mm_entry_count; i++) { + struct limine_memmap_entry* entry = bootloader.mm_entries[i]; + + uint64_t flags = 0; + switch (entry->type) { + case LIMINE_MEMMAP_USABLE: flags = PTE_FLAG_WRITE; break; + case LIMINE_MEMMAP_RESERVED: continue; + case LIMINE_MEMMAP_ACPI_RECLAIMABLE: flags = PTE_FLAG_WRITE; break; + case LIMINE_MEMMAP_ACPI_NVS: break; + case LIMINE_MEMMAP_BAD_MEMORY: continue; + case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: flags = PTE_FLAG_WRITE; break; + case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: flags = PTE_FLAG_WRITE; break; + case LIMINE_MEMMAP_FRAMEBUFFER: flags = PTE_FLAG_WRITE | PTE_FLAG_PWT; break; + case LIMINE_MEMMAP_RESERVED_MAPPED: flags = 0; break; + default: + debugf( + "[pag] %p - %p: Unknown memmap type %d. Will remain unmapped.\n", + entry->base, entry->base + entry->length, entry->type + ); + continue; + } + + pag_map_region( + pag_kernel_pml4, + (void*)(entry->base + bootloader.hhdm_offset), (void*)entry->base, + dceil(entry->length, PAGE_SIZE), + flags | PTE_FLAG_PRESENT + ); + } + + debugf("[pag] Switching to kernel PML4 at %p.\n", pag_kernel_pml4); + pag_set_pml4(pag_kernel_pml4); debugf("[pag] Paging initialized."); - return 0; } -void pag_map(size_t vaddr, size_t paddr, size_t flags); -size_t pag_umap(size_t vaddr); -size_t pag_virt_to_phys(size_t vaddr); -size_t* pag_get_pagedir(); -size_t* pag_get_task_pagedir(void* task); -void pag_set_pagedir(size_t* pagedir); -size_t* pag_alloc_pagedir(); -void pag_free_pagedir(size_t* pagedir); -void pag_invalidate(size_t vaddr); +uint64_t* pag_get_pte(uint64_t* pml4, void* vaddr) { + uintptr_t v = (uintptr_t)vaddr; + if (v % PAGE_SIZE != 0) { + debugf("[pag] Could not get PTE from non-aligned virtual address %p.\n", vaddr); + return NULL; + } + + uint64_t* pdpt = pag_get_next_tab(pml4, PML4_IDX(v)); + if (pdpt == NULL) { + debugf("[pag] Could not get PTE. %p was not found in PML4.\n"); + return NULL; + } + uint64_t* pd = pag_get_next_tab(pdpt, PDPT_IDX(v)); + if (pd == NULL) { + debugf("[pag] Could not get PTE. %p was not found in PDPT.\n"); + return NULL; + } + uint64_t* pt = pag_get_next_tab(pd, PD_IDX(v)); + if (pt == NULL) { + debugf("[pag] Could not get PTE. %p was not found in PD.\n"); + return NULL; + } + + return &pt[PT_IDX(v)]; +} + +uint64_t* pag_new_pte(uint64_t* pml4, void* vaddr) { + uintptr_t v = (uintptr_t)vaddr; + if (v % PAGE_SIZE != 0) { + debugf("[pag] Could not create PTE for non-aligned virtual address %p.\n", vaddr); + return NULL; + } + + uint64_t* pdpt = pag_new_next_tab(pml4, PML4_IDX(v)); + if (pdpt == NULL) { + debugf("[pag] Failed to create PDPT. Not enough memory.\n"); + return NULL; + } + uint64_t* pd = pag_new_next_tab(pdpt, PDPT_IDX(v)); + if (pd == NULL) { + debugf("[pag] Failed to create PD. Not enough memory.\n"); + if (pdpt != NULL) pmm_free(pdpt); + return NULL; + } + uint64_t* pt = pag_new_next_tab(pd, PD_IDX(v)); + if (pt == NULL) { + debugf("[pag] Failed to create PT. Not enough memory.\n"); + if (pd != NULL) pmm_free(pd); + if (pdpt != NULL) pmm_free(pdpt); + return NULL; + } + + return &pt[PT_IDX(v)]; +} + +void pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags) { + uintptr_t p = (uintptr_t)paddr; + if (p % PAGE_SIZE != 0) { + debugf("[pag] Could not map %p to %p. Physical address is not aligned.\n", paddr, vaddr); + return; + } + + uint64_t* page = pag_new_pte(pml4, vaddr); + *page = PTE_ADDR(p) | flags | PTE_FLAG_PRESENT; + pag_invalidate(vaddr); +} + +void pag_map_region(uint64_t* pml4, void* vaddr, void* paddr, size_t count, uint64_t flags) { + for (size_t i = 0; i < count; i++) { + pag_map(pml4, vaddr + i * PAGE_SIZE, paddr + i * PAGE_SIZE, flags); + } +} + +// TODO: This is a pmm memory leak. If all pages in a PT are unmapped, remove +// that PT from it's PD. This applies to PDs & PDPTs too. +void pag_unmap(uint64_t* pml4, void* vaddr) { + uint64_t* pte = pag_get_pte(pml4, vaddr); + *pte = 0; + pag_invalidate(vaddr); +} + +void pag_unmap_region(uint64_t* pml4, void* vaddr, size_t count) { + for (size_t i = 0; i < count; i++) { + pag_unmap(pml4, vaddr + i * PAGE_SIZE); + } +} + +void pag_protect(uint64_t* pml4, void* vaddr, uint64_t flags) { + uint64_t* pte = pag_get_pte(pml4, vaddr); + *pte = PTE_ADDR(*pte) | flags | PTE_FLAG_PRESENT; +} + +void* pag_get_phys(uint64_t* pml4, void* vaddr) { + uint64_t* pte = pag_get_pte(pml4, vaddr); + return (void*)PTE_ADDR(*pte); +} + +void pag_set_pml4(uint64_t* pml4) { + __asm__ volatile("mov %0, %%cr3" :: "r" (TO_PHYS(pml4)) : "memory"); +} + +uint64_t* pag_get_pml4(void) { + uint64_t* pml4; + __asm__ volatile("movq %%cr3, %0" : "=r"(pml4) :: "memory"); + return TO_VIRT(pml4); +} + +uint64_t* pag_get_next_tab(uint64_t* tab, offset_t index) { + if (index >= 512 || tab == NULL) return NULL; + else if (tab[index] & PTE_FLAG_PRESENT) return TO_VIRT(tab[index] & PTE_ADDR_MASK); + else return NULL; +} + +uint64_t* pag_new_next_tab(uint64_t* tab, offset_t index) { + if (tab == NULL) return NULL; + uint64_t* vaddr = pag_get_next_tab(tab, index); + if (vaddr != NULL) return vaddr; + + void* paddr = pmm_alloc(); + if (!paddr) { + debugf("[pag] Failed to allocate page for new table.\n"); + return NULL; + } + + vaddr = TO_VIRT(paddr); + memset(vaddr, 0, PAGE_SIZE); + + tab[index] = (uintptr_t)paddr | PTE_FLAG_PRESENT | PTE_FLAG_WRITE | PTE_FLAG_USER; + return vaddr; +}