From nobody Sat Sep 26 20:00:15 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=linux.dev Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1789993743239377.7186624619561; Mon, 21 Sep 2026 05:29:03 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x8d8f-000404-Ev; Mon, 21 Sep 2026 08:28:57 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x8Yhv-0006b6-Qx for qemu-devel@nongnu.org; Mon, 21 Sep 2026 03:44:59 -0400 Received: from out-131.mta0.migadu.com ([2001:41d0:1004:224b::83] helo=mta0.migadu.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x8Yht-000665-Vh for qemu-devel@nongnu.org; Mon, 21 Sep 2026 03:44:59 -0400 Received: by smtp.migadu.com with ESMTPS id 7935730cc60c79cc; Mon, 21 Sep 2026 07:44:56 +0000 X-Envelope-To: qemu-devel@nongnu.org DKIM-Signature: a=rsa-sha256; bh=kPmJSsdV69w6Xb0zUY7kjI4w++wJtH7CYxl3NJK6p1g=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1789976696; v=1; x=1790581496; b=sdfsd7WY8KI4jsjUAsytMyX1TxL2z4sP4sESO88UTb/L9VxRVwb+eHad6cFVArBVwcI2W/Ot O++YDIpedviAhEgQf554qc77gkeE1JHMIonA5jjwI7ESthT+MrWSUN1AjwAeuPvDCdD60gXwnGE QyQNq9cmxBwauGIzxodW7LSQ= X-Envelope-To: qemu-devel@nongnu.org X-Mizu-Trace-ID: 7935730cc60c79cc X-Migadu-Flow: FLOW_OUT From: Fuad Tabba To: Peter Maydell Cc: qemu-arm@nongnu.org, qemu-devel@nongnu.org, qemu-stable@nongnu.org, =?UTF-8?q?Alex=20Benn=C3=A9e?= , Richard Henderson , Will Deacon , Fuad Tabba Subject: [PATCH 1/2] target/arm: Fix next timer tick when the offset puts the count ahead Date: Mon, 21 Sep 2026 08:44:50 +0100 Message-Id: <20260921074451.3158645-2-fuad.tabba@linux.dev> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20260921074451.3158645-1-fuad.tabba@linux.dev> References: <20260921074451.3158645-1-fuad.tabba@linux.dev> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2001:41d0:1004:224b::83; envelope-from=fuad.tabba@linux.dev; helo=mta0.migadu.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Mon, 21 Sep 2026 08:27:51 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1789993744577158500 Content-Type: text/plain; charset="utf-8" gt_recalc_timer() arms the timer for the ISTATUS 0->1 transition at count =3D=3D cval + offset, treating an overflow of that sum as "beyond the counter's wrap" and arming at INT64_MAX. That reading is valid only when offset <=3D count. When offset > count (a CNTVOFF_EL2 or CNTPOFF_EL2 that puts the timer's counter ahead of the physical count), the sum overflows for every cval still in the future and the wrapped value is the correct next tick, which is what the code used before commit 8d37a1425b99. The timer then never fires on its own. A KVM guest whose counter is set ahead of the host's, as the arch_timer_edge_cases selftest does via KVM_REG_ARM_TIMER_CNT, gets its timer interrupt only when the vCPU is next loaded and KVM rewrites the timer registers, so a guest polling for the interrupt hangs. Reproduced with VHE, nVHE and pKVM hosts on QEMU 11.0.0. The test passes on hardware. Test count + (cval - (count - offset)) for overflow instead, so only a physical count past 2^64 is "never", and add a vtimer test case with an offset that puts the count ahead. Fixes: 8d37a1425b99 ("target/arm: Handle overflow in calculation of next ti= mer tick") Cc: qemu-stable@nongnu.org Signed-off-by: Fuad Tabba --- target/arm/helper.c | 9 +++++---- tests/tcg/aarch64/system/vtimer.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index c3f607e6d6..f89f41fe63 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -1575,11 +1575,12 @@ static void gt_recalc_timer(ARMCPU *cpu, int timeri= dx) } else { /* * Next transition is when (count - offset) =3D=3D cval, i.e. - * when count =3D=3D (cval + offset). - * If that would overflow, then again we set up the next inter= rupt - * for "as far in the future as possible" for the code below. + * cval - (count - offset) ticks from now. If count plus that + * overflows, set up "as far in the future as possible" below. */ - if (uadd64_overflow(gt->cval, offset, &nexttick)) { + uint64_t remaining =3D gt->cval - (count - offset); + + if (uadd64_overflow(count, remaining, &nexttick)) { nexttick =3D UINT64_MAX; } } diff --git a/tests/tcg/aarch64/system/vtimer.c b/tests/tcg/aarch64/system/v= timer.c index 7d725eced3..e1cfad36be 100644 --- a/tests/tcg/aarch64/system/vtimer.c +++ b/tests/tcg/aarch64/system/vtimer.c @@ -27,6 +27,7 @@ =20 int main(void) { + uint64_t freq, now; int i; =20 ml_printf("VTimer Test\n"); @@ -44,5 +45,32 @@ int main(void) ml_printf("%d: cntv_cval_el0=3D%lx\n", i, read_sysreg(cntv_cval_el= 0)); } =20 + /* + * An offset that puts the virtual count ahead of the physical one, + * so cval + cntvoff wraps for every future cval. The timer must + * still fire. ISTATUS is set by the expiry, so poll it with a bound. + */ + write_sysreg(cntv_ctl_el0, 0); + write_sysreg(cntvoff_el2, -(1ULL << 60)); + asm volatile("isb"); + + freq =3D read_sysreg(cntfrq_el0); + now =3D read_sysreg(cntvct_el0); + write_sysreg(cntv_cval_el0, now + freq / 100); + write_sysreg(cntv_ctl_el0, 1); + + ml_printf("cntvoff_el2=3D%lx\n", read_sysreg(cntvoff_el2)); + ml_printf("cntvct_el0=3D%lx\n", now); + ml_printf("cntv_cval_el0=3D%lx\n", read_sysreg(cntv_cval_el0)); + + while (!(read_sysreg(cntv_ctl_el0) & 4)) { + if (read_sysreg(cntvct_el0) - now > freq) { + ml_printf("FAIL: ISTATUS not set within 1s: cntv_ctl_el0=3D%lx= \n", + read_sysreg(cntv_ctl_el0)); + return 1; + } + } + ml_printf("ISTATUS set at cntvct_el0=3D%lx\n", read_sysreg(cntvct_el0)= ); + return 0; } --=20 2.39.5 From nobody Sat Sep 26 20:00:15 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=linux.dev Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 17899937255581012.9280459773665; Mon, 21 Sep 2026 05:28:45 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x8d8D-0003oK-Sg; Mon, 21 Sep 2026 08:28:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x8Yi0-0006dJ-TQ for qemu-devel@nongnu.org; Mon, 21 Sep 2026 03:45:04 -0400 Received: from out-144.mta0.migadu.com ([2001:41d0:1004:224b::90] helo=mta0.migadu.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1x8Yhy-000673-DI for qemu-devel@nongnu.org; Mon, 21 Sep 2026 03:45:04 -0400 Received: by smtp.migadu.com with ESMTPS id 48b83ec94cbf3cef; Mon, 21 Sep 2026 07:45:00 +0000 X-Envelope-To: qemu-devel@nongnu.org DKIM-Signature: a=rsa-sha256; bh=DkCA3LQk6XsB5+Wn3PTg4D9l6WDGjBmZQIzEuLBxvDE=; c=simple/simple; d=linux.dev; h=from:to:subject:date:message-id:mime-version:content-type; s=key1; t=1789976700; v=1; x=1790581500; b=Z/zb/q2SIH8yzPf6Vk0RCrqeXo/3xZi0pM07vFPjRURV3NVzi7qyOiGX5kv+pGpyPidDA/Va yDTD15w0CgIT9yK82s/Gn8QUiKKUGRNgnq+nvg8U5FRKmZXp8mbpZ+cOURCEKPfV95NcUOi8vO7 5idBHM6eDaGpDTKcaWQe1G1c= X-Envelope-To: qemu-devel@nongnu.org X-Mizu-Trace-ID: 48b83ec94cbf3cef X-Migadu-Flow: FLOW_OUT From: Fuad Tabba To: Peter Maydell Cc: qemu-arm@nongnu.org, qemu-devel@nongnu.org, qemu-stable@nongnu.org, =?UTF-8?q?Alex=20Benn=C3=A9e?= , Richard Henderson , Will Deacon , Fuad Tabba Subject: [PATCH 2/2] target/arm: Fix WFxT timeouts when the offset puts the count ahead Date: Mon, 21 Sep 2026 08:44:51 +0100 Message-Id: <20260921074451.3158645-3-fuad.tabba@linux.dev> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20260921074451.3158645-1-fuad.tabba@linux.dev> References: <20260921074451.3158645-1-fuad.tabba@linux.dev> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2001:41d0:1004:224b::90; envelope-from=fuad.tabba@linux.dev; helo=mta0.migadu.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Mon, 21 Sep 2026 08:27:51 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1789993726704158500 Content-Type: text/plain; charset="utf-8" The WFIT and WFET helpers arm the wakeup timer at count =3D=3D 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 <=3D 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 --- target/arm/tcg/op_helper.c | 6 +- tests/tcg/aarch64/system/meson.build | 7 ++ tests/tcg/aarch64/system/wfxt.c | 118 +++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 tests/tcg/aarch64/system/wfxt.c diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index c2b09176cb..562f11f390 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); } =20 - 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 =3D 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 >=3D the specified value. */ cpu =3D 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 =3D UINT64_MAX; } if (nexttick > INT64_MAX / gt_cntfrq_period_ns(cpu)) { diff --git a/tests/tcg/aarch64/system/meson.build b/tests/tcg/aarch64/syste= m/meson.build index 94e16ba330..5d96a9c25a 100644 --- a/tests/tcg/aarch64/system/meson.build +++ b/tests/tcg/aarch64/system/meson.build @@ -72,6 +72,13 @@ tests +=3D { '-semihosting-config', 'enable=3Don,arg=3D2', qemu_base_args] }, + 'wfxt.c': { + 'cflags': cflags, + 'qemu_args': ['-M', 'virt,virtualization=3Don,gic-version=3D2', + '-cpu', 'max', '-smp', '1', + '-semihosting-config', 'enable=3Don,arg=3D2', + qemu_base_args] + }, } =20 tests +=3D { diff --git a/tests/tcg/aarch64/system/wfxt.c b/tests/tcg/aarch64/system/wfx= t.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 + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include + +/* 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) : "=3Dr" (__val)); \ + __val; \ +}) + +#define write_sysreg(r, v) do { \ + uint64_t __val =3D (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") =3D timeout; + + asm volatile(".inst 0xd5031020" : : "r" (x0) : "memory"); +} + +static inline void wfet(uint64_t timeout) +{ + register uint64_t x0 asm("x0") =3D 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 =3D val; +} + +static int test_one(const char *name, void (*wait)(uint64_t), uint64_t fre= q) +{ + uint64_t now, timeout, elapsed; + int early =3D 0; + + /* + * The virtual timer interrupt, 1s out, wakes the CPU even with + * interrupts masked. The timeout, 10ms out, must be what wakes it. + */ + now =3D read_sysreg(cntvct_el0); + write_sysreg(cntv_cval_el0, now + freq); + write_sysreg(cntv_ctl_el0, 1); + timeout =3D now + freq / 100; + + do { + wait(timeout); + early++; + } while (read_sysreg(cntvct_el0) < timeout && early < 1000); + + elapsed =3D read_sysreg(cntvct_el0) - now; + write_sysreg(cntv_ctl_el0, 0); + + ml_printf("%s: woke after %ld ticks (%d wakes)\n", name, elapsed, earl= y); + if (early >=3D 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 =3D read_sysreg(cntfrq_el0); + + ml_printf("cntvoff_el2=3D%lx cntfrq_el0=3D%ld\n", + read_sysreg(cntvoff_el2), freq); + + ret =3D test_one("wfit", wfit, freq); + ret |=3D test_one("wfet", wfet, freq); + + return ret; +} --=20 2.39.5