[PATCH] arm64: Force the use of CNTVCT_EL0 in __delay()

Marc Zyngier maz at kernel.org
Mon Feb 23 06:31:44 PST 2026


Hi Ben,

On Mon, 23 Feb 2026 11:16:32 +0000,
Ben Horgan <ben.horgan at arm.com> wrote:
> 
> Hi Marc,
> 
> On Fri, Feb 13, 2026 at 02:16:19PM +0000, Marc Zyngier wrote:
> > Quentin forwards a report from Hyesoo Yu, describing an interesting
> > problem with the use of WFxT in __delay() when a vcpu is loaded and
> > that KVM is *not* in VHE mode (either nVHE or hVHE).
> > 
> > In this case, CNTVOFF_EL2 is set to a non-zero value to reflect the
> > state of the guest virtual counter. At the same time, __delay() is
> > using get_cycles() to read the counter value, which is indirected to
> > reading CNTPCT_EL0.
> > 
> > The core of the issue is that WFxT is using the *virtual* counter,
> > while the kernel is using the physical counter, and that the offset
> > introduces a really bad discrepancy between the two.
> > 
> > Fix this by forcing the use of CNTVCT_EL0, making __delay() consistent
> > irrespective of the value of CNTVOFF_EL2.
> > 
> > Reported-by: Hyesoo Yu <hyesoo.yu at samsung.com>
> > Reported-by: Quentin Perret <qperret at google.com>
> > Reviewed-by: Quentin Perret <qperret at google.com>
> > Fixes: 7d26b0516a0df ("arm64: Use WFxT for __delay() when possible")
> > Signed-off-by: Marc Zyngier <maz at kernel.org>
> > Link: https://lore.kernel.org/r/ktosachvft2cgqd5qkukn275ugmhy6xrhxur4zqpdxlfr3qh5h@o3zrfnsq63od
> > Cc: stable at vger.kernel.org
> > ---
> >  arch/arm64/lib/delay.c | 19 +++++++++++++++----
> >  1 file changed, 15 insertions(+), 4 deletions(-)
> > 
> > diff --git a/arch/arm64/lib/delay.c b/arch/arm64/lib/delay.c
> > index cb2062e7e2340..d02341303899e 100644
> > --- a/arch/arm64/lib/delay.c
> > +++ b/arch/arm64/lib/delay.c
> > @@ -23,9 +23,20 @@ static inline unsigned long xloops_to_cycles(unsigned long xloops)
> >  	return (xloops * loops_per_jiffy * HZ) >> 32;
> >  }
> >  
> > +/*
> > + * Force the use of CNTVCT_EL0 in order to have the same base as WFxT.
> > + * This avoids some annoying issues when CNTVOFF_EL2 is not reset 0 on a
> > + * KVM host running at EL1 until we do a vcpu_put() on the vcpu. When
> > + * running at EL2, the effective offset is always 0.
> > + *
> > + * Note that userspace cannot change the offset behind our back either,
> > + * as the vcpu mutex is held as long as KVM_RUN is in progress.
> > + */
> > +#define __delay_cycles()	__arch_counter_get_cntvct_stable()
> 
> I'm seeing this CONFIG_DEBUG_PREEMPT warning, see below, when running 7.0-rc1 on
> FVP Base RevC. I haven't tried bisecting but it looks to be introduced by this
> change.
> 
> The calls are:
> 
> __this_cpu_read()
> erratum_handler()
> arch_timer_reg_read_stable()
> __arch_counter_get_cntvct_stable()
> __delay()
> 
> This silences the warning:
> 
> diff --git a/arch/arm64/include/asm/arch_timer.h b/arch/arm64/include/asm/arch_timer.h
> index f5794d50f51d..f07e4efa0d2b 100644
> --- a/arch/arm64/include/asm/arch_timer.h
> +++ b/arch/arm64/include/asm/arch_timer.h
> @@ -24,14 +24,14 @@
>  #define has_erratum_handler(h)                                         \
>         ({                                                              \
>                 const struct arch_timer_erratum_workaround *__wa;       \
> -               __wa = __this_cpu_read(timer_unstable_counter_workaround); \
> +               __wa = raw_cpu_read(timer_unstable_counter_workaround); \
>                 (__wa && __wa->h);                                      \
>         })
>  
>  #define erratum_handler(h)                                             \
>         ({                                                              \
>                 const struct arch_timer_erratum_workaround *__wa;       \
> -               __wa = __this_cpu_read(timer_unstable_counter_workaround); \
> +               __wa = raw_cpu_read(timer_unstable_counter_workaround); \
>                 (__wa && __wa->h) ? ({ isb(); __wa->h;}) : arch_timer_##h; \
>         })

It does indeed silence it, but that's IMO the wrong thing to do since
you can end-up calling a workaround helper on the wrong CPU if
preempted.  If you look at how things were done before this patch, we
had:

get_cycles() -> arch_timer_read_counter() -> arch_counter_get_cntvct_stable()

Crucially, arch_counter_get_cntvct_stable() does disable preemption,
and we should preserve it. Something like this:

diff --git a/arch/arm64/lib/delay.c b/arch/arm64/lib/delay.c
index d02341303899e..25fb593f95b0c 100644
--- a/arch/arm64/lib/delay.c
+++ b/arch/arm64/lib/delay.c
@@ -32,7 +32,16 @@ static inline unsigned long xloops_to_cycles(unsigned long xloops)
  * Note that userspace cannot change the offset behind our back either,
  * as the vcpu mutex is held as long as KVM_RUN is in progress.
  */
-#define __delay_cycles()	__arch_counter_get_cntvct_stable()
+static cycles_t __delay_cycles(void)
+{
+	cycles_t val;
+
+	preempt_disable();
+	val = __arch_counter_get_cntvct_stable();
+	preenpt_enable();
+
+	return val;
+}
 
 void __delay(unsigned long cycles)
 {

The question is whether there is a material benefit in replicating the
arch_timer_read_counter() indirection for the virtual counter in order
to not pay the price of preempt_disable() when we're on a non-broken
system (hopefully the vast majority of implementations).

Thanks,

	M.

-- 
Without deviation from the norm, progress is not possible.



More information about the linux-arm-kernel mailing list