Files
frostbyte/src/isr.c
T

190 lines
7.5 KiB
C

#include "isr.h"
#include "console.h"
#include "pic.h"
#include "pit.h"
#include "keyboard.h"
#include "sched.h"
#include "ports.h"
#include "signal.h"
#include "mmap.h"
static const char* exceptionNames[32] = {
"divide by zero",
"debug",
"non maskable interrupt",
"breakpoint",
"overflow",
"bound range exceeded",
"invalid opcode",
"device not available",
"double fault",
"coprocessor segment overrun",
"invalid tss",
"segment not present",
"stack segment fault",
"general protection fault",
"page fault",
"reserved",
"x87 floating point",
"alignment check",
"machine check",
"simd floating point",
"virtualization",
"control protection",
"reserved", "reserved", "reserved", "reserved",
"reserved", "reserved", "reserved", "reserved",
"hypervisor injection", "vmm communication"
};
static void panicOnException(Registers* regs)
{
uint64 faultAddress = 0;
if (regs->intNo == 14)
__asm__ volatile("mov %%cr2, %0" : "=r"(faultAddress));
kprintf("\n[PANIC] exception %lu (%s)\n", regs->intNo, exceptionNames[regs->intNo]);
kprintf(" errCode=%lx rip=%lx cs=%lx rflags=%lx\n",
regs->errCode, regs->rip, regs->cs, regs->rflags);
if (regs->intNo == 14)
kprintf(" cr2=%lx (faulting address)\n", faultAddress);
kprintf(" rax=%lx rbx=%lx rcx=%lx rdx=%lx\n", regs->rax, regs->rbx, regs->rcx, regs->rdx);
kprintf(" system halted\n");
for (;;)
__asm__ volatile("cli; hlt");
}
/* Runs at every single return-to-ring3 point this kernel has (a completed
syscall, a timer reschedule, any other IRQ resuming whoever it
interrupted) -- the one choke point isrDispatch funnels its result
through below, regardless of which of those three paths produced it.
`rsp` is whatever Registers* the caller is about to resume; if that
context is not ring 3 (a kernel thread, e.g. workerA/shell -- see
sched.h's userCodeSelector|3 vs kernelCodeSelector), signals are not a
concept that applies to it at all and this is a no-op. Otherwise
currentThread() is guaranteed to be exactly the owner of `rsp` (schedule()
already updated currentIndex before returning it; the syscall/other-IRQ
paths never changed currentIndex away from whoever trapped in), so
sigConsumePending always inspects the right thread's pending/blocked
state. A default-disposition Terminate (or job-control Stop, sigTstp's
default -- see signal.h) signal cannot be handled here directly -- it
needs a NEW thread's rsp, which only schedule() can produce -- so the
loop below reuses the exact same "flip state, then reschedule" pattern
sysExit/sysSleep already use (threadExit for Terminate, threadStop for
Stop), and loops again in case the freshly chosen thread also has a
signal of its own waiting (e.g. two threads both made Runnable by the
same timer tick and both signaled). */
static uint64 deliverPendingSignals(uint64 rsp)
{
for (;;) {
Registers* regs = (Registers*)rsp;
if ((regs->cs & 3) != 3)
return rsp;
Thread* thread = currentThread();
int signum = 0;
SigOutcome outcome = sigConsumePending(thread, regs, &signum);
if (outcome == sigOutcomeNone)
return rsp;
if (outcome == sigOutcomeHandler)
return (uint64)regs;
if (outcome == sigOutcomeStop) {
kprintf("[sig] pid=%u '%s' stopped by signal %d\n",
thread->id, thread->name, signum);
threadStop(thread->id);
rsp = schedule(rsp);
continue;
}
kprintf("[sig] pid=%u '%s' terminated by uncaught signal %d\n",
thread->id, thread->name, signum);
threadExit(128u + (uint64)signum);
rsp = schedule(rsp);
}
}
/* Attempts to resolve a ring-3 #PF through mmap.c's own demand-paging
machinery: cr2 is the real faulting address (same register the panic
path above already reads), errCode bit 1 (0x2) is the CPU's own
"this was a write access" flag -- the exact distinction
mmapFaultHandler needs to refuse a write against a read-only mapping
instead of silently allowing it. Returns whatever mmapFaultHandler
itself returns: true means a real frame is now mapped and the faulting
instruction can simply be retried (the CPU never advanced rip past a
#PF to begin with); false means this address is not a mapping this
kernel's mmap.c even knows about, or the access violates its own prot
bits -- the caller (isrDispatch below) turns that into a real SIGSEGV
rather than the kernel-wide panic every OTHER exception still gets. */
static bool tryResolveUserPageFault(Registers* regs)
{
/* errCode bit 4 (I/D) is the CPU's own "this fault was an instruction
fetch" flag -- only ever set once paging.h's pageNoExecute is real
hardware enforcement (IA32_EFER.NXE, see paging.c's
pagingNxSupported/boot.S), meaning a PRESENT page's own NX bit
refused execution. This can never be something mmapFaultHandler's
demand-paging logic fixes: there is no lazy version of "make this
page executable" the way there is for a not-yet-faulted-in page, and
mmapFaultHandler's own "pagingResolve != 0 -> already resolved, just
retry" shortcut (mmap.c) would otherwise see the very same present
page, report success, and send the CPU straight back to retry the
identical faulting instruction fetch -- an infinite fault loop that
hangs the whole kernel rather than the one offending thread. Refuse
outright instead; isrDispatch below turns this into a real SIGSEGV
exactly like any other refusal, whether the address belongs to an
mmap Vma, or (mmapFind would never even find it) a stack/brk page
mapped NX by kmain.c/syscall.c/jobctl.c/sched.c. */
if (regs->errCode & 0x10)
return false;
uint64 faultAddress;
__asm__ volatile("mov %%cr2, %0" : "=r"(faultAddress));
bool forWrite = (regs->errCode & 0x2) != 0;
return mmapFaultHandler(currentThread(), faultAddress, forWrite);
}
uint64 isrDispatch(Registers* regs)
{
/* A ring-3 (user-mode) page fault gets one real chance at recovery
before falling through to the unconditional panic below: an
instruction inside SOME OTHER thread's buggy user program should
never be able to take the whole machine down with it, exactly like
a real kernel's own per-process fault isolation -- this kernel's
flat, single-shared-address-space design (see paging.c's own file
comments) cannot isolate a bad ACCESS the way separate page tables
would, but it can still isolate the CONSEQUENCE, by killing only the
one thread that caused it. A kernel-mode fault (ring 0, cs&3==0 --
this kernel's own bug, not a user program's) still panics
unconditionally below, exactly as before this existed. */
if (regs->intNo == 14 && (regs->cs & 3) == 3) {
if (!tryResolveUserPageFault(regs))
sigRaiseInfo(currentThreadId(), sigSegv, siCodeKernel, 0, 0);
return deliverPendingSignals((uint64)regs);
}
if (regs->intNo < 32)
panicOnException(regs);
uint64 nextRsp;
if (regs->intNo == 128) {
nextRsp = syscallDispatch(regs);
} else {
uint8 irq = (uint8)(regs->intNo - 32);
nextRsp = (uint64)regs;
if (irq == 0) {
pitTick();
nextRsp = schedule((uint64)regs);
} else if (irq == 1) {
keyboardHandler();
}
picSendEoi(irq);
}
return deliverPendingSignals(nextRsp);
}