diff options
Diffstat (limited to 'kernel/trap.cc')
| -rw-r--r-- | kernel/trap.cc | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/kernel/trap.cc b/kernel/trap.cc new file mode 100644 index 0000000..2116d91 --- /dev/null +++ b/kernel/trap.cc @@ -0,0 +1,258 @@ +/* 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(); + } +} |
