Commit faf92218ec for qemu.org

commit faf92218ec4b643b63e1ebbdc11b201afee1eee2
Author: Magnus Kulke <magnuskulke@linux.microsoft.com>
Date:   Fri Nov 6 15:39:28 2026 +0100

    accel/mshv: freeze time during TSC MSR write

    A regression was introduced when the HV_X64_REGISTER_TSC MSR state is
    being written to the hypervisor as part of the migration framework. If
    the MSR is written without the time being frozen, the tsc_page (a
    hyper-v enlightenment) is zeroed out and cannot be used by a guest,
    which results in a performance regression, because the guest falls back
    to costly reads of HV_X64_MSR_TIME_REF_COUNT.

    An implementation of pre_resume_vm hook was added to handle the clock
    lifecycle, relying on the dirty_vcpu state to avoid redundant state
    writes.

    Fixes: d540dcc5e28c ("target/i386/mshv: migrate MSRs")

    Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com>
    Reviewed-by: Doru Blânzeanu <dblanzeanu@linux.microsoft.com>

diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c
index be3abb0824..5519768cc2 100644
--- a/accel/mshv/mshv-all.c
+++ b/accel/mshv/mshv-all.c
@@ -34,6 +34,7 @@
 #include "accel/accel-cpu-ops.h"
 #include "exec/cpu-common.h"
 #include "system/cpus.h"
+#include "system/hw_accel.h"
 #include "system/runstate.h"
 #include "system/accel-blocker.h"
 #include "system/address-spaces.h"
@@ -579,7 +580,10 @@ static int mshv_init(AccelState *as, MachineState *ms)

     register_savevm_live("mshv", 0, 1, &savevm_mshv, s);

-    mshv_clock_init();
+    ret = mshv_clock_init(vm_fd);
+    if (ret < 0) {
+        return -1;
+    }

     return 0;
 }
@@ -928,6 +932,32 @@ static void mshv_cpu_synchronize_pre_loadvm(CPUState *cpu)
     run_on_cpu(cpu, do_mshv_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL);
 }

+static void mshv_pre_resume_vm(AccelState *as, bool step_pending)
+{
+    MshvState *s = MSHV_STATE(as);
+    CPUState *cpu;
+    int ret, vm_fd = s->vm;
+
+    ret = mshv_clock_prepare_resume(vm_fd);
+    if (ret < 0) {
+        error_report("Failed to prepare clock for resume");
+        abort();
+    }
+
+    CPU_FOREACH(cpu) {
+        if (!cpu->vcpu_dirty) {
+            continue;
+        }
+        cpu_synchronize_post_init(cpu);
+    }
+
+    ret = mshv_clock_finish_resume(vm_fd);
+    if (ret < 0) {
+        error_report("Failed to finish clock for resume");
+        abort();
+    }
+}
+
 static void do_mshv_cpu_synchronize(CPUState *cpu, run_on_cpu_data arg)
 {
     if (!cpu->vcpu_dirty) {
@@ -963,6 +993,7 @@ static void mshv_accel_class_init(ObjectClass *oc, const void *data)
     ac->name = "MSHV";
     ac->init_machine = mshv_init;
     ac->allowed = &mshv_allowed;
+    ac->pre_resume_vm = mshv_pre_resume_vm;
 }

 static void mshv_accel_instance_init(Object *obj)
diff --git a/hw/i386/mshv/clock.c b/hw/i386/mshv/clock.c
index 02c586503a..50c9aa6417 100644
--- a/hw/i386/mshv/clock.c
+++ b/hw/i386/mshv/clock.c
@@ -29,6 +29,7 @@
 typedef struct MshvClockState {
     uint64_t ref_time;
     bool ref_time_pending;
+    bool time_frozen;
 } MshvClockState;

 static MshvClockState mshv_clock;
@@ -110,52 +111,88 @@ static int mshv_set_time_freeze(int vm_fd, int freeze)
     return 0;
 }

