cpufreq: Avoid using inconsistent policy->min and policy->max
Subsystems (2)
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).
---
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);
Quoted 25 lines · Rafael J. Wysocki
From: Rafael J. Wysocki <[email protected]>
Since
cpufreq_driver_resolve_freq()can run in parallel withcpufreq_set_policy()and there is no synchronization between them, the former may accesspolicy->minandpolicy->maxwhile 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 incpufreq_set_policy()and to the loads incpufreq_driver_resolve_freq()which may result in additional inconsistencies.To address this, use
WRITE_ONCE()when updatingpolicy->minandpolicy->maxincpufreq_set_policy()and useREAD_ONCE()for reading them incpufreq_driver_resolve_freq(). Moreover, rearrange the update incpufreq_set_policy()to avoid storing intermediate values inpolicy->minandpolicy->maxwith 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 ofpolicy->minandpolicy->max, which may happen depending on the ordering of operations when this function andcpufreq_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+Signed-off-by Rafael J. Wysocki <[email protected]>
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);
Quoted 39 lines · Rafael J. Wysocki
From: Rafael J. Wysocki <[email protected]>
Since
cpufreq_driver_resolve_freq()can run in parallel withcpufreq_set_policy()and there is no synchronization between them, the former may accesspolicy->minandpolicy->maxwhile 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 incpufreq_set_policy()and to the loads incpufreq_driver_resolve_freq()which may result in additional inconsistencies.To address this, use
WRITE_ONCE()when updatingpolicy->minandpolicy->maxincpufreq_set_policy()and useREAD_ONCE()for reading them incpufreq_driver_resolve_freq(). Moreover, rearrange the update incpufreq_set_policy()to avoid storing intermediate values inpolicy->minandpolicy->maxwith 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 ofpolicy->minandpolicy->max, which may happen depending on the ordering of operations when this function andcpufreq_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+Signed-off-by Rafael J. Wysocki <[email protected]>---
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
Hi Rafael,
Quoted 94 lines · Rafael J. Wysocki
From: Rafael J. Wysocki <[email protected]>
Since
cpufreq_driver_resolve_freq()can run in parallel withcpufreq_set_policy()and there is no synchronization between them, the former may accesspolicy->minandpolicy->maxwhile 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 incpufreq_set_policy()and to the loads incpufreq_driver_resolve_freq()which may result in additional inconsistencies.To address this, use
WRITE_ONCE()when updatingpolicy->minandpolicy->maxincpufreq_set_policy()and useREAD_ONCE()for reading them incpufreq_driver_resolve_freq(). Moreover, rearrange the update incpufreq_set_policy()to avoid storing intermediate values inpolicy->minandpolicy->maxwith 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 ofpolicy->minandpolicy->max, which may happen depending on the ordering of operations when this function andcpufreq_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+Signed-off-by Rafael J. Wysocki <[email protected]>---
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 0Any 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
Rafael J. Wysocki
maintainerHi,
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 withcpufreq_set_policy()and there is no synchronization between them, the former may accesspolicy->minandpolicy->maxwhile 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 incpufreq_set_policy()and to the loads incpufreq_driver_resolve_freq()which may result in additional inconsistencies.To address this, use
WRITE_ONCE()when updatingpolicy->minandpolicy->maxincpufreq_set_policy()and useREAD_ONCE()for reading them incpufreq_driver_resolve_freq(). Moreover, rearrange the update incpufreq_set_policy()to avoid storing intermediate values inpolicy->minandpolicy->maxwith 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 ofpolicy->minandpolicy->max, which may happen depending on the ordering of operations when this function andcpufreq_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+Signed-off-by Rafael J. Wysocki <[email protected]>---
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_LIMITSregression 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->maxwill 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*() andcpufreq_is_in_limits()are still using the old policy->min/max value. In this patch, you have only moved theclamp_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!
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 withcpufreq_set_policy()and there is no synchronization between them, the former may accesspolicy->minandpolicy->maxwhile 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 incpufreq_set_policy()and to the loads incpufreq_driver_resolve_freq()which may result in additional inconsistencies.To address this, use
WRITE_ONCE()when updatingpolicy->minandpolicy->maxincpufreq_set_policy()and useREAD_ONCE()for reading them incpufreq_driver_resolve_freq(). Moreover, rearrange the update incpufreq_set_policy()to avoid storing intermediate values inpolicy->minandpolicy->maxwith 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 ofpolicy->minandpolicy->max, which may happen depending on the ordering of operations when this function andcpufreq_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+Signed-off-by Rafael J. Wysocki <[email protected]>---
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_LIMITSregression 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->maxwill 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*() andcpufreq_is_in_limits()are still using the old policy->min/max value. In this patch, you have only moved theclamp_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.
--- 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
Rafael J. Wysocki
maintainerQuoted 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 withcpufreq_set_policy()and there is no synchronization between them, the former may accesspolicy->minandpolicy->maxwhile 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 incpufreq_set_policy()and to the loads incpufreq_driver_resolve_freq()which may result in additional inconsistencies.To address this, use
WRITE_ONCE()when updatingpolicy->minandpolicy->maxincpufreq_set_policy()and useREAD_ONCE()for reading them incpufreq_driver_resolve_freq(). Moreover, rearrange the update incpufreq_set_policy()to avoid storing intermediate values inpolicy->minandpolicy->maxwith 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 ofpolicy->minandpolicy->max, which may happen depending on the ordering of operations when this function andcpufreq_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+Signed-off-by Rafael J. Wysocki <[email protected]>---
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_LIMITSregression 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->maxwill 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*() andcpufreq_is_in_limits()are still using the old policy->min/max value. In this patch, you have only moved theclamp_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 usedCommit
7491cdf46b5c("cpufreq: Avoid using inconsistentpolicy->minandpolicy->max") overlooked the fact thatpolicy->minandpolicy->maxwere accessed directly incpufreq_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()andcpufreq_frequency_table_target()and propagating them to the functions called by the latter.Fixes 7491cdf46b5c ("cpufreq: Avoid using inconsistent policy->min and policy->max")Reported-by Stephan Gerhold <[email protected]>Signed-off-by Rafael J. Wysocki <[email protected]>--- 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!