From: Fuad Tabba <fuad.tabba@linux.dev>
To: Peter Maydell <peter.maydell@linaro.org>
Cc: qemu-arm@nongnu.org, qemu-devel@nongnu.org,
qemu-stable@nongnu.org, "Alex Bennée" <alex.bennee@linaro.org>,
"Richard Henderson" <richard.henderson@linaro.org>,
"Will Deacon" <will@kernel.org>, "Fuad Tabba" <tabba@google.com>
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 [thread overview]
Message-ID: <20260921074451.3158645-2-fuad.tabba@linux.dev> (raw)
In-Reply-To: <20260921074451.3158645-1-fuad.tabba@linux.dev>
gt_recalc_timer() arms the timer for the ISTATUS 0->1 transition at
count == 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 <= 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 timer tick")
Cc: qemu-stable@nongnu.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
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 timeridx)
} else {
/*
* Next transition is when (count - offset) == cval, i.e.
- * when count == (cval + offset).
- * If that would overflow, then again we set up the next interrupt
- * 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 = gt->cval - (count - offset);
+
+ if (uadd64_overflow(count, remaining, &nexttick)) {
nexttick = UINT64_MAX;
}
}
diff --git a/tests/tcg/aarch64/system/vtimer.c b/tests/tcg/aarch64/system/vtimer.c
index 7d725eced3..e1cfad36be 100644
--- a/tests/tcg/aarch64/system/vtimer.c
+++ b/tests/tcg/aarch64/system/vtimer.c
@@ -27,6 +27,7 @@
int main(void)
{
+ uint64_t freq, now;
int i;
ml_printf("VTimer Test\n");
@@ -44,5 +45,32 @@ int main(void)
ml_printf("%d: cntv_cval_el0=%lx\n", i, read_sysreg(cntv_cval_el0));
}
+ /*
+ * 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 = read_sysreg(cntfrq_el0);
+ now = read_sysreg(cntvct_el0);
+ write_sysreg(cntv_cval_el0, now + freq / 100);
+ write_sysreg(cntv_ctl_el0, 1);
+
+ ml_printf("cntvoff_el2=%lx\n", read_sysreg(cntvoff_el2));
+ ml_printf("cntvct_el0=%lx\n", now);
+ ml_printf("cntv_cval_el0=%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=%lx\n",
+ read_sysreg(cntv_ctl_el0));
+ return 1;
+ }
+ }
+ ml_printf("ISTATUS set at cntvct_el0=%lx\n", read_sysreg(cntvct_el0));
+
return 0;
}
--
2.39.5
next prev parent reply other threads:[~2026-09-21 12:27 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-21 7:44 [PATCH 0/2] target/arm: Fix timer deadlines when the offset puts the count ahead Fuad Tabba
2026-09-21 7:44 ` Fuad Tabba [this message]
2026-09-21 7:44 ` [PATCH 2/2] target/arm: Fix WFxT timeouts " Fuad Tabba
2026-09-30 8:56 ` Michael Tokarev
2026-09-30 11:41 ` Peter Maydell
2026-09-28 16:20 ` [PATCH 0/2] target/arm: Fix timer deadlines " Peter Maydell
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=20260921074451.3158645-2-fuad.tabba@linux.dev \
--to=fuad.tabba@linux.dev \
--cc=alex.bennee@linaro.org \
--cc=peter.maydell@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=qemu-stable@nongnu.org \
--cc=richard.henderson@linaro.org \
--cc=tabba@google.com \
--cc=will@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.