* [RFC PATCH 1/4] cpufreq: Add a CPUFREQ_UPDATE_CPUINFO policy notifier event
2026-11-01 18:58 [RFC PATCH 0/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes Hugo Sales
@ 2026-11-01 18:58 ` Hugo Sales
2026-11-01 18:58 ` [RFC PATCH 2/4] cpufreq: intel_pstate: Send CPUFREQ_UPDATE_CPUINFO on max_freq changes Hugo Sales
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-01 18:58 UTC (permalink / raw)
To: Rafael J. Wysocki, Viresh Kumar
Cc: Len Brown, Zhang Rui, Srinivas Pandruvada, Jonathan Corbet,
Shuah Khan, Ashton A.,
linux-pm, linux-acpi, linux-doc, linux-kernel
Turning boost on or off changes policy->cpuinfo.max_freq in the
->set_boost() callbacks of amd-pstate, cppc_cpufreq and the drivers that
use cpufreq_boost_set_sw(). Users that derive a QoS request from
cpuinfo.max_freq have no way to learn about it. The ACPI processor
thermal code is one: its throttling steps are fixed fractions of
cpuinfo.max_freq, computed only when the cooling state is written
Its freq QoS notifiers don't help, since they only fire when the
aggregate limit changes. While a thermal cap is lower than both maximums,
turning boost on changes boost_freq_req but not the aggregate
Add a CPUFREQ_UPDATE_CPUINFO policy notifier event and send it from
policy_set_boost() once the driver has updated cpuinfo. The helper that
sends it is global, so a driver that changes cpuinfo.max_freq outside
->set_boost() can send it too
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
Documentation/cpu-freq/core.rst | 9 ++++++---
drivers/cpufreq/cpufreq.c | 16 ++++++++++++++++
include/linux/cpufreq.h | 2 ++
3 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/Documentation/cpu-freq/core.rst b/Documentation/cpu-freq/core.rst
index 4ceef8e7217c3..946913c024121 100644
--- a/Documentation/cpu-freq/core.rst
+++ b/Documentation/cpu-freq/core.rst
@@ -49,11 +49,14 @@ transition notifiers.
2.1 CPUFreq policy notifiers
----------------------------
-These are notified when a new policy is created or removed.
+These are notified when a new policy is created or removed, and when boost
+is turned on or off or the driver reports a change of cpuinfo.max_freq.
The phase is specified in the second argument to the notifier. The phase is
-CPUFREQ_CREATE_POLICY when the policy is first created and it is
-CPUFREQ_REMOVE_POLICY when the policy is removed.
+CPUFREQ_CREATE_POLICY when the policy is first created, it is
+CPUFREQ_REMOVE_POLICY when the policy is removed and it is
+CPUFREQ_UPDATE_CPUINFO when boost has been turned on or off or the driver
+reports that cpuinfo.max_freq has changed.
The third argument, a ``void *pointer``, points to a struct cpufreq_policy
consisting of several values, including min, max (the lower and upper
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 54dde8419bdc4..4d974dac4dff5 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -594,6 +594,8 @@ static int policy_set_boost(struct cpufreq_policy *policy, bool enable)
return ret;
}
+ cpufreq_notify_cpuinfo_update(policy);
+
return 0;
}
@@ -1184,6 +1186,20 @@ void refresh_frequency_limits(struct cpufreq_policy *policy)
}
EXPORT_SYMBOL(refresh_frequency_limits);
+/**
+ * cpufreq_notify_cpuinfo_update - Notify policy notifiers of a cpuinfo change
+ * @policy: Policy whose cpuinfo limits have changed
+ *
+ * Send CPUFREQ_UPDATE_CPUINFO to the policy notifiers, so that QoS requests
+ * derived from @policy->cpuinfo can be recomputed. An inactive policy is
+ * notified too, since its QoS requests stay until CPUFREQ_REMOVE_POLICY
+ */
+void cpufreq_notify_cpuinfo_update(struct cpufreq_policy *policy)
+{
+ blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
+ CPUFREQ_UPDATE_CPUINFO, policy);
+}
+
static void handle_update(struct work_struct *work)
{
struct cpufreq_policy *policy =
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index d3d0d9d02aa4a..f3acdcee7183f 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -249,6 +249,7 @@ void disable_cpufreq(void);
u64 get_cpu_idle_time(unsigned int cpu, u64 *wall, int io_busy);
void refresh_frequency_limits(struct cpufreq_policy *policy);
+void cpufreq_notify_cpuinfo_update(struct cpufreq_policy *policy);
void cpufreq_update_policy(unsigned int cpu);
void cpufreq_update_limits(unsigned int cpu);
bool have_governor_per_policy(void);
@@ -536,6 +537,7 @@ static inline void cpufreq_resume(void) {}
/* Policy Notifiers */
#define CPUFREQ_CREATE_POLICY (0)
#define CPUFREQ_REMOVE_POLICY (1)
+#define CPUFREQ_UPDATE_CPUINFO (2)
#ifdef CONFIG_CPU_FREQ
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
base-commit: 5854d2ce4b0b9ccb37b154319d8283bad889a694
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [RFC PATCH 2/4] cpufreq: intel_pstate: Send CPUFREQ_UPDATE_CPUINFO on max_freq changes
2026-11-01 18:58 [RFC PATCH 0/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes Hugo Sales
2026-11-01 18:58 ` [RFC PATCH 1/4] cpufreq: Add a CPUFREQ_UPDATE_CPUINFO policy notifier event Hugo Sales
@ 2026-11-01 18:58 ` Hugo Sales
2026-11-08 7:12 ` Ximing Zhang
2026-11-01 18:58 ` [RFC PATCH 3/4] ACPI: processor: thermal: Factor out the thermal_req update Hugo Sales
2026-11-01 18:58 ` [RFC PATCH 4/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes Hugo Sales
3 siblings, 1 reply; 6+ messages in thread
From: Hugo Sales @ 2026-11-01 18:58 UTC (permalink / raw)
To: Rafael J. Wysocki, Viresh Kumar
Cc: Len Brown, Zhang Rui, Srinivas Pandruvada, Jonathan Corbet,
Shuah Khan, Ashton A.,
linux-pm, linux-acpi, linux-doc, linux-kernel
intel_pstate has no ->set_boost() callback. It changes
policy->cpuinfo.max_freq in __intel_pstate_update_max_freq() when
no_turbo is written and when the HWP capabilities change, so
policy_set_boost() never sends CPUFREQ_UPDATE_CPUINFO for it
Send the event from there when the value changes, before the policy
limits are refreshed
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
drivers/cpufreq/intel_pstate.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c
index 0e6ae4cdda5de..f9e356409f199 100644
--- a/drivers/cpufreq/intel_pstate.c
+++ b/drivers/cpufreq/intel_pstate.c
@@ -1429,13 +1429,19 @@ static void intel_pstate_update_policies(void)
static void __intel_pstate_update_max_freq(struct cpufreq_policy *policy,
struct cpudata *cpudata)
{
+ unsigned int max_freq;
+
guard(cpufreq_policy_write)(policy);
if (hwp_active)
intel_pstate_get_hwp_cap(cpudata);
- policy->cpuinfo.max_freq = READ_ONCE(global.no_turbo) ?
- cpudata->pstate.max_freq : cpudata->pstate.turbo_freq;
+ max_freq = READ_ONCE(global.no_turbo) ?
+ cpudata->pstate.max_freq : cpudata->pstate.turbo_freq;
+ if (policy->cpuinfo.max_freq != max_freq) {
+ policy->cpuinfo.max_freq = max_freq;
+ cpufreq_notify_cpuinfo_update(policy);
+ }
refresh_frequency_limits(policy);
}
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [RFC PATCH 2/4] cpufreq: intel_pstate: Send CPUFREQ_UPDATE_CPUINFO on max_freq changes
2026-11-01 18:58 ` [RFC PATCH 2/4] cpufreq: intel_pstate: Send CPUFREQ_UPDATE_CPUINFO on max_freq changes Hugo Sales
@ 2026-11-08 7:12 ` Ximing Zhang
0 siblings, 0 replies; 6+ messages in thread
From: Ximing Zhang @ 2026-11-08 7:12 UTC (permalink / raw)
To: Hugo Sales
Cc: Ximing Zhang, Rafael J. Wysocki, Viresh Kumar, Len Brown,
Zhang Rui, Srinivas Pandruvada, Jonathan Corbet, Shuah Khan,
Ashton A.,
linux-pm, linux-acpi, linux-doc, linux-kernel
Hi Hugo,
I tested the four patches unchanged on an Intel Core i7-10510U using
intel_pstate in active/HWP mode, on the stated base commit
5854d2ce4b0b9ccb37b154319d8283bad889a694.
Cooling states 0 through 3 followed Turbo changes in both directions.
I also ran 100 CPU 7 offline/online cycles concurrently with Turbo and
cooling-state writes, followed by the same sequential checks, and a
suspend/resume via rtcwake -m mem -s 15. Turbo was disabled before suspend
and enabled afterward. Those checks passed with lockdep enabled and no
new warnings. The test kernel was already tainted by unsigned modules;
that taint did not change during the tests.
There is one remaining Intel hotplug case: changing Turbo while CPU 7
is offline leaves that policy's cpuinfo.max_freq stale when it returns.
I reproduced the same stale CPU information on the stock
7.2.5-1-cachyos kernel, so this behavior predates the RFC.
On the patched kernel, starting with all CPUs online, Turbo enabled and
cooling state 0, I ran the following as root:
cpu=/sys/devices/system/cpu
cdev=/sys/class/thermal/cooling_device0
echo 1 > $cpu/intel_pstate/no_turbo
echo 1 > $cdev/cur_state
echo 0 > $cpu/cpu7/online
echo 0 > $cpu/intel_pstate/no_turbo
echo 1 > $cpu/cpu7/online
cat $cpu/cpufreq/policy7/cpuinfo_max_freq
cat $cpu/cpufreq/policy7/scaling_max_freq
These report 1800000 and 1440000 kHz. The policies for the other CPUs
report 4900000 and 3920000 kHz. With the Turbo transition reversed,
CPU 7 returns with a cpuinfo maximum of 4900000 instead of 1800000,
and an effective cap of 1800000 instead of 1440000 kHz.
I restored the starting state with all CPUs online:
echo 1 > $cpu/intel_pstate/no_turbo
echo 0 > $cpu/intel_pstate/no_turbo
echo 0 > $cdev/cur_state
intel_pstate_update_max_freq() uses cpufreq_cpu_get(), whose raw lookup
rejects an offline CPU. The reused policy then takes
intel_pstate_cpu_online(), which does not refresh cpuinfo.max_freq.
This appears to prevent the new notification from covering that case.
Is this case intended to be covered by this series, or would a separate
intel_pstate follow-up be more appropriate?
Thanks,
Ximing
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH 3/4] ACPI: processor: thermal: Factor out the thermal_req update
2026-11-01 18:58 [RFC PATCH 0/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes Hugo Sales
2026-11-01 18:58 ` [RFC PATCH 1/4] cpufreq: Add a CPUFREQ_UPDATE_CPUINFO policy notifier event Hugo Sales
2026-11-01 18:58 ` [RFC PATCH 2/4] cpufreq: intel_pstate: Send CPUFREQ_UPDATE_CPUINFO on max_freq changes Hugo Sales
@ 2026-11-01 18:58 ` Hugo Sales
2026-11-01 18:58 ` [RFC PATCH 4/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes Hugo Sales
3 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-01 18:58 UTC (permalink / raw)
To: Rafael J. Wysocki, Viresh Kumar
Cc: Len Brown, Zhang Rui, Srinivas Pandruvada, Jonathan Corbet,
Shuah Khan, Ashton A.,
linux-pm, linux-acpi, linux-doc, linux-kernel
Split the cap computation and the thermal_req update out of
cpufreq_update_thermal_limit() into cpufreq_update_thermal_req(), which
takes the policy and the step as arguments. This prepares for
recomputing the cap on CPUFREQ_UPDATE_CPUINFO, where the notifier
passes the policy and the step is stored for each CPU
No functional change
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
drivers/acpi/processor_thermal.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c
index 11036f0d2b942..25c4b0ad64e36 100644
--- a/drivers/acpi/processor_thermal.c
+++ b/drivers/acpi/processor_thermal.c
@@ -88,23 +88,30 @@ static int cpufreq_get_cur_state(unsigned int cpu)
return reduction_step(cpu);
}
-static bool cpufreq_update_thermal_limit(unsigned int cpu, struct acpi_processor *pr)
+static void cpufreq_update_thermal_req(struct cpufreq_policy *policy,
+ struct acpi_processor *pr,
+ unsigned int step)
{
unsigned long max_freq;
int ret;
- struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpu);
- if (!policy)
- return false;
-
max_freq = (policy->cpuinfo.max_freq *
- (100 - reduction_step(cpu) * cpufreq_thermal_reduction_pctg)) / 100;
+ (100 - step * cpufreq_thermal_reduction_pctg)) / 100;
ret = freq_qos_update_request(&pr->thermal_req, max_freq);
if (ret < 0) {
pr_warn("Failed to update thermal freq constraint: CPU%d (%d)\n",
pr->id, ret);
}
+}
+
+static bool cpufreq_update_thermal_limit(unsigned int cpu, struct acpi_processor *pr)
+{
+ struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpu);
+ if (!policy)
+ return false;
+
+ cpufreq_update_thermal_req(policy, pr, reduction_step(cpu));
return true;
}
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [RFC PATCH 4/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes
2026-11-01 18:58 [RFC PATCH 0/4] ACPI: processor: thermal: Recompute the frequency cap when boost changes Hugo Sales
` (2 preceding siblings ...)
2026-11-01 18:58 ` [RFC PATCH 3/4] ACPI: processor: thermal: Factor out the thermal_req update Hugo Sales
@ 2026-11-01 18:58 ` Hugo Sales
3 siblings, 0 replies; 6+ messages in thread
From: Hugo Sales @ 2026-11-01 18:58 UTC (permalink / raw)
To: Rafael J. Wysocki, Viresh Kumar
Cc: Len Brown, Zhang Rui, Srinivas Pandruvada, Jonathan Corbet,
Shuah Khan, Ashton A.,
linux-pm, linux-acpi, linux-doc, linux-kernel
thermal_req is a fixed fraction of policy->cpuinfo.max_freq, computed
only when the cooling state is written, so it keeps the boost state of
that moment. That includes step 0, which caps at cpuinfo.max_freq
itself. The ACPI thermal zone writes step 0 on every resume, so if boost
was off at suspend, turning it on after resume leaves the CPUs at their
base clock
Recompute the cap on CPUFREQ_UPDATE_CPUINFO, as the CPUFREQ_ADJUST
notifier did before d15ce412737a, with the step each CPU's request was
last set to
Fixes: d15ce412737a ("ACPI: cpufreq: Switch to QoS requests instead of cpufreq notifier")
Link: https://lore.kernel.org/all/20261028-acpi-thermal-stale-limit-v1-1-4b0ab3230f01@ashtonantila.com/
Link: https://bugzilla.kernel.org/show_bug.cgi?id=221795
Signed-off-by: Hugo Sales <hugo@hsal.es>
---
This is an alternative to Ashton's one-line fix linked above, which
stops step 0 from setting a cap. Here step 0 still sets one, at
cpuinfo.max_freq, and it is recomputed with the other steps when boost
changes
The step is kept per CPU because reduction_step()'s per-package slot
moves when the first CPU of the package goes offline
A new mutex serializes the recompute with cpufreq_set_cur_state() and
with adding and removing thermal_req. Boost can be toggled while a
policy is being removed, and a cooling state write that read the old
cpuinfo.max_freq must not overwrite the recomputed cap
---
drivers/acpi/processor_driver.c | 2 ++
drivers/acpi/processor_thermal.c | 48 +++++++++++++++++++++++++++++---
include/acpi/processor.h | 4 +++
3 files changed, 50 insertions(+), 4 deletions(-)
diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c
index bdd1529d79f03..c3929425a88b6 100644
--- a/drivers/acpi/processor_driver.c
+++ b/drivers/acpi/processor_driver.c
@@ -221,6 +221,8 @@ static int acpi_processor_notifier(struct notifier_block *nb,
} else if (event == CPUFREQ_REMOVE_POLICY) {
acpi_processor_ppc_exit(policy);
acpi_thermal_cpufreq_exit(policy);
+ } else if (event == CPUFREQ_UPDATE_CPUINFO) {
+ acpi_thermal_cpufreq_update(policy);
}
return 0;
diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c
index 25c4b0ad64e36..8b1a3cb004571 100644
--- a/drivers/acpi/processor_thermal.c
+++ b/drivers/acpi/processor_thermal.c
@@ -14,6 +14,7 @@
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/acpi.h>
+#include <linux/mutex.h>
#include <acpi/processor.h>
#include <linux/uaccess.h>
@@ -40,6 +41,16 @@ static int cpufreq_thermal_max_step __read_mostly = 3;
static int cpufreq_thermal_reduction_pctg __read_mostly = 20;
static DEFINE_PER_CPU(unsigned int, cpufreq_thermal_reduction_step);
+/* The step each CPU's thermal_req was last set to */
+static DEFINE_PER_CPU(unsigned int, cpufreq_thermal_req_step);
+
+/*
+ * Serializes adding, updating and removing thermal_req, and the step saved
+ * with it, so recomputing the cap after a boost change can't race a cooling
+ * state write. It nests inside policy->rwsem and the cooling device lock,
+ * so take neither while holding it
+ */
+static DEFINE_MUTEX(cpufreq_thermal_lock);
#define reduction_step(cpu) \
per_cpu(cpufreq_thermal_reduction_step, phys_package_first_cpu(cpu))
@@ -95,6 +106,8 @@ static void cpufreq_update_thermal_req(struct cpufreq_policy *policy,
unsigned long max_freq;
int ret;
+ lockdep_assert_held(&cpufreq_thermal_lock);
+
max_freq = (policy->cpuinfo.max_freq *
(100 - step * cpufreq_thermal_reduction_pctg)) / 100;
@@ -102,7 +115,10 @@ static void cpufreq_update_thermal_req(struct cpufreq_policy *policy,
if (ret < 0) {
pr_warn("Failed to update thermal freq constraint: CPU%d (%d)\n",
pr->id, ret);
+ return;
}
+
+ per_cpu(cpufreq_thermal_req_step, pr->id) = step;
}
static bool cpufreq_update_thermal_limit(unsigned int cpu, struct acpi_processor *pr)
@@ -124,6 +140,8 @@ static int cpufreq_set_cur_state(unsigned int cpu, int state)
if (!cpu_has_cpufreq(cpu))
return 0;
+ guard(mutex)(&cpufreq_thermal_lock);
+
reduction_step(cpu) = state;
/*
@@ -177,9 +195,13 @@ void acpi_thermal_cpufreq_init(struct cpufreq_policy *policy)
if (!pr)
continue;
- ret = freq_qos_add_request(&policy->constraints,
- &pr->thermal_req,
- FREQ_QOS_MAX, INT_MAX);
+ scoped_guard(mutex, &cpufreq_thermal_lock) {
+ ret = freq_qos_add_request(&policy->constraints,
+ &pr->thermal_req,
+ FREQ_QOS_MAX, INT_MAX);
+ if (ret >= 0)
+ per_cpu(cpufreq_thermal_req_step, cpu) = 0;
+ }
if (ret < 0) {
pr_err("Failed to add freq constraint for CPU%d (%d)\n",
cpu, ret);
@@ -190,6 +212,23 @@ void acpi_thermal_cpufreq_init(struct cpufreq_policy *policy)
}
}
+void acpi_thermal_cpufreq_update(struct cpufreq_policy *policy)
+{
+ unsigned int cpu;
+
+ guard(mutex)(&cpufreq_thermal_lock);
+
+ for_each_cpu(cpu, policy->related_cpus) {
+ struct acpi_processor *pr = per_cpu(processors, cpu);
+
+ if (!pr || !freq_qos_request_active(&pr->thermal_req))
+ continue;
+
+ cpufreq_update_thermal_req(policy, pr,
+ per_cpu(cpufreq_thermal_req_step, cpu));
+ }
+}
+
void acpi_thermal_cpufreq_exit(struct cpufreq_policy *policy)
{
unsigned int cpu;
@@ -200,7 +239,8 @@ void acpi_thermal_cpufreq_exit(struct cpufreq_policy *policy)
if (!pr)
continue;
- freq_qos_remove_request(&pr->thermal_req);
+ scoped_guard(mutex, &cpufreq_thermal_lock)
+ freq_qos_remove_request(&pr->thermal_req);
thermal_cooling_device_update(pr->cdev);
}
diff --git a/include/acpi/processor.h b/include/acpi/processor.h
index b5447af4d40b4..ccd5ac6acb57e 100644
--- a/include/acpi/processor.h
+++ b/include/acpi/processor.h
@@ -432,12 +432,16 @@ void acpi_processor_thermal_exit(struct acpi_processor *pr);
extern const struct thermal_cooling_device_ops processor_cooling_ops;
#ifdef CONFIG_CPU_FREQ
void acpi_thermal_cpufreq_init(struct cpufreq_policy *policy);
+void acpi_thermal_cpufreq_update(struct cpufreq_policy *policy);
void acpi_thermal_cpufreq_exit(struct cpufreq_policy *policy);
#else
static inline void acpi_thermal_cpufreq_init(struct cpufreq_policy *policy)
{
return;
}
+
+static inline void acpi_thermal_cpufreq_update(struct cpufreq_policy *policy) { }
+
static inline void acpi_thermal_cpufreq_exit(struct cpufreq_policy *policy)
{
return;
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread