summaryrefslogtreecommitdiff
path: root/lume-legacy/src/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'lume-legacy/src/kernel')
-rw-r--r--lume-legacy/src/kernel/bio.cc139
-rw-r--r--lume-legacy/src/kernel/config.cc13
-rw-r--r--lume-legacy/src/kernel/cxx.cc78
-rw-r--r--lume-legacy/src/kernel/fdt.cc273
-rw-r--r--lume-legacy/src/kernel/initcode.S11
-rw-r--r--lume-legacy/src/kernel/main.cc79
-rw-r--r--lume-legacy/src/kernel/mm/pmm.cc305
-rw-r--r--lume-legacy/src/kernel/mm/slab.cc259
-rw-r--r--lume-legacy/src/kernel/mm/vm.cc459
-rw-r--r--lume-legacy/src/kernel/proc/exec.cc260
-rw-r--r--lume-legacy/src/kernel/proc/proc.cc654
-rw-r--r--lume-legacy/src/kernel/proc/swtch.S37
-rw-r--r--lume-legacy/src/kernel/sbi.cc52
-rw-r--r--lume-legacy/src/kernel/spinlock.cc199
-rw-r--r--lume-legacy/src/kernel/syscall.cc698
-rw-r--r--lume-legacy/src/kernel/timer.cc66
-rw-r--r--lume-legacy/src/kernel/trap/handler.cc217
-rw-r--r--lume-legacy/src/kernel/trap/trampoline.S101
-rw-r--r--lume-legacy/src/kernel/trap/trap.S90
19 files changed, 3990 insertions, 0 deletions
diff --git a/lume-legacy/src/kernel/bio.cc b/lume-legacy/src/kernel/bio.cc
new file mode 100644
index 0000000..5f1aae9
--- /dev/null
+++ b/lume-legacy/src/kernel/bio.cc
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "kernel/buf.h"
+#include "kernel/spinlock.h"
+#include "kernel/riscv.h"
+#include "drivers/virtio.h"
+#include "drivers/uart.h"
+
+namespace BufferCache
+{
+ struct
+ {
+ Spinlock lock;
+ struct Buf buf[NBUF];
+ struct Buf head;
+ } bcache;
+
+ void init()
+ {
+ bcache.lock.init("bcache");
+ // Initialize LRU doubly circular linked list
+ bcache.head.prev = &bcache.head;
+ bcache.head.next = &bcache.head;
+ for (struct Buf *b = bcache.buf; b < bcache.buf + NBUF; ++b)
+ {
+ b->lock.init("buffer");
+ b->next = bcache.head.next;
+ b->prev = &bcache.head;
+ bcache.head.next->prev = b;
+ bcache.head.next = b;
+ }
+ Drivers::uart_puts("[Bio] Buffer cache initialized.\n");
+ }
+
+ static struct Buf* bget(uint32 dev, uint32 blockno)
+ {
+ struct Buf* b;
+ bcache.lock.acquire();
+
+ // Attempt to hit the cache
+ for (b = bcache.head.next; b != &bcache.head; b = b->next)
+ {
+ if (b->dev == dev && b->blockno == blockno)
+ {
+ b->refcnt++;
+ bcache.lock.release();
+ b->lock.acquire(); // Acquire Sleep Lock
+ return b;
+ }
+ }
+
+ // Cache miss, evicting LRU
+ for (b = bcache.head.prev; b != &bcache.head; b = b->prev)
+ {
+ if (b->refcnt == 0)
+ {
+ b->dev = dev;
+ b->blockno = blockno;
+ b->valid = 0;
+ b->refcnt = 1;
+ bcache.lock.release();
+ b->lock.acquire();
+ return b;
+ }
+ }
+
+ Drivers::uart_puts("panic: bget: no buffers\n");
+ while (1)
+ ;
+ }
+
+ struct Buf* bread(uint32 dev, uint32 blockno)
+ {
+ struct Buf *b = bget(dev, blockno);
+
+ if (!b->valid)
+ {
+ VirtIO::rw(0, b->blockno, (char *)b->data);
+ b->valid = 1;
+ }
+
+ return b;
+ }
+
+ void bwrite(struct Buf *b)
+ {
+ if (!b->lock.holding())
+ {
+ Drivers::uart_puts("panic: bwrite\n");
+ while (1)
+ ;
+ }
+ VirtIO::rw(1, b->blockno, (char *)b->data);
+ }
+
+ void brelse(struct Buf *b)
+ {
+ if (!b->lock.holding())
+ {
+ Drivers::uart_puts("panic: brelse\n");
+ while (1)
+ ;
+ }
+ b->lock.release();
+
+ bcache.lock.acquire();
+ b->refcnt--;
+
+ if (b->refcnt == 0)
+ {
+ b->next->prev = b->prev;
+ b->prev->next = b->next;
+
+ b->next = bcache.head.next;
+ b->prev = &bcache.head;
+ bcache.head.next->prev = b;
+ bcache.head.next = b;
+ }
+
+ bcache.lock.release();
+ }
+
+ void bpin(struct Buf *b)
+ {
+ bcache.lock.acquire();
+ b->refcnt++;
+ bcache.lock.release();
+ }
+
+ void bunpin(struct Buf* b)
+ {
+ bcache.lock.acquire();
+ b->refcnt --;
+ bcache.lock.release();
+ }
+
+} // namespace BufferCache
diff --git a/lume-legacy/src/kernel/config.cc b/lume-legacy/src/kernel/config.cc
new file mode 100644
index 0000000..36858a1
--- /dev/null
+++ b/lume-legacy/src/kernel/config.cc
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+
+uint64 g_uart_base = 0;
+// uint64 g_clint_base = 0;
+uint64 g_plic_base = 0;
+uint64 g_ram_start = 0;
+uint64 g_ram_size = 0;
+
+extern "C" uint64 g_dtb_addr; \ No newline at end of file
diff --git a/lume-legacy/src/kernel/cxx.cc b/lume-legacy/src/kernel/cxx.cc
new file mode 100644
index 0000000..00f2799
--- /dev/null
+++ b/lume-legacy/src/kernel/cxx.cc
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+#include "kernel/pmm.h"
+#include "drivers/uart.h"
+
+/*
+ * C++ Kernel Runtime Support
+ *
+ * * Strategy: Map directly to PMM::alloc_page().
+ * Pros: Simple, works immediately for VFS.
+ * Cons: Allocates 4KB for every object (wasteful for small objects),
+ * but acceptable for current stage.
+ */
+
+void *operator new(unsigned long size)
+{
+ // Safety check: PMM allocates 4096 bytes max per call
+ if (size > 4096)
+ {
+ Drivers::uart_puts("panic: new allocation too big (max 4096)\n");
+ while (1)
+ ;
+ }
+
+ void *p = PMM::alloc_page();
+ if (!p)
+ {
+ Drivers::uart_puts("panic: new out of memory\n");
+ while (1)
+ ;
+ }
+ // PMM::alloc_page returns zeroed memory usually,
+ // but standard 'new' doesn't guarantee it.
+ // The constructor will handle initialization.
+ return p;
+}
+
+void *operator new[](unsigned long size)
+{
+ return operator new(size);
+}
+
+void operator delete(void *p)
+{
+ if (p)
+ PMM::free_page(p);
+}
+
+void operator delete[](void *p)
+{
+ if (p)
+ PMM::free_page(p);
+}
+
+// C++14 Sized Deallocation (Required by modern compilers)
+// The compiler optimizes 'delete' calls to pass the size of the object
+void operator delete(void *p, unsigned long size)
+{
+ (void)size; // We ignore size because PMM always frees a full page
+ if (p)
+ PMM::free_page(p);
+}
+
+void operator delete[](void *p, unsigned long size)
+{
+ (void)size;
+ if (p)
+ PMM::free_page(p);
+}
+
+void *operator new(unsigned long size, void *ptr)
+{
+ (void)size;
+ return ptr;
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/fdt.cc b/lume-legacy/src/kernel/fdt.cc
new file mode 100644
index 0000000..ae09c84
--- /dev/null
+++ b/lume-legacy/src/kernel/fdt.cc
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard Qin
+ * LumeOS - FDT Parser
+ *
+ * Parses the Flattened Device Tree to discover hardware.
+ * Populates g_devices for use by all drivers.
+ */
+#include "common/types.h"
+#include "kernel/fdt.h"
+#include "lib/string.h"
+
+// Global device registry
+DeviceRegistry g_devices;
+
+// Keep g_dtb_addr for PMM to protect DTB pages
+// g_dtb_addr is defined in entry.S, g_uart_base in config.cc
+extern "C" uint64 g_dtb_addr;
+extern uint64 g_uart_base;
+
+static inline uint32 bswap(uint32 x)
+{
+ return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) |
+ ((x & 0xFF0000) >> 8) | ((x >> 24) & 0xFF);
+}
+
+struct fdt_header
+{
+ uint32 magic;
+ uint32 totalsize;
+ uint32 off_dt_struct;
+ uint32 off_dt_strings;
+ uint32 off_mem_rsvmap;
+ uint32 version;
+ uint32 last_comp_version;
+ uint32 boot_cpuid_phys;
+ uint32 size_dt_strings;
+ uint32 size_dt_struct;
+};
+
+#define FDT_MAGIC 0xd00dfeed
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_NOP 4
+#define FDT_END 9
+
+// Check if 'str' starts with 'prefix'
+static bool starts_with(const char *str, const char *prefix)
+{
+ while (*prefix)
+ {
+ if (*str != *prefix)
+ return false;
+ str++;
+ prefix++;
+ }
+ return true;
+}
+
+// Check if 'haystack' contains 'needle' as a substring within 'len' bytes
+// (compatible strings may have multiple null-separated entries)
+static bool compat_match(const uint8 *data, uint32 len, const char *needle)
+{
+ uint32 nlen = strlen(needle);
+ for (uint32 i = 0; i + nlen <= len; i++)
+ {
+ if (memcmp(data + i, needle, nlen) == 0)
+ return true;
+ }
+ return false;
+}
+
+// Add a MMIO region to the registry (avoids duplicates)
+static void add_mmio_region(uint64 base, uint64 size)
+{
+ if (base == 0 || size == 0)
+ return;
+ // Only track I/O regions below RAM
+ if (base >= 0x80000000ULL)
+ return;
+ if (g_devices.mmio_count >= DeviceRegistry::MAX_MMIO)
+ return;
+
+ // Check for overlap/duplicate
+ for (int i = 0; i < g_devices.mmio_count; i++)
+ {
+ if (g_devices.mmio_regions[i].base == base)
+ return;
+ }
+
+ g_devices.mmio_regions[g_devices.mmio_count].base = base;
+ g_devices.mmio_regions[g_devices.mmio_count].size = size;
+ g_devices.mmio_count++;
+}
+
+void fdt_parse(uint64 dtb)
+{
+ // Clear registry
+ memset(&g_devices, 0, sizeof(g_devices));
+
+ if (dtb == 0)
+ return;
+
+ g_dtb_addr = dtb;
+
+ struct fdt_header *header = (struct fdt_header *)dtb;
+ if (bswap(header->magic) != FDT_MAGIC)
+ return;
+
+ uint8 *struct_ptr = (uint8 *)(dtb + bswap(header->off_dt_struct));
+ char *strings_ptr = (char *)(dtb + bswap(header->off_dt_strings));
+
+ // Parsing state
+ char current_node_name[64];
+ current_node_name[0] = '\0';
+
+ // Per-node state
+ uint64 current_reg_addr = 0;
+ uint64 current_reg_size = 0;
+ uint32 current_irq = 0;
+ const uint8 *current_compat = nullptr;
+ uint32 current_compat_len = 0;
+ bool has_reg = false;
+ bool has_compat = false;
+ // bool has_irq = false; // Unused
+
+ // Depth tracking (for #address-cells / #size-cells)
+ // Default for root: 2 address-cells, 2 size-cells
+ int depth = 0;
+
+ while (1)
+ {
+ uint32 token = bswap(*(uint32 *)struct_ptr);
+ struct_ptr += 4;
+
+ if (token == FDT_END)
+ break;
+ if (token == FDT_NOP)
+ continue;
+
+ if (token == FDT_BEGIN_NODE)
+ {
+ // Save node name
+ // char *name = (char *)struct_ptr;
+ int i = 0;
+ while (*struct_ptr != 0 && i < 63)
+ {
+ current_node_name[i++] = *struct_ptr;
+ struct_ptr++;
+ }
+ current_node_name[i] = '\0';
+ while (*struct_ptr != 0)
+ struct_ptr++;
+ struct_ptr++;
+ struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3);
+
+ // Reset per-node state
+ current_reg_addr = 0;
+ current_reg_size = 0;
+ current_irq = 0;
+ current_compat = nullptr;
+ current_compat_len = 0;
+ has_reg = false;
+ has_compat = false;
+ // has_irq = false;
+
+ depth++;
+ }
+ else if (token == FDT_END_NODE)
+ {
+ // Process the node we just finished
+ if (has_reg && has_compat)
+ {
+ // UART: ns16550a
+ if (compat_match(current_compat, current_compat_len, "ns16550"))
+ {
+ g_devices.uart.base_addr = current_reg_addr;
+ g_devices.uart.size = current_reg_size ? current_reg_size : 0x100;
+ g_devices.uart.irq = current_irq;
+ add_mmio_region(current_reg_addr, g_devices.uart.size);
+ }
+ // PLIC
+ else if (compat_match(current_compat, current_compat_len, "plic"))
+ {
+ g_devices.plic.base_addr = current_reg_addr;
+ g_devices.plic.size = current_reg_size ? current_reg_size : 0x4000000;
+ g_devices.plic.irq = 0;
+ add_mmio_region(current_reg_addr, g_devices.plic.size);
+ }
+ // CLINT
+ else if (compat_match(current_compat, current_compat_len, "clint"))
+ {
+ g_devices.clint.base_addr = current_reg_addr;
+ g_devices.clint.size = current_reg_size ? current_reg_size : 0x10000;
+ g_devices.clint.irq = 0;
+ add_mmio_region(current_reg_addr, g_devices.clint.size);
+ }
+ // VirtIO
+ else if (compat_match(current_compat, current_compat_len, "virtio"))
+ {
+ if (g_devices.virtio_count < DeviceRegistry::MAX_VIRTIO)
+ {
+ int idx = g_devices.virtio_count++;
+ g_devices.virtio[idx].base_addr = current_reg_addr;
+ g_devices.virtio[idx].size = current_reg_size ? current_reg_size : 0x1000;
+ g_devices.virtio[idx].irq = current_irq;
+ add_mmio_region(current_reg_addr, g_devices.virtio[idx].size);
+ }
+ }
+ }
+
+ // Memory node: name starts with "memory"
+ if (has_reg && starts_with(current_node_name, "memory"))
+ {
+ g_devices.mem_base = current_reg_addr;
+ g_devices.mem_size = current_reg_size;
+ }
+
+ depth--;
+ }
+ else if (token == FDT_PROP)
+ {
+ uint32 len = bswap(*(uint32 *)struct_ptr);
+ struct_ptr += 4;
+ uint32 nameoff = bswap(*(uint32 *)struct_ptr);
+ struct_ptr += 4;
+
+ char *name = strings_ptr + nameoff;
+ uint8 *data = struct_ptr;
+
+ struct_ptr += len;
+ struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3);
+
+ if (strcmp(name, "reg") == 0 && len >= 8)
+ {
+ // Assume 2 address-cells, 2 size-cells (standard for QEMU virt)
+ uint32 hi = bswap(*(uint32 *)data);
+ uint32 lo = bswap(*(uint32 *)(data + 4));
+ current_reg_addr = ((uint64)hi << 32) | lo;
+
+ if (len >= 16)
+ {
+ uint32 shi = bswap(*(uint32 *)(data + 8));
+ uint32 slo = bswap(*(uint32 *)(data + 12));
+ current_reg_size = ((uint64)shi << 32) | slo;
+ }
+ has_reg = true;
+ }
+ else if (strcmp(name, "compatible") == 0)
+ {
+ current_compat = data;
+ current_compat_len = len;
+ has_compat = true;
+ }
+ else if (strcmp(name, "interrupts") == 0 && len >= 4)
+ {
+ current_irq = bswap(*(uint32 *)data);
+ // has_irq = true;
+ }
+ else if (strcmp(name, "interrupts-extended") == 0 && len >= 8)
+ {
+ // interrupts-extended = <&phandle irq_num>
+ // IRQ number is typically the second cell
+ current_irq = bswap(*(uint32 *)(data + 4));
+ // has_irq = true;
+ }
+ }
+ }
+
+ // Legacy compat
+ g_uart_base = g_devices.uart.base_addr;
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/initcode.S b/lume-legacy/src/kernel/initcode.S
new file mode 100644
index 0000000..22e55ca
--- /dev/null
+++ b/lume-legacy/src/kernel/initcode.S
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+.section .rodata
+.global initcode_start
+.global initcode_end
+.align 4
+
+initcode_start:
+ .incbin "build/user/initcode"
+initcode_end:
diff --git a/lume-legacy/src/kernel/main.cc b/lume-legacy/src/kernel/main.cc
new file mode 100644
index 0000000..6a19915
--- /dev/null
+++ b/lume-legacy/src/kernel/main.cc
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+#include "kernel/riscv.h"
+#include "drivers/uart.h"
+#include "drivers/plic.h"
+#include "drivers/virtio.h"
+#include "kernel/pmm.h"
+#include "kernel/mm.h"
+#include "kernel/slab.h"
+#include "kernel/proc.h"
+#include "kernel/trap.h"
+#include "kernel/timer.h"
+#include "kernel/cpu.h"
+#include "kernel/buf.h"
+#include "fs/fs.h"
+#include "fs/file.h"
+#include "fs/fat32.h"
+#include "lib/string.h"
+
+#include "kernel/fdt.h" // fdt_parse
+
+extern uint64 g_dtb_addr;
+
+// Placement new support (referenced from cxx.cc)
+void *operator new(unsigned long size, void *ptr);
+
+volatile static int started = 0;
+
+extern "C" void kernel_main(uint64 hartid, uint64 dtb)
+{
+ if (hartid == 0)
+ {
+ fdt_parse(dtb);
+
+ Drivers::uart_init();
+ Drivers::uart_puts("\n[Lume OS] Booting...\n");
+
+ Drivers::uart_puts("[Boot] PMP configured.\n");
+
+ PMM::init(); // Physical Memory Management
+ VM::kvminit(); // Kernel Pagetable
+ VM::kvminithart(); // Enable MMU
+ Slab::init();
+ Trap::init(); // Trap Management
+ Trap::inithart();
+ PLIC::init();
+ PLIC::inithart();
+ VirtIO::init();
+ BufferCache::init();
+ VFS::init();
+ // Initialize global FAT32 object manually (global constructors not supported yet)
+ new (&fat32_fs) FAT32FileSystem();
+ VFS::register_fs(&fat32_fs);
+ FileTable::init();
+ Timer::init();
+ ProcManager::init(); // Process Management
+ ProcManager::user_init(); // Initialize first user process
+
+ __atomic_store_n(&started, 1, __ATOMIC_RELEASE);
+
+ Drivers::uart_puts("[Boot] System initialized. Entering scheduler...\n");
+ }
+ else
+ {
+ while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0)
+ ;
+
+ VM::kvminithart();
+ Trap::inithart();
+ PLIC::inithart();
+ Timer::init();
+ Drivers::uart_puts("[Boot] Hart started.\n");
+ }
+
+ ProcManager::scheduler(); // Enable Scheduler
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/mm/pmm.cc b/lume-legacy/src/kernel/mm/pmm.cc
new file mode 100644
index 0000000..d3d99ba
--- /dev/null
+++ b/lume-legacy/src/kernel/mm/pmm.cc
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+#include "kernel/riscv.h"
+#include "kernel/pmm.h"
+#include "kernel/mm.h"
+#include "kernel/fdt.h"
+// #include "kernel/spinlock.h"
+#include "drivers/uart.h"
+#include "lib/string.h"
+
+extern "C" char kernel_end[];
+extern uint64 g_dtb_addr;
+
+// Default memory size 128MB, will be overwritten by FDT
+uint64 g_phys_size = 128 * 1024 * 1024;
+
+namespace PMM
+{
+ // Global metadata array pointer
+ struct Page *mem_map = nullptr;
+ // Total number of physical memory pages
+ uint64 total_pages = 0;
+ // Free list array (free_area[k] stores the head of the free block list of order=k)
+ struct Page *free_area[MAX_ORDER];
+ // Protective lock
+ Spinlock pmm_lock;
+
+ struct Page *pa_to_page(uint64 pa)
+ {
+ if (pa < KERNBASE || pa >= PHYSTOP)
+ return nullptr;
+ uint64 pfn = (pa - KERNBASE) >> PGSHIFT;
+ if (pfn >= total_pages)
+ return nullptr;
+ return &mem_map[pfn];
+ }
+
+ uint64 page_to_pa(struct Page *page)
+ {
+ uint64 pfn = page - mem_map;
+ return KERNBASE + (pfn << PGSHIFT);
+ }
+
+ void list_add(struct Page *page, int order)
+ {
+ page->order = order;
+ page->flags = 0; // Mark as available
+ page->next = free_area[order];
+ page->prev = nullptr;
+
+ if (free_area[order])
+ {
+ free_area[order]->prev = page;
+ }
+ free_area[order] = page;
+ }
+
+ void list_del(struct Page* page, int order)
+ {
+ if (page->prev)
+ {
+ page->prev->next = page->next;
+ }
+ else
+ {
+ free_area[order] = page->next;
+ }
+ if (page->next)
+ {
+ page->next->prev = page->prev;
+ }
+ page->next = nullptr;
+ page->prev = nullptr;
+ }
+
+ void init()
+ {
+ // Use FDT memory size if available
+ if (g_devices.mem_size > 0)
+ {
+ g_phys_size = g_devices.mem_size;
+ }
+
+ pmm_lock.init("pmm");
+
+ uint64 kern_end_pa = PGROUNDUP((uint64)kernel_end);
+ total_pages = (PHYSTOP - KERNBASE) / PGSIZE;
+
+ Drivers::uart_puts(ANSI_GREEN "[PMM] Total RAM size: ");
+ Drivers::uart_put_int(g_phys_size / (1024 * 1024));
+ Drivers::uart_puts(" MB\n" ANSI_RESET);
+ // Place the mem_map array
+ // mem_map is located immediately after kernel_end
+ mem_map = (struct Page*)kern_end_pa;
+
+ uint64 mem_map_size = total_pages * sizeof(struct Page);
+ uint64 free_mem_start = PGROUNDUP(kern_end_pa + mem_map_size);
+
+ Drivers::uart_puts(ANSI_GREEN "[PMM] MemMap placed at: " );
+ Drivers::print_hex((uint64)mem_map);
+ Drivers::uart_puts("\n" ANSI_RESET);
+
+ Drivers::uart_puts(ANSI_GREEN "[PMM] Free memory starts at: ");
+ Drivers::print_hex(free_mem_start);
+ Drivers::uart_puts("\n" ANSI_RESET);
+
+ if (free_mem_start >= PHYSTOP)
+ {
+ Drivers::uart_puts(ANSI_RED "[PMM] Error - Kernel too large, no memory left!\n" ANSI_RESET);
+ while (1)
+ ;
+ }
+
+ // Initialize all page states
+ for (uint64 i = 0; i < total_pages; i++)
+ {
+ mem_map[i].flags = PG_reserved;
+ mem_map[i].order = 0;
+ mem_map[i].refcnt = 0;
+ mem_map[i].next = nullptr;
+ mem_map[i].prev = nullptr;
+ }
+
+ for (int i = 0; i < MAX_ORDER; i++)
+ {
+ free_area[i] = nullptr;
+ }
+
+ // Release actual free memory to the allocator
+ // Range: [free_mem_start, PHYSTOP)
+ // Note: need to avoid the page where the DTB is located
+ uint64 dtb_pa_start = PGROUNDDOWN(g_dtb_addr);
+ uint64 dtb_pa_end = PGROUNDUP(g_dtb_addr + 0x10000);
+
+ uint64 p = free_mem_start;
+ while (p < PHYSTOP)
+ {
+ if (p >= dtb_pa_start && p < dtb_pa_end)
+ {
+ p += PGSIZE;
+ continue;
+ }
+
+ // Mark the page as Used, then call free_pages to release it
+ // This allows free_pages to handle the merging logic automatically
+ struct Page *page = pa_to_page(p);
+ if (page)
+ {
+ page->flags = PG_used;
+ free_pages((void *)p, 0); // Release page by page, let Buddy merge automatically
+ }
+ p += PGSIZE;
+
+
+ }
+
+ }
+
+ void *alloc_pages(int order)
+ {
+ if (order < 0 || order >= MAX_ORDER)
+ return nullptr;
+
+ pmm_lock.acquire();
+
+ int current_order = order;
+ while (current_order < MAX_ORDER)
+ {
+ if (free_area[current_order])
+ {
+ // Find free block
+ struct Page *page = free_area[current_order];
+ list_del(page, current_order);
+
+ // If the block is too large, split it.
+ while (current_order > order)
+ {
+ current_order--;
+ // page is the left part (Buddy A)
+ // buddy is the right part (Buddy B)
+ // Pointer addition: Page* + n is equivalent to address offset n * sizeof(Page)
+ // Since mem_map is linearly mapped, mem_map[i + (1<<k)] exactly corresponds to physical address + (1<<k)*PGSIZE
+ struct Page *buddy = page + (1 << current_order);
+
+ // Add the buddy to a lower-level linked list
+ list_add(buddy, current_order);
+ }
+
+ // Mark as Occupied
+ page->flags = PG_used;
+ page->order = order; // Record the orders that have been assigned
+ page->refcnt = 1;
+
+ pmm_lock.release();
+ return (void *)page_to_pa(page);
+ }
+ current_order++;
+ }
+ pmm_lock.release();
+ Drivers::uart_puts("PMM: OOM alloc_pages\n");
+ return nullptr;
+ }
+
+ void free_pages(void *pa, int order)
+ {
+ if (!pa)
+ return;
+ struct Page *page = pa_to_page((uint64)pa);
+ if (!page)
+ return;
+ pmm_lock.acquire();
+
+ if (!(page->flags & PG_used))
+ {
+ Drivers::uart_puts("PMM: Double free warning\n");
+ pmm_lock.release();
+ return;
+ }
+
+ // Mark as basic status
+ page->flags = 0;
+ page->refcnt = 0;
+
+ // try merge
+ uint64 page_idx = page - mem_map;
+
+ while (order < MAX_ORDER - 1)
+ {
+ // calculate Buddy index: index ^ (1 << order)
+ uint64 buddy_idx = page_idx ^ (1 << order);
+ struct Page *buddy = &mem_map[buddy_idx];
+
+ // Check if the Buddy can be merged:
+ // 1. Must be within the memory range
+ // 2. Must be free (flags == 0)
+ // 3. Must be at the current order (to prevent merging already split small blocks)
+ if (buddy_idx >= total_pages || (buddy->flags & PG_used) || (buddy->flags & PG_reserved) || buddy->order != order)
+ {
+ break;
+ }
+
+ // Can be merged!
+ // Remove Buddy from the free list
+ list_del(buddy, order);
+
+ // Update the current block to the merged block
+ // The starting address of the merged block is the smaller one between index and buddy_index
+ if (buddy_idx < page_idx)
+ {
+ page = buddy;
+ page_idx = buddy_idx;
+ }
+
+ order++;
+ }
+
+ // Insert into the final order linked list
+ list_add(page, order);
+
+ pmm_lock.release();
+ }
+
+ void *alloc_page()
+ {
+ void *p = alloc_pages(0);
+ if (p)
+ {
+ memset(p, 0, PGSIZE);
+ }
+ return p;
+ }
+
+ void free_page(void *pa)
+ {
+ free_pages(pa, 0);
+ }
+
+ void print_free_lists()
+ {
+ // Debug print the number of free blocks at each level
+ Drivers::uart_puts("PMM Stats: [ ");
+ for (int i = 0; i < MAX_ORDER; i++)
+ {
+ int count = 0;
+ struct Page *p = free_area[i];
+ while (p)
+ {
+ count++;
+ p = p->next;
+ }
+ if (count > 0)
+ {
+ Drivers::uart_puts("Order");
+ // Drivers::uart_put_int(i);
+ Drivers::uart_puts(":");
+ // Drivers::uart_put_int(count);
+ Drivers::uart_puts(" ");
+ }
+ }
+ Drivers::uart_puts("]\n");
+ }
+} // namespace PMM
diff --git a/lume-legacy/src/kernel/mm/slab.cc b/lume-legacy/src/kernel/mm/slab.cc
new file mode 100644
index 0000000..3bb6b1b
--- /dev/null
+++ b/lume-legacy/src/kernel/mm/slab.cc
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "kernel/slab.h"
+#include "kernel/pmm.h"
+#include "kernel/spinlock.h"
+#include "kernel/riscv.h"
+#include "drivers/uart.h"
+#include "lib/string.h"
+
+#define PG_slab 0x04
+
+// Supported object size (must be a multiple of 8 and <= 2048)
+const uint64 kmalloc_sizes[] = {
+ 16, 32, 64, 128, 256, 512, 1024, 2048};
+const int NUM_CACHES = 8;
+
+struct SlabHeader
+{
+ struct SlabHeader *next; // Next slab in the linked list
+ uint16 free_idx; // Index of the head of the free list (0xFFFF means none)
+ uint16 used_count; // Number of allocated objects
+ // Followed immediately by object data...
+ // The free list of objects is embedded in the first 2 bytes of the object itself (stores the index of the next object)
+ uint32 padding;
+};
+
+struct KmemCache
+{
+ Spinlock lock;
+ uint64 obj_size;
+ uint64 obj_count_per_slab;
+ struct SlabHeader *slab_list; // Slab list with free objects
+ const char *name;
+};
+
+struct KmemCache caches[NUM_CACHES];
+
+// Virtual address -> struct Page*
+static struct Page *virt_to_page(void *ptr)
+{
+ uint64 pa = (uint64)ptr;
+ return PMM::pa_to_page(pa);
+}
+
+// Slab Implementation
+
+namespace Slab
+{
+
+ void init()
+ {
+ for (int i = 0; i < NUM_CACHES; i++)
+ {
+ caches[i].lock.init("slab_lock");
+ caches[i].obj_size = kmalloc_sizes[i];
+ caches[i].name = "kmalloc";
+
+ // Calculate how many objects can fit on a page
+ // Excluding the overhead of the SlabHeader
+ uint64 available = PGSIZE - sizeof(struct SlabHeader);
+ caches[i].obj_count_per_slab = available / caches[i].obj_size;
+ caches[i].slab_list = nullptr;
+ }
+ Drivers::uart_puts("[Slab] Initialized kmalloc caches.\n");
+ }
+
+ // Allocate a new slab page for the cache
+ // Must hold cache->lock
+ static struct SlabHeader *grow_cache(struct KmemCache *c)
+ {
+ void *p = PMM::alloc_page();
+ if (!p)
+ return nullptr;
+
+ // Mark this page as a Slab page
+ struct Page *page = virt_to_page(p);
+ if (page)
+ {
+ page->flags |= PG_slab;
+ int cache_idx = c - caches;
+ page->order = (uint8)cache_idx;
+ }
+
+ struct SlabHeader *slab = (struct SlabHeader *)p;
+ slab->next = nullptr;
+ slab->used_count = 0;
+ slab->free_idx = 0;
+ slab->padding = 0;
+
+ // Initialize the free list of objects
+ // The starting position of each object stores the index of the next object
+ // Starting position of the object data area
+ char *base = (char *)(slab + 1);
+
+ for (uint i = 0; i < c->obj_count_per_slab - 1; i++)
+ {
+ // The content of the i-th object is i+1
+ uint16 *ptr = (uint16 *)(base + i * c->obj_size);
+ *ptr = i + 1;
+ }
+ // The last object points to 0xFFFF
+ uint16 *ptr = (uint16 *)(base + (c->obj_count_per_slab - 1) * c->obj_size);
+ *ptr = 0xFFFF;
+
+ return slab;
+ }
+
+ void *kmalloc(uint64 size)
+ {
+ if (size == 0)
+ return nullptr;
+
+ // Large objects go directly through PMM
+ if (size > 2048)
+ {
+ // calculate order
+ int order = 0;
+ uint64 req = size;
+ uint64 pages = (size + PGSIZE - 1) / PGSIZE;
+ (void)req;
+
+ // calculate order: 2^order >= pages
+ while ((1UL << order) < pages)
+ {
+ order++;
+ }
+ if (order > MAX_ORDER)
+ return nullptr;
+
+ void *p = PMM::alloc_pages(order);
+ // Here, page->flags defaults to PG_used, not PG_slab
+ // kfree needs to be able to recognize it
+ return p;
+ }
+
+ int idx = 0;
+ while (idx < NUM_CACHES && kmalloc_sizes[idx] < size)
+ {
+ idx++;
+ }
+
+ struct KmemCache *c = &caches[idx];
+ c->lock.acquire();
+
+ if (!c->slab_list)
+ {
+ c->slab_list = grow_cache(c);
+ if (!c->slab_list)
+ {
+ c->lock.release();
+ return nullptr;
+ }
+ }
+
+ struct SlabHeader *slab = c->slab_list;
+
+ int obj_idx = slab->free_idx;
+ char *base = (char *)(slab + 1);
+ void *ptr = base + obj_idx * c->obj_size;
+
+ uint16 *link = (uint16 *)ptr;
+ slab->free_idx = *link;
+ slab->used_count++;
+
+ if (slab->free_idx == 0xFFFF)
+ {
+ if (slab->next)
+ {
+ c->slab_list = slab->next;
+ slab->next = nullptr;
+ }
+ else
+ {
+ c->slab_list = nullptr;
+ }
+ }
+
+ c->lock.release();
+
+ memset(ptr, 0, size);
+ return ptr;
+ }
+
+ void kfree(void *ptr)
+ {
+ if (!ptr)
+ return;
+
+ struct Page *page = virt_to_page(ptr);
+ if (!page)
+ return;
+
+ if (!page || !(page->flags & PG_slab))
+ {
+ PMM::free_pages(ptr, page->order);
+ return;
+ }
+
+ int cache_idx = page->order;
+ if (cache_idx >= NUM_CACHES)
+ {
+ Drivers::uart_puts("Slab: Corrupted page metadata\n");
+ return;
+ }
+
+ struct KmemCache *c = &caches[cache_idx];
+ c->lock.acquire();
+
+ struct SlabHeader *slab = (struct SlabHeader *)PGROUNDDOWN((uint64)ptr);
+
+ if ((uint64)ptr < (uint64)(slab + 1) || (uint64)ptr >= (uint64)slab + PGSIZE)
+ {
+ Drivers::uart_puts("Slab: kfree invalid pointer\n");
+ c->lock.release();
+ return;
+ }
+
+ char *base = (char *)(slab + 1);
+ int obj_idx = ((uint64)ptr - (uint64)base) / c->obj_size;
+
+ uint16 *link = (uint16 *)ptr;
+ *link = slab->free_idx;
+ slab->free_idx = (uint16)obj_idx;
+
+ slab->used_count--;
+
+ if (slab->used_count == 0)
+ {
+ struct SlabHeader **pp = &c->slab_list;
+ // bool found = false;
+ while (*pp)
+ {
+ if (*pp == slab)
+ {
+ *pp = slab->next;
+ // found = true;
+ break;
+ }
+ pp = &(*pp)->next;
+ }
+
+ page->flags &= ~PG_slab;
+ PMM::free_page(slab);
+ }
+ else
+ {
+
+ if (slab->used_count == c->obj_count_per_slab - 1)
+ {
+ slab->next = c->slab_list;
+ c->slab_list = slab;
+ }
+ }
+
+ c->lock.release();
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/mm/vm.cc b/lume-legacy/src/kernel/mm/vm.cc
new file mode 100644
index 0000000..be2f2f2
--- /dev/null
+++ b/lume-legacy/src/kernel/mm/vm.cc
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "kernel/mm.h"
+#include "kernel/pmm.h"
+#include "kernel/riscv.h"
+#include "common/types.h"
+#include "lib/string.h"
+#include "drivers/uart.h"
+#include "kernel/fdt.h"
+
+extern "C"
+{
+ extern char kernel_end[];
+ extern char trampoline[];
+}
+
+namespace VM
+{
+
+ uint64 *kernel_pagetable;
+
+ static void page_ref_inc(uint64 pa)
+ {
+ struct Page *p = PMM::pa_to_page(pa);
+ if (p)
+ {
+ __atomic_add_fetch(&p->refcnt, 1, __ATOMIC_RELAXED);
+ }
+ }
+
+ static bool page_ref_dec(uint64 pa)
+ {
+ struct Page *p = PMM::pa_to_page(pa);
+ if (p)
+ {
+ if (__atomic_sub_fetch(&p->refcnt, 1, __ATOMIC_RELEASE) == 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static uint64 *walk(uint64 *pagetable, uint64 va, int alloc)
+ {
+ if (va >= MAXVA)
+ return nullptr;
+
+ for (int level = 2; level > 0; level--)
+ {
+ uint64 *pte = &pagetable[PX(level, va)];
+ if (*pte & PTE_V)
+ {
+ pagetable = (uint64 *)PTE2PA(*pte);
+ }
+ else
+ {
+ if (!alloc || (pagetable = (uint64 *)PMM::alloc_page()) == nullptr)
+ return nullptr;
+
+ memset(pagetable, 0, PGSIZE);
+ *pte = PA2PTE(pagetable) | PTE_V;
+ }
+ }
+ return &pagetable[PX(0, va)];
+ }
+
+ int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm)
+ {
+ uint64 a, last;
+ uint64 *pte;
+
+ if (size == 0)
+ return -1;
+
+ a = PGROUNDDOWN(va);
+ last = PGROUNDDOWN(va + size - 1);
+
+ for (;;)
+ {
+ if ((pte = walk(pagetable, a, 1)) == nullptr)
+ return -1;
+
+ if (*pte & PTE_V)
+ {
+ return -1;
+ }
+
+ *pte = PA2PTE(pa) | perm | PTE_V;
+
+ if (a == last)
+ break;
+ a += PGSIZE;
+ pa += PGSIZE;
+ }
+ return 0;
+ }
+
+ uint64 *uvmcreate()
+ {
+ uint64 *pagetable = (uint64 *)PMM::alloc_page();
+ if (pagetable)
+ {
+ memset(pagetable, 0, PGSIZE);
+
+ if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0)
+ {
+ PMM::free_page(pagetable);
+ return nullptr;
+ }
+ }
+ return pagetable;
+ }
+
+ void uvminit(uint64 *pagetable, uchar *src, uint64 sz)
+ {
+ char *mem;
+
+ if (sz >= PGSIZE)
+ {
+ Drivers::uart_puts("uvminit: initcode too big!\n");
+ return;
+ }
+
+ mem = (char *)PMM::alloc_page();
+ memset(mem, 0, PGSIZE);
+
+ mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W | PTE_R | PTE_X | PTE_U);
+ memmove(mem, src, sz);
+ }
+
+ uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm)
+ {
+ char *mem;
+ uint64 a;
+
+ if (newsz < oldsz)
+ return oldsz;
+
+ oldsz = PGROUNDUP(oldsz);
+ for (a = oldsz; a < newsz; a += PGSIZE)
+ {
+ mem = (char *)PMM::alloc_page();
+ if (mem == nullptr)
+ {
+ uvmdealloc(pagetable, a, oldsz);
+ return 0;
+ }
+ memset(mem, 0, PGSIZE);
+ if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R | PTE_U | xperm) != 0)
+ {
+ PMM::free_page(mem);
+ uvmdealloc(pagetable, a, oldsz);
+ return 0;
+ }
+ }
+ return newsz;
+ }
+
+ uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz)
+ {
+ if (newsz >= oldsz)
+ return oldsz;
+
+ if (PGROUNDUP(newsz) < PGROUNDUP(oldsz))
+ {
+ int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE;
+ uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1);
+ }
+
+ return newsz;
+ }
+
+ void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free)
+ {
+ uint64 a;
+ uint64 *pte;
+
+ if ((va % PGSIZE) != 0)
+ return;
+
+ for (a = va; a < va + npages * PGSIZE; a += PGSIZE)
+ {
+ if ((pte = walk(pagetable, a, 0)) == nullptr)
+ continue;
+
+ if ((*pte & PTE_V) == 0)
+ continue;
+
+ if ((PTE_FLAGS(*pte) & (PTE_R | PTE_W | PTE_X)) == 0)
+ {
+ continue;
+ }
+
+ if (do_free)
+ {
+ uint64 pa = PTE2PA(*pte);
+ if (page_ref_dec(pa))
+ {
+ PMM::free_page((void *)pa);
+ }
+ }
+ *pte = 0;
+ }
+ }
+
+ static void freewalk(uint64 *pagetable)
+ {
+ for (int i = 0; i < 512; i++)
+ {
+ uint64 pte = pagetable[i];
+ if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0)
+ {
+ uint64 *child = (uint64 *)PTE2PA(pte);
+ freewalk(child);
+ pagetable[i] = 0;
+ }
+ }
+ PMM::free_page(pagetable);
+ }
+
+ void uvmfree(uint64 *pagetable, uint64 sz)
+ {
+ if (sz > 0)
+ uvmunmap(pagetable, 0, PGROUNDUP(sz) / PGSIZE, 1);
+ freewalk(pagetable);
+ }
+
+ int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz)
+ {
+ uint64 *pte;
+ uint64 pa, i;
+ uint flags;
+
+ for (i = 0; i < sz; i += PGSIZE)
+ {
+ if ((pte = walk(oldpt, i, 0)) == nullptr)
+ continue;
+ if ((*pte & PTE_V) == 0)
+ continue;
+
+ pa = PTE2PA(*pte);
+ flags = PTE_FLAGS(*pte);
+
+ if (flags & PTE_W)
+ {
+ flags &= ~PTE_W;
+ flags |= PTE_COW;
+ *pte = PA2PTE(pa) | flags;
+ }
+
+ if (mappages(newpt, i, PGSIZE, pa, flags) != 0)
+ {
+ return -1;
+ }
+
+ page_ref_inc(pa);
+ }
+
+ sfence_vma();
+ return 0;
+ }
+
+ int handle_cow_fault(uint64 *pagetable, uint64 va);
+
+ int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len)
+ {
+ uint64 n, va0, pa0;
+ uint64 *pte;
+
+ while (len > 0)
+ {
+ va0 = PGROUNDDOWN(dstva);
+ pte = walk(pagetable, va0, 0);
+ if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
+ return -1;
+
+ // Note: Maybe a bug
+ if (*pte & PTE_COW)
+ {
+ if (handle_cow_fault(pagetable, va0) < 0)
+ return -1;
+ // Re-walk because handle_cow_fault replaced the page mapping
+ pte = walk(pagetable, va0, 0);
+ if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
+ return -1;
+ }
+
+ pa0 = PTE2PA(*pte);
+ n = PGSIZE - (dstva - va0);
+ if (n > len)
+ n = len;
+
+ memmove((void *)(pa0 + (dstva - va0)), src, n);
+
+ len -= n;
+ src += n;
+ dstva = va0 + PGSIZE;
+ }
+ return 0;
+ }
+
+ int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len)
+ {
+ uint64 n, va0, pa0;
+ uint64 *pte;
+
+ while (len > 0)
+ {
+ va0 = PGROUNDDOWN(srcva);
+ pte = walk(pagetable, va0, 0);
+ if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
+ return -1;
+
+ pa0 = PTE2PA(*pte);
+ n = PGSIZE - (srcva - va0);
+ if (n > len)
+ n = len;
+
+ memmove(dst, (void *)(pa0 + (srcva - va0)), n);
+
+ len -= n;
+ dst += n;
+ srcva = va0 + PGSIZE;
+ }
+ return 0;
+ }
+
+ int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max)
+ {
+ uint64 n, va0, pa0;
+ uint64 *pte;
+ int got_null = 0;
+ uint64 len = 0;
+
+ while (len < max)
+ {
+ va0 = PGROUNDDOWN(srcva);
+ pte = walk(pagetable, va0, 0);
+
+ if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
+ return -1;
+
+ pa0 = PTE2PA(*pte);
+
+ n = PGSIZE - (srcva - va0);
+ if (n > max - len)
+ n = max - len;
+
+ char *p = (char *)(pa0 + (srcva - va0));
+ while (n > 0)
+ {
+ if (*p == '\0')
+ {
+ *dst = '\0';
+ got_null = 1;
+ break;
+ }
+ *dst = *p;
+ n--;
+ srcva++;
+ dst++;
+ p++;
+ len++;
+ }
+ if (got_null)
+ break;
+ }
+
+ if (got_null)
+ return 0;
+
+ return -1;
+ }
+
+ int handle_cow_fault(uint64 *pagetable, uint64 va)
+ {
+ if (va >= MAXVA)
+ return -1;
+
+ uint64 *pte = walk(pagetable, va, 0);
+ if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
+ return -1;
+
+ if ((*pte & PTE_COW) == 0)
+ return -1;
+
+ uint64 pa = PTE2PA(*pte);
+ uint64 flags = PTE_FLAGS(*pte);
+
+ char *mem = (char *)PMM::alloc_page();
+ if (mem == nullptr)
+ return -1;
+
+ memmove(mem, (char *)pa, PGSIZE);
+
+ uint64 new_flags = (flags | PTE_W) & ~PTE_COW;
+ *pte = PA2PTE((uint64)mem) | new_flags | PTE_V;
+
+ if (page_ref_dec(pa))
+ {
+ PMM::free_page((void *)pa);
+ }
+
+ sfence_vma();
+ return 0;
+ }
+
+ static void kvmmap(uint64 va, uint64 pa, uint64 sz, int perm)
+ {
+ if (mappages(kernel_pagetable, va, sz, pa, perm) != 0)
+ {
+ Drivers::uart_puts("kvmmap: failed\n");
+ while (1)
+ ;
+ }
+ }
+
+ void kvminit()
+ {
+ kernel_pagetable = uvmcreate();
+
+ // 1. Map all device MMIO regions discovered by FDT
+ for (int i = 0; i < g_devices.mmio_count; i++)
+ {
+ uint64 base = g_devices.mmio_regions[i].base;
+ uint64 size = g_devices.mmio_regions[i].size;
+ kvmmap(base, base, size, PTE_R | PTE_W);
+ }
+
+ // 2. Map Kernel Memory (PHYSTOP is now dynamic based on FDT)
+ kvmmap(KERNBASE, KERNBASE, PHYSTOP - KERNBASE, PTE_R | PTE_W | PTE_X);
+ }
+
+ void kvminithart()
+ {
+ w_satp(MAKE_SATP(kernel_pagetable));
+ sfence_vma();
+ Drivers::uart_puts("[Boot] MMU ENABLED! Using virtual addresses.\n");
+ }
+
+ uint64 walkaddr(uint64 *pagetable, uint64 va)
+ {
+ uint64 *pte;
+ uint64 pa;
+
+ if (va >= MAXVA)
+ return 0;
+
+ pte = walk(pagetable, va, 0);
+ if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
+ return 0;
+
+ pa = PTE2PA(*pte);
+ return pa;
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/proc/exec.cc b/lume-legacy/src/kernel/proc/exec.cc
new file mode 100644
index 0000000..8d8c467
--- /dev/null
+++ b/lume-legacy/src/kernel/proc/exec.cc
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+#include "kernel/riscv.h"
+#include "kernel/proc.h"
+#include "kernel/mm.h"
+#include "kernel/elf.h"
+#include "fs/fs.h"
+#include "lib/string.h"
+#include "drivers/uart.h"
+#include "kernel/trap.h"
+
+extern char trampoline[];
+
+// RAII: Page Table Resource Management
+class VmGuard
+{
+public:
+ uint64 *pagetable = nullptr;
+ uint64 sz = 0;
+
+ VmGuard() : pagetable(VM::uvmcreate()) {}
+
+ ~VmGuard()
+ {
+ if (pagetable)
+ {
+ VM::uvmfree(pagetable, sz);
+ }
+ }
+
+ // Copying is prohibited
+ VmGuard(const VmGuard &) = delete;
+ VmGuard &operator=(const VmGuard &) = delete;
+
+ // Submit resources: Ownership transfer, destructor no longer releases
+ uint64 *commit()
+ {
+ uint64 *pt = pagetable;
+ pagetable = nullptr;
+ return pt;
+ }
+};
+
+// RAII: Inode Lock Management
+class InodeGuard
+{
+public:
+ Inode *ip;
+ InodeGuard(Inode *i) : ip(i)
+ {
+ if (ip)
+ VFS::ilock(ip);
+ }
+ ~InodeGuard()
+ {
+ if (ip)
+ {
+ VFS::iunlockput(ip); // Automatically unlock and release references
+ }
+ }
+ // Copying is prohibited
+ InodeGuard(const InodeGuard &) = delete;
+ InodeGuard &operator=(const InodeGuard &) = delete;
+};
+
+// laod segment
+static int loadseg(uint64 *pagetable, uint64 va, Inode *ip, uint64 offset, uint64 sz)
+{
+ for (uint64 i = 0; i < sz; i += PGSIZE)
+ {
+ uint64 pa = VM::walkaddr(pagetable, va + i);
+ if (pa == 0)
+ {
+ Drivers::uart_puts("[Exec Debug] loadseg: walkaddr failed\n");
+ return -1;
+ }
+
+ uint64 n = PGSIZE - (va + i) % PGSIZE;
+ if (i + n > sz)
+ n = sz - i;
+
+ // Read physical address (isUser=0)
+ if (ip->read((char *)(pa + (va + i) % PGSIZE), offset + i, n, 0) != (int)n)
+ {
+ Drivers::uart_puts("[Exec Debug] loadseg: read failed\n");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+namespace Exec
+{
+ int exec(char *path, char **argv)
+ {
+ // 1. Get Inode
+ Inode *raw_ip = VFS::namei(path);
+ if (!raw_ip)
+ {
+ Drivers::uart_puts("[Exec Debug] namei failed for path: ");
+ Drivers::uart_puts(path);
+ Drivers::uart_puts("\n");
+ return -1;
+ }
+ InodeGuard iguard(raw_ip);
+
+ // Get ELF head
+ struct elfhdr elf;
+ if (raw_ip->read((char *)&elf, 0, sizeof(elf), 0) != sizeof(elf))
+ {
+ Drivers::uart_puts("[Exec Debug] read elf header failed\n");
+ return -1;
+ }
+ if (elf.magic != ELF_MAGIC)
+ {
+ Drivers::uart_puts("[Exec Debug] invalid elf magic\n");
+ return -1;
+ }
+
+ // Create new pagetable
+ VmGuard vm_guard;
+ if (!vm_guard.pagetable)
+ {
+ Drivers::uart_puts("[Exec Debug] uvmcreate failed\n");
+ return -1;
+ }
+
+ // Load Program Segment
+ struct proghdr ph;
+ for (int i = 0; i < elf.phnum; i++)
+ {
+ uint64 off = elf.phoff + i * sizeof(ph);
+ if (raw_ip->read((char *)&ph, off, sizeof(ph), 0) != sizeof(ph))
+ {
+ Drivers::uart_puts("[Exec Debug] read proghdr failed\n");
+ return -1;
+ }
+
+ if (ph.type != ELF_PROG_LOAD)
+ continue;
+ if (ph.memsz < ph.filesz)
+ return -1;
+ if (ph.vaddr + ph.memsz < ph.vaddr)
+ return -1;
+
+ uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, vm_guard.sz, ph.vaddr + ph.memsz, PTE_W | PTE_X | PTE_R | PTE_U);
+ if (sz1 == 0)
+ {
+ Drivers::uart_puts("[Exec Debug] uvmalloc failed (segment)\n");
+ return -1;
+ }
+ vm_guard.sz = sz1;
+
+ if (loadseg(vm_guard.pagetable, ph.vaddr, raw_ip, ph.off, ph.filesz) < 0)
+ return -1;
+ }
+
+ // Prepare user stack
+ uint64 sz = PGROUNDUP(vm_guard.sz);
+
+ const int stack_data_pages = 16;
+ const int stack_total_pages = stack_data_pages + 1;
+
+ uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, sz, sz + stack_total_pages * PGSIZE, PTE_W | PTE_R | PTE_U);
+ if (sz1 == 0)
+ {
+ Drivers::uart_puts("[Exec Debug] uvmalloc failed (stack)\n");
+ return -1;
+ }
+ vm_guard.sz = sz1;
+
+ VM::uvmunmap(vm_guard.pagetable, sz, 1, 0);
+
+ uint64 sp = sz1;
+ uint64 stackbase = sp - stack_data_pages * PGSIZE;
+ uint64 ustack[32];
+ uint64 argc = 0;
+
+ // Push parameter string
+ for (; argv[argc]; argc++)
+ {
+ if (argc >= 31)
+ return -1;
+ sp -= strlen(argv[argc]) + 1;
+ sp -= sp % 16;
+ if (sp < stackbase)
+ {
+ Drivers::uart_puts("[Exec Debug] stack overflow (args)\n");
+ return -1;
+ }
+
+ if (VM::copyout(vm_guard.pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
+ {
+ Drivers::uart_puts("[Exec Debug] copyout arg string failed\n");
+ return -1;
+ }
+ ustack[argc] = sp;
+ }
+ ustack[argc] = 0;
+
+ // Push into parameter array
+ sp -= (argc + 1) * sizeof(uint64);
+ sp -= sp % 16;
+ if (sp < stackbase)
+ {
+ Drivers::uart_puts("[Exec Debug] stack overflow (argv array)\n");
+ return -1;
+ }
+ if (VM::copyout(vm_guard.pagetable, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) < 0)
+ {
+ Drivers::uart_puts("[Exec Debug] copyout argv array failed\n");
+ return -1;
+ }
+
+ // Commit Changes
+ struct Proc *p = myproc();
+ if (VM::mappages(vm_guard.pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0)
+ {
+ Drivers::uart_puts("[Exec Debug] mappages TRAPFRAME failed\n");
+ return -1;
+ }
+
+ uint64 oldsz = p->sz;
+ uint64 *oldpagetable = p->pagetable;
+
+ p->tf->a0 = argc;
+ p->tf->a1 = sp;
+ p->tf->sp = sp;
+ p->tf->epc = elf.entry;
+
+ char *s = path, *last = path;
+ while (*s)
+ {
+ if (*s == '/')
+ last = s + 1;
+ s++;
+ }
+
+ size_t len = 0;
+ while (last[len] && len < sizeof(p->name) - 1)
+ {
+ p->name[len] = last[len];
+ len++;
+ }
+ p->name[len] = '\0';
+
+ // change pagetable
+ p->pagetable = vm_guard.commit();
+ p->sz = vm_guard.sz;
+
+ // release old pagetable
+ VM::uvmfree(oldpagetable, oldsz);
+ asm volatile("fence.i");
+
+ return 0; // iguard unlocks and releases IP upon destruction
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/proc/proc.cc b/lume-legacy/src/kernel/proc/proc.cc
new file mode 100644
index 0000000..3923fe8
--- /dev/null
+++ b/lume-legacy/src/kernel/proc/proc.cc
@@ -0,0 +1,654 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "kernel/proc.h"
+#include "kernel/proc_def.h"
+#include "kernel/runqueue.h"
+#include "kernel/cpu.h"
+#include "kernel/spinlock.h"
+#include "kernel/pmm.h"
+#include "kernel/mm.h"
+#include "kernel/trap.h"
+#include "kernel/riscv.h"
+#include "kernel/slab.h"
+#include "drivers/uart.h"
+#include "fs/fs.h"
+#include "fs/file.h"
+#include "lib/string.h"
+
+// #define NPROC 64
+#define NCPU 8
+
+extern "C" void swtch(struct Context *old_ctx, struct Context *new_ctx);
+extern char uservec[], trampoline[];
+extern "C" void usertrap();
+extern "C" void userret(uint64, uint64);
+extern char initcode_start[], initcode_end[];
+
+// struct Proc procs[NPROC];
+struct Proc *proc_list_head = nullptr;
+struct Proc *initproc = nullptr;
+struct Spinlock proc_mem_lock;
+struct cpu cpus[NCPU];
+struct RunQueue runqueues[NCPU];
+
+
+static int nextpid = 1;
+
+
+struct cpu *mycpu()
+{
+ int id = r_tp();
+ return &cpus[id];
+}
+
+
+void RunQueue::init(const char *name)
+{
+ lock.init(name);
+ count = 0;
+ for (int i = 0; i < N_PRIO; i++)
+ {
+ heads[i] = nullptr;
+ tails[i] = nullptr;
+ }
+}
+
+void RunQueue::enqueue(struct Proc *p)
+{
+ if (p->priority < 0 || p->priority >= N_PRIO)
+ p->priority = PRIO_MIN;
+ p->next = nullptr;
+ if (heads[p->priority] == nullptr)
+ {
+ heads[p->priority] = p;
+ tails[p->priority] = p;
+ }
+ else
+ {
+ tails[p->priority]->next = p;
+ tails[p->priority] = p;
+ }
+ count++;
+}
+
+struct Proc *RunQueue::dequeue()
+{
+ for (int i = 0; i < N_PRIO; i++)
+ {
+ if (heads[i])
+ {
+ struct Proc *p = heads[i];
+ heads[i] = p->next;
+ if (!heads[i])
+ tails[i] = nullptr;
+ p->next = nullptr;
+ count--;
+ return p;
+ }
+ }
+ return nullptr;
+}
+
+struct Proc *RunQueue::steal()
+{
+ for (int i = N_PRIO - 1; i >= 0; i--)
+ {
+ if (heads[i])
+ {
+ struct Proc *p = heads[i];
+ heads[i] = p->next;
+ if (!heads[i])
+ tails[i] = nullptr;
+ p->next = nullptr;
+ count--;
+ return p;
+ }
+ }
+ return nullptr;
+}
+
+// Trap Return Functions
+
+void usertrapret()
+{
+ struct Proc *p = myproc();
+
+ intr_off();
+
+ // Set stvec to point to uservec in the trampoline
+ // When an interrupt occurs in user mode, the CPU will jump here
+ uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
+ w_stvec(trampoline_uservec);
+
+ // Fill in the Trapframe, used when uservec saves the kernel state
+ p->tf->kernel_satp = r_satp();
+ p->tf->kernel_sp = (uint64)p->kstack + PGSIZE;
+ p->tf->kernel_trap = (uint64)usertrap;
+ p->tf->kernel_hartid = r_tp();
+
+ // Set SSTATUS
+ // SPP = 0 (return to User Mode)
+ // SPIE = 1 (enable interrupts)
+ unsigned long x = r_sstatus();
+ x &= ~SSTATUS_SPP;
+ x |= SSTATUS_SPIE;
+ w_sstatus(x);
+
+ // Set SEPC to the user program counter
+ w_sepc(p->tf->epc);
+
+ // Prepare user page table SATP
+ uint64 satp = MAKE_SATP(p->pagetable);
+
+ // Calculate the virtual address of userret in the trampoline
+ uint64 fn = TRAMPOLINE + ((uint64)userret - (uint64)trampoline);
+
+ // Jump to userret, with parameters being the TRAPFRAME address and the user page table SATP
+ ((void (*)(uint64, uint64))fn)(TRAPFRAME, satp);
+}
+
+
+void forkret()
+{
+
+ struct cpu *c = mycpu();
+ c->runqueue->lock.release();
+
+ // Debug
+ // Drivers::uart_puts("[Proc] forkret: jumping to user.\n");
+
+ // Enter user mode
+ usertrapret();
+}
+
+// ProcManager Implementation
+
+static Proc *allocproc()
+{
+ struct Proc *p;
+ proc_mem_lock.acquire();
+
+ p = (struct Proc *)Slab::kmalloc(sizeof(struct Proc));
+
+ if (!p)
+ {
+ proc_mem_lock.release();
+ return 0;
+ }
+
+ memset(p, 0, sizeof(struct Proc));
+
+ p->all_next = proc_list_head;
+ p->all_prev = nullptr;
+ if (proc_list_head)
+ proc_list_head->all_prev = p;
+ proc_list_head = p;
+
+
+ p->state = USED;
+ p->pid = nextpid++;
+ p->priority = 0;
+ p->killed = 0;
+ p->cpu = mycpu();
+ p->cwd = nullptr;
+ for (int i = 0; i < NOFILE; i++)
+ p->ofile[i] = nullptr;
+
+ if ((p->tf = (struct Trapframe *)PMM::alloc_page()) == 0)
+ {
+ p->state = UNUSED;
+ if (p->all_next)
+ p->all_next->all_prev = p->all_prev;
+ if (p->all_prev)
+ p->all_prev->all_next = p->all_next;
+ if (p == proc_list_head)
+ proc_list_head = p->all_next;
+ Slab::kfree(p);
+ proc_mem_lock.release();
+ return 0;
+ }
+
+ if ((p->kstack = PMM::alloc_page()) == 0)
+ {
+ PMM::free_page(p->tf);
+ p->state = UNUSED;
+ if (p->all_next)
+ p->all_next->all_prev = p->all_prev;
+ if (p->all_prev)
+ p->all_prev->all_next = p->all_next;
+ if (p == proc_list_head)
+ proc_list_head = p->all_next;
+ Slab::kfree(p);
+
+ proc_mem_lock.release();
+ return 0;
+ }
+ memset(p->kstack, 0, PGSIZE);
+
+ memset(&p->context, 0, sizeof(p->context));
+
+ // Set the context so that the next time this process is scheduled, it starts executing directly from forkret
+ p->context.ra = (uint64)forkret;
+ p->context.sp = (uint64)p->kstack + PGSIZE;
+ return p;
+}
+
+static void freeproc(struct Proc *p)
+{
+ if (p->tf)
+ PMM::free_page(p->tf);
+ p->tf = 0;
+ if (p->kstack)
+ PMM::free_page(p->kstack);
+ p->kstack = 0;
+ if (p->pagetable)
+ VM::uvmfree(p->pagetable, p->sz);
+ for (int i = 0; i < NOFILE; i++)
+ {
+ if (p->ofile[i])
+ {
+ FileTable::close(p->ofile[i]);
+ p->ofile[i] = nullptr;
+ }
+ }
+ if (p->cwd)
+ {
+ VFS::iput(p->cwd);
+ p->cwd = nullptr;
+ }
+
+ if (p->all_prev)
+ p->all_prev->all_next = p->all_next;
+ else
+ proc_list_head = p->all_next;
+
+ if (p->all_next)
+ p->all_next->all_prev = p->all_prev;
+
+ Slab::kfree(p);
+}
+
+int fork()
+{
+ struct Proc *np;
+ struct Proc *p = myproc();
+
+ if ((np = allocproc()) == 0)
+ return -1;
+
+ np->pagetable = VM::uvmcreate();
+ np->sz = p->sz;
+ if (VM::uvmcopy(p->pagetable, np->pagetable, p->sz) < 0)
+ {
+ freeproc(np);
+ proc_mem_lock.release();
+ return -1;
+ }
+
+
+ if (VM::mappages(np->pagetable, TRAPFRAME, PGSIZE, (uint64)np->tf, PTE_R | PTE_W) < 0)
+ {
+ freeproc(np);
+ proc_mem_lock.release();
+ return -1;
+ }
+
+ *(np->tf) = *(p->tf);
+ np->tf->a0 = 0;
+ strcpy(np->name, p->name);
+ np->parent = p;
+ np->priority = p->priority;
+
+ for (int i = 0; i < NOFILE; i++)
+ {
+ if (p->ofile[i])
+ {
+ np->ofile[i] = FileTable::dup(p->ofile[i]);
+ }
+ }
+
+ if (p->cwd)
+ {
+ np->cwd = VFS::idup(p->cwd);
+ }
+
+ proc_mem_lock.release();
+
+ struct cpu *c = mycpu();
+ c->runqueue->lock.acquire();
+ np->state = RUNNABLE;
+ c->runqueue->enqueue(np);
+ c->runqueue->lock.release();
+
+ return np->pid;
+}
+
+namespace ProcManager
+{
+
+ void init()
+ {
+ proc_mem_lock.init("proc_mem");
+
+ proc_list_head = nullptr;
+
+ for (int i = 0; i < NCPU; i++)
+ {
+ cpus[i].runqueue = &runqueues[i];
+ cpus[i].runqueue->init("runqueue");
+ }
+ }
+
+ void scheduler()
+ {
+ struct Proc *p;
+ struct cpu *c = mycpu();
+ c->proc = 0;
+ Drivers::uart_puts("[Proc] Scheduler started (Per-CPU).\n");
+
+ for (;;)
+ {
+ intr_on();
+
+ // Get from the local queue
+ c->runqueue->lock.acquire();
+ p = c->runqueue->dequeue();
+ c->runqueue->lock.release();
+
+ // Work Stealing
+ if (!p)
+ {
+ for (int i = 0; i < NCPU; i++)
+ {
+ if ((uint64)i == r_tp())
+ continue;
+
+ struct cpu *victim = &cpus[i];
+ victim->runqueue->lock.acquire();
+ if (victim->runqueue->count > 0)
+ p = victim->runqueue->steal();
+ victim->runqueue->lock.release();
+ if (p)
+ break;
+ }
+ }
+
+ if (p)
+ {
+ p->state = RUNNING;
+ c->proc = p;
+
+ c->runqueue->lock.acquire();
+ swtch(&c->context, &p->context);
+ c->runqueue->lock.release();
+
+ c->proc = 0;
+ }
+ }
+ }
+
+ void yield()
+ {
+ struct Proc *p = myproc();
+ struct cpu *c = mycpu();
+ c->runqueue->lock.acquire();
+ if (p && p->state == RUNNING)
+ {
+ p->state = RUNNABLE;
+ if (p->priority < PRIO_MIN)
+ p->priority++;
+ c->runqueue->enqueue(p);
+ swtch(&p->context, &c->context);
+ }
+ c->runqueue->lock.release();
+ }
+
+ void user_init()
+ {
+ struct Proc *p = allocproc();
+ if (!p)
+ return;
+
+ initproc = p;
+
+ p->pagetable = VM::uvmcreate();
+
+ uint64 initcode_sz = initcode_end - initcode_start;
+ Drivers::uart_puts("[Proc] initcode size: ");
+ Drivers::uart_put_int(initcode_sz);
+ Drivers::uart_puts(" bytes\n");
+
+ uint64 code_pages = PGROUNDUP(initcode_sz) / PGSIZE;
+
+ for (uint64 i = 0; i < code_pages; i++)
+ {
+ void *mem = PMM::alloc_page();
+ if (!mem)
+ {
+ Drivers::uart_puts("user_init: out of memory for code\n");
+ return;
+ }
+ memset(mem, 0, PGSIZE);
+
+ uint64 src_offset = i * PGSIZE;
+ uint64 copy_len = PGSIZE;
+ if (src_offset + copy_len > initcode_sz)
+ {
+ copy_len = initcode_sz - src_offset;
+ }
+ memmove(mem, (void *)(initcode_start + src_offset), copy_len);
+
+ if (VM::mappages(p->pagetable, i * PGSIZE, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_X | PTE_U) < 0)
+ {
+ Drivers::uart_puts("user_init: mappages code failed\n");
+ return;
+ }
+ }
+
+ uint64 stack_va = (code_pages + 1) * PGSIZE;
+
+ void *stack = PMM::alloc_page();
+ if (!stack)
+ return;
+ memset(stack, 0, PGSIZE);
+
+ if (VM::mappages(p->pagetable, stack_va, PGSIZE, (uint64)stack, PTE_R | PTE_W | PTE_U) < 0)
+ return;
+
+ if (VM::mappages(p->pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0)
+ return;
+
+ p->tf->epc = 0;
+ p->tf->sp = stack_va + PGSIZE;
+ p->sz = stack_va + PGSIZE;
+ strcpy(p->name, "initcode");
+
+ p->cwd = nullptr;
+
+ proc_mem_lock.release();
+
+ struct cpu *c = &cpus[0];
+ c->runqueue->lock.acquire();
+ p->state = RUNNABLE;
+ c->runqueue->enqueue(p);
+ c->runqueue->lock.release();
+
+ Drivers::uart_puts("[Proc] user_init success. PID: 1\n");
+ asm volatile("fence.i");
+ }
+
+ void sleep(void *chan, Spinlock *lk)
+ {
+ struct Proc *p = myproc();
+ struct cpu *c = mycpu();
+
+ if (lk && lk != &proc_mem_lock)
+ lk->release();
+
+ c->runqueue->lock.acquire();
+ p->chan = chan;
+ p->state = SLEEPING;
+ swtch(&p->context, &c->context);
+ p->chan = 0;
+ c->runqueue->lock.release();
+
+ if (lk && lk != &proc_mem_lock)
+ lk->acquire();
+ }
+
+ void wakeup(void *chan)
+ {
+ for (struct Proc *p = proc_list_head; p != nullptr; p = p->all_next)
+ {
+ if (p->state == SLEEPING && p->chan == chan)
+ {
+ struct cpu *c = mycpu();
+ c->runqueue->lock.acquire();
+ p->state = RUNNABLE;
+ c->runqueue->enqueue(p);
+ c->runqueue->lock.release();
+ }
+ }
+ }
+
+ void exit(int status)
+ {
+ struct Proc *p = myproc();
+ if (p == initproc)
+ while (1)
+ ;
+
+ wakeup(p->parent);
+
+ proc_mem_lock.acquire();
+
+ for (struct Proc *pp = proc_list_head; pp != nullptr; pp = pp->all_next)
+ {
+ if (pp->parent == p)
+ {
+ pp->parent = initproc;
+ wakeup(initproc);
+ }
+ }
+
+ proc_mem_lock.release();
+
+ p->xstate = status;
+
+ struct cpu *c = mycpu();
+ c->runqueue->lock.acquire();
+ p->state = ZOMBIE;
+ swtch(&p->context, &c->context);
+ while (1)
+ ;
+ }
+
+ int wait(uint64 addr)
+ {
+
+ struct Proc *np;
+ int havekids, pid;
+ struct Proc *p = myproc();
+ for (;;)
+ {
+ havekids = 0;
+ proc_mem_lock.acquire();
+
+ for (np = proc_list_head; np != nullptr; np = np->all_next)
+ {
+ if (np->parent == p)
+ {
+ havekids = 1;
+ if (np->state == ZOMBIE)
+ {
+
+ struct cpu *child_cpu = np->cpu;
+ if (child_cpu)
+ {
+ proc_mem_lock.release();
+ child_cpu->runqueue->lock.acquire();
+ child_cpu->runqueue->lock.release();
+ proc_mem_lock.acquire();
+ }
+
+ pid = np->pid;
+ if (addr != 0 && VM::copyout(p->pagetable, addr, (char *)&np->xstate, sizeof(np->xstate)) < 0)
+ {
+ proc_mem_lock.release();
+ return -1;
+ }
+ freeproc(np);
+ proc_mem_lock.release();
+ return pid;
+ }
+ }
+ }
+ proc_mem_lock.release();
+
+ if (!havekids || p->state == ZOMBIE)
+ return -1;
+ sleep(p, &proc_mem_lock);
+ }
+ }
+
+ int kill(int pid)
+ {
+ Proc* p;
+ proc_mem_lock.acquire();
+ for (p = proc_list_head; p != nullptr; p = p->all_next)
+ {
+ if (p->pid == pid)
+ {
+ p->killed = 1;
+
+ if (p->state == SLEEPING)
+ {
+ p->state = RUNNABLE;
+
+ struct cpu *c = mycpu();
+ c->runqueue->lock.acquire();
+ c->runqueue->enqueue(p);
+ c->runqueue->lock.release();
+ }
+
+ proc_mem_lock.release();
+ return 0;
+ }
+ }
+ proc_mem_lock.release();
+ return -1;
+ }
+
+ int growproc(int n)
+ {
+ uint64 sz;
+ struct Proc *p = myproc();
+
+ sz = p->sz;
+
+ if (n > 0)
+ {
+ // Call VM::uvmalloc to allocate memory
+ // Parameters: page table, old size, new size, permissions (PTE_W)
+ if ((sz = VM::uvmalloc(p->pagetable, sz, sz + n, PTE_W | PTE_R | PTE_U)) == 0)
+ {
+ return -1;
+ }
+ }
+ else if (n < 0)
+ {
+ sz = VM::uvmdealloc(p->pagetable, sz, sz + n);
+ }
+
+ p->sz = sz;
+ return 0;
+ }
+
+ void create_kernel_thread(void (*func)(), const char *name)
+ {
+ (void)func;
+ (void)name;
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/proc/swtch.S b/lume-legacy/src/kernel/proc/swtch.S
new file mode 100644
index 0000000..d55c03e
--- /dev/null
+++ b/lume-legacy/src/kernel/proc/swtch.S
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+.global swtch
+
+swtch:
+ sd ra, 0(a0)
+ sd sp, 8(a0)
+ sd s0, 16(a0)
+ sd s1, 24(a0)
+ sd s2, 32(a0)
+ sd s3, 40(a0)
+ sd s4, 48(a0)
+ sd s5, 56(a0)
+ sd s6, 64(a0)
+ sd s7, 72(a0)
+ sd s8, 80(a0)
+ sd s9, 88(a0)
+ sd s10, 96(a0)
+ sd s11, 104(a0)
+
+ ld ra, 0(a1)
+ ld sp, 8(a1)
+ ld s0, 16(a1)
+ ld s1, 24(a1)
+ ld s2, 32(a1)
+ ld s3, 40(a1)
+ ld s4, 48(a1)
+ ld s5, 56(a1)
+ ld s6, 64(a1)
+ ld s7, 72(a1)
+ ld s8, 80(a1)
+ ld s9, 88(a1)
+ ld s10, 96(a1)
+ ld s11, 104(a1)
+
+ ret
diff --git a/lume-legacy/src/kernel/sbi.cc b/lume-legacy/src/kernel/sbi.cc
new file mode 100644
index 0000000..5ae7edb
--- /dev/null
+++ b/lume-legacy/src/kernel/sbi.cc
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+#include "kernel/sbi.h"
+
+namespace SBI
+{
+ struct sbiret
+ {
+ long error;
+ long value;
+ };
+
+ struct sbiret sbi_call(int ext, int fid, uint64 arg0, uint64 arg1, uint64 arg2)
+ {
+ struct sbiret ret;
+ register uint64 a0 asm("a0") = arg0;
+ register uint64 a1 asm("a1") = arg1;
+ register uint64 a2 asm("a2") = arg2;
+ register uint64 a6 asm("a6") = fid;
+ register uint64 a7 asm("a7") = ext;
+
+ asm volatile(
+ "ecall"
+ : "=r"(a0), "=r"(a1)
+ : "r"(a0), "r"(a1), "r"(a2), "r"(a6), "r"(a7)
+ : "memory");
+ ret.error = a0;
+ ret.value = a1;
+ return ret;
+ }
+
+ void set_timer(uint64 stime_value)
+ {
+ sbi_call(0x54494D45, 0, stime_value, 0, 0);
+ }
+
+ void sbi_shutdown()
+ {
+ sbi_call(0x53525354, 0, 0, 0, 0);
+ sbi_call(0x08, 0, 0, 0, 0);
+ while (1)
+ ;
+ }
+
+ void sbi_reboot()
+ {
+ sbi_call(SBI_EID_SRST, SBI_FID_SRST_RESET, SBI_SRST_TYPE_COLD_REBOOT, SBI_SRST_REASON_NONE, 0);
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/spinlock.cc b/lume-legacy/src/kernel/spinlock.cc
new file mode 100644
index 0000000..aadce21
--- /dev/null
+++ b/lume-legacy/src/kernel/spinlock.cc
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "kernel/spinlock.h"
+#include "kernel/riscv.h"
+#include "kernel/cpu.h"
+#include "kernel/proc_def.h"
+#include "drivers/uart.h"
+
+extern struct cpu cpus[];
+
+namespace ProcManager
+{
+ void sleep(void *chan, Spinlock *lk);
+ void wakeup(void *chan);
+}
+
+// Interrupt Depth Management
+void push_off(void);
+void pop_off(void);
+
+// // Get the pointer of the current CPU
+// // Depends on r_tp() in riscv.h to read the tp register
+// struct cpu *mycpu()
+// {
+// int id = r_tp();
+// return &cpus[id];
+// }
+
+// Get the current process
+// Must be called with interrupts disabled, or read atomically in a safe manner
+struct Proc *myproc()
+{
+ push_off();
+ struct cpu *c = mycpu();
+ struct Proc *p = c->proc;
+ pop_off();
+ return p;
+}
+
+// Spinlock Implementation
+
+void Spinlock::init(const char *lock_name)
+{
+ name = lock_name;
+ locked = 0;
+ cpu = nullptr;
+}
+
+void Spinlock::acquire()
+{
+ push_off(); // Disable interrupts: prevent deadlocks (interrupt handler requests a lock that is already held)
+
+ if (holding())
+ {
+ Drivers::uart_puts("panic: recursive acquire ");
+ Drivers::uart_puts(name);
+ Drivers::uart_puts("\n");
+ while (1)
+ ;
+ }
+
+ // Atomic Exchange
+ // __ATOMIC_ACQUIRE: Ensures that reads and writes after the acquire are not reordered before the acquire
+ while (__atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE))
+ {
+ // Busy-wait (Spinning)
+ // Optimization: Read-only, no writes, reducing cache coherence traffic
+ while (__atomic_load_n(&locked, __ATOMIC_RELAXED) == 1)
+ {
+ asm volatile("nop"); // RISC-V indicates that the CPU is in a spin state
+ }
+ }
+
+ // Record holder
+ cpu = mycpu();
+}
+
+void Spinlock::release()
+{
+ if (!holding())
+ {
+ Drivers::uart_puts("panic: release not holding\n");
+ while (1)
+ ;
+ }
+
+ cpu = nullptr;
+
+ // Atomic Store
+ // __ATOMIC_RELEASE: Ensures all reads and writes before the Release are completed
+ __atomic_store_n(&locked, 0, __ATOMIC_RELEASE);
+
+ pop_off();
+}
+
+// Check if the current CPU holds the lock
+bool Spinlock::holding()
+{
+ // Interrupt checks must be disabled, otherwise a process switch may occur causing CPU fluctuations.
+ int r;
+ push_off();
+ r = (locked && cpu == mycpu());
+ pop_off();
+ return r;
+}
+
+
+// Mutex Implementation (Sleep Locks)
+
+void Mutex::init(const char *lock_name)
+{
+ name = lock_name;
+ locked = 0;
+ pid = 0;
+ lk.init("mutex_internal");
+}
+
+void Mutex::acquire()
+{
+ lk.acquire(); // Acquire the internal lock to protect the locked field
+
+ while (locked)
+ {
+ // Lock is held, preparing to sleep
+ // Atomicity contract of sleep():
+ // 1. Mark the current process as SLEEPING
+ // 2. Release lk (so others can operate on locked)
+ // 3. Switch CPU (context switch)
+ // 4. After being woken up, reacquire the lk lock and return
+ ProcManager::sleep(this, &lk);
+ }
+
+ locked = 1;
+ pid = myproc()->pid;
+
+ lk.release();
+}
+
+void Mutex::release()
+{
+ lk.acquire();
+
+ locked = 0;
+ pid = 0;
+
+ // Wake up all processes sleeping on this lock (chan = this)
+ ProcManager::wakeup(this);
+
+ lk.release();
+}
+
+bool Mutex::holding()
+{
+ lk.acquire(); // Must use a lock when reading to prevent race conditions.
+ bool r = locked && (pid == myproc()->pid);
+ lk.release();
+ return r;
+}
+
+// Interrupt Depth Management
+
+void push_off(void)
+{
+ int old = intr_get();
+ intr_off();
+
+ struct cpu *c = mycpu();
+
+ if (c->noff == 0)
+ c->intena = old;
+
+ c->noff += 1;
+}
+
+void pop_off(void)
+{
+ struct cpu *c = mycpu();
+
+ if (intr_get())
+ {
+ Drivers::uart_puts("panic: pop_off - interruptible\n");
+ while (1)
+ ;
+ }
+ if (c->noff < 1)
+ {
+ Drivers::uart_puts("panic: pop_off count\n");
+ while (1)
+ ;
+ }
+
+ c->noff -= 1;
+
+ // An interrupt is only truly enabled when the nesting level returns to zero and the outermost layer allows interrupts.
+ if (c->noff == 0 && c->intena)
+ intr_on();
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/syscall.cc b/lume-legacy/src/kernel/syscall.cc
new file mode 100644
index 0000000..0979c24
--- /dev/null
+++ b/lume-legacy/src/kernel/syscall.cc
@@ -0,0 +1,698 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard Qin
+ */
+#include "kernel/syscall.h"
+#include "common/types.h"
+#include "common/fcntl.h"
+#include "kernel/proc.h"
+#include "kernel/riscv.h"
+#include "drivers/uart.h"
+#include "drivers/virtio.h"
+#include "kernel/timer.h"
+#include "kernel/mm.h"
+#include "kernel/trap.h"
+#include "kernel/slab.h"
+#include "kernel/sbi.h"
+#include "fs/fat32.h"
+#include "fs/file.h"
+#include "lib/string.h"
+#include "lib/errno.h"
+
+extern "C++" int fork();
+namespace Exec
+{
+ int exec(char *path, char **argv);
+}
+
+namespace ProcManager
+{
+ void exit(int status);
+ int wait(uint64_t addr);
+}
+
+static uint64_t argraw(int n)
+{
+ Proc *p = myproc();
+ switch (n)
+ {
+ case 0:
+ return p->tf->a0;
+ case 1:
+ return p->tf->a1;
+ case 2:
+ return p->tf->a2;
+ case 3:
+ return p->tf->a3;
+ case 4:
+ return p->tf->a4;
+ case 5:
+ return p->tf->a5;
+ default:
+ return static_cast<uint64_t>(-1);
+ }
+}
+
+static int argint(int n, int *ip)
+{
+ *ip = static_cast<int>(argraw(n));
+ return 0;
+}
+
+static int argstr(int n, char *buf, int max)
+{
+ uint64 addr = argraw(n);
+ return VM::copyinstr(myproc()->pagetable, buf, addr, max);
+}
+
+struct KernelArgv
+{
+ static const int MAX_ARGS = 32;
+ static const int MAX_ARG_LEN = 128;
+ char *args[MAX_ARGS];
+
+ KernelArgv() { memset(args, 0, sizeof(args)); }
+ ~KernelArgv()
+ {
+ for (int i = 0; i < MAX_ARGS; i++)
+ if (args[i])
+ {
+ Slab::kfree(args[i]);
+ args[i] = nullptr;
+ }
+ }
+ KernelArgv(const KernelArgv &) = delete;
+ KernelArgv &operator=(const KernelArgv &) = delete;
+};
+
+static int fdalloc(struct file *f)
+{
+ struct Proc *p = myproc();
+ for (int i = 0; i < NOFILE; i++)
+ {
+ if (p->ofile[i] == 0)
+ {
+ p->ofile[i] = f;
+ return i;
+ }
+ }
+ return -1;
+}
+
+// sys_openat(int dfd, const char *filename, int flags, mode_t mode)
+static uint64 sys_openat()
+{
+ char path[128];
+ int dfd, omode, mode_unused;
+ int fd;
+ struct file *f;
+ Inode *ip;
+
+ if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &omode) < 0 || argint(3, &mode_unused) < 0)
+ return -EINVAL;
+
+ if (omode & O_CREATE)
+ {
+ ip = VFS::namei(path);
+ if (ip == nullptr)
+ {
+ char name[128];
+ Inode *dp = VFS::nameiparent(path, name);
+ if (dp)
+ {
+ VFS::ilock(dp);
+ ip = dp->create(name, T_FILE, 0, 0);
+ VFS::iunlockput(dp);
+ }
+ }
+ }
+ else
+ {
+ ip = VFS::namei(path);
+ }
+
+ if (ip == nullptr)
+ return -ENOENT;
+
+ VFS::ilock(ip);
+ if ((omode & O_TRUNC) && ip->type == T_FILE)
+ {
+ ip->truncate();
+ }
+ VFS::iunlock(ip);
+
+ f = FileTable::alloc();
+ if (f == nullptr)
+ {
+ VFS::iput(ip);
+ return -ENFILE;
+ }
+
+ fd = fdalloc(f);
+ if (fd < 0)
+ {
+ f->ref = 0;
+ f->type = FD_NONE;
+ VFS::iput(ip);
+ return -EMFILE;
+ }
+
+ f->type = FD_INODE;
+ f->ip = ip;
+ f->off = 0;
+ f->readable = !(omode & O_WRONLY);
+ f->writable = (omode & O_WRONLY) || (omode & O_RDWR);
+
+ return fd;
+}
+
+// sys_mkdirat(int dfd, const char *pathname, mode_t mode)
+static uint64 sys_mkdirat()
+{
+ char path[128];
+ int dfd, mode;
+
+ if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0)
+ return -EINVAL;
+
+ Inode *ip = VFS::namei(path);
+ if (ip != nullptr)
+ {
+ VFS::iput(ip);
+ return -EEXIST;
+ }
+
+ char name[128];
+ Inode *dp = VFS::nameiparent(path, name);
+ if (dp == nullptr)
+ return -ENOENT;
+
+ VFS::ilock(dp);
+ ip = dp->create(name, T_DIR, 0, 0);
+ VFS::iunlockput(dp);
+
+ if (ip == nullptr)
+ return -EIO;
+
+ VFS::iput(ip);
+ return 0;
+}
+
+static uint64 sys_chdir()
+{
+ char path[128];
+ if (argstr(0, path, 128) < 0)
+ return -EFAULT;
+
+ Inode *ip = VFS::namei(path);
+ if (ip == nullptr)
+ return -ENOENT;
+
+ VFS::ilock(ip);
+ if (ip->type != T_DIR)
+ {
+ VFS::iunlockput(ip);
+ return -ENOTDIR;
+ }
+ VFS::iunlock(ip);
+
+ struct Proc *p = myproc();
+ if (p->cwd)
+ VFS::iput(p->cwd);
+ p->cwd = ip;
+ return 0;
+}
+
+static uint64 sys_dup()
+{
+ int oldfd;
+ if (argint(0, &oldfd) < 0)
+ return -EINVAL;
+
+ struct Proc *p = myproc();
+ if (oldfd < 0 || oldfd >= NOFILE || p->ofile[oldfd] == nullptr)
+ return -EBADF;
+
+ struct file *f = FileTable::dup(p->ofile[oldfd]);
+ int newfd = fdalloc(f);
+ if (newfd < 0)
+ {
+ FileTable::close(f);
+ return -EMFILE;
+ }
+ return newfd;
+}
+
+static uint64 sys_fstat()
+{
+ int fd;
+ uint64 stat_addr;
+
+ if (argint(0, &fd) < 0 || argint(1, (int *)&stat_addr) < 0)
+ return -EINVAL;
+
+ struct Proc *p = myproc();
+ if (fd < 0 || fd >= NOFILE || p->ofile[fd] == nullptr)
+ return -EBADF;
+
+ if (FileTable::stat(p->ofile[fd], stat_addr) < 0)
+ return -EIO;
+
+ return 0;
+}
+
+// sys_mknodat(int dfd, const char *filename, mode_t mode, dev_t dev)
+static uint64 sys_mknodat()
+{
+ char path[128];
+ int dfd, mode, dev;
+
+ if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0 || argint(3, &dev) < 0)
+ return -EINVAL;
+
+ Inode *ip = VFS::namei(path);
+ if (ip != nullptr)
+ {
+ VFS::iput(ip);
+ return -EEXIST;
+ }
+
+ char name[128];
+ Inode *dp = VFS::nameiparent(path, name);
+ if (dp == nullptr)
+ return -ENOENT;
+
+ // Decode dev_t (Assuming simplified minor/major for now)
+ int major = (dev >> 20) & 0xFFF; // Just a guess for Linux packing, or pass raw
+ int minor = dev & 0xFFFFF;
+
+ // For Phase 1 compatibility with init.cc:
+ // init.cc passes raw major/minor. Shim will pack them.
+ // We just unpack naively or assume Shim passed us correct structure.
+
+ VFS::ilock(dp);
+ ip = dp->create(name, T_DEVICE, major, minor);
+ VFS::iunlockput(dp);
+
+ if (ip == nullptr)
+ return -EIO;
+
+ VFS::iput(ip);
+ return 0;
+}
+
+static uint64 sys_close()
+{
+ int fd;
+ if (argint(0, &fd) < 0)
+ return -EINVAL;
+
+ struct Proc *p = myproc();
+ if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0)
+ return -EBADF;
+
+ FileTable::close(p->ofile[fd]);
+ p->ofile[fd] = 0;
+ return 0;
+}
+
+static uint64 sys_pipe2()
+{
+ uint64 fdarray;
+ int flags;
+ struct file *rf, *wf;
+ int fd0, fd1;
+ struct Proc *p = myproc();
+
+ if (argint(0, (int *)&fdarray) < 0 || argint(1, &flags) < 0)
+ return -EFAULT;
+
+ if (Pipe::create_pair(&rf, &wf) < 0)
+ return -ENFILE;
+
+ if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0)
+ {
+ if (fd0 >= 0)
+ p->ofile[fd0] = nullptr;
+ FileTable::close(rf);
+ FileTable::close(wf);
+ return -EMFILE;
+ }
+
+ if (VM::copyout(p->pagetable, fdarray, (char *)&fd0, sizeof(int)) < 0 ||
+ VM::copyout(p->pagetable, fdarray + sizeof(int), (char *)&fd1, sizeof(int)) < 0)
+ {
+ p->ofile[fd0] = nullptr;
+ p->ofile[fd1] = nullptr;
+ FileTable::close(rf);
+ FileTable::close(wf);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static uint64 sys_unlinkat()
+{
+ char path[128], name[128];
+ int dfd, flag;
+
+ if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &flag) < 0)
+ return -EINVAL;
+
+ Inode *dp = VFS::nameiparent(path, name);
+ if (!dp)
+ return -ENOENT;
+
+ VFS::ilock(dp);
+ int ret = dp->unlink(name);
+ VFS::iunlockput(dp);
+
+ return (ret < 0) ? -EIO : 0;
+}
+
+// sys_linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags)
+static uint64 sys_linkat()
+{
+ // FAT32 does not support hard links.
+ // Return Error to allow compilation/running to proceed (ln will fail gracefully).
+ return -EPERM;
+}
+
+static uint64 sys_execve()
+{
+ char path[128];
+ uint64 uargv_ptr;
+
+ if (argstr(0, path, sizeof(path)) < 0)
+ return -EFAULT;
+
+ uargv_ptr = argraw(1);
+ // uenvp_ptr = argraw(2); // Ignored
+
+ KernelArgv kargv;
+
+ for (int i = 0; i < KernelArgv::MAX_ARGS; ++i)
+ {
+ uint64 uarg_str_ptr;
+ if (VM::copyin(myproc()->pagetable, (char *)&uarg_str_ptr, uargv_ptr + i * sizeof(uint64), sizeof(uint64)) < 0)
+ {
+ return -EFAULT;
+ }
+
+ if (uarg_str_ptr == 0)
+ {
+ kargv.args[i] = nullptr;
+ break;
+ }
+
+ kargv.args[i] = (char *)Slab::kmalloc(KernelArgv::MAX_ARG_LEN);
+ if (kargv.args[i] == nullptr)
+ {
+ return -ENOMEM;
+ }
+
+ if (VM::copyinstr(myproc()->pagetable, kargv.args[i], uarg_str_ptr, KernelArgv::MAX_ARG_LEN) < 0)
+ {
+ return -EFAULT;
+ }
+ }
+ return Exec::exec(path, kargv.args);
+}
+
+static uint64 sys_exit()
+{
+ int n;
+ if (argint(0, &n) < 0)
+ return -EINVAL;
+ ProcManager::exit(n);
+ return 0;
+}
+
+static uint64 sys_clone()
+{
+ return static_cast<uint64>(fork());
+}
+
+static uint64 sys_wait4()
+{
+ uint64 status_addr;
+
+ // argraw(0) is pid (ignored)
+ status_addr = argraw(1);
+ // argraw(2) is options (ignored)
+ // argraw(3) is rusage (ignored)
+
+ if (status_addr == static_cast<uint64>(-1))
+ return -EINVAL;
+
+ return static_cast<uint64>(ProcManager::wait(status_addr));
+}
+
+static uint64 sys_getpid()
+{
+ return static_cast<uint64>(myproc()->pid);
+}
+
+static uint64 sys_putc()
+{
+ char c = static_cast<char>(argraw(0));
+ Drivers::uart_putc(c);
+ return 0;
+}
+
+static uint64 sys_write()
+{
+ struct file *f;
+ int n;
+ uint64 p;
+ int fd;
+
+ if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0)
+ return -EINVAL;
+
+ p = argraw(1);
+
+ struct Proc *proc = myproc();
+
+ if (fd == 1 && proc->ofile[fd] == nullptr)
+ {
+ constexpr int MAX_WRITE_BUF = 128;
+ char buf[MAX_WRITE_BUF];
+ int i = 0;
+
+ while (i < n)
+ {
+ int len = n - i;
+ if (len > MAX_WRITE_BUF)
+ len = MAX_WRITE_BUF;
+
+ if (VM::copyin(proc->pagetable, buf, p + i, static_cast<uint64>(len)) < 0)
+ return -EFAULT;
+
+ for (int j = 0; j < len; j++)
+ {
+ Drivers::uart_putc(buf[j]);
+ }
+ i += len;
+ }
+ return static_cast<uint64>(n);
+ }
+
+ if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0)
+ return -EBADF;
+
+ return FileTable::write(f, p, n);
+}
+
+static uint64_t sys_read()
+{
+ struct file *f;
+ int n;
+ uint64 p;
+ int fd;
+
+ if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0)
+ return -EINVAL;
+
+ p = argraw(1);
+
+ struct Proc *proc = myproc();
+ if (fd == 0 && proc->ofile[fd] == nullptr)
+ return Drivers::console_read(p, n);
+
+ if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0)
+ return -EBADF;
+
+ return FileTable::read(f, p, n);
+}
+
+static uint64 sys_brk()
+{
+ int n;
+ if (argint(0, &n) < 0)
+ return -EINVAL;
+
+ uint64 addr = myproc()->sz;
+ if (ProcManager::growproc(n) < 0)
+ return -ENOMEM;
+
+ return addr;
+}
+
+static uint64 sys_kill()
+{
+ int pid;
+ if (argint(0, &pid) < 0)
+ return -EINVAL;
+ return ProcManager::kill(pid);
+}
+
+static uint64 sys_sleep()
+{
+ int n;
+ uint64 ticks0;
+
+ if (argint(0, &n) < 0)
+ return -EINVAL;
+
+ Spinlock *lk = Timer::get_lock();
+ lk->acquire();
+ ticks0 = Timer::get_ticks();
+ while (Timer::get_ticks() - ticks0 < (uint64)n)
+ {
+ if (myproc()->killed)
+ {
+ lk->release();
+ return -EINTR;
+ }
+ ProcManager::sleep(Timer::get_tick_chan(), lk);
+ }
+ lk->release();
+ return 0;
+}
+
+static uint64 sys_disk_test()
+{
+ // VirtIO::test_rw();
+ // VirtIO::test_bio();
+ fat32_test();
+ return 0;
+}
+
+static uint64 sys_lseek()
+{
+ int fd, offset, whence;
+ if (argint(0, &fd) < 0 || argint(1, &offset) < 0 || argint(2, &whence) < 0)
+ return -EINVAL;
+
+ struct Proc *p = myproc();
+ if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0)
+ return -EBADF;
+
+ return FileTable::lseek(p->ofile[fd], offset, whence);
+}
+
+static uint64 sys_reboot()
+{
+ SBI::sbi_shutdown();
+ return 0;
+}
+
+void syscall()
+{
+ Proc *p = myproc();
+ int num = p->tf->a7;
+ uint64 ret = static_cast<uint64>(-ENOSYS);
+
+ switch (num)
+ {
+ case SYS_mknodat:
+ ret = sys_mknodat();
+ break;
+ case SYS_mkdirat:
+ ret = sys_mkdirat();
+ break;
+ case SYS_unlinkat:
+ ret = sys_unlinkat();
+ break;
+ case SYS_linkat:
+ ret = sys_linkat();
+ break;
+ case SYS_chdir:
+ ret = sys_chdir();
+ break;
+ case SYS_openat:
+ ret = sys_openat();
+ break;
+ case SYS_dup:
+ ret = sys_dup();
+ break;
+ case SYS_close:
+ ret = sys_close();
+ break;
+ case SYS_pipe2:
+ ret = sys_pipe2();
+ break;
+ case SYS_lseek:
+ ret = sys_lseek();
+ break;
+ case SYS_read:
+ ret = sys_read();
+ break;
+ case SYS_write:
+ ret = sys_write();
+ break;
+ case SYS_fstat:
+ ret = sys_fstat();
+ break;
+ case SYS_exit:
+ ret = sys_exit();
+ break;
+ case SYS_exit_group:
+ ret = sys_exit();
+ break;
+ case SYS_kill:
+ ret = sys_kill();
+ break;
+ case SYS_getpid:
+ ret = sys_getpid();
+ break;
+ case SYS_brk:
+ ret = sys_brk();
+ break;
+ case SYS_clone:
+ ret = sys_clone();
+ break;
+ case SYS_execve:
+ ret = sys_execve();
+ if (ret == 0)
+ return;
+ break;
+ case SYS_wait4:
+ ret = sys_wait4();
+ break;
+
+ // Custom / Debug
+ case SYS_reboot:
+ ret = sys_reboot();
+ break;
+ case SYS_putc:
+ ret = sys_putc();
+ break;
+ case SYS_disk_test:
+ ret = sys_disk_test();
+ break;
+ case SYS_nanosleep:
+ ret = sys_sleep();
+ break;
+
+ default:
+ Drivers::uart_puts("Unknown Syscall ID: ");
+ Drivers::print_hex(num);
+ Drivers::uart_puts("\n");
+ ret = -ENOSYS;
+ break;
+ }
+
+ p->tf->a0 = ret;
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/timer.cc b/lume-legacy/src/kernel/timer.cc
new file mode 100644
index 0000000..fc02f27
--- /dev/null
+++ b/lume-legacy/src/kernel/timer.cc
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "common/types.h"
+#include "kernel/config.h"
+#include "kernel/spinlock.h"
+#include "kernel/proc.h"
+#include "kernel/sbi.h"
+
+#define INTERVAL 1000000
+
+namespace Timer
+{
+ uint64 ticks;
+ Spinlock tickslock;
+
+ uint64 get_time()
+ {
+ uint64 time;
+ asm volatile("rdtime %0" : "=r"(time));
+ return time;
+ }
+
+ void set_next_trigger()
+ {
+ SBI::set_timer(get_time() + INTERVAL);
+ }
+
+ void init()
+ {
+ set_next_trigger();
+
+ uint64 sie;
+ asm volatile("csrr %0, sie" : "=r"(sie));
+ sie |= (1L << 5);
+ asm volatile("csrw sie, %0" : : "r"(sie));
+ }
+
+ void tick()
+ {
+ tickslock.acquire();
+ ticks++;
+ ProcManager::wakeup(&ticks);
+ tickslock.release();
+ set_next_trigger();
+ }
+
+ uint64 get_ticks()
+ {
+ tickslock.acquire();
+ uint64 t = ticks;
+ tickslock.release();
+ return t;
+ }
+
+ Spinlock *get_lock()
+ {
+ return &tickslock;
+ }
+
+ void *get_tick_chan()
+ {
+ return &ticks;
+ }
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/trap/handler.cc b/lume-legacy/src/kernel/trap/handler.cc
new file mode 100644
index 0000000..b7bd5f0
--- /dev/null
+++ b/lume-legacy/src/kernel/trap/handler.cc
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "kernel/trap.h"
+#include "kernel/riscv.h"
+#include "kernel/proc.h"
+#include "drivers/uart.h"
+#include "kernel/syscall.h"
+#include "kernel/mm.h"
+#include "kernel/timer.h"
+#include "drivers/plic.h"
+#include "drivers/virtio.h"
+#include "kernel/fdt.h"
+#include "lib/string.h"
+
+
+extern "C" void kernelvec();
+
+// Handle Function
+
+void panic(const char *s)
+{
+ Drivers::uart_puts("\n\n!!! KERNEL PANIC !!!\n");
+ Drivers::uart_puts(s);
+ Drivers::uart_puts("\nSystem Halted.\n");
+ while (1)
+ ;
+}
+
+// Trap Implementation
+
+namespace Trap
+{
+ void init()
+ {
+ Drivers::uart_puts("[Trap] Global init done.\n");
+ }
+
+ // Each CPU core needs to call this function when starting to set its own stvec
+ void inithart()
+ {
+ w_stvec((uint64)kernelvec);
+
+ // Ensure that the S-Mode interrupt enable bit (SIE) is cleared during initialization
+ // Interrupt enabling will be done by the scheduler when the first process runs
+ w_sstatus(r_sstatus() & ~SSTATUS_SIE);
+
+ // Enable the external interrupt, clock interrupt, and software interrupt bits in S-Mode (in SIE register)
+ uint64 sie = r_sie();
+ sie |= SIE_SEIE | SIE_STIE | SIE_SSIE;
+ w_sie(sie);
+
+ Drivers::uart_puts("[Trap] Hart initialized (stvec set).\n");
+ }
+
+ // Device Interrupt Check
+ int devintr()
+ {
+ uint64 scause = r_scause();
+
+ bool is_interrupt = (scause & 0x8000000000000000L) != 0;
+ uint64 code = scause & 0xff;
+
+ if (is_interrupt && code == 9)
+ {
+ // Supervisor External Interrupt (IRQ 9)
+ int irq = PLIC::claim();
+
+ if (irq == (int)g_devices.uart.irq)
+ {
+ Drivers::uart_intr();
+ }
+ else
+ {
+ // Check if it's a VirtIO IRQ
+ bool handled = false;
+ for (int i = 0; i < g_devices.virtio_count; i++)
+ {
+ if (irq == (int)g_devices.virtio[i].irq)
+ {
+ VirtIO::intr();
+ handled = true;
+ break;
+ }
+ }
+ }
+
+ if (irq > 0)
+ {
+ PLIC::complete(irq);
+ }
+
+ return 1;
+ }
+ else if (is_interrupt && code == 5)
+ {
+ // Supervisor Timer Interrupt (IRQ 5)
+ // Timer::set_next_trigger();
+ Timer::tick();
+ // w_sip(r_sip() & ~2);
+ return 2;
+ }
+
+ return 0;
+ }
+}
+
+// Exception Handling Entry
+
+extern "C" void kerneltrap(struct Trapframe *tf)
+{
+ uint64 sepc = r_sepc();
+ uint64 sstatus = r_sstatus();
+ uint64 scause = r_scause();
+
+ if ((sstatus & SSTATUS_SPP) == 0)
+ panic("kerneltrap: not from supervisor mode");
+
+ if (intr_get() != 0)
+ panic("kerneltrap: interrupts enabled");
+
+ if (Trap::devintr() == 0)
+ {
+ Drivers::uart_puts("\n--- KERNEL EXCEPTION ---\n");
+ Drivers::uart_puts("scause: ");
+ Drivers::print_hex(scause);
+ Drivers::uart_puts("\n");
+ Drivers::uart_puts("sepc: ");
+ Drivers::print_hex(sepc);
+ Drivers::uart_puts("\n");
+ Drivers::uart_puts("stval: ");
+ Drivers::print_hex(r_stval());
+ Drivers::uart_puts("\n");
+ panic("kerneltrap exception");
+ }
+
+ w_sepc(sepc);
+ w_sstatus(sstatus);
+}
+
+extern "C" void usertrap()
+{
+ // Drivers::uart_puts("[Debug] Entered usertrap!\n");
+ int which_dev = 0;
+
+ if ((r_sstatus() & SSTATUS_SPP) != 0)
+ panic("usertrap: not from user mode");
+
+ w_stvec((uint64)kernelvec);
+
+ struct Proc *p = myproc();
+ if (!p)
+ panic("usertrap: no process");
+
+ p->tf->epc = r_sepc();
+
+ uint64 scause = r_scause();
+ uint64 stval = r_stval();
+
+ if (scause == 8) // Syscall
+ {
+ if (p->state == ZOMBIE)
+ ProcManager::exit(-1);
+ p->tf->epc += 4;
+ intr_on();
+ syscall();
+ intr_off();
+ }
+ else if (scause == 15) // Store Page Fault
+ {
+ if (VM::handle_cow_fault(p->pagetable, PGROUNDDOWN(stval)) != 0)
+ {
+ uint64 pa = VM::walkaddr(p->pagetable, stval);
+
+ if (pa == 0)
+ {
+ Drivers::uart_puts("usertrap: Segmentation Fault (unmapped) at ");
+ }
+ else
+ {
+ Drivers::uart_puts("usertrap: Protection Fault (or OOM) at ");
+ }
+
+ Drivers::print_hex(stval);
+ Drivers::uart_puts("\n");
+ p->state = ZOMBIE;
+ ProcManager::exit(-1);
+ }
+ }
+ else if ((which_dev = Trap::devintr()) != 0)
+ {
+ if (which_dev == 2)
+ {
+ ProcManager::yield();
+ }
+ }
+ else
+ {
+ Drivers::uart_puts("\nusertrap(): unexpected scause ");
+ Drivers::print_hex(scause);
+ Drivers::uart_puts(" pid=");
+ Drivers::uart_put_int(p->pid);
+ Drivers::uart_puts(" sepc=");
+ Drivers::print_hex(p->tf->epc);
+ Drivers::uart_puts(" stval=");
+ Drivers::print_hex(stval);
+ Drivers::uart_puts("\n");
+ p->state = ZOMBIE;
+ ProcManager::exit(-1);
+ }
+
+ if (p->state == ZOMBIE)
+ ProcManager::exit(-1);
+
+ usertrapret();
+} \ No newline at end of file
diff --git a/lume-legacy/src/kernel/trap/trampoline.S b/lume-legacy/src/kernel/trap/trampoline.S
new file mode 100644
index 0000000..57d6f25
--- /dev/null
+++ b/lume-legacy/src/kernel/trap/trampoline.S
@@ -0,0 +1,101 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+.section .text
+.globl trampoline
+.globl uservec
+.globl userret
+
+.align 12
+trampoline:
+.align 4
+uservec:
+
+ csrrw a0, sscratch, a0
+
+ sd ra, 40(a0)
+ sd sp, 48(a0)
+ sd gp, 56(a0)
+ sd tp, 64(a0)
+ sd t0, 72(a0)
+ sd t1, 80(a0)
+ sd t2, 88(a0)
+ sd s0, 96(a0)
+ sd s1, 104(a0)
+ sd a1, 120(a0)
+ sd a2, 128(a0)
+ sd a3, 136(a0)
+ sd a4, 144(a0)
+ sd a5, 152(a0)
+ sd a6, 160(a0)
+ sd a7, 168(a0)
+ sd s2, 176(a0)
+ sd s3, 184(a0)
+ sd s4, 192(a0)
+ sd s5, 200(a0)
+ sd s6, 208(a0)
+ sd s7, 216(a0)
+ sd s8, 224(a0)
+ sd s9, 232(a0)
+ sd s10, 240(a0)
+ sd s11, 248(a0)
+ sd t3, 256(a0)
+ sd t4, 264(a0)
+ sd t5, 272(a0)
+ sd t6, 280(a0)
+
+ csrr t0, sscratch
+ sd t0, 112(a0)
+
+ ld sp, 8(a0)
+ ld tp, 32(a0)
+ ld t0, 16(a0)
+ ld t1, 0(a0)
+
+ csrw satp, t1
+ sfence.vma zero, zero
+
+ jr t0
+
+.align 4
+userret:
+
+ csrw satp, a1
+ sfence.vma zero, zero
+
+ csrw sscratch, a0
+
+ ld ra, 40(a0)
+ ld sp, 48(a0)
+ ld gp, 56(a0)
+ ld tp, 64(a0)
+ ld t0, 72(a0)
+ ld t1, 80(a0)
+ ld t2, 88(a0)
+ ld s0, 96(a0)
+ ld s1, 104(a0)
+ ld a1, 120(a0)
+ ld a2, 128(a0)
+ ld a3, 136(a0)
+ ld a4, 144(a0)
+ ld a5, 152(a0)
+ ld a6, 160(a0)
+ ld a7, 168(a0)
+ ld s2, 176(a0)
+ ld s3, 184(a0)
+ ld s4, 192(a0)
+ ld s5, 200(a0)
+ ld s6, 208(a0)
+ ld s7, 216(a0)
+ ld s8, 224(a0)
+ ld s9, 232(a0)
+ ld s10, 240(a0)
+ ld s11, 248(a0)
+ ld t3, 256(a0)
+ ld t4, 264(a0)
+ ld t5, 272(a0)
+ ld t6, 280(a0)
+
+ ld a0, 112(a0)
+
+ sret \ No newline at end of file
diff --git a/lume-legacy/src/kernel/trap/trap.S b/lume-legacy/src/kernel/trap/trap.S
new file mode 100644
index 0000000..8bce53d
--- /dev/null
+++ b/lume-legacy/src/kernel/trap/trap.S
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2026 Richard Qin
+
+.section .text
+.global kernelvec
+.global uservec
+.global userret
+.align 12
+
+kernelvec:
+ addi sp, sp, -288
+
+ sd ra, 40(sp)
+ sd sp, 48(sp)
+ sd gp, 56(sp)
+ sd tp, 64(sp)
+ sd t0, 72(sp)
+ sd t1, 80(sp)
+ sd t2, 88(sp)
+ sd s0, 96(sp)
+ sd s1, 104(sp)
+ sd a0, 112(sp)
+ sd a1, 120(sp)
+ sd a2, 128(sp)
+ sd a3, 136(sp)
+ sd a4, 144(sp)
+ sd a5, 152(sp)
+ sd a6, 160(sp)
+ sd a7, 168(sp)
+ sd s2, 176(sp)
+ sd s3, 184(sp)
+ sd s4, 192(sp)
+ sd s5, 200(sp)
+ sd s6, 208(sp)
+ sd s7, 216(sp)
+ sd s8, 224(sp)
+ sd s9, 232(sp)
+ sd s10, 240(sp)
+ sd s11, 248(sp)
+ sd t3, 256(sp)
+ sd t4, 264(sp)
+ sd t5, 272(sp)
+ sd t6, 280(sp)
+
+ csrr t0, sepc
+ csrr t1, sstatus
+ sd t0, 24(sp) # Trapframe.epc
+ sd t1, 32(sp) # Trapframe.kernel_hartid
+
+ mv a0, sp
+ call kerneltrap
+
+ ld t0, 24(sp)
+ ld t1, 32(sp)
+ csrw sepc, t0
+ csrw sstatus, t1
+
+ ld ra, 40(sp)
+ ld gp, 56(sp)
+ ld tp, 64(sp)
+ ld t0, 72(sp)
+ ld t1, 80(sp)
+ ld t2, 88(sp)
+ ld s0, 96(sp)
+ ld s1, 104(sp)
+ ld a0, 112(sp)
+ ld a1, 120(sp)
+ ld a2, 128(sp)
+ ld a3, 136(sp)
+ ld a4, 144(sp)
+ ld a5, 152(sp)
+ ld a6, 160(sp)
+ ld a7, 168(sp)
+ ld s2, 176(sp)
+ ld s3, 184(sp)
+ ld s4, 192(sp)
+ ld s5, 200(sp)
+ ld s6, 208(sp)
+ ld s7, 216(sp)
+ ld s8, 224(sp)
+ ld s9, 232(sp)
+ ld s10, 240(sp)
+ ld s11, 248(sp)
+ ld t3, 256(sp)
+ ld t4, 264(sp)
+ ld t5, 272(sp)
+ ld t6, 280(sp)
+
+ addi sp, sp, 288
+ sret \ No newline at end of file