[PATCH 3/3] cpufreq: Skip updates for unchanged resolved limits
Zhongqiu Han
zhongqiu.han at oss.qualcomm.com
Fri Oct 2 05:46:05 PDT 2026
On 9/29/2026 6:29 PM, Christian Loehle wrote:
> Commit 9801be8bef65 ("cpufreq: Avoid redundant target() calls for unchanged
> limits") introduced policy->update_limits to skip ->target() when both the
> frequency and effective limits are unchanged. schedutil still bypasses its
> frequency cache on every limit notification for CPUFREQ_NEED_UPDATE_LIMITS
> drivers, and fast switching never consumes the pending state.
>
> Keep raw QoS/limit notifications triggering schedutil recomputation, but
> only let CPUFREQ_NEED_UPDATE_LIMITS force a same-frequency callback when
> resolved policy->{min,max} changes remain pending. Expose that test through
> a cpufreq-core helper.
>
> Consume pending state before both slow and fast callbacks, including
> frequency changes, instead of clearing it only in the target == cur case.
> Use release/acquire ordering for the limits and restore pending state on
> failure, preserving concurrent updates and retries. Favor the maximum
> if lockless limit reads observe an inverted pair.
>
> This applies to cppc-cpufreq and amd-pstate's frequency-based paths; the
> ->adjust_perf() path is unchanged.
>
> Signed-off-by: Christian Loehle <christian.loehle at arm.com>
> ---
> drivers/cpufreq/cpufreq.c | 82 ++++++++++++++++++++++++++------
> include/linux/cpufreq.h | 1 +
> kernel/sched/cpufreq_schedutil.c | 19 ++------
> 3 files changed, 72 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
> index 44bda2f32fcf..9e932c2a986d 100644
> --- a/drivers/cpufreq/cpufreq.c
> +++ b/drivers/cpufreq/cpufreq.c
> @@ -2058,6 +2058,21 @@ bool cpufreq_driver_test_flags(u16 flags)
> return !!(cpufreq_driver->flags & flags);
> }
>
> +/**
> + * cpufreq_driver_needs_limits_update - Check for a pending driver limit update.
> + * @policy: CPU frequency policy to check.
> + *
> + * Return: Whether changed resolved limits require a driver callback.
> + */
> +bool cpufreq_driver_needs_limits_update(struct cpufreq_policy *policy)
> +{
> + if (!cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS))
> + return false;
> +
> + /* Pairs with cpufreq_set_update_limits(). */
> + return smp_load_acquire(&policy->update_limits);
> +}
> +
> /**
> * cpufreq_get_current_driver - Return the current driver's name.
> *
> @@ -2183,6 +2198,30 @@ EXPORT_SYMBOL(cpufreq_unregister_notifier);
> * GOVERNORS *
> *********************************************************************/
>
> +static bool cpufreq_test_and_clear_update_limits(struct cpufreq_policy *policy)
> +{
> + if (!cpufreq_driver_needs_limits_update(policy))
> + return false;
> +
> + return xchg(&policy->update_limits, false);
Since policy->update_limits is a bool, this would require a 1-byte
xchg(), which, as far as I know, is not supported on all architectures.
such as on sparc:
static __always_inline unsigned long
__arch_xchg(unsigned long x, __volatile__ void * ptr, int size)
{
switch (size) {
case 2:
return xchg16(ptr, x);
case 4:
return xchg32(ptr, x);
case 8:
return xchg64(ptr, x);
}
__xchg_called_with_bad_pointer();
return x;
}
> +}
> +
> +static void cpufreq_set_update_limits(struct cpufreq_policy *policy)
> +{
> + /* Publish the new limits before making their update pending. */
> + smp_store_release(&policy->update_limits, true);
> +}
> +
> +static void cpufreq_read_policy_limits(struct cpufreq_policy *policy,
> + unsigned int *min, unsigned int *max)
> +{
> + /* Lockless reads can mix limit updates; favor max if inverted. */
> + *min = READ_ONCE(policy->min);
> + *max = READ_ONCE(policy->max);
> + if (unlikely(*min > *max))
> + *min = *max;
> +}
> +
> /**
> * cpufreq_driver_fast_switch - Carry out a fast CPU frequency switch.
> * @policy: cpufreq policy to switch the frequency for.
> @@ -2209,14 +2248,20 @@ EXPORT_SYMBOL(cpufreq_unregister_notifier);
> unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy,
> unsigned int target_freq)
> {
> - unsigned int freq;
> + bool update_limits;
> + unsigned int min, max, freq;
> int cpu;
>
> - target_freq = clamp_val(target_freq, policy->min, policy->max);
> + update_limits = cpufreq_test_and_clear_update_limits(policy);
> + cpufreq_read_policy_limits(policy, &min, &max);
> + target_freq = clamp_val(target_freq, min, max);
> freq = cpufreq_driver->fast_switch(policy, target_freq);
>
> - if (!freq)
> + if (!freq) {
> + if (update_limits)
> + cpufreq_set_update_limits(policy);
> return 0;
> + }
>
> policy->cur = freq;
> arch_set_freq_scale(policy->related_cpus, freq,
> @@ -2367,12 +2412,16 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy,
> unsigned int relation)
> {
> unsigned int old_target_freq = target_freq;
> + unsigned int min, max;
> + bool update_limits;
> + int ret;
>
> if (cpufreq_disabled())
> return -ENODEV;
>
> - target_freq = __resolve_freq(policy, target_freq, policy->min,
> - policy->max, relation);
> + update_limits = cpufreq_test_and_clear_update_limits(policy);
> + cpufreq_read_policy_limits(policy, &min, &max);
> + target_freq = __resolve_freq(policy, target_freq, min, max, relation);
>
> pr_debug("CPU %u: cur %u kHz -> target %u kHz (req %u kHz, rel %u)\n",
> policy->cpu, policy->cur, target_freq, old_target_freq, relation);
> @@ -2384,11 +2433,8 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy,
> * calls.
> */
> if (target_freq == policy->cur) {
> - if (!(cpufreq_driver->flags & CPUFREQ_NEED_UPDATE_LIMITS) ||
> - !policy->update_limits)
> + if (!update_limits)
> return 0;
> -
> - policy->update_limits = false;
> }
>
> if (cpufreq_driver->target) {
> @@ -2399,13 +2445,19 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy,
> if (!policy->efficiencies_available)
> relation &= ~CPUFREQ_RELATION_E;
>
> - return cpufreq_driver->target(policy, target_freq, relation);
> + ret = cpufreq_driver->target(policy, target_freq, relation);
> + } else if (cpufreq_driver->target_index) {
> + ret = __target_index(policy, policy->cached_resolved_idx);
> + } else {
> + if (update_limits)
> + cpufreq_set_update_limits(policy);
> + return -EINVAL;
> }
>
> - if (!cpufreq_driver->target_index)
> - return -EINVAL;
> + if (ret && update_limits)
> + cpufreq_set_update_limits(policy);
>
> - return __target_index(policy, policy->cached_resolved_idx);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(__cpufreq_driver_target);
>
> @@ -2681,7 +2733,7 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy,
> CPUFREQ_RELATION_H);
> if (freq != policy->max) {
> WRITE_ONCE(policy->max, freq);
> - policy->update_limits = true;
> + cpufreq_set_update_limits(policy);
> }
>
> freq = __resolve_freq(policy, new_data.min, new_data.min, new_data.max,
> @@ -2689,7 +2741,7 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy,
> freq = min(freq, policy->max);
> if (freq != policy->min) {
> WRITE_ONCE(policy->min, freq);
> - policy->update_limits = true;
> + cpufreq_set_update_limits(policy);
> }
>
> trace_cpu_frequency_limits(policy);
> diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
> index a0a7619d11fd..acd60c2b30f1 100644
> --- a/include/linux/cpufreq.h
> +++ b/include/linux/cpufreq.h
> @@ -499,6 +499,7 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data);
> void cpufreq_unregister_driver(struct cpufreq_driver *driver_data);
>
> bool cpufreq_driver_test_flags(u16 flags);
> +bool cpufreq_driver_needs_limits_update(struct cpufreq_policy *policy);
> const char *cpufreq_get_current_driver(void);
> void *cpufreq_get_driver_data(void);
>
> diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c
> index 49ccd6f1c185..43e448ee3c4c 100644
> --- a/kernel/sched/cpufreq_schedutil.c
> +++ b/kernel/sched/cpufreq_schedutil.c
> @@ -123,22 +123,11 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
> static bool sugov_update_next_freq(struct sugov_policy *sg_policy, u64 time,
> unsigned int next_freq)
> {
> - if (sg_policy->need_freq_update) {
> - sg_policy->need_freq_update = false;
> - /*
> - * The policy limits have changed, but if the return value of
> - * cpufreq_driver_resolve_freq() after applying the new limits
> - * is still equal to the previously selected frequency, the
> - * driver callback need not be invoked unless the driver
> - * specifically wants that to happen on every update of the
> - * policy limits.
> - */
> - if (sg_policy->next_freq == next_freq &&
> - !cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS))
> - return false;
> - } else if (sg_policy->next_freq == next_freq) {
> + sg_policy->need_freq_update = false;
> +
> + if (sg_policy->next_freq == next_freq &&
> + !cpufreq_driver_needs_limits_update(sg_policy->policy))
> return false;
> - }
>
> sg_policy->next_freq = next_freq;
> sg_policy->last_freq_update_time = time;
--
Thx and BRs,
Zhongqiu Han
More information about the linux-arm-kernel
mailing list