git: 87ba088fa310 - main - x86/local_apic.c: Add support for installing a thermal interrupt handler

From: Olivier Certner <olce_at_FreeBSD.org>
Date: Tue, 07 Jul 2026 16:35:15 UTC
The branch main has been updated by olce:

URL: https://cgit.FreeBSD.org/src/commit/?id=87ba088fa3108dd180008a04f25760ec71476c87

commit 87ba088fa3108dd180008a04f25760ec71476c87
Author:     Koine Yuusuke <koinec@yahoo.co.jp>
AuthorDate: 2026-07-01 15:28:21 +0000
Commit:     Olivier Certner <olce@FreeBSD.org>
CommitDate: 2026-07-07 16:33:31 +0000

    x86/local_apic.c: Add support for installing a thermal interrupt handler
    
    The thermal interrupt is initially masked.
    
    Thermal interrupt handling is enabled by calling lapic_enable_thermal(),
    which installs a (single) handler.
    
    [olce: Wrote the commit message.]
    
    Reviewed by:    kib, olce
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D44454
---
 sys/amd64/amd64/apic_vector.S |  9 ++++++
 sys/x86/include/apicvar.h     | 12 ++++---
 sys/x86/x86/local_apic.c      | 74 +++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/sys/amd64/amd64/apic_vector.S b/sys/amd64/amd64/apic_vector.S
index c753ddbad9be..85136b2f17df 100644
--- a/sys/amd64/amd64/apic_vector.S
+++ b/sys/amd64/amd64/apic_vector.S
@@ -144,6 +144,15 @@ IDTVEC(spuriousint)
 	KMSAN_LEAVE
 	jmp	doreti
 
+/*
+ * Local APIC thermal interrupt handler.
+ */
+	INTR_HANDLER thermalint
+	KMSAN_ENTER
+	call	lapic_handle_thermal
+	KMSAN_LEAVE
+	jmp	doreti
+
 #ifdef XENHVM
 /*
  * Xen event channel upcall interrupt handler.
diff --git a/sys/x86/include/apicvar.h b/sys/x86/include/apicvar.h
index 2dda3103f93c..66d53bbf7e5d 100644
--- a/sys/x86/include/apicvar.h
+++ b/sys/x86/include/apicvar.h
@@ -185,12 +185,12 @@ struct apic_enumerator {
 inthand_t
 	IDTVEC(apic_isr1), IDTVEC(apic_isr2), IDTVEC(apic_isr3),
 	IDTVEC(apic_isr4), IDTVEC(apic_isr5), IDTVEC(apic_isr6),
-	IDTVEC(apic_isr7), IDTVEC(cmcint), IDTVEC(errorint),
-	IDTVEC(spuriousint), IDTVEC(timerint),
+	IDTVEC(apic_isr7), IDTVEC(cmcint), IDTVEC(thermalint),
+	IDTVEC(errorint), IDTVEC(spuriousint), IDTVEC(timerint),
 	IDTVEC(apic_isr1_pti), IDTVEC(apic_isr2_pti), IDTVEC(apic_isr3_pti),
 	IDTVEC(apic_isr4_pti), IDTVEC(apic_isr5_pti), IDTVEC(apic_isr6_pti),
-	IDTVEC(apic_isr7_pti), IDTVEC(cmcint_pti), IDTVEC(errorint_pti),
-	IDTVEC(spuriousint_pti), IDTVEC(timerint_pti);
+	IDTVEC(apic_isr7_pti), IDTVEC(cmcint_pti), IDTVEC(thermalint_pti),
+	IDTVEC(errorint_pti), IDTVEC(spuriousint_pti), IDTVEC(timerint_pti);
 
 extern vm_paddr_t lapic_paddr;
 extern int *apic_cpuids;
@@ -199,6 +199,7 @@ extern int *apic_cpuids;
 extern void (*ipi_vectored)(u_int, int);
 
 typedef struct ioapic *ioapic_drv_t;
+typedef void (*lapic_thermal_handle_function)(int, void *);
 
 void	apic_register_enumerator(struct apic_enumerator *enumerator);
 ioapic_drv_t	ioapic_create(vm_paddr_t addr, int32_t apic_id, int intbase);
@@ -233,6 +234,8 @@ void	apic_enable_vector(u_int apic_id, u_int vector);
 void	apic_disable_vector(u_int apic_id, u_int vector);
 void	apic_free_vector(u_int apic_id, u_int vector, u_int irq);
 void	lapic_calibrate_timer(void);
+void	lapic_enable_thermal(lapic_thermal_handle_function thermfunc, void *value);
+void	lapic_disable_thermal(void);
 int	lapic_enable_pcint(void);
 void	lapic_disable_pcint(void);
 void	lapic_reenable_pcint(void);
@@ -257,6 +260,7 @@ int	lapic_set_lvt_polarity(u_int apic_id, u_int lvt,
 int	lapic_set_lvt_triggermode(u_int apic_id, u_int lvt,
 	    enum intr_trigger trigger);
 void	lapic_handle_cmc(void);
+void	lapic_handle_thermal(void);
 void	lapic_handle_error(void);
 void	lapic_handle_intr(int vector, struct trapframe *frame);
 void	lapic_handle_timer(struct trapframe *frame);
diff --git a/sys/x86/x86/local_apic.c b/sys/x86/x86/local_apic.c
index cd5e4d474080..08748a1fe7c2 100644
--- a/sys/x86/x86/local_apic.c
+++ b/sys/x86/x86/local_apic.c
@@ -60,6 +60,7 @@
 #include <vm/pmap.h>
 
 #include <x86/apicreg.h>
+#include <machine/atomic.h>
 #include <machine/clock.h>
 #include <machine/cpufunc.h>
 #include <machine/cputypes.h>
@@ -313,6 +314,8 @@ static uint64_t lapic_ipi_wait_mult;
 static int __read_mostly lapic_ds_idle_timeout = 1000000;
 #endif
 unsigned int max_apic_id;
+static void *lapic_thermal_function_value;
+static lapic_thermal_handle_function lapic_thermal_function_ptr;
 static int pcint_refcnt = 0;
 
 SYSCTL_NODE(_hw, OID_AUTO, apic, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
@@ -596,7 +599,9 @@ lapic_init(vm_paddr_t addr)
 	setidt(APIC_ERROR_INT, pti ? IDTVEC(errorint_pti) : IDTVEC(errorint),
 	    SDT_APIC, SEL_KPL, GSEL_APIC);
 
-	/* XXX: Thermal interrupt */
+	/* Thermal interrupt */
+	setidt(APIC_THERMAL_INT, pti ? IDTVEC(thermalint_pti) : IDTVEC(thermalint),
+	    SDT_APIC, SEL_KPL, GSEL_APIC);
 
 	/* Local APIC CMCI. */
 	setidt(APIC_CMC_INT, pti ? IDTVEC(cmcint_pti) : IDTVEC(cmcint),
@@ -923,7 +928,9 @@ lapic_setup(int boot)
 	    lapic_read32(LAPIC_LVT_ERROR)));
 	lapic_write32(LAPIC_ESR, 0);
 
