#include "arch/x86/lapic.h"
#include "arch/x86/kernel_va_map.h"
#include "hal/cpu_features.h"
#include "timer.h"
#include "vmm.h"
#include "io.h"
#include "console.h"
#include "utils.h"
#include <stdint.h>
/* IA32_APIC_BASE MSR */
#define IA32_APIC_BASE_MSR 0x1B
#define IA32_APIC_BASE_ENABLE (1U << 11)
#define IA32_APIC_BASE_ADDR 0xFFFFF000U
static volatile uint32_t* lapic_base = 0;
static int lapic_active = 0;
static uint32_t lapic_timer_ticks_saved = 0; /* BSP-calibrated ticks for AP reuse */
uint64_t rdmsr(uint32_t msr) {
uint32_t lo, hi;
__asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr));
return ((uint64_t)hi << 32) | lo;
}
void wrmsr(uint32_t msr, uint64_t value) {
uint32_t lo = (uint32_t)(value & 0xFFFFFFFF);
uint32_t hi = (uint32_t)(value >> 32);
__asm__ volatile("wrmsr" : : "c"(msr), "a"(lo), "d"(hi));
}
uint32_t lapic_read(uint32_t reg) {
return lapic_base[reg / 4];
}
void lapic_write(uint32_t reg, uint32_t val) {
lapic_base[reg / 4] = val;
}
int lapic_is_enabled(void) {
return lapic_active;
}
uint32_t lapic_get_id(void) {
if (!lapic_active) return 0;
return (lapic_read(LAPIC_ID) >> 24) & 0xFF;
}
void lapic_eoi(void) {
if (lapic_active) {
lapic_write(LAPIC_EOI, 0);
}
}
void lapic_send_ipi(uint8_t dest_id, uint32_t icr_lo) {
if (!lapic_active) return;
/* Write destination LAPIC ID to ICR high (bits 24-31) */
lapic_write(LAPIC_ICR_HI, ((uint32_t)dest_id) << 24);
/* Write command to ICR low — this triggers the IPI */
lapic_write(LAPIC_ICR_LO, icr_lo);
/* Wait for delivery (bit 12 = delivery status, 0 = idle) */
while (lapic_read(LAPIC_ICR_LO) & (1U << 12)) {
__asm__ volatile("pause");
}
}
/* Disable the legacy 8259 PIC by masking all IRQs */
void pic_disable(void) {
outb(0xA1, 0xFF); /* Mask all slave PIC IRQs */
outb(0x21, 0xFF); /* Mask all master PIC IRQs */
}
int lapic_init(void) {
const struct cpu_features* f = hal_cpu_get_features();
if (!f->has_apic) {
kprintf("[LAPIC] CPU does not support APIC.\n");
return 0;
}
if (!f->has_msr) {
kprintf("[LAPIC] CPU does not support MSR.\n");
return 0;
}
/* Read APIC base address from MSR */
uint64_t apic_base_msr = rdmsr(IA32_APIC_BASE_MSR);
uintptr_t phys_base = (uintptr_t)(apic_base_msr & IA32_APIC_BASE_ADDR);
/* Enable APIC in MSR if not already enabled */
if (!(apic_base_msr & IA32_APIC_BASE_ENABLE)) {
apic_base_msr |= IA32_APIC_BASE_ENABLE;
wrmsr(IA32_APIC_BASE_MSR, apic_base_msr);
}
/* Map LAPIC MMIO region into kernel virtual address space.
* Use a fixed kernel VA for the LAPIC page. */
uintptr_t lapic_va = KVA_LAPIC;
vmm_map_page((uint64_t)phys_base, (uint64_t)lapic_va,
VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE);
lapic_base = (volatile uint32_t*)lapic_va;
/* NOTE: Do NOT disable the PIC here. The PIC must remain active
* until the IOAPIC is fully configured with IRQ routes.
* The caller (arch_platform_setup) will disable the PIC after
* IOAPIC setup is complete. */
/* Set spurious interrupt vector and enable APIC */
lapic_write(LAPIC_SVR, LAPIC_SVR_ENABLE | LAPIC_SPURIOUS_VEC);
/* Clear error status register (write twice per Intel spec) */
lapic_write(LAPIC_ESR, 0);
lapic_write(LAPIC_ESR, 0);
/* Set task priority to 0 (accept all interrupts) */
lapic_write(LAPIC_TPR, 0);
/* Mask LINT0 and LINT1 */
lapic_write(LAPIC_LINT0_LVT, LAPIC_LVT_MASKED);
lapic_write(LAPIC_LINT1_LVT, LAPIC_LVT_MASKED);
/* Mask timer initially */
lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED);
/* Send EOI to clear any pending interrupts */
lapic_eoi();
lapic_active = 1;
kprintf("[LAPIC] Enabled at phys=0x%x, ID=%u\n",
(unsigned)phys_base, (unsigned)lapic_get_id());
return 1;
}
void lapic_timer_start(uint32_t frequency_hz) {
if (!lapic_active) return;
/* Use divide-by-16 */
lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16);
/* Calibrate: use PIT channel 2 to measure LAPIC timer speed.
* We'll use a simple approach: count how many LAPIC ticks in ~10ms.
*
* PIT channel 2 at 1193180 Hz, count for ~10ms = 11932 ticks.
*/
/* Set PIT channel 2 for one-shot, mode 0 */
outb(0x61, (inb(0x61) & 0xFD) | 0x01); /* Gate high, speaker off */
outb(0x43, 0xB0); /* Channel 2, lobyte/hibyte, mode 0 */
uint16_t pit_count = 11932; /* ~10ms */
outb(0x42, (uint8_t)(pit_count & 0xFF));
outb(0x42, (uint8_t)((pit_count >> 8) & 0xFF));
/* Reset LAPIC timer to max count */
lapic_write(LAPIC_TIMER_ICR, 0xFFFFFFFF);
/* Read TSC before and after to calibrate TSC frequency */
uint32_t tsc_lo0, tsc_hi0, tsc_lo1, tsc_hi1;
__asm__ volatile("rdtsc" : "=a"(tsc_lo0), "=d"(tsc_hi0));
/* Wait for PIT to count down */
while (!(inb(0x61) & 0x20)) {
__asm__ volatile("pause");
}
__asm__ volatile("rdtsc" : "=a"(tsc_lo1), "=d"(tsc_hi1));
/* Stop LAPIC timer */
lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED);
/* Read how many ticks elapsed in ~10ms */
uint32_t elapsed = 0xFFFFFFFF - lapic_read(LAPIC_TIMER_CCR);
/* Calibrate TSC: tsc_delta ticks in ~10ms → tsc_khz = tsc_delta / 10 */
uint64_t tsc0 = ((uint64_t)tsc_hi0 << 32) | tsc_lo0;
uint64_t tsc1 = ((uint64_t)tsc_hi1 << 32) | tsc_lo1;
uint64_t tsc_delta = tsc1 - tsc0;
uint32_t tsc_khz = (uint32_t)(tsc_delta / 10);
if (tsc_khz > 0) {
tsc_calibrate(tsc_khz);
kprintf("[TSC] Calibrated: %u kHz (%u MHz)\n",
(unsigned)tsc_khz, (unsigned)(tsc_khz / 1000));
}
/* Calculate ticks per desired frequency:
* ticks_per_second = elapsed * 100 (since we measured 10ms)
* ticks_per_interrupt = ticks_per_second / frequency_hz */
uint32_t ticks_per_interrupt = (elapsed * 100) / frequency_hz;
if (ticks_per_interrupt == 0) ticks_per_interrupt = 1;
/* Save calibrated value for AP reuse */
lapic_timer_ticks_saved = ticks_per_interrupt;
/* Configure periodic timer */
lapic_write(LAPIC_TIMER_LVT, LAPIC_TIMER_PERIODIC | LAPIC_TIMER_VEC);
lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16);
lapic_write(LAPIC_TIMER_ICR, ticks_per_interrupt);
kprintf("[LAPIC] Timer started at %uHz (ticks=%u)\n",
(unsigned)frequency_hz, ticks_per_interrupt);
}
void lapic_timer_start_ap(void) {
/* Start LAPIC timer on an AP using BSP-calibrated ticks.
* All CPUs share the same bus clock, so the same ticks value works. */
if (!lapic_active || lapic_timer_ticks_saved == 0) return;
lapic_write(LAPIC_TIMER_LVT, LAPIC_TIMER_PERIODIC | LAPIC_TIMER_VEC);
lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16);
lapic_write(LAPIC_TIMER_ICR, lapic_timer_ticks_saved);
}
void lapic_timer_stop(void) {
if (!lapic_active) return;
lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED);
lapic_write(LAPIC_TIMER_ICR, 0);
}