aboutsummaryrefslogtreecommitdiff
path: root/kernel/main/apic.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/main/apic.c')
-rw-r--r--kernel/main/apic.c648
1 files changed, 648 insertions, 0 deletions
diff --git a/kernel/main/apic.c b/kernel/main/apic.c
new file mode 100644
index 0000000..4d6f21c
--- /dev/null
+++ b/kernel/main/apic.c
@@ -0,0 +1,648 @@
+#include "types.h"
+
+#include "boot/config.h"
+
+#include "main/acpi.h"
+#include "main/apic.h"
+#include "main/cpuid.h"
+#include "main/interrupt.h"
+#include "main/io.h"
+
+#define APIC_SIGNATURE (*(uint32_t *)"APIC")
+
+#define TYPE_LAPIC 0
+#define TYPE_IOAPIC 1
+
+/* For disabling interrupts on the 8259 PIC, it needs to be
+ * disabled to use the APIC
+ */
+#define PIC_COMPLETE_MASK 0xff
+
+#define PIC1 0x20
+#define PIC1_COMMAND PIC1
+#define PIC1_DATA (PIC1 + 1)
+#define PIC1_VECTOR 0x20
+
+#define PIC2 0xa0
+#define PIC2_COMMAND PIC2
+#define PIC2_DATA (PIC2 + 1)
+#define PIC2_VECTOR 0x28
+
+#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
+#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
+#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
+#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
+#define ICW1_INIT 0x10 /* Initialization - required! */
+
+#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
+#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
+#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
+#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
+#define ICW4_SFNM 0x10 /* Special fully nested (not) */
+
+/* For enabling interrupts from the APIC rather than the
+ * Master PIC, use the Interrupt Mode Configuration Register (IMCR)
+ */
+
+#define SELECT_REGISTER 0x22
+#define IMCR_REGISTER 0x70
+#define ENABLE_APIC 0x23
+#define ENABLE_APIC_PORT 0x01
+
+/* For Local APICS */
+#define IA32_APIC_BASE_MSR 0x1b
+#define IA32_APIC_BASE_MSR_ENABLE 0x800
+#define LOCAL_APIC_SPURIOUS_REGISTER 0xf0
+#define LOCAL_APIC_ENABLE_INTERRUPT 0x100
+
+#define LOCAL_APIC_ID 0x20
+#define LOCAL_APIC_VERSION 0x30
+#define LOCAL_APIC_TASKPRIOR 0x80
+#define LOCAL_APIC_EOI 0xb0
+#define LOCAL_APIC_LDR 0xd0
+#define LOCAL_APIC_DFR 0xe0
+#define LOCAL_APIC_SPURIOUS 0xf0
+#define LOCAL_APIC_ESR 0x280
+#define LOCAL_APIC_ICRL 0x300
+#define LOCAL_APIC_ICRH 0x310
+#define LOCAL_APIC_LVT_TMR 0x320
+#define LOCAL_APIC_LVT_PERF 0x340
+#define LOCAL_APIC_LVT_LINT0 0x350
+#define LOCAL_APIC_LVT_LINT1 0x360
+#define LOCAL_APIC_LVT_ERR 0x370
+#define LOCAL_APIC_TMRINITCNT 0x380
+#define LOCAL_APIC_TMRCURRCNT 0x390
+#define LOCAL_APIC_TMRDIV 0x3e0
+#define LOCAL_APIC_LAST 0x38f
+#define LOCAL_APIC_DISABLE 0x10000
+#define LOCAL_APIC_SW_ENABLE 0x100
+#define LOCAL_APIC_CPUFOCUS 0x200
+#define LOCAL_APIC_NMI (4 << 8)
+#define LOCAL_APIC_TMR_PERIODIC 0x20000
+#define LOCAL_APIC_TMR_BASEDIV (1 << 20)
+
+#define APIC_ADDR (apic->at_addr + PHYS_OFFSET)
+#define APIC_REG(x) (*(uint32_t *)(APIC_ADDR + (x)))
+#define LAPICID APIC_REG(LOCAL_APIC_ID)
+#define LAPICVER APIC_REG(LOCAL_APIC_VERSION)
+#define LAPICTPR APIC_REG(LOCAL_APIC_TASKPRIOR)
+#define LAPICSPUR APIC_REG(LOCAL_APIC_SPURIOUS)
+#define LAPICEOI APIC_REG(LOCAL_APIC_EOI)
+#define LAPICDFR APIC_REG(LOCAL_APIC_DFR)
+#define LAPICLDR APIC_REG(LOCAL_APIC_LDR)
+#define LAPICLVTTMR APIC_REG(LOCAL_APIC_LVT_TMR)
+#define LAPICLVTPERF APIC_REG(LOCAL_APIC_LVT_PERF)
+#define LAPICLVTLINT0 APIC_REG(LOCAL_APIC_LVT_LINT0)
+#define LAPICLVTLINT1 APIC_REG(LOCAL_APIC_LVT_LINT1)
+#define LAPICLVTERR APIC_REG(LOCAL_APIC_LVT_ERR)
+#define LAPICTIC APIC_REG(LOCAL_APIC_TMRINITCNT)
+#define LAPICTCC APIC_REG(LOCAL_APIC_TMRCURRCNT)
+#define LAPICTMRDIV APIC_REG(LOCAL_APIC_TMRDIV)
+#define LAPICICRH APIC_REG(LOCAL_APIC_ICRH)
+#define LAPICICRL APIC_REG(LOCAL_APIC_ICRL)
+#define LAPICESR APIC_REG(LOCAL_APIC_ESR)
+
+/* IO APIC */
+#define IOAPIC_IOWIN 0x10
+
+/* Some configuration for the IO APIC */
+#define IOAPIC_ID 0x00
+#define IOAPIC_VER 0x01
+#define IOAPIC_ARB 0x02
+#define IOAPIC_REDTBL 0x03
+
+#define IOAPIC_ADDR (ioapic->at_addr + PHYS_OFFSET)
+#define IOAPIC (*(uint32_t *)IOAPIC_ADDR)
+#define IOAPICWIN (*(uint32_t *)(IOAPIC_ADDR + IOAPIC_IOWIN))
+
+/* Helpful Macros for IO APIC programming */
+#define BIT_SET(data, bit) \
+ do \
+ { \
+ (data) = ((data) | (0x1 << (bit))); \
+ } while (0);
+#define BIT_UNSET(data, bit) \
+ do \
+ { \
+ (data) = ((data) & ~(0x1 << (bit))); \
+ } while (0);
+
+#define IRQ_TO_OFFSET(irq, part) ((uint8_t)((0x10 + (irq * 2) + part)))
+
+typedef struct apic_table
+{
+ struct acpi_header at_header;
+ uint32_t at_addr;
+ uint32_t at_flags;
+} packed apic_table_t;
+
+typedef struct lapic_table
+{
+ uint8_t at_type;
+ uint8_t at_size;
+ uint8_t at_procid;
+ uint8_t at_apicid;
+ uint32_t at_flags;
+} packed lapic_table_t;
+
+typedef struct ioapic_table
+{
+ uint8_t at_type;
+ uint8_t at_size;
+ uint8_t at_apicid;
+ uint8_t at_reserved;
+ uint32_t at_addr;
+ uint32_t at_inti;
+} packed ioapic_table_t;
+
+static apic_table_t *apic = NULL;
+static ioapic_table_t *ioapic = NULL;
+
+// Use MAX_LAPICS + 1 entries so we can guarantee the last entry is null
+static lapic_table_t *lapics[MAX_LAPICS + 1] = {NULL};
+static long max_apicid;
+
+static long initialized = 0;
+
+// Returns the maximum APIC ID
+inline long apic_max_id() { return max_apicid; }
+
+/* [APIC ID------------------------] */
+inline static long __lapic_getid(void) { return (LAPICID >> 24) & 0xff; }
+
+// Returns the APIC ID of the current processor/core
+inline long apic_current_id() { return __lapic_getid(); }
+
+inline static uint32_t __lapic_getver(void) { return LAPICVER & 0xff; }
+
+inline static void __lapic_setspur(uint8_t intr)
+{
+ uint32_t data = LAPICSPUR | LOCAL_APIC_SW_ENABLE;
+ *((uint8_t *)&data) = intr;
+ LAPICSPUR = data;
+}
+
+/* [LOGICID-------------------------] */
+inline static void __lapic_setlogicalid(uint8_t id)
+{
+ LAPICLDR = ((uint32_t)id) << 24;
+}
+
+inline static uint32_t ioapic_read(uint8_t reg_offset)
+{
+ /* Tell IOREGSEL where we want to read from */
+ IOAPIC = reg_offset;
+ return IOAPICWIN;
+}
+
+inline static void ioapic_write(uint8_t reg_offset, uint32_t value)
+{
+ /* Tell IOREGSEL where to write to */
+ IOAPIC = reg_offset;
+ /* Write the value to IOWIN */
+ IOAPICWIN = value;
+}
+
+inline static uint32_t __ioapic_getid(void)
+{
+ return (ioapic_read(IOAPIC_ID) >> 24) & 0x0f;
+}
+
+inline static uint32_t __ioapic_getver(void)
+{
+ return ioapic_read(IOAPIC_VER) & 0xff;
+}
+
+inline static uint32_t __ioapic_getmaxredir(void)
+{
+ return (ioapic_read(IOAPIC_VER) >> 16) & 0xff;
+}
+
+inline static void __ioapic_setredir(uint32_t irq, uint8_t intr)
+{
+ /* Read in the redirect table lower register first */
+ uint32_t data = ioapic_read(IRQ_TO_OFFSET(irq, 0));
+ /* Set the interrupt vector */
+ ((uint8_t *)&data)[0] = intr;
+ /* Set bit 8, unset bits 9,10 to set interrupt delivery mode to lowest
+ * priority */
+ BIT_SET(data, 8);
+ BIT_UNSET(data, 9);
+ BIT_UNSET(data, 10);
+ /* Set bit 11 to set the destination mode to a logical destination */
+ BIT_SET(data, 11);
+ /* Unset bit 13 to set the pin polarity to Active High */
+ BIT_UNSET(data, 13);
+ /* Unset bit 15 to set the trigger mode to Edge */
+ BIT_UNSET(data, 15);
+ /* Write this value to the apic */
+ ioapic_write(IRQ_TO_OFFSET(irq, 0), data);
+ /* Now deal with the higher order register */
+ data = ioapic_read(IRQ_TO_OFFSET(irq, 1));
+ ((uint8_t *)&data)[3] = 0xff;
+ ioapic_write(IRQ_TO_OFFSET(irq, 1), data);
+}
+
+inline static void __ioapic_setmask(uint32_t irq, int mask)
+{
+ uint32_t data = ioapic_read(IRQ_TO_OFFSET(irq, 0));
+ if (mask)
+ {
+ BIT_SET(data, 16);
+ }
+ else
+ {
+ BIT_UNSET(data, 16);
+ }
+ ioapic_write(IRQ_TO_OFFSET(irq, 0), data);
+}
+
+static uint32_t apic_exists(void)
+{
+ uint32_t eax, ebx, ecx, edx;
+ cpuid(CPUID_GETFEATURES, &eax, &ebx, &ecx, &edx);
+ return edx & CPUID_FEAT_EDX_APIC;
+}
+
+static void apic_set_base(uint32_t apic)
+{
+ uint32_t edx = 0;
+ uint32_t eax = (apic & 0xfffff000) | IA32_APIC_BASE_MSR_ENABLE;
+ edx = 0;
+ cpuid_set_msr(IA32_APIC_BASE_MSR, eax, edx);
+}
+
+static uint32_t apic_get_base(void)
+{
+ uint32_t eax, edx;
+ cpuid_get_msr(IA32_APIC_BASE_MSR, &eax, &edx);
+ return (eax & 0xfffff000);
+}
+
+static long __apic_err()
+{
+ dbg(DBG_PRINT, "[+] APIC Error: 0x%d", LAPICESR);
+ __asm__("cli; hlt");
+ return 0;
+}
+
+void apic_enable()
+{
+ // [MODE---------------------------]
+ // L
+ LAPICDFR = 0xffffffff;
+
+ KASSERT(apic_current_id() < 8);
+ __lapic_setlogicalid((uint8_t)(1 << apic_current_id()));
+ LAPICLVTTMR = LOCAL_APIC_DISABLE;
+ LAPICLVTPERF = LOCAL_APIC_NMI;
+ LAPICLVTLINT0 = LOCAL_APIC_DISABLE;
+ LAPICLVTLINT1 = LOCAL_APIC_DISABLE;
+ LAPICLVTERR = INTR_APICERR;
+ LAPICTPR = 0;
+ apic_set_base(apic_get_base());
+ apic_setspur(INTR_SPURIOUS);
+ intr_register(INTR_APICERR, __apic_err);
+}
+
+void apic_disable_periodic_timer()
+{
+ LAPICLVTTMR = LOCAL_APIC_DISABLE;
+ LAPICLVTPERF = LOCAL_APIC_NMI;
+ LAPICLVTLINT0 = LOCAL_APIC_DISABLE;
+ LAPICLVTLINT1 = LOCAL_APIC_DISABLE;
+ LAPICTPR = 0;
+}
+
+/* get_cpu_bus_frequency - Uses PIT to determine APIC frequency in Hz (ticks per
+ * second). NOTE: NOT SMP FRIENDLY! Note: For more info, visit the osdev wiki
+ * page on the Programmable Interval Timer. */
+static uint32_t get_cpu_bus_frequency()
+{
+ static uint32_t freq = 0;
+ if (!freq)
+ {
+ /* Division rate: 0b1011 corresponds to division by 1, which does
+ * nothing. */
+ LAPICTMRDIV = 0b1011;
+
+ /* 0x61 controls the PC speaker.
+ * Clearing bit 1 prevents any sound.
+ * Setting bit 0 connects the speaker to the output of PIT channel 2. */
+ outb(0x61, (uint8_t)((inb(0x61) & 0xfd) | 1));
+
+ /* Control reg:
+ * 0x1011 = Channel 2, lobyte/hibyte access
+ * 0x0010 = Mode 1 (hardware one-shot) */
+ outb(0x43, 0xb2);
+
+ /* Not sure why there's an inb, but the two outb send the reload value:
+ * 0x2e9b = 11931, aka 1/100th of the PIT oscillator rate, aka 10 ms. */
+ outb(0x42, 0x9b);
+ inb(0x60);
+ outb(0x42, 0x2e);
+
+ /* Reset the one-shot counter by clearing and resetting bit 0. */
+ uint32_t tmp = (uint32_t)(inb(0x61) & 0xfe);
+ outb(0x61, (uint8_t)tmp);
+ outb(0x61, (uint8_t)(tmp | 1));
+ /* Reset APIC's initial countdown value. */
+ LAPICTIC = 0xffffffff;
+ /* PC speaker sets bit 5 when it hits 0. */
+ while (!(inb(0x61) & 0x20))
+ ;
+ /* Stop the APIC timer */
+ LAPICLVTTMR = LOCAL_APIC_DISABLE;
+ /* Subtract current count from the initial count to get total ticks per
+ * second. */
+ freq = (LAPICTIC - LAPICTCC) * 100;
+ dbgq(DBG_CORE, "CPU Bus Freq: %u ticks per second\n", freq);
+ }
+ return freq;
+}
+
+/* apic_enable_periodic_timer - Starts the periodic timer (continuously send
+ * interrupts) at a given frequency. For more information, refer to: Intel
+ * System Programming Guide, Vol 3A Part 1, 10.5.4. */
+void apic_enable_periodic_timer(uint32_t freq)
+{
+ // TODO: Check this math! Don't assume it's correct...
+
+ uint32_t ticks_per_second = get_cpu_bus_frequency();
+ /* Demand at least the desired precision. */
+ if (ticks_per_second < freq)
+ {
+ panic(
+ "apic timer is not precise enough for desired frequency\n");
+ }
+
+ /* TODO: Pretty sure this can be more precise using the initial count
+ * properly. */
+
+ /* Round the bus frequency down to the nearest multiple of the desired
+ * frequency. If bus/freq is large, the remainder will get amortized to a
+ * degree that should be acceptable for Weenix. */
+ uint32_t rem = ticks_per_second % freq;
+ if (rem > (freq / 2))
+ ticks_per_second += (freq - rem);
+ else
+ ticks_per_second -= rem;
+ // TODO: Provide a warning when there is a lot of drift, e.g. more than
+ // 1/10th inaccuracy per interval
+
+ /* Divide configuration. */
+ uint32_t div = 0b0111; /* Starts at division by 1. */
+ uint32_t tmp = ticks_per_second;
+ for (int i = 1; i < 7; i++)
+ { /* Max division is 2^7. */
+ /* Don't cut the freq in half if it would ruin divisibility. */
+ if ((tmp >> 1) % freq != 0)
+ break;
+ if ((tmp >> 1) < freq)
+ break;
+ /* Cut freq in half. */
+ tmp >>= 1;
+ /* Increment the order of division (1, 2, 4, ...). */
+ div++;
+ }
+
+ uint32_t tmpdiv = div;
+
+ /* Clear bit 3, which probably artificially overflowed. */
+ div &= 0b0111;
+
+ /* APIC DIV register skips bit 2, so if set, move it to bit 3. */
+ if (div & 0b0100)
+ {
+ div &= 0b0011; /* Clear bit 2. */
+ div |= 0b1011; /* Set bit 3. */
+ }
+
+ /* Set up three registers to configure timer:
+ * 1) Initial count: count down from this value, send interrupt upon hitting
+ * 0. */
+ LAPICTIC = tmp / freq;
+ /* 3) Divide config: calculated above to cut bus clock. */
+ LAPICTMRDIV = div;
+ /* 2) LVT timer: use a periodic timer and raise the provided interrupt
+ * vector. */
+ LAPICLVTTMR = LOCAL_APIC_TMR_PERIODIC | INTR_APICTIMER;
+}
+
+static void apic_disable_8259()
+{
+ dbgq(DBG_CORE, "--- DISABLE 8259 PIC ---\n");
+ /* disable 8259 PICs by initializing them and masking all interrupts */
+ /* the first step is initialize them normally */
+ outb(PIC1_COMMAND, ICW1_INIT + ICW1_ICW4);
+ io_wait();
+ outb(PIC2_COMMAND, ICW1_INIT + ICW1_ICW4);
+ io_wait();
+ outb(PIC1_DATA, PIC1_VECTOR);
+ io_wait();
+ outb(PIC2_DATA, PIC2_VECTOR);
+ io_wait();
+ outb(PIC1_DATA, 0x04);
+ io_wait();
+ outb(PIC2_DATA, 0x02);
+ io_wait();
+ outb(PIC1_DATA, ICW4_8086);
+ io_wait();
+ outb(PIC2_DATA, ICW4_8086);
+ /* Now mask all interrupts */
+ dbgq(DBG_CORE, "Masking all interrupts on the i8259 PIC\n");
+ outb(PIC1_DATA, PIC_COMPLETE_MASK);
+ outb(PIC2_DATA, PIC_COMPLETE_MASK);
+}
+
+static void map_apic_addr(uintptr_t paddr)
+{
+ page_mark_reserved((void *)paddr);
+ pt_map(pt_get(), paddr, paddr + PHYS_OFFSET, PT_WRITE | PT_PRESENT,
+ PT_WRITE | PT_PRESENT);
+}
+
+void apic_init()
+{
+ uint8_t *ptr = acpi_table(APIC_SIGNATURE, 0);
+ apic = (apic_table_t *)ptr;
+ KASSERT(NULL != apic && "APIC table not found in ACPI.");
+
+ apic_disable_8259();
+
+ dbgq(DBG_CORE, "--- APIC INIT ---\n");
+ dbgq(DBG_CORE, "local apic paddr: 0x%x\n", apic->at_addr);
+ dbgq(DBG_CORE, "PC-AT compatible: %i\n", apic->at_flags & 0x1);
+ KASSERT(PAGE_ALIGNED((void *)(uintptr_t)apic->at_addr));
+
+ KASSERT(apic->at_addr < 0xffffffff);
+
+ map_apic_addr(apic->at_addr);
+
+ /* Get the tables for the local APIC and IO APICS */
+ uint8_t off = sizeof(*apic);
+ while (off < apic->at_header.ah_size)
+ {
+ uint8_t type = *(ptr + off);
+ uint8_t size = *(ptr + off + 1);
+ lapic_table_t *lapic = NULL;
+ if (TYPE_LAPIC == type)
+ {
+ KASSERT(apic_exists() && "Local APIC does not exist");
+ KASSERT(sizeof(lapic_table_t) == size);
+ lapic = (lapic_table_t *)(ptr + off);
+ KASSERT(lapic->at_apicid < MAX_LAPICS &&
+ "Weenix only supports MAX_LAPICS local APICs");
+ lapics[lapic->at_apicid] = lapic;
+
+ page_mark_reserved(PAGE_ALIGN_DOWN((uintptr_t)lapic - PHYS_OFFSET));
+ max_apicid = lapic->at_apicid;
+
+ dbgq(DBG_CORE, "LAPIC:\n");
+ dbgq(DBG_CORE, " id: 0x%.2x\n",
+ (uint32_t)lapic->at_apicid);
+ dbgq(DBG_CORE, " processor: 0x%.3x\n",
+ (uint32_t)lapic->at_procid);
+ dbgq(DBG_CORE, " enabled: %i\n", apic->at_flags & 0x1);
+ }
+ else if (TYPE_IOAPIC == type)
+ {
+ KASSERT(apic_exists() && "IO APIC does not exist");
+ KASSERT(sizeof(ioapic_table_t) == size);
+ KASSERT(NULL == ioapic && "Weenix only supports a single IO APIC");
+ ioapic = (ioapic_table_t *)(ptr + off);
+ page_mark_reserved(
+ PAGE_ALIGN_DOWN((uintptr_t)ioapic - PHYS_OFFSET));
+ map_apic_addr(ioapic->at_addr);
+
+ dbgq(DBG_CORE, "IOAPIC:\n");
+ dbgq(DBG_CORE, " id: 0x%.2x\n",
+ (uint32_t)ioapic->at_apicid);
+ dbgq(DBG_CORE, " base paddr: 0x%.8x\n", ioapic->at_addr);
+ dbgq(DBG_CORE, " inti addr: 0x%.8x\n", ioapic->at_inti);
+ KASSERT(PAGE_ALIGNED((void *)(uintptr_t)ioapic->at_addr));
+ }
+ else
+ {
+ dbgq(DBG_CORE, "Unknown APIC type: 0x%x\n", (uint32_t)type);
+ }
+ off += size;
+ }
+ KASSERT(NULL != lapics[apic_current_id()] &&
+ "Could not find a local APIC device");
+ KASSERT(NULL != ioapic && "Could not find an IO APIC");
+
+ initialized = 1;
+}
+
+inline long apic_initialized() { return initialized; }
+
+inline uint8_t apic_getipl() { return (uint8_t)LAPICTPR; }
+
+inline void apic_setipl(uint8_t ipl) { LAPICTPR = ipl; }
+
+inline void apic_setspur(uint8_t intr)
+{
+ dbg(DBG_CORE, "mapping spurious interrupts to %u\n", intr);
+ __lapic_setspur(intr);
+}
+
+inline void apic_eoi() { LAPICEOI = 0x0; }
+
+void apic_setredir(uint32_t irq, uint8_t intr)
+{
+ dbg(DBG_CORE, "redirecting irq %u to interrupt %u\n", irq, intr);
+ __ioapic_setredir(irq, intr);
+ __ioapic_setmask(irq, 0);
+}
+
+void apic_start_processor(uint8_t processor, uint8_t execution_page)
+{
+ // [+] TODO FIX MAGIC NUMBERS
+ KASSERT(processor < 8);
+ uint32_t icr_low = 0;
+ icr_low |= 0;
+ icr_low |= DESTINATION_MODE_INIT << 8;
+ BIT_UNSET(icr_low, 11); // physical destination
+
+ BIT_SET(icr_low, 14);
+ BIT_UNSET(icr_low, 15);
+
+ dbg(DBG_CORE, "Sending IPI: ICR_LOW = 0x%.8x, ICR_HIGH = 0x%.8x\n", icr_low,
+ processor << 24);
+ LAPICICRH = processor << 24;
+ LAPICICRL = icr_low;
+
+ apic_wait_ipi();
+
+ icr_low = 0;
+ icr_low |= execution_page;
+ icr_low |= DESTINATION_MODE_SIPI << 8;
+ BIT_UNSET(icr_low, 11); // physical destination
+
+ BIT_SET(icr_low, 14);
+ BIT_UNSET(icr_low, 15);
+ dbg(DBG_CORE, "Sending IPI: ICR_LOW = 0x%.8x, ICR_HIGH = 0x%.8x\n", icr_low,
+ processor << 24);
+
+ LAPICICRH = processor << 24;
+ LAPICICRL = icr_low;
+
+ apic_wait_ipi();
+}
+
+void apic_send_ipi(uint8_t target, ipi_destination_mode mode, uint8_t vector)
+{
+ // See https://wiki.osdev.org/APIC#Interrupt_Command_Register for a
+ // description of how this works. This function only supports targeting a
+ // single APIC, instead of using the special destination modes. Since we
+ // already parse the APIC table, it's more reliable to interrupt a specific
+ // processor.
+ KASSERT(target < 8);
+
+ uint32_t icr_low = 0;
+ icr_low |= vector; // bits 0-7 are the vector number
+ icr_low |= mode << 8; // bits 8-10 are the destination mode
+ BIT_SET(icr_low, 11); // logical destination
+
+ BIT_SET(icr_low, 14);
+
+ dbgq(DBG_CORE, "Sending IPI: ICR_LOW = 0x%.8x, ICR_HIGH = 0x%.8x\n",
+ icr_low, (1U << target) << 24);
+
+ // Bits 24-27 of ICR_HIGH are the target logical APIC ID. Setting ICR_LOW
+ // sends the interrupt, so we have to set this first
+ LAPICICRH = (1U << target) << 24;
+ // send the IPI
+ LAPICICRL = icr_low;
+}
+
+void apic_broadcast_ipi(ipi_destination_mode mode, uint8_t vector,
+ long include_self)
+{
+ uint32_t icr_low = 0;
+ icr_low |= vector;
+ icr_low |= mode << 8;
+ BIT_SET(icr_low, 11);
+ BIT_SET(icr_low, 14);
+
+ if (!include_self)
+ BIT_SET(icr_low, 18);
+ BIT_SET(icr_low, 19);
+
+ LAPICICRH = 0;
+ LAPICICRL = icr_low;
+}
+
+/**
+ * Wait for the last IPI sent to be acknowledged by the other processor.
+ *
+ * Note: this is separate from apic_send_ipi because there are circumstances
+ * where we don't want to wait.
+ */
+void apic_wait_ipi()
+{
+ // Bit 12 of ICR_LOW is the delivery status flag.
+ while (LAPICICRL & (1 << 12))
+ ;
+}