-static void mshv_clock_vm_state_change(void *opaque, bool running,
-                                       RunState state)
+static int ensure_time_frozen(int vm_fd)
 {
-    MshvClockState *s = opaque;
-    int vm_fd = mshv_state->vm;
     int ret;

-    if (running) {
-        /* Skip if we have nothing to restore, e.g. on initial boot. */
-        if (!s->ref_time_pending) {
-            return;
-        }
+    /* Ensure time is frozen */
+    if (mshv_clock.time_frozen) {
+        return 0;
+    }

-        ret = mshv_set_time_freeze(vm_fd, 1);
-        if (ret < 0) {
-            error_report("Failed to freeze partition time on resume");
-            abort();
-        }
+    ret = mshv_set_time_freeze(vm_fd, 1);
+    if (ret < 0) {
+        error_report("Failed to freeze partition");
+        return -1;
+    }
+    mshv_clock.time_frozen = true;
+
+    return 0;
+}

+int mshv_clock_prepare_resume(int vm_fd)
+{
+    return ensure_time_frozen(vm_fd);
+}
+
+int mshv_clock_finish_resume(int vm_fd)
+{
+    int ret;
+    MshvClockState *s = &mshv_clock;
+
+    assert(mshv_clock.time_frozen);
+
+    /* Skip if we have nothing to restore, e.g. on initial boot. */
+    if (s->ref_time_pending) {
         /* INVARIANT: reference time can only be written if time is frozen. */
         ret = mshv_set_reference_time(vm_fd, s->ref_time);
         if (ret < 0) {
             error_report("Failed to restore reference time on resume");
-            abort();
+            return -1;
         }
+        s->ref_time_pending = false;
+    }

-        if (mshv_set_time_freeze(vm_fd, 0) < 0) {
-            error_report("Failed to unfreeze partition time on resume");
-            abort();
-        }
+    ret = mshv_set_time_freeze(vm_fd, 0);
+    if (ret < 0) {
+        error_report("Failed to thaw partition");
+        return -1;
+    }
+    mshv_clock.time_frozen = false;

-        s->ref_time_pending = false;
-    } else {
-        /* Skip if we already have a to-be-set ref time */
-        if (s->ref_time_pending) {
-            return;
-        }
+    return 0;
+}

-        ret = mshv_get_reference_time(vm_fd, &s->ref_time);
-        if (ret < 0) {
-            error_report("Failed to capture reference time on stop");
-            abort();
-        }
+static void mshv_clock_vm_state_change(void *opaque, bool running,
+                                       RunState state)
+{
+    MshvClockState *s = opaque;
+    int vm_fd = mshv_state->vm;
+    int ret;

-        s->ref_time_pending = true;
+    if (running) {
+        return;
     }
+
+    /* Ensure the time if frozen before we capture the reference time. */
+    ret = ensure_time_frozen(vm_fd);
+    if (ret < 0) {
+        error_report("Failed to freeze partition time on stop");
+        abort();
+    }
+
+    /* Skip if we already have a to-be-set ref time */
+    if (s->ref_time_pending) {
+        return;
+    }
+
+    ret = mshv_get_reference_time(vm_fd, &s->ref_time);
+    if (ret < 0) {
+        error_report("Failed to capture reference time on stop");
+        abort();
+    }
+
+    s->ref_time_pending = true;
 }

 /*
@@ -182,8 +219,19 @@ static const VMStateDescription vmstate_mshv_clock = {
     },
 };

-void mshv_clock_init(void)
+int mshv_clock_init(int vm_fd)
 {
+    int ret;
+
+    ret = mshv_set_time_freeze(vm_fd, 1);
+    if (ret < 0) {
+        error_report("Failed to freeze partition time on init");
+        return -1;
+    }
+    mshv_clock.time_frozen = true;
+
     vmstate_register(NULL, 0, &vmstate_mshv_clock, &mshv_clock);
     qemu_add_vm_change_state_handler(mshv_clock_vm_state_change, &mshv_clock);
+
+    return 0;
 }
diff --git a/include/system/mshv.h b/include/system/mshv.h
index f98ddff9b5..864f2aa34d 100644
--- a/include/system/mshv.h
+++ b/include/system/mshv.h
@@ -58,7 +58,9 @@ DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE,
 extern MshvState *mshv_state;

 /* clock (partition reference time) */
-void mshv_clock_init(void);
+int mshv_clock_init(int vm_fd);
+int mshv_clock_prepare_resume(int vm_fd);
+int mshv_clock_finish_resume(int vm_fd);

 /* interrupt */
 int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint32_t vector,