168 lines
6.3 KiB
C++
168 lines
6.3 KiB
C++
/*
|
|
* IoApic.cpp
|
|
* I/O APIC (I/O Advanced Programmable Interrupt Controller)
|
|
* Copyright (c) 2025 Daniel Hammer
|
|
*/
|
|
|
|
#include "IoApic.hpp"
|
|
#include <Terminal/Terminal.hpp>
|
|
#include <CppLib/Stream.hpp>
|
|
#include <Memory/HHDM.hpp>
|
|
|
|
using namespace Kt;
|
|
|
|
namespace Hal {
|
|
namespace IoApic {
|
|
static volatile uint32_t* g_ioApicBase = nullptr;
|
|
static uint32_t g_gsiBase = 0;
|
|
static uint32_t g_maxRedirEntries = 0;
|
|
|
|
// Stored overrides for IRQ remapping
|
|
static MADT::InterruptSourceOverride g_overrides[MADT::ParsedMADT::MaxOverrides];
|
|
static int g_overrideCount = 0;
|
|
|
|
// Shadow copy of redirection entries for S3 resume replay
|
|
static constexpr uint32_t MAX_REDIR_ENTRIES = 24;
|
|
static uint64_t g_savedRedirEntries[MAX_REDIR_ENTRIES];
|
|
static uint32_t g_savedEntryCount = 0;
|
|
|
|
static uint32_t ReadRegister(uint32_t reg) {
|
|
// Write the register index to IOREGSEL (offset 0x00)
|
|
g_ioApicBase[0] = reg;
|
|
// Read the value from IOWIN (offset 0x10 = index 4 in uint32_t array)
|
|
return g_ioApicBase[4];
|
|
}
|
|
|
|
static void WriteRegister(uint32_t reg, uint32_t value) {
|
|
g_ioApicBase[0] = reg;
|
|
g_ioApicBase[4] = value;
|
|
}
|
|
|
|
void SetRedirectionEntry(uint8_t index, uint64_t entry) {
|
|
uint32_t regLow = IOREDTBL_BASE + (index * 2);
|
|
uint32_t regHigh = IOREDTBL_BASE + (index * 2) + 1;
|
|
|
|
WriteRegister(regHigh, (uint32_t)(entry >> 32));
|
|
WriteRegister(regLow, (uint32_t)(entry & 0xFFFFFFFF));
|
|
|
|
// Shadow the entry for S3 resume replay
|
|
if (index < MAX_REDIR_ENTRIES) {
|
|
g_savedRedirEntries[index] = entry;
|
|
if (index >= g_savedEntryCount)
|
|
g_savedEntryCount = index + 1;
|
|
}
|
|
}
|
|
|
|
uint64_t GetRedirectionEntry(uint8_t index) {
|
|
uint32_t regLow = IOREDTBL_BASE + (index * 2);
|
|
uint32_t regHigh = IOREDTBL_BASE + (index * 2) + 1;
|
|
|
|
uint64_t low = ReadRegister(regLow);
|
|
uint64_t high = ReadRegister(regHigh);
|
|
|
|
return (high << 32) | low;
|
|
}
|
|
|
|
void MaskIrq(uint8_t irq) {
|
|
uint64_t entry = GetRedirectionEntry(irq);
|
|
entry |= REDIR_MASKED;
|
|
SetRedirectionEntry(irq, entry);
|
|
}
|
|
|
|
void UnmaskIrq(uint8_t irq) {
|
|
uint64_t entry = GetRedirectionEntry(irq);
|
|
entry &= ~REDIR_MASKED;
|
|
SetRedirectionEntry(irq, entry);
|
|
}
|
|
|
|
uint32_t GetGsiForIrq(uint8_t isaIrq) {
|
|
for (int i = 0; i < g_overrideCount; i++) {
|
|
if (g_overrides[i].IrqSource == isaIrq) {
|
|
return g_overrides[i].GlobalSystemInterrupt;
|
|
}
|
|
}
|
|
// No override found, identity map
|
|
return isaIrq;
|
|
}
|
|
|
|
void RouteIrq(uint8_t isaIrq, uint8_t vector, uint8_t destinationApicId) {
|
|
uint32_t gsi = GetGsiForIrq(isaIrq);
|
|
uint32_t ioApicIndex = gsi - g_gsiBase;
|
|
|
|
// Build redirection entry
|
|
uint64_t entry = 0;
|
|
entry |= vector; // Vector
|
|
entry |= ((uint64_t)destinationApicId << 56); // Destination
|
|
|
|
// Check if there's an override with specific polarity/trigger settings
|
|
for (int i = 0; i < g_overrideCount; i++) {
|
|
if (g_overrides[i].IrqSource == isaIrq) {
|
|
uint16_t flags = g_overrides[i].Flags;
|
|
// Polarity: bits 0-1 (0b11 = active low)
|
|
if ((flags & 0x03) == 0x03) {
|
|
entry |= REDIR_ACTIVE_LOW;
|
|
}
|
|
// Trigger mode: bits 2-3 (0b11 = level triggered)
|
|
if ((flags & 0x0C) == 0x0C) {
|
|
entry |= REDIR_LEVEL_TRIGGER;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
SetRedirectionEntry(ioApicIndex, entry);
|
|
|
|
KernelLogStream(DEBUG, "IOAPIC") << "Routed ISA IRQ " << base::dec << (uint64_t)isaIrq
|
|
<< " -> GSI " << (uint64_t)gsi
|
|
<< " -> vector " << (uint64_t)vector
|
|
<< " -> APIC " << (uint64_t)destinationApicId;
|
|
}
|
|
|
|
void Reinitialize() {
|
|
if (!g_ioApicBase || g_savedEntryCount == 0) return;
|
|
|
|
// Replay all saved redirection entries into the I/O APIC hardware.
|
|
// The hardware registers are lost during S3 but our shadow copy in
|
|
// RAM survives.
|
|
for (uint32_t i = 0; i < g_savedEntryCount; i++) {
|
|
uint32_t regLow = IOREDTBL_BASE + (i * 2);
|
|
uint32_t regHigh = IOREDTBL_BASE + (i * 2) + 1;
|
|
|
|
WriteRegister(regHigh, (uint32_t)(g_savedRedirEntries[i] >> 32));
|
|
WriteRegister(regLow, (uint32_t)(g_savedRedirEntries[i] & 0xFFFFFFFF));
|
|
}
|
|
|
|
KernelLogStream(DEBUG, "IOAPIC") << "Restored " << base::dec
|
|
<< (uint64_t)g_savedEntryCount << " redirection entries after S3 resume";
|
|
}
|
|
|
|
void Initialize(uint64_t ioApicBasePhys, uint32_t gsiBase,
|
|
MADT::InterruptSourceOverride* overrides, int overrideCount) {
|
|
g_ioApicBase = (volatile uint32_t*)Memory::HHDM(ioApicBasePhys);
|
|
g_gsiBase = gsiBase;
|
|
|
|
// Store overrides
|
|
g_overrideCount = overrideCount;
|
|
for (int i = 0; i < overrideCount; i++) {
|
|
g_overrides[i] = overrides[i];
|
|
}
|
|
|
|
// Read IOAPIC version and max redirection entries
|
|
uint32_t version = ReadRegister(IOAPICVER);
|
|
g_maxRedirEntries = ((version >> 16) & 0xFF) + 1;
|
|
uint32_t ioapicId = (ReadRegister(IOAPICID) >> 24) & 0x0F;
|
|
|
|
KernelLogStream(OK, "IOAPIC") << "IOAPIC initialized: id=" << base::dec << (uint64_t)ioapicId
|
|
<< " version=" << base::hex << (uint64_t)(version & 0xFF)
|
|
<< " entries=" << base::dec << (uint64_t)g_maxRedirEntries;
|
|
|
|
// Mask all redirection entries initially
|
|
for (uint32_t i = 0; i < g_maxRedirEntries; i++) {
|
|
SetRedirectionEntry(i, REDIR_MASKED | (IRQ_VECTOR_BASE + i));
|
|
}
|
|
|
|
KernelLogStream(OK, "IOAPIC") << "All redirection entries masked";
|
|
}
|
|
};
|
|
};
|