102 lines
3.1 KiB
C
102 lines
3.1 KiB
C
// ke_irq.c - IRQ dispatch, kernel-side
|
|
#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 <ntlite/ddi/hal_api.h>
|
|
#include <kernel/ke_debug.h>
|
|
|
|
#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 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) 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 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();
|
|
}
|
|
}
|