// ke_irq.c - IRQ dispatch, kernel-side #include #include #define MAX_IRQS 24 static void (*gIrqHandlers[MAX_IRQS])(void); static const HAL_API *gHal; void KeIrqInitialize(const HAL_API *hal) { gHal = hal; } __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) { if (irq >= MAX_IRQS || !handler) return; gIrqHandlers[irq] = handler; gHal->Intc.RouteIrq(irq); } void KeIrqDispatch(uint8_t irq) { // KeIrqDispatch sends EOI after the handler returns, but the handler switched context before returning. // The EOI call is still sitting on the idle thread's stack, waiting for that thread to be resumed. // The LAPIC sees ISR[0x20] still set. Its rule is: don't deliver another interrupt at the same or lower priority until EOI is written. // So the timer never fires again. The user process runs completely unpreempted until it calls SYS_EXIT_PROCESS, which calls KeYield, which cooperatively switches back // and only then does the idle thread resume, KeIrqDispatch finally sends its EOI, and the timer starts firing again. // To fix this we send EOI before calling the handler, not after. gHal->Intc.SendEoi(); if (irq < MAX_IRQS && gIrqHandlers[irq]) gIrqHandlers[irq](); }