-	/* XXX: Thermal LVT */
+	/* Thermal LVT */
+	lapic_write32(LAPIC_LVT_THERMAL, lvt_mode(la, APIC_LVT_THERMAL,
+	    lapic_read32(LAPIC_LVT_THERMAL)));
 
 	/* Program the CMCI LVT entry if present. */
 	if (maxlvt >= APIC_LVT_CMCI) {
@@ -1632,6 +1639,69 @@ lapic_enable_mca_elvt(void)
 	return (APIC_ELVT_MCA);
 }
 
+void
+lapic_handle_thermal(void)
+{
+	lapic_thermal_handle_function func;
+
+	func = (lapic_thermal_handle_function)atomic_load_acq_ptr(
+	    (void *)&lapic_thermal_function_ptr);
+
+	if (func != NULL)
+		func(PCPU_GET(cpuid), lapic_thermal_function_value);
+
+	lapic_eoi();
+}
+
+static void
+lapic_update_thermal(void *dummy __unused)
+{
+	struct lapic *la;
+
+	la = &lapics[lapic_id()];
+	lapic_write32(LAPIC_LVT_THERMAL, lvt_mode(la, APIC_LVT_THERMAL,
+	    lapic_read32(LAPIC_LVT_THERMAL)));
+}
+
+void
+lapic_enable_thermal(lapic_thermal_handle_function thermfunc, void *value)
+{
+#ifdef DEV_ATPIC
+	/* Fail if the local APIC is not present. */
+	if (!x2apic_mode && lapic_map == NULL)
+		return;
+#endif
+
+	lapic_thermal_function_value = value;
+	atomic_store_rel_ptr((uintptr_t *)&lapic_thermal_function_ptr,
+	    (uintptr_t)thermfunc);
+
+	lvts[APIC_LVT_THERMAL].lvt_masked = 0;
+
+	MPASS(mp_ncpus == 1 || smp_started);
+	smp_rendezvous(NULL, lapic_update_thermal, NULL, NULL);
+}
+
+void
+lapic_disable_thermal(void)
+{
+#ifdef DEV_ATPIC
+	/* Fail if the local APIC is not present. */
+	if (!x2apic_mode && lapic_map == NULL)
+		return;
+#endif
+
+	lvts[APIC_LVT_THERMAL].lvt_masked = 1;
+
+#ifdef SMP
+	KASSERT(mp_ncpus == 1 || smp_started, ("thermal driver unloaded too early"));
+#endif
+	smp_rendezvous(NULL, lapic_update_thermal, NULL, NULL);
+
+	atomic_store_rel_ptr((uintptr_t *)&lapic_thermal_function_ptr,
+	    (uintptr_t)NULL);
+}
+
 void
 lapic_handle_error(void)
 {