This is a partition-wide state for which we use a dedicated hw clock
facility, similar to KVM. We have to freeze the time for a partition
before we are allowed to set it. We register a state change handler for
the clock device and a post-load handler for migration state. In the
post-load handler we toggle a flag that will set the reference time
state on next state to "running" on the partition.

We can move the time freeze and reference-time ioctls/hvcalls to the
clock module.

Signed-off-by: Magnus Kulke <[email protected]>
---
 MAINTAINERS              |   1 +
 accel/mshv/mshv-all.c    |  67 ++------------
 accel/mshv/trace-events  |   1 +
 hw/i386/meson.build      |   1 +
 hw/i386/mshv/clock.c     | 189 +++++++++++++++++++++++++++++++++++++++
 hw/i386/mshv/meson.build |   4 +
 include/system/mshv.h    |   3 +
 7 files changed, 208 insertions(+), 58 deletions(-)
 create mode 100644 hw/i386/mshv/clock.c
 create mode 100644 hw/i386/mshv/meson.build

diff --git a/MAINTAINERS b/MAINTAINERS
index 6171cc7494..269876db3e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -613,6 +613,7 @@ R: Wei Liu <[email protected]>
 R: Doru Blânzeanu <[email protected]>
 S: Supported
 F: target/i386/mshv/
+F: hw/i386/mshv/
 
 X86 Instruction Emulator
 M: Roman Bolshakov <[email protected]>
diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c
index b34fb34d05..7b3c13cf1e 100644
--- a/accel/mshv/mshv-all.c
+++ b/accel/mshv/mshv-all.c
@@ -60,54 +60,6 @@ static int init_mshv(int *mshv_fd)
     return 0;
 }
 
-/* freeze 1 to pause, 0 to resume */
-static int set_time_freeze(int vm_fd, int freeze)
-{
-    int ret;
-    struct hv_input_set_partition_property in = {0};
-    in.property_code = HV_PARTITION_PROPERTY_TIME_FREEZE;
-    in.property_value = freeze;
-
-    struct mshv_root_hvcall args = {0};
-    args.code = HVCALL_SET_PARTITION_PROPERTY;
-    args.in_sz = sizeof(in);
-    args.in_ptr = (uint64_t)&in;
-
-    ret = mshv_hvcall(vm_fd, &args);
-    if (ret < 0) {
-        error_report("Failed to set time freeze");
-        return -1;
-    }
-
-    return 0;
-}
-
-static int pause_vm(int vm_fd)
-{
-    int ret;
-
-    ret = set_time_freeze(vm_fd, 1);
-    if (ret < 0) {
-        error_report("Failed to pause partition: %s", strerror(errno));
-        return -1;
-    }
-
-    return 0;
-}
-
-static int resume_vm(int vm_fd)
-{
-    int ret;
-
-    ret = set_time_freeze(vm_fd, 0);
-    if (ret < 0) {
-        error_report("Failed to resume partition: %s", strerror(errno));
-        return -1;
-    }
-
-    return 0;
-}
-
 static int get_host_partition_property(int mshv_fd, uint32_t property_code,
                                        uint64_t *value)
 {
@@ -330,9 +282,6 @@ static int create_vm(int mshv_fd, int *vm_fd)
         return -1;
     }
 
-    /* Always create a frozen partition */
-    pause_vm(*vm_fd);
-
     return 0;
 }
 
@@ -578,13 +527,6 @@ static int mshv_init(AccelState *as, MachineState *ms)
         return -1;
     }
 
-    ret = resume_vm(vm_fd);
-    if (ret < 0) {
-        close(mshv_fd);
-        close(vm_fd);
-        return -1;
-    }
-
     s->vm = vm_fd;
     s->fd = mshv_fd;
 
@@ -606,6 +548,8 @@ static int mshv_init(AccelState *as, MachineState *ms)
 
     register_savevm_live("mshv", 0, 1, &savevm_mshv, s);
 
+    mshv_clock_init();
+
     return 0;
 }
 
