summaryrefslogtreecommitdiff
path: root/na/kernel
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
commite90b72ec3082588b296956730bd2930cf8d5d671 (patch)
tree36ff6916af9b4176ea5a9662219d11e721f0b691 /na/kernel
parent1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff)
downloadlume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz
lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'na/kernel')
-rw-r--r--na/kernel/exec.cc659
-rw-r--r--na/kernel/irq.cc106
-rw-r--r--na/kernel/main.cc468
-rw-r--r--na/kernel/module.mk26
-rw-r--r--na/kernel/pgrp.cc148
-rw-r--r--na/kernel/pid.cc63
-rw-r--r--na/kernel/poll.cc250
-rw-r--r--na/kernel/ptrace.cc92
-rw-r--r--na/kernel/sched_class_o1.cc81
-rw-r--r--na/kernel/sched_core.cc571
-rw-r--r--na/kernel/sched_internal.h115
-rw-r--r--na/kernel/signal.cc442
-rw-r--r--na/kernel/smp.cc112
-rw-r--r--na/kernel/sync/cpu_state.h35
-rw-r--r--na/kernel/sync/mutex.cc156
-rw-r--r--na/kernel/sync/spinlock.cc120
-rw-r--r--na/kernel/sync/spinlock.h61
-rw-r--r--na/kernel/sys_fs.cc679
-rw-r--r--na/kernel/sys_info.cc49
-rw-r--r--na/kernel/sys_misc.cc360
-rw-r--r--na/kernel/sys_mm.cc178
-rw-r--r--na/kernel/sys_process.cc185
-rw-r--r--na/kernel/sys_signal.cc351
-rw-r--r--na/kernel/syscall.cc208
-rw-r--r--na/kernel/task.cc780
-rw-r--r--na/kernel/timer.cc118
-rw-r--r--na/kernel/trap.cc258
-rw-r--r--na/kernel/uaccess.cc53
-rw-r--r--na/kernel/waitqueue.cc144
29 files changed, 0 insertions, 6868 deletions
diff --git a/na/kernel/exec.cc b/na/kernel/exec.cc
deleted file mode 100644
index 616c0da..0000000
--- a/na/kernel/exec.cc
+++ /dev/null
@@ -1,659 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * exec.cc — ELF64 binary loader and execve implementation
- *
- * Loads a static ELF64 executable into a fresh address space,
- * constructs the user stack (argc/argv/envp/auxv), and redirects
- * the current task's trapframe to begin executing at the ELF entry.
- *
- * Reference: Linux fs/binfmt_elf.c, fs/exec.c
- */
-
-#include <lume/elf.h>
-#include <lume/types.h>
-#include <lume/task.h>
-#include <lume/vmm.h>
-#include <lume/addr.h>
-#include <lume/config.h>
-#include <lume/errno.h>
-#include <lume/pmm.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/file.h>
-#include <lume/fs.h>
-#include <lume/dcache.h>
-#include <lume/namei.h>
-#include <lume/fdtable.h>
-#include <lume/mm_layout.h>
-#include <lume/signal_types.h>
-#include <arch/pmap.h>
-#include <arch/trapframe.h>
-#include <arch/mmu.h>
-#include <arch/cpu.h>
-#include <arch/trap.h>
-
-/* Assembly entry points (trap_vector.S) */
-extern "C" void trap_vector();
-extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*);
-extern "C" void userret(arch::TrapFrame* tf);
-
-/* ================================================================== */
-/* Constants */
-/* ================================================================== */
-
-/* User stack layout comes from mm_layout.h (MI layer).
- * kUserStackTop, kUserStackBase, kUserStackSize are defined there,
- * derived from kUserVaLimit so they automatically adapt to the
- * page table mode (SV39/SV48). */
-
-/* ================================================================== */
-/* Helper: kernel_read — read from a file into a kernel buffer */
-/* ================================================================== */
-
-static int64 kernel_read(file* fp, void* buf, uint64 count, int64 offset)
-{
- int64 pos = offset;
- if (!fp->f_op || !fp->f_op->read) return -EINVAL;
- return fp->f_op->read(fp, static_cast<char*>(buf), count, &pos);
-}
-
-/* ================================================================== */
-/* Helper: copy data to a user page via kernel direct map */
-/* ================================================================== */
-
-static void memcpy_kernel(void* dst, const void* src, uint64 n)
-{
- auto* d = static_cast<uint8*>(dst);
- auto* s = static_cast<const uint8*>(src);
- for (uint64 i = 0; i < n; i++)
- d[i] = s[i];
-}
-
-static void memset_kernel(void* dst, uint8 val, uint64 n)
-{
- auto* d = static_cast<uint8*>(dst);
- for (uint64 i = 0; i < n; i++)
- d[i] = val;
-}
-
-static uint64 strlen_kernel(const char* s)
-{
- uint64 n = 0;
- while (s[n]) n++;
- return n;
-}
-
-/* ================================================================== */
-/* Helper: allocate and map a page at a specific user VA */
-/* ================================================================== */
-
-static int map_page_at(VmSpace* mm, uint64 va, uint64 pte_perm)
-{
- Page* pg = pmm_alloc_page();
- if (!pg) return -ENOMEM;
-
- uint64 pa = page_to_pa(pg).raw;
-
- /* Zero the page */
- void* kva = reinterpret_cast<void*>(pa_to_va(PhysAddr{pa}).raw);
- memset_kernel(kva, 0, kPageSize);
-
- int ret = pmap::map(mm->root_pa, va, pa, pte_perm);
- if (ret != 0) {
- pmm_free_page(pg);
- return -ENOMEM;
- }
- page_inc_mapcount(pg);
- return 0;
-}
-
-/* ================================================================== */
-/* load_elf_binary — parse ELF and map segments */
-/* ================================================================== */
-
-struct elf_load_info {
- uint64 entry; /* ELF entry point (after base offset) */
- uint64 phdr_addr; /* User VA where phdr was loaded */
- uint16 phnum; /* Number of program headers */
- uint16 phentsize; /* Size of each phdr */
- uint64 brk_start; /* End of highest PT_LOAD segment (for brk) */
- uint64 load_bias; /* PIE load bias (0 for ET_EXEC) */
-};
-
-static int load_elf_binary(file* fp, VmSpace* mm, elf_load_info* info)
-{
- /* Read ELF header */
- Elf64_Ehdr ehdr{};
- int64 n = kernel_read(fp, &ehdr, sizeof(ehdr), 0);
- if (n < static_cast<int64>(sizeof(ehdr))) {
- printk(KERN_ERR "exec: failed to read ELF header (got %lld)\n", n);
- return -ENOEXEC;
- }
-
- /* Validate magic */
- if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
- ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
- ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
- ehdr.e_ident[EI_MAG3] != ELFMAG3) {
- printk(KERN_ERR "exec: bad ELF magic\n");
- return -ENOEXEC;
- }
-
- /* Validate class (64-bit) and endianness (little-endian) */
- if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) return -ENOEXEC;
- if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) return -ENOEXEC;
-
- /* Validate type and machine */
- if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN) return -ENOEXEC;
- if (ehdr.e_machine != EM_RISCV) return -ENOEXEC;
-
- if (ehdr.e_phnum == 0 || ehdr.e_phentsize < sizeof(Elf64_Phdr))
- return -ENOEXEC;
-
- /* Read program headers */
- uint64 phdr_total = static_cast<uint64>(ehdr.e_phnum) * ehdr.e_phentsize;
- if (phdr_total > kPageSize) return -ENOEXEC; /* Sanity limit */
-
- /* Allocate kernel buffer for program headers */
- uint8 phdr_buf[512]; /* Max ~8 phdrs * 56 bytes each */
- if (phdr_total > sizeof(phdr_buf)) return -ENOEXEC;
-
- n = kernel_read(fp, phdr_buf, phdr_total, static_cast<int64>(ehdr.e_phoff));
- if (n < static_cast<int64>(phdr_total)) return -ENOEXEC;
-
- info->phnum = ehdr.e_phnum;
- info->phentsize = ehdr.e_phentsize;
- info->phdr_addr = 0;
- info->brk_start = 0;
- info->load_bias = 0;
-
- /*
- * PIE (ET_DYN): segments have p_vaddr starting near 0.
- * We must choose a load base and add it to all addresses.
- * Linux uses ELF_ET_DYN_BASE (~2/3 of TASK_SIZE); we use 0x400000.
- */
- constexpr uint64 kPieLoadBase = 0x400000;
- uint64 load_bias = (ehdr.e_type == ET_DYN) ? kPieLoadBase : 0;
- info->load_bias = load_bias;
- info->entry = ehdr.e_entry + load_bias;
-
- /* Process each PT_LOAD segment */
- for (uint16 i = 0; i < ehdr.e_phnum; i++) {
- auto* phdr = reinterpret_cast<Elf64_Phdr*>(
- phdr_buf + i * ehdr.e_phentsize);
-
- if (phdr->p_type == PT_PHDR) {
- /* Remember where program headers are mapped */
- info->phdr_addr = phdr->p_vaddr + load_bias;
- continue;
- }
-
- if (phdr->p_type != PT_LOAD) continue;
- if (phdr->p_memsz == 0) continue;
-
- /* Compute permissions */
- uint64 perm = pmap::PTE_V | pmap::PTE_U;
- if (phdr->p_flags & PF_R) perm |= pmap::PTE_R;
- if (phdr->p_flags & PF_W) perm |= pmap::PTE_W;
- if (phdr->p_flags & PF_X) perm |= pmap::PTE_X;
-
- /* Page-align the segment boundaries (apply load bias for PIE) */
- uint64 seg_start = page_align_down(phdr->p_vaddr + load_bias);
- uint64 seg_end = page_align_up(phdr->p_vaddr + phdr->p_memsz + load_bias);
-
- /* Create VMA for this segment */
- uint64 vm_perm = VM_USER;
- if (phdr->p_flags & PF_R) vm_perm |= VM_READ;
- if (phdr->p_flags & PF_W) vm_perm |= VM_WRITE;
- if (phdr->p_flags & PF_X) vm_perm |= VM_EXEC;
-
- int err = vmm_map_user(mm, seg_start, seg_end - seg_start, vm_perm);
- if (err) {
- printk(KERN_ERR "exec: vmm_map_user failed for segment %d: %d\n",
- i, err);
- return err;
- }
-
- /* Map and fill pages */
- for (uint64 va = seg_start; va < seg_end; va += kPageSize) {
- err = map_page_at(mm, va, perm);
- if (err) return err;
-
- /* Determine how much file data goes into this page */
- uint64 file_seg_start = phdr->p_vaddr + load_bias;
- uint64 file_seg_end = phdr->p_vaddr + phdr->p_filesz + load_bias;
-
- uint64 page_start = va;
- uint64 page_end = va + kPageSize;
-
- /* Overlap between [page_start, page_end) and [file_seg_start, file_seg_end) */
- if (page_start < file_seg_end && page_end > file_seg_start) {
- uint64 copy_start = (page_start > file_seg_start) ? page_start : file_seg_start;
- uint64 copy_end = (page_end < file_seg_end) ? page_end : file_seg_end;
- uint64 copy_len = copy_end - copy_start;
-
- /* File offset for this data */
- uint64 file_off = phdr->p_offset + (copy_start - phdr->p_vaddr - load_bias);
-
- /* Get kernel VA of the mapped page */
- uint64 pa;
- if (!pmap::lookup(mm->root_pa, va, &pa)) return -EFAULT;
- uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
-
- /* Read file data into the page */
- uint64 page_offset = copy_start - va;
- n = kernel_read(fp, kva + page_offset, copy_len,
- static_cast<int64>(file_off));
- if (n < static_cast<int64>(copy_len)) {
- printk(KERN_ERR "exec: short read at offset %llu\n", file_off);
- return -EIO;
- }
- }
- /* BSS region (memsz > filesz) is already zeroed by map_page_at */
- }
-
- /* Track highest loaded address for brk */
- if (seg_end > info->brk_start)
- info->brk_start = seg_end;
-
- /* Detect which PT_LOAD contains the program header table.
- * Linux: fs/binfmt_elf.c line ~1209 */
- if (info->phdr_addr == 0 &&
- phdr->p_offset <= ehdr.e_phoff &&
- ehdr.e_phoff < phdr->p_offset + phdr->p_filesz) {
- info->phdr_addr = ehdr.e_phoff - phdr->p_offset
- + phdr->p_vaddr + load_bias;
- }
- }
-
- /* phdr_addr should have been set by the PT_LOAD loop above
- * or by a PT_PHDR entry. If still zero, something is wrong. */
- if (info->phdr_addr == 0) {
- printk(KERN_ERR "exec: unable to determine AT_PHDR address\n");
- return -ENOEXEC;
- }
-
- return 0;
-}
-
-/* ================================================================== */
-/* setup_user_stack — build argc/argv/envp/auxv on the user stack */
-/* ================================================================== */
-
-static int setup_user_stack(VmSpace* mm, const char* const* argv,
- const char* const* envp,
- const elf_load_info* info,
- uint64* out_sp)
-{
- /* Create stack VMA */
- uint64 vm_perm = VM_USER | VM_READ | VM_WRITE;
- int err = vmm_map_user(mm, kUserStackBase, kUserStackSize, vm_perm);
- if (err) return err;
-
- /* We only need the top few pages for initial stack content.
- * Map 2 pages at the top for argc/argv/envp/auxv. */
- uint64 perm = pmap::PTE_V | pmap::PTE_U | pmap::PTE_R | pmap::PTE_W;
- for (uint64 va = kUserStackTop - 2 * kPageSize; va < kUserStackTop; va += kPageSize) {
- err = map_page_at(mm, va, perm);
- if (err) return err;
- }
-
- /* Count arguments and environment */
- int argc = 0;
- int envc = 0;
- if (argv) { while (argv[argc]) argc++; }
- if (envp) { while (envp[envc]) envc++; }
-
- /*
- * Build the stack from top down using kernel VA access.
- * Layout (top to bottom):
- * 1. String data (argv strings, envp strings)
- * 2. Padding (16-byte align)
- * 3. auxv[]
- * 4. NULL (end of envp)
- * 5. envp[] pointers
- * 6. NULL (end of argv)
- * 7. argv[] pointers
- * 8. argc
- */
-
- /* First pass: calculate total string space */
- uint64 str_total = 0;
- for (int i = 0; i < argc; i++)
- str_total += strlen_kernel(argv[i]) + 1;
- for (int i = 0; i < envc; i++)
- str_total += strlen_kernel(envp[i]) + 1;
-
- /* String area starts at top of stack */
- uint64 str_pos = kUserStackTop - str_total;
- str_pos &= ~15ULL; /* 16-byte align */
-
- /* Copy strings and record user addresses */
- uint64 argv_addrs[32]; /* Max 32 args */
- uint64 envp_addrs[32];
- if (argc > 32) argc = 32;
- if (envc > 32) envc = 32;
-
- uint64 cur = str_pos;
- for (int i = 0; i < argc; i++) {
- uint64 len = strlen_kernel(argv[i]) + 1;
- argv_addrs[i] = cur;
-
- /* Copy string bytes to user stack via kernel VA */
- uint64 pa;
- if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa))
- return -EFAULT;
- uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
- uint64 off = cur - page_align_down(cur);
- memcpy_kernel(kva + off, argv[i], len);
- cur += len;
- }
- for (int i = 0; i < envc; i++) {
- uint64 len = strlen_kernel(envp[i]) + 1;
- envp_addrs[i] = cur;
-
- uint64 pa;
- if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa))
- return -EFAULT;
- uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
- uint64 off = cur - page_align_down(cur);
- memcpy_kernel(kva + off, envp[i], len);
- cur += len;
- }
-
- /* Build auxv, envp[], argv[], argc below the strings */
- /* Count entries: argc + argv_ptrs + NULL + envp_ptrs + NULL + auxv (6 pairs + terminator) */
- int auxv_count = 8; /* AT_PHDR, AT_PHENT, AT_PHNUM, AT_PAGESZ, AT_BASE, AT_ENTRY, AT_RANDOM, AT_NULL */
- uint64 table_size = sizeof(uint64) * (1 + argc + 1 + envc + 1 + auxv_count * 2);
- uint64 table_start = str_pos - table_size;
- table_start &= ~15ULL;
-
- /* Write the table entries.
- * We need to handle page boundaries, but for simplicity we assume
- * the table fits in the already-mapped pages. */
- uint64 sp = table_start;
-
- /* Helper: write a uint64 to user stack */
- auto write_u64 = [&](uint64 addr, uint64 val) -> int {
- uint64 pa;
- if (!pmap::lookup(mm->root_pa, page_align_down(addr), &pa))
- return -EFAULT;
- uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
- uint64 off = addr - page_align_down(addr);
- *reinterpret_cast<uint64*>(kva + off) = val;
- return 0;
- };
-
- /* argc */
- err = write_u64(sp, static_cast<uint64>(argc));
- if (err) return err;
- sp += 8;
-
- /* argv[] */
- for (int i = 0; i < argc; i++) {
- err = write_u64(sp, argv_addrs[i]);
- if (err) return err;
- sp += 8;
- }
- err = write_u64(sp, 0); /* NULL terminator */
- if (err) return err;
- sp += 8;
-
- /* envp[] */
- for (int i = 0; i < envc; i++) {
- err = write_u64(sp, envp_addrs[i]);
- if (err) return err;
- sp += 8;
- }
- err = write_u64(sp, 0); /* NULL terminator */
- if (err) return err;
- sp += 8;
-
- /* auxv[] */
- auto write_auxv = [&](uint64 type, uint64 val) -> int {
- int r = write_u64(sp, type);
- if (r) return r;
- sp += 8;
- r = write_u64(sp, val);
- if (r) return r;
- sp += 8;
- return 0;
- };
-
- write_auxv(AT_PHDR, info->phdr_addr);
- write_auxv(AT_PHENT, info->phentsize);
- write_auxv(AT_PHNUM, info->phnum);
- write_auxv(AT_PAGESZ, kPageSize);
- write_auxv(AT_BASE, info->load_bias);
- write_auxv(AT_ENTRY, info->entry);
- write_auxv(AT_RANDOM, str_pos - 16); /* Point to 16 bytes of zeros for now */
- write_auxv(AT_NULL, 0);
-
- *out_sp = table_start;
- return 0;
-}
-
-/* ================================================================== */
-/* do_execve — main entry point */
-/* ================================================================== */
-
-int do_execve(const char* pathname, const char* const* argv,
- const char* const* envp)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) return -EINVAL;
-
- /* 1. Look up the file */
- path p{};
- int err = filename_lookup(AT_FDCWD, pathname, 0, &p);
- if (err) {
- printk(KERN_ERR "exec: file not found: %s (err=%d)\n", pathname, err);
- return err;
- }
-
- inode* ip = p.de->d_inode;
- if (!ip || !S_ISREG(ip->i_mode)) {
- printk(KERN_ERR "exec: not a regular file: %s\n", pathname);
- return -EACCES;
- }
-
- /* Open a kernel file handle */
- file* fp = alloc_empty_file(O_RDONLY);
- if (!fp) {
- return -ENOMEM;
- }
- fp->f_inode = ip;
- fp->f_path = p;
- fp->f_op = ip->i_fop;
- fp->f_pos = 0;
-
- if (fp->f_op && fp->f_op->open) {
- err = fp->f_op->open(ip, fp);
- if (err) {
- fput(fp);
- return err;
- }
- }
-
- /* 2. Create new address space */
- VmSpace* new_mm = VmSpace::create();
- if (!new_mm) {
- fput(fp);
- return -ENOMEM;
- }
-
- /* 3. Load ELF segments */
- elf_load_info info{};
- err = load_elf_binary(fp, new_mm, &info);
- if (err) {
- printk(KERN_ERR "exec: load_elf_binary failed: %d\n", err);
- new_mm->put();
- fput(fp);
- dput(p.de);
- return err;
- }
-
- /* 4. Set up user stack */
- uint64 user_sp = 0;
- err = setup_user_stack(new_mm, argv, envp, &info, &user_sp);
- if (err) {
- printk(KERN_ERR "exec: setup_user_stack failed: %d\n", err);
- new_mm->put();
- fput(fp);
- return err;
- }
-
- /* Set brk */
- new_mm->brk_start = page_align_up(info.brk_start);
- new_mm->brk_end = new_mm->brk_start;
-
- /* Set mmap base — Linux: arch_pick_mmap_layout() sets mm->mmap_base
- * to TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3).
- * This ensures mmap(NULL) returns addresses well above the ELF
- * text/data segments. */
- new_mm->mmap_base = page_align_up(kUserVaLimit / 3);
-
- fput(fp);
-
- /* =============================================================== */
- /* Point of no return — replace the current process */
- /* =============================================================== */
-
- /* 5a. Replace address space */
- VmSpace* old_mm = curr->mm;
- curr->mm = new_mm;
- pmap::activate(new_mm->root_pa);
- if (old_mm) old_mm->put();
-
- /* 5a'. Transition from kernel thread to user process.
- * Linux: begin_new_exec() in fs/exec.c:
- * current->flags &= ~PF_KTHREAD; (line 1173)
- * tsk->exit_signal = SIGCHLD; (line 1021)
- * Without this, a kernel_execve'd task stays marked as a kthread
- * and would be auto-reaped (exit_signal=-1) instead of becoming
- * a proper zombie for parent wait(). */
- curr->flags &= ~TF_KTHREAD;
- curr->exit_signal = SIGCHLD;
-
- /* 5b. Reset signal handlers to SIG_DFL (POSIX requirement).
- *
- * Linux fs/exec.c:flush_signal_handlers() runs under sighand->siglock.
- * Even though after fork our sighand is no longer shared with the
- * parent (we copy in task_clone), Linux still locks here — and so
- * must we, because the same sighand may be shared with CLONE_SIGHAND
- * sibling threads of the execing task. Holding siglock here also
- * keeps the per-task blocked mask reset atomic w.r.t. concurrent
- * sigprocmask / signal delivery on this very task. */
- if (curr->sighand) {
- LockGuard guard(curr->sighand->siglock);
- for (int i = 0; i < _NSIG; i++) {
- if (curr->sighand->action[i].sa_handler != SIG_IGN)
- curr->sighand->action[i].sa_handler = SIG_DFL;
- curr->sighand->action[i].sa_flags = 0;
- curr->sighand->action[i].sa_mask.clear();
- }
- curr->blocked.clear();
- } else {
- curr->blocked.clear();
- }
-
- /* 5c. Close O_CLOEXEC file descriptors */
- /* TODO: implement close-on-exec */
-
- /* 6. Set up trapframe to enter user space */
- arch::TrapFrame* tf = curr->trapframe;
- if (!tf) {
- printk(KERN_ERR "exec: no trapframe!\n");
- task_kill_current(SIGKILL);
- return -EINVAL; /* not reached */
- }
-
- /* Initialize trapframe for user-mode return.
- * This is the MI equivalent of Linux's start_thread() + kernel
- * metadata setup. All arch-specific details (sstatus, FP state,
- * kernel_satp, kernel_sp, trap handler address, etc.) are handled
- * by the MD layer. MI exec never touches arch registers. */
- arch::trapframe_init_user(tf, info.entry, user_sp);
-
- pr_debug_s(KLOG_SUBSYS_EXEC,
- "exec: pid %d entry=%llx sp=%llx phdr=%llx\n",
- curr->pid, info.entry, user_sp, info.phdr_addr);
-
- /* Verify loaded data at entry point — debug-level, ring buffer only. */
- {
- uint64 pa;
- if (pmap::lookup(curr->mm->root_pa, page_align_down(info.entry), &pa)) {
- uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
- uint64 off = info.entry - page_align_down(info.entry);
- pr_debug_s(KLOG_SUBSYS_EXEC,
- "exec: entry bytes @%llx: %02x %02x %02x %02x %02x %02x %02x %02x\n",
- info.entry,
- kva[off+0], kva[off+1], kva[off+2], kva[off+3],
- kva[off+4], kva[off+5], kva[off+6], kva[off+7]);
- }
- for (uint64 va = 0x10000; va < 0x14000; va += kPageSize) {
- if (pmap::lookup(curr->mm->root_pa, va, &pa)) {
- uint8* kva2 = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
- pr_debug_s(KLOG_SUBSYS_EXEC,
- "exec: page %llx: %02x%02x%02x%02x %02x%02x%02x%02x\n",
- va, kva2[0], kva2[1], kva2[2], kva2[3],
- kva2[4], kva2[5], kva2[6], kva2[7]);
- } else {
- pr_debug_s(KLOG_SUBSYS_EXEC,
- "exec: page %llx: NOT MAPPED\n", va);
- }
- }
- }
-
- /* Flush instruction cache after loading executable code into memory.
- * Without this, the I-cache may contain stale data for the pages
- * we just loaded, causing the CPU to execute garbage. */
- arch::mmu::flush_icache();
-
- return 0; /* Will return to user mode via trap return */
-}
-
-/* ================================================================== */
-/* sys_execve_wrapper — syscall entry point */
-/* ================================================================== */
-
-int64 sys_execve_wrapper(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- const char* pathname = reinterpret_cast<const char*>(a0);
- const char* const* argv = reinterpret_cast<const char* const*>(a1);
- const char* const* envp = reinterpret_cast<const char* const*>(a2);
-
- return do_execve(pathname, argv, envp);
-}
-
-/* ================================================================== */
-/* kernel_execve — call execve from kernel context (e.g. init) */
-/* ================================================================== */
-
-int kernel_execve(const char* pathname, const char* const* argv,
- const char* const* envp)
-{
- TaskControlBlock* curr = current_task();
-
- int ret = do_execve(pathname, argv, envp);
- if (ret != 0)
- return ret;
-
- /*
- * do_execve succeeded: the trapframe is set up with entry point,
- * user stack, and sstatus (SPP=0 for U-mode). We must NOT return
- * normally — that would go back through the kthread return path
- * and jump to address 0. Instead, jump directly to user mode via
- * the same userret() path that fork_child_entry uses.
- *
- * Switch stvec from kernelvec to trap_vector so that traps from
- * U-mode are handled by the user trap entry path (which loads
- * kernel context from the trapframe via sscratch).
- */
- arch::trap::set_vector(reinterpret_cast<uint64>(trap_vector));
- userret(curr->trapframe);
- __builtin_unreachable();
-}
diff --git a/na/kernel/irq.cc b/na/kernel/irq.cc
deleted file mode 100644
index 4de468a..0000000
--- a/na/kernel/irq.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * IRQ Manager Implementation
- *
- * Lock-free dispatch path for interrupt context safety.
- * Registration/unregistration is protected by atomic publish.
- *
- * Reference: docs/specs/trap.md §3.3
- */
-
-#include <lume/console.h>
-#include <lume/errno.h>
-#include <lume/irq.h>
-
-/* Global singleton */
-IrqManager g_irq_manager;
-
-void IrqManager::init() {
- for (uint32 i = 0; i < kMaxIrqs; i++) {
- entries_[i].handler_ptr.store(0, __ATOMIC_RELAXED);
- entries_[i].arg = nullptr;
- }
-}
-
-int IrqManager::register_handler(uint32 irq, IrqHandler handler, void *arg) {
- if (irq >= kMaxIrqs)
- return -EINVAL;
-
- if (!handler)
- return -EINVAL;
-
- /* Check if already registered (CAS from 0 to handler) */
- uint64 expected = 0;
- uint64 desired = reinterpret_cast<uint64>(handler);
-
- /* Set arg first (before publishing handler) so dispatch sees valid arg */
- entries_[irq].arg = arg;
-
- if (!entries_[irq].handler_ptr.compare_exchange(expected, desired,
- __ATOMIC_ACQ_REL)) {
- /* Already registered by someone else */
- return -EBUSY;
- }
-
- return 0;
-}
-
-void IrqManager::unregister_handler(uint32 irq) {
- if (irq >= kMaxIrqs)
- return;
-
- /* Clear handler with release semantics */
- entries_[irq].handler_ptr.store(0, __ATOMIC_RELEASE);
- entries_[irq].arg = nullptr;
-}
-
-void IrqManager::dispatch(uint32 irq) {
- if (irq >= kMaxIrqs)
- return;
-
- /* Load handler with acquire semantics — pairs with register's release */
- uint64 h = entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE);
- if (h == 0) {
- /* Spurious or unregistered IRQ — silently ignore */
- return;
- }
-
- auto handler = reinterpret_cast<IrqHandler>(h);
- handler(irq, entries_[irq].arg);
-}
-
-bool IrqManager::has_handler(uint32 irq) const {
- if (irq >= kMaxIrqs)
- return false;
- return entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE) != 0;
-}
-
-void IrqManager::set_chip(const IrqChip* chip) {
- chip_ = chip;
-}
-
-uint32 IrqManager::claim() const {
- return chip_ ? chip_->claim() : 0;
-}
-
-void IrqManager::complete(uint32 irq) const {
- if (chip_) chip_->complete(irq);
-}
-
-void IrqManager::init_ap() const {
- if (chip_ && chip_->init_ap) chip_->init_ap();
-}
-
-void IrqManager::enable(uint32 irq) const {
- if (chip_ && chip_->enable) chip_->enable(irq);
-}
-
-void IrqManager::disable(uint32 irq) const {
- if (chip_ && chip_->disable) chip_->disable(irq);
-}
-
-void IrqManager::set_priority(uint32 irq, uint32 priority) const {
- if (chip_ && chip_->set_priority) chip_->set_priority(irq, priority);
-}
diff --git a/na/kernel/main.cc b/na/kernel/main.cc
deleted file mode 100644
index db5843c..0000000
--- a/na/kernel/main.cc
+++ /dev/null
@@ -1,468 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * LumeOS Kernel Main Entry — Global Initialization Sequence
- *
- * All CPUs enter kernel_main() after the assembly bootstrap (entry.S)
- * has enabled MMU, cleared BSS, and called early_cpp_init().
- *
- * BSP (cpu_id == 0) executes the full subsystem initialization DAG.
- * APs spin on ap_boot_sync, then perform per-CPU init only.
- *
- * Boot DAG invariants (docs/specs/linux_abi_mm_plan.md §Boot Sequence):
- * - trap_init() MUST precede any allocator (exception safety net)
- * - sched_init() MUST precede timer_init() (software before hardware)
- * - PID 1 (init) is spawned before entering idle loop
- *
- * Reference: docs/specs/global_init.md
- */
-
-#include <arch/config.h>
-#include <arch/cpu.h>
-#include <arch/extable.h>
-#include <arch/sbi.h>
-#include <lume/addr.h>
-#include <lume/atomic.h>
-#include <lume/console.h>
-#include <lume/driver.h>
-#include <lume/fdt.h>
-#include <lume/fdtable.h>
-#include <lume/fs_struct.h>
-#include <lume/fs_types.h>
-#include <lume/irq.h>
-#include <lume/kernel_map.h>
-#include <lume/klog.h>
-#include <lume/kprintf.h>
-#include <lume/memblock.h>
-#include <lume/mount.h>
-#include <lume/page_cache.h>
-#include <lume/pmm.h>
-#include <lume/random.h>
-#include <lume/sched.h>
-#include <lume/smp.h>
-#include <lume/task.h>
-#include <lume/pid.h>
-#include <lume/pgrp.h>
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/trap.h>
-#include <lume/types.h>
-
-// Declared by entry.S: the hart ID that won BSP election
-extern "C" uint32 bsp_hart_id;
-
-// Linker symbol: kernel entry point (physical start address)
-extern "C" char _start[];
-
-// ============================================================
-// Forward declarations for subsystem init functions.
-// Each is implemented by its owning module. On failure they
-// call kernel_panic() directly — no error return.
-// ============================================================
-
-// BSP global init (strict order)
-void console_init(); // 1. UART ready; kprintf/panic can output
-void fdt_init(uint64 fdt_paddr); // 2. Parse FDT, build device node table
-void pmm_init(); // 3. Buddy allocator ready
-void slab_init(); // 4. kmem_cache ready; operator new usable
-void vmm_init(); // 5. Fine-grained kernel page table; retire early_pgdir
-void task_init(); // 6. PID allocator + global task list
-void pgrp_init(); // 6.5. Process group hash table
-void sched_init(); // 7. Create BSP Idle Task, init ready queues
-void timer_init(); // 8. Configure timer interrupt (scheduler tick)
-void syscall_init(); // 9. Register syscall jump table
-void super_init(); // 10. VFS superblock registry
-void ramfs_init(); // 11. RamFS filesystem registration
-void vfs_init(); // 12. Mount root filesystem
-void cdev_init(); // 13. Character device registry
-void console_cdev_init(); // 14. Register console/null/zero cdevs
-void devfs_init(); // 15. Create /dev and populate nodes
-void tty_init(); // 15.5. TTY subsystem (console TTY + UART RX IRQ)
-void populate_rootfs(); // 15.6. Unpack embedded initramfs CPIO into ramfs
-
-// Boot self-test runner
-void selftest_run_all();
-
-// AP per-CPU init
-void vmm_init_ap(); // Switch to BSP-built kernel page table
-void timer_init_ap(); // Start this CPU's timer interrupt
-void sched_init_ap(); // Init per-CPU ready queue, create Idle Task
-
-// kthread_create from task.cc
-extern TaskControlBlock *kthread_create(const char *name, void (*entry)(void *),
- void *arg, uint8 priority);
-
-// ============================================================
-// PID 1 — init thread
-// ============================================================
-static void init_thread_func(void *) {
- printk(KERN_INFO "[init] PID 1 started\n");
-
- /* Run post-SMP tests — APs are online by now */
- extern void selftest_smp();
- kprintf("\n========================================\n");
- kprintf(" Post-SMP Test Suite\n");
- kprintf("========================================\n");
- selftest_smp();
- kprintf("========================================\n");
- kprintf(" Post-SMP tests PASSED\n");
- kprintf("========================================\n\n");
-
- /*
- * Set up init's own VFS context (like Linux kernel_init).
- * kthread_create does NOT inherit files/fs, so init creates its own.
- */
- TaskControlBlock *self = current_task();
-
- if (!self->files) {
- self->files = alloc_files_struct();
- if (!self->files) {
- printk(KERN_ERR "[init] failed to allocate files_struct\n");
- goto out;
- }
- }
- if (!self->fs) {
- self->fs = alloc_fs_struct();
- if (!self->fs) {
- printk(KERN_ERR "[init] failed to allocate fs_struct\n");
- goto out;
- }
- /* Point to the global root filesystem */
- self->fs->root.de = g_root_dentry;
- self->fs->root.mnt = g_root_mnt;
- self->fs->pwd = self->fs->root;
- }
-
- /* Open /dev/console → fd 0 (stdin), dup to fd 1, 2 */
- {
- extern int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64);
- extern int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64);
-
- int64 fd0 =
- sys_openat(static_cast<uint64>(-100), /* AT_FDCWD */
- reinterpret_cast<uint64>("/dev/console"), O_RDWR, 0, 0, 0);
- if (fd0 < 0) {
- printk(KERN_ERR "[init] open /dev/console failed: %d\n",
- static_cast<int>(fd0));
- } else {
- sys_dup(static_cast<uint64>(fd0), 0, 0, 0, 0, 0); /* fd 1 */
- sys_dup(static_cast<uint64>(fd0), 0, 0, 0, 0, 0); /* fd 2 */
- printk(KERN_INFO "[init] fd 0/1/2 -> /dev/console\n");
- }
- }
-
- /*
- * Try to exec /sbin/init (like Linux kernel_init).
- * If not found, fall through to idle.
- */
- {
- extern int kernel_execve(const char *, const char *const *,
- const char *const *);
-
- const char *init_paths[] = {"/sbin/init", "/etc/init", "/bin/init",
- "/bin/sh", nullptr};
- const char *argv[] = {"/sbin/init", nullptr};
- const char *envp[] = {"HOME=/", "PATH=/sbin:/bin", nullptr};
-
- for (int i = 0; init_paths[i]; i++) {
- argv[0] = init_paths[i];
- int ret = kernel_execve(init_paths[i], argv, envp);
- if (ret == 0) {
- /* Success — do_execve modified our trapframe.
- * When this kthread returns through the trap return path,
- * it will jump to user space. */
- return;
- }
- printk(KERN_INFO "[init] exec %s failed: %d\n", init_paths[i], ret);
- }
-
- printk(KERN_ERR "[init] No init found! Kernel panic.\n");
- }
-
-out:
- extern void task_exit(int code);
- task_exit(0);
-}
-
-// ============================================================
-// Second-stage synchronization barrier (C++ layer)
-// AP cores spin here until BSP finishes all global init.
-// ============================================================
-static lume::atomic<uint32> ap_boot_sync;
-
-// ============================================================
-// kernel_main — C++ entry point for all CPUs
-// ============================================================
-extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) {
-
- if (cpu_id == bsp_hart_id) {
- // ========================================================
- // BSP: Absolute single-core period — no locks needed.
- // APs are spinning on ap_boot_sync.
- // ========================================================
-
- uint64 fdt_pa = fdt_paddr;
-
- // ---- Phase 1: Console + Exception Safety Net ----
-
- // 1. Early console setup: discover UART via FDT before ANY output.
- console_early_init(fdt_pa);
- klog_init();
-
- printk("\n");
- printk(KERN_INFO "LumeOS Kernel v0.1\n");
- printk(KERN_INFO "boot: BSP online, cpu_id=%llu\n", cpu_id);
- printk(KERN_INFO "boot: higher-half kernel active\n");
-
- /* kernel_map filled by arch/riscv/mm/init.cc::setup_vm before MMU
- * enable; relocate_kernel has already applied .rela.dyn. */
- printk(KERN_INFO "boot: kimage phys=0x%llx virt=0x%llx size=%llu\n",
- static_cast<unsigned long long>(kernel_map.phys_addr),
- static_cast<unsigned long long>(kernel_map.virt_addr),
- static_cast<unsigned long long>(kernel_map.size));
-
- // --- Subsystem init DAG (strict top-down order) ---
-
- // 2. Trap handler — install exception vector FIRST.
- // Any fault from here on gets a proper panic backtrace
- // instead of a silent hang. (Linux: trap_init is in start_kernel
- // right after setup_arch.)
- trap_init();
- printk(KERN_INFO "init: trap_init done\n");
-
- // 3. FDT parsing — make device tree available
- fdt_init(fdt_pa);
- printk(KERN_INFO "init: fdt_init done\n");
-
- // 4. KASLR — seed PRNG and randomise VA layout.
- // MUST happen before vmm_init() builds the kernel page table.
- {
- uint64 kaslr_seed = 0;
- fdt_early_get_kaslr_seed(&kaslr_seed);
- random_early_init(kaslr_seed);
- arch::kaslr_init();
- }
-
- // Sort exception table before setting RO permissions in vmm_init()
- extable_init();
-
- // ---- Phase 2: Memory Subsystem (strict cascade) ----
-
- // 5. Memblock allocator (Early boot PA allocator)
- memblock_init(fdt_pa);
- printk(KERN_INFO "init: memblock_init done\n");
-
- // 6. Virtual memory manager — builds page table with KASLR'd bases
- vmm_init();
- printk(KERN_INFO "init: vmm_init done\n");
-
- // 7. Physical memory manager (Buddy)
- pmm_init();
- printk(KERN_INFO "init: pmm_init done\n");
-
- // 8. Slab allocator — operator new usable after this
- slab_init();
- printk(KERN_INFO "init: slab_init done\n");
-
- // 9. Page cache (minimal hash table, stable interface for file-backed mmap)
- page_cache_init();
-
- // 10. FDT Phase 2 — build DeviceNode object tree (needs Slab)
- fdt_unflatten();
- printk(KERN_INFO "init: fdt_unflatten done\n");
-
- // ---- Phase 3: Task & Scheduler (software state before hardware) ----
-
- // 11. Task management (PID allocator + global task list)
- task_init();
- printk(KERN_INFO "init: task_init done\n");
-
- pgrp_init();
-
- sched_init();
- printk(KERN_INFO "init: sched_init done\n");
-
- // ---- Phase 3.5: VFS (needs slab + sched) ----
- super_init();
- ramfs_init();
- vfs_init();
- printk(KERN_INFO "init: vfs_init done (ramfs root mounted)\n");
-
- // ---- Phase 3.55: Unpack initramfs into root ----
- populate_rootfs();
-
- // ---- Phase 3.6: Device nodes ----
- cdev_init();
- console_cdev_init();
- devfs_init();
- printk(KERN_INFO "init: devfs done\n");
-
- // ---- Phase 4: Hardware Drivers & Interrupts ----
-
- // 13. Hardware Drivers Discovery (Interrupt Controllers, etc)
- driver_probe_all();
- printk(KERN_INFO "init: driver_probe_all done\n");
-
- // 13.5. TTY subsystem — needs PLIC (driver_probe_all) for UART RX IRQ
- tty_init();
- printk(KERN_INFO "init: tty_init done\n");
-
- // 14. Timer interrupt (scheduler tick source)
- // Safe now: scheduler is fully initialized.
- timer_init();
- printk(KERN_INFO "init: timer_init done\n");
-
- // 15. Syscall jump table
- syscall_init();
- printk(KERN_INFO "init: syscall_init done\n");
-
- // 16. SMP / IPI infrastructure
- smp_init();
- arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI
- arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART)
-
- // ---- Phase 5: Self-test & Launch ----
-
- // 17. Boot self-tests — verify subsystem correctness.
- // Tests that create kthreads must wait() for them (freeing PIDs).
- selftest_run_all();
-
- // 18. Spawn PID 1 (init) — the first persistent task.
- // Like Linux's rest_init() → user_mode_thread(kernel_init).
- // All test PIDs have been freed, so PID 1 is available.
- TaskControlBlock *init_task =
- kthread_create("init", init_thread_func, nullptr, 0);
- if (init_task) {
- init_task->flags |= TF_INIT;
-
- /* Init is session and process-group leader: sid = pgid = pid.
- * Allocate signal_struct AND sighand_struct eagerly so every
- * descendant (created via task_clone) inherits a real identity
- * and a real lock object — signal sending must take siglock and
- * cannot tolerate a null sighand. */
- init_task->signal = alloc_signal_struct(init_task->pid, init_task);
- if (!init_task->signal) {
- printk(KERN_ERR "boot: FAILED to allocate init signal_struct\n");
- }
- init_task->sighand = alloc_sighand();
- if (!init_task->sighand) {
- printk(KERN_ERR "boot: FAILED to allocate init sighand\n");
- }
-
- /* Publish g_init_task so reparenting paths can find the foster
- * parent when other tasks exit before init takes over user space. */
- extern TaskControlBlock* g_init_task;
- g_init_task = init_task;
-
- printk(KERN_INFO "boot: init thread created (pid=%d sid=%d pgid=%d)\n",
- init_task->pid,
- init_task->signal ? init_task->signal->sid : -1,
- init_task->signal ? init_task->signal->pgid : -1);
- } else {
- printk(KERN_ERR "boot: FAILED to create init thread!\n");
- }
-
-
- BOOT_STAGE(5, "core kernel init");
- printk(KERN_INFO "boot: BSP init complete, releasing APs\n");
-
- // Release APs with RELEASE semantics:
- // guarantees all global data is visible to APs.
- ap_boot_sync.store(1, __ATOMIC_RELEASE);
-
- // Start AP harts via SBI HSM extension.
- // OpenSBI v1.0+ parks non-boot harts in STOPPED state.
- // They must be explicitly started with sbi_hart_start().
- //
- // kernel_map.phys_addr was filled by setup_vm in arch/riscv/mm/init.cc
- // before MMU enable — it's the runtime PA at which the kernel was
- // loaded. APs start at the same PA as the BSP.
- {
- uint64 start_pa = kernel_map.phys_addr;
- if (start_pa == 0) {
- /* Defensive fallback if setup_vm hasn't filled the field. */
- start_pa =
- boot_va_to_pa(virt_addr(reinterpret_cast<uint64>(_start))).raw;
- }
- uint32 num_harts = 4; // TODO: read from FDT /cpus
- for (uint32 h = 0; h < num_harts; h++) {
- if (h == static_cast<uint32>(cpu_id))
- continue; // skip BSP
- auto ret = arch::sbi::sbi_hart_start(h, start_pa, fdt_paddr);
- if (ret.error == arch::sbi::SBI_SUCCESS) {
- printk(KERN_INFO "smp: started hart %u at PA 0x%llx\n", h, start_pa);
- } else {
- printk(KERN_ERR "smp: failed to start hart %u (err=%ld)\n", h,
- ret.error);
- }
- }
- }
-
- // BSP enables interrupts — first timer tick will preempt
- // into the scheduler loop, which will pick up PID 1.
- arch::cpu::intr_on();
-
- printk(KERN_INFO "boot: BSP entering idle loop\n");
-
- } else {
- // ========================================================
- // AP: spin until BSP finishes global init
- // ========================================================
- // DEBUG (commented out — uncomment for SMP debugging):
- // arch::sbi::sbi_ecall(1, 0, '1'); // AP entered kernel_main
-
- while (ap_boot_sync.load(__ATOMIC_ACQUIRE) == 0)
- ;
-
- // arch::sbi::sbi_ecall(1, 0, '2'); // AP passed ap_boot_sync
-
- // --- Per-CPU init (must happen before any printk!) ---
- // early_pgdir doesn't map UART MMIO, so we MUST switch to
- // BSP's kernel page table before any console output.
- vmm_init_ap(); // Switch to BSP-built kernel page table
-
- // arch::sbi::sbi_ecall(1, 0, '3'); // vmm_init_ap done
-
- trap_init_ap(); // Install stvec on this CPU
-
- // arch::sbi::sbi_ecall(1, 0, '4'); // trap_init_ap done
-
- printk(KERN_INFO "boot: AP online, cpu_id=%llu\n", cpu_id);
-
- sched_init_ap(); // Init per-CPU RunQueue, create Idle Task
-
- // arch::sbi::sbi_ecall(1, 0, '5'); // sched_init_ap done
-
- timer_init_ap(); // Start this CPU's timer interrupt
- arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI
- arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART)
-
- // AP is now a fully functional scheduler CPU.
- // Enable interrupts — first timer tick preempts into idle loop.
- arch::cpu::intr_on();
-
- printk(KERN_INFO "boot: AP cpu_id=%llu fully initialized\n", cpu_id);
- }
-
- // All CPUs reach here.
- // The "current task" is the boot stub (bsp_task or ap_task, pid=-1).
- // We must hand off to the scheduler's idle task by calling schedule().
- // Without this, the BSP runs as bsp_task (O1 class, timeslice=20 ticks)
- // instead of the idle task (which yields immediately when any task is
- // runnable). That delays PID 1 by up to 200ms and can stall entirely
- // if no timer tick fires before the first WFI.
- {
- TaskControlBlock *self = current_task();
- if (self) {
- self->state = TaskState::Dead; // Boot stub is done; don't re-enqueue
- }
- schedule(); // Switch to idle task (or init if it's already runnable)
- }
-
- // Should not reach here — schedule() switches away permanently.
- // But just in case, spin with WFI as a safety net.
- while (true) {
- arch::cpu::halt_until_interrupt();
- }
-}
diff --git a/na/kernel/module.mk b/na/kernel/module.mk
deleted file mode 100644
index 754a14c..0000000
--- a/na/kernel/module.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-# kernel/module.mk
-SRCS_CC += kernel/main.cc
-SRCS_CC += kernel/sync/spinlock.cc
-SRCS_CC += kernel/sync/mutex.cc
-SRCS_CC += kernel/trap.cc
-SRCS_CC += kernel/irq.cc
-SRCS_CC += kernel/sched_core.cc
-SRCS_CC += kernel/sched_class_o1.cc
-SRCS_CC += kernel/timer.cc
-SRCS_CC += kernel/pid.cc
-SRCS_CC += kernel/pgrp.cc
-SRCS_CC += kernel/task.cc
-SRCS_CC += kernel/syscall.cc
-SRCS_CC += kernel/sys_process.cc
-SRCS_CC += kernel/sys_mm.cc
-SRCS_CC += kernel/sys_info.cc
-SRCS_CC += kernel/sys_fs.cc
-SRCS_CC += kernel/uaccess.cc
-SRCS_CC += kernel/waitqueue.cc
-SRCS_CC += kernel/signal.cc
-SRCS_CC += kernel/sys_signal.cc
-SRCS_CC += kernel/sys_misc.cc
-SRCS_CC += kernel/exec.cc
-SRCS_CC += kernel/poll.cc
-SRCS_CC += kernel/ptrace.cc
-SRCS_CC += kernel/smp.cc
diff --git a/na/kernel/pgrp.cc b/na/kernel/pgrp.cc
deleted file mode 100644
index e5b382c..0000000
--- a/na/kernel/pgrp.cc
+++ /dev/null
@@ -1,148 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Process Group / Session Implementation
- *
- * Hash table of signal_struct objects keyed by pgid. Lifecycle of
- * signal_struct mirrors Linux: one per thread group, refcounted across
- * CLONE_THREAD siblings, freed when the last task in the group exits.
- *
- * Reference: Linux kernel/pid.c, kernel/exit.c
- */
-
-#include <lume/pgrp.h>
-#include <lume/signal_types.h>
-#include <lume/task.h>
-#include <lume/new.h>
-#include <lume/klog.h>
-
-/* ================================================================== */
-/* Hash table state */
-/* ================================================================== */
-
-list_node g_pgrp_hash[kPgrpHashSize];
-Spinlock g_pgrp_lock;
-
-void pgrp_init()
-{
- for (auto& bucket : g_pgrp_hash) bucket.init();
- g_pgrp_lock.init("pgrp");
-}
-
-/* ================================================================== */
-/* Internal helpers (must be called with g_pgrp_lock held) */
-/* ================================================================== */
-
-static void pgrp_attach_locked(signal_struct* sig, int32 pgid)
-{
- sig->pgid = pgid;
- list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(pgid)];
- sig->pgrp_link.prev = bucket->prev;
- sig->pgrp_link.next = bucket;
- bucket->prev->next = &sig->pgrp_link;
- bucket->prev = &sig->pgrp_link;
-}
-
-static void pgrp_detach_locked(signal_struct* sig)
-{
- /* Unlink only if currently on a list — init() makes prev/next point
- * to self, which we treat as "not linked". */
- if (sig->pgrp_link.next == &sig->pgrp_link) return;
- sig->pgrp_link.prev->next = sig->pgrp_link.next;
- sig->pgrp_link.next->prev = sig->pgrp_link.prev;
- sig->pgrp_link.init();
-}
-
-/* ================================================================== */
-/* signal_struct allocation / refcount */
-/* ================================================================== */
-
-signal_struct* alloc_signal_struct(int32 leader_pid, TaskControlBlock* leader)
-{
- auto* sig = new signal_struct;
- if (!sig) return nullptr;
- sig->init(leader_pid, leader);
-
- LockGuard guard(g_pgrp_lock);
- pgrp_attach_locked(sig, leader_pid);
- return sig;
-}
-
-signal_struct* fork_signal_struct(signal_struct* parent,
- TaskControlBlock* child_leader)
-{
- if (!parent) return nullptr;
-
- auto* sig = new signal_struct;
- if (!sig) return nullptr;
-
- /* Copy identity from parent, then override leader and attach. */
- sig->lock.init("signal");
- sig->refcount = 1;
- sig->leader = child_leader;
- sig->tty_old_pgrp = 0;
- sig->pgrp_link.init();
-
- {
- LockGuard pg(parent->lock);
- sig->pgid = parent->pgid;
- sig->sid = parent->sid;
- sig->tty = parent->tty;
- }
-
- LockGuard guard(g_pgrp_lock);
- pgrp_attach_locked(sig, sig->pgid);
- return sig;
-}
-
-void get_signal_struct(signal_struct* sig)
-{
- if (!sig) return;
- LockGuard guard(sig->lock);
- sig->refcount++;
-}
-
-void put_signal_struct(signal_struct* sig)
-{
- if (!sig) return;
-
- uint32 remaining;
- {
- LockGuard guard(sig->lock);
- remaining = --sig->refcount;
- }
- if (remaining != 0) return;
-
- /* Last reference — detach from pgrp hash and free. */
- {
- LockGuard guard(g_pgrp_lock);
- pgrp_detach_locked(sig);
- }
- delete sig;
-}
-
-/* ================================================================== */
-/* Pgrp membership change */
-/* ================================================================== */
-
-void change_pgid(signal_struct* sig, int32 new_pgid)
-{
- if (!sig) return;
- LockGuard guard(g_pgrp_lock);
- if (sig->pgid == new_pgid) return;
- pgrp_detach_locked(sig);
- pgrp_attach_locked(sig, new_pgid);
-}
-
-int pgrp_member_count(int32 pgid)
-{
- LockGuard guard(g_pgrp_lock);
- int n = 0;
- list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(pgid)];
- for (list_node* node = bucket->next; node != bucket; node = node->next) {
- auto* sig = list_entry<signal_struct, &signal_struct::pgrp_link>(node);
- if (sig->pgid == pgid) n++;
- }
- return n;
-}
diff --git a/na/kernel/pid.cc b/na/kernel/pid.cc
deleted file mode 100644
index 04b846b..0000000
--- a/na/kernel/pid.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * PID Allocator — Bitmap-based O(1) block search
- * Reference: docs/specs/task.md §3.2
- */
-
-#include <lume/pid.h>
-
-void PidAllocator::init()
-{
- for (int i = 0; i < kBitmapWords; i++)
- bitmap_[i] = 0;
- next_hint_ = 1;
- bitmap_[0] |= 0x1ULL; /* Reserve PID 0 (idle/swapper) only.
- * PID 1 (init) is allocated normally — the first
- * kthread_create() call gets it, matching Linux. */
-}
-
-int32 PidAllocator::alloc()
-{
- LockGuard guard(lock_);
-
- int32 word_start = next_hint_ / 64;
-
- for (int32 passes = 0; passes < kBitmapWords; passes++) {
- int32 w = (word_start + passes) % kBitmapWords;
- uint64 free_bits = ~bitmap_[w];
- if (free_bits == 0)
- continue;
-
- int bit = 0;
- uint64 v = free_bits;
- while ((v & 1ULL) == 0) { v >>= 1; bit++; }
- int32 pid = w * 64 + bit;
-
- if (pid >= kMaxPid)
- continue;
-
- bitmap_[w] |= (1ULL << bit);
- next_hint_ = pid + 1;
- if (next_hint_ >= kMaxPid)
- next_hint_ = 2;
-
- return pid;
- }
-
- return -1;
-}
-
-void PidAllocator::free(int32 pid)
-{
- if (pid < 0 || pid >= kMaxPid)
- return;
-
- LockGuard guard(lock_);
- int32 w = pid / 64;
- int32 bit = pid % 64;
- bitmap_[w] &= ~(1ULL << bit);
-}
-
-PidAllocator g_pid_alloc;
diff --git a/na/kernel/poll.cc b/na/kernel/poll.cc
deleted file mode 100644
index 3674491..0000000
--- a/na/kernel/poll.cc
+++ /dev/null
@@ -1,250 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * sys_ppoll — POSIX ppoll() implementation.
- *
- * Linux fs/select.c:do_sys_poll() pattern, simplified for our needs:
- * - Two-phase loop: "register-then-check". First pass calls every
- * f_op->poll() with a real poll_table so drivers can register their
- * wait queues; if any fd is already ready we return immediately.
- * Otherwise schedule() until a wq fires (or timeout/signal), then
- * re-check with poll_table=nullptr (probe-only).
- *
- * Limitations vs. Linux:
- * - Only a single wait queue per ppoll call is tracked (the first one
- * a driver hands to poll_wait). Multi-fd polls work, but only the
- * first wq's wake actually re-runs the loop; other fds becoming
- * ready won't be noticed until the timer fires or another wake.
- * BusyBox interactive shell polls one fd (stdin), so this matches.
- *
- * Reference: Linux fs/select.c
- */
-
-#include <lume/poll.h>
-#include <lume/file.h>
-#include <lume/fdtable.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/timer.h>
-#include <lume/waitqueue.h>
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/uaccess.h>
-#include <lume/errno.h>
-#include <lume/klog.h>
-#include <lume/new.h>
-
-/* ================================================================== */
-/* poll_wait — called by file_operations->poll() implementations */
-/* ================================================================== */
-
-void poll_wait(file* /*fp*/, WaitQueue* wq, poll_table* pt)
-{
- /* Probe-only pass: pt is null, drivers skip registration. */
- if (!pt || !wq) return;
- /* Single-wq design: keep the first one we see. A more complete
- * implementation would chain entries onto a list. */
- if (!pt->wq) pt->wq = wq;
-}
-
-/* ================================================================== */
-/* Linux-compatible timespec */
-/* ================================================================== */
-
-struct linux_timespec_pp {
- int64 tv_sec;
- int64 tv_nsec;
-};
-
-/*
- * Convert a timespec into a deadline in jiffies (g_jiffies clock).
- * Returns 0 if the input is null (= infinite wait), or a deadline
- * jiffy value otherwise. Caller treats 0 deadline as "no timeout".
- */
-static uint64 timespec_to_deadline(const linux_timespec_pp* ts)
-{
- if (!ts) return 0;
- /* g_jiffies advances at 100 Hz (HZ=100) — 1 jiffy = 10 ms */
- uint64 ticks = static_cast<uint64>(ts->tv_sec) * 100ULL;
- ticks += ts->tv_nsec / 10'000'000; /* 10 ms granularity */
- /* Round up sub-jiffy nsec so a tiny timeout doesn't truncate to 0. */
- if (ts->tv_nsec % 10'000'000 != 0) ticks++;
- return g_jiffies + ticks;
-}
-
-/* ================================================================== */
-/* do_poll — one scan over all fds */
-/* ================================================================== */
-
-/*
- * For each fd: look up the file, call its poll() callback. If `pt` is
- * non-null, drivers register their wait queues onto it. Returns the
- * number of fds that have any non-zero revents.
- */
-static int do_poll_scan(pollfd* fds, uint32 nfds, poll_table* pt)
-{
- TaskControlBlock* c = current_task();
- files_struct* files = c ? c->files : nullptr;
- if (!files) return 0;
- int ready = 0;
-
- for (uint32 i = 0; i < nfds; i++) {
- if (fds[i].fd < 0) {
- fds[i].revents = 0;
- continue;
- }
- file* fp = fcheck(files, fds[i].fd);
- if (!fp) {
- fds[i].revents = POLLNVAL;
- ready++;
- continue;
- }
- uint32 mask = 0;
- if (fp->f_op && fp->f_op->poll) {
- mask = fp->f_op->poll(fp, pt);
- } else {
- /* No poll handler — treat as always readable/writable so
- * a missing implementation doesn't permanently block sh. */
- mask = POLLIN | POLLOUT;
- }
- /* Filter: revents always include error/hup; userland event bits
- * only set if requested in events. */
- uint32 always = POLLERR | POLLHUP | POLLNVAL;
- uint32 want = static_cast<uint32>(fds[i].events) | always;
- fds[i].revents = static_cast<int16>(mask & want);
- if (fds[i].revents) ready++;
- }
- return ready;
-}
-
-/* ================================================================== */
-/* sys_ppoll */
-/* ================================================================== */
-
-/*
- * a0 = struct pollfd __user *ufds
- * a1 = nfds_t nfds
- * a2 = const struct __kernel_timespec __user *tsp (nullable = infinite)
- * a3 = const sigset_t __user *sigmask (nullable = no override)
- * a4 = size_t sigsetsize
- */
-int64 sys_ppoll(uint64 a0, uint64 a1, uint64 a2, uint64 a3,
- uint64 a4, uint64 /*a5*/)
-{
- uint64 ufds = a0;
- uint32 nfds = static_cast<uint32>(a1);
- uint64 utsp = a2;
- uint64 usigmask = a3;
- uint64 sigsetsize = a4;
-
- if (nfds > 1024) return -EINVAL; /* Linux RLIMIT_NOFILE-ish cap */
- if (usigmask && sigsetsize != sizeof(sigset_t)) return -EINVAL;
-
- /* Copy timespec from user. Null pointer means "wait forever". */
- bool have_timeout = (utsp != 0);
- linux_timespec_pp ts{};
- if (have_timeout) {
- if (copy_from_user(&ts, utsp, sizeof(ts)) < 0) return -EFAULT;
- if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= 1'000'000'000)
- return -EINVAL;
- }
- uint64 deadline = have_timeout ? timespec_to_deadline(&ts) : 0;
-
- /* Apply temporary sigmask if requested. Linux semantics: the new
- * mask is in effect for the duration of the call; on return we
- * restore the old mask. If a signal interrupts us mid-wait, the
- * original mask is restored before returning -EINTR. */
- TaskControlBlock* curr = current_task();
- sigset_t saved_mask{};
- bool mask_swapped = false;
- if (usigmask && curr && curr->sighand) {
- sigset_t newmask{};
- if (copy_from_user(&newmask, usigmask, sizeof(newmask)) < 0)
- return -EFAULT;
- newmask.del(SIGKILL);
- newmask.del(SIGSTOP);
- LockGuard guard(curr->sighand->siglock);
- saved_mask = curr->blocked;
- curr->blocked = newmask;
- mask_swapped = true;
- }
-
- /* Copy pollfd array from user. Allocate a kernel buffer; for the
- * common nfds<=8 case this is one slab object. */
- pollfd* kfds = nullptr;
- uint64 fds_bytes = static_cast<uint64>(nfds) * sizeof(pollfd);
- int64 ret = 0;
-
- if (nfds > 0) {
- kfds = static_cast<pollfd*>(operator new[](fds_bytes));
- if (!kfds) { ret = -ENOMEM; goto out_restore_mask; }
- if (copy_from_user(kfds, ufds, fds_bytes) < 0) {
- ret = -EFAULT; goto out_free;
- }
- }
-
- /* The main poll loop. */
- {
- poll_table pt{};
- pt.wq = nullptr;
- int n_ready = do_poll_scan(kfds, nfds, &pt);
-
- while (n_ready == 0) {
- /* Timeout==0 means non-blocking probe. */
- if (have_timeout && deadline <= g_jiffies) break;
-
- /* No registered wait queue? Nothing to sleep on. This
- * happens when no fd has a poll handler that calls
- * poll_wait — fall back to a short reschedule loop so we
- * don't burn the CPU. */
- if (!pt.wq) {
- schedule();
- if (signal_pending(curr)) { ret = -EINTR; goto out_free; }
- pt.wq = nullptr;
- n_ready = do_poll_scan(kfds, nfds, &pt);
- continue;
- }
-
- /* Standard prepare-check-schedule pattern. */
- pt.wq->prepare_to_wait(curr, TaskState::Sleeping);
-
- /* Re-probe with pt=nullptr (no extra registration) so we
- * see any state change that arrived between the previous
- * scan and prepare_to_wait. */
- n_ready = do_poll_scan(kfds, nfds, nullptr);
- if (n_ready > 0) {
- pt.wq->finish_wait(curr);
- break;
- }
- if (signal_pending(curr)) {
- pt.wq->finish_wait(curr);
- ret = -EINTR; goto out_free;
- }
-
- schedule();
- pt.wq->finish_wait(curr);
-
- /* Reset the registration and rescan — drivers may register
- * fresh wait queues this pass. */
- pt.wq = nullptr;
- n_ready = do_poll_scan(kfds, nfds, &pt);
- }
- ret = n_ready;
- }
-
- /* Copy revents back to user (events bits stay as user wrote them). */
- if (nfds > 0) {
- if (copy_to_user(ufds, kfds, fds_bytes) < 0) ret = -EFAULT;
- }
-
-out_free:
- if (kfds) operator delete[](kfds);
-
-out_restore_mask:
- if (mask_swapped && curr && curr->sighand) {
- LockGuard guard(curr->sighand->siglock);
- curr->blocked = saved_mask;
- }
- return ret;
-}
diff --git a/na/kernel/ptrace.cc b/na/kernel/ptrace.cc
deleted file mode 100644
index 3280c07..0000000
--- a/na/kernel/ptrace.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * PtRegs ↔ TrapFrame conversion
- *
- * This is the ONLY file that knows both the internal TrapFrame layout
- * and the Linux-ABI PtRegs layout. When TrapFrame is restructured
- * (e.g., to embed pt_regs directly), only this file needs to change.
- */
-
-#include <lume/ptrace.h>
-#include <arch/trapframe.h>
-
-void trapframe_to_pt_regs(PtRegs* dst, const arch::TrapFrame* src,
- uint64 scause, uint64 stval)
-{
- dst->epc = src->epc;
- dst->ra = src->ra;
- dst->sp = src->sp;
- dst->gp = src->gp;
- dst->tp = src->tp;
- dst->t0 = src->t[0];
- dst->t1 = src->t[1];
- dst->t2 = src->t[2];
- dst->s0 = src->s[0];
- dst->s1 = src->s[1];
- dst->a0 = src->a[0];
- dst->a1 = src->a[1];
- dst->a2 = src->a[2];
- dst->a3 = src->a[3];
- dst->a4 = src->a[4];
- dst->a5 = src->a[5];
- dst->a6 = src->a[6];
- dst->a7 = src->a[7];
- dst->s2 = src->s[2];
- dst->s3 = src->s[3];
- dst->s4 = src->s[4];
- dst->s5 = src->s[5];
- dst->s6 = src->s[6];
- dst->s7 = src->s[7];
- dst->s8 = src->s[8];
- dst->s9 = src->s[9];
- dst->s10 = src->s[10];
- dst->s11 = src->s[11];
- dst->t3 = src->t[3];
- dst->t4 = src->t[4];
- dst->t5 = src->t[5];
- dst->t6 = src->t[6];
- dst->status = src->sstatus;
- dst->badaddr = stval;
- dst->cause = scause;
- dst->orig_a0 = src->a[0]; /* Caller should set this before syscall dispatch */
-}
-
-void pt_regs_to_trapframe(arch::TrapFrame* dst, const PtRegs* src)
-{
- dst->epc = src->epc;
- dst->ra = src->ra;
- dst->sp = src->sp;
- dst->gp = src->gp;
- dst->tp = src->tp;
- dst->t[0] = src->t0;
- dst->t[1] = src->t1;
- dst->t[2] = src->t2;
- dst->s[0] = src->s0;
- dst->s[1] = src->s1;
- dst->a[0] = src->a0;
- dst->a[1] = src->a1;
- dst->a[2] = src->a2;
- dst->a[3] = src->a3;
- dst->a[4] = src->a4;
- dst->a[5] = src->a5;
- dst->a[6] = src->a6;
- dst->a[7] = src->a7;
- dst->s[2] = src->s2;
- dst->s[3] = src->s3;
- dst->s[4] = src->s4;
- dst->s[5] = src->s5;
- dst->s[6] = src->s6;
- dst->s[7] = src->s7;
- dst->s[8] = src->s8;
- dst->s[9] = src->s9;
- dst->s[10] = src->s10;
- dst->s[11] = src->s11;
- dst->t[3] = src->t3;
- dst->t[4] = src->t4;
- dst->t[5] = src->t5;
- dst->t[6] = src->t6;
- dst->sstatus = src->status;
- /* badaddr and cause are CSR values, not written back to TrapFrame */
-}
diff --git a/na/kernel/sched_class_o1.cc b/na/kernel/sched_class_o1.cc
deleted file mode 100644
index 28c693c..0000000
--- a/na/kernel/sched_class_o1.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * O(1) Priority Bitmap Scheduling Class
- */
-
-#include <lume/sched.h>
-#include <lume/sched_class.h>
-#include <lume/task.h>
-#include <lume/bitops.h>
-#include <kernel/sched_internal.h>
-
-static void o1_init_rq(RunQueue* rq)
-{
- rq->o1().init();
-}
-
-static void o1_enqueue_task(RunQueue* rq, TaskControlBlock* task)
-{
- rq->o1().enqueue(task);
- rq->inc_running();
-}
-
-static void o1_dequeue_task(RunQueue* rq, TaskControlBlock* task)
-{
- rq->o1().dequeue(task);
- rq->dec_running();
-}
-
-static TaskControlBlock* o1_pick_next_task(RunQueue* rq)
-{
- return rq->o1().pick_next();
-}
-
-static void o1_put_prev_task(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {}
-
-int32 priority_to_timeslice(uint8 priority)
-{
- int32 ts = 20 - static_cast<int32>(priority) * 19 / 31;
- if (ts < 1) ts = 1;
- return ts;
-}
-
-static void o1_task_tick(RunQueue* rq, TaskControlBlock* curr)
-{
- if (curr == rq->idle()) {
- /* Idle task: if anything is runnable, yield immediately */
- if (rq->nr_running() > 0) {
- rq->set_need_resched(true);
- }
- return;
- }
-
- curr->time_slice--;
- if (curr->time_slice <= 0) {
- curr->time_slice = priority_to_timeslice(curr->priority);
- rq->set_need_resched(true);
- }
-}
-
-static void o1_yield_task(RunQueue* rq, TaskControlBlock* curr)
-{
- if (curr == rq->idle())
- return;
-
- curr->time_slice = 0;
- rq->set_need_resched(true);
-}
-
-const SchedClass g_sched_class_o1 = {
- .name = "o1",
- .next = &g_sched_class_idle,
- .init_rq = o1_init_rq,
- .enqueue_task = o1_enqueue_task,
- .dequeue_task = o1_dequeue_task,
- .pick_next_task = o1_pick_next_task,
- .put_prev_task = o1_put_prev_task,
- .task_tick = o1_task_tick,
- .yield_task = o1_yield_task,
-};
diff --git a/na/kernel/sched_core.cc b/na/kernel/sched_core.cc
deleted file mode 100644
index c0e068e..0000000
--- a/na/kernel/sched_core.cc
+++ /dev/null
@@ -1,571 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Scheduler Core
- *
- * The core walks sched_class chain to pick the next task. Each class
- * owns its own runqueue data and policy. This file owns the per-CPU
- * runqueue array and the scheduling state machine.
- */
-
-#include <lume/sched.h>
-#include <lume/sched_class.h>
-#include <lume/task.h>
-#include <lume/pmm.h>
-#include <lume/vmm.h>
-#include <lume/addr.h>
-#include <lume/new.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/smp.h>
-#include <arch/cpu.h>
-#include <arch/mmu.h>
-#include <arch/trap.h>
-#include <arch/context.h>
-#include <kernel/sched_internal.h>
-
-/* Assembly trampoline symbol (MD layer) */
-extern "C" void kthread_entry_trampoline();
-
-/* Per-CPU runqueue array */
-static RunQueue g_rq[kMaxCpus];
-
-/* sched_class chain head (highest priority first) */
-static const SchedClass* g_sched_class_head = &g_sched_class_o1;
-
-/* ========================================================================
- * Idle thread
- * ======================================================================== */
-
-static void idle_thread_func(void* /*arg*/)
-{
- while (true) {
- if (g_rq[arch::cpu::id()].need_resched()) {
- schedule();
- }
- arch::cpu::intr_on();
- arch::cpu::halt_until_interrupt();
- }
-}
-
-
-/* ========================================================================
- * Idle class (always returns idle task)
- * ======================================================================== */
-
-static void idle_init_rq(RunQueue* /*rq*/) {}
-static void idle_enqueue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {}
-static void idle_dequeue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {}
-static TaskControlBlock* idle_pick_next(RunQueue* rq) { return rq->idle(); }
-static void idle_put_prev(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {}
-static void idle_task_tick(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {}
-static void idle_yield(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {}
-
-const SchedClass g_sched_class_idle = {
- .name = "idle",
- .next = nullptr,
- .init_rq = idle_init_rq,
- .enqueue_task = idle_enqueue,
- .dequeue_task = idle_dequeue,
- .pick_next_task = idle_pick_next,
- .put_prev_task = idle_put_prev,
- .task_tick = idle_task_tick,
- .yield_task = idle_yield,
-};
-
-/* ========================================================================
- * Internal helpers
- * ======================================================================== */
-
-static RunQueue* rq_for_cpu(uint32 cpu_id)
-{
- return &g_rq[cpu_id];
-}
-
-static RunQueue* this_rq()
-{
- return rq_for_cpu(arch::cpu::id());
-}
-
-static TaskControlBlock* pick_next_task(RunQueue* rq)
-{
- for (const SchedClass* c = g_sched_class_head; c; c = c->next) {
- TaskControlBlock* t = c->pick_next_task(rq);
- if (t) {
- return t;
- }
- }
- return rq->idle();
-}
-
-static const char* idle_name_for_cpu(uint32 cpu)
-{
- static char names[kMaxCpus][16];
- if (cpu >= kMaxCpus) {
- kernel_panic("sched: cpu_id out of range");
- }
-
- if (names[cpu][0] == '\0') {
- ksnprintf(names[cpu], sizeof(names[cpu]), "idle/%u", cpu);
- }
- return names[cpu];
-}
-
-/* ========================================================================
- * Kernel thread entry — MI-layer function called by MD trampoline
- * ======================================================================== */
-
-/* Forward declaration — C++ linkage (task.cc) */
-void task_exit(int code);
-
-extern "C" void kthread_entry(void (*entry)(void*), void* arg)
-{
- RunQueue* rq = this_rq();
- rq->lock().release();
-
- arch::cpu::intr_on();
-
- entry(arg);
-
- /* Entry function returned — clean exit (release PID, stack, TCB) */
- task_exit(0);
- __builtin_unreachable();
-}
-
-/* ========================================================================
- * fork_child_entry — called from ret_from_fork assembly
- * ======================================================================== */
-extern "C" void userret(arch::TrapFrame* tf);
-extern "C" void trap_vector();
-
-extern "C" void fork_child_entry(arch::TrapFrame* tf)
-{
- RunQueue* rq = this_rq();
- rq->lock().release();
-
- /* Set stvec to trap_vector for user-mode traps, then return to user */
- arch::trap::set_vector(reinterpret_cast<uint64>(trap_vector));
- userret(tf);
-
- __builtin_unreachable();
-}
-
-/* ========================================================================
- * alloc_task — canonical TCB allocator (defined in task.cc)
- *
- * Single point of initialization for TaskControlBlock. Ensures all
- * fields are properly zeroed when the struct gains new members.
- * ======================================================================== */
-
-extern TaskControlBlock* alloc_task(const char* name, uint8 priority,
- uint8 flags, uint8 cpu);
-
-/* ========================================================================
- * Public API: current_task()
- * ======================================================================== */
-
-TaskControlBlock* current_task()
-{
- return this_rq()->current();
-}
-
-/* ========================================================================
- * Public API: sched_enqueue / sched_dequeue
- * ======================================================================== */
-
-void sched_enqueue(TaskControlBlock* task)
-{
- RunQueue* rq = rq_for_cpu(task->cpu_id);
- LockGuard guard(rq->lock());
-
- if (!task->sched_class) {
- task->sched_class = &g_sched_class_o1;
- }
- if (!task->sched_entity) {
- task->sched_entity = &task->run_link;
- }
-
- task->sched_class->enqueue_task(rq, task);
-
- if (rq->current() == rq->idle() || task->priority < rq->current()->priority) {
- rq->set_need_resched(true);
- }
-}
-
-void sched_dequeue(TaskControlBlock* task)
-{
- RunQueue* rq = rq_for_cpu(task->cpu_id);
- LockGuard guard(rq->lock());
-
- if (!task->sched_class) {
- task->sched_class = &g_sched_class_o1;
- }
- task->sched_class->dequeue_task(rq, task);
-}
-
-/* ========================================================================
- * schedule() — main scheduling entry point
- * ======================================================================== */
-
-void schedule()
-{
- RunQueue* rq = this_rq();
- rq->lock().acquire();
-
- TaskControlBlock* prev = rq->current();
-
- if (prev && prev->state == TaskState::Running) {
- prev->state = TaskState::Runnable;
- prev->sched_class->enqueue_task(rq, prev);
- } else if (prev) {
- prev->sched_class->put_prev_task(rq, prev);
- }
-
- TaskControlBlock* next = pick_next_task(rq);
-
- if (prev == next) {
- /* We picked the same task. It must be Running again. */
- next->state = TaskState::Running;
- next->sched_class->dequeue_task(rq, next);
- rq->clear_need_resched();
- rq->lock().release();
- return;
- }
-
- if (next != rq->idle()) {
- next->sched_class->dequeue_task(rq, next);
- }
-
- next->state = TaskState::Running;
- rq->set_current(next);
- rq->clear_need_resched();
-
- /* Switch address space (satp) if the new task has a different mm.
- * Kernel threads (mm == nullptr) borrow the previous task's mm,
- * so no satp switch is needed — this matches Linux's lazy TLB behavior. */
- {
- VmSpace* prev_mm = prev ? prev->mm : nullptr;
- VmSpace* next_mm = next->mm;
- uint32 cpu = static_cast<uint32>(arch::cpu::id());
-
- if (next_mm && next_mm != prev_mm) {
- /* Switching TO a user process with a different address space.
- * Linux: switch_mm_irqs_off() in arch/riscv/mm/context.c
- * sets satp and flushes TLB. */
- arch::mmu::set_page_table(next_mm->root_pa);
- arch::mmu::flush_tlb_all();
- next_mm->cpu_mask.fetch_or(1ULL << cpu, __ATOMIC_RELAXED);
-
- /* Critical: update TrapFrame's kernel_satp so that trap_vector.S
- * restores the correct page table on next trap entry.
- * Without this, a process migrated to a new CPU would have its
- * trap entry use a stale satp from when trapframe_init_user()
- * was called, pointing to whatever page table was active then
- * on the ORIGINAL CPU. */
- if (next->trapframe) {
- next->trapframe->kernel_satp = arch::mmu::read_satp();
- }
- }
-
- if (prev_mm && prev_mm != next_mm) {
- /* Leaving prev's address space — clear our bit */
- prev_mm->cpu_mask.fetch_and(~(1ULL << cpu), __ATOMIC_RELAXED);
- }
- }
-
- /* FPU lazy save/restore.
- * Save prev's FP registers only if they were modified (FS=Dirty).
- * Restore next's FP state for user tasks; disable FPU for kthreads. */
- if (arch::fpu_state_dirty(arch::fpu_get_fs())) {
- fpu_save(&prev->fpstate);
- }
- if (next->mm != nullptr) {
- arch::fpu_set_fs(arch::kSstatusFS_Clean);
- fpu_restore(&next->fpstate);
- } else {
- arch::fpu_set_fs(arch::kSstatusFS_Off);
- }
-
- arch::context_switch(&prev->context, &next->context);
-
- rq->lock().release();
-}
-
-/* ========================================================================
- * sched_tick() — called from timer interrupt (MI trap layer)
- * ======================================================================== */
-
-void sched_tick()
-{
- RunQueue* rq = this_rq();
- {
- LockGuard guard(rq->lock());
- TaskControlBlock* curr = rq->current();
- if (!curr) return;
- curr->sched_class->task_tick(rq, curr);
- }
-
- /* Periodic load balancing — runs without holding rq lock */
- sched_balance();
-}
-
-/* ========================================================================
- * sched_check_preempt() — called from trap return path
- *
- * Only preempts if:
- * 1. need_resched flag is set (a higher-priority task is ready)
- * 2. preempt_count == 0 (no spinlocks held, safe to context switch)
- * ======================================================================== */
-
-void sched_check_preempt()
-{
- RunQueue* rq = this_rq();
- if (!rq->need_resched()) return;
-
- TaskControlBlock* curr = current_task();
- if (curr && curr->preempt_count > 0) {
- /* Cannot preempt — caller is in a critical section.
- * The preemption will fire when the last lock is released. */
- return;
- }
- schedule();
-}
-
-/* ========================================================================
- * sched_yield() — voluntary yield
- * ======================================================================== */
-
-void sched_yield()
-{
- RunQueue* rq = this_rq();
- {
- LockGuard guard(rq->lock());
- TaskControlBlock* curr = rq->current();
- if (curr && curr != rq->idle()) {
- curr->sched_class->yield_task(rq, curr);
- rq->set_need_resched(true);
- }
- }
- schedule();
-}
-
-/* ========================================================================
- * sched_init() — BSP initialization
- * ======================================================================== */
-
-void sched_init()
-{
- for (int i = 0; i < kMaxCpus; i++) {
- RunQueue* rq = &g_rq[i];
- rq->init(static_cast<uint32>(i));
-
- g_sched_class_o1.init_rq(rq);
- g_sched_class_idle.init_rq(rq);
- }
-
- g_sched_class_head = &g_sched_class_o1;
-
- uint64 bsp_cpu = arch::cpu::id();
- uint8 bsp_cpu8 = static_cast<uint8>(bsp_cpu);
-
- TaskControlBlock* idle0 = alloc_task(idle_name_for_cpu(static_cast<uint32>(bsp_cpu)),
- kIdlePriority,
- TF_KTHREAD | TF_IDLE, bsp_cpu8);
- if (!idle0) kernel_panic("sched_init: failed to create idle/bsp");
-
- idle0->pid = 0;
- idle0->tgid = 0;
- idle0->sched_class = &g_sched_class_idle;
- idle0->sched_entity = nullptr;
-
- uint64 stack_top = idle0->kstack_va + kKernelStackSize
- - sizeof(arch::TrapFrame);
- idle0->context = {};
- idle0->context.ra = reinterpret_cast<uint64>(kthread_entry_trampoline);
- idle0->context.sp = stack_top;
- idle0->context.s[0] = reinterpret_cast<uint64>(idle_thread_func);
- idle0->context.s[1] = 0;
-
- g_rq[bsp_cpu].set_idle(idle0);
-
- TaskControlBlock* bsp_task = alloc_task("kernel_main", 0, TF_KTHREAD, bsp_cpu8);
- if (!bsp_task) kernel_panic("sched_init: failed to create bsp_task");
-
- bsp_task->pid = -1;
- bsp_task->tgid = -1;
- bsp_task->state = TaskState::Running;
-
- g_rq[bsp_cpu].set_current(bsp_task);
-}
-
-/* ========================================================================
- * sched_init_ap() — AP per-CPU initialization
- * ======================================================================== */
-
-void sched_init_ap()
-{
- uint64 cpu = arch::cpu::id();
-
- const char* name = idle_name_for_cpu(static_cast<uint32>(cpu));
-
- TaskControlBlock* idle = alloc_task(name, kIdlePriority,
- TF_KTHREAD | TF_IDLE,
- static_cast<uint8>(cpu));
- if (!idle) kernel_panic("sched_init_ap: failed to create idle");
-
- idle->pid = 0;
- idle->tgid = 0;
- idle->sched_class = &g_sched_class_idle;
- idle->sched_entity = nullptr;
-
- uint64 stack_top = idle->kstack_va + kKernelStackSize
- - sizeof(arch::TrapFrame);
- idle->context = {};
- idle->context.ra = reinterpret_cast<uint64>(kthread_entry_trampoline);
- idle->context.sp = stack_top;
- idle->context.s[0] = reinterpret_cast<uint64>(idle_thread_func);
- idle->context.s[1] = 0;
-
- g_rq[cpu].set_idle(idle);
-
- TaskControlBlock* ap_task = alloc_task("ap_boot", 0, TF_KTHREAD,
- static_cast<uint8>(cpu));
- if (!ap_task) kernel_panic("sched_init_ap: failed to create ap_task");
-
- ap_task->pid = -1;
- ap_task->tgid = -1;
- ap_task->state = TaskState::Running;
-
- g_rq[cpu].set_current(ap_task);
-}
-
-/* ========================================================================
- * sched_nr_running() — runnable task count for a CPU
- * ======================================================================== */
-
-uint32 sched_nr_running(uint32 cpu_id)
-{
- if (cpu_id >= kMaxCpus) return 0;
- return g_rq[cpu_id].o1().nr_running();
-}
-
-/* ========================================================================
- * sched_migrate_task() — move a task between CPUs
- *
- * Locks are acquired in cpu_id order to prevent ABBA deadlock.
- * The task must be Runnable (in a runqueue) and not currently running.
- * ======================================================================== */
-
-int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu)
-{
- if (!task || target_cpu >= kMaxCpus) return -22; /* -EINVAL */
-
- uint8 src_cpu = task->cpu_id;
- if (src_cpu == target_cpu) return 0; /* already there */
-
- /* Cannot migrate idle or currently-running tasks */
- if (task->flags & TF_IDLE) return -22;
-
- RunQueue* src_rq = rq_for_cpu(src_cpu);
- RunQueue* dst_rq = rq_for_cpu(target_cpu);
-
- /* Ordered lock acquisition: always lock lower cpu_id first */
- if (src_cpu < target_cpu) {
- src_rq->lock().acquire();
- dst_rq->lock().acquire();
- } else {
- dst_rq->lock().acquire();
- src_rq->lock().acquire();
- }
-
- int ret = -22;
-
- /* Double-check: task must not be current on either CPU */
- if (task != src_rq->current() && task->state == TaskState::Runnable) {
- /* Dequeue from source */
- task->sched_class->dequeue_task(src_rq, task);
-
- /* Update cpu_id and enqueue on destination */
- task->cpu_id = target_cpu;
- task->sched_class->enqueue_task(dst_rq, task);
-
- /* Signal target CPU to reschedule */
- dst_rq->set_need_resched(true);
- ret = 0;
- }
-
- /* Release in reverse order */
- if (src_cpu < target_cpu) {
- dst_rq->lock().release();
- src_rq->lock().release();
- } else {
- src_rq->lock().release();
- dst_rq->lock().release();
- }
-
- /* If migration succeeded, send reschedule IPI to target */
- if (ret == 0) {
- smp_send_reschedule(target_cpu);
- }
-
- return ret;
-}
-
-/* ========================================================================
- * sched_balance() — periodic load balancer (pull model)
- *
- * Called from sched_tick() context. Looks at all other CPUs and if any
- * has 2+ more runnable tasks than the local CPU, pulls one task.
- * This is a simplified Linux-style load_balance().
- * ======================================================================== */
-
-void sched_balance()
-{
- uint32 this_cpu = static_cast<uint32>(arch::cpu::id());
- uint32 local_load = g_rq[this_cpu].o1().nr_running();
-
- /* Only balance if we're idle or lightly loaded */
- if (local_load > 1) return;
-
- uint32 busiest_cpu = this_cpu;
- uint32 busiest_load = local_load;
-
- /* Find the busiest CPU */
- for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) {
- if (cpu == this_cpu) continue;
- uint32 load = g_rq[cpu].o1().nr_running();
- if (load > busiest_load) {
- busiest_load = load;
- busiest_cpu = cpu;
- }
- }
-
- /* Only pull if imbalance is >= 2 */
- if (busiest_cpu == this_cpu || busiest_load < local_load + 2) return;
-
- /* Try to pull one task from the busiest CPU */
- RunQueue* busy_rq = rq_for_cpu(busiest_cpu);
-
- /* Lock ordering: lower cpu_id first */
- RunQueue* first = (this_cpu < busiest_cpu)
- ? &g_rq[this_cpu] : busy_rq;
- RunQueue* second = (this_cpu < busiest_cpu)
- ? busy_rq : &g_rq[this_cpu];
-
- first->lock().acquire();
- second->lock().acquire();
-
- /* Re-check after acquiring locks */
- TaskControlBlock* victim = busy_rq->o1().pick_next();
- if (victim && victim != busy_rq->current()
- && !(victim->flags & TF_IDLE)) {
- victim->sched_class->dequeue_task(busy_rq, victim);
- victim->cpu_id = static_cast<uint8>(this_cpu);
- victim->sched_class->enqueue_task(&g_rq[this_cpu], victim);
- g_rq[this_cpu].set_need_resched(true);
- }
-
- second->lock().release();
- first->lock().release();
-}
diff --git a/na/kernel/sched_internal.h b/na/kernel/sched_internal.h
deleted file mode 100644
index 514b613..0000000
--- a/na/kernel/sched_internal.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-#include <lume/types.h>
-#include <lume/list.h>
-#include <lume/sched.h>
-#include <lume/config.h>
-#include <lume/bitops.h>
-#include <lume/task.h>
-#include <kernel/sync/spinlock.h>
-
-class O1RunQueue {
-public:
- void init() {
- bitmap_ = 0;
- nr_running_ = 0;
- for (int p = 0; p < kNumPriorities; p++) {
- queues_[p].init();
- }
- }
-
- void enqueue(TaskControlBlock* task) {
- int prio = task->priority;
-
- list_node* head = &queues_[prio];
- task->run_link.prev = head->prev;
- task->run_link.next = head;
- head->prev->next = &task->run_link;
- head->prev = &task->run_link;
-
- bitmap_ |= (1U << prio);
- nr_running_++;
-
- task->state = TaskState::Runnable;
- }
-
- void dequeue(TaskControlBlock* task) {
- int prio = task->priority;
-
- task->run_link.prev->next = task->run_link.next;
- task->run_link.next->prev = task->run_link.prev;
- task->run_link.init();
-
- if (queues_[prio].is_empty()) {
- bitmap_ &= ~(1U << prio);
- }
-
- if (nr_running_ > 0) {
- nr_running_--;
- }
- }
-
- TaskControlBlock* pick_next() const {
- if (bitmap_ == 0) {
- return nullptr;
- }
-
- int prio = lume::ctz32(bitmap_);
- list_node* node = queues_[prio].next;
- return list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node);
- }
-
- uint32 nr_running() const { return nr_running_; }
-
-private:
- uint32 bitmap_ = 0;
- list_node queues_[kNumPriorities];
- uint32 nr_running_ = 0;
-};
-
-class RunQueue {
-public:
- void init(uint32 cpu_id) {
- lock_.init("RunQueue");
- cpu_id_ = cpu_id;
- nr_running_ = 0;
- need_resched_ = false;
- current_ = nullptr;
- idle_ = nullptr;
- }
-
- Spinlock& lock() { return lock_; }
-
- uint32 cpu_id() const { return cpu_id_; }
-
- uint32 nr_running() const { return nr_running_; }
- void inc_running() { nr_running_++; }
- void dec_running() { if (nr_running_ > 0) nr_running_--; }
-
- bool need_resched() const { return need_resched_; }
- void set_need_resched(bool v) { need_resched_ = v; }
- void clear_need_resched() { need_resched_ = false; }
-
- TaskControlBlock* current() const { return current_; }
- void set_current(TaskControlBlock* task) { current_ = task; }
-
- TaskControlBlock* idle() const { return idle_; }
- void set_idle(TaskControlBlock* task) { idle_ = task; }
-
- O1RunQueue& o1() { return o1_; }
- const O1RunQueue& o1() const { return o1_; }
-
-private:
- Spinlock lock_;
- uint32 cpu_id_ = 0;
- uint32 nr_running_ = 0;
- bool need_resched_ = false;
-
- TaskControlBlock* current_ = nullptr;
- TaskControlBlock* idle_ = nullptr;
-
- O1RunQueue o1_;
-};
diff --git a/na/kernel/signal.cc b/na/kernel/signal.cc
deleted file mode 100644
index 53d26da..0000000
--- a/na/kernel/signal.cc
+++ /dev/null
@@ -1,442 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Signal Delivery — Queue, Dequeue, Default Actions, and do_signal
- *
- * Implements the core signal infrastructure:
- * - send_signal(): enqueue a signal to a task's pending set
- * - force_sig_fault(): fault-induced signal (trap handler API)
- * - do_signal(): called on return-to-user, delivers pending signals
- * - next_signal(): dequeue the highest-priority deliverable signal
- *
- * Signal frame construction (arch-specific) is in arch/riscv/signal.cc.
- *
- * Reference: Linux kernel/signal.c
- */
-
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/signal_nr.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/wait.h>
-#include <lume/console.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/new.h>
-#include <lume/errno.h>
-#include <arch/trapframe.h>
-
-/* Forward declaration: arch-specific signal frame setup */
-int setup_signal_frame(arch::TrapFrame* tf, int signo,
- kernel_sigaction* ka, kernel_siginfo* info);
-
-/* ================================================================== */
-/* sighand_struct lifecycle (refcount-shared, Linux copy_sighand) */
-/* ================================================================== */
-
-sighand_struct* alloc_sighand()
-{
- auto* sh = new sighand_struct;
- if (sh) sh->init();
- return sh;
-}
-
-sighand_struct* copy_sighand(sighand_struct* parent)
-{
- /* Linux: kernel/fork.c:copy_sighand() when !CLONE_SIGHAND copies
- * the action table verbatim so the child inherits dispositions
- * without sharing future modifications. */
- if (!parent) return alloc_sighand();
- auto* sh = new sighand_struct;
- if (!sh) return nullptr;
- sh->init();
- LockGuard guard(parent->siglock);
- for (int i = 0; i < _NSIG; i++) sh->action[i] = parent->action[i];
- return sh;
-}
-
-void get_sighand(sighand_struct* sh)
-{
- if (!sh) return;
- LockGuard guard(sh->siglock);
- sh->refcount++;
-}
-
-void put_sighand(sighand_struct* sh)
-{
- if (!sh) return;
- uint32 remaining;
- {
- LockGuard guard(sh->siglock);
- remaining = --sh->refcount;
- }
- if (remaining == 0) delete sh;
-}
-
-/* ================================================================== */
-/* next_signal — find lowest-numbered deliverable signal */
-/* ================================================================== */
-
-/* Manual count-trailing-zeros (no libgcc __ctzdi2 dependency) */
-static inline int sig_ctz64(uint64 x)
-{
- if (x == 0) return 64;
- int n = 0;
- if ((x & 0xFFFFFFFF) == 0) { n += 32; x >>= 32; }
- if ((x & 0x0000FFFF) == 0) { n += 16; x >>= 16; }
- if ((x & 0x000000FF) == 0) { n += 8; x >>= 8; }
- if ((x & 0x0000000F) == 0) { n += 4; x >>= 4; }
- if ((x & 0x00000003) == 0) { n += 2; x >>= 2; }
- if ((x & 0x00000001) == 0) { n += 1; }
- return n;
-}
-
-static int next_signal(sigpending* pending, const sigset_t* mask)
-{
- /* Standard signals: check bitmap & ~blocked */
- for (int i = 0; i < _NSIG_WORDS; i++) {
- uint64 bits = pending->signal.sig[i] & ~mask->sig[i];
- if (bits != 0) {
- int sig = i * 64 + sig_ctz64(bits) + 1;
- return sig;
- }
- }
- return 0;
-}
-
-/* ================================================================== */
-/* dequeue_signal — remove a signal from the pending set */
-/* ================================================================== */
-
-/*
- * Dequeue the next deliverable signal. MUST be called with
- * task->sighand->siglock held. Mirrors Linux's __dequeue_signal()
- * (kernel/signal.c) which requires the same lock.
- */
-static int dequeue_signal_locked(TaskControlBlock* task, kernel_siginfo* info)
-{
- int sig = next_signal(&task->pending, &task->blocked);
- if (sig == 0) return 0;
-
- /* Clear the bitmap bit */
- task->pending.signal.del(sig);
-
- /* Search the queue for a matching sigqueue entry */
- list_node* node = task->pending.list.next;
- while (node != &task->pending.list) {
- auto* q = list_entry<sigqueue, &sigqueue::list>(node);
- if (q->info.si_signo == sig) {
- /* Remove from queue */
- node->prev->next = node->next;
- node->next->prev = node->prev;
- if (info) *info = q->info;
- delete q;
- return sig;
- }
- node = node->next;
- }
-
- /* No queued entry — fill in minimal info */
- if (info) {
- info->si_signo = sig;
- info->si_errno = 0;
- info->si_code = 0; /* SI_USER */
- }
- return sig;
-}
-
-/* ================================================================== */
-/* send_signal — enqueue a signal to a task */
-/* ================================================================== */
-
-/*
- * Drop a queued sigqueue entry with the given signo from `task`.
- *
- * MUST be called with task->sighand->siglock held — Linux's
- * flush_sigqueue_mask() has the same precondition (kernel/signal.c).
- */
-static bool discard_pending_locked(TaskControlBlock* task, int signo)
-{
- bool any = false;
- list_node* node = task->pending.list.next;
- while (node != &task->pending.list) {
- list_node* nxt = node->next;
- auto* q = list_entry<sigqueue, &sigqueue::list>(node);
- if (q->info.si_signo == signo) {
- node->prev->next = node->next;
- node->next->prev = node->prev;
- delete q;
- any = true;
- }
- node = nxt;
- }
- task->pending.signal.del(signo);
- return any;
-}
-
-static inline bool is_stop_sig(int sig)
-{
- return sig == SIGSTOP || sig == SIGTSTP ||
- sig == SIGTTIN || sig == SIGTTOU;
-}
-
-/*
- * send_signal — queue a signal to a task.
- *
- * Acquires task->sighand->siglock for the full update (bitmap, queue,
- * stop/cont side-effects, scheduler wake). Mirrors Linux's
- * __send_signal_locked() which also requires this lock (see
- * kernel/signal.c:1048 lockdep_assert_held).
- *
- * Returns 0 on success or a negative errno.
- */
-int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task)
-{
- if (sig < 1 || sig > _NSIG) return -EINVAL;
- if (!task) return -ESRCH;
- if (!task->sighand) return -ESRCH; /* eager-alloc invariant; tasks
- * without sighand can't receive
- * signals (idle / early kthreads) */
-
- LockGuard guard(task->sighand->siglock);
-
- /*
- * Linux's prepare_signal(): a stop signal cancels any pending
- * SIGCONT and vice versa, so the most recent decision wins.
- */
- if (is_stop_sig(sig)) {
- discard_pending_locked(task, SIGCONT);
- } else if (sig == SIGCONT) {
- discard_pending_locked(task, SIGSTOP);
- discard_pending_locked(task, SIGTSTP);
- discard_pending_locked(task, SIGTTIN);
- discard_pending_locked(task, SIGTTOU);
-
- /* SIGCONT's resume effect happens atomically with delivery. */
- if (task->state == TaskState::Stopped) {
- task->state = TaskState::Runnable;
- task->notify_continued = true;
- task->stopped_code = 0;
- sched_enqueue(task);
- if (task->parent) task->parent->wait_chld.wake_all();
- }
- }
-
- /* Already pending? For standard signals, just mark the bitmap */
- if (sig < SIGRTMIN && task->pending.signal.has(sig))
- return 0;
-
- /* Enqueue a sigqueue entry */
- auto* q = new sigqueue;
- if (!q) {
- /* Can't allocate — just set the bitmap (signal still delivered) */
- task->pending.signal.add(sig);
- return 0;
- }
-
- q->list.init();
- if (info) {
- q->info = *info;
- } else {
- q->info.si_signo = sig;
- q->info.si_errno = 0;
- q->info.si_code = 0; /* SI_USER */
- }
- q->info.si_signo = sig; /* ensure consistent */
-
- /* Add to tail of queue */
- list_node* head = &task->pending.list;
- q->list.prev = head->prev;
- q->list.next = head;
- head->prev->next = &q->list;
- head->prev = &q->list;
-
- /* Set bitmap */
- task->pending.signal.add(sig);
-
- /* Wake the task if it's sleeping in an interruptible wait. */
- if (task->state == TaskState::Sleeping) {
- task->state = TaskState::Runnable;
- sched_enqueue(task);
- }
-
- /* SIGKILL must run even if the target is Stopped. */
- if (sig == SIGKILL && task->state == TaskState::Stopped) {
- task->state = TaskState::Runnable;
- task->stopped_code = 0;
- sched_enqueue(task);
- }
-
- return 0;
-}
-
-/* ================================================================== */
-/* force_sig_fault — trap handler API */
-/* ================================================================== */
-
-void force_sig_fault(int signo, int code, uint64 addr)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) return;
-
- /* Kernel threads cannot handle signals — kill immediately */
- if (curr->flags & TF_KTHREAD) {
- pr_err("signal: kernel thread %s received signal %d\n",
- curr->name ? curr->name : "(noname)", signo);
- task_kill_current(signo);
- return;
- }
-
- /* Sighand must already exist for user-mode tasks (allocated at
- * fork / kernel_execve time). If it's somehow missing, the only
- * safe action is to terminate. */
- if (!curr->sighand) {
- pr_err("signal: pid %d has no sighand, killing\n", curr->pid);
- task_kill_current(signo);
- return;
- }
-
- kernel_siginfo info{};
- info.si_signo = signo;
- info.si_code = code;
- info._sifields._sigfault._addr = addr;
-
- /* Force: bypass blocked mask for fatal signals (lock to keep
- * blocked-mask updates consistent with sigprocmask). */
- {
- LockGuard guard(curr->sighand->siglock);
- curr->blocked.del(signo);
- }
-
- send_signal(signo, &info, curr);
-}
-
-/* ================================================================== */
-/* signal_pending — check if unblocked signals are pending */
-/* ================================================================== */
-
-bool signal_pending(TaskControlBlock* task)
-{
- /* Advisory check (matches Linux's recalc_sigpending() usage):
- * callers re-validate under siglock before acting. The bitmap is
- * a uint64 word; a torn read can only produce false-positives or
- * false-negatives transiently, never a corrupt pointer. */
- if (!task) return false;
- sigset_t unblocked = task->pending.signal & ~task->blocked;
- return !unblocked.is_empty();
-}
-
-/* ================================================================== */
-/* do_signal — called before returning to user mode */
-/* ================================================================== */
-
-void do_signal(arch::TrapFrame* tf)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) return;
-
- /* Kernel threads never deliver signals via frames */
- if (curr->flags & TF_KTHREAD) return;
-
- /* No sighand means no signal infrastructure for this task */
- if (!curr->sighand) return;
-
- while (true) {
- kernel_siginfo info{};
- kernel_sigaction ka_copy;
- int sig;
-
- /*
- * Take siglock for dequeue + action snapshot. Action processing
- * (default actions like SIG_ACT_TERM, or constructing a signal
- * frame for a user handler) is done OUTSIDE the lock — it can
- * touch user memory and even call schedule(). This matches
- * Linux's get_signal() loop in kernel/signal.c: it takes
- * siglock around dequeue_signal+ka_copy, drops it before
- * dispatching, and re-acquires next iteration.
- */
- {
- LockGuard guard(curr->sighand->siglock);
- sig = dequeue_signal_locked(curr, &info);
- if (sig == 0) break;
- ka_copy = curr->sighand->action[sig - 1];
- }
- kernel_sigaction* ka = &ka_copy;
-
- /* SIG_IGN: discard */
- if (ka->sa_handler == SIG_IGN && !sig_kernel_only(sig))
- continue;
-
- /* SIG_DFL: apply default action */
- if (ka->sa_handler == SIG_DFL) {
- SigDefaultAction act = sig_default_action(sig);
- switch (act) {
- case SIG_ACT_IGN:
- continue;
- case SIG_ACT_TERM:
- case SIG_ACT_CORE:
- pr_debug_s(KLOG_SUBSYS_SIGNAL,
- "signal: pid %d killed by signal %d\n",
- curr->pid, sig);
- task_kill_current(sig);
- return; /* never reached */
- case SIG_ACT_STOP: {
- /*
- * Enter Stopped state. Record the encoded wstatus so
- * the parent's wait4(WUNTRACED) can observe and report.
- * The schedule() below does NOT re-enqueue Stopped
- * tasks — when SIGCONT eventually arrives, send_signal
- * flips us back to Runnable and re-enqueues.
- */
- curr->stopped_code = W_STOPCODE(sig);
- curr->state = TaskState::Stopped;
- if (curr->parent)
- curr->parent->wait_chld.wake_all();
- pr_debug_s(KLOG_SUBSYS_SIGNAL,
- "signal: pid %d stopped by sig %d\n",
- curr->pid, sig);
- schedule();
- /*
- * Resumed (either by SIGCONT or SIGKILL). Loop back to
- * deliver any remaining pending signals before returning
- * to user mode.
- */
- continue;
- }
- case SIG_ACT_CONT:
- /* SIGCONT's resume side-effect already happened in
- * send_signal(). The only thing left at handler time
- * is to consume the signal (no-op for SIG_DFL). */
- continue;
- }
- }
-
- /* User handler: construct signal frame */
- pr_debug_s(KLOG_SUBSYS_SIGNAL,
- "signal: pid %d delivering sig %d handler=%llx\n",
- curr->pid, sig, reinterpret_cast<uint64>(ka->sa_handler));
- int ret = setup_signal_frame(tf, sig, ka, &info);
- if (ret < 0) {
- pr_err("signal: failed to set up frame for sig %d\n", sig);
- task_kill_current(sig);
- return;
- }
-
- /* Post-delivery bookkeeping under siglock — both the per-task
- * blocked mask and the shared action table (SA_RESETHAND) are
- * touched, so we must hold the same lock the senders use. */
- {
- LockGuard guard(curr->sighand->siglock);
- if (!(ka->sa_flags & SA_NODEFER)) curr->blocked.add(sig);
- curr->blocked = curr->blocked | ka->sa_mask;
- if (ka->sa_flags & SA_RESETHAND) {
- curr->sighand->action[sig - 1].sa_handler = SIG_DFL;
- }
- }
-
- /* Only deliver one signal per return-to-user */
- break;
- }
-}
diff --git a/na/kernel/smp.cc b/na/kernel/smp.cc
deleted file mode 100644
index 1a79ac3..0000000
--- a/na/kernel/smp.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * smp.cc — SMP IPI implementation
- *
- * Sending side: atomically set reason bits in target CPU's IpiData,
- * then invoke arch-specific IPI delivery (SBI ecall on RISC-V).
- *
- * Receiving side: smp_handle_ipi() is called from the trap handler
- * when an S-mode software interrupt arrives. It atomically consumes
- * all pending reason bits and dispatches to the appropriate handler.
- *
- * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.2
- */
-
-#include <lume/smp.h>
-#include <lume/klog.h>
-#include <lume/sched.h>
-#include <arch/cpu.h>
-#include <arch/mmu.h>
-#include <arch/sbi.h>
-
-/* Per-CPU IPI data — BSS zero-initialized */
-IpiData g_ipi_data[kMaxCpus];
-
-/* ------------------------------------------------------------------ */
-/* smp_init */
-/* ------------------------------------------------------------------ */
-
-void smp_init()
-{
- for (uint32 i = 0; i < kMaxCpus; i++) {
- g_ipi_data[i].pending.store(0, __ATOMIC_RELAXED);
- g_ipi_data[i].tlb_start = 0;
- g_ipi_data[i].tlb_end = 0;
- }
-
- printk(KERN_INFO "smp: IPI framework initialized (%u CPUs max)\n",
- static_cast<unsigned>(kMaxCpus));
-}
-
-/* ------------------------------------------------------------------ */
-/* smp_send_ipi — send IPI to target CPUs */
-/* ------------------------------------------------------------------ */
-
-void smp_send_ipi(uint64 cpu_mask, uint32 reason)
-{
- if (cpu_mask == 0) return;
-
- /* Set pending bits on each target CPU's IpiData */
- for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) {
- if (cpu_mask & (1ULL << cpu)) {
- g_ipi_data[cpu].pending.fetch_or(reason, __ATOMIC_RELEASE);
- }
- }
-
- /* Deliver hardware interrupt via SBI.
- * RISC-V SBI IPI extension sends S-mode software interrupt
- * to the specified harts. hart_mask maps 1:1 to cpu_mask
- * (assuming hartid == cpu_id, which holds for QEMU virt). */
- arch::sbi::sbi_send_ipi(cpu_mask, 0);
-}
-
-/* ------------------------------------------------------------------ */
-/* smp_handle_ipi — called from trap handler on software interrupt */
-/* ------------------------------------------------------------------ */
-
-void smp_handle_ipi()
-{
- uint32 cpu = static_cast<uint32>(arch::cpu::id());
-
- /* Clear the S-mode software interrupt pending bit (sip.SSIP).
- * On RISC-V, we must clear this in software, otherwise the
- * interrupt will keep firing. */
- arch::cpu::clear_sip_ssip();
-
- /* Atomically consume all pending reasons */
- uint32 reasons = g_ipi_data[cpu].pending.exchange(0, __ATOMIC_ACQUIRE);
-
- if (reasons == 0) return; /* Spurious */
-
- /* Dispatch each reason */
-
- if (reasons & IPI_RESCHEDULE) {
- /* Just need to return from interrupt — schedule() will be
- * called on the return-from-trap path if need_resched is set.
- * For now, set the flag by yielding on next opportunity. */
- /* TODO: set need_resched flag when preemption tracking is added */
- }
-
- if (reasons & IPI_TLB_SHOOTDOWN) {
- /* Execute local TLB flush with the parameters the sender stored */
- uint64 start = g_ipi_data[cpu].tlb_start;
- uint64 end = g_ipi_data[cpu].tlb_end;
- if (start == 0 && end == 0) {
- arch::mmu::flush_tlb_all();
- } else {
- for (uint64 va = start; va < end; va += kPageSize) {
- arch::mmu::flush_tlb_page(va);
- }
- }
- }
-
- if (reasons & IPI_HALT) {
- printk(KERN_INFO "smp: CPU %u halting by IPI\n", cpu);
- arch::cpu::intr_off();
- while (true) {
- arch::cpu::halt_until_interrupt();
- }
- }
-}
diff --git a/na/kernel/sync/cpu_state.h b/na/kernel/sync/cpu_state.h
deleted file mode 100644
index 1f7dde0..0000000
--- a/na/kernel/sync/cpu_state.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-/*
- * Per-CPU Interrupt Nesting Tracker
- *
- * Tracks spinlock nesting depth on each CPU to ensure interrupts
- * are only restored when the last spinlock is released.
- *
- * BSS-segment global array; zero-initialized by entry.S BSS clear.
- * Reference: docs/specs/spinlock.md §3.1
- */
-
-#include <lume/types.h>
-#include <lume/config.h>
-
-namespace sync {
-
-struct CpuSyncState {
- int lock_depth; // Number of spinlocks held by this CPU
- bool intr_was_on; // Were interrupts enabled before the first lock?
-};
-
-inline constexpr uint64 kInvalidCpuId = ~0ULL;
-
-// BSS-segment global array, zero-initialized by entry.S
-extern CpuSyncState g_cpu_sync_states[kMaxCpus];
-
-// Internal API — called only by Spinlock::acquire/release
-void push_intr();
-void pop_intr();
-
-} // namespace sync
diff --git a/na/kernel/sync/mutex.cc b/na/kernel/sync/mutex.cc
deleted file mode 100644
index c4d3e9d..0000000
--- a/na/kernel/sync/mutex.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Mutex — Linux-style sleeping mutex (no PI).
- *
- * Implementation notes (mirroring kernel/locking/mutex.c, simplified):
- *
- * - Fast path (uncontended): wait_lock_ -> see owner_==null -> claim -> unlock.
- * - Slow path (contended): enqueue MutexWaiter on stack, sleep, race for
- * ownership after each wake. unlock() wakes only the head waiter.
- * - No optimistic spin, no handoff anti-starvation, no interruptible wait.
- * These belong to follow-up work (see mutex.md §5).
- *
- * Reference: docs/specs/mutex.md
- */
-
-#include <lume/mutex.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/klog.h>
-#include <arch/cpu.h>
-
-namespace {
-
-struct MutexWaiter {
- list_node link;
- TaskControlBlock* task;
-};
-
-} // namespace
-
-/* ------------------------------------------------------------------ */
-/* init */
-/* ------------------------------------------------------------------ */
-
-void Mutex::init(const char* name)
-{
- owner_ = nullptr;
- name_ = name;
- wait_lock_.init(name != nullptr ? name : "mutex");
- wait_list_.init();
-}
-
-/* ------------------------------------------------------------------ */
-/* try_lock — non-blocking, safe from any context */
-/* ------------------------------------------------------------------ */
-
-bool Mutex::try_lock()
-{
- LockGuard g(wait_lock_);
- if (owner_ != nullptr) {
- return false;
- }
- owner_ = current_task();
- return true;
-}
-
-/* ------------------------------------------------------------------ */
-/* lock — blocking, must be called in schedulable context */
-/* ------------------------------------------------------------------ */
-
-void Mutex::lock()
-{
- TaskControlBlock* curr = current_task();
-
- if (curr == nullptr) {
- kernel_panic("Mutex::lock: no current task");
- }
- if (curr->preempt_count > 0) {
- kernel_panic("Mutex::lock: preemption disabled (holding spinlock?)");
- }
-
- wait_lock_.acquire();
-
- if (owner_ == nullptr) {
- owner_ = curr;
- wait_lock_.release();
- return;
- }
-
- if (owner_ == curr) {
- wait_lock_.release();
- kernel_panic("Mutex::lock: recursive acquire by same task");
- }
-
- /* Slow path: enqueue ourselves and sleep until we are head + lock free. */
- MutexWaiter waiter;
- waiter.link.init();
- waiter.task = curr;
- list_add_tail(&waiter.link, &wait_list_);
-
- for (;;) {
- /* Set state inside wait_lock_ so unlock-side sees a coherent view:
- * either we are on the list AND Sleeping (will be woken), or we
- * already took ownership above (not in this loop). */
- curr->state = TaskState::Sleeping;
- wait_lock_.release();
-
- schedule();
-
- wait_lock_.acquire();
-
- if (owner_ == nullptr && wait_list_.next == &waiter.link) {
- owner_ = curr;
- list_del_init(&waiter.link);
- wait_lock_.release();
- return;
- }
- /* Spurious wake / lost race — keep waiting. */
- }
-}
-
-/* ------------------------------------------------------------------ */
-/* unlock */
-/* ------------------------------------------------------------------ */
-
-void Mutex::unlock()
-{
- TaskControlBlock* curr = current_task();
-
- wait_lock_.acquire();
-
- if (owner_ != curr) {
- wait_lock_.release();
- kernel_panic("Mutex::unlock: not the owner");
- }
-
- owner_ = nullptr;
-
- if (!wait_list_.is_empty()) {
- list_node* first = wait_list_.next;
- MutexWaiter* w = list_entry<MutexWaiter, &MutexWaiter::link>(first);
- TaskControlBlock* victim = w->task;
-
- /* Race-tolerant wake: only enqueue if still sleeping. If the
- * waiter is already Runnable (e.g. it was already woken by a
- * timer-driven schedule reentry and didn't yet retry the lock),
- * sched_enqueue would double-link it. */
- if (victim->state == TaskState::Sleeping) {
- victim->state = TaskState::Runnable;
- sched_enqueue(victim);
- }
- }
-
- wait_lock_.release();
-}
-
-/* ------------------------------------------------------------------ */
-/* Debug accessors */
-/* ------------------------------------------------------------------ */
-
-bool Mutex::is_owned_by_current() const
-{
- return owner_ == current_task();
-}
diff --git a/na/kernel/sync/spinlock.cc b/na/kernel/sync/spinlock.cc
deleted file mode 100644
index f649fe7..0000000
--- a/na/kernel/sync/spinlock.cc
+++ /dev/null
@@ -1,120 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Spinlock Implementation
- *
- * TTAS (Test-and-Test-and-Set) spinlock with:
- * - Acquire: __ATOMIC_ACQUIRE memory ordering
- * - Release: __ATOMIC_RELEASE memory ordering
- * - Automatic interrupt disable/enable via push_intr/pop_intr
- * - Dead-lock and invalid-release detection via kernel_panic
- *
- * Reference: docs/specs/spinlock.md
- */
-
-#include "spinlock.h"
-#include "cpu_state.h"
-#include <arch/cpu.h>
-#include <lume/klog.h>
-#include <lume/task.h>
-
-// ============================================================
-// Per-CPU sync state (BSS zero-initialized)
-// ============================================================
-namespace sync {
-
-CpuSyncState g_cpu_sync_states[kMaxCpus];
-
-void push_intr() {
- bool was_on = arch::cpu::intr_enabled();
- arch::cpu::intr_off();
-
- auto& state = g_cpu_sync_states[arch::cpu::id()];
- if (state.lock_depth == 0) {
- state.intr_was_on = was_on;
- }
- state.lock_depth++;
-}
-
-void pop_intr() {
- auto& state = g_cpu_sync_states[arch::cpu::id()];
- state.lock_depth--;
-
- if (state.lock_depth == 0 && state.intr_was_on) {
- arch::cpu::intr_on();
- }
-}
-
-} // namespace sync
-
-// ============================================================
-// Spinlock methods
-// ============================================================
-
-void Spinlock::init(const char* name) {
- name_ = name;
- locked_.store(0, __ATOMIC_RELAXED);
- cpu_id_ = sync::kInvalidCpuId;
-}
-
-void Spinlock::acquire() {
-#ifdef DEBUG
- // Optional: detect interrupt-state leakage
- if (arch::cpu::intr_enabled()) {
- auto& state = sync::g_cpu_sync_states[arch::cpu::id()];
- if (state.lock_depth > 0) {
- kernel_panic("spinlock: interrupts enabled while holding lock");
- }
- }
-#endif
-
- // Step 1: disable interrupts, track nesting depth
- sync::push_intr();
-
- // Step 2: dead-lock detection (same CPU re-acquiring)
- if (is_held_by_current_cpu()) {
- kernel_panic("spinlock: double lock", name_);
- }
-
- // Step 3: TTAS spin loop
- while (true) {
- // Outer: spin on local cache copy (no bus traffic)
- while (locked_.load(__ATOMIC_RELAXED))
- ;
- // Inner: attempt atomic exchange with acquire semantics
- if (locked_.exchange(1, __ATOMIC_ACQUIRE) == 0)
- break;
- }
-
- // Step 4: record owner
- cpu_id_ = arch::cpu::id();
-
- // Step 5: bump preempt_count — prevents schedule() from preempting
- TaskControlBlock* curr = current_task();
- if (curr) curr->preempt_count++;
-}
-
-void Spinlock::release() {
- // Step 1: verify caller is the holder
- if (!is_held_by_current_cpu()) {
- kernel_panic("spinlock: invalid release", name_);
- }
-
- // Step 2: clear owner
- cpu_id_ = sync::kInvalidCpuId;
-
- // Step 3: release the lock with release semantics
- locked_.store(0, __ATOMIC_RELEASE);
-
- // Step 4: drop preempt_count
- TaskControlBlock* curr = current_task();
- if (curr) curr->preempt_count--;
-
- // Step 5: restore interrupt state if this was the last lock
- sync::pop_intr();
-}
-
-bool Spinlock::is_held_by_current_cpu() const {
- return cpu_id_ == arch::cpu::id();
-}
diff --git a/na/kernel/sync/spinlock.h b/na/kernel/sync/spinlock.h
deleted file mode 100644
index a1369d1..0000000
--- a/na/kernel/sync/spinlock.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-/*
- * Spinlock & LockGuard
- *
- * Provides mutual exclusion for SMP kernels using TTAS
- * (Test-and-Test-and-Set) with acquire/release memory ordering.
- * Interrupt disable/enable is managed automatically via push_intr/pop_intr.
- *
- * Reference: docs/specs/spinlock.md
- */
-
-#include <lume/atomic.h>
-#include <lume/types.h>
-#include "cpu_state.h"
-
-class Spinlock {
-public:
- // Default: BSS zero is safe. Call init() to give the lock a name.
- Spinlock() = default;
-
- // Explicit initialization (for global objects or Placement New)
- void init(const char* name);
-
- // Non-copyable, non-movable (lock identity is its address)
- Spinlock(const Spinlock&) = delete;
- Spinlock& operator=(const Spinlock&) = delete;
- Spinlock(Spinlock&&) = delete;
- Spinlock& operator=(Spinlock&&) = delete;
-
- void acquire();
- void release();
- bool is_held_by_current_cpu() const;
-
-private:
- lume::atomic<uint32> locked_; // 0 = free, 1 = held
- const char* name_ = "uninit"; // Lock name (for panic messages)
- uint64 cpu_id_ = sync::kInvalidCpuId; // Holder CPU ID
-};
-
-class LockGuard {
-public:
- explicit LockGuard(Spinlock& lock) : lock_(lock) {
- lock_.acquire();
- }
-
- ~LockGuard() {
- lock_.release();
- }
-
- LockGuard(const LockGuard&) = delete;
- LockGuard& operator=(const LockGuard&) = delete;
- LockGuard(LockGuard&&) = delete;
- LockGuard& operator=(LockGuard&&) = delete;
-
-private:
- Spinlock& lock_;
-};
diff --git a/na/kernel/sys_fs.cc b/na/kernel/sys_fs.cc
deleted file mode 100644
index 1d26fdd..0000000
--- a/na/kernel/sys_fs.cc
+++ /dev/null
@@ -1,679 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * sys_fs.cc — Filesystem-related syscall handlers
- *
- * Implements: openat, close, read, write, lseek, fstat, getdents64,
- * dup, dup3, getcwd, chdir, mkdirat, unlinkat
- */
-
-#include <lume/syscall.h>
-#include <lume/syscall_nr.h>
-#include <lume/errno.h>
-#include <lume/console.h>
-#include <lume/task.h>
-#include <lume/fs.h>
-#include <lume/file.h>
-#include <lume/dcache.h>
-#include <lume/namei.h>
-#include <lume/fdtable.h>
-#include <lume/fs_struct.h>
-#include <lume/mount.h>
-#include <lume/fs_types.h>
-#include <lume/klog.h>
-
-/* ================================================================== */
-/* Helpers */
-/* ================================================================== */
-
-static files_struct* current_files()
-{
- TaskControlBlock* t = current_task();
- return t ? t->files : nullptr;
-}
-
-static fs_struct* current_fs()
-{
- TaskControlBlock* t = current_task();
- return t ? t->fs : nullptr;
-}
-
-/* ================================================================== */
-/* sys_openat(dirfd, pathname, flags, mode) */
-/* ================================================================== */
-
-int64 sys_openat(uint64 a0, uint64 a1, uint64 a2,
- uint64 a3, uint64 /*a4*/, uint64 /*a5*/)
-{
- int dirfd = static_cast<int>(a0);
- const char* pathname = reinterpret_cast<const char*>(a1);
- int flags = static_cast<int>(a2);
- uint32 mode = static_cast<uint32>(a3);
-
- files_struct* files = current_files();
- if (!files) return -EMFILE;
-
-
- /* Pathname lookup */
- path p{};
- int err = filename_lookup(dirfd, pathname, 0, &p);
-
- if (err == -ENOENT && (flags & O_CREAT)) {
- /* Create: look up the parent directory */
- /* Find last '/' to split parent path / filename */
- const char* last_slash = nullptr;
- for (const char* s = pathname; *s; s++) {
- if (*s == '/') last_slash = s;
- }
-
- if (!last_slash || last_slash == pathname) {
- /* File in root or current directory */
- path parent{};
- if (last_slash == pathname) {
- /* Absolute: parent is root */
- parent.de = g_root_dentry;
- parent.mnt = g_root_mnt;
- } else {
- /* Relative: parent is cwd */
- fs_struct* fs = current_fs();
- if (!fs) return -ENOENT;
- parent = fs->pwd;
- }
-
- const char* name = last_slash ? last_slash + 1 : pathname;
- inode* dir_ip = parent.de->d_inode;
- if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->create) {
- return -EACCES;
- }
-
- /* Create the inode via the directory's i_op->create */
- qstr q{};
- q.name = name;
- q.len = 0;
- for (const char* s = name; *s; s++) q.len++;
- dentry* new_de = d_alloc(parent.de, &q);
- if (!new_de) return -ENOMEM;
-
-
- err = dir_ip->i_op->create(dir_ip, new_de, mode & ~S_IFMT, false);
- if (err) {
- /* TODO: free new_de */
- return err;
- }
-
- p.de = new_de;
- p.mnt = parent.mnt;
- } else {
- /* TODO: multi-component parent lookup */
- return -ENOENT;
- }
- } else if (err) {
- return err;
- }
-
- /* Allocate fd */
- int fd = alloc_fd(files, 0);
- if (fd < 0) {
- return fd;
- }
-
- /* Allocate file object */
- file* fp = alloc_empty_file(flags);
- if (!fp) {
- /* TODO: release fd */
- return -ENFILE;
- }
-
- fp->f_inode = p.de->d_inode;
- fp->f_path = p;
- fp->f_op = p.de->d_inode->i_fop;
- fp->f_pos = 0;
-
- /* Chardev: look up cdev by i_rdev and replace f_op */
- if (S_ISCHR(fp->f_inode->i_mode)) {
- extern int devfs_chrdev_open(inode* ip, file* fp);
- err = devfs_chrdev_open(fp->f_inode, fp);
- if (err) {
- fput(fp);
- return err;
- }
- } else if (fp->f_op && fp->f_op->open) {
- err = fp->f_op->open(fp->f_inode, fp);
- if (err) {
- fput(fp);
- return err;
- }
- }
-
- /* O_TRUNC for regular files */
- if ((flags & O_TRUNC) && S_ISREG(fp->f_inode->i_mode)) {
- fp->f_inode->i_size = 0;
- }
-
- fd_install(files, fd, fp);
- return fd;
-}
-
-/* ================================================================== */
-/* sys_close(fd) */
-/* ================================================================== */
-
-int64 sys_close(uint64 a0, uint64 /*a1*/, uint64 /*a2*/,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- files_struct* files = current_files();
- if (!files) return -EBADF;
- return close_fd(files, fd);
-}
-
-/* ================================================================== */
-/* sys_read(fd, buf, count) */
-/* ================================================================== */
-
-int64 sys_read(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- char* buf = reinterpret_cast<char*>(a1);
- uint64 count = a2;
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
-
- if (!(fp->f_mode & FMODE_READ)) return -EBADF;
-
- return vfs_read(fp, buf, count, &fp->f_pos);
-}
-
-/* ================================================================== */
-/* sys_write(fd, buf, count) */
-/* ================================================================== */
-
-int64 sys_write(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- const char* buf = reinterpret_cast<const char*>(a1);
- uint64 count = a2;
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
-
- if (!(fp->f_mode & FMODE_WRITE)) return -EBADF;
-
- return vfs_write(fp, buf, count, &fp->f_pos);
-}
-
-/* ================================================================== */
-/* sys_lseek(fd, offset, whence) */
-/* ================================================================== */
-
-int64 sys_lseek(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- int64 offset = static_cast<int64>(a1);
- int whence = static_cast<int>(a2);
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
-
- return vfs_llseek(fp, offset, whence);
-}
-
-/* ================================================================== */
-/* sys_fstat(fd, statbuf) */
-/* ================================================================== */
-
-int64 sys_fstat_wrapper(uint64 a0, uint64 a1, uint64 /*a2*/,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- auto* statbuf = reinterpret_cast<kstat*>(a1);
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
-
- return vfs_fstat(fp, statbuf);
-}
-
-/* ================================================================== */
-/* sys_getdents64(fd, dirp, count) */
-/* ================================================================== */
-
-struct linux_dirent64 {
- uint64 d_ino;
- int64 d_off;
- uint16 d_reclen;
- uint8 d_type;
- char d_name[];
-};
-
-struct getdents_ctx {
- dir_context ctx;
- char* buf;
- int count;
- int written;
- int error;
-};
-
-static bool filldir64(dir_context* ctx, const char* name, int namelen,
- int64 offset, uint64 ino, unsigned int d_type)
-{
- auto* gctx = container_of(ctx, getdents_ctx, ctx);
-
- /* Calculate record length (rounded up to 8 bytes) */
- int reclen = static_cast<int>(
- (sizeof(linux_dirent64) + namelen + 1 + 7) & ~7ULL);
-
- if (gctx->written + reclen > gctx->count) {
- return false; /* Buffer full */
- }
-
- auto* dirent = reinterpret_cast<linux_dirent64*>(
- gctx->buf + gctx->written);
-
- dirent->d_ino = ino;
- dirent->d_off = offset + 1;
- dirent->d_reclen = static_cast<uint16>(reclen);
- dirent->d_type = static_cast<uint8>(d_type);
-
- /* Copy name */
- for (int i = 0; i < namelen; i++) {
- dirent->d_name[i] = name[i];
- }
- dirent->d_name[namelen] = '\0';
-
- gctx->written += reclen;
- return true;
-}
-
-int64 sys_getdents64(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- char* buf = reinterpret_cast<char*>(a1);
- int count = static_cast<int>(a2);
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
-
- if (!fp->f_op || !fp->f_op->iterate_shared) {
- return -ENOTDIR;
- }
-
- getdents_ctx gctx{};
- gctx.ctx.actor = filldir64;
- gctx.ctx.pos = fp->f_pos;
- gctx.buf = buf;
- gctx.count = count;
- gctx.written = 0;
- gctx.error = 0;
-
- fp->f_op->iterate_shared(fp, &gctx.ctx);
- fp->f_pos = gctx.ctx.pos;
-
- return gctx.written;
-}
-
-/* ================================================================== */
-/* sys_dup(oldfd) */
-/* ================================================================== */
-
-int64 sys_dup(uint64 a0, uint64 /*a1*/, uint64 /*a2*/,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int oldfd = static_cast<int>(a0);
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, oldfd);
- if (!fp) return -EBADF;
-
- int newfd = alloc_fd(files, 0);
- if (newfd < 0) return newfd;
-
- get_file(fp);
- fd_install(files, newfd, fp);
- return newfd;
-}
-
-/* ================================================================== */
-/* sys_dup3(oldfd, newfd, flags) */
-/* ================================================================== */
-
-int64 sys_dup3(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int oldfd = static_cast<int>(a0);
- int newfd = static_cast<int>(a1);
- int flags = static_cast<int>(a2);
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- return do_dup2(files, oldfd, newfd, flags);
-}
-
-/* ================================================================== */
-/* sys_getcwd(buf, size) */
-/* ================================================================== */
-
-int64 sys_getcwd(uint64 a0, uint64 a1, uint64 /*a2*/,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- char* buf = reinterpret_cast<char*>(a0);
- uint64 size = a1;
-
- if (!buf || size == 0) return -EINVAL;
-
- fs_struct* fs = current_fs();
- if (!fs) return -ENOENT;
-
- char tmp[256];
- char* result = d_path(&fs->pwd, tmp, sizeof(tmp));
- if (!result) return -ERANGE;
-
- /* Calculate length */
- int len = 0;
- for (const char* s = result; *s; s++) len++;
- len++; /* Include NUL */
-
- if (static_cast<uint64>(len) > size) return -ERANGE;
-
- for (int i = 0; i < len; i++) {
- buf[i] = result[i];
- }
-
- return len;
-}
-
-/* ================================================================== */
-/* sys_chdir(pathname) */
-/* ================================================================== */
-
-int64 sys_chdir(uint64 a0, uint64 /*a1*/, uint64 /*a2*/,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- const char* pathname = reinterpret_cast<const char*>(a0);
-
- path p{};
- int err = filename_lookup(AT_FDCWD, pathname, LOOKUP_DIRECTORY, &p);
- if (err) return err;
-
- if (!S_ISDIR(p.de->d_inode->i_mode)) {
- path_put(&p);
- return -ENOTDIR;
- }
-
- fs_struct* fs = current_fs();
- if (!fs) {
- path_put(&p);
- return -ENOENT;
- }
-
- set_fs_pwd(fs, &p);
- path_put(&p);
- return 0;
-}
-
-/* ================================================================== */
-/* sys_mkdirat(dirfd, pathname, mode) */
-/* ================================================================== */
-
-int64 sys_mkdirat(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int dirfd = static_cast<int>(a0);
- const char* pathname = reinterpret_cast<const char*>(a1);
- uint32 mode = static_cast<uint32>(a2);
-
- /* Walk to the parent directory */
- /* For simplicity, support only "single component" paths for now */
- path parent{};
- int err;
-
- if (pathname[0] == '/') {
- parent.de = g_root_dentry;
- parent.mnt = g_root_mnt;
- pathname++; /* skip leading / */
- } else {
- fs_struct* fs = current_fs();
- if (!fs) return -ENOENT;
- parent = fs->pwd;
- (void)dirfd; /* TODO: respect dirfd for AT_FDCWD check */
- }
-
- inode* dir_ip = parent.de->d_inode;
- if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mkdir) {
- return -EPERM;
- }
-
- qstr q{};
- q.name = pathname;
- q.len = 0;
- for (const char* s = pathname; *s; s++) q.len++;
- dentry* new_de = d_alloc(parent.de, &q);
- if (!new_de) return -ENOMEM;
-
- err = dir_ip->i_op->mkdir(dir_ip, new_de, mode | S_IFDIR);
- if (err) {
- return err;
- }
-
- return 0;
-}
-
-/* ================================================================== */
-/* sys_unlinkat(dirfd, pathname, flags) */
-/* ================================================================== */
-
-int64 sys_unlinkat(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- (void)a0; (void)a1; (void)a2;
- /* TODO: implement unlink/rmdir */
- return -ENOSYS;
-}
-
-/* ================================================================== */
-/* sys_writev(fd, iov, iovcnt) */
-/* ================================================================== */
-
-struct iovec {
- void* iov_base;
- uint64 iov_len;
-};
-
-int64 sys_writev(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- const iovec* iov = reinterpret_cast<const iovec*>(a1);
- int iovcnt = static_cast<int>(a2);
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
- if (!(fp->f_mode & FMODE_WRITE)) return -EBADF;
-
- int64 total = 0;
- for (int i = 0; i < iovcnt; i++) {
- if (iov[i].iov_len == 0) continue;
- int64 ret = vfs_write(fp, static_cast<const char*>(iov[i].iov_base),
- iov[i].iov_len, &fp->f_pos);
- if (ret < 0) {
- return total > 0 ? total : ret;
- }
- total += ret;
- if (static_cast<uint64>(ret) < iov[i].iov_len) break;
- }
- return total;
-}
-
-/* ================================================================== */
-/* sys_ioctl — dispatch to file_operations */
-/* ================================================================== */
-
-int64 sys_ioctl(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- unsigned int cmd = static_cast<unsigned int>(a1);
- uint64 arg = a2;
-
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->files) return -EBADF;
-
- file* fp = fcheck(curr->files, fd);
- if (!fp) return -EBADF;
-
- if (fp->f_op && fp->f_op->unlocked_ioctl)
- return fp->f_op->unlocked_ioctl(fp, cmd, arg);
-
- return -ENOTTY;
-}
-
-/* ================================================================== */
-/* sys_pipe2(fds, flags) */
-/* ================================================================== */
-
-#include <lume/pipe.h>
-
-int64 sys_pipe2(uint64 a0, uint64 a1, uint64 /*a2*/,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int* user_fds = reinterpret_cast<int*>(a0);
- int flags = static_cast<int>(a1);
-
- files_struct* files = current_files();
- if (!files) return -EMFILE;
-
- int fds[2];
- int err = do_pipe2(files, fds, flags);
- if (err) return err;
-
- user_fds[0] = fds[0];
- user_fds[1] = fds[1];
- return 0;
-}
-
-/* ================================================================== */
-/* sys_fcntl(fd, cmd, arg) */
-/* ================================================================== */
-
-/* fcntl commands — Linux ABI values */
-inline constexpr int F_DUPFD = 0;
-inline constexpr int F_GETFD = 1;
-inline constexpr int F_SETFD = 2;
-inline constexpr int F_GETFL = 3;
-inline constexpr int F_SETFL = 4;
-inline constexpr int F_DUPFD_CLOEXEC = 1030;
-
-inline constexpr int FD_CLOEXEC = 1;
-
-/* Bitmap helpers (duplicated from fd.cc — TODO: move to shared header) */
-static inline bool fcntl_bit_test(const uint64* bmap, int bit) {
- return (bmap[bit / 64] >> (bit % 64)) & 1;
-}
-static inline void fcntl_bit_set(uint64* bmap, int bit) {
- bmap[bit / 64] |= (1ULL << (bit % 64));
-}
-static inline void fcntl_bit_clear(uint64* bmap, int bit) {
- bmap[bit / 64] &= ~(1ULL << (bit % 64));
-}
-
-int64 sys_fcntl(uint64 a0, uint64 a1, uint64 a2,
- uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/)
-{
- int fd = static_cast<int>(a0);
- int cmd = static_cast<int>(a1);
- uint64 arg = a2;
-
- files_struct* files = current_files();
- if (!files) return -EBADF;
-
- file* fp = fcheck(files, fd);
- if (!fp) return -EBADF;
-
- switch (cmd) {
- case F_DUPFD:
- case F_DUPFD_CLOEXEC: {
- int new_fd = alloc_fd(files, static_cast<unsigned int>(arg));
- if (new_fd < 0) return new_fd;
- get_file(fp);
- fd_install(files, new_fd, fp);
- if (cmd == F_DUPFD_CLOEXEC) {
- LockGuard guard(files->file_lock);
- fcntl_bit_set(files->close_on_exec_init, new_fd);
- }
- return new_fd;
- }
-
- case F_GETFD: {
- LockGuard guard(files->file_lock);
- return fcntl_bit_test(files->close_on_exec_init, fd) ? FD_CLOEXEC : 0;
- }
-
- case F_SETFD: {
- LockGuard guard(files->file_lock);
- if (arg & FD_CLOEXEC)
- fcntl_bit_set(files->close_on_exec_init, fd);
- else
- fcntl_bit_clear(files->close_on_exec_init, fd);
- return 0;
- }
-
- case F_GETFL:
- return fp->f_flags;
-
- case F_SETFL:
- /* Only allow changing O_APPEND, O_NONBLOCK, O_ASYNC */
- fp->f_flags = (fp->f_flags & ~(O_APPEND | O_NONBLOCK)) |
- (static_cast<unsigned int>(arg) & (O_APPEND | O_NONBLOCK));
- return 0;
-
- default:
- return -EINVAL;
- }
-}
-
-/* ================================================================== */
-/* sys_socket (SYS_socket = 198) & sys_sendto (SYS_sendto = 206) */
-/* ================================================================== */
-
-int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- /*
- * COMPATIBILITY STUB:
- * LumeOS currently lacks a networking stack and Unix Domain Sockets (AF_UNIX).
- * We return -EAFNOSUPPORT (Address family not supported by protocol) as expected
- * by Linux applications (like BusyBox syslogd connecting to /dev/log).
- */
- return -EAFNOSUPPORT;
-}
-
-int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- /* COMPATIBILITY STUB */
- return -EAFNOSUPPORT;
-}
diff --git a/na/kernel/sys_info.cc b/na/kernel/sys_info.cc
deleted file mode 100644
index 79baab0..0000000
--- a/na/kernel/sys_info.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * System Information Calls
- *
- * Implements: uname
- *
- * Reference: docs/specs/syscall.md §7.3
- */
-
-#include <lume/types.h>
-#include <lume/errno.h>
-#include <lume/uaccess.h>
-
-/* Linux-compatible utsname structure (65-byte fields) */
-struct utsname {
- char sysname[65];
- char nodename[65];
- char release[65];
- char version[65];
- char machine[65];
- char domainname[65];
-};
-
-/* Simple safe string copy */
-static void safe_strncpy(char* dst, const char* src, uint64 n)
-{
- uint64 i = 0;
- for (; i < n - 1 && src[i] != '\0'; i++)
- dst[i] = src[i];
- dst[i] = '\0';
-}
-
-int64 sys_uname(uint64 buf_ptr, uint64, uint64, uint64, uint64, uint64)
-{
- utsname info = {};
- safe_strncpy(info.sysname, "LumeOS", sizeof(info.sysname));
- safe_strncpy(info.nodename, "lume", sizeof(info.nodename));
- safe_strncpy(info.release, "0.1.0", sizeof(info.release));
- safe_strncpy(info.version, "Phase 2", sizeof(info.version));
- safe_strncpy(info.machine, "riscv64", sizeof(info.machine));
- safe_strncpy(info.domainname, "(none)", sizeof(info.domainname));
-
- if (copy_to_user(buf_ptr, &info, sizeof(info)) < 0)
- return -EFAULT;
-
- return 0;
-}
diff --git a/na/kernel/sys_misc.cc b/na/kernel/sys_misc.cc
deleted file mode 100644
index 48eb47b..0000000
--- a/na/kernel/sys_misc.cc
+++ /dev/null
@@ -1,360 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Miscellaneous System Calls — busybox bootstrap set
- *
- * Each syscall follows the Linux kernel semantics exactly.
- * Reference: Linux kernel/sys.c, kernel/signal.c, kernel/time/posix-timers.c
- */
-
-#include <lume/types.h>
-#include <lume/task.h>
-#include <lume/errno.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/pid.h>
-#include <lume/pgrp.h>
-#include <lume/signal_types.h>
-#include <lume/tty.h>
-#include <lume/uaccess.h>
-#include <arch/cpu.h>
-
-/* ======================================================================
- * sys_reboot — Linux kernel/reboot.c
- *
- * Validates magic numbers and performs the requested action.
- * For now, we only support LINUX_REBOOT_CMD_CAD_ON/OFF (ignore)
- * and reject everything else with -EPERM (no CAP_SYS_BOOT).
- * ====================================================================== */
-
-static constexpr uint32 LINUX_REBOOT_MAGIC1 = 0xfee1dead;
-static constexpr uint32 LINUX_REBOOT_MAGIC2 = 672274793; /* 0x28121969 */
-static constexpr uint32 LINUX_REBOOT_MAGIC2A = 85072278;
-static constexpr uint32 LINUX_REBOOT_MAGIC2B = 369367448;
-static constexpr uint32 LINUX_REBOOT_MAGIC2C = 537993216;
-
-int64 sys_reboot(uint64 magic1, uint64 magic2, uint64 cmd,
- uint64, uint64, uint64)
-{
- /* Linux: kernel/reboot.c — validate magic numbers first */
- if (static_cast<uint32>(magic1) != LINUX_REBOOT_MAGIC1)
- return -EINVAL;
- if (static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2 &&
- static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2A &&
- static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2B &&
- static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2C)
- return -EINVAL;
-
- /* We don't have CAP_SYS_BOOT yet — reject with -EPERM.
- * This matches Linux behavior for unprivileged callers.
- * Busybox handles this gracefully and continues. */
- (void)cmd;
- printk(KERN_INFO "reboot: cmd=%llu (not implemented, returning -EPERM)\n", cmd);
- return -EPERM;
-}
-
-/* ======================================================================
- * sys_prctl — Linux kernel/sys.c
- *
- * Process control. Most options are optional for early boot.
- * We implement the minimum set busybox needs.
- * ====================================================================== */
-
-/* prctl option constants (from <linux/prctl.h>) */
-static constexpr int PR_SET_NAME = 15;
-static constexpr int PR_GET_NAME = 16;
-static constexpr int PR_SET_PDEATHSIG = 1;
-static constexpr int PR_GET_PDEATHSIG = 2;
-
-int64 sys_prctl(uint64 option, uint64 arg2, uint64 arg3,
- uint64 arg4, uint64 arg5, uint64)
-{
- switch (static_cast<int>(option)) {
- case PR_SET_PDEATHSIG:
- /* Linux: set signal delivered when parent dies.
- * Silently accept — we don't track pdeath_signal yet. */
- return 0;
- case PR_GET_PDEATHSIG: {
- /* Linux: return current pdeath_signal (0 = none) */
- int32 zero = 0;
- if (copy_to_user(arg2, &zero, sizeof(zero)) != 0)
- return -EFAULT;
- return 0;
- }
- case PR_SET_NAME: {
- /* Linux: set comm (task name), max 16 chars */
- /* TODO: copy_from_user the string */
- return 0;
- }
- case PR_GET_NAME: {
- /* Linux: get comm (task name) */
- TaskControlBlock* curr = current_task();
- if (!curr) return -ESRCH;
- const char* name = curr->name ? curr->name : "";
- uint64 len = 0;
- while (name[len] && len < 15) len++;
- char buf[16] = {};
- for (uint64 i = 0; i < len; i++) buf[i] = name[i];
- if (copy_to_user(arg2, buf, 16) != 0)
- return -EFAULT;
- return 0;
- }
- default:
- /* Unknown prctl — Linux returns -EINVAL for unrecognized options */
- return -EINVAL;
- }
-}
-
-/* ======================================================================
- * Session / Process-Group syscalls
- *
- * All four operate on the caller's signal_struct (or a target task's).
- * Identity lives in signal_struct, so these become straightforward reads
- * and pgrp-hash updates. We mirror Linux kernel/sys.c semantics closely.
- * ====================================================================== */
-
-static TaskControlBlock* find_task_by_pid(int32 pid)
-{
- extern list_node g_all_tasks;
- extern Spinlock g_task_list_lock;
- LockGuard guard(g_task_list_lock);
- list_node* node = g_all_tasks.next;
- while (node != &g_all_tasks) {
- auto* task = list_entry<TaskControlBlock, &TaskControlBlock::all_link>(node);
- if (task->pid == pid) return task;
- node = node->next;
- }
- return nullptr;
-}
-
-/*
- * sys_setsid — Linux kernel/sys.c:ksys_setsid()
- *
- * Caller becomes leader of a new session+pgrp. Refused if a pgrp with
- * id == caller pid already exists with members other than the caller —
- * that's Linux's "process group id already exists" check, which prevents
- * collisions when a non-leader tries to setsid.
- */
-int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ESRCH;
-
- /* Detect collision: another pgrp already uses our pid as pgid. */
- bool conflict = false;
- for_each_pgrp_member(curr->pid, [&](signal_struct* sig) {
- if (sig != curr->signal) conflict = true;
- });
- if (conflict) return -EPERM;
-
- /* Move to new pgrp (creates one if missing), update sid, drop ctty. */
- change_pgid(curr->signal, curr->pid);
-
- /* Detach the controlling terminal. If this task is the session
- * leader that owns the tty (tty->session == caller's old sid),
- * also clear the tty's session/pgrp so the next session-leader
- * that opens the device can claim it. Without this, tty->session
- * permanently sticks to the abandoned sid and every subsequent
- * tty_acquire_ctty() bails with "owned by another session" —
- * which is exactly what kept BusyBox's child sh from picking the
- * console back up after init's setsid().
- *
- * Linux's behavior is similar in spirit (disassociate_ctty when
- * the leader exits), but it triggers from exit, not setsid. Our
- * simpler "clear-on-give-up" matches the way our acquisition path
- * is written. */
- tty_struct* old_tty = nullptr;
- int32 old_sid = 0;
- {
- LockGuard guard(curr->signal->lock);
- old_tty = curr->signal->tty;
- old_sid = curr->signal->sid;
- curr->signal->sid = curr->pid;
- curr->signal->tty = nullptr;
- curr->signal->tty_old_pgrp = 0;
- }
- if (old_tty) {
- LockGuard guard(old_tty->lock);
- if (old_tty->session == old_sid) {
- old_tty->session = 0;
- old_tty->pgrp = 0;
- }
- }
- return curr->pid;
-}
-
-/*
- * sys_setpgid — Linux kernel/sys.c:ksys_setpgid()
- *
- * Move a process between groups within the same session. Allowed:
- * - caller targeting self or a child
- * - target is not a session leader
- * - target stays in caller's session
- * - target hasn't exec()'d (we don't track did_exec yet, so skip)
- * - new pgid is either == target's pid (new group) or names a pgrp
- * that already exists in the same session
- */
-int64 sys_setpgid(uint64 pid, uint64 pgid, uint64, uint64, uint64, uint64)
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ESRCH;
-
- int32 target_pid = static_cast<int32>(pid);
- int32 target_pgid = static_cast<int32>(pgid);
-
- if (target_pid == 0) target_pid = curr->pid;
- if (target_pgid == 0) target_pgid = target_pid;
- if (target_pgid < 0) return -EINVAL;
-
- TaskControlBlock* target = (target_pid == curr->pid)
- ? curr
- : find_task_by_pid(target_pid);
- if (!target || !target->signal) return -ESRCH;
-
- /* Target must be caller or a child of caller. */
- if (target != curr && target->parent != curr) return -ESRCH;
-
- /* Same session as caller. */
- if (target->signal->sid != curr->signal->sid) return -EPERM;
-
- /* Cannot move a session leader into a different pgrp. */
- if (target->signal->sid == target->pid) return -EPERM;
-
- /* If target_pgid != target_pid, the pgrp must already exist in
- * the same session. pgrp existence == any signal_struct with
- * pgid == target_pgid AND sid == caller's sid. */
- if (target_pgid != target->pid) {
- bool found = false;
- for_each_pgrp_member(target_pgid, [&](signal_struct* sig) {
- if (sig->sid == curr->signal->sid) found = true;
- });
- if (!found) return -EPERM;
- }
-
- change_pgid(target->signal, target_pgid);
- return 0;
-}
-
-/*
- * sys_getpgid — return process group ID of `pid` (0 = self).
- */
-int64 sys_getpgid(uint64 pid, uint64, uint64, uint64, uint64, uint64)
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ESRCH;
-
- if (pid == 0) return curr->signal->pgid;
-
- TaskControlBlock* target = find_task_by_pid(static_cast<int32>(pid));
- if (!target || !target->signal) return -ESRCH;
- return target->signal->pgid;
-}
-
-/*
- * sys_getsid — return session ID of `pid` (0 = self).
- */
-int64 sys_getsid(uint64 pid, uint64, uint64, uint64, uint64, uint64)
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->signal) return -ESRCH;
-
- if (pid == 0) return curr->signal->sid;
-
- TaskControlBlock* target = find_task_by_pid(static_cast<int32>(pid));
- if (!target || !target->signal) return -ESRCH;
-
- /* Linux: -EPERM if target's session differs from caller's and we're
- * in a different namespace. We have one namespace, so always allow. */
- return target->signal->sid;
-}
-
-/* ======================================================================
- * sys_geteuid / sys_getegid — Linux kernel/sys.c
- *
- * Return effective UID/GID. We run everything as root (0).
- * ====================================================================== */
-
-int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- /* Linux: return current_euid(). We are always root. */
- return 0;
-}
-
-int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- /* Linux: return current_egid(). We are always root. */
- return 0;
-}
-
-/* ======================================================================
- * sys_sigaltstack — Linux kernel/signal.c
- *
- * Set/get alternate signal stack. For now, accept and ignore
- * (busybox doesn't critically need it for shell operation).
- * ====================================================================== */
-
-int64 sys_sigaltstack(uint64 uss_ptr, uint64 uoss_ptr,
- uint64, uint64, uint64, uint64)
-{
- /* Linux: if uoss != NULL, return current alt stack info.
- * We don't have alt stacks yet — report SS_DISABLE. */
- if (uoss_ptr != 0) {
- /* stack_t { void* ss_sp; int ss_flags; size_t ss_size; }
- * On RISC-V LP64: 8 + 4 + padding + 8 = 24 bytes typically,
- * but the ABI packs it as {uint64, int32, pad, uint64}. */
- struct {
- uint64 ss_sp;
- int32 ss_flags;
- int32 _pad;
- uint64 ss_size;
- } oss = { 0, 2 /* SS_DISABLE */, 0, 0 };
- if (copy_to_user(uoss_ptr, &oss, sizeof(oss)) != 0)
- return -EFAULT;
- }
-
- /* uss: silently accept (we'll need it for proper signal handling later) */
- (void)uss_ptr;
- return 0;
-}
-
-/* ======================================================================
- * sys_clock_gettime — Linux kernel/time/posix-timers.c
- *
- * Get the current time for a given clock.
- * We use the RISC-V cycle counter as a monotonic source.
- * ====================================================================== */
-
-static constexpr int CLOCK_REALTIME = 0;
-static constexpr int CLOCK_MONOTONIC = 1;
-
-int64 sys_clock_gettime(uint64 clk_id, uint64 tp_ptr,
- uint64, uint64, uint64, uint64)
-{
- if (tp_ptr == 0) return -EFAULT;
-
- /* Read RISC-V time CSR (mtime mapped to SBI) */
- uint64 ticks = arch::cpu::read_time();
-
- /* Convert to seconds + nanoseconds.
- * QEMU virt timebase-frequency is 10MHz. */
- constexpr uint64 kTimebaseHz = 10'000'000ULL;
- uint64 sec = ticks / kTimebaseHz;
- uint64 nsec = (ticks % kTimebaseHz) * (1'000'000'000ULL / kTimebaseHz);
-
- struct timespec64 {
- int64 tv_sec;
- int64 tv_nsec;
- };
-
- timespec64 ts;
- ts.tv_sec = static_cast<int64>(sec);
- ts.tv_nsec = static_cast<int64>(nsec);
-
- (void)clk_id; /* We treat all clocks as monotonic for now */
-
- if (copy_to_user(tp_ptr, &ts, sizeof(ts)) != 0)
- return -EFAULT;
-
- return 0;
-}
diff --git a/na/kernel/sys_mm.cc b/na/kernel/sys_mm.cc
deleted file mode 100644
index bfa067c..0000000
--- a/na/kernel/sys_mm.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Memory Management System Calls
- *
- * Implements: brk, mmap (anonymous only), munmap, mprotect.
- *
- * Reference: docs/specs/syscall.md §7.2
- */
-
-#include <lume/types.h>
-#include <lume/task.h>
-#include <lume/vmm.h>
-#include <lume/errno.h>
-#include <lume/mman.h>
-#include <lume/config.h>
-#include <lume/klog.h>
-
-
-/* Page alignment helpers */
-static inline uint64 page_align_up(uint64 x)
-{
- return (x + kPageSize - 1) & ~(kPageSize - 1);
-}
-
-static inline uint64 page_align_down(uint64 x)
-{
- return x & ~(kPageSize - 1);
-}
-
-/* ======================================================================
- * sys_brk
- * ====================================================================== */
-
-int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64)
-{
- VmSpace* mm = current_task()->mm;
- if (!mm)
- return -ENOMEM; /* kernel threads have no user address space */
-
- if (new_brk == 0) {
- return static_cast<int64>(mm->brk_end);
- }
-
- if (mm->brk_start == 0) {
- uint64 init_brk = page_align_up(new_brk);
- mm->brk_start = init_brk;
- mm->brk_end = init_brk;
- }
-
- if (new_brk < mm->brk_start) {
- return -EINVAL;
- }
-
- uint64 new_end = page_align_up(new_brk);
- if (new_end > mm->brk_end) {
- uint64 grow = new_end - mm->brk_end;
- int ret = vmm_map_user(mm, mm->brk_end, grow, VM_READ | VM_WRITE);
- if (ret < 0) {
- return ret;
- }
- } else if (new_end < mm->brk_end) {
- uint64 shrink = mm->brk_end - new_end;
- int ret = vmm_unmap_user(mm, new_end, shrink);
- if (ret < 0) {
- return ret;
- }
- }
-
- mm->brk_end = new_end;
- return static_cast<int64>(new_brk);
-}
-
-/* ======================================================================
- * sys_mmap
- * ====================================================================== */
-
-int64 sys_mmap(uint64 addr, uint64 length, uint64 prot,
- uint64 flags, uint64 fd, uint64 offset)
-{
- printk("[mmap] addr=%llx len=%llx prot=%llx flags=%llx fd=%lld off=%llx\n",
- addr, length, prot, flags, static_cast<int64>(fd), offset);
-
- if (length == 0) return -EINVAL;
- length = page_align_up(length);
- if (offset % kPageSize != 0) return -EINVAL;
-
- if ((flags & MAP_PRIVATE) && (flags & MAP_SHARED)) return -EINVAL;
- if (!(flags & MAP_PRIVATE) && !(flags & MAP_SHARED)) return -EINVAL;
-
- VmSpace* mm = current_task()->mm;
- if (!mm) return -ENOMEM;
-
- /* Phase 2: only anonymous private mappings */
- if (!(flags & MAP_ANONYMOUS)) {
- printk("[mmap] REJECTED: file-backed mmap not implemented\n");
- return -ENOSYS; /* File-backed mmap is Phase 3 */
- }
- if (flags & MAP_SHARED)
- return -ENOSYS; /* Shared mappings are Phase 3 */
-
- (void)fd;
-
- /* Convert POSIX prot → MI permission flags */
- uint64 vm_perm = 0;
- if (prot & PROT_READ) vm_perm |= VM_READ;
- if (prot & PROT_WRITE) vm_perm |= VM_WRITE;
- if (prot & PROT_EXEC) vm_perm |= VM_EXEC;
- vm_perm |= VM_USER;
-
- uint64 va = addr;
- if (flags & MAP_FIXED) {
- if (va % kPageSize != 0) return -EINVAL;
- } else if (va != 0) {
- va = page_align_down(va);
- }
-
- if (va == 0) {
- va = mm_find_free_va(mm, length, kPageSize);
- if (!va) return -ENOMEM;
- }
-
- if (flags & MAP_FIXED) {
- int ret = vmm_unmap_user(mm, va, length);
- if (ret < 0) return ret;
- }
-
- int ret = vmm_map_user(mm, va, length, vm_perm);
- if (ret < 0 && !(flags & MAP_FIXED) && addr != 0) {
- uint64 fallback = mm_find_free_va(mm, length, kPageSize);
- if (!fallback) return ret;
- ret = vmm_map_user(mm, fallback, length, vm_perm);
- if (ret < 0) return ret;
- va = fallback;
- } else if (ret < 0) {
- return ret;
- }
-
- return static_cast<int64>(va);
-}
-
-/* ======================================================================
- * sys_munmap
- * ====================================================================== */
-
-int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64)
-{
- if (length == 0) return -EINVAL;
- if (addr % kPageSize != 0) return -EINVAL;
- length = page_align_up(length);
-
- VmSpace* mm = current_task()->mm;
- if (!mm) return -EINVAL;
-
- return vmm_unmap_user(mm, addr, length);
-}
-
-/* ======================================================================
- * sys_mprotect — Phase 2 stub
- * ====================================================================== */
-
-int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot,
- uint64, uint64, uint64)
-{
- if (len == 0) return -EINVAL;
- if (addr % kPageSize != 0) return -EINVAL;
-
- VmSpace* mm = current_task()->mm;
- if (!mm) return -ENOMEM;
-
- uint64 vm_perm = 0;
- if (prot & PROT_READ) vm_perm |= VM_READ;
- if (prot & PROT_WRITE) vm_perm |= VM_WRITE;
- if (prot & PROT_EXEC) vm_perm |= VM_EXEC;
-
- return vmm_protect_user(mm, addr, len, vm_perm);
-}
diff --git a/na/kernel/sys_process.cc b/na/kernel/sys_process.cc
deleted file mode 100644
index 438ad1b..0000000
--- a/na/kernel/sys_process.cc
+++ /dev/null
@@ -1,185 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Process Control System Calls
- *
- * Implements: exit, exit_group, getpid, gettid, getppid,
- * sched_yield, clone (stub), wait4 (stub),
- * set_tid_address, getuid, getgid.
- *
- * Reference: docs/specs/syscall.md §7.1
- */
-
-#include "lume/klog.h"
-#include <lume/types.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/errno.h>
-#include <lume/uaccess.h>
-#include <lume/wait.h>
-
-/* External: task lifecycle (kernel/task.cc) */
-extern void task_exit(int code);
-extern int32 do_wait4(int32 wait_pid, int options, int32* exit_code);
-extern int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags,
- uint64 child_stack, uint64 parent_tidptr,
- uint64 child_tidptr, uint64 tls);
-extern int32 task_fork(arch::TrapFrame* tf);
-
-/* ======================================================================
- * sys_exit / sys_exit_group
- * ====================================================================== */
-
-int64 sys_exit(uint64 code, uint64, uint64, uint64, uint64, uint64)
-{
- task_exit(static_cast<int>(code & 0xFF));
- __builtin_unreachable();
-}
-
-int64 sys_exit_group(uint64 code, uint64, uint64, uint64, uint64, uint64)
-{
- /* Phase 2: single-threaded, equivalent to sys_exit.
- * Phase 3: kill all threads in tgid then exit. */
- task_exit(static_cast<int>(code & 0xFF));
- __builtin_unreachable();
-}
-
-/* ======================================================================
- * sys_getpid / sys_gettid / sys_getppid
- * ====================================================================== */
-
-int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- return current_task()->tgid; /* Linux: getpid() returns tgid */
-}
-
-int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- return current_task()->pid; /* Linux: gettid() returns real tid */
-}
-
-int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- TaskControlBlock* parent = current_task()->parent;
- return parent ? parent->tgid : 0;
-}
-
-/* ======================================================================
- * sys_sched_yield
- * ====================================================================== */
-
-int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- sched_yield();
- return 0;
-}
-
-int64 sys_clone_wrapper(uint64 flags, uint64 child_stack,
- uint64 ptid, uint64 ctid, uint64 tls, uint64)
-{
- arch::TrapFrame* tf = current_task()->trapframe;
- if (!tf) return -EINVAL;
-
- return task_clone(tf, flags, child_stack, ptid, ctid, tls);
-}
-
-/* ========================================================================
- * sys_wait4 — Wait for child to exit
- * ======================================================================== */
-
-int64 sys_wait4_wrapper(uint64 pid, uint64 wstatus_ptr,
- uint64 options, uint64, uint64, uint64)
-{
- int32 exit_code = 0;
- int32 child_pid = do_wait4(static_cast<int32>(pid),
- static_cast<int>(options),
- &exit_code);
-
- if (child_pid < 0) return child_pid;
-
- /* exit_code is already in Linux wstatus format */
- if (wstatus_ptr != 0) {
- if (copy_to_user(wstatus_ptr, &exit_code, sizeof(int)) < 0)
- return -EFAULT;
- }
-
- return child_pid;
-}
-
-/* ======================================================================
- * sys_set_tid_address — musl libc calls this very early
- * ====================================================================== */
-
-int64 sys_set_tid_address(uint64 tidptr, uint64, uint64,
- uint64, uint64, uint64)
-{
- /* Store clear_child_tid pointer — on exit, kernel writes 0 here
- * and does futex_wake (enables pthread_join). */
- current_task()->clear_child_tid = tidptr;
- return current_task()->pid;
-}
-
-/* ======================================================================
- * sys_getuid / sys_getgid — Phase 2: always root
- * ====================================================================== */
-
-int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- return 0; /* root */
-}
-
-int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- return 0; /* root */
-}
-
-/* ======================================================================
- * sys_nanosleep (SYS_nanosleep = 101)
- * ====================================================================== */
-#include <lume/wait.h>
-#include <lume/kprintf.h>
-#include <lume/timer.h>
-
-struct linux_timespec {
- int64 tv_sec;
- int64 tv_nsec;
-};
-
-static void nanosleep_wakeup(TimerList* t) {
- TaskControlBlock* task = static_cast<TaskControlBlock*>(t->data);
- task->state = TaskState::Runnable;
- sched_enqueue(task);
-}
-
-int64 sys_nanosleep(uint64 req_ptr, uint64 rem_ptr, uint64, uint64, uint64, uint64) {
- if (req_ptr == 0) return -EINVAL;
-
- linux_timespec req;
- if (copy_from_user(&req, req_ptr, sizeof(req)) != 0) {
- return -EFAULT;
- }
-
- uint64 target = *(volatile uint64*)&g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000);
-
- TaskControlBlock* curr = current_task();
- curr->state = TaskState::Sleeping;
-
- TimerList t;
- t.init(nanosleep_wakeup);
- t.expires = target;
- t.data = curr;
-
- add_timer(&t);
-
- if (*(volatile uint64*)&g_jiffies < target) {
- schedule();
- } else {
- curr->state = TaskState::Running;
- }
-
-
- del_timer(&t);
-
- return 0;
-}
diff --git a/na/kernel/sys_signal.cc b/na/kernel/sys_signal.cc
deleted file mode 100644
index 8b459d5..0000000
--- a/na/kernel/sys_signal.cc
+++ /dev/null
@@ -1,351 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * sys_signal.cc — Signal-related system calls
- *
- * Implements:
- * sys_kill (SYS_kill = 129)
- * sys_rt_sigaction (SYS_rt_sigaction = 134)
- * sys_rt_sigprocmask (SYS_rt_sigprocmask = 135)
- * sys_rt_sigreturn (SYS_rt_sigreturn = 139)
- *
- * Reference: Linux kernel/signal.c, arch/riscv/kernel/signal.c
- */
-
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/signal_nr.h>
-#include <lume/task.h>
-#include <lume/errno.h>
-#include <lume/uaccess.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/new.h>
-#include <lume/pgrp.h>
-
-/* From signal.cc */
-extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task);
-
-/* From task.cc */
-extern list_node g_all_tasks;
-extern Spinlock g_task_list_lock;
-
-/* ================================================================== */
-/* Helper: find task by PID */
-/* ================================================================== */
-
-static TaskControlBlock* find_task_by_pid(int32 pid)
-{
- LockGuard guard(g_task_list_lock);
- list_node* node = g_all_tasks.next;
- while (node != &g_all_tasks) {
- auto* task = list_entry<TaskControlBlock, &TaskControlBlock::all_link>(node);
- if (task->pid == pid) return task;
- node = node->next;
- }
- return nullptr;
-}
-
-/* ================================================================== */
-/* sys_kill — send a signal to a process */
-/* ================================================================== */
-
-/*
- * Deliver `sig` to the thread-group leader represented by `sig_st`.
- * sighand is allocated eagerly at fork/exec time, so we just trust
- * that the leader has one; send_signal returns -ESRCH if not.
- */
-static int deliver_to_group(signal_struct* sig_st, int sig,
- kernel_siginfo* info)
-{
- if (!sig_st || !sig_st->leader) return 0;
- if (send_signal(sig, info, sig_st->leader) == 0) return 1;
- return 0;
-}
-
-int64 sys_kill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64)
-{
- int32 pid = static_cast<int32>(a0);
- int sig = static_cast<int>(a1);
-
- if (sig < 0 || sig > _NSIG) return -EINVAL;
-
- TaskControlBlock* curr = current_task();
- if (!curr) return -ESRCH;
-
- kernel_siginfo info{};
- info.si_signo = sig;
- info.si_code = 0; /* SI_USER */
- info._sifields._kill._pid = curr->pid;
- info._sifields._kill._uid = 0;
-
- /* pid > 0: deliver to that thread group's leader. */
- if (pid > 0) {
- TaskControlBlock* target = find_task_by_pid(pid);
- if (!target) return -ESRCH;
- /* Group signal: prefer the thread-group leader if available. */
- if (target->signal && target->signal->leader)
- target = target->signal->leader;
- if (sig == 0) return 0;
- return send_signal(sig, &info, target);
- }
-
- /* pid == 0: every member of caller's process group. */
- if (pid == 0) {
- if (!curr->signal) return -ESRCH;
- if (sig == 0) return 0;
- int delivered = 0;
- int32 target_pgid = curr->signal->pgid;
- for_each_pgrp_member(target_pgid, [&](signal_struct* s) {
- delivered += deliver_to_group(s, sig, &info);
- });
- return delivered > 0 ? 0 : -ESRCH;
- }
-
- /* pid == -1: every process the caller may signal, except init.
- * We approximate "every" with "every thread-group leader on every
- * pgrp bucket". This is rare in practice (BusyBox doesn't use it
- * during normal shell operation). */
- if (pid == -1) {
- if (sig == 0) return 0;
- int delivered = 0;
- LockGuard guard(g_pgrp_lock);
- for (auto& bucket : g_pgrp_hash) {
- list_node* node = bucket.next;
- while (node != &bucket) {
- list_node* next = node->next;
- auto* s = list_entry<signal_struct,
- &signal_struct::pgrp_link>(node);
- if (s->leader && s->leader->pid != 1)
- delivered += deliver_to_group(s, sig, &info);
- node = next;
- }
- }
- return delivered > 0 ? 0 : -ESRCH;
- }
-
- /* pid < -1: every member of pgrp |pid|. */
- int32 target_pgid = -pid;
- if (sig == 0) {
- bool any = false;
- for_each_pgrp_member(target_pgid, [&](signal_struct*) { any = true; });
- return any ? 0 : -ESRCH;
- }
- int delivered = 0;
- for_each_pgrp_member(target_pgid, [&](signal_struct* s) {
- delivered += deliver_to_group(s, sig, &info);
- });
- return delivered > 0 ? 0 : -ESRCH;
-}
-
-/* ================================================================== */
-/* sys_tkill / sys_tgkill — send a signal to a specific thread */
-/* ================================================================== */
-
-static int64 do_tkill(int32 tgid, int32 tid, int sig)
-{
- if (sig < 0 || sig > _NSIG) return -EINVAL;
- if (tid <= 0) return -EINVAL;
- TaskControlBlock* target = find_task_by_pid(tid);
- if (!target) return -ESRCH;
- /* tgid == -1 from tkill means "any tgid"; otherwise enforce match. */
- if (tgid != -1 && target->tgid != tgid) return -ESRCH;
-
- if (sig == 0) return 0;
-
- kernel_siginfo info{};
- info.si_signo = sig;
- info.si_code = 0; /* SI_TKILL would be -6; userland rarely checks */
- info._sifields._kill._pid = current_task() ? current_task()->pid : 0;
- info._sifields._kill._uid = 0;
- return send_signal(sig, &info, target);
-}
-
-int64 sys_tkill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64)
-{
- return do_tkill(-1,
- static_cast<int32>(a0),
- static_cast<int>(a1));
-}
-
-int64 sys_tgkill(uint64 a0, uint64 a1, uint64 a2, uint64, uint64, uint64)
-{
- return do_tkill(static_cast<int32>(a0),
- static_cast<int32>(a1),
- static_cast<int>(a2));
-}
-
-/* ================================================================== */
-/* sys_rt_sigaction — get/set signal disposition */
-/* ================================================================== */
-
-/*
- * User-space sigaction struct layout (RISC-V LP64):
- * uint64 sa_handler;
- * uint64 sa_flags;
- * uint64 sa_restorer; (SA_RESTORER)
- * sigset_t sa_mask; (8 bytes on LP64)
- */
-struct user_sigaction {
- uint64 sa_handler;
- uint64 sa_flags;
- uint64 sa_restorer;
- sigset_t sa_mask;
-};
-
-int64 sys_rt_sigaction(uint64 a0, uint64 a1, uint64 a2, uint64 a3,
- uint64, uint64)
-{
- int sig = static_cast<int>(a0);
- uint64 act_uaddr = a1; /* const struct sigaction __user * */
- uint64 oact_uaddr = a2; /* struct sigaction __user * */
- uint64 sigsetsize = a3;
-
- if (sig < 1 || sig > _NSIG) return -EINVAL;
- if (sig_kernel_only(sig)) return -EINVAL;
- if (sigsetsize != sizeof(sigset_t)) return -EINVAL;
-
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->sighand) return -EINVAL;
-
- /* All reads/writes of the action table happen under siglock —
- * Linux's do_sigaction() also takes the same lock (kernel/signal.c). */
- user_sigaction old{};
- user_sigaction act{};
- bool have_new = (act_uaddr != 0);
-
- if (have_new) {
- int err = copy_from_user(&act, act_uaddr, sizeof(act));
- if (err < 0) return -EFAULT;
- }
-
- {
- LockGuard guard(curr->sighand->siglock);
- kernel_sigaction* ksa = &curr->sighand->action[sig - 1];
- old.sa_handler = ksa->sa_handler;
- old.sa_flags = ksa->sa_flags;
- old.sa_restorer = ksa->sa_restorer;
- old.sa_mask = ksa->sa_mask;
-
- if (have_new) {
- ksa->sa_handler = act.sa_handler;
- ksa->sa_flags = act.sa_flags;
- ksa->sa_restorer = act.sa_restorer;
- ksa->sa_mask = act.sa_mask;
- ksa->sa_mask.del(SIGKILL);
- ksa->sa_mask.del(SIGSTOP);
- }
- }
-
- if (oact_uaddr != 0) {
- int err = copy_to_user(oact_uaddr, &old, sizeof(old));
- if (err < 0) return -EFAULT;
- }
- return 0;
-}
-
-/* ================================================================== */
-/* sys_rt_sigprocmask — change signal blocked mask */
-/* ================================================================== */
-
-int64 sys_rt_sigprocmask(uint64 a0, uint64 a1, uint64 a2, uint64 a3,
- uint64, uint64)
-{
- int how = static_cast<int>(a0);
- uint64 set_uaddr = a1;
- uint64 oset_uaddr = a2;
- uint64 sigsetsize = a3;
-
- if (sigsetsize != sizeof(sigset_t)) return -EINVAL;
-
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->sighand) return -EINVAL;
-
- sigset_t newset{};
- bool have_new = (set_uaddr != 0);
- if (have_new) {
- int err = copy_from_user(&newset, set_uaddr, sizeof(sigset_t));
- if (err < 0) return -EFAULT;
- newset.del(SIGKILL);
- newset.del(SIGSTOP);
- }
-
- sigset_t old{};
- {
- /* Linux: kernel/signal.c:do_sigprocmask() also holds siglock
- * across read+update so the snapshot is atomic vs. concurrent
- * signal delivery. */
- LockGuard guard(curr->sighand->siglock);
- old = curr->blocked;
- if (have_new) {
- switch (how) {
- case SIG_BLOCK:
- curr->blocked = curr->blocked | newset;
- break;
- case SIG_UNBLOCK:
- curr->blocked = curr->blocked & ~newset;
- break;
- case SIG_SETMASK:
- curr->blocked = newset;
- break;
- default:
- return -EINVAL;
- }
- }
- }
-
- if (oset_uaddr != 0) {
- int err = copy_to_user(oset_uaddr, &old, sizeof(sigset_t));
- if (err < 0) return -EFAULT;
- }
- return 0;
-}
-
-/* ================================================================== */
-/* sys_rt_sigreturn — restore context after signal handler */
-/* ================================================================== */
-
-/* Arch-specific: implemented in arch/riscv/signal.cc */
-extern int64 arch_sigreturn(arch::TrapFrame* tf);
-
-int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64)
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->trapframe) return -EINVAL;
-
- return arch_sigreturn(curr->trapframe);
-}
-
-/* ================================================================== */
-/* sys_rt_sigtimedwait (SYS_rt_sigtimedwait = 137) */
-/* ================================================================== */
-#include <lume/timer.h>
-#include <lume/sched.h>
-
-struct linux_timespec {
- int64 tv_sec;
- int64 tv_nsec;
-};
-
-int64 sys_rt_sigtimedwait(uint64 /* set_ptr */, uint64 /* info_ptr */, uint64 timeout_ptr, uint64 /* sigsetsize */, uint64, uint64)
-{
- /*
- * COMPATIBILITY STUB:
- * LumeOS lacks full real-time signal queues and timed waiting mechanism.
- * When BusyBox init runs out of children to reap, it calls this to wait
- * for incoming signals (like SIGCHLD) with a timeout.
- * We simulate the timeout by yielding the CPU, then return -EAGAIN (Try again)
- * which tells user space that the timeout expired without receiving a signal.
- */
- if (timeout_ptr != 0) {
- linux_timespec req;
- if (copy_from_user(&req, timeout_ptr, sizeof(req)) == 0) {
- uint64 target = g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000);
- while (g_jiffies < target) {
- schedule();
- }
- }
- }
- return -EAGAIN;
-}
diff --git a/na/kernel/syscall.cc b/na/kernel/syscall.cc
deleted file mode 100644
index a5b80ec..0000000
--- a/na/kernel/syscall.cc
+++ /dev/null
@@ -1,208 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * System Call MI Layer — Jump Table & Dispatcher
- *
- * O(1) function pointer array dispatch. No switch-case.
- * syscall_init() registers all implemented handlers.
- *
- * Reference: docs/specs/syscall.md §6
- */
-
-#include <lume/syscall.h>
-#include <lume/syscall_nr.h>
-#include <lume/errno.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/console.h>
-#include <arch/cpu.h>
-
-/* ======================================================================
- * Jump table
- * ====================================================================== */
-
-inline constexpr int kNrSyscalls = 512;
-
-static SyscallHandler sys_call_table[kNrSyscalls];
-
-/* ======================================================================
- * MI Dispatcher
- * ====================================================================== */
-
-int64 syscall_dispatch(uint64 nr, uint64 args[6])
-{
- if (nr >= static_cast<uint64>(kNrSyscalls)) {
- kprintf("[syscall] nr=%llu out of range\n", nr);
- return -ENOSYS;
- }
-
- /* Per-syscall entry trace. KLOG_DEBUG keeps it out of the console
- * by default; enable via klog_set_level(KLOG_DEBUG) or read the
- * ring buffer to investigate. Subsys mask gates this further. */
- pr_debug_s(KLOG_SUBSYS_SYSCALL,
- "syscall: nr=%llu a0=%llx a1=%llx a2=%llx a3=%llx\n",
- nr, args[0], args[1], args[2], args[3]);
-
- SyscallHandler handler = sys_call_table[nr];
- if (!handler) {
- kprintf("[syscall] unimplemented nr=%llu\n", nr);
- return -ENOSYS;
- }
-
- return handler(args[0], args[1], args[2],
- args[3], args[4], args[5]);
-}
-
-/* ======================================================================
- * Forward declarations of sys_* handlers
- * ====================================================================== */
-
-/* sys_process.cc */
-int64 sys_exit(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_exit_group(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_clone_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_wait4_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_nanosleep(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* sys_mm.cc */
-int64 sys_brk(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_mmap(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_munmap(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_mprotect(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* sys_info.cc */
-int64 sys_uname(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* sys_fs.cc */
-int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_ppoll(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* sys_signal.cc */
-int64 sys_kill(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_tkill(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_tgkill(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_rt_sigaction(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_rt_sigprocmask(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_rt_sigtimedwait(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* exec.cc */
-int64 sys_execve_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* sys_misc.cc */
-int64 sys_reboot(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_prctl(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getsid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_setpgid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getpgid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_sigaltstack(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_clock_gettime(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* ======================================================================
- * syscall_init — register all handlers
- * ====================================================================== */
-
-void syscall_init()
-{
- /* Zero the table (redundant for BSS, but explicit) */
- for (int i = 0; i < kNrSyscalls; i++)
- sys_call_table[i] = nullptr;
-
- /* Phase 2: minimal runnable set */
- sys_call_table[SYS_exit] = sys_exit;
- sys_call_table[SYS_exit_group] = sys_exit_group;
- sys_call_table[SYS_getpid] = sys_getpid;
- sys_call_table[SYS_gettid] = sys_gettid;
- sys_call_table[SYS_getppid] = sys_getppid;
- sys_call_table[SYS_sched_yield] = sys_sched_yield_wrapper;
- sys_call_table[SYS_clone] = sys_clone_wrapper;
- sys_call_table[SYS_wait4] = sys_wait4_wrapper;
- sys_call_table[SYS_set_tid_address] = sys_set_tid_address;
- sys_call_table[SYS_getuid] = sys_getuid;
- sys_call_table[SYS_getgid] = sys_getgid;
- sys_call_table[SYS_brk] = sys_brk;
- sys_call_table[SYS_mmap] = sys_mmap;
- sys_call_table[SYS_munmap] = sys_munmap;
- sys_call_table[SYS_mprotect] = sys_mprotect;
- sys_call_table[SYS_uname] = sys_uname;
-
- /* Phase 3: filesystem */
- sys_call_table[SYS_openat] = sys_openat;
- sys_call_table[SYS_close] = sys_close;
- sys_call_table[SYS_read] = sys_read;
- sys_call_table[SYS_write] = sys_write;
- sys_call_table[SYS_lseek] = sys_lseek;
- sys_call_table[SYS_fstat] = sys_fstat_wrapper;
- sys_call_table[SYS_getdents64] = sys_getdents64;
- sys_call_table[SYS_dup] = sys_dup;
- sys_call_table[SYS_dup3] = sys_dup3;
- sys_call_table[SYS_getcwd] = sys_getcwd;
- sys_call_table[SYS_chdir] = sys_chdir;
- sys_call_table[SYS_mkdirat] = sys_mkdirat;
- sys_call_table[SYS_unlinkat] = sys_unlinkat;
- sys_call_table[SYS_writev] = sys_writev;
- sys_call_table[SYS_ioctl] = sys_ioctl;
- sys_call_table[SYS_ppoll] = sys_ppoll;
-
- /* Phase 4: signals */
- sys_call_table[SYS_kill] = sys_kill;
- sys_call_table[SYS_tkill] = sys_tkill;
- sys_call_table[SYS_tgkill] = sys_tgkill;
- sys_call_table[SYS_rt_sigaction] = sys_rt_sigaction;
- sys_call_table[SYS_rt_sigprocmask] = sys_rt_sigprocmask;
- sys_call_table[SYS_rt_sigreturn] = sys_rt_sigreturn;
- sys_call_table[SYS_rt_sigtimedwait] = sys_rt_sigtimedwait;
-
- /* Phase 5: exec */
- sys_call_table[SYS_execve] = sys_execve_wrapper;
-
- /* Phase 6: pipe + fcntl */
- sys_call_table[SYS_pipe2] = sys_pipe2;
- sys_call_table[SYS_fcntl] = sys_fcntl;
-
- /* Phase 7: busybox bootstrap */
- sys_call_table[SYS_reboot] = sys_reboot;
- sys_call_table[SYS_prctl] = sys_prctl;
- sys_call_table[SYS_setsid] = sys_setsid;
- sys_call_table[SYS_getsid] = sys_getsid;
- sys_call_table[SYS_setpgid] = sys_setpgid;
- sys_call_table[SYS_getpgid] = sys_getpgid;
- sys_call_table[SYS_geteuid] = sys_geteuid;
- sys_call_table[SYS_getegid] = sys_getegid;
- sys_call_table[SYS_sigaltstack] = sys_sigaltstack;
- sys_call_table[SYS_clock_gettime] = sys_clock_gettime;
- sys_call_table[SYS_nanosleep] = sys_nanosleep;
- sys_call_table[SYS_socket] = sys_socket;
- sys_call_table[SYS_sendto] = sys_sendto;
-
- printk(KERN_INFO "init: syscall_init done (%d handlers registered)\n", 46);
-}
diff --git a/na/kernel/task.cc b/na/kernel/task.cc
deleted file mode 100644
index 9f9b043..0000000
--- a/na/kernel/task.cc
+++ /dev/null
@@ -1,780 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Task Management — create, exit, destroy, kthread
- *
- * Implements task lifecycle management: kernel thread creation,
- * task exit (zombie transition), and resource reclamation.
- *
- * Reference: docs/specs/task.md §4-§6
- */
-
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/pmm.h>
-#include <lume/addr.h>
-#include <lume/new.h>
-#include <lume/kprintf.h>
-#include <lume/klog.h>
-#include <lume/vmm.h>
-#include <lume/errno.h>
-#include <lume/pid.h>
-#include <lume/wait.h>
-#include <lume/clone.h>
-#include <lume/signal_nr.h>
-#include <arch/cpu.h>
-#include <arch/context.h>
-#include <lume/uaccess.h>
-#include <lume/fs_struct.h>
-#include <lume/fdtable.h>
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/pgrp.h>
-#include <arch/pmap.h>
-#include <kernel/sync/spinlock.h>
-
-/* ======================================================================
- * Forward declarations
- * ====================================================================== */
-
-extern "C" void kthread_entry_trampoline();
-extern "C" void ret_from_fork();
-
-/* ======================================================================
- * Global task list
- * ====================================================================== */
-
-list_node g_all_tasks;
-Spinlock g_task_list_lock;
-int32 g_nr_tasks = 0;
-
-/* Special tasks */
-TaskControlBlock* g_kernel_task = nullptr; /* PID 0 container */
-TaskControlBlock* g_init_task = nullptr; /* PID 1 (future) */
-
-/* ======================================================================
- * task_init() — called once from kernel_main before sched_init
- * ====================================================================== */
-
-void task_init()
-{
- g_all_tasks.init();
- g_task_list_lock.init("task_list");
- g_pid_alloc.init();
-
- printk(KERN_INFO "init: task_init done (pid_alloc ready)\n");
-}
-
-/* ======================================================================
- * alloc_task — allocate TCB + kernel stack
- *
- * This is the SINGLE canonical TCB initializer. Both kthread_create
- * (task.cc) and sched_init (sched_core.cc) must use this function.
- * NEVER duplicate this logic — adding fields to TaskControlBlock
- * requires updating only this function.
- * ====================================================================== */
-
-TaskControlBlock* alloc_task(const char* name, uint8 priority,
- uint8 flags, uint8 cpu)
-{
- auto* task = new TaskControlBlock;
- if (!task) return nullptr;
-
- task->context = {};
-
- /* Allocate 8KB kernel stack (order-1 = 2 pages) */
- Page* stack_page = pmm_alloc_pages(1);
- if (!stack_page) {
- delete task;
- return nullptr;
- }
- task->kstack_pa = page_to_pa(stack_page).raw;
- task->kstack_va = pa_to_va(phys_addr(task->kstack_pa)).raw;
-
- /* Write stack canary at bottom */
- *reinterpret_cast<uint64*>(task->kstack_va) = kStackCanary;
-
- /* Place TrapFrame at the top of the kernel stack (like Linux pt_regs).
- * ALL tasks — kernel threads and user processes — have a TrapFrame
- * here. kthread_create sets the initial SP below the TrapFrame so
- * the kthread function never touches this reserved area.
- * kernel_execve / do_execve can later fill it in for user-mode return. */
- uint64 tf_addr = task->kstack_va + kKernelStackSize
- - sizeof(arch::TrapFrame);
- task->trapframe = reinterpret_cast<arch::TrapFrame*>(tf_addr);
- __builtin_memset(task->trapframe, 0, sizeof(arch::TrapFrame));
-
- /* Basic fields */
- task->name = name;
- task->priority = priority;
- task->time_slice = priority_to_timeslice(priority);
- task->state = TaskState::Runnable;
- task->cpu_id = cpu;
- task->flags = flags;
- task->exit_code = 0;
- task->exit_signal = 0;
- task->preempt_count = 0;
- task->clear_child_tid = 0;
- task->stopped_code = 0;
- task->notify_continued = false;
- task->parent = nullptr;
- task->mm = nullptr;
- task->fpstate = {};
- task->files = nullptr;
- task->fs = nullptr;
- task->pending.init();
- task->blocked.clear();
- task->sighand = nullptr;
- task->signal = nullptr;
-
- /* Init all list nodes */
- task->run_link.init();
- task->all_link.init();
- task->children.init();
- task->sibling.init();
- task->lock.init("task");
- task->wait_chld.init("wait_chld");
-
- /* Default sched class */
- extern const SchedClass g_sched_class_o1;
- task->sched_class = &g_sched_class_o1;
- task->sched_entity = &task->run_link;
-
- return task;
-}
-
-/* Add task to global task list */
-static void task_list_add(TaskControlBlock* task)
-{
- LockGuard guard(g_task_list_lock);
-
- list_node* head = &g_all_tasks;
- task->all_link.prev = head->prev;
- task->all_link.next = head;
- head->prev->next = &task->all_link;
- head->prev = &task->all_link;
-
- g_nr_tasks++;
-}
-
-/* Remove task from global task list */
-static void task_list_remove(TaskControlBlock* task)
-{
- LockGuard guard(g_task_list_lock);
-
- task->all_link.prev->next = task->all_link.next;
- task->all_link.next->prev = task->all_link.prev;
- task->all_link.init();
-
- if (g_nr_tasks > 0)
- g_nr_tasks--;
-}
-
-/* ======================================================================
- * kthread_create — create a kernel thread
- * ====================================================================== */
-
-TaskControlBlock* kthread_create(const char* name,
- void (*entry)(void*),
- void* arg,
- uint8 priority)
-{
- TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD,
- static_cast<uint8>(arch::cpu::id()));
- if (!task) return nullptr;
-
- /* Allocate PID */
- task->pid = g_pid_alloc.alloc();
- if (task->pid < 0) {
- /* PID exhausted — clean up */
- pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1);
- delete task;
- return nullptr;
- }
- task->tgid = task->pid;
-
- /* Kernel threads: exit_signal = -1 → auto-reap on exit.
- * Linux: kernel_thread() passes (flags & ~CSIGNAL) → exit_signal = 0,
- * but kthreads' parent (kthreadd) reaps them. We have no kthreadd,
- * so we use -1 which makes exit_notify() call release_task() directly,
- * identical to Linux's CLONE_THREAD autoreap path. */
- task->exit_signal = -1;
-
- /* Set up context for kthread_entry_trampoline.
- * SP starts BELOW the TrapFrame reserved at the stack top.
- * This ensures the kthread function never clobbers the TrapFrame area. */
- uint64 stack_top = task->kstack_va + kKernelStackSize
- - sizeof(arch::TrapFrame);
- task->context = {};
- task->context.ra = reinterpret_cast<uint64>(kthread_entry_trampoline);
- task->context.sp = stack_top;
- task->context.s[0] = reinterpret_cast<uint64>(entry);
- task->context.s[1] = reinterpret_cast<uint64>(arg);
-
- /* Parent is kernel_task (PID 0) or current caller */
- TaskControlBlock* parent = g_kernel_task ? g_kernel_task : current_task();
- task->parent = parent;
- if (parent) {
- LockGuard guard(parent->lock);
- list_node* head = &parent->children;
- task->sibling.prev = head->prev;
- task->sibling.next = head;
- head->prev->next = &task->sibling;
- head->prev = &task->sibling;
- }
-
- /* Add to global list and enqueue */
- task_list_add(task);
- sched_enqueue(task);
-
- return task;
-}
-
-/* ======================================================================
- * task_exit — transition current task to Zombie
- * ====================================================================== */
-
-void task_exit(int code)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) kernel_panic("task_exit: no current task");
-
- /* Linux ABI: normal exit encodes status as (code & 0xFF) << 8 */
- curr->exit_code = (code & 0xFF) << 8;
-
- /* CLONE_CHILD_CLEARTID / set_tid_address: write 0 to user tidptr.
- * This is how pthread_join detects thread termination.
- * TODO: futex_wake(clear_child_tid, 1) after writing 0. */
- if (curr->clear_child_tid != 0 && curr->mm != nullptr) {
- int32 zero = 0;
- copy_to_user(curr->clear_child_tid, &zero, sizeof(zero));
- curr->clear_child_tid = 0;
- }
-
- /* Release user address space (refcount-safe).
- * MUST switch to kernel page table FIRST — otherwise we're running
- * on a freed page table after put() deallocates it. */
- if (curr->mm != nullptr) {
- extern uint64 g_kernel_pgtbl;
- pmap::activate(g_kernel_pgtbl);
- curr->mm->put();
- curr->mm = nullptr;
- }
-
- /* Reparent children to init (or kernel_task if no init yet) */
- TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task;
- if (foster && !curr->children.is_empty()) {
- LockGuard guard(curr->lock);
-
- list_node* child_node = curr->children.next;
- while (child_node != &curr->children) {
- list_node* next_sib = child_node->next;
- auto* child = list_entry<TaskControlBlock,
- &TaskControlBlock::sibling>(child_node);
- child->parent = foster;
-
- /* Move sibling link to foster's children */
- child_node->prev->next = child_node->next;
- child_node->next->prev = child_node->prev;
-
- list_node* fhead = &foster->children;
- child_node->prev = fhead->prev;
- child_node->next = fhead;
- fhead->prev->next = child_node;
- fhead->prev = child_node;
-
- child_node = next_sib;
- }
- curr->children.init();
- }
-
-
- /* ---- exit_notify: Linux kernel/exit.c:exit_notify() ----
- * Determine whether this task should be auto-reaped or become zombie.
- *
- * Linux logic (simplified):
- * - exit_signal == -1 (kthreads, sub-threads): autoreap = true
- * - exit_signal >= 1 (user processes): notify parent, become zombie
- *
- * If autoreap, we call release_task() equivalent inline:
- * free PID, remove from task list, then deschedule to Dead. */
- bool autoreap = (curr->exit_signal <= 0);
-
- if (autoreap) {
- /* Auto-reap: release resources immediately (no zombie state).
- * Linux: exit_notify() → tsk->exit_state = EXIT_DEAD → release_task() */
- task_list_remove(curr);
- g_pid_alloc.free(curr->pid);
-
- /* Remove from parent's children list */
- curr->sibling.prev->next = curr->sibling.next;
- curr->sibling.next->prev = curr->sibling.prev;
- curr->sibling.init();
-
- /* Drop signal_struct ref: last task in the thread group also
- * detaches us from the pgrp hash. */
- if (curr->signal) {
- put_signal_struct(curr->signal);
- curr->signal = nullptr;
- }
- if (curr->sighand) {
- put_sighand(curr->sighand);
- curr->sighand = nullptr;
- }
-
- curr->state = TaskState::Dead;
- schedule();
- __builtin_unreachable();
- }
-
- /* User process path: become Zombie for parent wait() */
- curr->state = TaskState::Zombie;
-
- /* Wake parent->wait_chld queue */
- if (curr->parent) {
- curr->parent->wait_chld.wake_all();
- }
-
- /* Yield CPU — never returns */
- schedule();
-
- __builtin_unreachable();
-}
-
-void task_kill_current(int sig)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) kernel_panic("task_kill_current: no current task");
-
- /* Linux ABI: signal-killed encodes status as (signo & 0x7F) */
- curr->exit_code = sig & 0x7F;
-
- /* Release user address space (refcount-safe).
- * Switch to kernel page table first (same as task_exit). */
- if (curr->mm) {
- extern uint64 g_kernel_pgtbl;
- pmap::activate(g_kernel_pgtbl);
- curr->mm->put();
- curr->mm = nullptr;
- }
-
- /* Reparent children (same as task_exit) */
- TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task;
- if (foster && !curr->children.is_empty()) {
- LockGuard guard(curr->lock);
-
- list_node* child_node = curr->children.next;
- while (child_node != &curr->children) {
- list_node* next_sib = child_node->next;
- auto* child = list_entry<TaskControlBlock,
- &TaskControlBlock::sibling>(child_node);
- child->parent = foster;
-
- child_node->prev->next = child_node->next;
- child_node->next->prev = child_node->prev;
-
- list_node* fhead = &foster->children;
- child_node->prev = fhead->prev;
- child_node->next = fhead;
- fhead->prev->next = child_node;
- fhead->prev = child_node;
-
- child_node = next_sib;
- }
- curr->children.init();
- }
-
- curr->state = TaskState::Zombie;
-
- if (curr->parent) {
- curr->parent->wait_chld.wake_all();
- }
-
- schedule();
- __builtin_unreachable();
-}
-
-/* ======================================================================
- * task_destroy — fully reclaim a Zombie task (called by waitpid)
- * ====================================================================== */
-
-void task_destroy(TaskControlBlock* task)
-{
- /* Remove from global list */
- task_list_remove(task);
-
- /* Remove from parent's children */
- task->sibling.prev->next = task->sibling.next;
- task->sibling.next->prev = task->sibling.prev;
- task->sibling.init();
-
- /* Free PID */
- g_pid_alloc.free(task->pid);
-
- /* Drop signal_struct + sighand refs. For zombies these are delayed
- * until reap (here) so a waitpid() walker can still inspect
- * pgid/sid; Linux defers __exit_signal/__cleanup_sighand to
- * release_task() for the same reason. */
- if (task->signal) {
- put_signal_struct(task->signal);
- task->signal = nullptr;
- }
- if (task->sighand) {
- put_sighand(task->sighand);
- task->sighand = nullptr;
- }
-
- /* Free kernel stack */
- if (task->kstack_pa != 0) {
- pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1);
- }
-
- /* Free TCB */
- delete task;
-}
-
-/* ======================================================================
- * do_wait4 — wait for a child to exit (Linux-compatible)
- *
- * @wait_pid: >0 = specific child PID
- * -1 = any child
- * 0 = any child in same process group (TODO)
- * <-1 = any child in specific process group (TODO)
- * @options: WNOHANG = return immediately if no zombie
- * @exit_code: output parameter (already in Linux wstatus format)
- *
- * Returns: PID of exited child, -ECHILD, or -EAGAIN (WNOHANG).
- * ====================================================================== */
-
-int32 do_wait4(int32 wait_pid, int options, int32* exit_code)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) return -EINVAL;
-
- while (true) {
- TaskControlBlock* zombie = nullptr;
- TaskControlBlock* report = nullptr; /* Stopped/Continued report */
- int32 report_status = 0;
- bool has_children = false;
-
- /*
- * Two-phase wait: put ourselves on the wait queue BEFORE
- * checking the condition. This closes the race window where
- * a child exits between our check and schedule().
- *
- * If a child exits and calls wake_all() while we're checking,
- * our state will be flipped back to Runnable, and schedule()
- * will simply return immediately.
- */
- curr->wait_chld.prepare_to_wait(curr, TaskState::Sleeping);
-
- /* Scan children */
- curr->lock.acquire();
-
- if (curr->children.is_empty()) {
- curr->lock.release();
- curr->wait_chld.finish_wait(curr);
- return -ECHILD;
- }
- has_children = true;
-
- list_node* node = curr->children.next;
- while (node != &curr->children) {
- auto* child = list_entry<TaskControlBlock, &TaskControlBlock::sibling>(node);
- bool matches = (wait_pid == -1) || (wait_pid == child->pid);
- if (!matches) {
- node = node->next;
- continue;
- }
-
- /* Zombie reaping has priority — it frees resources. */
- if (child->state == TaskState::Zombie) {
- zombie = child;
- break;
- }
-
- /* WUNTRACED: report Stopped child once per stop event. */
- if ((options & WUNTRACED) && child->stopped_code != 0) {
- report = child;
- report_status = child->stopped_code;
- break;
- }
-
- /* WCONTINUED: report a one-shot SIGCONT resume. */
- if ((options & WCONTINUED) && child->notify_continued) {
- report = child;
- report_status = W_CONTINUED();
- break;
- }
-
- node = node->next;
- }
- curr->lock.release();
-
- if (zombie) {
- curr->wait_chld.finish_wait(curr);
- int32 pid = zombie->pid;
- if (exit_code) *exit_code = zombie->exit_code;
- task_destroy(zombie);
- return pid;
- }
-
- if (report) {
- /* Status-only report: clear the one-shot flag, leave the
- * child alive. WNOWAIT (peek) would skip the clear, but
- * we don't yet honour it. */
- curr->wait_chld.finish_wait(curr);
- int32 pid = report->pid;
- if (exit_code) *exit_code = report_status;
- if (W_CONTINUED() == report_status) {
- report->notify_continued = false;
- } else {
- report->stopped_code = 0;
- }
- return pid;
- }
-
- /* No event to report */
- if (!has_children) {
- curr->wait_chld.finish_wait(curr);
- return -ECHILD;
- }
- if (options & WNOHANG) {
- curr->wait_chld.finish_wait(curr);
- return 0;
- }
-
- /* Condition still false — deschedule.
- * We are already on the wait queue and marked Sleeping. */
- schedule();
-
- /* Woken up — finish_wait removes us from queue, restores Runnable */
- curr->wait_chld.finish_wait(curr);
-
- /* Loop back to re-check the condition */
- }
-}
-
-/* Legacy wrapper for internal callers */
-int32 task_wait(int32* exit_code)
-{
- return do_wait4(-1, 0, exit_code);
-}
-
-/* ======================================================================
- * task_clone — clone current task with Linux-compatible flags
- *
- * @tf: trap frame to copy to child
- * @clone_flags: CLONE_VM | CLONE_THREAD | CSIGNAL etc.
- * @child_stack: if non-zero, set as child's user SP (for pthread)
- * @parent_tidptr: write child tid here in parent (CLONE_PARENT_SETTID)
- * @child_tidptr: write child tid here in child (CLONE_CHILD_SETTID)
- * @tls: TLS pointer (CLONE_SETTLS → tp register)
- *
- * Returns: child PID in parent, 0 in child, or negative errno.
- * ====================================================================== */
-
-int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags,
- uint64 child_stack, uint64 parent_tidptr,
- uint64 child_tidptr, uint64 tls)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) return -EINVAL;
-
- TaskControlBlock* child = alloc_task("cloned", curr->priority, 0,
- static_cast<uint8>(arch::cpu::id()));
- if (!child) return -ENOMEM;
-
- /* Allocate PID */
- child->pid = g_pid_alloc.alloc();
- if (child->pid < 0) {
- pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1);
- delete child;
- return -ENOMEM;
- }
-
- /* exit_signal + tgid: from clone_flags.
- * Linux: fork.c line 2418/2424 */
- if (clone_flags & CLONE_THREAD) {
- child->tgid = curr->tgid;
- child->exit_signal = -1; /* Linux: sub-threads auto-reap */
- } else {
- child->tgid = child->pid;
- child->exit_signal = static_cast<int32>(clone_flags & 0xFF); /* CSIGNAL */
- if (child->exit_signal == 0) {
- child->exit_signal = SIGCHLD; /* default for fork() */
- }
- }
-
- /* Address space: CLONE_VM shares, otherwise deep copy */
- if (clone_flags & CLONE_VM) {
- if (curr->mm) {
- curr->mm->incref();
- child->mm = curr->mm;
- }
- } else {
- if (curr->mm) {
- child->mm = vmm_clone(curr->mm);
- if (!child->mm) {
- g_pid_alloc.free(child->pid);
- pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1);
- delete child;
- return -ENOMEM;
- }
- }
- }
-
- /* Copy TrapFrame to the child's kernel stack */
- uint64 tf_addr = child->kstack_va + kKernelStackSize - sizeof(arch::TrapFrame);
- child->trapframe = reinterpret_cast<arch::TrapFrame*>(tf_addr);
- *child->trapframe = *tf;
-
- /* Set Kernel SP */
- child->trapframe->kernel_sp = reinterpret_cast<uint64>(child->trapframe);
- child->trapframe->kernel_cpu_id = child->cpu_id;
-
- /* Child returns 0 from fork/clone */
- child->trapframe->a[0] = 0;
-
- /* If caller specified a child stack, use it (pthread_create) */
- if (child_stack != 0) {
- child->trapframe->sp = child_stack;
- }
-
- /* CLONE_SETTLS: set thread pointer register */
- if (clone_flags & CLONE_SETTLS) {
- child->trapframe->tp = tls;
- }
-
- /* Set up context for ret_from_fork */
- uint64 stack_top = tf_addr;
- child->context = {};
- child->context.ra = reinterpret_cast<uint64>(ret_from_fork);
- child->context.sp = stack_top;
- child->context.s[0] = tf_addr;
-
- /* ---- Copy fs_struct (Linux: copy_fs in fork.c) ----
- * CLONE_FS: share the same fs_struct (increment refcount).
- * Otherwise: allocate a new fs_struct copying root/pwd. */
- if (curr->fs != nullptr) {
- if (clone_flags & CLONE_FS) {
- curr->fs->count.fetch_add(1, __ATOMIC_RELAXED);
- child->fs = curr->fs;
- } else {
- child->fs = copy_fs_struct(curr->fs);
- }
- }
-
- /* ---- Copy files_struct (Linux: copy_files in fork.c) ----
- * CLONE_FILES: share the same fd table.
- * Otherwise: duplicate the fd table. */
- if (curr->files != nullptr) {
- if (clone_flags & CLONE_FILES) {
- curr->files->count.fetch_add(1, __ATOMIC_RELAXED);
- child->files = curr->files;
- } else {
- child->files = dup_fd(curr->files);
- }
- }
-
- /* ---- Copy sighand (Linux kernel/fork.c:copy_sighand) ----
- * CLONE_SIGHAND: share signal handlers (incref parent's).
- * Otherwise: deep copy of the action table; child gets its own
- * sighand_struct (and its own siglock). Both branches MUST yield
- * a non-null sighand — every user task needs one so signal paths
- * can take the siglock without lazy-allocation races. */
- if (clone_flags & CLONE_SIGHAND) {
- if (!curr->sighand) {
- /* CLONE_SIGHAND requires the parent to have a sighand. */
- return -EINVAL;
- }
- get_sighand(curr->sighand);
- child->sighand = curr->sighand;
- } else {
- child->sighand = copy_sighand(curr->sighand);
- if (!child->sighand) {
- /* OOM — same best-effort policy as fs/files/signal above.
- * A separate cleanup-on-OOM pass is on the to-do list. */
- }
- }
-
- /* ---- Copy signal_struct (Linux: copy_signal in fork.c) ----
- * CLONE_THREAD shares the parent's signal_struct (incref).
- * Otherwise allocate a new one that inherits pgid/sid/tty from the
- * parent so the child stays in the same process group and session —
- * this is what makes fork() participate in the existing job-control
- * identity without explicit setpgid() calls. */
- if (curr->signal != nullptr) {
- if (clone_flags & CLONE_THREAD) {
- get_signal_struct(curr->signal);
- child->signal = curr->signal;
- } else {
- child->signal = fork_signal_struct(curr->signal, child);
- }
- }
-
- /* Add to task tree */
- child->parent = curr;
- {
- LockGuard guard(curr->lock);
- list_node* head = &curr->children;
- child->sibling.prev = head->prev;
- child->sibling.next = head;
- head->prev->next = &child->sibling;
- head->prev = &child->sibling;
- }
-
- /* Add to global list and enqueue */
- task_list_add(child);
- sched_enqueue(child);
-
- /* CLONE_PARENT_SETTID: write child tid to parent's user address */
- if ((clone_flags & CLONE_PARENT_SETTID) && parent_tidptr != 0) {
- copy_to_user(parent_tidptr, &child->pid, sizeof(child->pid));
- }
-
- /* CLONE_CHILD_SETTID: write child tid into child's user address.
- * Note: this runs in parent context, but the address is in the
- * child's mm. For CLONE_VM (threads), parent and child share mm
- * so this works directly. For fork, the page was just CoW-copied. */
- if ((clone_flags & CLONE_CHILD_SETTID) && child_tidptr != 0) {
- if (clone_flags & CLONE_VM) {
- /* Same address space — write directly */
- copy_to_user(child_tidptr, &child->pid, sizeof(child->pid));
- }
- /* For non-CLONE_VM fork, the child's mm is separate.
- * The write would need to go into the child's pagetable.
- * Linux handles this in the child's first schedule via
- * a special flag. For now, threads (CLONE_VM) are the
- * primary consumer. */
- }
-
- /* CLONE_CHILD_CLEARTID: kernel writes 0 to child_tidptr on exit */
- if (clone_flags & CLONE_CHILD_CLEARTID) {
- child->clear_child_tid = child_tidptr;
- }
-
- return child->pid;
-}
-
-/* Legacy wrapper — plain fork() semantics */
-int32 task_fork(arch::TrapFrame* tf)
-{
- return task_clone(tf, SIGCHLD, 0, 0, 0, 0);
-}
-
-
-/* ======================================================================
- * task_check_stack_canary
- * ====================================================================== */
-
-void task_check_stack_canary(TaskControlBlock* task)
-{
- if (!task || task->kstack_va == 0) return;
-
- auto* canary = reinterpret_cast<uint64*>(task->kstack_va);
- if (*canary != kStackCanary) {
- kernel_panic("kernel stack overflow detected");
- }
-}
diff --git a/na/kernel/timer.cc b/na/kernel/timer.cc
deleted file mode 100644
index 3cf03cf..0000000
--- a/na/kernel/timer.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/timer.h>
-#include <lume/fdt.h>
-#include <lume/sched.h>
-#include <arch/cpu.h>
-#include <arch/sbi.h>
-
-#include <kernel/sync/spinlock.h>
-
-uint64 g_jiffies = 0;
-static uint64 g_timer_freq = 10000000;
-static uint64 g_tick_interval = 0;
-
-static list_node g_timer_list;
-static Spinlock g_timer_lock;
-
-
-void add_timer(TimerList* timer) {
- LockGuard guard(g_timer_lock);
-
- list_node* pos;
- list_for_each(pos, &g_timer_list) {
- TimerList* t = list_entry<TimerList, &TimerList::entry>(pos);
- if (t->expires > timer->expires) {
- break;
- }
- }
-
- timer->entry.prev = pos->prev;
- timer->entry.next = pos;
- pos->prev->next = &timer->entry;
- pos->prev = &timer->entry;
-}
-
-int del_timer(TimerList* timer) {
- LockGuard guard(g_timer_lock);
- if (timer->entry.next != nullptr && timer->entry.next != &timer->entry) {
- timer->entry.prev->next = timer->entry.next;
- timer->entry.next->prev = timer->entry.prev;
- timer->entry.init();
- return 1;
- }
- return 0;
-}
-
-int mod_timer(TimerList* timer, uint64 expires) {
- int ret = del_timer(timer);
- timer->expires = expires;
- add_timer(timer);
- return ret;
-}
-
-static void run_timers() {
- g_timer_lock.acquire();
-
- while (!g_timer_list.is_empty()) {
- TimerList* t = list_entry<TimerList, &TimerList::entry>(g_timer_list.next);
- if (t->expires > g_jiffies) {
- break;
- }
-
- t->entry.prev->next = t->entry.next;
- t->entry.next->prev = t->entry.prev;
- t->entry.init();
-
- g_timer_lock.release();
- t->function(t);
- g_timer_lock.acquire();
- }
-
- g_timer_lock.release();
-}
-
-static void timer_set_next() {
- arch::sbi::set_timer(arch::cpu::read_time() + g_tick_interval);
-}
-
-void timer_init() {
- DeviceNode* cpus = fdt_get_node_by_path("/cpus");
- if (cpus) {
- g_timer_freq = cpus->get_prop_u32("timebase-frequency", g_timer_freq);
- }
-
- // Default 100 Hz (10ms per tick)
- g_tick_interval = g_timer_freq / 100;
-
- g_timer_lock.init("timer_list");
- g_timer_list.init();
-
- timer_set_next();
-
- arch::cpu::timer_intr_on();
-}
-
-
-void timer_init_ap() {
- timer_set_next();
- arch::cpu::timer_intr_on();
-}
-
-extern "C" uint32 bsp_hart_id;
-
-void timer_tick() {
- if (arch::cpu::id() == bsp_hart_id) {
- g_jiffies++;
- run_timers();
- }
-
- timer_set_next();
- sched_tick();
-}
-
-
-uint64 timer_get_freq() {
- return g_timer_freq;
-}
diff --git a/na/kernel/trap.cc b/na/kernel/trap.cc
deleted file mode 100644
index 2116d91..0000000
--- a/na/kernel/trap.cc
+++ /dev/null
@@ -1,258 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Trap MI Layer — Machine-Independent Semantic Dispatcher
- *
- * Receives a translated TrapCause from the MD layer and routes to
- * the appropriate handler.
- *
- * *** MI INVARIANT: This file contains ZERO inline assembly,
- * ZERO arch-specific constants (no CSR reads, no scause codes). ***
- * arch/trapframe.h is included because TrapFrame is the cross-layer
- * data contract defined in docs/specs/arch_abstraction.md §3.2.
- *
- * Reference: docs/specs/trap.md §4
- */
-
-#include <arch/cpu.h>
-#include <lume/trap.h>
-#include <lume/irq.h>
-#include <lume/console.h>
-#include <lume/kprintf.h>
-#include <lume/errno.h>
-#include <lume/sched.h>
-#include <lume/timer.h>
-#include <lume/klog.h>
-#include <lume/syscall.h>
-#include <lume/task.h>
-#include <lume/vmm.h>
-#include <lume/signal.h>
-#include <lume/signal_nr.h>
-#include <lume/smp.h>
-#include <arch/trapframe.h> /* TrapFrame: cross-layer data contract */
-#include <arch/extable.h> /* search_extable: kernel fault recovery */
-
-/* ========================================================================
- * trap_handle() — MI-layer dispatcher for traps
- *
- * Called by MD layer after scause → TrapInfo translation.
- * ======================================================================== */
-
-static TrapStats g_trap_stats = {};
-
-static uint32 trap_cause_index(TrapCause cause)
-{
- uint32 raw = static_cast<uint32>(cause);
-
- /* Exceptions 0-7 map directly to indices 0-7 */
- if (raw <= 7) {
- return raw;
- }
- /* IRQs 16-18 map to indices 8-10 */
- if (raw >= 16 && raw <= 18) {
- return raw - 16 + 8;
- }
- /* Unknown / out-of-range */
- return 11;
-}
-
-static void trap_record(const TrapInfo& info)
-{
- uint32 idx = trap_cause_index(info.cause);
- __atomic_fetch_add(&g_trap_stats.total, 1, __ATOMIC_RELAXED);
- __atomic_fetch_add(&g_trap_stats.by_cause[idx], 1, __ATOMIC_RELAXED);
- g_trap_stats.last_cause = info.cause;
- g_trap_stats.last_class = info.cls;
- g_trap_stats.last_origin = info.origin;
- g_trap_stats.last_val = info.val;
- g_trap_stats.last_epc = info.epc;
- g_trap_stats.last_scause = info.scause;
-}
-
-static VmFaultCause vm_fault_cause_from_trap(TrapCause cause)
-{
- switch (cause) {
- case TrapCause::PageFaultStore:
- return VmFaultCause::Write;
- case TrapCause::PageFaultExec:
- return VmFaultCause::Exec;
- case TrapCause::PageFaultLoad:
- default:
- return VmFaultCause::Read;
- }
-}
-
-void trap_stats_get(TrapStats* out)
-{
- if (!out) {
- return;
- }
- *out = g_trap_stats;
-}
-
-void trap_stats_reset()
-{
- g_trap_stats = {};
-}
-
-void trap_handle(arch::TrapFrame* tf, const TrapInfo& info)
-{
- trap_record(info);
-
- /* ---- Kernel exception handling ----
- * Interrupts are always allowed through. For exceptions:
- * - Page faults: handled in the switch below (extable fixup path)
- * - Other exceptions: try extable, then panic.
- *
- * This replaces the old blanket kernel_panic, enabling
- * copy_from_user/copy_to_user to safely fault on user pages. */
- if (info.origin == TrapOrigin::FromKernel &&
- info.cls != TrapClass::Interrupt) {
-
- /* Page faults are dispatched in the switch — let them through */
- bool is_page_fault =
- (info.cause == TrapCause::PageFaultLoad ||
- info.cause == TrapCause::PageFaultStore ||
- info.cause == TrapCause::PageFaultExec);
-
- if (!is_page_fault) {
- /* Non-page-fault kernel exception: try extable fixup */
- uint64 fixup = search_extable(info.epc);
- if (fixup != 0) {
- tf->epc = fixup;
- return;
- }
- kprintf("[trap] fatal kernel exception: cause=%u epc=0x%llx val=0x%llx\n",
- static_cast<unsigned>(info.cause), info.epc, info.val);
- kernel_panic("unrecoverable kernel exception");
- }
- }
-
- switch (info.cause) {
- /* ---- Page Faults ---- */
- case TrapCause::PageFaultLoad:
- case TrapCause::PageFaultStore:
- case TrapCause::PageFaultExec:
- if (info.origin == TrapOrigin::FromUser) {
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->mm) {
- kernel_panic("user page fault with no current mm");
- }
-
- int ret = vmm_handle_page_fault(curr->mm, info.val,
- vm_fault_cause_from_trap(info.cause));
- if (ret == -EFAULT) {
- kprintf("[trap] user SIGSEGV (MAPERR) epc=0x%llx addr=0x%llx\n",
- info.epc, info.val);
- force_sig_fault(SIGSEGV, 1 /* SEGV_MAPERR */, info.val);
- } else if (ret == -ENOMEM) {
- kprintf("[trap] user SIGKILL (OOM) epc=0x%llx addr=0x%llx\n",
- info.epc, info.val);
- force_sig_fault(SIGKILL, 0, info.val);
- } else if (ret < 0) {
- kprintf("[trap] user SIGSEGV (ACCERR) epc=0x%llx addr=0x%llx\n",
- info.epc, info.val);
- force_sig_fault(SIGSEGV, 2 /* SEGV_ACCERR */, info.val);
- }
- break;
- }
-
- /* Kernel page fault — try exception table fixup.
- * This is the path used by copy_from_user/copy_to_user when
- * accessing unmapped or faulting user addresses from S-mode. */
- {
- uint64 fixup = search_extable(info.epc);
- if (fixup != 0) {
- tf->epc = fixup;
- break;
- }
- }
- kprintf("[trap] kernel page fault: epc=0x%llx addr=0x%llx\n",
- info.epc, info.val);
- {
- TaskControlBlock* curr = current_task();
- if (curr) {
- kprintf("[trap] pid=%d state=%d\n",
- curr->pid, static_cast<int>(curr->state));
- kprintf("[trap] tf->epc=%llx tf->ra=%llx tf->sp=%llx\n",
- tf->epc, tf->ra, tf->sp);
- }
- }
- kernel_panic("unhandled kernel page fault (no extable entry)");
- break;
-
- /* ---- System Call ---- */
- case TrapCause::Syscall:
- syscall_dispatch_md(tf);
- break;
-
- /* ---- Interrupts ---- */
- case TrapCause::IrqTimer:
- /* Timer tick */
- timer_tick();
- break;
-
- case TrapCause::IrqExternal: {
- /* External interrupt: generic claim → dispatch → complete */
- uint32 irq = g_irq_manager.claim();
- if (irq != 0) {
- g_irq_manager.dispatch(irq);
- g_irq_manager.complete(irq);
- }
- break;
- }
-
- case TrapCause::IrqSoftware:
- /* S-mode software interrupt = IPI from another hart */
- smp_handle_ipi();
- break;
-
- /* ---- Fatal exceptions ---- */
- case TrapCause::IllegalInst:
- if (info.origin == TrapOrigin::FromUser) {
- kprintf("[trap] user illegal instruction at epc=0x%llx stval=0x%llx\n",
- info.epc, info.val);
- force_sig_fault(SIGILL, 1 /* ILL_ILLOPC */, info.epc);
- } else {
- kprintf("[trap] illegal instruction at epc=0x%llx\n", info.epc);
- kernel_panic("illegal instruction in kernel mode");
- }
- break;
-
- case TrapCause::AccessFault:
- kprintf("[trap] access fault\n");
- kernel_panic("access fault");
- break;
-
- case TrapCause::AlignFault:
- kprintf("[trap] alignment fault\n");
- kernel_panic("alignment fault");
- break;
-
- case TrapCause::Breakpoint:
- kprintf("[trap] breakpoint\n");
- /* ebreak advance is handled by MD layer (arch-specific instruction size) */
- break;
-
- case TrapCause::Unknown:
- default:
- kprintf("[trap] unknown trap\n");
- kernel_panic("unknown trap");
- break;
- }
-
- /* Process pending signals before returning to user mode */
- if (info.origin == TrapOrigin::FromUser) {
- do_signal(tf);
- }
-
- /* Preemption check on return from any interrupt.
- * This is the kernel's equivalent of Linux's retint_kernel path:
- * if a timer tick (or IPI) set need_resched, we must call schedule()
- * before returning — otherwise the idle loop never yields to
- * runnable tasks like PID 1. */
- if (info.cls == TrapClass::Interrupt) {
- sched_check_preempt();
- }
-}
diff --git a/na/kernel/uaccess.cc b/na/kernel/uaccess.cc
deleted file mode 100644
index 14c5fb7..0000000
--- a/na/kernel/uaccess.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * User-Kernel Memory Copy — Phase 3 (Exception Tables)
- *
- * Validates user pointers by checking they fall within user address space
- * (below KERNEL_VA_BASE). Uses assembly-level safe copy functions that
- * integrate with the kernel's Exception Table mechanism.
- *
- * Reference: docs/specs/syscall.md §8
- */
-
-#include <lume/uaccess.h>
-#include <lume/task.h>
-#include <lume/errno.h>
-#include <lume/config.h>
-#include <lume/mm_layout.h>
-
-extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len);
-extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len);
-
-int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len)
-{
- if (len == 0) return 0;
- if (!is_user_range(user_src, len)) return -EFAULT;
-
- return __uaccess_memcpy(kernel_dst, reinterpret_cast<const void*>(user_src), len);
-}
-
-int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len)
-{
- if (len == 0) return 0;
- if (!is_user_range(user_dst, len)) return -EFAULT;
-
- return __uaccess_memcpy(reinterpret_cast<void*>(user_dst), kernel_src, len);
-}
-
-int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len)
-{
- if (!is_user_range(user_src, 1)) return -EFAULT;
-
- /* We must also check that the string does not cross into kernel space.
- This is safely handled by the assembly routine which will fault if it hits
- an unmapped page. However, to strictly respect kKernelVaBase, we should
- restrict max_len to the distance to kKernelVaBase. */
- uint64 max_allowed = kKernelVaBase - user_src;
- if (max_len > max_allowed) {
- max_len = max_allowed;
- }
-
- return __uaccess_strncpy(kernel_dst, reinterpret_cast<const char*>(user_src), max_len);
-}
diff --git a/na/kernel/waitqueue.cc b/na/kernel/waitqueue.cc
deleted file mode 100644
index 9689417..0000000
--- a/na/kernel/waitqueue.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * WaitQueue — blocking synchronization implementation
- *
- * Two-phase wait protocol:
- *
- * prepare_to_wait(): task → Sleeping, link into queue
- * [caller checks condition — if still false, calls schedule()]
- * finish_wait(): remove from queue, restore Runnable
- *
- * Legacy sleep() wraps the two-phase pattern for simple callers.
- *
- * wake_one()/wake_all(): dequeue waiters → Runnable, sched_enqueue()
- *
- * The internal spinlock protects the wait list only.
- * schedule() is called AFTER releasing all spinlocks to avoid
- * holding a lock across a context switch.
- */
-
-#include <lume/waitqueue.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/kprintf.h>
-#include <arch/cpu.h>
-
-/* ------------------------------------------------------------------ */
-/* Two-phase wait API */
-/* ------------------------------------------------------------------ */
-
-void WaitQueue::prepare_to_wait(TaskControlBlock* task, TaskState new_state)
-{
- LockGuard guard(lock_);
-
- /* Set state BEFORE linking — this is the key to preventing lost wakeups.
- * If wake_one fires between now and schedule(), it will set state back
- * to Runnable, and schedule() will see Runnable and not block. */
- task->state = new_state;
-
- /* Only add to queue if not already linked (idempotent) */
- if (task->run_link.next == &task->run_link) {
- /* Link into wait queue tail */
- list_node* node = &task->run_link;
- node->prev = head_.prev;
- node->next = &head_;
- head_.prev->next = node;
- head_.prev = node;
- }
-}
-
-void WaitQueue::finish_wait(TaskControlBlock* task)
-{
- LockGuard guard(lock_);
-
- /* Remove from wait queue if still linked */
- if (task->run_link.next != &task->run_link) {
- list_node* node = &task->run_link;
- node->prev->next = node->next;
- node->next->prev = node->prev;
- node->init();
- }
-
- /* Ensure task is Runnable (it may have been woken by wake_one/wake_all
- * already, or it may never have slept if the condition was true). */
- if (task->state == TaskState::Sleeping) {
- task->state = TaskState::Runnable;
- }
-}
-
-/* ------------------------------------------------------------------ */
-/* Legacy convenience API */
-/* ------------------------------------------------------------------ */
-
-void WaitQueue::sleep(Spinlock* external_lock)
-{
- TaskControlBlock* curr = current_task();
- if (!curr) {
- if (external_lock) external_lock->release();
- return;
- }
-
- /* Phase 1: add to queue, mark Sleeping */
- prepare_to_wait(curr, TaskState::Sleeping);
-
- /* Release external lock AFTER we are on the queue (critical ordering) */
- if (external_lock) {
- external_lock->release();
- }
-
- /* Phase 2: context switch away — will return here when woken */
- schedule();
-
- /* Phase 3: clean up */
- finish_wait(curr);
-}
-
-/* ------------------------------------------------------------------ */
-/* Wakeup */
-/* ------------------------------------------------------------------ */
-
-bool WaitQueue::wake_one()
-{
- LockGuard guard(lock_);
-
- if (head_.is_empty())
- return false;
-
- /* Dequeue the first (oldest) waiter */
- list_node* node = head_.next;
- node->prev->next = node->next;
- node->next->prev = node->prev;
- node->init();
-
- TaskControlBlock* task =
- list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node);
-
- task->state = TaskState::Runnable;
- sched_enqueue(task);
-
- return true;
-}
-
-int WaitQueue::wake_all()
-{
- LockGuard guard(lock_);
-
- int count = 0;
- while (!head_.is_empty()) {
- list_node* node = head_.next;
- node->prev->next = node->next;
- node->next->prev = node->prev;
- node->init();
-
- TaskControlBlock* task =
- list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node);
-
- task->state = TaskState::Runnable;
- sched_enqueue(task);
- count++;
- }
-
- return count;
-}