Skip to contentSkip to conversation

cpufreq: Avoid using inconsistent policy->min and policy->max

PATCH In review Rafael J. Wysocki 5 participants 7 messages Apr 16, 2025 – Apr 25, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH v3 1/1 cpufreq: Avoid using inconsistent policy->min and policy->max

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
drivers/cpufreq/cpufreq.c +25 -7
@@ -495,8 +495,6 @@
 {
 	unsigned int idx;
 
-	target_freq = clamp_val(target_freq, policy->min, policy->max);
-
 	if (!policy->freq_table)
 		return target_freq;
 
@@ -520,7 +518,22 @@
 unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy,
 					 unsigned int target_freq)
 {
-	return __resolve_freq(policy, target_freq, CPUFREQ_RELATION_LE);
+	unsigned int min = READ_ONCE(policy->min);
+	unsigned int max = READ_ONCE(policy->max);
+
+	/*
+	 * If this function runs in parallel with cpufreq_set_policy(), it may
+	 * read policy->min before the update and policy->max after the update
+	 * or the other way around, so there is no ordering guarantee.
+	 *
+	 * Resolve this by always honoring the max (in case it comes from
+	 * thermal throttling or similar).
+	 */
+	if (unlikely(min > max))
+		min = max;
+
+	return __resolve_freq(policy, clamp_val(target_freq, min, max),
+			      CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2338,6 +2351,7 @@
 	if (cpufreq_disabled())
 		return -ENODEV;
 
+	target_freq = clamp_val(target_freq, policy->min, policy->max);
 	target_freq = __resolve_freq(policy, target_freq, relation);
 
 	pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
@@ -2631,11 +2645,15 @@
 	 * Resolve policy min/max to available frequencies. It ensures
 	 * no frequency resolution will neither overshoot the requested maximum
 	 * nor undershoot the requested minimum.
+	 *
+	 * Avoid storing intermediate values in policy->max or policy->min and
+	 * compiler optimizations around them because they may be accessed
+	 * concurrently by cpufreq_driver_resolve_freq() during the update.
 	 */
-	policy->min = new_data.min;
-	policy->max = new_data.max;
-	policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
-	policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
+	WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
+	new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+	WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);
+
 	trace_cpu_frequency_limits(policy);
 
 	cpufreq_update_pressure(policy);
Reviewed-by on v3 1/1
#1 lore raw
Quoted 25 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Quoted 3 lines
Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+
Quoted 83 lines

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
drivers/cpufreq/cpufreq.c +25 -7
@@ -495,8 +495,6 @@
 {
 	unsigned int idx;
 
-	target_freq = clamp_val(target_freq, policy->min, policy->max);
-
 	if (!policy->freq_table)
 		return target_freq;
 
@@ -520,7 +518,22 @@
 unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy,
 					 unsigned int target_freq)
 {
-	return __resolve_freq(policy, target_freq, CPUFREQ_RELATION_LE);
+	unsigned int min = READ_ONCE(policy->min);
+	unsigned int max = READ_ONCE(policy->max);
+
+	/*
+	 * If this function runs in parallel with cpufreq_set_policy(), it may
+	 * read policy->min before the update and policy->max after the update
+	 * or the other way around, so there is no ordering guarantee.
+	 *
+	 * Resolve this by always honoring the max (in case it comes from
+	 * thermal throttling or similar).
+	 */
+	if (unlikely(min > max))
+		min = max;
+
+	return __resolve_freq(policy, clamp_val(target_freq, min, max),
+			      CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2338,6 +2351,7 @@
 	if (cpufreq_disabled())
 		return -ENODEV;
 
+	target_freq = clamp_val(target_freq, policy->min, policy->max);
 	target_freq = __resolve_freq(policy, target_freq, relation);
 
 	pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
@@ -2631,11 +2645,15 @@
 	 * Resolve policy min/max to available frequencies. It ensures
 	 * no frequency resolution will neither overshoot the requested maximum
 	 * nor undershoot the requested minimum.
+	 *
+	 * Avoid storing intermediate values in policy->max or policy->min and
+	 * compiler optimizations around them because they may be accessed
+	 * concurrently by cpufreq_driver_resolve_freq() during the update.
 	 */
-	policy->min = new_data.min;
-	policy->max = new_data.max;
-	policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
-	policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
+	WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
+	new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+	WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);
+
 	trace_cpu_frequency_limits(policy);
 
 	cpufreq_update_pressure(policy);
maintainer Acked-by on v3 1/1
#2 lore raw
In reply to Rafael J. Wysocki
Quoted 39 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

Quoted 5 lines

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
Signature · 1 line
viresh
#3 lore raw
In reply to Rafael J. Wysocki

Hi Rafael,

Quoted 94 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
drivers/cpufreq/cpufreq.c +19 -3
@@ -495,8 +495,6 @@
 {
 	unsigned int idx;
 
-	target_freq = clamp_val(target_freq, policy->min, policy->max);
-
 	if (!policy->freq_table)
 		return target_freq;
 
@@ -520,7 +518,22 @@
 unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy,
 					 unsigned int target_freq)
 {
-	return __resolve_freq(policy, target_freq, CPUFREQ_RELATION_LE);
+	unsigned int min = READ_ONCE(policy->min);
+	unsigned int max = READ_ONCE(policy->max);
+
+	/*
+	 * If this function runs in parallel with cpufreq_set_policy(), it may
+	 * read policy->min before the update and policy->max after the update
+	 * or the other way around, so there is no ordering guarantee.
+	 *
+	 * Resolve this by always honoring the max (in case it comes from
+	 * thermal throttling or similar).
+	 */
+	if (unlikely(min > max))
+		min = max;
+
+	return __resolve_freq(policy, clamp_val(target_freq, min, max),
+			      CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2338,6 +2351,7 @@
 	if (cpufreq_disabled())
 		return -ENODEV;
 
+	target_freq = clamp_val(target_freq, policy->min, policy->max);
 	target_freq = __resolve_freq(policy, target_freq, relation);
 
 	pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
@@ -2631,11 +2645,15 @@
 	 * Resolve policy min/max to available frequencies. It ensures
 	 * no frequency resolution will neither overshoot the requested maximum
 	 * nor undershoot the requested minimum.
+	 *
+	 * Avoid storing intermediate values in policy->max or policy->min and

drivers/cpufreq/cpufreq.c line 2655

Quoted 10 lines
drivers/cpufreq/cpufreq.c +5 -4
+	 * compiler optimizations around them because they may be accessed
+	 * concurrently by cpufreq_driver_resolve_freq() during the update.
 	 */
-	policy->min = new_data.min;
-	policy->max = new_data.max;
-	policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
-	policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
+	WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
+	new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+	WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);

I've tested the cpufreq throttling again in 6.15-rc3 to check your fix for the schedutil CPUFREQ_NEED_UPDATE_LIMITS regression I reported [1]. The CPU frequency is now being throttled correctly when reaching high temperatures. Thanks for fixing this!

Unfortunately, the opposite case has now regressed with this patch:
After the CPU frequency has been throttled due to high temperature and the device cools down again, the CPU frequency is stuck at minimum until you reboot. policy->max will never restore to the maximum frequency.

I've confirmed that this causes unexpected slowness after temperature throttling on a Qualcomm X1E laptop, and Johan has confirmed that e.g. the ThinkPad X13s is also affected. I would expect that most devices using cpufreq cooling in the kernel are affected.

Looking at the code, I think the problem is that __resolve_freq() -> cpufreq_frequency_table_target() -> cpufreq_table_find_index*() and cpufreq_is_in_limits() are still using the old policy->min/max value. In this patch, you have only moved the clamp_val() usage directly in __resolve_freq().

You can see this in the following debug log. I started a stress test that increases the device temperature until the CPU frequency was throttled to minimum. Then I stopped the stress test, the device cooled down, cpufreq_set_policy() was called with the new max frequency, but __resolve_freq() still returned the frequency clamped to the old maximum.

  [  149.959693] cpufreq: handle_update for cpu 0 called
  [  149.964782] cpufreq: updating policy for CPU 0
  [  149.969411] cpufreq: setting new policy for CPU 0: 0 - 3206400 kHz
  [  149.975842] cpufreq: new min and max freqs are 710400 - 3206400 kHz
  [  149.982347] cpufreq: governor limits update
  [  149.986715] cpufreq: cpufreq_governor_limits: for CPU 0
  [...]
  [  161.219209] cpufreq: handle_update for cpu 0 called
  [  161.224291] cpufreq: updating policy for CPU 0
  [  161.228927] cpufreq: setting new policy for CPU 0: 0 - 710400 kHz
  [  161.235238] cpufreq: new min and max freqs are 710400 - 710400 kHz
  [  161.241635] cpufreq: governor limits update
  [  161.245989] cpufreq: cpufreq_governor_limits: for CPU 0
  [  221.253253] cpufreq: handle_update for cpu 0 called
  [  221.258322] cpufreq: updating policy for CPU 0
  [  221.262946] cpufreq: setting new policy for CPU 0: 0 - 3417600 kHz
  [  221.269418] cpufreq: new min and max freqs are 710400 - 710400 kHz
                 ^ here the new maximum is not being applied
  [  221.275839] cpufreq: governor limits update
  [  221.280195] cpufreq: cpufreq_governor_limits: for CPU 0

Any thoughts how to fix this properly?

Please Cc me if you send a patch with the fix. The Reported-by: tag from me on the fix for the CPUFREQ_NEED_UPDATE_LIMITS problem didn't Cc me when sending for some reason, so I only learned about your fix when Greg sent out the stable backports yesterday. :-)

Thanks,
Stephan

[1]: https://lore.kernel.org/lkml/[email protected]/

#regzbot introduced: 7491cdf46b5cbdf123fc84fbe0a07e9e3d7b7620

#4 lore raw

Hi,

Quoted 124 lines · Stephan Gerhold

Hi Rafael,

Quoted 94 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
drivers/cpufreq/cpufreq.c +19 -3
@@ -495,8 +495,6 @@
 {
      unsigned int idx;
 
-     target_freq = clamp_val(target_freq, policy->min, policy->max);
-
      if (!policy->freq_table)
              return target_freq;
 
@@ -520,7 +518,22 @@
 unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy,
                                       unsigned int target_freq)
 {
-     return __resolve_freq(policy, target_freq, CPUFREQ_RELATION_LE);
+     unsigned int min = READ_ONCE(policy->min);
+     unsigned int max = READ_ONCE(policy->max);
+
+     /*
+      * If this function runs in parallel with cpufreq_set_policy(), it may
+      * read policy->min before the update and policy->max after the update
+      * or the other way around, so there is no ordering guarantee.
+      *
+      * Resolve this by always honoring the max (in case it comes from
+      * thermal throttling or similar).
+      */
+     if (unlikely(min > max))
+             min = max;
+
+     return __resolve_freq(policy, clamp_val(target_freq, min, max),
+                           CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2338,6 +2351,7 @@
      if (cpufreq_disabled())
              return -ENODEV;
 
+     target_freq = clamp_val(target_freq, policy->min, policy->max);
      target_freq = __resolve_freq(policy, target_freq, relation);
 
      pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
@@ -2631,11 +2645,15 @@
       * Resolve policy min/max to available frequencies. It ensures
       * no frequency resolution will neither overshoot the requested maximum
       * nor undershoot the requested minimum.
+      *
+      * Avoid storing intermediate values in policy->max or policy->min and

drivers/cpufreq/cpufreq.c line 2655

Quoted 10 lines
drivers/cpufreq/cpufreq.c +5 -4
+      * compiler optimizations around them because they may be accessed
+      * concurrently by cpufreq_driver_resolve_freq() during the update.
       */
-     policy->min = new_data.min;
-     policy->max = new_data.max;
-     policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
-     policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
+     WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
+     new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+     WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);

I've tested the cpufreq throttling again in 6.15-rc3 to check your fix for the schedutil CPUFREQ_NEED_UPDATE_LIMITS regression I reported [1]. The CPU frequency is now being throttled correctly when reaching high temperatures. Thanks for fixing this!

Unfortunately, the opposite case has now regressed with this patch:
After the CPU frequency has been throttled due to high temperature and the device cools down again, the CPU frequency is stuck at minimum until you reboot. policy->max will never restore to the maximum frequency.

I've confirmed that this causes unexpected slowness after temperature throttling on a Qualcomm X1E laptop, and Johan has confirmed that e.g. the ThinkPad X13s is also affected. I would expect that most devices using cpufreq cooling in the kernel are affected.

Quoted 5 lines

Looking at the code, I think the problem is that __resolve_freq() -> cpufreq_frequency_table_target() -> cpufreq_table_find_index*() and cpufreq_is_in_limits() are still using the old policy->min/max value. In this patch, you have only moved the clamp_val() usage directly in __resolve_freq().

You are right, that's the problem.

The fix is basically straightforward, pass min and max to cpufreq_frequency_table_target() and propagate downward, but making that change may be somewhat error-prone.

I'll try to cut a patch to test tomorrow.

Thanks!

#5 lore raw
Quoted 134 lines · CEST Rafael J. Wysocki

Hi,

Quoted 124 lines · Stephan Gerhold

Hi Rafael,

Quoted 94 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
drivers/cpufreq/cpufreq.c +19 -3
@@ -495,8 +495,6 @@
 {
      unsigned int idx;
 
-     target_freq = clamp_val(target_freq, policy->min, policy->max);
-
      if (!policy->freq_table)
              return target_freq;
 
@@ -520,7 +518,22 @@
 unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy,
                                       unsigned int target_freq)
 {
-     return __resolve_freq(policy, target_freq, CPUFREQ_RELATION_LE);
+     unsigned int min = READ_ONCE(policy->min);
+     unsigned int max = READ_ONCE(policy->max);
+
+     /*
+      * If this function runs in parallel with cpufreq_set_policy(), it may
+      * read policy->min before the update and policy->max after the update
+      * or the other way around, so there is no ordering guarantee.
+      *
+      * Resolve this by always honoring the max (in case it comes from
+      * thermal throttling or similar).
+      */
+     if (unlikely(min > max))
+             min = max;
+
+     return __resolve_freq(policy, clamp_val(target_freq, min, max),
+                           CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2338,6 +2351,7 @@
      if (cpufreq_disabled())
              return -ENODEV;
 
+     target_freq = clamp_val(target_freq, policy->min, policy->max);
      target_freq = __resolve_freq(policy, target_freq, relation);
 
      pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
@@ -2631,11 +2645,15 @@
       * Resolve policy min/max to available frequencies. It ensures
       * no frequency resolution will neither overshoot the requested maximum
       * nor undershoot the requested minimum.
+      *
+      * Avoid storing intermediate values in policy->max or policy->min and

drivers/cpufreq/cpufreq.c line 2655

Quoted 10 lines
drivers/cpufreq/cpufreq.c +5 -4
+      * compiler optimizations around them because they may be accessed
+      * concurrently by cpufreq_driver_resolve_freq() during the update.
       */
-     policy->min = new_data.min;
-     policy->max = new_data.max;
-     policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
-     policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
+     WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
+     new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+     WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);

I've tested the cpufreq throttling again in 6.15-rc3 to check your fix for the schedutil CPUFREQ_NEED_UPDATE_LIMITS regression I reported [1]. The CPU frequency is now being throttled correctly when reaching high temperatures. Thanks for fixing this!

Unfortunately, the opposite case has now regressed with this patch:
After the CPU frequency has been throttled due to high temperature and the device cools down again, the CPU frequency is stuck at minimum until you reboot. policy->max will never restore to the maximum frequency.

I've confirmed that this causes unexpected slowness after temperature throttling on a Qualcomm X1E laptop, and Johan has confirmed that e.g. the ThinkPad X13s is also affected. I would expect that most devices using cpufreq cooling in the kernel are affected.

Quoted 5 lines

Looking at the code, I think the problem is that __resolve_freq() -> cpufreq_frequency_table_target() -> cpufreq_table_find_index*() and cpufreq_is_in_limits() are still using the old policy->min/max value. In this patch, you have only moved the clamp_val() usage directly in __resolve_freq().

Quoted 7 lines

You are right, that's the problem.

The fix is basically straightforward, pass min and max to cpufreq_frequency_table_target() and propagate downward, but making that change may be somewhat error-prone.

I'll try to cut a patch to test tomorrow.

Actually, I have it already, so please find appended.

Please give it a go as soon as you reasonably can.

Thanks!

---
From: Rafael J. Wysocki <[email protected]>
Subject: [PATCH v1] cpufreq: Fix setting policy limits when frequency tables are used

Commit 7491cdf46b5c ("cpufreq: Avoid using inconsistent policy->min and policy->max") overlooked the fact that policy->min and policy->max were accessed directly in cpufreq_frequency_table_target() and in the functions called by it and the changes made by that commit led to problems with setting policy limits.

Address this by passing the target frequency limits to __resolve_freq() and cpufreq_frequency_table_target() and propagating them to the functions called by the latter.

Fixes 7491cdf46b5c ("cpufreq: Avoid using inconsistent policy->min and policy->max")
---
 drivers/cpufreq/cpufreq.c          |   22 ++++++----
 drivers/cpufreq/cpufreq_ondemand.c |    3 -
 drivers/cpufreq/freq_table.c       |    6 +-
 include/linux/cpufreq.h            |   81 ++++++++++++++++++++++++-------------
 4 files changed, 72 insertions(+), 40 deletions(-)
drivers/cpufreq/cpufreq.c +14 -8
@@ -491,14 +491,18 @@
 EXPORT_SYMBOL_GPL(cpufreq_disable_fast_switch);
 
 static unsigned int __resolve_freq(struct cpufreq_policy *policy,
-		unsigned int target_freq, unsigned int relation)
+				   unsigned int target_freq,
+				   unsigned int min, unsigned int max,
+				   unsigned int relation)
 {
 	unsigned int idx;
 
 	if (!policy->freq_table)
 		return target_freq;
 
-	idx = cpufreq_frequency_table_target(policy, target_freq, relation);
+	target_freq = clamp_val(target_freq, min, max);
+
+	idx = cpufreq_frequency_table_target(policy, target_freq, min, max, relation);
 	policy->cached_resolved_idx = idx;
 	policy->cached_target_freq = target_freq;
 	return policy->freq_table[idx].frequency;
@@ -532,8 +536,7 @@
 	if (unlikely(min > max))
 		min = max;
 
-	return __resolve_freq(policy, clamp_val(target_freq, min, max),
-			      CPUFREQ_RELATION_LE);
+	return __resolve_freq(policy, target_freq, min, max, CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2351,8 +2354,8 @@
 	if (cpufreq_disabled())
 		return -ENODEV;
 
-	target_freq = clamp_val(target_freq, policy->min, policy->max);
-	target_freq = __resolve_freq(policy, target_freq, relation);
+	target_freq = __resolve_freq(policy, target_freq, policy->min,
+				     policy->max, relation);
 
 	pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
 		 policy->cpu, target_freq, relation, old_target_freq);
@@ -2650,8 +2653,11 @@
 	 * compiler optimizations around them because they may be accessed
 	 * concurrently by cpufreq_driver_resolve_freq() during the update.
 	 */
-	WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
-	new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+	WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max,
+					       new_data.min, new_data.max,
+					       CPUFREQ_RELATION_H));
+	new_data.min = __resolve_freq(policy, new_data.min, new_data.min,
+				      new_data.max, CPUFREQ_RELATION_L);
 	WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);
 
 	trace_cpu_frequency_limits(policy);
drivers/cpufreq/cpufreq_ondemand.c +2 -1
@@ -76,7 +76,8 @@
 		return freq_next;
 	}
 
-	index = cpufreq_frequency_table_target(policy, freq_next, relation);
+	index = cpufreq_frequency_table_target(policy, freq_next, policy->min,
+					       policy->max, relation);
 	freq_req = freq_table[index].frequency;
 	freq_reduc = freq_req * od_tuners->powersave_bias / 1000;
 	freq_avg = freq_req - freq_reduc;
drivers/cpufreq/freq_table.c +3 -3
@@ -115,8 +115,8 @@
 EXPORT_SYMBOL_GPL(cpufreq_generic_frequency_table_verify);
 
 int cpufreq_table_index_unsorted(struct cpufreq_policy *policy,
-				 unsigned int target_freq,
-				 unsigned int relation)
+				 unsigned int target_freq, unsigned int min,
+				 unsigned int max, unsigned int relation)
 {
 	struct cpufreq_frequency_table optimal = {
 		.driver_data = ~0,
@@ -147,7 +147,7 @@
 	cpufreq_for_each_valid_entry_idx(pos, table, i) {
 		freq = pos->frequency;
 
-		if ((freq < policy->min) || (freq > policy->max))
+		if (freq < min || freq > max)
 			continue;
 		if (freq == target_freq) {
 			optimal.driver_data = i;
include/linux/cpufreq.h +53 -28

155 lines not on this page · raw message

#6 lore raw
Quoted 182 lines · Rafael J. Wysocki
Quoted 134 lines · CEST Rafael J. Wysocki

Hi,

Quoted 124 lines · Stephan Gerhold

Hi Rafael,

Quoted 94 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

Since cpufreq_driver_resolve_freq() can run in parallel with cpufreq_set_policy() and there is no synchronization between them, the former may access policy->min and policy->max while the latter is updating them and it may see intermediate values of them due to the way the update is carried out. Also the compiler is free to apply any optimizations it wants both to the stores in cpufreq_set_policy() and to the loads in cpufreq_driver_resolve_freq() which may result in additional inconsistencies.

To address this, use WRITE_ONCE() when updating policy->min and policy->max in cpufreq_set_policy() and use READ_ONCE() for reading them in cpufreq_driver_resolve_freq(). Moreover, rearrange the update in cpufreq_set_policy() to avoid storing intermediate values in policy->min and policy->max with the help of the observation that their new values are expected to be properly ordered upfront.

Also modify cpufreq_driver_resolve_freq() to take the possible reverse ordering of policy->min and policy->max, which may happen depending on the ordering of operations when this function and cpufreq_set_policy() run concurrently, into account by always honoring the max when it turns out to be less than the min (in case it comes from thermal throttling or similar).

Fixes 151717690694 ("cpufreq: Make policy min/max hard requirements")
Cc 5.16+ <[email protected]> # 5.16+

---

This replaces the last 3 patches in

https://lore.kernel.org/linux-pm/[email protected]/

v2 -> v3:
* Fold 3 patches into one.
* Drop an unrelated white space fixup change.
* Fix a typo in a comment (Christian).

v1 -> v2: Cosmetic changes

---
 drivers/cpufreq/cpufreq.c |   32 +++++++++++++++++++++++++-------
 1 file changed, 25 insertions(+), 7 deletions(-)
drivers/cpufreq/cpufreq.c +19 -3
@@ -495,8 +495,6 @@
 {
      unsigned int idx;
 
-     target_freq = clamp_val(target_freq, policy->min, policy->max);
-
      if (!policy->freq_table)
              return target_freq;
 
@@ -520,7 +518,22 @@
 unsigned int cpufreq_driver_resolve_freq(struct cpufreq_policy *policy,
                                       unsigned int target_freq)
 {
-     return __resolve_freq(policy, target_freq, CPUFREQ_RELATION_LE);
+     unsigned int min = READ_ONCE(policy->min);
+     unsigned int max = READ_ONCE(policy->max);
+
+     /*
+      * If this function runs in parallel with cpufreq_set_policy(), it may
+      * read policy->min before the update and policy->max after the update
+      * or the other way around, so there is no ordering guarantee.
+      *
+      * Resolve this by always honoring the max (in case it comes from
+      * thermal throttling or similar).
+      */
+     if (unlikely(min > max))
+             min = max;
+
+     return __resolve_freq(policy, clamp_val(target_freq, min, max),
+                           CPUFREQ_RELATION_LE);
 }
 EXPORT_SYMBOL_GPL(cpufreq_driver_resolve_freq);
 
@@ -2338,6 +2351,7 @@
      if (cpufreq_disabled())
              return -ENODEV;
 
+     target_freq = clamp_val(target_freq, policy->min, policy->max);
      target_freq = __resolve_freq(policy, target_freq, relation);
 
      pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
@@ -2631,11 +2645,15 @@
       * Resolve policy min/max to available frequencies. It ensures
       * no frequency resolution will neither overshoot the requested maximum
       * nor undershoot the requested minimum.
+      *
+      * Avoid storing intermediate values in policy->max or policy->min and

drivers/cpufreq/cpufreq.c line 2655

Quoted 10 lines
drivers/cpufreq/cpufreq.c +5 -4
+      * compiler optimizations around them because they may be accessed
+      * concurrently by cpufreq_driver_resolve_freq() during the update.
       */
-     policy->min = new_data.min;
-     policy->max = new_data.max;
-     policy->min = __resolve_freq(policy, policy->min, CPUFREQ_RELATION_L);
-     policy->max = __resolve_freq(policy, policy->max, CPUFREQ_RELATION_H);
+     WRITE_ONCE(policy->max, __resolve_freq(policy, new_data.max, CPUFREQ_RELATION_H));
+     new_data.min = __resolve_freq(policy, new_data.min, CPUFREQ_RELATION_L);
+     WRITE_ONCE(policy->min, new_data.min > policy->max ? policy->max : new_data.min);

I've tested the cpufreq throttling again in 6.15-rc3 to check your fix for the schedutil CPUFREQ_NEED_UPDATE_LIMITS regression I reported [1]. The CPU frequency is now being throttled correctly when reaching high temperatures. Thanks for fixing this!

Unfortunately, the opposite case has now regressed with this patch:
After the CPU frequency has been throttled due to high temperature and the device cools down again, the CPU frequency is stuck at minimum until you reboot. policy->max will never restore to the maximum frequency.

I've confirmed that this causes unexpected slowness after temperature throttling on a Qualcomm X1E laptop, and Johan has confirmed that e.g. the ThinkPad X13s is also affected. I would expect that most devices using cpufreq cooling in the kernel are affected.

Quoted 5 lines

Looking at the code, I think the problem is that __resolve_freq() -> cpufreq_frequency_table_target() -> cpufreq_table_find_index*() and cpufreq_is_in_limits() are still using the old policy->min/max value. In this patch, you have only moved the clamp_val() usage directly in __resolve_freq().

Quoted 7 lines

You are right, that's the problem.

The fix is basically straightforward, pass min and max to cpufreq_frequency_table_target() and propagate downward, but making that change may be somewhat error-prone.

I'll try to cut a patch to test tomorrow.

Actually, I have it already, so please find appended.

Please give it a go as soon as you reasonably can.

Thanks!

---
From: Rafael J. Wysocki <[email protected]>
Subject: [PATCH v1] cpufreq: Fix setting policy limits when frequency tables are used

Commit 7491cdf46b5c ("cpufreq: Avoid using inconsistent policy->min and policy->max") overlooked the fact that policy->min and policy->max were accessed directly in cpufreq_frequency_table_target() and in the functions called by it and the changes made by that commit led to problems with setting policy limits.

Address this by passing the target frequency limits to __resolve_freq() and cpufreq_frequency_table_target() and propagating them to the functions called by the latter.

Fixes 7491cdf46b5c ("cpufreq: Avoid using inconsistent policy->min and policy->max")
---
 drivers/cpufreq/cpufreq.c          |   22 ++++++----
 drivers/cpufreq/cpufreq_ondemand.c |    3 -
 drivers/cpufreq/freq_table.c       |    6 +-
 include/linux/cpufreq.h            |   81 ++++++++++++++++++++++++-------------
 4 files changed, 72 insertions(+), 40 deletions(-)
drivers/cpufreq/cpufreq.c +1 -1
@@ -491,14 +491,18 @@
 EXPORT_SYMBOL_GPL(cpufreq_disable_fast_switch);
 
 static unsigned int __resolve_freq(struct cpufreq_policy *policy,
-               unsigned int target_freq, unsigned int relation)
+                                  unsigned int target_freq,

drivers/cpufreq/cpufreq.c line 503

Quoted 10 lines
drivers/cpufreq/cpufreq.c +3 -1
+                                  unsigned int min, unsigned int max,
+                                  unsigned int relation)
 {
        unsigned int idx;
 
        if (!policy->freq_table)
                return target_freq;
 
-       idx = cpufreq_frequency_table_target(policy, target_freq, relation);
+       target_freq = clamp_val(target_freq, min, max);

This needs to be done before the freq_table check above or drivers without freq tables will have a problem.

I'll send an update of this patch shortly.

Thanks!