From: David Woodhouse <[email protected]>

When kvm_xen_update_runstate() is invoked to set a vCPU's runstate, the
time spent in the previous runstate is accounted. This is based on the
delta between the current KVM clock time, and the previous value stored
in vcpu->arch.xen.runstate_entry_time.

If the KVM clock goes backwards, that delta will be negative. Or, since
it's an unsigned 64-bit integer, very *large*. Linux guests deal with
that particularly badly, reporting 100% steal time for ever more (well,
for *centuries* at least, until the delta has been consumed).

So when a negative delta is detected, just refrain from updating the
runstate times until the KVM clock catches up with runstate_entry_time
again.

Also clamp steal_ns to delta_ns to prevent steal time from exceeding
the total elapsed time, and handle negative steal_ns (which can happen
if run_delay goes backwards across a scheduler update).

The userspace APIs for setting the runstate times do not allow them to
be set past the current KVM clock, but userspace can still adjust the
KVM clock *after* setting the runstate times, which would cause this
situation to occur.

Signed-off-by: David Woodhouse <[email protected]>
Reviewed-by: Paul Durrant <[email protected]>
---
 arch/x86/kvm/xen.c | 28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 82e34edbfdbd..b1d67ece5db3 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -586,29 +586,45 @@ void kvm_xen_update_runstate(struct kvm_vcpu *v, int 
state)
 {
        struct kvm_vcpu_xen *vx = &v->arch.xen;
        u64 now = get_kvmclock_ns(v->kvm);
-       u64 delta_ns = now - vx->runstate_entry_time;
        u64 run_delay = current->sched_info.run_delay;
+       s64 delta_ns = now - vx->runstate_entry_time;
+       s64 steal_ns = run_delay - vx->last_steal;
 
+       /*
+        * If the vCPU was never run before, its prior state should
+        * be considered RUNSTATE_offline.
+        */
        if (unlikely(!vx->runstate_entry_time))
                vx->current_runstate = RUNSTATE_offline;
 
+       /*
+        * If KVM clock went backwards, just update the current runstate
+        * but don't account any time. Leave entry_time unchanged so the
+        * next positive delta covers the full period once the clock
+        * catches up. Update last_steal every time so stolen time only
+        * reflects the interval since the most recent call.
+        */
+       if (delta_ns < 0)
+               goto update_guest;
+
        /*
         * Time waiting for the scheduler isn't "stolen" if the
         * vCPU wasn't running anyway.
         */
-       if (vx->current_runstate == RUNSTATE_running) {
-               u64 steal_ns = run_delay - vx->last_steal;
+       if (vx->current_runstate == RUNSTATE_running && steal_ns > 0) {
+               if (steal_ns > delta_ns)
+                       steal_ns = delta_ns;
 
                delta_ns -= steal_ns;
-
                vx->runstate_times[RUNSTATE_runnable] += steal_ns;
        }
-       vx->last_steal = run_delay;
 
        vx->runstate_times[vx->current_runstate] += delta_ns;
-       vx->current_runstate = state;
        vx->runstate_entry_time = now;
 
+ update_guest:
+       vx->current_runstate = state;
+       vx->last_steal = run_delay;
        if (vx->runstate_cache.active)
                kvm_xen_update_runstate_guest(v, state == RUNSTATE_runnable);
 }
-- 
2.54.0


Reply via email to