100 lines
3.6 KiB
C++
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);
|
|
}
|
|
}
|