diff options
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/Build.mk | 11 | ||||
| -rw-r--r-- | kernel/exec.cc | 659 | ||||
| -rw-r--r-- | kernel/irq.cc | 106 | ||||
| -rw-r--r-- | kernel/main.cc | 474 | ||||
| -rw-r--r-- | kernel/pgrp.cc | 148 | ||||
| -rw-r--r-- | kernel/pid.cc | 63 | ||||
| -rw-r--r-- | kernel/poll.cc | 250 | ||||
| -rw-r--r-- | kernel/ptrace.cc | 92 | ||||
| -rw-r--r-- | kernel/sched_class_o1.cc | 81 | ||||
| -rw-r--r-- | kernel/sched_core.cc | 571 | ||||
| -rw-r--r-- | kernel/sched_internal.h | 115 | ||||
| -rw-r--r-- | kernel/signal.cc | 442 | ||||
| -rw-r--r-- | kernel/smp.cc | 112 | ||||
| -rw-r--r-- | kernel/sync/cpu_state.h | 35 | ||||
| -rw-r--r-- | kernel/sync/mutex.cc | 156 | ||||
| -rw-r--r-- | kernel/sync/spinlock.cc | 120 | ||||
| -rw-r--r-- | kernel/sync/spinlock.h | 61 | ||||
| -rw-r--r-- | kernel/sys_fs.cc | 679 | ||||
| -rw-r--r-- | kernel/sys_info.cc | 49 | ||||
| -rw-r--r-- | kernel/sys_misc.cc | 360 | ||||
| -rw-r--r-- | kernel/sys_mm.cc | 178 | ||||
| -rw-r--r-- | kernel/sys_process.cc | 185 | ||||
| -rw-r--r-- | kernel/sys_signal.cc | 351 | ||||
| -rw-r--r-- | kernel/syscall.cc | 208 | ||||
| -rw-r--r-- | kernel/task.cc | 780 | ||||
| -rw-r--r-- | kernel/timer.cc | 118 | ||||
| -rw-r--r-- | kernel/trap.cc | 258 | ||||
| -rw-r--r-- | kernel/uaccess.cc | 53 | ||||
| -rw-r--r-- | kernel/waitqueue.cc | 144 |
29 files changed, 0 insertions, 6859 deletions
diff --git a/kernel/Build.mk b/kernel/Build.mk deleted file mode 100644 index 8f8ffa0..0000000 --- a/kernel/Build.mk +++ /dev/null @@ -1,11 +0,0 @@ -targets += kernel/core -target.kernel/core.kind := source-set -target.kernel/core.sources := \ - kernel/main.cc kernel/sync/spinlock.cc kernel/sync/mutex.cc \ - kernel/trap.cc kernel/irq.cc kernel/sched_core.cc \ - kernel/sched_class_o1.cc kernel/timer.cc kernel/pid.cc kernel/pgrp.cc \ - kernel/task.cc kernel/syscall.cc kernel/sys_process.cc kernel/sys_mm.cc \ - kernel/sys_info.cc kernel/sys_fs.cc kernel/uaccess.cc kernel/waitqueue.cc \ - kernel/signal.cc kernel/sys_signal.cc kernel/sys_misc.cc kernel/exec.cc \ - kernel/poll.cc kernel/ptrace.cc kernel/smp.cc - diff --git a/kernel/exec.cc b/kernel/exec.cc deleted file mode 100644 index 616c0da..0000000 --- a/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/kernel/irq.cc b/kernel/irq.cc deleted file mode 100644 index 4de468a..0000000 --- a/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/kernel/main.cc b/kernel/main.cc deleted file mode 100644 index c84f3ad..0000000 --- a/kernel/main.cc +++ /dev/null @@ -1,474 +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 -#ifdef CONFIG_SELFTEST -void selftest_run_all(); -#endif - -// 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 */ -#ifdef CONFIG_SELFTEST - 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"); -#endif - - /* - * 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). -#ifdef CONFIG_SELFTEST - selftest_run_all(); -#endif - - // 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/kernel/pgrp.cc b/kernel/pgrp.cc deleted file mode 100644 index e5b382c..0000000 --- a/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/kernel/pid.cc b/kernel/pid.cc deleted file mode 100644 index 04b846b..0000000 --- a/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/kernel/poll.cc b/kernel/poll.cc deleted file mode 100644 index 3674491..0000000 --- a/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/kernel/ptrace.cc b/kernel/ptrace.cc deleted file mode 100644 index 3280c07..0000000 --- a/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/kernel/sched_class_o1.cc b/kernel/sched_class_o1.cc deleted file mode 100644 index 28c693c..0000000 --- a/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/kernel/sched_core.cc b/kernel/sched_core.cc deleted file mode 100644 index c0e068e..0000000 --- a/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/kernel/sched_internal.h b/kernel/sched_internal.h deleted file mode 100644 index 514b613..0000000 --- a/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/kernel/signal.cc b/kernel/signal.cc deleted file mode 100644 index 53d26da..0000000 --- a/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/kernel/smp.cc b/kernel/smp.cc deleted file mode 100644 index 1a79ac3..0000000 --- a/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/kernel/sync/cpu_state.h b/kernel/sync/cpu_state.h deleted file mode 100644 index 1f7dde0..0000000 --- a/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/kernel/sync/mutex.cc b/kernel/sync/mutex.cc deleted file mode 100644 index c4d3e9d..0000000 --- a/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/kernel/sync/spinlock.cc b/kernel/sync/spinlock.cc deleted file mode 100644 index f649fe7..0000000 --- a/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/kernel/sync/spinlock.h b/kernel/sync/spinlock.h deleted file mode 100644 index a1369d1..0000000 --- a/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/kernel/sys_fs.cc b/kernel/sys_fs.cc deleted file mode 100644 index 1d26fdd..0000000 --- a/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/kernel/sys_info.cc b/kernel/sys_info.cc deleted file mode 100644 index 79baab0..0000000 --- a/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/kernel/sys_misc.cc b/kernel/sys_misc.cc deleted file mode 100644 index 48eb47b..0000000 --- a/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/kernel/sys_mm.cc b/kernel/sys_mm.cc deleted file mode 100644 index bfa067c..0000000 --- a/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/kernel/sys_process.cc b/kernel/sys_process.cc deleted file mode 100644 index 438ad1b..0000000 --- a/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/kernel/sys_signal.cc b/kernel/sys_signal.cc deleted file mode 100644 index 8b459d5..0000000 --- a/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/kernel/syscall.cc b/kernel/syscall.cc deleted file mode 100644 index a5b80ec..0000000 --- a/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/kernel/task.cc b/kernel/task.cc deleted file mode 100644 index 9f9b043..0000000 --- a/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/kernel/timer.cc b/kernel/timer.cc deleted file mode 100644 index 3cf03cf..0000000 --- a/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/kernel/trap.cc b/kernel/trap.cc deleted file mode 100644 index 2116d91..0000000 --- a/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/kernel/uaccess.cc b/kernel/uaccess.cc deleted file mode 100644 index 14c5fb7..0000000 --- a/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/kernel/waitqueue.cc b/kernel/waitqueue.cc deleted file mode 100644 index 9689417..0000000 --- a/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; -} |
