[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v13 07/26] KVM: x86: Don't latch the Hyper-V TSC page from a superseded snapshot



From: David Woodhouse <dwmw@xxxxxxxxxxxx>

The Hyper-V TSC page is rewritten from kvm_guest_time_update() when its
HOST_CHANGED state is set, and latches to HV_TSC_PAGE_SET, which blocks
further rewrites. But the state is set before the new pvclock snapshot
is published (setting it takes hv_lock, a mutex, so it cannot happen
inside the tsc_write_lock section), and nothing validates the snapshot
the consumer is holding. Two interleavings latch stale parameters which
nothing then rewrites until some unrelated clock event invalidates the
page again:

 - A vCPU handling an unrelated KVM_REQ_CLOCK_UPDATE completes its
   pvclock read, is preempted (or sleeps in kvm_write_guest(), holding
   hv_lock), a KVM_SET_CLOCK completes meanwhile, and the vCPU then
   writes the page from the old snapshot and sets HV_TSC_PAGE_SET —
   consuming the state intended for the new one.

 - The state is consumed legitimately an instant before the publish,
   writing values which were current at that moment and superseded the
   next; the CLOCK_UPDATE broadcast which follows the publish finds
   HV_TSC_PAGE_SET and rewrites nothing.

For a KVM_SET_CLOCK stepping kvmclock_offset — a live migration
restore — the page is left disagreeing with the restored clock by the
full step.

Close the first at the consumer: kvm_hv_setup_tsc_page() revalidates
the pvclock seqcount under hv_lock — it must be under the lock, or the
consumer could be preempted between a check and the consumption — and
requeues the clock update rather than consume the state against a
superseded snapshot. Close the second by re-marking the page once the
new snapshot is published, with a CLOCK_UPDATE queued on every vCPU
behind the re-mark: the broadcast from kvm_end_pvclock_update() can
have been entirely consumed by then, and no vCPU in particular is
guaranteed to ever run again. The hv_lock mutex which lets a consumer
sleep across the whole update is also what serializes its write
against the re-mark, so the queued consumer always finds either fresh
page contents or the HOST_CHANGED state; guests which never enabled
the page skip the broadcast entirely.

Fixes: cc9cfddb0433 ("KVM: x86: hyper-v: Track Hyper-V TSC page status")
Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
---
 arch/x86/kvm/hyperv.c | 20 ++++++++++++++++++--
 arch/x86/kvm/hyperv.h | 10 ++++++----
 arch/x86/kvm/x86.c    | 36 +++++++++++++++++++++++++++++++++++-
 3 files changed, 59 insertions(+), 7 deletions(-)

diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 5d2e3617c952..56da410a7f32 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -1182,9 +1182,11 @@ static inline bool tsc_page_update_unsafe(struct kvm_hv 
*hv)
                hv->hv_tsc_emulation_control;
 }
 
