summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--na/include/lume/vmm.h83
-rw-r--r--na/mm/vma.cc181
-rw-r--r--na/mm/vmm.cc124
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();
+}
+
+
+