// ke_irq.c - IRQ dispatch, kernel-side #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; 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; } void KeIrqInitialize(const HAL_API *hal) { gHal = hal; } __declspec(dllexport) KSTATUS KeIrqAllocateMsiVector(uint8_t* outIrq) { if (!outIrq) return STATUS_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 STATUS_SUCCESS; } } return STATUS_NO_MEMORY; } __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; if (!gIrqSoftwareVector[irq]) 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 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->Intc.SendEoi(); KeLowerIrql(oldIrql); } void KiEnterInterrupt(void) { KeGetCurrentCpu()->InterruptDepth++; } 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) { KiDrainDpcQueue(); if (KeGetCurrentIrql() == PASSIVE_LEVEL) KiScheduleFromInterrupt(); } }