/* * IoApic.cpp * I/O APIC (I/O Advanced Programmable Interrupt Controller) * Copyright (c) 2025 Daniel Hammer */ #include "IoApic.hpp" #include #include #include 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"; } }; };