From: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
To: "Rafael J. Wysocki" <rafael@kernel.org>,
Daniel Lezcano <daniel.lezcano@kernel.org>,
Zhang Rui <rui.zhang@intel.com>,
Lukasz Luba <lukasz.luba@arm.com>,
Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Cc: linux-pm@vger.kernel.org, linux-api@vger.kernel.org,
linux-kernel@vger.kernel.org, ricardo.neri@intel.com,
Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
Subject: [PATCH 13/13] thermal: intel: dpti: Add sysfs control of interrupt owner
Date: Mon, 09 Nov 2026 19:36:40 -0800 [thread overview]
Message-ID: <20261109-dpti-custom-cpu-v1-13-ead8e2d2067e@linux.intel.com> (raw)
In-Reply-To: <20261109-dpti-custom-cpu-v1-0-ead8e2d2067e@linux.intel.com>
Override automatic selection and let the user choose a CPU to own a
package's directed package-level thermal interrupt (DPTI). Add a boolean
attribute:
/sys/devices/system/cpu/cpuX/thermal_throttle/package_interrupt_owner
Reading it reports whether the CPU owns its package's interrupt. Writing 1
migrates the interrupt to the CPU and pins it there. Writing 0 releases the
pin. Documentation/ABI/testing describes the semantics in full.
The read format matches the other boolean attributes under
/sys/devices/system/cpu. The write side uses kstrtobool(), as
umwait_control/enable_c02 does.
A user who isolates a CPU at runtime, through a cpuset isolated partition,
can move the interrupt away with this attribute. A pin to a
nohz_full-isolated CPU is honored with a warning, rate-limited because the
write is repeatable.
The attribute is created before the hardware is programmed, and a
programming failure during boot tears DPTI down for good. Remove the
attribute from the CPUs that already got it: otherwise it outlives the
feature it controls, failing every access with -ENODEV, while CPUs coming
online later get no such file at all. A failure after boot is different:
DPTI stays up, and the attribute keeps reporting hardware state truthfully.
The other thermal_throttle attributes need no equivalent teardown, as the
CPUID features and boot parameter gating them cannot be withdrawn at
runtime.
Although the owner is a per-package property, keep the implementation
simple and report ownership through the per-CPU thermal_throttle directory.
A CPU's file is a stateless view onto the same per-package state. This
mirrors the package_throttle_count attribute.
Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
checkpatch warns that the msleep(5) can sleep for up to 20ms and suggests
usleep_range(). The 5ms is taken from lock_device_hotplug_sysfs(), which
answers a contended hotplug lock with the same trylock, sleep and
restart_syscall() sequence. Sleeping longer than asked only delays the
retry of a contended write.
---
Documentation/ABI/testing/sysfs-devices-system-cpu | 40 ++++++
drivers/thermal/intel/therm_throt.c | 139 +++++++++++++++++++++
2 files changed, 179 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu
index 545687ebadcc..73dd75c9004a 100644
--- a/Documentation/ABI/testing/sysfs-devices-system-cpu
+++ b/Documentation/ABI/testing/sysfs-devices-system-cpu
@@ -830,3 +830,43 @@ Description:
degradations due to underlying physical CPU contention.
See Documentation/scheduler/sched-paravirt.rst for more details.
+
+What: /sys/devices/system/cpu/cpuX/thermal_throttle/package_interrupt_owner
+Date: November 2026
+Contact: Linux power management list <linux-pm@vger.kernel.org>
+Description:
+ (RW) Boolean. On CPUs supporting the directed package-level
+ thermal interrupt, a single CPU per package is programmed to
+ receive it. This attribute reports and controls whether cpuX is
+ that CPU.
+
+ Reading returns 1 if cpuX currently owns the package thermal
+ interrupt, 0 otherwise. The owner is per package: at most one
+ CPU of a package reads 1, and every CPU of the package reads 0
+ while the package has no owner. A package has no owner until one
+ is programmed, and none if the hardware does not acknowledge
+ one. On Intel processors, a package without an owner broadcasts
+ the thermal interrupt to all of its CPUs. If such a failure
+ happens while the kernel boots, the attribute does not exist on
+ any CPU.
+
+ Writing 1 (also 'y' or 'on') migrates the interrupt to cpuX and
+ pins it there, overriding automatic selection. The request is
+ honored even if cpuX is nohz_full-isolated; a warning is logged
+ in that case, since a directed interrupt disturbs the isolated
+ CPU. The write fails if the hardware does not acknowledge the
+ new owner.
+
+ Writing 0 (also 'n' or 'off') releases a pin on cpuX and returns
+ the package to automatic selection, which prefers CPUs that are
+ not nohz_full-isolated. If cpuX is nohz_full-isolated and such a
+ preferred CPU of the package is online, the interrupt moves
+ there, provided the hardware acknowledges the new owner.
+ Otherwise cpuX remains the owner and keeps reading 1. Writing 0
+ to a CPU that is not pinned has no effect.
+
+ The pin survives offlining. While a pinned CPU is offline this
+ attribute does not exist for it, and automatic selection hands
+ the interrupt to another CPU of the package, which reads 1 in
+ the meantime. The interrupt returns to the pinned CPU as soon as
+ it comes back online.
diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c
index c8c91a621cf6..1688f6362f6f 100644
--- a/drivers/thermal/intel/therm_throt.c
+++ b/drivers/thermal/intel/therm_throt.c
@@ -18,6 +18,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/sched/isolation.h>
+#include <linux/sched/signal.h>
#include <linux/syscore_ops.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
@@ -760,6 +761,13 @@ static void dpti_warn_if_isolated(unsigned int cpu, enum dpti_rank rank)
topology_logical_package_id(cpu), cpu);
}
+static void dpti_warn_if_pinned_isolated(unsigned int cpu)
+{
+ if (dpti_cpu_rank(cpu) == DPTI_RANK_ISOLATED)
+ pr_warn_ratelimited("Package %u thermal interrupt pinned to nohz_full-isolated CPU%u\n",
+ topology_logical_package_id(cpu), cpu);
+}
+
/*
* Reselect the owner for @cpu's package and migrate to it, but only on a
* strict rank improvement.
@@ -881,6 +889,121 @@ static void dpti_offline(unsigned int cpu)
dpti_warn_if_isolated(new_cpu, new_rank);
}
+/*
+ * The DPTI owner is a per-package property, but it is exposed through the
+ * existing per-CPU thermal_throttle sysfs group.
+ *
+ * Reading reports whether @cpu is the owner programmed in hardware, not
+ * whether it is pinned. A released pin therefore still reads 1 until the
+ * placement changes, and while a pinned CPU is offline the remaining CPUs
+ * report the automatically chosen fallback.
+ */
+static ssize_t package_interrupt_owner_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int cpu = dev->id;
+ bool is_owner;
+ u16 pkg_id;
+ int ret;
+
+ ret = dpti_lookup_pkg_id(cpu, &pkg_id);
+ if (ret)
+ return ret;
+
+ scoped_guard(mutex, &dpti_mutex)
+ is_owner = dpti_pkgs[pkg_id].owner == cpu;
+
+ return sysfs_emit(buf, "%u\n", is_owner);
+}
+
+static int dpti_pin_owner(unsigned int cpu)
+{
+ struct dpti_pkg *pkg = dpti_pkg_of_cpu_nocheck(cpu);
+ unsigned int old_pin;
+ int ret;
+
+ guard(mutex)(&dpti_mutex);
+
+ /* The pin outranks every automatic choice, so it always migrates. */
+ old_pin = pkg->pin;
+ pkg->pin = cpu;
+
+ /*
+ * A failed write must not change policy or get silently retried on the
+ * next hotplug event.
+ */
+ ret = dpti_update_owner(cpu);
+ if (ret)
+ pkg->pin = old_pin;
+ else
+ dpti_warn_if_pinned_isolated(cpu);
+
+ return ret;
+}
+
+static void dpti_unpin_owner(unsigned int cpu)
+{
+ struct dpti_pkg *pkg = dpti_pkg_of_cpu_nocheck(cpu);
+
+ guard(mutex)(&dpti_mutex);
+
+ if (pkg->pin != cpu)
+ return;
+
+ pkg->pin = nr_cpu_ids;
+
+ /*
+ * Without the pin, a housekeeping CPU of the package outranks an
+ * isolated owner. Migrate rather than wait for a re-evaluation that may
+ * never come. A hardware acknowledgment failure leaves the interrupt in
+ * place, which is still a valid outcome of releasing a pin.
+ */
+ dpti_update_owner(cpu);
+}
+
+static ssize_t package_interrupt_owner_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int cpu = dev->id;
+ bool want_pin;
+ int ret;
+
+ ret = dpti_lookup_pkg_id(cpu, NULL);
+ if (ret)
+ return ret;
+
+ ret = kstrtobool(buf, &want_pin);
+ if (ret)
+ return ret;
+
+ /*
+ * The selection reads the package's online CPUs; the programming runs
+ * remotely on the CPU it picks. Both need the CPU hotplug lock, which
+ * this write cannot block on: offlining @cpu holds it while
+ * sysfs_remove_group() waits for this write to release its reference on
+ * the attribute. Restart the syscall instead, which drops that
+ * reference and lets the offline proceed. Avoid busy looping with a 5ms
+ * sleep between tries. lock_device_hotplug_sysfs() follows the same
+ * approach.
+ */
+ if (!cpus_read_trylock()) {
+ msleep(5);
+ return restart_syscall();
+ }
+
+ if (want_pin)
+ ret = dpti_pin_owner(cpu);
+ else
+ dpti_unpin_owner(cpu);
+
+ cpus_read_unlock();
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(package_interrupt_owner);
+
/*
* CPU0 may own the directed interrupt, but the CPU hotplug callbacks are not
* called for CPU0 during suspend and resume.
@@ -947,9 +1070,17 @@ static __init void dpti_init(void)
static void dpti_cleanup(void)
{
+ unsigned int cpu;
+
if (!dpti_supported())
return;
+ /* The attribute must not outlive the per-package state. */
+ for_each_online_cpu(cpu)
+ sysfs_remove_file_from_group(&get_cpu_device(cpu)->kobj,
+ &dev_attr_package_interrupt_owner.attr,
+ thermal_attr_group.name);
+
unregister_syscore(&dpti_syscore);
dpti_clear_all();
kfree(dpti_pkgs);
@@ -966,6 +1097,14 @@ static int thermal_throttle_add_dev(struct device *dev, unsigned int cpu)
if (err)
return err;
+ if (dpti_supported()) {
+ err = sysfs_add_file_to_group(&dev->kobj,
+ &dev_attr_package_interrupt_owner.attr,
+ thermal_attr_group.name);
+ if (err)
+ goto del_group;
+ }
+
if (cpu_has(c, X86_FEATURE_PLN) && int_pln_enable) {
err = sysfs_add_file_to_group(&dev->kobj,
&dev_attr_core_power_limit_count.attr,
--
2.43.0
prev parent reply other threads:[~2026-11-10 3:36 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 3:36 [PATCH 00/13] thermal: intel: dpti: Direct the package thermal interrupt to a chosen CPU Ricardo Neri
2026-11-10 3:36 ` [PATCH 01/13] thermal: intel: Keep directed package interrupts off after syscore shutdown Ricardo Neri
2026-11-10 3:36 ` [PATCH 02/13] thermal: intel: Prefix log messages via pr_fmt() Ricardo Neri
2026-11-10 3:36 ` [PATCH 03/13] thermal: intel: Move the sysfs device helpers next to their callers Ricardo Neri
2026-11-10 3:36 ` [PATCH 04/13] thermal: intel: dpti: Adopt the dpti_ prefix and the owner terminology Ricardo Neri
2026-11-10 3:36 ` [PATCH 05/13] thermal: intel: dpti: Tear down DPTI only during boot Ricardo Neri
2026-11-10 3:36 ` [PATCH 06/13] thermal: intel: dpti: Introduce struct dpti_pkg for per-package state Ricardo Neri
2026-11-10 3:36 ` [PATCH 07/13] thermal: intel: dpti: Configure the local CPU by value Ricardo Neri
2026-11-10 3:36 ` [PATCH 08/13] thermal: intel: dpti: Factor the programming sequence out into a helper Ricardo Neri
2026-11-10 3:36 ` [PATCH 09/13] thermal: intel: dpti: Program the directed interrupt on the target CPU Ricardo Neri
2026-11-10 3:36 ` [PATCH 10/13] thermal: intel: dpti: Add rank-based owner selection Ricardo Neri
2026-11-10 3:36 ` [PATCH 11/13] thermal: intel: dpti: Serialize updates to the directed interrupt state Ricardo Neri
2026-11-10 3:36 ` [PATCH 12/13] thermal: intel: dpti: Avoid nohz_full CPUs when possible Ricardo Neri
2026-11-10 3:36 ` Ricardo Neri [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109-dpti-custom-cpu-v1-13-ead8e2d2067e@linux.intel.com \
--to=ricardo.neri-calderon@linux.intel.com \
--cc=daniel.lezcano@kernel.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=lukasz.luba@arm.com \
--cc=rafael@kernel.org \
--cc=ricardo.neri@intel.com \
--cc=rui.zhang@intel.com \
--cc=srinivas.pandruvada@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®