#include #include #include #define IA32_APIC_BASE 0x1B #define APIC_BASE_ENABLE (1ull << 11) #define APIC_BASE_ADDR_MASK 0xFFFFF000ull #define APIC_DEFAULT_BASE 0xFEE00000u #define APIC_REG_ID 0x20 #define APIC_REG_VERSION 0x30 #define APIC_REG_EOI 0xB0 #define APIC_REG_SPURIOUS 0xF0 #define APIC_REG_TIMER 0x320 /* bit 16 of the timer control register masks it */ #define APIC_TIMER_MASKED (1u << 16) #define APIC_REG_ICR 0x300 #define APIC_REG_ICR_HIGH 0x310 #define ICR_DELIVERY_STATUS 0x1000u /* bit 12: set while the IPI is in flight */ #define ICR_TRIGGER_MODE 0x4000u /* bit 14: level vs edge */ #define ICR_SHORTHAND_SHIFT 18 /* bounded so a lost IPI surfaces as a failed bringup, never a hang */ #define ICR_WAIT_SPINS 1000000u #define SPURIOUS_VECTOR 0xFF static volatile uint32_t *apic_reg(uint32_t reg) { return (volatile uint32_t *)(uint64_t)(lapic_base() + reg); } void lapic_enable(void) { uint64_t base = rdmsr(IA32_APIC_BASE); /* * ORDER MATTERS: mask the timer BEFORE enabling the LAPIC. * * firmware leaves the LAPIC timer running on whatever vector its divide * config selects, which is 0x20 here. enabling the LAPIC first opens the * delivery path, so that timer can latch vector 0x20 in the ISR before the * mask below lands. IF is clear at this point so the request is held pending * rather than taken, and is delivered at the first `sti` much later, which * is the self-IPI test. that lands on the default gate and halts the kernel, * so the failure reads as "unexpected interrupt during the self-IPI test" * with nothing pointing at the timer. */ *apic_reg(APIC_REG_TIMER) = APIC_TIMER_MASKED; *apic_reg(APIC_REG_SPURIOUS) = (uint32_t)(SPURIOUS_VECTOR | (1u << 8)); if ((base & APIC_BASE_ENABLE) == 0) { wrmsr(IA32_APIC_BASE, base | APIC_BASE_ENABLE); } /* * drain anything firmware already had in flight. masking the LVT stops new * requests but does not retract one already pending, and there is no way to * read the ISR to find out what it was. acknowledge blindly: an EOI with * nothing outstanding is a no-op. */ lapic_eoi(); } uint32_t lapic_base(void) { uint32_t base = (uint32_t)(rdmsr(IA32_APIC_BASE) & APIC_BASE_ADDR_MASK); if (base == 0) { base = APIC_DEFAULT_BASE; } return base; } uint32_t lapic_id(void) { /* * only the low 8 bits of the xAPIC ID register are meaningful; bits above * that read back as zero on real hardware but are not architecturally * guaranteed to, so mask rather than trust the read. widened from uint8_t * because an x2APIC MADT reports 32-bit IDs; see lapic_ipi_send_flags for * what happens to those. */ return (uint32_t)(*apic_reg(APIC_REG_ID) >> 24) & 0xffu; } void lapic_eoi(void) { *apic_reg(APIC_REG_EOI) = 0; } bool lapic_ipi_wait(void) { uint32_t spins = ICR_WAIT_SPINS; while ((*apic_reg(APIC_REG_ICR) & ICR_DELIVERY_STATUS) != 0) { if (spins-- == 0) { return false; } } return true; } /* * returns 0 if the IPI was sent, non-zero if the destination could not be * addressed at all (an x2APIC ID beyond the xAPIC ICR's 8-bit field). nothing * is delivered in that case. */ uint32_t lapic_ipi_send_flags(uint32_t delivery, uint32_t shorthand, uint32_t vector, uint32_t apic_id, uint32_t flags) { /* * the delivery-status bit reads from the high half of the 64-bit ICR, and * the destination field only applies when shorthand is unused, so it is * harmless to always program ICR_HIGH. */ if (shorthand == LAPIC_DEST) { /* * the xAPIC destination field is only bits 24..31 of ICR_HIGH, so it can * name at most 256 processors. an ID above that comes from an x2APIC MADT * entry and would be silently truncated, sending INIT to processor 0 and * resetting the BSP instead of the intended AP. * * addressing those needs IA32_X2APIC_ICR (MSR 0x8B), which is only * reachable once x2APIC mode is enabled in IA32_APIC_BASE. that is not done * yet, so refuse loudly instead of misdirecting the IPI. callers treat a * non-zero return as "this AP was not started". */ if (apic_id > 0xffu) { return 1; } *apic_reg(APIC_REG_ICR_HIGH) = apic_id << 24; } else { *apic_reg(APIC_REG_ICR_HIGH) = 0; } *apic_reg(APIC_REG_ICR) = vector | (delivery << 8) | (shorthand << ICR_SHORTHAND_SHIFT) | flags; lapic_ipi_wait(); return 0; } uint32_t lapic_ipi_send(uint32_t delivery, uint32_t shorthand, uint32_t vector, uint32_t apic_id) { return lapic_ipi_send_flags(delivery, shorthand, vector, apic_id, 0); } uint32_t lapic_ipi_init_assert(uint32_t apic_id) { return lapic_ipi_send_flags(LAPIC_IPI_INIT, LAPIC_DEST, 0, apic_id, ICR_TRIGGER_MODE); } uint32_t lapic_ipi_init_deassert(uint32_t apic_id) { return lapic_ipi_send_flags(LAPIC_IPI_INIT, LAPIC_DEST, 0, apic_id, 0); } uint32_t lapic_ipi_sipi(uint32_t apic_id, uint8_t vector) { return lapic_ipi_send_flags(LAPIC_IPI_STARTUP, LAPIC_DEST, vector, apic_id, 0); }