Files
2026-10-27 09:28:31 +01:00

370 lines
13 KiB
C

// HAL APIC support. This file owns local APIC, I/O APIC, and interrupt-controller setup.
#include <kernel/hal/hal_apic.h>
#include <kernel/hal/hal_internal.h>
#include <kernel/hal/hal_api.h>
#include <kernel/hal/hal_timer.h>
#include <kernel/hal/hal_processor.h>
#include <kernel/ke_vectors.h>
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
// ---- Local APIC ----.
static volatile uint32_t* gLapic = (volatile uint32_t*)0;
#define LAPIC_REG(off) (*(volatile uint32_t*)((uint8_t*)gLapic + (off)))
#define LAPIC_ID 0x020
#define LAPIC_EOI 0x0B0
#define LAPIC_TPR 0x080
#define LAPIC_ISR_BASE 0x100
#define LAPIC_IRR_BASE 0x200
#define LAPIC_SPURIOUS 0x0F0
#define LAPIC_ICR_LO 0x300
#define LAPIC_ICR_HI 0x310
#define LAPIC_ENABLE 0x100 // bit in spurious vector register
#define IA32_APIC_BASE 0x0000001Bu
#define IA32_APIC_BASE_ENABLE (1ULL << 11)
#define IA32_APIC_BASE_X2APIC (1ULL << 10)
#define IA32_X2APIC_ID 0x00000802u
#define IA32_X2APIC_EOI 0x0000080Bu
#define IA32_X2APIC_TPR 0x00000808u
#define IA32_X2APIC_ISR_BASE 0x00000810u
#define IA32_X2APIC_IRR_BASE 0x00000820u
#define IA32_X2APIC_SVR 0x0000080Fu
#define IA32_X2APIC_ICR 0x00000830u
#define CPUID_FEATURE_ECX_X2APIC (1u << 21)
// ---- I/O APIC interrupt domains ----.
#define HAL_IOAPIC_MAX 8u
#define IOAPIC_IDX 0x00u
#define IOAPIC_DATA 0x10u
typedef struct HAL_IOAPIC {
volatile uint32_t* Registers;
uintptr_t Physical;
uint32_t GsiBase;
uint32_t Pins;
volatile long RegisterLock;
} HAL_IOAPIC;
static HAL_IOAPIC gIoapics[HAL_IOAPIC_MAX];
static uint32_t gIoapicCount;
static uint32_t gBspLapicId;
static uint8_t gX2ApicSupported;
static uint8_t gX2ApicEnabled;
static uint32_t gIsaGsi[16];
static uint16_t gIsaFlags[16];
static bool gIsaOverridesInitialized;
// I/O APIC register selectors are shared by every CPU accessing that APIC.
static uint32_t IoapicRead(HAL_IOAPIC* apic, uint8_t reg) {
uintptr_t interruptFlags = HalDisableInterrupts();
while (_InterlockedCompareExchange(&apic->RegisterLock, 1, 0))
_mm_pause();
apic->Registers[IOAPIC_IDX / 4u] = reg;
uint32_t value = apic->Registers[IOAPIC_DATA / 4u];
_InterlockedExchange(&apic->RegisterLock, 0);
HalRestoreInterrupts(interruptFlags);
return value;
}
static void IoapicWrite(HAL_IOAPIC* apic, uint8_t reg, uint32_t value) {
uintptr_t interruptFlags = HalDisableInterrupts();
while (_InterlockedCompareExchange(&apic->RegisterLock, 1, 0))
_mm_pause();
apic->Registers[IOAPIC_IDX / 4u] = reg;
apic->Registers[IOAPIC_DATA / 4u] = value;
_InterlockedExchange(&apic->RegisterLock, 0);
HalRestoreInterrupts(interruptFlags);
}
static HAL_IOAPIC* HalApicFindGsi(uint32_t gsi, uint32_t* pinOut) {
for (uint32_t i = 0; i < gIoapicCount; i++) {
HAL_IOAPIC* apic = &gIoapics[i];
if (gsi >= apic->GsiBase && gsi - apic->GsiBase < apic->Pins) {
*pinOut = gsi - apic->GsiBase;
return apic;
}
}
return (HAL_IOAPIC*)0;
}
// Implements the apic Detect Mode operation.
static void HalApicDetectMode(void) {
int regs[4] = {0, 0, 0, 0};
__cpuid(regs, 1);
gX2ApicSupported = (regs[2] & CPUID_FEATURE_ECX_X2APIC) ? 1u : 0u;
uint64_t apicBase = __readmsr(IA32_APIC_BASE);
gX2ApicEnabled = (gX2ApicSupported && (apicBase & IA32_APIC_BASE_X2APIC)) ? 1u : 0u;
}
// Stalls for the requested bootstrap interval using the HAL timer fallback chain.
static void HalApicStallUs(uint32_t us) {
HalTimerStall(us);
}
// Implements the apic Wait Icr Idle operation.
static void HalApicWaitIcrIdle(void) {
if (gX2ApicEnabled)
return;
for (uint32_t i = 0; i < 1000000u; i++) {
if (!(LAPIC_REG(LAPIC_ICR_LO) & (1u << 12)))
return;
_mm_pause();
}
}
// Implements the apic Send Ipi operation.
static void HalApicSendIpi(uint32_t lapicId, uint32_t low) {
if (gX2ApicEnabled) {
__writemsr(IA32_X2APIC_ICR, ((uint64_t)lapicId << 32) | low);
return;
}
HalApicWaitIcrIdle();
LAPIC_REG(LAPIC_ICR_HI) = lapicId << 24;
LAPIC_REG(LAPIC_ICR_LO) = low;
HalApicWaitIcrIdle();
}
// IRQ n -> redirection table at register 0x10 + 2*n (low), 0x11 + 2*n (high).
#define IOREDTBL_LO(n) (0x10 + 2*(n))
#define IOREDTBL_HI(n) (0x11 + 2*(n))
// Returns the local interrupt-controller hardware ID of the boot processor.
uint32_t HalApicGetBootProcessorId(void) {
return gBspLapicId;
}
// Returns whether the local interrupt controller is using x2APIC mode.
uint8_t HalApicUsingX2Apic(void) {
return gX2ApicEnabled;
}
// Returns whether the active local interrupt-controller mode can address one hardware ID.
uint8_t HalApicCanAddressId(uint32_t hardwareId) {
if (gX2ApicEnabled)
return 1u;
return (hardwareId < 256u) ? 1u : 0u;
}
// Initializes the local interrupt controller on the current processor.
void HalApicInitializeCurrentProcessor(void) {
uint64_t apicBase = __readmsr(IA32_APIC_BASE) | IA32_APIC_BASE_ENABLE;
if (gX2ApicEnabled) {
apicBase |= IA32_APIC_BASE_X2APIC;
__writemsr(IA32_APIC_BASE, apicBase);
__writemsr(IA32_X2APIC_SVR, LAPIC_ENABLE | 0xFFu);
HalApicSetTaskPriority(0);
return;
}
__writemsr(IA32_APIC_BASE, apicBase);
if (gLapic)
LAPIC_REG(LAPIC_SPURIOUS) = LAPIC_ENABLE | 0xFF;
HalApicSetTaskPriority(0);
}
// Controls the current CPU's interrupt priority without changing its interrupt-enable flag.
void HalApicSetTaskPriority(uint8_t priority) {
if (gX2ApicEnabled) {
__writemsr(IA32_X2APIC_TPR, priority);
} else if (gLapic) {
LAPIC_REG(LAPIC_TPR) = priority;
}
}
uint8_t HalApicQueryTaskPriority(void) {
if (gX2ApicEnabled)
return (uint8_t)__readmsr(IA32_X2APIC_TPR);
return gLapic ? (uint8_t)LAPIC_REG(LAPIC_TPR) : 0u;
}
// Reads the current processor's local delivery state after a source is masked.
bool HalApicVectorPending(uint8_t vector) {
const uint32_t index = (uint32_t)vector >> 5u;
const uint32_t mask = 1u << (vector & 31u);
uint32_t irr, isr;
if (gX2ApicEnabled) {
irr = (uint32_t)__readmsr(IA32_X2APIC_IRR_BASE + index);
isr = (uint32_t)__readmsr(IA32_X2APIC_ISR_BASE + index);
} else if (gLapic) {
irr = LAPIC_REG(LAPIC_IRR_BASE + 0x10u * index);
isr = LAPIC_REG(LAPIC_ISR_BASE + 0x10u * index);
} else {
return true;
}
return ((irr | isr) & mask) != 0u;
}
// Broadcasts an NMI to every processor except the caller.
void HalApicBroadcastNmi(void) {
if (!gLapic && !gX2ApicEnabled) return;
// Stop-the-world panic IPI. Use NMI delivery instead of a normal fixed
// vector so CPUs running with IF=0 inside spinlock/high-IRQL regions still
// enter the panic path. The NMI vector is already installed in the IDT on
// every CPU that has reached kernel-owned state.
HalApicSendIpi(0, (3u << 18) | (4u << 8)); // all-excluding-self, NMI
}
// Sends one fixed-vector processor interrupt without exposing APIC mechanics.
void HalApicSendProcessorInterrupt(uint32_t hardwareId, uint8_t vector) {
if ((!gLapic && !gX2ApicEnabled) || vector < 0x20u || !HalApicCanAddressId(hardwareId))
return;
HalApicSendIpi(hardwareId, vector);
}
// Sends one non-maskable processor interrupt.
void HalApicSendProcessorNmi(uint32_t hardwareId) {
if ((!gLapic && !gX2ApicEnabled) || !HalApicCanAddressId(hardwareId))
return;
HalApicSendIpi(hardwareId, (4u << 8));
}
// Starts one secondary processor through the local interrupt-controller startup protocol.
KSTATUS HalApicStartProcessor(uint32_t hardwareId, uintptr_t startPhysical) {
if ((!gLapic && !gX2ApicEnabled) || !startPhysical ||
(startPhysical & 0xFFFu) != 0 || startPhysical >= 0x100000u ||
hardwareId == gBspLapicId) {
return NTLSTATUS_INVALID_PARAMETER;
}
if (!HalApicCanAddressId(hardwareId))
return NTLSTATUS_NOT_SUPPORTED;
uint8_t startupVector = (uint8_t)(startPhysical >> 12);
// INIT assert/deassert, then two SIPIs. The target begins at startPhysical.
HalApicSendIpi(hardwareId, 0x0000C500u); // INIT, level assert, level-triggered
HalApicStallUs(10000);
HalApicSendIpi(hardwareId, 0x00008500u); // INIT, level deassert
HalApicStallUs(200);
HalApicSendIpi(hardwareId, 0x00000600u | startupVector); // SIPI
HalApicStallUs(200);
HalApicSendIpi(hardwareId, 0x00000600u | startupVector); // SIPI retry
HalApicStallUs(200);
return NTLSTATUS_SUCCESS;
}
// MSI destination encoding without interrupt remapping supports an 8-bit APIC ID.
KSTATUS HalApicComposeMsi(uint32_t hardwareId, uint8_t vector, HAL_MSI_MESSAGE* out) {
if (!out || vector < KE_VECTOR_DEVICE_FIRST || vector > KE_VECTOR_DEVICE_LAST)
return NTLSTATUS_INVALID_PARAMETER;
if (hardwareId > 255u)
return NTLSTATUS_NOT_SUPPORTED;
out->Address = 0xFEE00000ull | ((uint64_t)hardwareId << 12);
out->Data = vector;
out->Reserved = 0;
return NTLSTATUS_SUCCESS;
}
static void HalApicInitializeIsaMap(void) {
if (gIsaOverridesInitialized) return;
for (uint32_t irq = 0; irq < 16; irq++) {
gIsaGsi[irq] = irq;
gIsaFlags[irq] = 0;
}
gIsaOverridesInitialized = true;
}
void HalApicAddIoapic(uintptr_t physical, uint32_t gsiBase) {
if (!physical || gIoapicCount >= HAL_IOAPIC_MAX)
return;
for (uint32_t i = 0; i < gIoapicCount; i++)
if (gIoapics[i].Physical == physical)
return;
HAL_IOAPIC* apic = &gIoapics[gIoapicCount++];
apic->Physical = physical;
apic->GsiBase = gsiBase;
}
void HalApicSetIsaOverride(uint8_t irq, uint32_t gsi, uint16_t flags) {
if (irq >= 16u) return;
HalApicInitializeIsaMap();
gIsaGsi[irq] = gsi;
gIsaFlags[irq] = flags;
}
KSTATUS HalApicResolveIsa(uint8_t irq, uint32_t* gsiOut, uint16_t* flagsOut) {
if (irq >= 16u || !gsiOut || !flagsOut)
return NTLSTATUS_INVALID_PARAMETER;
HalApicInitializeIsaMap();
uint32_t pin;
if (!HalApicFindGsi(gIsaGsi[irq], &pin))
return NTLSTATUS_NOT_SUPPORTED;
*gsiOut = gIsaGsi[irq];
*flagsOut = gIsaFlags[irq];
return NTLSTATUS_SUCCESS;
}
// ACPI polarity bits 1:0 and trigger bits 3:2 encode bus-defined defaults.
KSTATUS HalApicRouteGsi(uint32_t gsi, uint8_t vector, uint32_t hardwareId,
uint16_t flags, bool masked) {
if (vector < KE_VECTOR_DEVICE_FIRST || vector > KE_VECTOR_DEVICE_LAST || hardwareId > 255u)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t pin;
HAL_IOAPIC* apic = HalApicFindGsi(gsi, &pin);
if (!apic) return NTLSTATUS_NOT_SUPPORTED;
uint32_t mode = 0;
if ((flags & 3u) == 3u) mode |= (1u << 13);
if (((flags >> 2) & 3u) == 3u) mode |= (1u << 15);
IoapicWrite(apic, IOREDTBL_LO(pin), (1u << 16) | vector | mode);
IoapicWrite(apic, IOREDTBL_HI(pin), (hardwareId & 255u) << 24);
if (!masked) IoapicWrite(apic, IOREDTBL_LO(pin), vector | mode);
return NTLSTATUS_SUCCESS;
}
KSTATUS HalApicMaskGsi(uint32_t gsi, bool masked) {
uint32_t pin;
HAL_IOAPIC* apic = HalApicFindGsi(gsi, &pin);
if (!apic) return NTLSTATUS_NOT_SUPPORTED;
uint32_t lo = IoapicRead(apic, IOREDTBL_LO(pin));
if (masked) lo |= 1u << 16;
else lo &= ~(1u << 16);
IoapicWrite(apic, IOREDTBL_LO(pin), lo);
return NTLSTATUS_SUCCESS;
}
// Implements the apic Send Eoi operation.
void HalApicSendEoi(void) {
if (gX2ApicEnabled) {
__writemsr(IA32_X2APIC_EOI, 0);
return;
}
LAPIC_REG(LAPIC_EOI) = 0;
}
// Implements the apic Initialize operation.
void HalApicInitialize(uintptr_t lapicPhys) {
// The PIC remains masked: legacy input arrives through programmed I/O APICs.
__outbyte(0x21, 0xFF);
__outbyte(0xA1, 0xFF);
HalApicDetectMode();
gLapic = (volatile uint32_t*)HalPhysicalToVirtual(lapicPhys);
if (gX2ApicEnabled)
gBspLapicId = (uint32_t)__readmsr(IA32_X2APIC_ID);
else
gBspLapicId = (LAPIC_REG(LAPIC_ID) >> 24) & 0xFFu;
HalApicInitializeCurrentProcessor();
if (gIoapicCount == 0)
HalApicAddIoapic(0xFEC00000u, 0);
for (uint32_t index = 0; index < gIoapicCount; index++) {
HAL_IOAPIC* apic = &gIoapics[index];
apic->Registers = (volatile uint32_t*)HalPhysicalToVirtual(apic->Physical);
uint32_t version = IoapicRead(apic, 1u);
apic->Pins = ((version >> 16) & 0xFFu) + 1u;
for (uint32_t pin = 0; pin < apic->Pins; pin++) {
IoapicWrite(apic, IOREDTBL_LO(pin), (1u << 16) | KE_VECTOR_DEVICE_FIRST);
IoapicWrite(apic, IOREDTBL_HI(pin), (gBspLapicId & 255u) << 24);
}
}
}