From eee2a44e4e2b41c0699919a550d41cc38daa4f42 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 3 Nov 2026 20:45:38 +0100 Subject: cpufreq: Improve suspend-resume synchronization The suspend-resume synchronization in the cpufreq core is not perfect because the global cpufreq_suspended variable is set at the end of cpufreq_suspend() after stopping governors for all of the active policies, so the governor .limits() callback may run via cpufreq_governor_limits() after stopping the governor and before setting cpufreq_suspended which is questionable. Likewise, the governor .limits() callback may run between clearing cpufreq_suspended in cpufreq_resume() and starting the governor. Address this by adding a per-policy "suspended" flag that is always read under the policy rwsem and is updated under it when the governor is stopped in cpufreq_suspend() and when the governor is restarted in cpufreq_resume(). Also make cpufreq_resume() use the cpus_read_lock guard to reflect what is done in cpufreq_suspend() and call cpufreq_exit_governor() for policies that were suspended during a system suspend transition and are inactive during the subsequent system resume (to avoid leaking an inactive policy with a stopped governor that has not exited yet). Fixes: 8e30444e1530 ("cpufreq: fix cpufreq suspend/resume for intel_pstate") Fixes: b1b12babe3b7 ("cpufreq: update 'cpufreq_suspended' after stopping governors") Cc: All applicable Signed-off-by: Rafael J. Wysocki Tested-by: Zak Brighton Knight Reviewed-by: Zhongqiu Han Acked-by: Viresh Kumar Tested-by: Doug Smythies Tested-by: Christian Loehle Link: https://patch.msgid.link/4796894.LvFx2qVVIh@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- include/linux/cpufreq.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include') diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index d3d0d9d02aa4..20a6863b640a 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -149,6 +149,9 @@ struct cpufreq_policy { /* Pending policy->min/max update for the driver */ bool update_limits; + /* The policy has been suspended. */ + bool suspended; + /* Cached frequency lookup from cpufreq_driver_resolve_freq. */ unsigned int cached_target_freq; unsigned int cached_resolved_idx; -- cgit v1.2.3 From 342d3a73c5dbc2f1cd0c5142e2d11bda4dc54cd7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 3 Nov 2026 20:47:48 +0100 Subject: cpufreq: Force passing target frequency to driver during resume The policy->cur value preserved across a system suspend-resume cycle may become stale in some cases and it may prevent the CPU performance scaling logic from being updated due to the target_freq == policy->cur check in __cpufreq_driver_target() [1]. To address this, adjust the existing mechanism to force the invocation of the driver .target() callback in __cpufreq_driver_target() and make cpufreq_resume_policy() use it. This involves renaming update_limits in struct cpufreq_policy to force_target and moving the driver CPUFREQ_NEED_UPDATE_LIMITS flag check from __cpufreq_driver_target() to cpufreq_set_policy(), where it is used as a condition for setting policy->force_target. In cpufreq_resume_policy(), policy->force_target is set for drivers that implement the .target() callback, but don't implement the .get() one, which is the case when the driver callback needs to run if policy->cur becomes stale while suspended. Fixes: 2f0aea936360 ("cpufreq: suspend governors on system suspend/hibernate") Link: https://lore.kernel.org/linux-pm/20261027143725.3763827-1-zakbrightonknight@gmail.com/ [1] Reported-by: Zak Brighton Knight Reported-by: Doug Smythies Cc: All applicable # eee2a44e4e2b cpufreq: Improve suspend-resume synchronization Signed-off-by: Rafael J. Wysocki Tested-by: Zak Brighton Knight Reviewed-by: Zhongqiu Han Acked-by: Viresh Kumar Tested-by: Doug Smythies Tested-by: Christian Loehle Link: https://patch.msgid.link/1995295.tdWV9SEqCh@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq.c | 33 +++++++++++++++++++++------------ include/linux/cpufreq.h | 4 ++-- 2 files changed, 23 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 7b93cbf313bc..c8ab23afa66d 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2027,6 +2027,15 @@ static void cpufreq_resume_policy(struct cpufreq_policy *policy, bool start) if (!start) return; + /* + * Force executing the driver .target() callback, if present, in the + * next invocation of __cpufreq_driver_target() in case policy->cur has + * become stale while suspended, but do it only for drivers without a + * .get() callback for which cpufreq_verify_current_freq() is a NOP. + */ + if (cpufreq_driver->target && !cpufreq_driver->get) + policy->force_target = true; + if (cpufreq_start_governor(policy)) pr_err("%s: Failed to start governor for CPU%u's policy\n", __func__, policy->cpu); @@ -2404,17 +2413,14 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy, policy->cpu, policy->cur, target_freq, old_target_freq, relation); /* - * This might look like a redundant call as we are checking it again - * after finding index. But it is left intentionally for cases where - * exactly same freq is called again and so we can save on few function - * calls. + * Unless invoking the target callback is forced, skip the update if the + * frequency is not really changing. */ - if (target_freq == policy->cur) { - if (!(cpufreq_driver->flags & CPUFREQ_NEED_UPDATE_LIMITS) || - !policy->update_limits) - return 0; - - policy->update_limits = false; + if (policy->force_target) { + /* Avoid forcing the driver callback unnecessarily next time. */ + policy->force_target = false; + } else if (target_freq == policy->cur) { + return 0; } if (cpufreq_driver->target) { @@ -2673,6 +2679,7 @@ static int cpufreq_set_policy(struct cpufreq_policy *policy, struct cpufreq_governor *new_gov, unsigned int new_pol) { + bool need_update_limits = cpufreq_driver->flags & CPUFREQ_NEED_UPDATE_LIMITS; struct cpufreq_policy_data new_data; struct cpufreq_governor *old_gov; unsigned int freq; @@ -2712,7 +2719,8 @@ 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; + if (need_update_limits) + policy->force_target = true; } freq = __resolve_freq(policy, new_data.min, new_data.min, new_data.max, @@ -2720,7 +2728,8 @@ 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; + if (need_update_limits) + policy->force_target = true; } trace_cpu_frequency_limits(policy); diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 20a6863b640a..be1eaa43aeb0 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -146,8 +146,8 @@ struct cpufreq_policy { /* Per policy boost supported flag. */ bool boost_supported; - /* Pending policy->min/max update for the driver */ - bool update_limits; + /* Force passing target frequency to the driver. */ + bool force_target; /* The policy has been suspended. */ bool suspended; -- cgit v1.2.3