From: David Woodhouse <[email protected]>

Restructure kvm_guest_time_update() so that kernel_ns/host_tsc are
always "now" when doing TSC catchup, then swap in the master clock
reference values afterward for the hv_clock.

This makes the TSC upscaling code considerably simpler: the catchup
adjustment is computed as the delta between what the guest TSC *should*
be at "now" and what it actually is, rather than mixing "now" and
"master clock reference" timestamps.

The seqcount loop now also contains the kvm_get_time_and_clockread()
call (matching get_kvmclock's pattern).

Based on a suggestion by Sean Christopherson.

Signed-off-by: David Woodhouse <[email protected]>
---
 arch/x86/kvm/x86.c | 78 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 54 insertions(+), 24 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 55fb19fb7a88..9dc4213f0fa5 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3349,45 +3349,60 @@ static void kvm_setup_guest_pvclock(struct 
pvclock_vcpu_time_info *ref_hv_clock,
 int kvm_guest_time_update(struct kvm_vcpu *v)
 {
        struct pvclock_vcpu_time_info hv_clock = {};
-       unsigned long flags;
        u64 tgt_tsc_hz;
        unsigned seq;
        struct kvm_vcpu_arch *vcpu = &v->arch;
        struct kvm_arch *ka = &v->kvm->arch;
        s64 kernel_ns;
        u64 tsc_timestamp, host_tsc;
+       u64 master_host_tsc = 0;
+       s64 master_kernel_ns = 0;
+       s64 kvmclock_offset = 0;
        bool use_master_clock;
 
-       kernel_ns = 0;
-       host_tsc = 0;
-
        /*
         * If the host uses TSC clock, then passthrough TSC as stable
         * to the guest.
         */
        do {
                seq = read_seqcount_begin(&ka->pvclock_sc);
+
                use_master_clock = ka->use_master_clock;
+
+               /*
+                * The TSC read and the call to get_cpu_tsc_khz() must happen
+                * on the same CPU.
+                */
+               get_cpu();
+
+               tgt_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
+
+#ifdef CONFIG_X86_64
+               if (use_master_clock &&
+                   !kvm_get_time_and_clockread(&kernel_ns, &host_tsc) &&
+                   !read_seqcount_retry(&ka->pvclock_sc, seq))
+                       use_master_clock = false;
+#endif
+
+               put_cpu();
+
                if (use_master_clock) {
-                       host_tsc = ka->master_cycle_now;
-                       kernel_ns = ka->master_kernel_ns;
+                       master_host_tsc = ka->master_cycle_now;
+                       master_kernel_ns = ka->master_kernel_ns;
+               } else {
+                       local_irq_disable();
+                       host_tsc = rdtsc();
+                       kernel_ns = get_kvmclock_base_ns();
+                       local_irq_enable();
                }
+
+               kvmclock_offset = ka->kvmclock_offset;
        } while (read_seqcount_retry(&ka->pvclock_sc, seq));
 
-       /* Keep irq disabled to prevent changes to the clock */
-       local_irq_save(flags);
-       tgt_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
        if (unlikely(tgt_tsc_hz == 0)) {
-               local_irq_restore(flags);
                kvm_make_request(KVM_REQ_CLOCK_UPDATE, v);
                return 1;
        }
-       if (!use_master_clock) {
-               host_tsc = rdtsc();
-               kernel_ns = get_kvmclock_base_ns();
-       }
-
-       tsc_timestamp = kvm_read_l1_tsc(v, host_tsc);
 
        /*
         * We may have to catch up the TSC to match elapsed wall clock
@@ -3397,17 +3412,32 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
         *      entry to avoid unknown leaps of TSC even when running
         *      again on the same CPU.  This may cause apparent elapsed
         *      time to disappear, and the guest to stand still or run
-        *      very slowly.
+        *      very slowly.
         */
        if (vcpu->tsc_catchup) {
-               u64 tsc = compute_guest_tsc(v, kernel_ns);
-               if (tsc > tsc_timestamp) {
-                       adjust_tsc_offset_guest(v, tsc - tsc_timestamp);
-                       tsc_timestamp = tsc;
-               }
+               s64 adjustment;
+
+               /*
+                * Calculate the delta between what the guest TSC *should* be
+                * and what it actually is according to kvm_read_l1_tsc().
+                */
+               adjustment = compute_guest_tsc(v, kernel_ns) -
+                            kvm_read_l1_tsc(v, host_tsc);
+               if (adjustment > 0)
+                       adjust_tsc_offset_guest(v, adjustment);
        }
 
-       local_irq_restore(flags);
+       /*
+        * Now that TSC upscaling is out of the way, the remaining calculations
+        * are all relative to the reference time that's placed in hv_clock.
+        * If the master clock is NOT in use, the reference time is "now".  If
+        * master clock is in use, the reference time comes from there.
+        */
+       if (use_master_clock) {
+               host_tsc = master_host_tsc;
+               kernel_ns = master_kernel_ns;
+       }
+       tsc_timestamp = kvm_read_l1_tsc(v, host_tsc);
 
        /* With all the info we got, fill in the values */
 
@@ -3427,7 +3457,7 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
        hv_clock.tsc_shift = vcpu->pvclock_tsc_shift;
        hv_clock.tsc_to_system_mul = vcpu->pvclock_tsc_mul;
        hv_clock.tsc_timestamp = tsc_timestamp;
-       hv_clock.system_time = kernel_ns + v->kvm->arch.kvmclock_offset;
+       hv_clock.system_time = kernel_ns + kvmclock_offset;
        vcpu->last_guest_tsc = tsc_timestamp;
 
        /* If the host uses TSC clocksource, then it is stable */
-- 
2.54.0


Reply via email to