// ke_irq.c - IRQ dispatch, kernel-side #include #include #include #include #include #include #define MAX_IRQS 24 #define MAX_IRQ_HANDLERS 8 typedef struct IRQ_HANDLER_LIST { void (*Handlers[MAX_IRQ_HANDLERS])(void); uint8_t Count; } IRQ_HANDLER_LIST; static IRQ_HANDLER_LIST gIrqHandlers[MAX_IRQS]; static const HAL_API *gHal; static volatile uint32_t gInterruptDepth = 0; static KIRQL KiIrqToIrql(uint8_t irq) { KIRQL irql = (KIRQL)(DIRQL_BASE + irq); if (irql >= HIGH_LEVEL) irql = (KIRQL)(HIGH_LEVEL - 1); return irql; } void KeIrqInitialize(const HAL_API *hal) { gHal = hal; } __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) { if (irq >= MAX_IRQS || !handler) return; IRQ_HANDLER_LIST* list = &gIrqHandlers[irq]; for (uint8_t i = 0; i < list->Count; i++) { if (list->Handlers[i] == handler) return; } if (list->Count >= MAX_IRQ_HANDLERS) { DbgPrint("KeIrqRegister: IRQ %u handler table full\n", irq); return; } list->Handlers[list->Count++] = handler; gHal->Intc.RouteIrq(irq); } void KeIrqDispatch(uint8_t irq) { KIRQL targetIrql = KiIrqToIrql(irq); KIRQL currentIrql = KeGetCurrentIrql(); // Do not ever lower IRQL on interrupt entry. Nested IRQs are not expected // while IF is clear, but this keeps the software state sane if that changes // later. if (targetIrql < currentIrql) targetIrql = currentIrql; KIRQL oldIrql = KeRaiseIrql(targetIrql); if (irq < MAX_IRQS) { IRQ_HANDLER_LIST* list = &gIrqHandlers[irq]; for (uint8_t i = 0; i < list->Count; i++) { if (list->Handlers[i]) list->Handlers[i](); } } // Handlers are no longer allowed to context-switch directly. Timer IRQs // only request rescheduling; KiExitInterrupt performs the switch after this // function returns, so the EOI is guaranteed to be sent first. Keep the // software level at DIRQL until after the controller has been acknowledged. gHal->Intc.SendEoi(); KeLowerIrql(oldIrql); } void KiEnterInterrupt(void) { gInterruptDepth++; } void KiExitInterrupt(void) { if (gInterruptDepth) gInterruptDepth--; // A reschedule is only legal after the interrupt unwound back to the // interrupted thread's original PASSIVE context. If a future path ever // permits nested interrupting of DISPATCH/DIRQL code, leave the request // pending instead of switching while a spinlock/high-IRQL region is active. if (gInterruptDepth == 0 && KeGetCurrentIrql() == PASSIVE_LEVEL) { KiDrainDpcQueue(); if (KeGetCurrentIrql() == PASSIVE_LEVEL) KiScheduleFromInterrupt(); } }