#include "apic.h" #include "mm/vmm.h" #include "mm/memory.h" #include "arch/x86_64/cpu/io.h" #include "libk/stdio.h" /* ── Internal state ───────────────────────────────────────────────────────── */ static volatile uint32_t *g_lapic = NULL; /* Virtual address of LAPIC MMIO */ /* ── Low-level helpers ────────────────────────────────────────────────────── */ static inline uint32_t lapic_read(uint32_t reg) { return g_lapic[reg >> 2]; } static inline void lapic_write(uint32_t reg, uint32_t val) { g_lapic[reg >> 2] = val; /* Serialise: read back a read-only register to ensure the write landed * before we continue (required by the APIC spec). */ (void)g_lapic[LAPIC_ID >> 2]; } static inline void rdmsr(uint32_t msr, uint32_t *lo, uint32_t *hi) { asm volatile("rdmsr" : "=a"(*lo), "=d"(*hi) : "c"(msr)); } static inline void wrmsr(uint32_t msr, uint32_t lo, uint32_t hi) { asm volatile("wrmsr" : : "a"(lo), "d"(hi), "c"(msr)); } /* ── Public API ───────────────────────────────────────────────────────────── */ uint64_t lapic_get_phys_base(void) { uint32_t lo, hi; rdmsr(IA32_APIC_BASE_MSR, &lo, &hi); /* Physical base is in bits [35:12] of the 64-bit MSR */ return ((uint64_t)(hi & 0xFu) << 32) | (lo & 0xFFFFF000u); } void lapic_eoi(void) { lapic_write(LAPIC_EOI, 0); } uint32_t lapic_id(void) { /* On xAPIC the LAPIC ID sits in bits [31:24] of the ID register */ return lapic_read(LAPIC_ID) >> 24; } void lapic_init(void) { /* * ── Step 1: Re-enable the APIC global enable bit ───────────────────── * * Earlier in kmain (before pmm_init) we cleared bit 11 of IA32_APIC_BASE * so that the i8259 PIC would raise interrupts properly under Limine. * Now that the LAPIC is being brought online we must restore that bit; * without it the LAPIC is completely off and nothing below will work. */ uint32_t lo, hi; rdmsr(IA32_APIC_BASE_MSR, &lo, &hi); lo |= IA32_APIC_BASE_ENABLE; wrmsr(IA32_APIC_BASE_MSR, lo, hi); /* * ── Step 2: Locate the LAPIC MMIO window ───────────────────────────── * * The physical address is almost always 0xFEE00000 but we read it from * the MSR to be correct. vmm_init() already identity-maps all physical * memory through the HHDM (up to 256 GiB), so the MMIO page is reachable * at phys + MEM_PHYS_OFFSET with no extra mapping needed. * * TODO: For strict correctness the LAPIC page should be mapped as * uncacheable (PAT / PCD). In practice, QEMU / BOCHS work fine * because MTRRs mark the 0xFEE00000 range as UC by default. */ uint64_t phys_base = ((uint64_t)(hi & 0xFu) << 32) | (lo & 0xFFFFF000u); printf("[LAPIC] Physical base: 0x%08x%08x\n", (uint32_t)(phys_base >> 32), (uint32_t)phys_base); g_lapic = (volatile uint32_t *)(phys_base + MEM_PHYS_OFFSET); /* * ── Step 3: Software-enable the LAPIC via the SVR ──────────────────── * * Bit 8 = Software Enable. The lower 8 bits are the "spurious interrupt * vector" delivered if the CPU acknowledges an interrupt that was * retracted by the LAPIC; 0xFF is the conventional choice. */ lapic_write(LAPIC_SVR, LAPIC_SVR_ENABLE | 0xFF); /* * ── Step 4: Configure LINT0 as ExtINT (i8259 pass-through) ────────── * * On a standard PC the i8259 INTR pin is wired to the BSP's LINT0. * Setting LINT0 to ExtINT delivery mode makes the LAPIC act as a * transparent relay: the i8259 interrupt acknowledge cycle goes through * normally, the full 8-bit vector comes from the i8259, and the CPU's * existing IDT entries + irq.c EOI code all keep working unchanged. * * This is the "Virtual Wire" mode described in the Intel MP Spec. */ lapic_write(LAPIC_LVT_LINT0, LAPIC_LVT_DM_EXTINT); /* * ── Step 5: Configure LINT1 as NMI ─────────────────────────────────── * * LINT1 is the NMI pin on standard PCs. Delivery mode = 4 (NMI), * unmasked, edge-triggered (the default when LAPIC_LVT_LEVEL is clear). */ lapic_write(LAPIC_LVT_LINT1, LAPIC_LVT_DM_NMI); /* * ── Step 6: Mask LVT entries we are not yet using ──────────────────── * * Timer, error, and (if present) thermal / perf entries all start masked. * Assign distinct vectors in the 0xF0-0xFE range so that if one fires * spuriously the IDT handler can identify it and send EOI. */ lapic_write(LAPIC_LVT_TIMER, LAPIC_LVT_MASKED | 0xFD); lapic_write(LAPIC_LVT_ERROR, LAPIC_LVT_MASKED | 0xFE); /* Version register: bits [23:16] = max LVT entry index */ uint32_t ver = lapic_read(LAPIC_VER); uint32_t max_lvt = (ver >> 16) & 0xFF; if (max_lvt >= 4) lapic_write(LAPIC_LVT_THERMAL, LAPIC_LVT_MASKED | 0xFC); if (max_lvt >= 5) lapic_write(LAPIC_LVT_PERF, LAPIC_LVT_MASKED | 0xFB); /* * ── Step 7: Clear the Error Status Register ─────────────────────────── * * The APIC spec requires writing ESR twice to clear stale error bits. */ lapic_write(LAPIC_ESR, 0); lapic_write(LAPIC_ESR, 0); /* Dismiss any stale in-service interrupt */ lapic_write(LAPIC_EOI, 0); /* * ── Step 8: Set Task Priority to 0 ─────────────────────────────────── * * TPR = 0 means the CPU will accept all interrupt priorities. * Raise this later if need to block lower-priority interrupts. */ lapic_write(LAPIC_TPR, 0); printf("[LAPIC] Online. ID=%u version=0x%02x max_lvt=%u\n", lapic_id(), ver & 0xFF, max_lvt); }