-void kvm_hv_setup_tsc_page(struct kvm *kvm,
-                          struct pvclock_vcpu_time_info *hv_clock)
+void kvm_hv_setup_tsc_page(struct kvm_vcpu *v,
+                          struct pvclock_vcpu_time_info *hv_clock,
+                          unsigned int seq)
 {
+       struct kvm *kvm = v->kvm;
        struct kvm_hv *hv = to_kvm_hv(kvm);
        u32 tsc_seq;
        u64 gfn;
@@ -1202,6 +1204,20 @@ void kvm_hv_setup_tsc_page(struct kvm *kvm,
        if (!(hv->hv_tsc_page & HV_X64_MSR_TSC_REFERENCE_ENABLE))
                return;
 
+       /*
+        * @hv_clock derives from the pvclock snapshot current at @seq.
+        * Consuming the HOST_CHANGED state against a superseded snapshot
+        * would latch stale parameters as HV_TSC_PAGE_SET, which nothing
+        * then rewrites; the check must be made here, under hv_lock, or
+        * this vCPU could be preempted between a check and the
+        * consumption while an update completes. Leave the state alone
+        * and requeue; the retry reads the new snapshot.
+        */
+       if (read_seqcount_retry(&kvm->arch.pvclock_sc, seq)) {
+               kvm_make_request(KVM_REQ_CLOCK_UPDATE, v);
+               return;
+       }
+
        gfn = hv->hv_tsc_page >> HV_X64_MSR_TSC_REFERENCE_ADDRESS_SHIFT;
        /*
         * Because the TSC parameters only vary when there is a
diff --git a/arch/x86/kvm/hyperv.h b/arch/x86/kvm/hyperv.h
index 622a6553e9ac..62e7922440aa 100644
--- a/arch/x86/kvm/hyperv.h
+++ b/arch/x86/kvm/hyperv.h
@@ -284,8 +284,9 @@ static inline bool kvm_hv_invtsc_suppressed(struct kvm_vcpu 
*vcpu)
 
 void kvm_hv_process_stimers(struct kvm_vcpu *vcpu);
 
-void kvm_hv_setup_tsc_page(struct kvm *kvm,
-                          struct pvclock_vcpu_time_info *hv_clock);
+void kvm_hv_setup_tsc_page(struct kvm_vcpu *v,
+                          struct pvclock_vcpu_time_info *hv_clock,
+                          unsigned int seq);
 void kvm_hv_request_tsc_page_update(struct kvm *kvm);
 
 void kvm_hv_xsaves_xsavec_maybe_warn(struct kvm_vcpu *vcpu);
@@ -377,8 +378,9 @@ static inline void kvm_hv_nested_transtion_tlb_flush(struct 
kvm_vcpu *vcpu,
 
 int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu);
 #else /* CONFIG_KVM_HYPERV */
-static inline void kvm_hv_setup_tsc_page(struct kvm *kvm,
-                                        struct pvclock_vcpu_time_info 
*hv_clock) {}
+static inline void kvm_hv_setup_tsc_page(struct kvm_vcpu *v,
+                                        struct pvclock_vcpu_time_info 
*hv_clock,
+                                        unsigned int seq) {}
 static inline void kvm_hv_request_tsc_page_update(struct kvm *kvm) {}
 static inline void kvm_hv_xsaves_xsavec_maybe_warn(struct kvm_vcpu *vcpu) {}
 static inline void kvm_hv_init_vm(struct kvm *kvm) {}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 05c801973a7f..f7af94d8b3bb 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1789,6 +1789,36 @@ static void kvm_end_pvclock_update(struct kvm *kvm)
                kvm_clear_request(KVM_REQ_MCLOCK_INPROGRESS, vcpu);
 }
 
+/*
+ * Re-mark the Hyper-V TSC page once a new pvclock snapshot is published:
+ * the mark made before the update may have been legitimately consumed an
+ * instant before the publish, writing values the publish then superseded.
+ * The consumer must be queued after the re-mark: the CLOCK_UPDATE
+ * broadcast from kvm_end_pvclock_update() can have been entirely consumed
+ * by the time the re-mark lands, since a consumer may sleep in
+ * kvm_hv_setup_tsc_page() (hv_lock is a mutex) across the whole update —
+ * the same sleep which serializes it against this re-mark. Queue on
+ * every vCPU: any one suffices (the page is VM-wide), but none in
+ * particular is guaranteed to ever run again.
+ */
+static void kvm_hv_remark_tsc_page(struct kvm *kvm)
+{
+#ifdef CONFIG_KVM_HYPERV
+       kvm_hv_request_tsc_page_update(kvm);
+
+       /*
+        * Racy, but benign: a transition to HV_TSC_PAGE_SET in the
+        * instants after the re-mark means a consumer has just written
+        * the page from the current snapshot, and a guest which never
+        * enabled the page (UNSET/BROKEN, the common case) needs no
+        * broadcast at all.
+        */
+       if (READ_ONCE(to_kvm_hv(kvm)->hv_tsc_page_status) ==
+           HV_TSC_PAGE_HOST_CHANGED)
+               kvm_make_all_cpus_request(kvm, KVM_REQ_CLOCK_UPDATE);
+#endif
+}
+
 static void kvm_update_masterclock(struct kvm *kvm)
 {
        kvm_hv_request_tsc_page_update(kvm);
@@ -1961,6 +1991,7 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
        } while (read_seqcount_retry(&ka->pvclock_sc, seq));
 #else
        const bool use_master_clock = false;
+       unsigned int seq = read_seqcount_begin(&ka->pvclock_sc);
 #endif
        /*
         * Ensure reading the TSC+frequency pair is done on the same CPU.  When
@@ -2056,7 +2087,7 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
                hv_clock.flags &= ~PVCLOCK_GUEST_STOPPED;
        }
 
-       kvm_hv_setup_tsc_page(v->kvm, &hv_clock);
+       kvm_hv_setup_tsc_page(v, &hv_clock, seq);
 
 #ifdef CONFIG_KVM_XEN
        /*
@@ -4572,6 +4603,9 @@ static int kvm_vm_ioctl_set_clock(struct kvm *kvm, void 
__user *argp)
                now_raw_ns = get_kvmclock_base_ns();
        ka->kvmclock_offset = data.clock - now_raw_ns;
        kvm_end_pvclock_update(kvm);
+
+       /* As in kvm_update_masterclock(): re-mark after publishing. */
+       kvm_hv_remark_tsc_page(kvm);
        return 0;
 }
 
-- 
2.43.0




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.