[PATCH v2 4/5] thermal: intel: Add syscore callbacks for suspend and resume

From: Ricardo Neri

Date: Thu May 28 2026 - 11:30:01 EST


Directed package-level thermal interrupts are serviced by a single CPU per
package. These handler CPUs are selected at boot through the CPU hotplug
infrastructure. This mechanism is sufficient to restore the directed
interrupt configuration when resuming from suspend for non-boot packages.
It also keeps the handler-tracking array updated.

For the boot package, CPU0 is chosen during boot because its CPU hotplug
online callback runs first. However, this callback is not invoked on
resume. The directed package-level interrupt configuration for the boot
package is not restored. Add a syscore resume callback to re-enable
directed package-level interrupts for this package.

Disabling directed interrupts during suspend is required to keep the
handler-tracking array in a consistent state for the boot package,
allowing the correct configuration to be restored on resume.

The resume callback must busy-wait for hardware acknowledgment of the
directed interrupt setup. Otherwise, the handler-tracking array could be
left in an inconsistent state. This implies running with interrupts
disabled for up to 15ms, though in practice it takes less than 1ms.

Signed-off-by: Ricardo Neri <ricardo.neri-calderon@xxxxxxxxxxxxxxx>
---
While it may take up to 15ms for hardware to acknowledge the interrupt,
latency measurements show that the latency has a median of 0.4ms. Please
see the preceding patch for details.
---
Changes in v2:
* Relocated comment blocks for clarity.
* Reworded the changelog for clarity and accuracy.
---
drivers/thermal/intel/therm_throt.c | 41 ++++++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)

diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c
index dcb5d7051ac6..d4ea795e3c3b 100644
--- a/drivers/thermal/intel/therm_throt.c
+++ b/drivers/thermal/intel/therm_throt.c
@@ -14,6 +14,7 @@
* Credits: Adapted from Zwane Mwaikambo's original code in mce_intel.c.
* Inspired by Ross Biro's and Al Borchers' counter code.
*/
+#include <linux/syscore_ops.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
#include <linux/jiffies.h>
@@ -570,7 +571,10 @@ static void config_directed_thermal_pkg_intr(void *info)
wrmsrl(MSR_IA32_THERM_INTERRUPT, msr_val);
}

-/* Only accessed from CPU hotplug callbacks. No extra locking needed. */
+/*
+ * Only accessed from CPU hotplug and syscore callbacks. No extra locking
+ * needed.
+ */
static unsigned int *directed_intr_handler_cpus;

static bool directed_thermal_pkg_intr_supported(void)
@@ -588,6 +592,10 @@ static bool directed_thermal_pkg_intr_supported(void)
* Must be called with cpu_hotplug_lock held to prevent CPUs from going offline
* while iterating through packages. Also, interrupts must be enabled to avoid
* deadlocks in SMP function calls.
+ *
+ * The syscore resume callback may call this function but CPU hotplug is disabled
+ * in that context. It also runs with interrupts disabled, but no SMP function
+ * calls are issued because the directed interrupt was torn down before suspend.
*/
static void disable_all_directed_thermal_pkg_intr(void)
{
@@ -679,6 +687,10 @@ static void disable_directed_thermal_pkg_intr(unsigned int cpu)
* We are here via CPU hotplug. Since we are holding the
* cpu_hotplug_lock, @new_cpu cannot go offline and interrupts
* are enabled, so the SMP function call is safe.
+ *
+ * The syscore suspend callback runs with interrupts disabled,
+ * but it does not reach this path because all the secondary
+ * CPUs are offline.
*/
smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr,
&enable, true);
@@ -768,6 +780,31 @@ static int thermal_throttle_offline(unsigned int cpu)
return 0;
}

+/*
+ * CPU0 may be handling the directed interrupt, but the CPU hotplug callbacks
+ * are not called for CPU0 during suspend and resume.
+ */
+static void directed_pkg_intr_syscore_resume(void *data)
+{
+ enable_directed_thermal_pkg_intr(0);
+}
+
+static int directed_pkg_intr_syscore_suspend(void *data)
+{
+ disable_directed_thermal_pkg_intr(0);
+
+ return 0;
+}
+
+static const struct syscore_ops directed_pkg_intr_pm_ops = {
+ .resume = directed_pkg_intr_syscore_resume,
+ .suspend = directed_pkg_intr_syscore_suspend,
+};
+
+static struct syscore directed_pkg_intr_pm = {
+ .ops = &directed_pkg_intr_pm_ops,
+};
+
static __init void init_directed_pkg_intr(void)
{
int i;
@@ -783,6 +820,8 @@ static __init void init_directed_pkg_intr(void)

for (i = 0; i < topology_max_packages(); i++)
directed_intr_handler_cpus[i] = nr_cpu_ids;
+
+ register_syscore(&directed_pkg_intr_pm);
}

static __init int thermal_throttle_init_device(void)

--
2.43.0