mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


      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®