// Interrupt manager. This file owns IRQ routing, handler dispatch, and post-interrupt scheduling hooks. #include #include #include #include #include #include #include #include #include #include #define MAX_IRQS 24 #define MAX_IRQ_HANDLERS 8 #define MSI_IRQ_FIRST 16u #define MSI_IRQ_LAST 23u 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 bool gIrqSoftwareVector[MAX_IRQS]; static const HAL_API* gHal; // Implements the irq To Irql operation. static KIRQL KiIrqToIrql(uint8_t irq) { uint32_t level = (uint32_t)DIRQL_BASE + (uint32_t)irq; if (level >= HIGH_LEVEL) level = (uint32_t)(HIGH_LEVEL - 1); return (KIRQL)level; } // Implements the irq Initialize operation. void KeIrqInitialize(const HAL_API* hal) { if (!hal || !hal->InterruptController.RouteIrq || !hal->InterruptController.SendEoi) KePanic(KE_PANIC_CODE_INIT, "IRQ: invalid interrupt-controller API during initialization\n"); gHal = hal; } // Implements the irq Allocate Msi Vector operation. __declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq) { if (!outIrq) return NTLSTATUS_INVALID_PARAMETER; for (uint8_t irq = MSI_IRQ_FIRST; irq <= MSI_IRQ_LAST; irq++) { IRQ_HANDLER_LIST* list = &gIrqHandlers[irq]; if (!gIrqSoftwareVector[irq] && list->Count == 0) { gIrqSoftwareVector[irq] = true; *outIrq = irq; DbgPrint("KeIrq: allocated MSI vector slot IRQ %u vector 0x%02x\n", irq, 0x20u + irq); return NTLSTATUS_SUCCESS; } } return NTLSTATUS_NO_MEMORY; } // Implements the irq Register operation. __declspec(dllexport) void KeIrqRegister(uint8_t irq, void (*handler)(void)) { if (irq >= MAX_IRQS || !handler) return; if (!gHal) KePanic(KE_PANIC_CODE_INIT, "IRQ: registration before interrupt-manager initialization\n"); 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; if (!gIrqSoftwareVector[irq]) gHal->InterruptController.RouteIrq(irq); } // Implements the irq Dispatch operation. void KeIrqDispatch(uint8_t irq) { if (!gHal) KePanic(KE_PANIC_CODE_INIT, "IRQ: dispatch before interrupt-manager initialization, irq=%u\n", 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 preserving monotonic IRQL keeps nesting safe. if (targetIrql < currentIrql) targetIrql = currentIrql; KIRQL oldIrql = KeRaiseIrql(targetIrql); if (irq == KE_VECTOR_RESCHEDULE_IPI) { // Nothing to do here. The sender has already set this CPU's // ReschedulePending flag. KiExitInterrupt consumes it after EOI and // after the interrupted frame unwinds to PASSIVE_LEVEL. } else 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->InterruptController.SendEoi(); KeLowerIrql(oldIrql); } // Implements the enter Interrupt operation. void KiEnterInterrupt(void) { KeGetCurrentCpu()->InterruptDepth++; } // Implements the exit Interrupt operation. void KiExitInterrupt(void) { KCPU* cpu = KeGetCurrentCpu(); if (cpu->InterruptDepth) cpu->InterruptDepth--; // A reschedule is only legal after the interrupt unwinds back to the // interrupted thread's PASSIVE context. If an interrupt hit DISPATCH/DIRQL // code, leave the request pending instead of switching out of a spinlock or // high-IRQL region. if (cpu->InterruptDepth == 0 && KeGetCurrentIrql() == PASSIVE_LEVEL) { THREAD* current = cpu->CurrentThread; if (current && !current->IsIdleThread && current->Owner && KeIsProcessTerminating(current->Owner) && !KeThreadTerminationDeferred(current)) { KeExitThread(current, current->Owner->ExitCode); while (true) ; } KiDrainDpcQueue(); if (KeGetCurrentIrql() == PASSIVE_LEVEL) KiScheduleFromInterrupt(); } }