MIPS: KVM: Fix timer IRQ race when freezing timer
[firefly-linux-kernel-4.4.55.git] / arch / mips / kvm / emulate.c
index 41b1b090f56f6b73afc50240318634a4988ef427..eaf77b5479ce6591e9a5bc7e857a289d9e6a029a 100644 (file)
@@ -302,12 +302,31 @@ static inline ktime_t kvm_mips_count_time(struct kvm_vcpu *vcpu)
  */
 static uint32_t kvm_mips_read_count_running(struct kvm_vcpu *vcpu, ktime_t now)
 {
-       ktime_t expires;
+       struct mips_coproc *cop0 = vcpu->arch.cop0;
+       ktime_t expires, threshold;
+       uint32_t count, compare;
        int running;
 
-       /* Is the hrtimer pending? */
+       /* Calculate the biased and scaled guest CP0_Count */
+       count = vcpu->arch.count_bias + kvm_mips_ktime_to_count(vcpu, now);
+       compare = kvm_read_c0_guest_compare(cop0);
+
+       /*
+        * Find whether CP0_Count has reached the closest timer interrupt. If
+        * not, we shouldn't inject it.
+        */
+       if ((int32_t)(count - compare) < 0)
+               return count;
+
+       /*
+        * The CP0_Count we're going to return has already reached the closest
+        * timer interrupt. Quickly check if it really is a new interrupt by
+        * looking at whether the interval until the hrtimer expiry time is
+        * less than 1/4 of the timer period.
+        */
        expires = hrtimer_get_expires(&vcpu->arch.comparecount_timer);
-       if (ktime_compare(now, expires) >= 0) {
+       threshold = ktime_add_ns(now, vcpu->arch.count_period / 4);
+       if (ktime_before(expires, threshold)) {
                /*
                 * Cancel it while we handle it so there's no chance of
                 * interference with the timeout handler.
@@ -329,8 +348,7 @@ static uint32_t kvm_mips_read_count_running(struct kvm_vcpu *vcpu, ktime_t now)
                }
        }
 
-       /* Return the biased and scaled guest CP0_Count */
-       return vcpu->arch.count_bias + kvm_mips_ktime_to_count(vcpu, now);
+       return count;
 }
 
 /**