#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: lapic.c,v 1.94 2026/02/07 01:52:33 riastradh Exp $");
#include "acpica.h"
#include "ioapic.h"
#include "opt_acpi.h"
#include "opt_ddb.h"
#include "opt_mpbios.h"
#include "opt_multiprocessor.h"
#include "opt_ntp.h"
#include "opt_xen.h"
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/timetc.h>
#include <uvm/uvm_extern.h>
#include <dev/ic/i8253reg.h>
#include <x86/machdep.h>
#include <machine/cpu.h>
#include <machine/cpu_counter.h>
#include <machine/cpufunc.h>
#include <machine/cpuvar.h>
#include <machine/pmap.h>
#include <machine/vmparam.h>
#include <machine/mpacpi.h>
#include <machine/mpbiosvar.h>
#include <machine/pcb.h>
#include <machine/pmap_private.h>
#include <machine/specialreg.h>
#include <machine/segments.h>
#include <x86/x86/tsc.h>
#include <x86/i82093var.h>
#include <machine/apicvar.h>
#include <machine/i82489reg.h>
#include <machine/i82489var.h>
#ifndef XENPV
#if NACPICA > 0
#include <dev/acpi/acpica.h>
#include <dev/acpi/acpivar.h>
#endif
#ifdef DDB
#include <machine/db_machdep.h>
#ifdef MULTIPROCESSOR
#ifdef __x86_64__
typedef void (vector)(void);
extern vector Xintr_x2apic_ddbipi;
extern int ddb_vec;
#endif
#endif
#endif
#include <dev/vmt/vmtreg.h>
#include <dev/vmt/vmtvar.h>
void lapic_clockintr(void *, struct intrframe *);
static void lapic_delay(unsigned int);
static uint32_t lapic_gettick(void);
static void lapic_setup_bsp(paddr_t);
static void lapic_map(paddr_t);
static void lapic_hwmask(struct pic *, int);
static void lapic_hwunmask(struct pic *, int);
static void lapic_setup(struct pic *, struct cpu_info *, int, int, int);
void lapic_dump(void);
struct pic local_pic = {
.pic_name = "lapic",
.pic_type = PIC_LAPIC,
.pic_lock = __SIMPLELOCK_UNLOCKED,
.pic_hwmask = lapic_hwmask,
.pic_hwunmask = lapic_hwunmask,
.pic_addroute = lapic_setup,
.pic_delroute = lapic_setup,
.pic_intr_get_devname = x86_intr_get_devname,
.pic_intr_get_assigned = x86_intr_get_assigned,
.pic_intr_get_count = x86_intr_get_count,
};
static int i82489_ipi(int vec, int target, int dl);
static int x2apic_ipi(int vec, int target, int dl);
int (*x86_ipi)(int, int, int) = i82489_ipi;
bool x2apic_mode __read_mostly;
#ifdef LAPIC_ENABLE_X2APIC
bool x2apic_enable = true;
#else
bool x2apic_enable = false;
#endif
bool lapic_from_cpuid = false;
static bool lapic_broken_periodic __read_mostly;
static uint32_t
i82489_readreg(u_int reg)
{
return *((volatile uint32_t *)(local_apic_va + reg));
}
static void
i82489_writereg(u_int reg, uint32_t val)
{
*((volatile uint32_t *)(local_apic_va + reg)) = val;
}
static uint32_t
i82489_cpu_number(void)
{
return i82489_readreg(LAPIC_ID) >> LAPIC_ID_SHIFT;
}
static uint32_t
x2apic_readreg(u_int reg)
{
return rdmsr(MSR_X2APIC_BASE + (reg >> 4));
}
static void
x2apic_writereg(u_int reg, uint32_t val)
{
x86_mfence();
wrmsr(MSR_X2APIC_BASE + (reg >> 4), val);
}
static void
x2apic_writereg64(u_int reg, uint64_t val)
{
KDASSERT(reg == LAPIC_ICRLO);
x86_mfence();
wrmsr(MSR_X2APIC_BASE + (reg >> 4), val);
}
static void
x2apic_write_icr(uint32_t hi, uint32_t lo)
{
x2apic_writereg64(LAPIC_ICRLO, ((uint64_t)hi << 32) | lo);
}
static uint32_t
x2apic_cpu_number(void)
{
return x2apic_readreg(LAPIC_ID);
}
uint32_t
lapic_readreg(u_int reg)
{
if (x2apic_mode)
return x2apic_readreg(reg);
return i82489_readreg(reg);
}
void
lapic_writereg(u_int reg, uint32_t val)
{
if (x2apic_mode)
x2apic_writereg(reg, val);
else
i82489_writereg(reg, val);
}
void
lapic_write_tpri(uint32_t val)
{
val &= LAPIC_TPRI_MASK;
#ifdef i386
lapic_writereg(LAPIC_TPRI, val);
#else
lcr8(val >> 4);
#endif
}
uint32_t
lapic_cpu_number(void)
{
if (x2apic_mode)
return x2apic_cpu_number();
return i82489_cpu_number();
}
static void
lapic_enable_x2apic(void)
{
uint64_t apicbase;
apicbase = rdmsr(MSR_APICBASE);
if (!ISSET(apicbase, APICBASE_EN)) {
apicbase |= APICBASE_EN;
wrmsr(MSR_APICBASE, apicbase);
}
apicbase |= APICBASE_EXTD;
wrmsr(MSR_APICBASE, apicbase);
}
bool
lapic_is_x2apic(void)
{
uint64_t msr;
if (!ISSET(cpu_feature[0], CPUID_APIC) ||
rdmsr_safe(MSR_APICBASE, &msr) == EFAULT)
return false;
return (msr & (APICBASE_EN | APICBASE_EXTD)) ==
(APICBASE_EN | APICBASE_EXTD);
}
static void
lapic_setup_bsp(paddr_t lapic_base)
{
u_int regs[6];
const char *reason = NULL;
const char *hw_vendor;
bool bios_x2apic;
if (ISSET(cpu_feature[1], CPUID2_X2APIC)) {
#if NACPICA > 0
if (acpi_present) {
ACPI_TABLE_DMAR *dmar;
ACPI_STATUS status;
status = AcpiGetTable(ACPI_SIG_DMAR, 1,
(ACPI_TABLE_HEADER **)&dmar);
if (ACPI_SUCCESS(status)) {
if (ISSET(dmar->Flags, ACPI_DMAR_X2APIC_OPT_OUT)) {
reason = "by DMAR table";
}
AcpiPutTable(&dmar->Header);
}
}
#endif
if (vm_guest == VM_GUEST_VMWARE) {
vmt_hvcall(VM_CMD_GET_VCPU_INFO, regs);
if (ISSET(regs[0], VCPUINFO_VCPU_RESERVED) ||
!ISSET(regs[0], VCPUINFO_LEGACY_X2APIC))
reason = "inside VMWare without intr "
"redirection";
} else if (vm_guest == VM_GUEST_XENHVM) {
reason = "due to running under XEN";
} else if (vm_guest == VM_GUEST_NO &&
CPUID_TO_FAMILY(curcpu()->ci_signature) == 6 &&
CPUID_TO_MODEL(curcpu()->ci_signature) == 0x2a) {
hw_vendor = pmf_get_platform("board-vendor");
if (hw_vendor != NULL) {
if (strcmp(hw_vendor, "LENOVO") == 0 ||
strcmp(hw_vendor, "ASUSTeK Computer Inc.") == 0) {
reason = "for a suspected SandyBridge "
"BIOS bug";
}
}
}
bios_x2apic = lapic_is_x2apic();
if (reason != NULL && bios_x2apic) {
aprint_verbose("x2APIC should be disabled %s but "
"already enabled by BIOS; enabling.\n", reason);
reason = NULL;
}
if (reason == NULL)
x2apic_mode = true;
else
aprint_verbose("x2APIC available but disabled %s\n",
reason);
if (x2apic_enable != x2apic_mode) {
if (bios_x2apic && !x2apic_enable)
aprint_verbose("x2APIC disabled by user and "
"enabled by BIOS; ignoring user setting.\n");
else
x2apic_mode = x2apic_enable;
}
}
if (x2apic_mode) {
x86_ipi = x2apic_ipi;
#if NIOAPIC > 0
struct ioapic_softc *ioapic;
for (ioapic = ioapics; ioapic != NULL; ioapic = ioapic->sc_next) {
ioapic->sc_pic.pic_edge_stubs = x2apic_edge_stubs;
ioapic->sc_pic.pic_level_stubs = x2apic_level_stubs;
}
#endif
#if defined(DDB) && defined(MULTIPROCESSOR)
#ifdef __x86_64__
struct idt_vec *iv = &(cpu_info_primary.ci_idtvec);
idt_descriptor_t *idt = iv->iv_idt;
set_idtgate(&idt[ddb_vec], &Xintr_x2apic_ddbipi, 1,
SDT_SYS386IGT, SEL_KPL, GSEL(GCODE_SEL, SEL_KPL));
#else
#endif
#endif
x86_disable_intr();
lapic_enable_x2apic();
#ifdef MULTIPROCESSOR
cpu_init_first();
#endif
lapic_write_tpri(0);
x86_enable_intr();
} else
lapic_map(lapic_base);
}
static void
lapic_map(paddr_t lapic_base)
{
pt_entry_t *pte;
vaddr_t va = local_apic_va;
if (CPUID_TO_FAMILY(curcpu()->ci_signature) >= 6) {
lapic_base = (paddr_t)rdmsr(MSR_APICBASE);
if ((lapic_base & APICBASE_PHYSADDR) == 0) {
lapic_base |= LAPIC_BASE;
}
wrmsr(MSR_APICBASE, lapic_base | APICBASE_EN);
lapic_base &= APICBASE_PHYSADDR;
}
x86_disable_intr();
pte = kvtopte(va);
*pte = lapic_base | PTE_W | PTE_P | PTE_PCD | pmap_pg_g | pmap_pg_nx;
invlpg(va);
#ifdef MULTIPROCESSOR
cpu_init_first();
#endif
lapic_write_tpri(0);
x86_enable_intr();
}
void
lapic_enable(void)
{
lapic_writereg(LAPIC_SVR, LAPIC_SVR_ENABLE | LAPIC_SPURIOUS_VECTOR);
}
void
lapic_set_lvt(void)
{
struct cpu_info *ci = curcpu();
int i;
struct mp_intr_map *mpi;
uint32_t lint0, lint1;
#ifdef MULTIPROCESSOR
if (mp_verbose) {
apic_format_redir(device_xname(ci->ci_dev), "prelint", 0,
APIC_VECTYPE_LAPIC_LVT, 0, lapic_readreg(LAPIC_LVT_LINT0));
apic_format_redir(device_xname(ci->ci_dev), "prelint", 1,
APIC_VECTYPE_LAPIC_LVT, 0, lapic_readreg(LAPIC_LVT_LINT1));
}
#endif
lint0 = LAPIC_DLMODE_EXTINT;
if (nioapics > 0 || !CPU_IS_PRIMARY(curcpu()))
lint0 |= LAPIC_LVT_MASKED;
lapic_writereg(LAPIC_LVT_LINT0, lint0);
lint1 = LAPIC_DLMODE_NMI;
if (!CPU_IS_PRIMARY(curcpu()))
lint1 |= LAPIC_LVT_MASKED;
lapic_writereg(LAPIC_LVT_LINT1, lint1);
for (i = 0; i < mp_nintr; i++) {
mpi = &mp_intrs[i];
if (mpi->ioapic == NULL && (mpi->cpu_id == MPS_ALL_APICS ||
mpi->cpu_id == ci->ci_cpuid)) {
if (mpi->ioapic_pin > 1)
aprint_error_dev(ci->ci_dev,
"%s: WARNING: bad pin value %d\n",
__func__, mpi->ioapic_pin);
if (mpi->ioapic_pin == 0)
lapic_writereg(LAPIC_LVT_LINT0, mpi->redir);
else
lapic_writereg(LAPIC_LVT_LINT1, mpi->redir);
}
}
#ifdef MULTIPROCESSOR
if (mp_verbose)
lapic_dump();
#endif
}
void
lapic_boot_init(paddr_t lapic_base)
{
struct idt_vec *iv = &(cpu_info_primary.ci_idtvec);
lapic_setup_bsp(lapic_base);
#ifdef MULTIPROCESSOR
idt_vec_reserve(iv, LAPIC_IPI_VECTOR);
idt_vec_set(iv, LAPIC_IPI_VECTOR,
x2apic_mode ? Xintr_x2apic_ipi : Xintr_lapic_ipi);
idt_vec_reserve(iv, LAPIC_TLB_VECTOR);
idt_vec_set(iv, LAPIC_TLB_VECTOR,
x2apic_mode ? Xintr_x2apic_tlb : Xintr_lapic_tlb);
#endif
idt_vec_reserve(iv, LAPIC_SPURIOUS_VECTOR);
idt_vec_set(iv, LAPIC_SPURIOUS_VECTOR, Xintrspurious);
idt_vec_reserve(iv, LAPIC_TIMER_VECTOR);
idt_vec_set(iv, LAPIC_TIMER_VECTOR,
x2apic_mode ? Xintr_x2apic_ltimer : Xintr_lapic_ltimer);
}
static uint32_t
lapic_gettick(void)
{
return lapic_readreg(LAPIC_CCR_TIMER);
}
#include <sys/kernel.h>
uint32_t lapic_tval;
uint32_t lapic_per_second;
uint32_t lapic_frac_usec_per_cycle;
uint64_t lapic_frac_cycle_per_usec;
uint32_t lapic_delaytab[26];
static u_int
lapic_get_timecount(struct timecounter *tc)
{
struct cpu_info *ci;
uint32_t cur_timer;
int s;
s = splhigh();
ci = curcpu();
cur_timer = lapic_gettick();
if (cur_timer >= lapic_tval - 1) {
uint16_t reg = LAPIC_IRR + LAPIC_TIMER_VECTOR / 32 * 16;
if (lapic_readreg(reg) & (1 << (LAPIC_TIMER_VECTOR % 32))) {
cur_timer -= lapic_tval;
}
} else if (ci->ci_ipending & (1ULL << LIR_TIMER))
cur_timer = lapic_gettick() - lapic_tval;
cur_timer = ci->ci_lapic_counter - cur_timer;
splx(s);
return cur_timer;
}
static struct timecounter lapic_timecounter = {
.tc_get_timecount = lapic_get_timecount,
.tc_counter_mask = ~0u,
.tc_name = "lapic",
.tc_quality =
#ifndef MULTIPROCESSOR
2100,
#else
-100,
#endif
};
extern u_int i8254_get_timecount(struct timecounter *);
void
lapic_clockintr(void *arg, struct intrframe *frame)
{
struct cpu_info *ci = curcpu();
ci->ci_lapic_counter += lapic_tval;
ci->ci_isources[LIR_TIMER]->is_evcnt.ev_count++;
hardclock((struct clockframe *)frame);
}
void
lapic_reset(void)
{
lapic_writereg(LAPIC_LVT_TIMER,
LAPIC_LVT_TMM_PERIODIC | LAPIC_LVT_MASKED);
lapic_writereg(LAPIC_DCR_TIMER, LAPIC_DCRT_DIV1);
lapic_writereg(LAPIC_ICR_TIMER, lapic_tval);
lapic_writereg(LAPIC_LVT_TIMER,
LAPIC_LVT_TMM_PERIODIC | LAPIC_TIMER_VECTOR);
lapic_writereg(LAPIC_EOI, 0);
}
static void
lapic_initclock(void)
{
if (curcpu() == &cpu_info_primary) {
if (!lapic_from_cpuid)
lapic_calibrate_timer(true);
lapic_timecounter.tc_frequency = lapic_per_second;
tc_init(&lapic_timecounter);
}
lapic_reset();
}
void
lapic_calibrate_timer(bool secondpass)
{
struct cpu_info *ci = curcpu();
uint64_t tmp;
int i;
char tbuf[9];
KASSERT(ci == &cpu_info_primary);
aprint_debug_dev(ci->ci_dev, "%scalibrating local timer\n",
secondpass ? "re" : "");
x86_disable_intr();
lapic_writereg(LAPIC_LVT_TIMER, LAPIC_LVT_MASKED);
lapic_writereg(LAPIC_DCR_TIMER, LAPIC_DCRT_DIV1);
lapic_writereg(LAPIC_ICR_TIMER, 0x80000000);
(void)lapic_gettick();
if (secondpass && cpu_hascounter()) {
uint64_t l0, l1, t0, t1;
(void)cpu_counter();
t0 = cpu_counter();
l0 = lapic_gettick();
t0 += cpu_counter();
DELAY(50000);
t1 = cpu_counter();
l1 = lapic_gettick();
t1 += cpu_counter();
tmp = (l0 - l1) * cpu_frequency(ci) / ((t1 - t0 + 1) / 2);
lapic_per_second = rounddown(tmp + 500, 1000);
} else if (lapic_per_second == 0) {
unsigned int seen, delta, initial_i8254, initial_lapic;
unsigned int cur_i8254, cur_lapic;
(void)gettick();
initial_lapic = lapic_gettick();
initial_i8254 = gettick();
for (seen = 0; seen < TIMER_FREQ / 100; seen += delta) {
cur_i8254 = gettick();
if (cur_i8254 > initial_i8254)
delta = x86_rtclock_tval - (cur_i8254 - initial_i8254);
else
delta = initial_i8254 - cur_i8254;
initial_i8254 = cur_i8254;
}
cur_lapic = lapic_gettick();
tmp = initial_lapic - cur_lapic;
lapic_per_second = (tmp * TIMER_FREQ + seen / 2) / seen;
}
x86_enable_intr();
humanize_number(tbuf, sizeof(tbuf), lapic_per_second, "Hz", 1000);
aprint_debug_dev(ci->ci_dev, "apic clock running at %s\n", tbuf);
if (lapic_per_second != 0) {
lapic_tval = (lapic_per_second * 2) / hz;
lapic_tval = (lapic_tval / 2) + (lapic_tval & 0x1);
lapic_writereg(LAPIC_LVT_TIMER, LAPIC_LVT_TMM_PERIODIC
| LAPIC_LVT_MASKED | LAPIC_TIMER_VECTOR);
lapic_writereg(LAPIC_DCR_TIMER, LAPIC_DCRT_DIV1);
lapic_writereg(LAPIC_ICR_TIMER, lapic_tval);
tmp = (1000000 * (uint64_t)1 << 32) / lapic_per_second;
lapic_frac_usec_per_cycle = tmp;
tmp = (lapic_per_second * (uint64_t)1 << 32) / 1000000;
lapic_frac_cycle_per_usec = tmp;
for (i = 0; i < 26; i++)
lapic_delaytab[i] = (lapic_frac_cycle_per_usec * i) >>
32;
if (vm_guest == VM_GUEST_KVM) {
lapic_broken_periodic = true;
lapic_timecounter.tc_quality = -100;
aprint_debug_dev(ci->ci_dev,
"applying KVM timer workaround\n");
}
if (!secondpass) {
delay_func = lapic_delay;
x86_initclock_func = lapic_initclock;
initrtclock(0);
}
}
}
static void
lapic_delay(unsigned int usec)
{
int32_t xtick, otick;
int64_t deltat;
kpreempt_disable();
otick = lapic_gettick();
if (usec <= 0) {
kpreempt_enable();
return;
}
if (usec <= 25)
deltat = lapic_delaytab[usec];
else
deltat = (lapic_frac_cycle_per_usec * usec) >> 32;
while (deltat > 0) {
xtick = lapic_gettick();
if (lapic_broken_periodic && xtick == 0 && otick == 0) {
lapic_reset();
xtick = lapic_gettick();
if (xtick == 0)
panic("lapic timer stopped ticking");
}
if (xtick > otick)
deltat -= lapic_tval - (xtick - otick);
else
deltat -= otick - xtick;
otick = xtick;
x86_pause();
}
kpreempt_enable();
}
static void
i82489_icr_wait(void)
{
#ifdef DIAGNOSTIC
unsigned j = 100000;
#endif
while ((i82489_readreg(LAPIC_ICRLO) & LAPIC_DLSTAT_BUSY) != 0) {
x86_pause();
#ifdef DIAGNOSTIC
j--;
if (j == 0)
panic("i82489_icr_wait: busy");
#endif
}
}
static int
i82489_ipi_init(int target)
{
uint32_t esr;
i82489_writereg(LAPIC_ESR, 0);
(void)i82489_readreg(LAPIC_ESR);
i82489_writereg(LAPIC_ICRHI, target << LAPIC_ID_SHIFT);
i82489_writereg(LAPIC_ICRLO, LAPIC_DLMODE_INIT | LAPIC_LEVEL_ASSERT);
i82489_icr_wait();
delay_func(10000);
i82489_writereg(LAPIC_ICRLO,
LAPIC_DLMODE_INIT | LAPIC_TRIGMODE_LEVEL | LAPIC_LEVEL_DEASSERT);
i82489_icr_wait();
if ((i82489_readreg(LAPIC_ICRLO) & LAPIC_DLSTAT_BUSY) != 0)
return EBUSY;
esr = i82489_readreg(LAPIC_ESR);
if (esr != 0)
aprint_debug("%s: ESR %08x\n", __func__, esr);
return 0;
}
static int
i82489_ipi_startup(int target, int vec)
{
uint32_t esr;
i82489_writereg(LAPIC_ESR, 0);
(void)i82489_readreg(LAPIC_ESR);
i82489_icr_wait();
i82489_writereg(LAPIC_ICRHI, target << LAPIC_ID_SHIFT);
i82489_writereg(LAPIC_ICRLO, vec | LAPIC_DLMODE_STARTUP |
LAPIC_LEVEL_ASSERT);
i82489_icr_wait();
if ((i82489_readreg(LAPIC_ICRLO) & LAPIC_DLSTAT_BUSY) != 0)
return EBUSY;
esr = i82489_readreg(LAPIC_ESR);
if (esr != 0)
aprint_debug("%s: ESR %08x\n", __func__, esr);
return 0;
}
static int
i82489_ipi(int vec, int target, int dl)
{
int result, s;
s = splhigh();
i82489_icr_wait();
if ((target & LAPIC_DEST_MASK) == 0)
i82489_writereg(LAPIC_ICRHI, target << LAPIC_ID_SHIFT);
i82489_writereg(LAPIC_ICRLO,
(target & LAPIC_DEST_MASK) | vec | dl | LAPIC_LEVEL_ASSERT);
#ifdef DIAGNOSTIC
i82489_icr_wait();
result = (i82489_readreg(LAPIC_ICRLO) & LAPIC_DLSTAT_BUSY) ? EBUSY : 0;
#else
result = 0;
#endif
splx(s);
return result;
}
static int
x2apic_ipi_init(int target)
{
x2apic_write_icr(target, LAPIC_DLMODE_INIT | LAPIC_LEVEL_ASSERT);
delay_func(10000);
x2apic_write_icr(0,
LAPIC_DLMODE_INIT | LAPIC_TRIGMODE_LEVEL | LAPIC_LEVEL_DEASSERT);
return 0;
}
static int
x2apic_ipi_startup(int target, int vec)
{
x2apic_write_icr(target,
vec | LAPIC_DLMODE_STARTUP | LAPIC_LEVEL_ASSERT);
return 0;
}
static int
x2apic_ipi(int vec, int target, int dl)
{
uint32_t dest_id = 0;
if ((target & LAPIC_DEST_MASK) == 0)
dest_id = target;
x2apic_write_icr(dest_id,
(target & LAPIC_DEST_MASK) | vec | dl | LAPIC_LEVEL_ASSERT);
return 0;
}
int
x86_ipi_init(int target)
{
if (x2apic_mode)
return x2apic_ipi_init(target);
return i82489_ipi_init(target);
}
int
x86_ipi_startup(int target, int vec)
{
if (x2apic_mode)
return x2apic_ipi_startup(target, vec);
return i82489_ipi_startup(target, vec);
}
static void
lapic_hwmask(struct pic *pic, int pin)
{
int reg;
uint32_t val;
reg = LAPIC_LVT_TIMER + (pin << 4);
val = lapic_readreg(reg);
val |= LAPIC_LVT_MASKED;
lapic_writereg(reg, val);
}
static void
lapic_hwunmask(struct pic *pic, int pin)
{
int reg;
uint32_t val;
reg = LAPIC_LVT_TIMER + (pin << 4);
val = lapic_readreg(reg);
val &= ~LAPIC_LVT_MASKED;
lapic_writereg(reg, val);
}
static void
lapic_setup(struct pic *pic, struct cpu_info *ci,
int pin, int idtvec, int type)
{
}
void
lapic_dump(void)
{
struct cpu_info *ci = curcpu();
#define APIC_LVT_PRINT(ci, where, idx, lvtreg) \
apic_format_redir(device_xname(ci->ci_dev), where, (idx), \
APIC_VECTYPE_LAPIC_LVT, 0, lapic_readreg(lvtreg))
APIC_LVT_PRINT(ci, "cmci", 0, LAPIC_LVT_CMCI);
APIC_LVT_PRINT(ci, "timer", 0, LAPIC_LVT_TIMER);
APIC_LVT_PRINT(ci, "thermal", 0, LAPIC_LVT_THERM);
APIC_LVT_PRINT(ci, "pcint", 0, LAPIC_LVT_PCINT);
APIC_LVT_PRINT(ci, "lint", 0, LAPIC_LVT_LINT0);
APIC_LVT_PRINT(ci, "lint", 1, LAPIC_LVT_LINT1);
APIC_LVT_PRINT(ci, "err", 0, LAPIC_LVT_ERR);
#undef APIC_LVT_PRINT
}
#else
void
lapic_boot_init(paddr_t lapic_base)
{
}
#endif