hw/i386/mshv: migrate REFERENCE_TIME
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 <magnuskulke@linux.microsoft.com> Reviewed-by: Doru Blânzeanu <dblanzeanu@linux.microsoft.com> Link: https://lore.kernel.org/r/20260710101534.664604-4-magnuskulke@linux.microsoft.com Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Magnus Kulke committed
Jul 10, 2026 at 12:15 UTC
922c1a4aac5a02f2782153ff5702652c4b332ca6
7 files changed
+208
-58
MAINTAINERS
+1
@@ -620,6 +620,7 @@ R: Wei Liu <wei.liu@kernel.org>
620
R: Doru Blânzeanu <dblanzeanu@linux.microsoft.com>
621
S: Supported
622
F: target/i386/mshv/
623
+F: hw/i386/mshv/
624
625
X86 Instruction Emulator
626
M: Roman Bolshakov <rbolshakov@ddn.com>
accel/mshv/mshv-all.c
+9
-58
@@ -60,54 +60,6 @@ static int init_mshv(int *mshv_fd)
60
return 0;
61
}
62
63
-/* freeze 1 to pause, 0 to resume */
64
-static int set_time_freeze(int vm_fd, int freeze)
65
-{
66
- int ret;
67
- struct hv_input_set_partition_property in = {0};
68
- in.property_code = HV_PARTITION_PROPERTY_TIME_FREEZE;
69
- in.property_value = freeze;
70
-
71
- struct mshv_root_hvcall args = {0};
72
- args.code = HVCALL_SET_PARTITION_PROPERTY;
73
- args.in_sz = sizeof(in);
74
- args.in_ptr = (uint64_t)∈
75
-
76
- ret = mshv_hvcall(vm_fd, &args);
77
- if (ret < 0) {
78
- error_report("Failed to set time freeze");
79
- return -1;
80
- }
81
-
82
- return 0;
83
-}
84
-
85
-static int pause_vm(int vm_fd)
86
-{
87
- int ret;
88
-
89
- ret = set_time_freeze(vm_fd, 1);
90
- if (ret < 0) {
91
- error_report("Failed to pause partition: %s", strerror(errno));
92
- return -1;
93
- }
94
-
95
- return 0;
96
-}
97
-
98
-static int resume_vm(int vm_fd)
99
-{
100
- int ret;
101
-
102
- ret = set_time_freeze(vm_fd, 0);
103
- if (ret < 0) {
104
- error_report("Failed to resume partition: %s", strerror(errno));
105
- return -1;
106
- }
107
-
108
- return 0;
109
-}
110
-
63
static int get_host_partition_property(int mshv_fd, uint32_t property_code,
64
uint64_t *value)
65
{
@@ -330,9 +282,6 @@ static int create_vm(int mshv_fd, int *vm_fd)
282
return -1;
283
}
284
333
- /* Always create a frozen partition */
334
- pause_vm(*vm_fd);
335
-
285
return 0;
286
}
287
@@ -578,13 +527,6 @@ static int mshv_init(AccelState *as, MachineState *ms)
527
return -1;
528
}
529
581
- ret = resume_vm(vm_fd);
582
- if (ret < 0) {
583
- close(mshv_fd);
584
- close(vm_fd);
585
- return -1;
586
- }
587
-
530
s->vm = vm_fd;
531
s->fd = mshv_fd;
532
@@ -606,6 +548,8 @@ static int mshv_init(AccelState *as, MachineState *ms)
548
549
register_savevm_live("mshv", 0, 1, &savevm_mshv, s);
550
551
+ mshv_clock_init();
552
+
553
return 0;
554
}
555
@@ -643,6 +587,13 @@ static int mshv_cpu_exec(CPUState *cpu)
587
cpu->vcpu_dirty = false;
588
}
589
590
+ /* Corresponding store-release is in cpu_exit. */
591
+ if (qatomic_load_acquire(&cpu->exit_request)) {
592
+ trace_mshv_interrupt_exit_request(cpu->cpu_index);
593
+ ret = EXCP_INTERRUPT;
594
+ break;
595
+ }
596
+
597
ret = mshv_run_vcpu(mshv_state->vm, cpu, &mshv_msg, &exit_reason);
598
if (ret < 0) {
599
error_report("Failed to run on vcpu %d", cpu->cpu_index);
accel/mshv/trace-events
+1
@@ -4,6 +4,7 @@
4
# SPDX-License-Identifier: GPL-2.0-or-later
5
6
mshv_start_vcpu_thread(const char* thread, uint32_t cpu) "thread=%s cpu_index=%d"
7
+mshv_interrupt_exit_request(uint32_t cpu) "cpu_index=%d"
8
9
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"
10
mshv_mem_ioeventfd_add(uint64_t addr, uint32_t size, uint32_t data) "addr=0x%" PRIx64 " size=%d data=0x%x"
hw/i386/meson.build
+1
@@ -39,6 +39,7 @@ i386_ss.add(when: 'CONFIG_TDX', if_true: files('tdvf.c', 'tdvf-hob.c'))
39
40
subdir('kvm')
41
subdir('xen')
42
+subdir('mshv')
43
44
i386_ss.add_all(xenpv_ss)
45
hw/i386/mshv/clock.c
new
+189
@@ -0,0 +1,189 @@
1
+/*
2
+ * MSHV partition reference clock
3
+ *
4
+ * Copyright Microsoft, Corp. 2026
5
+ *
6
+ * Authors: Magnus Kulke <magnuskulke@microsoft.com>
7
+ *
8
+ * SPDX-License-Identifier: GPL-2.0-or-later
9
+ */
10
+
11
+#include "qemu/osdep.h"
12
+#include "qemu/error-report.h"
13
+#include "migration/vmstate.h"
14
+#include "system/runstate.h"
15
+#include "hw/hyperv/hvhdk.h"
16
+#include "hw/hyperv/hvhdk_mini.h"
17
+#include "hw/hyperv/hvgdk.h"
18
+#include "hw/hyperv/hvgdk_mini.h"
19
+#include "linux/mshv.h"
20
+#include "system/mshv.h"
21
+#include "system/mshv_int.h"
22
+
23
+/*
24
+ * Partition reference clock (HV_PARTITION_PROPERTY_REFERENCE_TIME), captured
25
+ * when the VM is stopped and re-applied when it resumes.
26
+ *
27
+ * Mirrors hw/i386/kvm/clock.c.
28
+ */
29
+typedef struct MshvClockState {
30
+ uint64_t ref_time;
31
+ bool ref_time_pending;
32
+} MshvClockState;
33
+
34
+static MshvClockState mshv_clock;
35
+
36
+static int mshv_get_reference_time(int vm_fd, uint64_t *ref_time)
37
+{
38
+ struct hv_input_get_partition_property in = { 0 };
39
+ struct hv_output_get_partition_property out = { 0 };
40
+ struct mshv_root_hvcall args = { 0 };
41
+ int ret;
42
+
43
+ in.property_code = HV_PARTITION_PROPERTY_REFERENCE_TIME;
44
+
45
+ args.code = HVCALL_GET_PARTITION_PROPERTY;
46
+ args.in_sz = sizeof(in);
47
+ args.in_ptr = (uint64_t)∈
48
+ args.out_sz = sizeof(out);
49
+ args.out_ptr = (uint64_t)&out;
50
+
51
+ ret = mshv_hvcall(vm_fd, &args);
52
+ if (ret < 0) {
53
+ error_report("Failed to get reference time");
54
+ return -1;
55
+ }
56
+
57
+ *ref_time = out.property_value;
58
+ return 0;
59
+}
60
+
61
+static int mshv_set_reference_time(int vm_fd, uint64_t ref_time)
62
+{
63
+ struct hv_input_set_partition_property in = { 0 };
64
+ struct mshv_root_hvcall args = { 0 };
65
+ int ret;
66
+
67
+ in.property_code = HV_PARTITION_PROPERTY_REFERENCE_TIME;
68
+ in.property_value = ref_time;
69
+
70
+ args.code = HVCALL_SET_PARTITION_PROPERTY;
71
+ args.in_sz = sizeof(in);
72
+ args.in_ptr = (uint64_t)∈
73
+
74
+ ret = mshv_hvcall(vm_fd, &args);
75
+ if (ret < 0) {
76
+ error_report("Failed to set reference time");
77
+ return -1;
78
+ }
79
+
80
+ return 0;
81
+}
82
+
83
+/*
84
+ * Freeze (freeze=1) or unfreeze (freeze=0) time for a partition. This will not
85
+ * pause/eject vCPU execution. It is assumed that the caller already has stopped
86
+ * the partition's vCPUs.
87
+ *
88
+ * NB: a partition's reference clock can only be written while the time is
89
+ * frozen.
90
+ */
91
+static int mshv_set_time_freeze(int vm_fd, int freeze)
92
+{
93
+ struct hv_input_set_partition_property in = { 0 };
94
+ struct mshv_root_hvcall args = { 0 };
95
+ int ret;
96
+
97
+ in.property_code = HV_PARTITION_PROPERTY_TIME_FREEZE;
98
+ in.property_value = freeze;
99
+
100
+ args.code = HVCALL_SET_PARTITION_PROPERTY;
101
+ args.in_sz = sizeof(in);
102
+ args.in_ptr = (uint64_t)∈
103
+
104
+ ret = mshv_hvcall(vm_fd, &args);
105
+ if (ret < 0) {
106
+ error_report("Failed to set time freeze");
107
+ return -1;
108
+ }
109
+
110
+ return 0;
111
+}
112
+
113
+static void mshv_clock_vm_state_change(void *opaque, bool running,
114
+ RunState state)
115
+{
116
+ MshvClockState *s = opaque;
117
+ int vm_fd = mshv_state->vm;
118
+ int ret;
119
+
120
+ if (running) {
121
+ /* Skip if we have nothing to restore, e.g. on initial boot. */
122
+ if (!s->ref_time_pending) {
123
+ return;
124
+ }
125
+
126
+ ret = mshv_set_time_freeze(vm_fd, 1);
127
+ if (ret < 0) {
128
+ error_report("Failed to freeze partition time on resume");
129
+ abort();
130
+ }
131
+
132
+ /* INVARIANT: reference time can only be written if time is frozen. */
133
+ ret = mshv_set_reference_time(vm_fd, s->ref_time);
134
+ if (ret < 0) {
135
+ error_report("Failed to restore reference time on resume");
136
+ abort();
137
+ }
138
+
139
+ if (mshv_set_time_freeze(vm_fd, 0) < 0) {
140
+ error_report("Failed to unfreeze partition time on resume");
141
+ abort();
142
+ }
143
+
144
+ s->ref_time_pending = false;
145
+ } else {
146
+ /* Skip if we already have a to-be-set ref time */
147
+ if (s->ref_time_pending) {
148
+ return;
149
+ }
150
+
151
+ ret = mshv_get_reference_time(vm_fd, &s->ref_time);
152
+ if (ret < 0) {
153
+ error_report("Failed to capture reference time on stop");
154
+ abort();
155
+ }
156
+
157
+ s->ref_time_pending = true;
158
+ }
159
+}
160
+
161
+/*
162
+ * The incoming reference time should be applied on the next resume before
163
+ * vCPUs start executing.
164
+ */
165
+static int mshv_clock_post_load(void *opaque, int version_id)
166
+{
167
+ MshvClockState *s = opaque;
168
+
169
+ s->ref_time_pending = true;
170
+
171
+ return 0;
172
+}
173
+
174
+static const VMStateDescription vmstate_mshv_clock = {
175
+ .name = "mshv-clock",
176
+ .version_id = 1,
177
+ .minimum_version_id = 1,
178
+ .post_load = mshv_clock_post_load,
179
+ .fields = (const VMStateField[]) {
180
+ VMSTATE_UINT64(ref_time, MshvClockState),
181
+ VMSTATE_END_OF_LIST()
182
+ },
183
+};
184
+
185
+void mshv_clock_init(void)
186
+{
187
+ vmstate_register(NULL, 0, &vmstate_mshv_clock, &mshv_clock);
188
+ qemu_add_vm_change_state_handler(mshv_clock_vm_state_change, &mshv_clock);
189
+}
hw/i386/mshv/meson.build
new
+4
@@ -0,0 +1,4 @@
1
+i386_mshv_ss = ss.source_set()
2
+i386_mshv_ss.add(files('clock.c'))
3
+
4
+i386_ss.add_all(when: 'CONFIG_MSHV', if_true: i386_mshv_ss)
include/system/mshv.h
+3
@@ -55,6 +55,9 @@ DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE,
55
56
extern MshvState *mshv_state;
57
58
+/* clock (partition reference time) */
59
+void mshv_clock_init(void);
60
+
61
/* interrupt */
62
int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint32_t vector,
63
uint32_t vp_index, bool logical_destination_mode,