From ac5cc691eb4a7b604687bcb9ced3a59d24cda65c Mon Sep 17 00:00:00 2001 From: Ricardo Neri Date: Sat, 13 Jun 2026 15:17:48 -0700 Subject: x86/thermal: Add bit definitions for Intel Directed Package Thermal Interrupt Add CPUID and MSR bit definitions required to support Intel Directed Package Thermal Interrupt. A CPU requests directed package-level thermal interrupts by setting bit 25 in IA32_THERM_INTERRUPT. Hardware acknowledges by setting bit 25 in IA32_PACKAGE_THERM_STATUS, indicating that only CPUs that opted in will receive the interrupt. If no CPU in the package requests it, delivery falls back to broadcast. Signed-off-by: Ricardo Neri Link: https://patch.msgid.link/20260613-rneri-directed-therm-intr-v3-2-3a26d1e47fc8@linux.intel.com Signed-off-by: Rafael J. Wysocki --- arch/x86/include/asm/cpufeatures.h | 2 ++ arch/x86/include/asm/msr-index.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index 1b4a48bff18f..94764d69fa76 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -365,6 +365,8 @@ #define X86_FEATURE_HWP_HIGHEST_PERF_CHANGE (14*32+15) /* HWP Highest perf change */ #define X86_FEATURE_HFI (14*32+19) /* "hfi" Hardware Feedback Interface */ +#define X86_FEATURE_DPTI (14*32+24) /* Intel Directed Package Thermal Interrupt */ + /* AMD SVM Feature Identification, CPUID level 0x8000000a (EDX), word 15 */ #define X86_FEATURE_NPT (15*32+ 0) /* "npt" Nested Page Table support */ #define X86_FEATURE_LBRV (15*32+ 1) /* "lbrv" LBR Virtualization support */ diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h index 18c4be75e927..3a8e51a0c9e8 100644 --- a/arch/x86/include/asm/msr-index.h +++ b/arch/x86/include/asm/msr-index.h @@ -1007,6 +1007,7 @@ #define THERM_INT_HIGH_ENABLE (1 << 0) #define THERM_INT_LOW_ENABLE (1 << 1) #define THERM_INT_PLN_ENABLE (1 << 24) +#define THERM_INT_DPTI_ENABLE (1 << 25) #define MSR_IA32_THERM_STATUS 0x0000019c @@ -1036,6 +1037,7 @@ #define PACKAGE_THERM_STATUS_PROCHOT (1 << 0) #define PACKAGE_THERM_STATUS_POWER_LIMIT (1 << 10) +#define PACKAGE_THERM_STATUS_DPTI_ACK (1 << 25) #define PACKAGE_THERM_STATUS_HFI_UPDATED (1 << 26) #define MSR_IA32_PACKAGE_THERM_INTERRUPT 0x000001b2 -- cgit From 2bedc0640478b6e714dd2e82875d8cc3c758f6a7 Mon Sep 17 00:00:00 2001 From: Ricardo Neri Date: Sat, 13 Jun 2026 15:17:49 -0700 Subject: thermal: intel: Add resources to handle directed package-level thermal interrupts When supported by hardware, a CPU requests to receive directed package- level thermal interrupts by setting a designated bit in IA32_THERM_INTERRUPT. It is sufficient to have one CPU per package handling the interrupt. Add an array to keep track of those CPUs as well as init and cleanup functions. A subsequent changeset will designate a CPU per package to handle the interrupt. Signed-off-by: Ricardo Neri Link: https://patch.msgid.link/20260613-rneri-directed-therm-intr-v3-3-3a26d1e47fc8@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/therm_throt.c | 53 ++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c index 45a8ef4a608b..8e259c4e5fe8 100644 --- a/drivers/thermal/intel/therm_throt.c +++ b/drivers/thermal/intel/therm_throt.c @@ -524,6 +524,50 @@ static void thermal_throttle_remove_dev(struct device *dev) sysfs_remove_group(&dev->kobj, &thermal_attr_group); } +/* + * Accessed from CPU hotplug callbacks and from code that runs while CPU + * hotplug is inactive: the init and cleanup paths. + * No extra locking needed. + */ +static unsigned int *directed_intr_handler_cpus; + +static bool directed_thermal_pkg_intr_supported(void) +{ + if (!boot_cpu_has(X86_FEATURE_DPTI)) + return false; + + if (!directed_intr_handler_cpus) + return false; + + return true; +} + +static __init void init_directed_pkg_intr(void) +{ + int i; + + if (!boot_cpu_has(X86_FEATURE_DPTI)) + return; + + directed_intr_handler_cpus = kmalloc_array(topology_max_packages(), + sizeof(*directed_intr_handler_cpus), + GFP_KERNEL); + if (!directed_intr_handler_cpus) + return; + + for (i = 0; i < topology_max_packages(); i++) + directed_intr_handler_cpus[i] = nr_cpu_ids; +} + +static void cleanup_directed_pkg_thermal_intr(void) +{ + if (!directed_thermal_pkg_intr_supported()) + return; + + kfree(directed_intr_handler_cpus); + directed_intr_handler_cpus = NULL; +} + /* Get notified when a cpu comes on/off. Be hotplug friendly. */ static int thermal_throttle_online(unsigned int cpu) { @@ -585,12 +629,19 @@ static __init int thermal_throttle_init_device(void) if (!atomic_read(&therm_throt_en)) return 0; + init_directed_pkg_intr(); + intel_hfi_init(); ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "x86/therm:online", thermal_throttle_online, thermal_throttle_offline); - return ret < 0 ? ret : 0; + if (ret >= 0) + return 0; + + cleanup_directed_pkg_thermal_intr(); + + return ret; } device_initcall(thermal_throttle_init_device); -- cgit From 15520ed82bc62df2b66dee8000cf33d0ee635d53 Mon Sep 17 00:00:00 2001 From: Ricardo Neri Date: Sat, 13 Jun 2026 15:17:50 -0700 Subject: thermal: intel: Enable the Directed Package-level Thermal Interrupt Package-level thermal interrupts are broadcast to all online CPUs within a package, even though only one CPU needs to service them. This results in unnecessary wakeups, lock contention, and corresponding performance and power-efficiency penalties. When supported by hardware, a CPU requests to receive directed package- level thermal interrupts by setting a designated bit in IA32_THERM_INTERRUPT. The operating system must then verify that hardware has acknowledged this request by checking a designated bit in IA32_PACKAGE_THERM_STATUS. Enable directed package-level thermal interrupts on one CPU per package using the CPU hotplug infrastructure. The first CPU of a package that comes online will handle the interrupt. If the handling CPU goes offline, select a new CPU. Temporarily enable directed interrupts on both the current and new CPU until hardware acknowledges the new selection, then disable them on the outgoing CPU. Systems without directed-interrupt support continue to broadcast the package-level interrupt to all CPUs. Also, add a rollback mechanism in the CPU hotplug online callback to fall back to broadcast mode if the directed-interrupt acknowledgment fails in any package. This is most important during boot, when all CPUs in a package come online and would otherwise keep retrying on faulty hardware. A complete rollback is not needed in the CPU hotplug offline callback since at that point the hardware is known to work. While here, update an inline comment to point to the correct volume of the Intel Software Developer's Manual. Signed-off-by: Ricardo Neri Link: https://patch.msgid.link/20260613-rneri-directed-therm-intr-v3-4-3a26d1e47fc8@linux.intel.com [ rjw: Rebased on top of 7.2-rc2 ] Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/therm_throt.c | 179 +++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c index 8e259c4e5fe8..00dcc76bddbb 100644 --- a/drivers/thermal/intel/therm_throt.c +++ b/drivers/thermal/intel/therm_throt.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -244,16 +245,23 @@ static void thermal_intr_init_pkg_clear_mask(void) * IA32_PACKAGE_THERM_STATUS. */ - /* All bits except BIT 26 depend on CPUID.06H: EAX[6] = 1 */ + /* All bits except BITs 25 and 26 depend on CPUID.06H: EAX[6] = 1 */ if (boot_cpu_has(X86_FEATURE_PTS)) therm_intr_pkg_clear_mask = (BIT(1) | BIT(3) | BIT(5) | BIT(7) | BIT(9) | BIT(11)); /* - * Intel SDM Volume 2A: Thermal and Power Management Leaf + * Intel SDM Volume 1: Thermal and Power Management Leaf * Bit 26: CPUID.06H: EAX[19] = 1 */ if (boot_cpu_has(X86_FEATURE_HFI)) therm_intr_pkg_clear_mask |= BIT(26); + + /* + * Intel SDM Volume 1: Thermal and Power Management Leaf + * Bit 25: CPUID.06H: EAX[24] = 1 + */ + if (boot_cpu_has(X86_FEATURE_DPTI)) + therm_intr_pkg_clear_mask |= BIT(25); } /* @@ -524,6 +532,44 @@ static void thermal_throttle_remove_dev(struct device *dev) sysfs_remove_group(&dev->kobj, &thermal_attr_group); } +static int check_directed_thermal_pkg_intr_ack(void) +{ + unsigned int count = 15000; + u64 msr_val; + + /* + * Hardware acknowledges the directed interrupt setup in 10ms or less. + * Wait 15ms to be safe. + */ + do { + rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); + udelay(1); + } while (!(msr_val & PACKAGE_THERM_STATUS_DPTI_ACK) && --count); + + if (!count) + return -ETIMEDOUT; + + thermal_clear_package_intr_status(PACKAGE_LEVEL, + PACKAGE_THERM_STATUS_DPTI_ACK); + + return 0; +} + +static void config_directed_thermal_pkg_intr(void *info) +{ + bool enable = *((bool *)info); + u64 msr_val; + + rdmsrq(MSR_IA32_THERM_INTERRUPT, msr_val); + + if (enable) + msr_val |= THERM_INT_DPTI_ENABLE; + else + msr_val &= ~THERM_INT_DPTI_ENABLE; + + wrmsrq(MSR_IA32_THERM_INTERRUPT, msr_val); +} + /* * Accessed from CPU hotplug callbacks and from code that runs while CPU * hotplug is inactive: the init and cleanup paths. @@ -542,6 +588,127 @@ static bool directed_thermal_pkg_intr_supported(void) return true; } +/* + * Must be called with cpu_hotplug_lock held to prevent CPUs from going offline + * while iterating through packages and interrupts must be enabled to avoid + * deadlocks in SMP function calls. + */ +static void disable_directed_thermal_pkg_intr_all(void) +{ + bool enable = false; + int i; + + if (!directed_thermal_pkg_intr_supported()) + return; + + for (i = 0; i < topology_max_packages(); i++) { + if (directed_intr_handler_cpus[i] == nr_cpu_ids) + continue; + + smp_call_function_single(directed_intr_handler_cpus[i], + config_directed_thermal_pkg_intr, + &enable, true); + } +} + +static int enable_directed_thermal_pkg_intr(unsigned int cpu) +{ + bool enable = true; + u16 pkg_id; + + if (!directed_thermal_pkg_intr_supported()) + return 0; + + pkg_id = topology_logical_package_id(cpu); + if (pkg_id >= topology_max_packages()) + return -EINVAL; + + /* Another CPU in this package already handles the directed interrupt. */ + if (directed_intr_handler_cpus[pkg_id] != nr_cpu_ids) + return 0; + + thermal_clear_package_intr_status(PACKAGE_LEVEL, + PACKAGE_THERM_STATUS_DPTI_ACK); + + config_directed_thermal_pkg_intr(&enable); + if (!check_directed_thermal_pkg_intr_ack()) { + directed_intr_handler_cpus[pkg_id] = cpu; + return 0; + } + + /* + * A failure indicates faulty hardware. Roll back completely so that + * no other CPU tries. This is especially important during boot as all + * CPUs may come online and would otherwise keep trying. + */ + enable = false; + config_directed_thermal_pkg_intr(&enable); + + return -ETIMEDOUT; +} + +static void disable_directed_thermal_pkg_intr(unsigned int cpu) +{ + unsigned int new_cpu; + bool enable; + u16 pkg_id; + + if (!directed_thermal_pkg_intr_supported()) + return; + + pkg_id = topology_logical_package_id(cpu); + if (pkg_id >= topology_max_packages()) + return; + + /* Not the CPU handling the directed interrupt. */ + if (directed_intr_handler_cpus[pkg_id] != cpu) + return; + + /* + * The package-level interrupt must remain directed after this CPU goes + * offline. + */ + new_cpu = cpumask_any_but(topology_core_cpumask(cpu), cpu); + if (new_cpu < nr_cpu_ids) { + enable = true; + thermal_clear_package_intr_status(PACKAGE_LEVEL, + PACKAGE_THERM_STATUS_DPTI_ACK); + + /* + * 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. + */ + smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr, + &enable, true); + } + + /* + * If hardware does not acknowledge the directed interrupt setup on + * @new_cpu, disable the redirection. Since no other CPU is configured + * to receive the package-level interrupt, all CPUs in the package will + * receive it. + */ + enable = false; + if (new_cpu < nr_cpu_ids && check_directed_thermal_pkg_intr_ack()) { + smp_call_function_single(new_cpu, config_directed_thermal_pkg_intr, + &enable, true); + + pr_warn_once("Failed to redirect package thermal interrupt from CPU%u to CPU%u; reverting to broadcast.\n", + cpu, new_cpu); + + new_cpu = nr_cpu_ids; + } + + /* + * Clear the directed interrupt on @cpu. Hardware acknowledgment can be + * ignored since @cpu is going offline. + */ + config_directed_thermal_pkg_intr(&enable); + + directed_intr_handler_cpus[pkg_id] = (new_cpu < nr_cpu_ids) ? new_cpu : nr_cpu_ids; +} + static __init void init_directed_pkg_intr(void) { int i; @@ -564,6 +731,7 @@ static void cleanup_directed_pkg_thermal_intr(void) if (!directed_thermal_pkg_intr_supported()) return; + disable_directed_thermal_pkg_intr_all(); kfree(directed_intr_handler_cpus); directed_intr_handler_cpus = NULL; } @@ -593,6 +761,11 @@ static int thermal_throttle_online(unsigned int cpu) */ intel_hfi_online(cpu); + if (enable_directed_thermal_pkg_intr(cpu)) { + pr_info_once("Failed to direct package thermal interrupts. All CPUs will receive it.\n"); + cleanup_directed_pkg_thermal_intr(); + } + /* Unmask the thermal vector after the above workqueues are initialized. */ l = apic_read(APIC_LVTTHMR); apic_write(APIC_LVTTHMR, l & ~APIC_LVT_MASKED); @@ -610,6 +783,8 @@ static int thermal_throttle_offline(unsigned int cpu) l = apic_read(APIC_LVTTHMR); apic_write(APIC_LVTTHMR, l | APIC_LVT_MASKED); + disable_directed_thermal_pkg_intr(cpu); + intel_hfi_offline(cpu); cancel_delayed_work_sync(&state->package_throttle.therm_work); -- cgit From 66e1929b489025d828cdd112765b0a3b06cf3147 Mon Sep 17 00:00:00 2001 From: Ricardo Neri Date: Sat, 13 Jun 2026 15:17:51 -0700 Subject: thermal: intel: Add syscore callbacks for suspend and resume 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 Link: https://patch.msgid.link/20260613-rneri-directed-therm-intr-v3-5-3a26d1e47fc8@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/therm_throt.c | 40 ++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c index 00dcc76bddbb..184deb2ad40b 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 #include #include #include @@ -572,7 +573,7 @@ static void config_directed_thermal_pkg_intr(void *info) /* * Accessed from CPU hotplug callbacks and from code that runs while CPU - * hotplug is inactive: the init and cleanup paths. + * hotplug is inactive: the init and cleanup paths as well as syscore callbacks. * No extra locking needed. */ static unsigned int *directed_intr_handler_cpus; @@ -678,6 +679,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); @@ -709,6 +714,36 @@ static void disable_directed_thermal_pkg_intr(unsigned int cpu) directed_intr_handler_cpus[pkg_id] = (new_cpu < nr_cpu_ids) ? new_cpu : nr_cpu_ids; } +/* + * 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) +{ + /* + * We can't do anything to handle errors. If direction fails for CPU0, + * another CPU will take over or disable direction entirely during CPU + * hotplug. + */ + 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; @@ -724,6 +759,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 void cleanup_directed_pkg_thermal_intr(void) @@ -731,6 +768,7 @@ static void cleanup_directed_pkg_thermal_intr(void) if (!directed_thermal_pkg_intr_supported()) return; + unregister_syscore(&directed_pkg_intr_pm); disable_directed_thermal_pkg_intr_all(); kfree(directed_intr_handler_cpus); directed_intr_handler_cpus = NULL; -- cgit From 1e768cc92bc7394b65898261b1b2e369732e0723 Mon Sep 17 00:00:00 2001 From: Ricardo Neri Date: Sat, 13 Jun 2026 15:17:52 -0700 Subject: thermal: intel: Add a syscore shutdown callback for kexec reboot A kexec reboot may load a kernel that does not support directed package- level thermal interrupts. Without a shutdown callback, the directed interrupt configuration remains enabled across kexec but will not be handled correctly. In particular, if the CPU designated to receive the directed interrupt goes offline, no other CPU in the package will receive it. Add a syscore shutdown callback to disable directed package-level thermal interrupts on all packages before a kexec reboot. If the post-kexec kernel does not enable directed interrupts, it falls back to broadcasting the interrupt to all CPUs. Signed-off-by: Ricardo Neri Link: https://patch.msgid.link/20260613-rneri-directed-therm-intr-v3-6-3a26d1e47fc8@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/therm_throt.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c index 184deb2ad40b..d1f9cf8bf6c5 100644 --- a/drivers/thermal/intel/therm_throt.c +++ b/drivers/thermal/intel/therm_throt.c @@ -592,7 +592,8 @@ 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 and interrupts must be enabled to avoid - * deadlocks in SMP function calls. + * deadlocks in SMP function calls. The syscore shutdown callback also calls + * this function, but runs with CPU hotplug disabled (and interrupts enabled). */ static void disable_directed_thermal_pkg_intr_all(void) { @@ -735,9 +736,15 @@ static int directed_pkg_intr_syscore_suspend(void *data) return 0; } +static void directed_pkg_intr_syscore_shutdown(void *data) +{ + disable_directed_thermal_pkg_intr_all(); +} + static const struct syscore_ops directed_pkg_intr_pm_ops = { .resume = directed_pkg_intr_syscore_resume, .suspend = directed_pkg_intr_syscore_suspend, + .shutdown = directed_pkg_intr_syscore_shutdown, }; static struct syscore directed_pkg_intr_pm = { -- cgit From 28f34d7dafa6dfa657efd6760b7c78e98c9b2f6a Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 15 Jun 2026 14:52:41 +0800 Subject: thermal: intel: int340x: clean up RFIM groups on DVFS failure proc_thermal_rfim_add() can create the FIVR and DLVR sysfs groups before creating the DVFS group. If DVFS group creation fails while both earlier groups are enabled, the current error handling removes only one group: the FIVR branch returns before the DLVR cleanup branch can run. This leaves the DLVR group behind even though RFIM setup fails. Use one DVFS failure path that removes all previously created RFIM groups before returning the error. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260615065245.84252-1-pengpeng@iscas.ac.cn Signed-off-by: Rafael J. Wysocki --- .../thermal/intel/int340x_thermal/processor_thermal_rfim.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c index 1a7e134dfcf8..96279756177f 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_rfim.c @@ -491,12 +491,11 @@ int proc_thermal_rfim_add(struct pci_dev *pdev, struct proc_thermal_device *proc if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS) { ret = sysfs_create_group(&pdev->dev.kobj, &dvfs_attribute_group); - if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) { - sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group); - return ret; - } - if (ret && proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) { - sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group); + if (ret) { + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DLVR) + sysfs_remove_group(&pdev->dev.kobj, &dlvr_attribute_group); + if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_FIVR) + sysfs_remove_group(&pdev->dev.kobj, &fivr_attribute_group); return ret; } } -- cgit From d83dc9ce57a746a6dca28439bcc0575d26fa6986 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Tue, 23 Jun 2026 09:51:40 +0800 Subject: thermal: intel: int3400: clean up ODVP on probe failures evaluate_odvp() creates per-ODVP sysfs files before the thermal zone and later probe resources are registered. The current unwind path only calls cleanup_odvp() from the late sysfs failure path, so failures after evaluate_odvp() but before that label, including thermal_tripless_zone_device_register() failures, leave the ODVP files and storage behind. Move the ODVP cleanup to the common ART/TRT unwind path so every failure after evaluate_odvp() releases the ODVP state. Also clear the cached ODVP pointers in cleanup_odvp(), because evaluate_odvp() can already call it for partial setup failures while probe continues. Fixes: 006f006f1e5c ("thermal/int340x_thermal: Export OEM vendor variables") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260623015140.19300-1-pengpeng@iscas.ac.cn Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/int340x_thermal/int3400_thermal.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index d200734625ee..5d70301d4a3d 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -356,8 +356,10 @@ static void cleanup_odvp(struct int3400_thermal_priv *priv) kfree(priv->odvp_attrs[i].attr.attr.name); } kfree(priv->odvp_attrs); + priv->odvp_attrs = NULL; } kfree(priv->odvp); + priv->odvp = NULL; priv->odvp_count = 0; } @@ -635,7 +637,6 @@ free_notify: acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify); free_sysfs: - cleanup_odvp(priv); if (!ZERO_OR_NULL_PTR(priv->data_vault)) { device_remove_bin_file(&pdev->dev, &bin_attr_data_vault); kfree(priv->data_vault); @@ -649,6 +650,7 @@ free_rel_misc: acpi_thermal_rel_misc_device_remove(priv->adev->handle); thermal_zone_device_unregister(priv->thermal); free_art_trt: + cleanup_odvp(priv); kfree(priv->trts); kfree(priv->arts); free_priv: -- cgit From 75292391464f70ef806c182c4058fa70b81eedd9 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 10:30:13 +0800 Subject: thermal: intel: int340x: Remove redundant dev_err() The devm_request_threaded_irq() now automatically logs detailed error messages on failure. This eliminates the need for driver-specific dev_err() calls that previously printed generic messages. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260709023048.599150-11-panchuang@vivo.com Signed-off-by: Rafael J. Wysocki --- .../thermal/intel/int340x_thermal/processor_thermal_device_pci.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c index c693d934103a..c5131423ec9b 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c @@ -308,10 +308,8 @@ static int proc_thermal_setup_msi(struct pci_dev *pdev, struct proc_thermal_pci ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, 0, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", irq); + if (ret) goto err_free_msi_vectors; - } proc_thermal_msi_map[i] = irq; } @@ -394,10 +392,8 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, irq_flag, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq); + if (ret) goto err_ret_tzone; - } } ret = thermal_zone_device_enable(pci_info->tzone); -- cgit From 5cc6da534cc36230943f6e4ce1c35493afe4729e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Thu, 9 Jul 2026 10:30:14 +0800 Subject: thermal: intel: bxt_pmic: Remove redundant dev_err() The devm_request_threaded_irq() now automatically logs detailed error messages on failure. This eliminates the need for driver-specific dev_err() calls that previously printed generic messages. Signed-off-by: Pan Chuang [ rjw: Subject adjustment ] Link: https://patch.msgid.link/20260709023048.599150-12-panchuang@vivo.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_bxt_pmic_thermal.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/thermal/intel/intel_bxt_pmic_thermal.c b/drivers/thermal/intel/intel_bxt_pmic_thermal.c index 6312c6ba081f..aeaefbbd5d8f 100644 --- a/drivers/thermal/intel/intel_bxt_pmic_thermal.c +++ b/drivers/thermal/intel/intel_bxt_pmic_thermal.c @@ -245,10 +245,8 @@ static int pmic_thermal_probe(struct platform_device *pdev) NULL, pmic_thermal_irq_handler, IRQF_ONESHOT, "pmic_thermal", pdev); - if (ret) { - dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); + if (ret) return ret; - } pmic_irq_count++; } -- cgit From f1e50b1bcc2377f16574f891c8650def928647ee Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Thu, 2 Jul 2026 19:02:40 +0300 Subject: thermal: intel: int340x: simplify ptc_temperature_write() Simplify 'ptc_temperature_write()' by using the convenient 'kstrtou32_from_user()'. Signed-off-by: Dmitry Antipov Link: https://patch.msgid.link/20260702160240.2929965-1-dmantipov@yandex.ru Signed-off-by: Rafael J. Wysocki --- .../intel/int340x_thermal/platform_temperature_control.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c index d92a6f84a778..43cb809c5dcc 100644 --- a/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c +++ b/drivers/thermal/intel/int340x_thermal/platform_temperature_control.c @@ -227,17 +227,12 @@ static ssize_t ptc_temperature_write(struct file *file, const char __user *data, { struct ptc_data *ptc_instance = file->private_data; struct pci_dev *pdev = ptc_instance->pdev; - char buf[32]; - ssize_t len; u32 value; + int ret; - len = min(count, sizeof(buf) - 1); - if (copy_from_user(buf, data, len)) - return -EFAULT; - - buf[len] = '\0'; - if (kstrtouint(buf, 0, &value)) - return -EINVAL; + ret = kstrtou32_from_user(data, count, 0, &value); + if (unlikely(ret)) + return ret; if (ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].units) value /= ptc_mmio_regs[PTC_TEMP_OVERRIDE_INDEX].units; -- cgit From c010e13b470463928d91a75f51c99b63096d0f42 Mon Sep 17 00:00:00 2001 From: Amarjeet Date: Sat, 20 Jun 2026 17:56:37 +0530 Subject: tools/thermal/thermometer: close fd on realloc() failure thermometer_add_tz() opens tz_path and then reallocates thermometer->tz. If realloc fails, the function returns without closing fd. Close fd before returning on realloc failure to avoid leaking a file descriptor. Signed-off-by: Amarjeet Link: https://patch.msgid.link/20260620122637.1334927-1-amarjeet@intel.com Signed-off-by: Rafael J. Wysocki --- tools/thermal/thermometer/thermometer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/thermal/thermometer/thermometer.c b/tools/thermal/thermometer/thermometer.c index 022865da8e3c..1ae6c3ecedb4 100644 --- a/tools/thermal/thermometer/thermometer.c +++ b/tools/thermal/thermometer/thermometer.c @@ -272,6 +272,7 @@ static int thermometer_add_tz(const char *path, const char *name, int polling, tz = realloc(thermometer->tz, sizeof(*thermometer->tz) * (thermometer->nr_tz + 1)); if (!tz) { ERROR("Failed to allocate thermometer->tz\n"); + close(fd); return -1; } -- cgit From de8e50af88d9467d9d6cfb9f3eabfd7a3257c5f9 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sun, 19 Jul 2026 15:22:53 +0200 Subject: thermal: sysfs: Use sysfs_emit_at() in trans_table_show() Replace snprintf() with sysfs_emit_at() in trans_table_show(). sysfs_emit_at() is preferred for formatting sysfs output because it provides safer bounds checking. Convert multiple PAGE_SIZE length checks to check the number of bytes actually copied by sysfs_emit_at(), thus avoiding manual buffer size accounting. Consistently return -EFBIG when the transition table output exceeds the sysfs buffer, including the header row. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260719132251.31936-3-thorsten.blum@linux.dev Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_sysfs.c | 49 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index b44abfc997ed..adbcb2c011e8 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -706,7 +706,7 @@ static ssize_t trans_table_show(struct device *dev, struct thermal_cooling_device *cdev = to_cooling_device(dev); struct cooling_dev_stats *stats; ssize_t len = 0; - int i, j; + int i, j, copied; guard(cooling_dev)(cdev); @@ -714,41 +714,40 @@ static ssize_t trans_table_show(struct device *dev, if (!stats) return -ENODATA; - len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n"); - len += snprintf(buf + len, PAGE_SIZE - len, " : "); + len += sysfs_emit_at(buf, len, " From : To\n"); + len += sysfs_emit_at(buf, len, " : "); for (i = 0; i <= cdev->max_state; i++) { - if (len >= PAGE_SIZE) - break; - len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i); + copied = sysfs_emit_at(buf, len, "state%2u ", i); + if (!copied) + goto buf_full; + len += copied; } - if (len >= PAGE_SIZE) - return PAGE_SIZE; - - len += snprintf(buf + len, PAGE_SIZE - len, "\n"); + len += sysfs_emit_at(buf, len, "\n"); for (i = 0; i <= cdev->max_state; i++) { - if (len >= PAGE_SIZE) - break; - - len += snprintf(buf + len, PAGE_SIZE - len, "state%2u:", i); + copied = sysfs_emit_at(buf, len, "state%2u:", i); + if (!copied) + goto buf_full; + len += copied; for (j = 0; j <= cdev->max_state; j++) { - if (len >= PAGE_SIZE) - break; - len += snprintf(buf + len, PAGE_SIZE - len, "%8u ", + copied = sysfs_emit_at(buf, len, "%8u ", stats->trans_table[i * (cdev->max_state + 1) + j]); + if (!copied) + goto buf_full; + len += copied; } - if (len >= PAGE_SIZE) - break; - len += snprintf(buf + len, PAGE_SIZE - len, "\n"); - } - - if (len >= PAGE_SIZE) { - pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n"); - len = -EFBIG; + copied = sysfs_emit_at(buf, len, "\n"); + if (!copied) + goto buf_full; + len += copied; } return len; + +buf_full: + pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n"); + return -EFBIG; } static DEVICE_ATTR_RO(total_trans); -- cgit From 2b57e24d34b2dfc43c81942bb359d6315bf302fb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 4 Aug 2026 22:11:27 +0200 Subject: thermal: hwmon: Remove hwmon class device along with its parent The current code creates one hwmon device per thermal zone type and that device is registered under the first thermal zone of the given type. That turns out to be problematic when the thermal zone holding the hwmon device is removed. For example, say that there are two ACPI thermal zones on a system /sys/devices/virtual/thermal/thermal_zone0/ /sys/devices/virtual/thermal/thermal_zone1/ The current code registers a hwmon class device for thermal_zone0 only: /sys/devices/virtual/thermal/thermal_zone0/hwmon0/ because the type is "acpitz" for both of them, but it adds a sysfs attribute that belongs to thermal_zone1 under it: /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp2_input There is also /sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp1_input which belongs to thermal_zone0. When thermal_zone0 is removed, say because the ACPI thermal driver is unbound from the underlying platform device, thermal_remove_hwmon_sysfs() skips the removal of hwmon0 because of the temp2_input attribute belonging to thermal_zone1 which effectively prevents thermal_zone0 removal from making progress. Address this by making thermal_remove_hwmon_sysfs() remove the entire hwmon class device interface for the given thermal zone type when the thermal zone device holding it is removed. To prevent races with thermal_add_hwmon_sysfs() that may interfere with this, carry out the entire addition and removal of hwmon sysfs interfaces for thermal zones under thermal_hwmon_list_lock. Also adjust the layout of the labels in thermal_add_hwmon_sysfs() to the current kernel coding style to align with the new "unlock" label. Link: https://lore.kernel.org/linux-pm/20260402021828.16556-1-liujia6264@gmail.com/ Fixes: f6b6b52ef7a5 ("thermal_hwmon: Pass the originating device down to hwmon_device_register_with_info") Signed-off-by: Rafael J. Wysocki Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/5094738.GXAFRqVoOG@rafael.j.wysocki --- drivers/thermal/thermal_hwmon.c | 87 ++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 53 deletions(-) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index ec73d03a1e60..45ce21914df8 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -95,34 +95,12 @@ thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; char type[THERMAL_NAME_LENGTH]; - mutex_lock(&thermal_hwmon_list_lock); list_for_each_entry(hwmon, &thermal_hwmon_list, node) { strscpy(type, tz->type); strreplace(type, '-', '_'); - if (!strcmp(hwmon->type, type)) { - mutex_unlock(&thermal_hwmon_list_lock); + if (!strcmp(hwmon->type, type)) return hwmon; - } } - mutex_unlock(&thermal_hwmon_list_lock); - - return NULL; -} - -/* Find the temperature input matching a given thermal zone */ -static struct thermal_hwmon_temp * -thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, - const struct thermal_zone_device *tz) -{ - struct thermal_hwmon_temp *temp; - - mutex_lock(&thermal_hwmon_list_lock); - list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) - if (temp->tz == tz) { - mutex_unlock(&thermal_hwmon_list_lock); - return temp; - } - mutex_unlock(&thermal_hwmon_list_lock); return NULL; } @@ -138,7 +116,9 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) struct thermal_hwmon_device *hwmon; struct thermal_hwmon_temp *temp; int new_hwmon_device = 1; - int result; + int result = 0; + + mutex_lock(&thermal_hwmon_list_lock); hwmon = thermal_hwmon_lookup_by_type(tz); if (hwmon) { @@ -147,8 +127,10 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) } hwmon = kzalloc_obj(*hwmon); - if (!hwmon) - return -ENOMEM; + if (!hwmon) { + result = -ENOMEM; + goto unlock; + } INIT_LIST_HEAD(&hwmon->tz_list); strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); @@ -196,24 +178,24 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) temp->temp_crit_present = true; } - mutex_lock(&thermal_hwmon_list_lock); if (new_hwmon_device) list_add_tail(&hwmon->node, &thermal_hwmon_list); list_add_tail(&temp->hwmon_node, &hwmon->tz_list); - mutex_unlock(&thermal_hwmon_list_lock); - return 0; + goto unlock; - unregister_input: +unregister_input: device_remove_file(hwmon->device, &temp->temp_input.attr); - free_temp_mem: +free_temp_mem: kfree(temp); - unregister_name: +unregister_name: if (new_hwmon_device) hwmon_device_unregister(hwmon->device); - free_mem: +free_mem: if (new_hwmon_device) kfree(hwmon); +unlock: + mutex_unlock(&thermal_hwmon_list_lock); return result; } @@ -221,8 +203,11 @@ EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) { + struct thermal_hwmon_temp *temp, *entry; struct thermal_hwmon_device *hwmon; - struct thermal_hwmon_temp *temp; + bool unregister; + + guard(mutex)(&thermal_hwmon_list_lock); hwmon = thermal_hwmon_lookup_by_type(tz); if (unlikely(!hwmon)) { @@ -231,29 +216,25 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) return; } - temp = thermal_hwmon_lookup_temp(hwmon, tz); - if (unlikely(!temp)) { - /* Should never happen... */ - dev_dbg(&tz->device, "temperature input lookup failed!\n"); - return; - } + unregister = hwmon->device->parent == &tz->device; - device_remove_file(hwmon->device, &temp->temp_input.attr); - if (temp->temp_crit_present) - device_remove_file(hwmon->device, &temp->temp_crit.attr); + list_for_each_entry_safe_reverse(temp, entry, &hwmon->tz_list, hwmon_node) { + if (!unregister && temp->tz != tz) + continue; - mutex_lock(&thermal_hwmon_list_lock); - list_del(&temp->hwmon_node); - kfree(temp); - if (!list_empty(&hwmon->tz_list)) { - mutex_unlock(&thermal_hwmon_list_lock); - return; + device_remove_file(hwmon->device, &temp->temp_input.attr); + if (temp->temp_crit_present) + device_remove_file(hwmon->device, &temp->temp_crit.attr); + + list_del(&temp->hwmon_node); + kfree(temp); } - list_del(&hwmon->node); - mutex_unlock(&thermal_hwmon_list_lock); - hwmon_device_unregister(hwmon->device); - kfree(hwmon); + if (unregister) { + list_del(&hwmon->node); + hwmon_device_unregister(hwmon->device); + kfree(hwmon); + } } EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); -- cgit