@@ -643,6 +587,13 @@ static int mshv_cpu_exec(CPUState *cpu)
             cpu->vcpu_dirty = false;
         }
 
+        /* Corresponding store-release is in cpu_exit. */
+        if (qatomic_load_acquire(&cpu->exit_request)) {
+            trace_mshv_interrupt_exit_request(cpu->cpu_index);
+            ret = EXCP_INTERRUPT;
+            break;
+        }
+
         ret = mshv_run_vcpu(mshv_state->vm, cpu, &mshv_msg, &exit_reason);
         if (ret < 0) {
             error_report("Failed to run on vcpu %d", cpu->cpu_index);
diff --git a/accel/mshv/trace-events b/accel/mshv/trace-events
index a4dffeb24a..859e8bfb0f 100644
--- a/accel/mshv/trace-events
+++ b/accel/mshv/trace-events
@@ -4,6 +4,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 mshv_start_vcpu_thread(const char* thread, uint32_t cpu) "thread=%s 
cpu_index=%d"
+mshv_interrupt_exit_request(uint32_t cpu) "cpu_index=%d"
 
 mshv_set_memory(bool add, uint64_t gpa, uint64_t size, uint64_t user_addr, 
bool readonly, int ret) "add=%d gpa=0x%" PRIx64 " size=0x%" PRIx64 " user=0x%" 
PRIx64 " readonly=%d result=%d"
 mshv_mem_ioeventfd_add(uint64_t addr, uint32_t size, uint32_t data) "addr=0x%" 
PRIx64 " size=%d data=0x%x"
diff --git a/hw/i386/meson.build b/hw/i386/meson.build
index b611fbb5a7..f4d3122863 100644
--- a/hw/i386/meson.build
+++ b/hw/i386/meson.build
@@ -39,6 +39,7 @@ i386_ss.add(when: 'CONFIG_TDX', if_true: files('tdvf.c', 
'tdvf-hob.c'))
 
 subdir('kvm')
 subdir('xen')
+subdir('mshv')
 
 i386_ss.add_all(xenpv_ss)
 
diff --git a/hw/i386/mshv/clock.c b/hw/i386/mshv/clock.c
new file mode 100644
index 0000000000..02c586503a
--- /dev/null
+++ b/hw/i386/mshv/clock.c
@@ -0,0 +1,189 @@
+/*
+ * MSHV partition reference clock
+ *
+ * Copyright Microsoft, Corp. 2026
+ *
+ * Authors: Magnus Kulke <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "migration/vmstate.h"
+#include "system/runstate.h"
+#include "hw/hyperv/hvhdk.h"
+#include "hw/hyperv/hvhdk_mini.h"
+#include "hw/hyperv/hvgdk.h"
+#include "hw/hyperv/hvgdk_mini.h"
+#include "linux/mshv.h"
+#include "system/mshv.h"
+#include "system/mshv_int.h"
+
+/*
+ * Partition reference clock (HV_PARTITION_PROPERTY_REFERENCE_TIME), captured
+ * when the VM is stopped and re-applied when it resumes.
+ *
+ * Mirrors hw/i386/kvm/clock.c.
+ */
+typedef struct MshvClockState {
+    uint64_t ref_time;
+    bool ref_time_pending;
+} MshvClockState;
+
+static MshvClockState mshv_clock;
+
+static int mshv_get_reference_time(int vm_fd, uint64_t *ref_time)
+{
+    struct hv_input_get_partition_property in = { 0 };
+    struct hv_output_get_partition_property out = { 0 };
+    struct mshv_root_hvcall args = { 0 };
+    int ret;
+
+    in.property_code = HV_PARTITION_PROPERTY_REFERENCE_TIME;
+
+    args.code        = HVCALL_GET_PARTITION_PROPERTY;
+    args.in_sz       = sizeof(in);
+    args.in_ptr      = (uint64_t)&in;
+    args.out_sz      = sizeof(out);
+    args.out_ptr     = (uint64_t)&out;
+
+    ret = mshv_hvcall(vm_fd, &args);
+    if (ret < 0) {
+        error_report("Failed to get reference time");
+        return -1;
+    }
+
+    *ref_time = out.property_value;
+    return 0;
+}
+
+static int mshv_set_reference_time(int vm_fd, uint64_t ref_time)
+{
+    struct hv_input_set_partition_property in = { 0 };
+    struct mshv_root_hvcall args = { 0 };
+    int ret;
+
+    in.property_code  = HV_PARTITION_PROPERTY_REFERENCE_TIME;
+    in.property_value = ref_time;
+
+    args.code         = HVCALL_SET_PARTITION_PROPERTY;
+    args.in_sz        = sizeof(in);
+    args.in_ptr       = (uint64_t)&in;
+
+    ret = mshv_hvcall(vm_fd, &args);
+    if (ret < 0) {
+        error_report("Failed to set reference time");
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Freeze (freeze=1) or unfreeze (freeze=0) time for a partition. This will not
+ * pause/eject vCPU execution. It is assumed that the caller already has 
stopped
+ * the partition's vCPUs.
+ *
+ * NB: a partition's reference clock can only be written while the time is
+ * frozen.
+ */
+static int mshv_set_time_freeze(int vm_fd, int freeze)
+{
+    struct hv_input_set_partition_property in = { 0 };
+    struct mshv_root_hvcall args = { 0 };
+    int ret;
+
+    in.property_code  = HV_PARTITION_PROPERTY_TIME_FREEZE;
+    in.property_value = freeze;
+
+    args.code         = HVCALL_SET_PARTITION_PROPERTY;
+    args.in_sz        = sizeof(in);
+    args.in_ptr       = (uint64_t)&in;
+
+    ret = mshv_hvcall(vm_fd, &args);
+    if (ret < 0) {
+        error_report("Failed to set time freeze");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void mshv_clock_vm_state_change(void *opaque, bool running,
+                                       RunState state)
+{
+    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;
+        }
+
+        ret = mshv_set_time_freeze(vm_fd, 1);
+        if (ret < 0) {
+            error_report("Failed to freeze partition time on resume");
+            abort();
+        }
+
+        /* 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();
+        }
+
+        if (mshv_set_time_freeze(vm_fd, 0) < 0) {
+            error_report("Failed to unfreeze partition time on resume");
+            abort();
+        }
+
+        s->ref_time_pending = false;
+    } else {
+        /* 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;
+    }
+}
+
+/*
+ * The incoming reference time should be applied on the next resume before
+ * vCPUs start executing.
+ */
+static int mshv_clock_post_load(void *opaque, int version_id)
+{
+    MshvClockState *s = opaque;
+
+    s->ref_time_pending = true;
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_mshv_clock = {
+    .name = "mshv-clock",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = mshv_clock_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT64(ref_time, MshvClockState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+void mshv_clock_init(void)
+{
+    vmstate_register(NULL, 0, &vmstate_mshv_clock, &mshv_clock);
+    qemu_add_vm_change_state_handler(mshv_clock_vm_state_change, &mshv_clock);
+}
diff --git a/hw/i386/mshv/meson.build b/hw/i386/mshv/meson.build
new file mode 100644
index 0000000000..0e556851b6
--- /dev/null
+++ b/hw/i386/mshv/meson.build
@@ -0,0 +1,4 @@
+i386_mshv_ss = ss.source_set()
+i386_mshv_ss.add(files('clock.c'))
+
+i386_ss.add_all(when: 'CONFIG_MSHV', if_true: i386_mshv_ss)
diff --git a/include/system/mshv.h b/include/system/mshv.h
index 46fe3fcebc..a6f815b822 100644
--- a/include/system/mshv.h
+++ b/include/system/mshv.h
@@ -55,6 +55,9 @@ DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE,
 
 extern MshvState *mshv_state;
 
+/* clock (partition reference time) */
+void mshv_clock_init(void);
+
 /* interrupt */
 int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, 
uint32_t vector,
                            uint32_t vp_index, bool logical_destination_mode,
-- 
2.34.1


Reply via email to