Files
nt-lite/code/kernel/ke_irq.c
T

139 lines
4.5 KiB
C

// Interrupt manager. This file owns IRQ routing, handler dispatch, and post-interrupt scheduling hooks.
#include <kernel/ke_irq.h>
#include <kernel/ke_sched.h>
#include <kernel/ke_cpu.h>
#include <kernel/ke_idt.h>
#include <kernel/ke_irql.h>
#include <kernel/ke_dpc.h>
#include <kernel/ke_thread.h>
#include <kernel/ke_process.h>
#include <ntlite/ddi/hal_api.h>
#include <kernel/ke_debug.h>
#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) {
gHal = hal;
}
// Implements the irq Allocate Msi Vector operation.
__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;
}
// Implements the irq Register operation.
__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);
}
// Implements the irq Dispatch operation.
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);
}
// 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)) {
KeExitThread(current, current->Owner->ExitCode);
while (true) ;
}
KiDrainDpcQueue();
if (KeGetCurrentIrql() == PASSIVE_LEVEL)
KiScheduleFromInterrupt();
}
}