From: Sean Christopherson <seanjc@google.com>
To: Igor Mammedov <imammedo@redhat.com>
Cc: kvm@vger.kernel.org, pbonzini@redhat.com
Subject: Re: [kvm-unit-tests PATCH 2/2] x86/apic: add retry logic to test_apic_change_mode
Date: Tue, 6 Oct 2026 00:15:20 -0700 [thread overview]
Message-ID: <asSgCLgeSCKh33_n@google.com> (raw)
In-Reply-To: <20260428133524.3628482-3-imammedo@redhat.com>
On Tue, Apr 28, 2026, Igor Mammedov wrote:
> The APIC timer runs at wall clock time under KVM. If vCPU is stalled for
> long enough, timer can expire before the guest reads TMCCT or tick well
> past expected values, causing various false test failures [1].
>
> Add retry attempts with increasing timer period (10ms, 60ms, 700ms)
> if any test fails, to handle spurious failures due to vCPU stalls.
>
> 1) Failures we sometimes observe in CI are:
> "FAIL: TMCCT should have a non-zero value"
> "FAIL: TMCCT should be reset to the initial-count"
> "FAIL: TMCCT should not be reset to TMICT value"
> Seen on both Intel and AMD hosts.
>
> PS:
> on most test runs, test completes fine on the 1st iteration.
> The patch would affect only failure path which will be slowed down
> due to retries but still fail if there is a bug with benefit of
> getting rid of false positives caused by vCPU stalls.
>
> PS2:
> Number of tries and tmict delta comes from analyzing vcpu stalls
> on heavily overcommited Haswell host with test being bounced between
> 2 sockets.
> Typically 2nd iteration (60ms) is enough to get rid of
> false positives.
Blech. I would rather rework the testcases to simply not be flaky. I don't see
any value in waiting for the TMCCT to be half of TMICT; just validate that it
counts down across switches, and that it can wrap in PERIODIC but not in ONESHOT.
We have a KVM selftest to validate the timer counts at the correct rate, so just
validate the core functionality here.
E.g. roughly something like this?
diff --git a/x86/apic.c b/x86/apic.c
index ed8be25a..1fe6877d 100644
--- a/x86/apic.c
+++ b/x86/apic.c
@@ -532,37 +532,6 @@ static void test_physical_broadcast(void)
report(broadcast_received(ncpus), "APIC physical broadcast shorthand");
}
-static void wait_until_tmcct_common(uint32_t initial_count, bool stop_when_half, bool should_wrap_around)
-{
- uint32_t tmcct = apic_read(APIC_TMCCT);
-
- if (tmcct) {
- while (tmcct > (initial_count / 2))
- tmcct = apic_read(APIC_TMCCT);
-
- if ( stop_when_half )
- return;
-
- /* Wait until the counter reach 0 or wrap-around */
- while ( tmcct <= (initial_count / 2) && tmcct > 0 )
- tmcct = apic_read(APIC_TMCCT);
-
- /* Wait specifically for wrap around to skip 0 TMCCR if we were asked to */
- while (should_wrap_around && !tmcct)
- tmcct = apic_read(APIC_TMCCT);
- }
-}
-
-static void wait_until_tmcct_is_zero(uint32_t initial_count, bool stop_when_half)
-{
- return wait_until_tmcct_common(initial_count, stop_when_half, false);
-}
-
-static void wait_until_tmcct_wrap_around(uint32_t initial_count, bool stop_when_half)
-{
- return wait_until_tmcct_common(initial_count, stop_when_half, true);
-}
-
static inline void apic_change_mode(unsigned long new_mode)
{
uint32_t lvtt;
@@ -576,64 +545,87 @@ static inline void apic_change_mode(unsigned long new_mode)
lvtt & APIC_LVT_TIMER_MASK, new_mode);
}
+static void check_apic_tmcct(unsigned long mode, uint32_t *tmict)
+{
+ /*
+ * Note, this also covers the case where the timer expired in one-shot
+ * mode, i.e. if tmcct0 == 0.
+ */
+ if (tmcct[1] > tmcct[0] && mode == APIC_LVT_TIMER_ONESHOT) {
+ report_fail(...);
+ return;
+ }
+
+ if (tmcct[0] == tmcct[1] &&
+ (tmcct[0] || mode == APIC_LVT_TIMER_PERIODIC)) {
+ tmcct[1] = apic_read(APIC_TMCCT);
+
+ /*
+ * Theoretically, this could get a false failure when the timer
+ * is periodic mode. In practice, the odds of all three reads
+ * getting the same value is so absurdly small that it will
+ * never happen
+ */
+ if (tmcct[0] == tmcct[1])
+ report_fail(...);
+ }
+}
+
static void test_apic_change_mode(void)
{
+ unsigned long mode = APIC_LVT_TIMER_ONESHOT;
uint32_t tmict = 0x999999;
+ uint32_t tmcct[2];
printf("starting apic change mode\n");
+ apic_change_mode(mode);
apic_write(APIC_TMICT, tmict);
- apic_change_mode(APIC_LVT_TIMER_PERIODIC);
-
- report(apic_read(APIC_TMICT) == tmict, "TMICT value reset");
-
- /* Testing one-shot */
- apic_change_mode(APIC_LVT_TIMER_ONESHOT);
- apic_write(APIC_TMICT, tmict);
- report(apic_read(APIC_TMCCT), "TMCCT should have a non-zero value");
-
- wait_until_tmcct_is_zero(tmict, false);
- report(!apic_read(APIC_TMCCT), "TMCCT should have reached 0");
-
/*
- * Write TMICT before changing mode from one-shot to periodic TMCCT should
- * be reset to TMICT periodicly
+ * 1. ONESHOT => PERIODIC
+ * 2. PERIODIC => ONESHOT
+ * 3. ONESHOT => PERIODIC
+ * 4. PERIODIC => ONESHOT => PERIODIC (final checks)
*/
- apic_write(APIC_TMICT, tmict);
- wait_until_tmcct_is_zero(tmict, true);
- apic_change_mode(APIC_LVT_TIMER_PERIODIC);
- report(apic_read(APIC_TMCCT), "TMCCT should have a non-zero value");
+ for (i = 0; i < 4; i++) {
+ if (apic_read(APIC_TMICT) != tmict)
+ report_fail(...);
+
+ mode ^= BIT(17);
+ apic_change_mode(mode);
+
+ tmcct[0] = apic_read(APIC_TMCCT);
+ do {
+ tmcct[1] = apic_read(APIC_TMCCT);
+ check_apic_tmcct(tmcct);
+ } while (tmcct[1] && tmcct[1] < tmcct[0]);
+
+ tmcct[0] = tmcct[1];
+ tmcct[1] = apic_read(APIC_TMCCT);
+ check_apic_tmcct(mode, tmcct);
+ if (mode == APIC_LVT_TIMER_ONESHOT)
+ apic_write(APIC_TMICT, tmict);
+ }
- /*
- * After the change of mode, the counter should not be reset and continue
- * counting down from where it was
- */
- report(apic_read(APIC_TMCCT) < (tmict / 2),
- "TMCCT should not be reset to TMICT value");
- /*
- * Specifically wait for timer wrap around and skip 0.
- * Under KVM lapic there is a possibility that a small amount of consecutive
- * TMCCR reads return 0 while hrtimer is reset in an async callback
- */
- wait_until_tmcct_wrap_around(tmict, false);
- report(apic_read(APIC_TMCCT) > (tmict / 2),
- "TMCCT should be reset to the initial-count");
+ apic_change_mode(APIC_LVT_TIMER_ONESHOT);
+ tmcct[0] = apic_read(APIC_TMCCT);
+ do {
+ tmcct[1] = apic_read(APIC_TMCCT);
+ check_apic_tmcct(APIC_LVT_TIMER_ONESHOT, tmcct);
+ } while (tmcct[1] && tmcct[1] < tmcct[0]);
+
+ if (tmcct[1])
+ report_fail(...);
- wait_until_tmcct_is_zero(tmict, true);
- /*
- * Keep the same TMICT and change timer mode to one-shot
- * TMCCT should be > 0 and count-down to 0
- */
apic_change_mode(APIC_LVT_TIMER_ONESHOT);
- report(apic_read(APIC_TMCCT) < (tmict / 2),
- "TMCCT should not be reset to init");
- wait_until_tmcct_is_zero(tmict, false);
- report(!apic_read(APIC_TMCCT), "TMCCT should have reach zero");
-
- /* now tmcct == 0 and tmict != 0 */
- apic_change_mode(APIC_LVT_TIMER_PERIODIC);
- report(!apic_read(APIC_TMCCT), "TMCCT should stay at zero");
+
+ /* Switching to PERIODIC mode should NOT re-arm the timer. */
+ tmcct[1] = apic_read(APIC_TMCCT);
+ if (tmcct[1])
+ report_fail(...);
+ else
+ report_pass();
}
#define KVM_HC_SEND_IPI 10
next prev parent reply other threads:[~2026-10-06 7:15 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-28 13:35 [kvm-unit-tests PATCH 0/2] x86/apic: fix false test_apic_change_mode failures on stalled vCPUs Igor Mammedov
2026-04-28 13:35 ` [kvm-unit-tests PATCH 1/2] x86/apic: separate reporting from actual measurements Igor Mammedov
2026-04-28 13:35 ` [kvm-unit-tests PATCH 2/2] x86/apic: add retry logic to test_apic_change_mode Igor Mammedov
2026-10-06 7:15 ` Sean Christopherson [this message]
2026-10-05 15:23 ` [kvm-unit-tests PATCH 0/2] x86/apic: fix false test_apic_change_mode failures on stalled vCPUs Igor Mammedov
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=asSgCLgeSCKh33_n@google.com \
--to=seanjc@google.com \
--cc=imammedo@redhat.com \
--cc=kvm@vger.kernel.org \
--cc=pbonzini@redhat.com \
/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