Commit 998b6db69e for qemu.org
commit 998b6db69e3ec58fd52ddc7a06ece6cf286f3c77
Author: Fuad Tabba <fuad.tabba@linux.dev>
Date: Mon Sep 21 08:44:51 2026 +0100
target/arm: Fix WFxT timeouts when the offset puts the count ahead
The WFIT and WFET helpers arm the wakeup timer at count == timeout +
offset, treating an overflow of that sum as "beyond the counter's
wrap" and arming at INT64_MAX. As in gt_recalc_timer(), that reading
is valid only when offset <= count: with a CNTVOFF_EL2 that puts the
virtual count ahead of the physical count, the sum overflows for
every timeout still in the future and the wrapped value was the
correct wakeup. The CPU then waits until an interrupt or event
instead of waking at its timeout. A Linux guest uses WFIT and WFET in
__delay() when FEAT_WFxT is present, which -cpu max advertises.
Arm cntval + (timeout - cntvct) instead, so only a physical count past
2^64 is "never", and add a tcg system test that issues WFIT and WFET
with such an offset, with a timer interrupt 1s out so a broken WFxT
still returns: before this change both wake at the interrupt, after
it at their timeout.
Fixes: a96edb687e76 ("target/arm: Implement FEAT WFxT and enable for '-cpu max'")
Fixes: da9b86c35fa8 ("target/arm: implement WFET")
Cc: qemu-stable@nongnu.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Message-id: 20260921074451.3158645-3-fuad.tabba@linux.dev
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c
index 643b148252..af56eff9f1 100644
--- a/target/arm/tcg/op_helper.c
+++ b/target/arm/tcg/op_helper.c
@@ -448,7 +448,8 @@ void HELPER(wfit)(CPUARMState *env, uint32_t rd)
raise_exception(env, excp, syn_wfx(1, 0xe, rd, true, WFIT, false), target_el);
}
- if (uadd64_overflow(timeout, offset, &nexttick)) {
+ /* Physical count at the timeout. Only an overflow of it is "never". */
+ if (uadd64_overflow(cntval, timeout - cntvct, &nexttick)) {
nexttick = UINT64_MAX;
}
if (nexttick > INT64_MAX / gt_cntfrq_period_ns(cpu)) {
@@ -705,7 +706,8 @@ void HELPER(wfet)(CPUARMState *env, uint32_t rd)
* The WFET should time out when CNTVCT_EL0 >= the specified value.
*/
cpu = env_archcpu(env);
- if (uadd64_overflow(timeout, offset, &nexttick)) {
+ /* Physical count at the timeout. Only an overflow of it is "never". */
+ if (uadd64_overflow(cntval, timeout - cntvct, &nexttick)) {
nexttick = UINT64_MAX;
}
if (nexttick > INT64_MAX / gt_cntfrq_period_ns(cpu)) {
diff --git a/tests/tcg/aarch64/system/meson.build b/tests/tcg/aarch64/system/meson.build
index f9c4448aba..f51feb253f 100644
--- a/tests/tcg/aarch64/system/meson.build
+++ b/tests/tcg/aarch64/system/meson.build
@@ -72,6 +72,13 @@ tests += {
'-semihosting-config', 'enable=on,arg=2',
qemu_base_args]
},
+ 'wfxt.c': {
+ 'cflags': cflags,
+ 'qemu_args': ['-M', 'virt,virtualization=on,gic-version=2',
+ '-cpu', 'max', '-smp', '1',
+ '-semihosting-config', 'enable=on,arg=2',
+ qemu_base_args]
+ },
}
tests += {
diff --git a/tests/tcg/aarch64/system/wfxt.c b/tests/tcg/aarch64/system/wfxt.c
new file mode 100644
index 0000000000..9d50590a68
--- /dev/null
+++ b/tests/tcg/aarch64/system/wfxt.c
@@ -0,0 +1,118 @@
+/*
+ * WFIT/WFET timeout test
+ *
+ * Copyright (c) 2026 Google LLC
+ * Author: Fuad Tabba <fuad.tabba@linux.dev>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdint.h>
+#include <minilib.h>
+
+/* from Linux's include/linux/stringify.h */
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+
+#define read_sysreg(r) ({ \
+ uint64_t __val; \
+ asm volatile("mrs %0, " __stringify(r) : "=r" (__val)); \
+ __val; \
+})
+
+#define write_sysreg(r, v) do { \
+ uint64_t __val = (uint64_t)(v); \
+ asm volatile("msr " __stringify(r) ", %x0" \
+ : : "rZ" (__val)); \
+} while (0)
+
+/* .inst forms of WFIT x0 and WFET x0, for assemblers without FEAT_WFxT */
+static inline void wfit(uint64_t timeout)
+{
+ register uint64_t x0 asm("x0") = timeout;
+
+ asm volatile(".inst 0xd5031020" : : "r" (x0) : "memory");
+}
+
+static inline void wfet(uint64_t timeout)
+{
+ register uint64_t x0 asm("x0") = timeout;
+
+ asm volatile(".inst 0xd5031000" : : "r" (x0) : "memory");
+}
+
+/* virt machine, GICv2 */
+#define GICD_BASE 0x08000000UL
+#define GICC_BASE 0x08010000UL
+#define GICD_CTLR 0x000
+#define GICD_ISENABLER0 0x100
+#define GICC_CTLR 0x000
+#define GICC_PMR 0x004
+#define VTIMER_PPI 27
+
+static inline void mmio_write32(uintptr_t addr, uint32_t val)
+{
+ /* GIC registers: MMIO, MMU off at EL2 */
+ *(volatile uint32_t *)addr = val;
+}
+
+static int test_one(const char *name, void (*wait)(uint64_t), uint64_t freq)
+{
+ uint64_t now, timeout, elapsed;
+ int early = 0;
+
+ /*
+ * The virtual timer interrupt, 1s out, wakes the CPU even with
+ * interrupts masked. The timeout, 10ms out, must be what wakes it.
+ */
+ now = read_sysreg(cntvct_el0);
+ write_sysreg(cntv_cval_el0, now + freq);
+ write_sysreg(cntv_ctl_el0, 1);
+ timeout = now + freq / 100;
+
+ do {
+ wait(timeout);
+ early++;
+ } while (read_sysreg(cntvct_el0) < timeout && early < 1000);
+
+ elapsed = read_sysreg(cntvct_el0) - now;
+ write_sysreg(cntv_ctl_el0, 0);
+
+ ml_printf("%s: woke after %ld ticks (%d wakes)\n", name, elapsed, early);
+ if (early >= 1000) {
+ ml_printf("FAIL: %s kept waking before its timeout\n", name);
+ return 1;
+ }
+ if (elapsed > freq / 2) {
+ ml_printf("FAIL: %s woke on the timer interrupt, not its timeout\n",
+ name);
+ return 1;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ uint64_t freq;
+ int ret;
+
+ ml_printf("WFxT Test\n");
+
+ mmio_write32(GICD_BASE + GICD_ISENABLER0, 1u << VTIMER_PPI);
+ mmio_write32(GICD_BASE + GICD_CTLR, 1);
+ mmio_write32(GICC_BASE + GICC_PMR, 0xff);
+ mmio_write32(GICC_BASE + GICC_CTLR, 1);
+
+ /* Put the virtual count ahead of the physical count */
+ write_sysreg(cntvoff_el2, -(1ULL << 60));
+ asm volatile("isb");
+ freq = read_sysreg(cntfrq_el0);
+
+ ml_printf("cntvoff_el2=%lx cntfrq_el0=%ld\n",
+ read_sysreg(cntvoff_el2), freq);
+
+ ret = test_one("wfit", wfit, freq);
+ ret |= test_one("wfet", wfet, freq);
+
+ return ret;
+}