From: David Woodhouse <[email protected]>

kvm_xen_inject_timer_irqs() can drop events if kvm_xen_set_evtchn()
fails transiently, as it can when a memslot is in the process of
being updated. Defer the delivery in that case by leaving
->arch.xen.timer_pending set, so that it gets retried. The same
applies when the shared_info cache has been deactivated (-EINVAL).
If delivery fails with -EFAULT, the shared_info gfn is genuinely
unbacked and can never succeed: drop the event and complete the
timer rather than spin on it.

True Xen does not have this problem: the shared_info page is owned
by Xen and always exists, even when not mapped into the guest. The
equivalent for a KVM/Xen VMM is KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA,
which has no memslot dependency and is the recommended mode; this
fixes the legacy GFN mode, which xen_shinfo_test exercises.

Don't re-arm the hrtimer in the KVM_XEN_VCPU_GET_ATTR(TIMER) flush
while a delivery is still pending; a re-fired callback racing the
retry would deliver twice.

The drop is vanishingly rare upstream, but hangs roughly half of all
xen_shinfo_test runs once the following atomic SRCU conversion
widens the window; hence fixing it first.

Fixes: 536395260582 ("KVM: x86/xen: handle PV timers oneshot mode")
Assisted-by: LLM
Signed-off-by: David Woodhouse <[email protected]>
---
 arch/x86/kvm/xen.c                            | 36 ++++++++++++++-----
 .../selftests/kvm/x86/xen_shinfo_test.c       | 11 +++---
 2 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 76708b896026..aea6572163d0 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -106,13 +106,23 @@ void kvm_xen_inject_timer_irqs(struct kvm_vcpu *vcpu)
 {
        if (atomic_read(&vcpu->arch.xen.timer_pending) > 0) {
                struct kvm_xen_evtchn e;
+               int rc;
 
                e.vcpu_id = vcpu->vcpu_id;
                e.vcpu_idx = vcpu->vcpu_idx;
                e.port = vcpu->arch.xen.timer_virq;
                e.priority = KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL;
 
-               kvm_xen_set_evtchn(&e, vcpu->kvm);
+               rc = kvm_xen_set_evtchn(&e, vcpu->kvm);
+
+               /*
+                * Deliberately leave timer_pending set if the memslot is
+                * being updated (-EAGAIN) or the shared_info cache has
+                * been deactivated (-EINVAL); delivery is retried on the
+                * next entry pass.
+                */
+               if (rc == -EAGAIN || rc == -EINVAL)
+                       return;
 
                vcpu->arch.xen.timer_expires = 0;
                atomic_set(&vcpu->arch.xen.timer_pending, 0);
@@ -1263,12 +1273,13 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, struct 
kvm_xen_vcpu_attr *data)
 
        case KVM_XEN_VCPU_ATTR_TYPE_TIMER:
                /*
-                * Ensure a consistent snapshot of state is captured, with a
-                * timer either being pending, or the event channel delivered
-                * to the corresponding bit in the shared_info. Not still
-                * lurking in the timer_pending flag for deferred delivery.
-                * Purely as an optimisation, if the timer_expires field is
-                * zero, that means the timer isn't active (or even in the
+                * Ensure a consistent snapshot of state is captured: a timer
+                * which already fired has either been delivered to the
+                * corresponding bit in the shared_info, or is reported as
+                * still owed (expires_ns != 0 with the pending flag set, if
+                * the shared_info page was transiently unavailable). Purely
+                * as an optimisation, if the timer_expires field is zero,
+                * that means the timer isn't active (or even in the
                 * timer_pending flag) and there is no need to cancel it.
                 */
                if (vcpu->arch.xen.timer_expires) {
@@ -1287,8 +1298,17 @@ int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, struct 
kvm_xen_vcpu_attr *data)
                 * That's fine, as the guest won't even have had a chance
                 * to run and handle the interrupt. Asserting an already
                 * pending event channel is idempotent.
+                *
+                * Don't re-arm if the flush above failed transiently and
+                * left the timer pending: delivery is owned by the retry
+                * on the guest entry path, and an immediate hrtimer fire
+                * would race it — the callback's timer_pending suppression
+                * check is not serialized against the entry path, so a
+                * late callback could re-assert a port the entry path had
+                * already delivered and the guest already consumed.
                 */
-               if (vcpu->arch.xen.timer_expires)
+               if (vcpu->arch.xen.timer_expires &&
+                   !atomic_read(&vcpu->arch.xen.timer_pending))
                        hrtimer_start_expires(&vcpu->arch.xen.timer,
                                              HRTIMER_MODE_ABS_HARD);
 
diff --git a/tools/testing/selftests/kvm/x86/xen_shinfo_test.c 
b/tools/testing/selftests/kvm/x86/xen_shinfo_test.c
index aaa414c677ad..3b11d0ee253f 100644
--- a/tools/testing/selftests/kvm/x86/xen_shinfo_test.c
+++ b/tools/testing/selftests/kvm/x86/xen_shinfo_test.c
@@ -356,11 +356,12 @@ static void guest_code(void)
 wait_for_timer:
        /*
         * Poll for a timer wake event while the worker thread is mucking with
-        * the shared info.  KVM XEN drops timer IRQs if the shared info is
-        * invalid when the timer expires.  Arbitrarily poll 100 times before
-        * giving up and asking the VMM to re-arm the timer.  100 polls should
-        * consume enough time to beat on KVM without taking too long if the
-        * timer IRQ is dropped due to an invalid event channel.
+        * the shared info.  KVM is now supposed to retry a timer IRQ whose
+        * delivery hit an invalid shared info rather than drop it, but older
+        * kernels drop it on the floor.  Arbitrarily poll 100 times before
+        * giving up and asking the VMM to re-arm the timer, so the test still
+        * completes (and documents the VMM-side recovery protocol) on
+        * unfixed kernels.
         */
        for (i = 0; i < 100 && !guest_saw_irq; i++)
                __xen_hypercall(__HYPERVISOR_sched_op, SCHEDOP_poll, &p);
-- 
2.43.0


Reply via email to