summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorIan Moffett <ian@osmora.org>2025-11-17 14:13:13 -0500
committerIan Moffett <ian@osmora.org>2025-11-17 14:18:12 -0500
commit73235e78ab73090b672c29184672e06706eda227 (patch)
tree50e551783bc25f3e52275cf26020d7ff9ce191d7 /sys
parent21fbb636b94baf094209d11cc942fd5af55a0ec6 (diff)
kern/amd64: lapic: Add support for sending IPIs
This commit introduces an interface for sending inter-processor interrupts using the current Local APIC unit Signed-off-by: Ian Moffett <ian@osmora.org>
Diffstat (limited to 'sys')
-rw-r--r--sys/arch/amd64/cpu/lapic.c83
-rw-r--r--sys/inc/arch/amd64/lapic.h65
2 files changed, 148 insertions, 0 deletions
diff --git a/sys/arch/amd64/cpu/lapic.c b/sys/arch/amd64/cpu/lapic.c
index def7dea..fb0100c 100644
--- a/sys/arch/amd64/cpu/lapic.c
+++ b/sys/arch/amd64/cpu/lapic.c
@@ -29,6 +29,7 @@
#include <sys/cdefs.h>
#include <sys/param.h>
+#include <sys/errno.h>
#include <sys/types.h>
#include <acpi/acpi.h>
#include <acpi/tables.h>
@@ -60,6 +61,8 @@
#define LAPIC_REG_TCCR 0x0390 /* Timer current counter register */
#define LAPIC_REG_TDCR 0x03E0 /* Timer divide configuration register */
#define LAPIC_REG_LVTTMR 0x0320 /* LVT timer entry */
+#define LAPIC_REG_ICRLO 0x0300U /* Interrupt Command Low Register */
+#define LAPIC_REG_ICRHI 0x0310U /* Interrupt Command High Register */
/* LAPIC SVR bits */
#define LAPIC_SVR_EBS BIT(12) /* EOI-broadcast supression */
@@ -220,6 +223,86 @@ lapic_tmr_clbr(struct mcb *mcb)
}
/*
+ * Used to serialize inter-processor interrupts when
+ * operating in x2APIC mode
+ */
+static void
+lapic_ipi_poll(struct mcb *mcb)
+{
+ uint32_t icr_lo;
+
+ do {
+ icr_lo = lapic_read(mcb, LAPIC_REG_ICRLO);
+ __asmv("pause");
+ } while (ISSET(icr_lo, BIT(12)));
+}
+
+int
+lapic_send_ipi(struct mcb *mcb, struct lapic_ipi *ipi)
+{
+ const uint16_t X2APIC_SELF = 0x083F;
+ uint32_t icr_lo, icr_hi;
+
+ if (ipi == NULL) {
+ return -EINVAL;
+ }
+
+ /*
+ * Section 2.4.5 of the x2APIC spec states that x2APICs
+ * have a special self IPI register that can be used
+ * instead of just sending out an interrupt on the system
+ * bus (or APIC bus) and having it loop all the way back.
+ * This interface is an optimized shorthand path
+ */
+ if (ipi->shorthand == IPI_SHAND_SELF && mcb->has_x2apic) {
+ wrmsr(X2APIC_SELF, ipi->vector);
+ return 0;
+ }
+
+ /* Clamp to 8 bits if xAPIC */
+ if (!mcb->has_x2apic) {
+ ipi->dest_id &= 0xFF;
+ }
+
+ /* Encode the ICR high bits */
+ if (!mcb->has_x2apic) {
+ icr_hi = lapic_read(mcb, LAPIC_REG_ICRHI);
+ icr_hi &= ~(0xFFUL << 56);
+ icr_hi |= (ipi->dest_id << 56);
+ lapic_write(mcb, LAPIC_REG_ICRHI, icr_hi);
+ } else {
+ icr_lo = lapic_read(mcb, LAPIC_REG_ICRLO);
+ icr_lo &= ~0xFFFFFFFF;
+ icr_lo |= ipi->dest_id;
+ }
+
+ /*
+ * Encode the low bits of the ICR. If we are x2APIC,
+ * there is no need to read the low DWORD again as
+ * reads load the entire register.
+ */
+ if (!mcb->has_x2apic) {
+ icr_lo = lapic_read(mcb, LAPIC_REG_ICRLO);
+ }
+ icr_lo |= ipi->vector;
+ icr_lo |= (ipi->delmod << 8);
+ icr_lo |= (ipi->logical_dest << 11);
+ icr_lo |= (ipi->shorthand << 18);
+ lapic_write(mcb, LAPIC_REG_ICRLO, icr_lo);
+
+ /* Poll if in x2APIC mode */
+ if (!mcb->has_x2apic) {
+ lapic_ipi_poll(mcb);
+ }
+
+ /*
+ * The x2APIC queues IPIs up and thus polling is not needed as
+ * the delivery status bit is not used nor is it present.
+ */
+ return 0;
+}
+
+/*
* Enable the Local APIC unit
*/
static void
diff --git a/sys/inc/arch/amd64/lapic.h b/sys/inc/arch/amd64/lapic.h
index 4f82681..ba3b333 100644
--- a/sys/inc/arch/amd64/lapic.h
+++ b/sys/inc/arch/amd64/lapic.h
@@ -30,9 +30,74 @@
#ifndef _MACHINE_LAPIC_H_
#define _MACHINE_LAPIC_H_ 1
+#include <sys/types.h>
+#include <mu/cpu.h>
+
#define LAPIC_TMR_VEC 0x81
/*
+ * Represents possible values of the destination shorthand
+ * field for inter-processor interrupts
+ *
+ * @IPI_SHAND_NONE: No shorthand
+ * @IPI_SHAND_SELF: Address self
+ * @IPI_SHAND_AIS: All including self
+ * @IPIS_HAND_AXS: All excluding self
+ */
+typedef enum {
+ IPI_SHAND_NONE,
+ IPI_SHAND_SELF,
+ IPI_SHAND_AIS,
+ IPI_SHAND_AXS
+} ipi_shand_t;
+
+/*
+ * Represents possible values of the delivery mode
+ * field for inter-processor interrupts
+ *
+ * @IPI_DELMOD_FIXED: Deliver vector to processor target(s)
+ * @IPI_DELMOD_LOPRI: Lowest priority [SDM advises against its use]
+ * @IPI_DELMOD_SMI: Reserved
+ * @IPI_DELMOD_RESERVED: Reserved
+ * @IPI_DELMOD_NMI: Deliver a non-maskable interrupt [vector unused]
+ * @IPI_DELMOD_INIT: Park a processor to the reset vector
+ * @IPI_DELMOD_STARTUP: Bring a processor up into real mode
+ */
+typedef enum {
+ IPI_DELMOD_FIXED,
+ IPI_DELMOD_LOPRI,
+ IPI_DELMOD_SMI,
+ IPI_DELMOD_RESERVED,
+ IPI_DELMOD_NMI,
+ IPI_DELMOD_INIT,
+ IPI_DELMOD_STARTUP
+} ipi_delmod_t;
+
+/*
+ * Represents an inter-processor interrupt descriptor
+ *
+ * @dest_id: APIC ID of destination processor
+ * @vector: Interrupt vector to send
+ * @delmod: Delivery mode
+ * @shorthand: Destination shorthand
+ * @logical_dest: Set if destination mode should be logical
+ */
+struct lapic_ipi {
+ uint64_t dest_id;
+ uint8_t vector;
+ ipi_delmod_t delmod : 3;
+ ipi_shand_t shorthand : 2;
+ uint8_t logical_dest : 1;
+};
+
+/*
+ * Send an interprocessor interrupt
+ *
+ * Returns zero on success
+ */
+int lapic_send_ipi(struct mcb *mcb, struct lapic_ipi *ipi);
+
+/*
* Initialize the Local APIC on-board the
* processor for the current core
*/