9a9b91c940
It's finally done.. Signed-off-by: kaguya <vpshinomiya@protonmail.com>
147 lines
6.3 KiB
C
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);
|
|
} |