diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-04-21 23:22:52 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-04-21 23:22:52 +0800 |
| commit | ab2c0a8ea472679c846e74ca40823091becc5bf0 (patch) | |
| tree | eeb05d4f93dc6ab901a3a5a1562604f02ed9c885 | |
| parent | 0a657596e19281f57ae74fd00e38cf25fee4d8cd (diff) | |
| download | lume-ab2c0a8ea472679c846e74ca40823091becc5bf0.tar.gz lume-ab2c0a8ea472679c846e74ca40823091becc5bf0.tar.xz | |
feat: add Virtual Memory Manager and Virtual Memory Area management
| -rw-r--r-- | na/include/lume/vmm.h | 83 | ||||
| -rw-r--r-- | na/mm/vma.cc | 181 | ||||
| -rw-r--r-- | na/mm/vmm.cc | 124 |
3 files changed, 388 insertions, 0 deletions
diff --git a/na/include/lume/vmm.h b/na/include/lume/vmm.h new file mode 100644 index 0000000..ea32332 --- /dev/null +++ b/na/include/lume/vmm.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * vmm.h — Virtual Memory Manager + * + * Managing kernel and user virtual address spaces. + * + * Reference: docs/specs/vmm.md + */ + +#include <lume/types.h> +#include <lume/rbtree.h> +#include <kernel/sync/spinlock.h> + +/* ------------------------------------------------------------------ */ +/* MI-layer permission flags (architecture-independent) */ +/* */ +/* These are used by VMA and vmm_map_user / vmm_handle_page_fault. */ +/* The pmap MD layer translates them to hardware PTE bits. */ +/* ------------------------------------------------------------------ */ +inline constexpr uint64 VM_READ = (1ULL << 0); +inline constexpr uint64 VM_WRITE = (1ULL << 1); +inline constexpr uint64 VM_EXEC = (1ULL << 2); +inline constexpr uint64 VM_USER = (1ULL << 3); + +/* Forward declarations */ +struct VmSpace; +struct VmAreaStruct; + +/* + * Virtual Memory Area (VMA) + * Represents a contiguous range [start, end) of uniform permissions. + */ +struct VmAreaStruct { + uint64 start; // Start VA (page aligned) + uint64 end; // End VA (exclusive, page aligned) + uint64 perm; // VM_READ | VM_WRITE | VM_EXEC | VM_USER + struct rb_node rb; // Linkage in VmSpace::vma_tree + VmSpace* mm; // Back pointer to the parent address space +}; + +/* + * Address Space (VmSpace) + * Represents a user process's complete virtual memory mapping. + * + * Lock contract: + * Holds: lock (internal spinlock) + * May acquire: PMM global lock (via pmm_alloc_frame inside page fault) + * Callers must NOT hold: PMM lock (lock ordering: VmSpace -> PMM) + */ +struct VmSpace { + uint64 root_pa; // SV39 root page table physical address + struct rb_root vma_tree; // Red-black tree of VmAreaStructs + Spinlock lock; // Protects vma_tree mutations + + // Create a brand new VmSpace (allocates root pagetable) + static VmSpace* create(); + + // Destroy VmSpace, unmapping all VMAs and freeing pagetables + void destroy(); +}; + +/* Initialize kernel page table (Phase 1) */ +void vmm_init(); + +/* Switch AP to the kernel page table built by BSP (Phase 1) */ +void vmm_init_ap(); + +/* Map MMIO device region to the active kernel page table */ +void vmm_map_kernel_mmio(uint64 pa, uint64 size); + +/* User space memory management (VMA & Demand Paging) */ +int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); +int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len); + +/* Handle user page fault (lookup VMA -> allocate frame -> map) */ +int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, uint64 cause); + +/* Clone an address space for fork() */ +VmSpace* vmm_clone(VmSpace* parent_space); diff --git a/na/mm/vma.cc b/na/mm/vma.cc new file mode 100644 index 0000000..d05a662 --- /dev/null +++ b/na/mm/vma.cc @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * vma.cc — Virtual Memory Area management (MI layer) + * + * Manages user address spaces using a red-black tree of VMA regions. + * Implements demand paging: vmm_map_user records intent only; + * physical pages are allocated on page fault via vmm_handle_page_fault. + * + * MI/MD separation: this file NEVER references pmap::PTE_* constants. + * Permission translation is delegated to pmap::vm_perm_to_pte(). + * + * Reference: docs/specs/vmm.md + */ + +#include <lume/vmm.h> +#include <lume/addr.h> +#include <lume/config.h> +#include <lume/errno.h> +#include <lume/pmm.h> +#include <lume/new.h> +#include <lume/panic.h> +#include <arch/pmap.h> + +/* ------------------------------------------------------------------ */ +/* Rbtree callbacks for VMA interval tree */ +/* ------------------------------------------------------------------ */ + +static bool vma_less(struct rb_node* node, const struct rb_node* parent) { + VmAreaStruct* n = reinterpret_cast<VmAreaStruct*>( + reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); + const VmAreaStruct* p = reinterpret_cast<const VmAreaStruct*>( + reinterpret_cast<const char*>(parent) - __builtin_offsetof(VmAreaStruct, rb)); + return n->start < p->start; +} + +static int vma_cmp(const void* key, const struct rb_node* node) { + uint64 addr = *static_cast<const uint64*>(key); + const VmAreaStruct* n = reinterpret_cast<const VmAreaStruct*>( + reinterpret_cast<const char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); + if (addr < n->start) return -1; + if (addr >= n->end) return 1; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* VmSpace lifecycle */ +/* ------------------------------------------------------------------ */ + +VmSpace* VmSpace::create() { + VmSpace* space = new VmSpace; + if (!space) return nullptr; + + space->root_pa = pmap::create(); + if (!space->root_pa) { + delete space; + return nullptr; + } + + space->vma_tree = RB_ROOT; + space->lock.init("VmSpace"); + return space; +} + +void VmSpace::destroy() { + /* Drain VMA tree under lock, then release BEFORE delete this */ + lock.acquire(); + struct rb_node* node = rb_first(&vma_tree); + while (node) { + VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>( + reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); + node = rb_next(node); + rb_erase(&vma->rb, &vma_tree); + delete vma; + } + uint64 root = root_pa; + lock.release(); + + /* Destroy hardware page tables outside the lock */ + pmap::destroy(root); + + /* Finally free VmSpace itself */ + delete this; +} + +/* ------------------------------------------------------------------ */ +/* Public API: VMA manipulation */ +/* ------------------------------------------------------------------ */ + +int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) { + if (!space) return -EINVAL; + + va = page_align_down(va); + len = page_align_up(len); + if (len == 0) return 0; + + VmAreaStruct* vma = new VmAreaStruct; + if (!vma) return -ENOMEM; + vma->start = va; + vma->end = va + len; + vma->perm = perm | VM_USER; // Enforce user-mode access (MI flag) + vma->mm = space; + + LockGuard guard(space->lock); + rb_add(&vma->rb, &space->vma_tree, vma_less); + return 0; +} + +int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len) { + if (!space) return -EINVAL; + va = page_align_down(va); + len = page_align_up(len); + uint64 end = va + len; + + LockGuard guard(space->lock); + struct rb_node* node = rb_first(&space->vma_tree); + while (node) { + VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>( + reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); + struct rb_node* nxt = rb_next(node); + if (vma->start >= va && vma->end <= end) { + rb_erase(node, &space->vma_tree); + delete vma; + } + node = nxt; + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Page fault handler (demand paging) */ +/* ------------------------------------------------------------------ */ + +int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, uint64 cause) { + if (!space) return -EINVAL; + + uint64 aligned_addr = page_align_down(fault_addr); + + LockGuard guard(space->lock); + struct rb_node* node = rb_find(&fault_addr, &space->vma_tree, vma_cmp); + if (!node) { + return -EFAULT; // No VMA covers this address → segfault + } + + VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>( + reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); + + (void)cause; // TODO: differentiate read/write/exec faults + + /* Check if page is already mapped */ + uint64 paddr; + if (pmap::lookup(space->root_pa, aligned_addr, &paddr)) { + return 0; // Already mapped + } + + /* Allocate physical frame */ + Frame* f = pmm_alloc_frame(); + if (!f) return -ENOMEM; + + uint64 pa = frame_to_pa(f); + + /* Translate MI permission flags to hardware PTE bits (MD layer) */ + uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); + + if (pmap::map(space->root_pa, aligned_addr, pa, pte_perm) != 0) { + pmm_free_frame(f); + return -ENOMEM; + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Fork / Clone (stub — requires COW infrastructure) */ +/* ------------------------------------------------------------------ */ + +VmSpace* vmm_clone(VmSpace* parent) { + (void)parent; + return nullptr; +} diff --git a/na/mm/vmm.cc b/na/mm/vmm.cc new file mode 100644 index 0000000..54e9b15 --- /dev/null +++ b/na/mm/vmm.cc @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * vmm.cc — Virtual Memory Manager (Phase 1: Kernel Page Table) + * + * Builds a fine-grained kernel page table (4KB pages, precise + * permissions per section), activates it, and retires the early_pgdir + * identity mapping from entry.S. + * + * This module is machine-independent (MI). All architecture-specific + * page table manipulation goes through the pmap:: namespace. + * + * Reference: docs/specs/vmm.md + */ + +#include <lume/types.h> +#include <lume/addr.h> +#include <lume/pmm.h> +#include <lume/panic.h> +#include <lume/fdt.h> +#include <lume/vmm.h> +#include <arch/pmap.h> +#include <arch/mmu.h> + +/* Linker-exported section boundaries (virtual addresses) */ +extern "C" char _stext[], _etext[]; +extern "C" char _srodata[], _erodata[]; +extern "C" char _sdata[], _edata[]; +extern "C" char _sbss[], _ebss[]; +extern "C" char _kernel_end[]; + +/* Global kernel page table root PA */ +static uint64 g_kernel_pgtbl = 0; + +/* Convert a linker symbol to PA. Handles both VA and PA cases. */ +static inline uint64 sym_to_pa(const void *sym) +{ + return ensure_pa(reinterpret_cast<uint64>(sym)); +} + +/* Map a PA range into the kernel page table with given permissions. + * All addresses must be page-aligned. Panics on failure. */ +static void map_range(uint64 root, uint64 pa_start, uint64 pa_end, uint64 perm) +{ + for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { + uint64 va = pa_to_va(pa); + int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); + if (ret != 0) + kernel_panic("vmm_init: pmap::map failed"); + } +} + + + +void vmm_init() +{ + /* 1. Allocate new root page table */ + uint64 root = pmap::create(); + if (!root) + kernel_panic("vmm_init: failed to create root page table"); + + /* 2. Compute PA boundaries of each kernel section */ + uint64 text_start = page_align_down(sym_to_pa(_stext)); + uint64 text_end = page_align_up(sym_to_pa(_etext)); + uint64 ro_start = page_align_down(sym_to_pa(_srodata)); + uint64 ro_end = page_align_up(sym_to_pa(_erodata)); + uint64 data_start = page_align_down(sym_to_pa(_sdata)); + uint64 bss_end = page_align_up(sym_to_pa(_ebss)); + + /* Memory extent from PMM */ + uint64 mem_base = pmm_get_mem_base(); + uint64 mem_end = mem_base + pmm_get_mem_size(); + + /* 3. Map kernel .text (RX) */ + map_range(root, text_start, text_end, + pmap::PTE_R | pmap::PTE_X); + + /* 4. Map .rodata (R) */ + map_range(root, ro_start, ro_end, + pmap::PTE_R); + + /* 5. Map .data + .bss (RW) */ + map_range(root, data_start, bss_end, + pmap::PTE_R | pmap::PTE_W); + + /* 6. Map remaining physical memory after kernel (RW) + * This covers frame_map, slab objects, and all free pages. */ + uint64 after_kernel = page_align_up(sym_to_pa(_kernel_end)); + if (after_kernel < mem_end) { + map_range(root, after_kernel, mem_end, + pmap::PTE_R | pmap::PTE_W); + } + /* 7. MMIO regions will be mapped later via vmm_map_kernel_mmio by architecture/drivers */ + + /* 8. Activate the new page table */ + pmap::activate(root); + + g_kernel_pgtbl = root; +} + +void vmm_init_ap() +{ + /* AP just switches to the kernel page table built by BSP */ + if (!g_kernel_pgtbl) + kernel_panic("vmm_init_ap: kernel page table not ready"); + pmap::activate(g_kernel_pgtbl); +} + +void vmm_map_kernel_mmio(uint64 pa, uint64 size) +{ + if (!g_kernel_pgtbl) + kernel_panic("vmm_map_kernel_mmio: kernel page table not ready"); + + uint64 aligned_pa = page_align_down(pa); + uint64 aligned_size = page_align_up(pa + size) - aligned_pa; + + map_range(g_kernel_pgtbl, aligned_pa, aligned_pa + aligned_size, + pmap::PTE_R | pmap::PTE_W | pmap::PTE_G); + arch::mmu::flush_tlb_all(); +} + + + |
