From: Jing Wu <realwujing@gmail.com>
To: Thomas Gleixner <tglx@kernel.org>, Ingo Molnar <mingo@redhat.com>,
Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
"Peter Zijlstra (Intel)" <peterz@infradead.org>,
"Paul E. McKenney" <paulmck@kernel.org>,
"Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
Cc: linux-kernel@vger.kernel.org,
Qiliang Yuan <yuanql9@chinatelecom.cn>,
Jian Zhang <zhangj332@chinatelecom.cn>,
Jing Wu <realwujing@gmail.com>
Subject: [PATCH] x86/aperfmperf: Refresh stale sample via IPI for busy NOHZ_FULL CPUs
Date: Tue, 28 Jul 2026 19:27:54 +0800 [thread overview]
Message-ID: <20260728-bug-isolatecpu-cpufreq-v1-1-e95d34db8bcd@gmail.com> (raw)
An isolated CPU covered by nohz_full stops its periodic tick once it
has only one runnable task, since sched_can_stop_tick() only checks
scheduling-class fairness and has no notion of cpufreq reporting
needs. arch_scale_freq_tick() runs only from scheduler_tick(), so
cpu_samples for that CPU is never refreshed again.
arch_freq_get_on_cpu() then permanently hits its staleness check and
falls back to cpufreq_quick_get(), which returns whatever policy->cur
was left at (typically the P-state floor). This happens even though
HWP hardware keeps running the CPU at full turbo autonomously, as
confirmed by turbostat and by directly reading APERF/MPERF.
Reproduce on an isolated, nohz_full CPU with intel_pstate/HWP by
loading it and watching scaling_cur_freq stay pinned at the floor:
taskset -c $CPU stress --cpu 1 &
for i in $(seq 10); do
cat /sys/devices/system/cpu/cpu$CPU/cpufreq/scaling_cur_freq
sleep 0.5
done
turbostat --cpu $CPU --interval 1 --num_iterations 5
scaling_cur_freq stays at the floor for the whole run, while
turbostat's Bzy_MHz confirms the CPU is actually at full turbo.
Refresh the stale sample with one on-demand arch_scale_freq_tick()
via IPI before falling back, but only when the target CPU is online
and not idle. APERF/MPERF both stop advancing during idle (C1+), so
a delta computed over an arbitrarily long stale window still yields
a correct busy-time frequency average.
Fixes: 7d84c1ebf9dd ("x86/aperfmperf: Replace aperfmperf_get_khz()")
Co-developed-by: Qiliang Yuan <yuanql9@chinatelecom.cn>
Signed-off-by: Qiliang Yuan <yuanql9@chinatelecom.cn>
Co-developed-by: Jian Zhang <zhangj332@chinatelecom.cn>
Signed-off-by: Jian Zhang <zhangj332@chinatelecom.cn>
Signed-off-by: Jing Wu <realwujing@gmail.com>
---
arch/x86/kernel/cpu/aperfmperf.c | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kernel/cpu/aperfmperf.c b/arch/x86/kernel/cpu/aperfmperf.c
index 7ffc78d5ebf21..e51544db9f9bb 100644
--- a/arch/x86/kernel/cpu/aperfmperf.c
+++ b/arch/x86/kernel/cpu/aperfmperf.c
@@ -12,6 +12,7 @@
#include <linux/math64.h>
#include <linux/percpu.h>
#include <linux/rcupdate.h>
+#include <linux/sched.h>
#include <linux/sched/isolation.h>
#include <linux/sched/topology.h>
#include <linux/smp.h>
@@ -503,16 +504,41 @@ void arch_scale_freq_tick(void)
*/
#define MAX_SAMPLE_AGE ((unsigned long)HZ / 50)
+static void aperfmperf_snapshot_cpu_ipi(void *info)
+{
+ arch_scale_freq_tick();
+}
+
+/*
+ * A NOHZ_FULL CPU with a single runnable task (e.g. an isolated CPU running
+ * a pinned PMD/busy-poll workload) stops its periodic tick, so nothing ever
+ * calls arch_scale_freq_tick() for it again and cpu_samples goes stale
+ * forever, not just for one MAX_SAMPLE_AGE window. Force one on-demand
+ * sample via IPI so a deliberate frequency read doesn't report the P-state
+ * floor from before isolation took effect. Skip idle CPUs: their frequency
+ * genuinely doesn't matter and there is no point poking them with an IPI.
+ */
+static bool aperfmperf_refresh_stale_sample(int cpu)
+{
+ if (!cpu_online(cpu) || idle_cpu(cpu))
+ return false;
+
+ smp_call_function_single(cpu, aperfmperf_snapshot_cpu_ipi, NULL, 1);
+ return true;
+}
+
int arch_freq_get_on_cpu(int cpu)
{
struct aperfmperf *s = per_cpu_ptr(&cpu_samples, cpu);
unsigned int seq, freq;
unsigned long last;
+ bool refreshed = false;
u64 acnt, mcnt;
if (!cpu_feature_enabled(X86_FEATURE_APERFMPERF))
goto fallback;
+again:
do {
seq = raw_read_seqcount_begin(&s->seq);
last = s->last_update;
@@ -524,8 +550,14 @@ int arch_freq_get_on_cpu(int cpu)
* Bail on invalid count and when the last update was too long ago,
* which covers idle and NOHZ full CPUs.
*/
- if (!mcnt || (jiffies - last) > MAX_SAMPLE_AGE)
+ if (!mcnt || (jiffies - last) > MAX_SAMPLE_AGE) {
+ if (!refreshed) {
+ refreshed = true;
+ if (aperfmperf_refresh_stale_sample(cpu))
+ goto again;
+ }
goto fallback;
+ }
return div64_u64((cpu_khz * acnt), mcnt);
---
base-commit: 502d801f0ab03e4f32f9a33d203154ce84887921
change-id: 20260728-bug-isolatecpu-cpufreq-864a5fba85b7
Best regards,
--
Jing Wu <realwujing@gmail.com>
next reply other threads:[~2026-07-28 11:27 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-28 11:27 Jing Wu [this message]
2026-07-28 13:44 ` [PATCH] x86/aperfmperf: Refresh stale sample via IPI for busy NOHZ_FULL CPUs Peter Zijlstra
2026-07-28 14:42 ` Peter Zijlstra
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260728-bug-isolatecpu-cpufreq-v1-1-e95d34db8bcd@gmail.com \
--to=realwujing@gmail.com \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=hpa@zytor.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=paulmck@kernel.org \
--cc=peterz@infradead.org \
--cc=rafael.j.wysocki@intel.com \
--cc=tglx@kernel.org \
--cc=x86@kernel.org \
--cc=yuanql9@chinatelecom.cn \
--cc=zhangj332@chinatelecom.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).