Files
MontaukOS/kernel/src/Arch/x86_64/Apic/Interrupts.cpp
T

100 lines
3.6 KiB
C++

/*
* Interrupts.cpp
* Hardware interrupt registration and dispatch
* Copyright (c) 2025 Daniel Hammer
*/
#include "Interrupts.hpp"
#include "Apic.hpp"
#include "IoApic.hpp"
#include <Arch/x86_64/IDT.hpp>
#include <Arch/Smp.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
// Assembly-defined stub table and spurious handler
extern "C" void* IrqStubTable[Hal::IRQ_COUNT];
extern "C" void IrqStubSpurious();
// Local APIC message address range; destination APIC ID in bits 19:12.
static constexpr uint64_t MsiAddressBase = 0xFEE00000;
namespace Hal {
// Dispatch table: one handler per IRQ
static Arch::IrqHandler g_irqHandlers[IRQ_COUNT] = {};
void InitializeIrqHandlers() {
// Install IRQ stubs into IDT vectors 32..79
for (int i = 0; i < IRQ_COUNT; i++) {
IDTEncodeInterrupt(IRQ_VECTOR_BASE + i, IrqStubTable[i], 0x8E);
}
// Install spurious interrupt handler at vector 0xFF
IDTEncodeInterrupt(0xFF, (void*)IrqStubSpurious, 0x8E);
Kt::KernelLogStream(Kt::OK, "IRQ") << "Installed " << base::dec << (uint64_t)IRQ_COUNT
<< " IRQ stubs (vectors " << (uint64_t)IRQ_VECTOR_BASE << "-"
<< (uint64_t)(IRQ_VECTOR_BASE + IRQ_COUNT - 1) << ")";
}
};
namespace Arch {
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
if (irq >= IRQ_COUNT) return;
Hal::g_irqHandlers[irq] = handler;
Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ " << base::dec << (uint64_t)irq
<< " (vector " << (uint64_t)(Hal::IRQ_VECTOR_BASE + irq) << ")";
}
void RouteLegacyIrq(uint8_t irq) {
// RouteIrq applies the MADT's Interrupt Source Overrides (polarity,
// trigger mode, GSI remapping).
Hal::IoApic::RouteIrq(irq, Hal::IRQ_VECTOR_BASE + irq,
(uint8_t)Hal::LocalApic::GetId());
}
void EnableLegacyIrq(uint8_t irq) {
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(irq));
}
bool PlatformLinesAreFixed() {
return true;
}
bool AssignPlatformLine(uint8_t, uint32_t, bool, bool) {
return false;
}
bool ComposeMsi(uint8_t irq, MsiMessage& out) {
// Physical destination mode, fixed delivery, edge triggered, to the
// BSP (APIC ID 0 -- the destination field is left zero, as every
// driver has always programmed it).
out = MsiMessage{ MsiAddressBase, (uint32_t)(Hal::IRQ_VECTOR_BASE + irq) };
return true;
}
bool SendIpi(int cpuIndex, uint8_t irq) {
auto* cpu = Smp::GetCpuData(cpuIndex);
if (cpu == nullptr) return false;
return Hal::LocalApic::SendFixedIpi(cpu->arch.lapicId,
Hal::IRQ_VECTOR_BASE + irq);
}
};
// C linkage dispatch function called from assembly stubs
extern "C" void HalIrqDispatch(uint64_t irqNumber, uint64_t savedCs) {
// Send EOI BEFORE calling the handler. The handler may context-switch
// (via Tick -> Schedule -> SchedContextSwitch) and never return here.
// If EOI is deferred until after the handler, the LAPIC timer vector
// stays masked on this CPU -- no more timer interrupts, no more
// scheduling, and the system eventually locks up.
// This is safe because the ISR runs with IF=0; the LAPIC won't
// deliver a new interrupt until iretq re-enables them.
Hal::LocalApic::SendEOI();
if (irqNumber < Hal::IRQ_COUNT && Hal::g_irqHandlers[irqNumber] != nullptr) {
Hal::g_irqHandlers[irqNumber]((uint8_t)irqNumber, (savedCs & 3) == 3);
}
}