Files
KirkOS/src/arch/x86_64/sys/apic.c
T
kaguya 9a9b91c940 user: implement mlibc as the libc, finally.
It's finally done..

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
2026-05-02 03:31:49 -04:00

147 lines
6.3 KiB
C

#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);
}