@samitouri / QOSamiQemu / commits / 922c1a4aac

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)&in;
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)&in;
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)&in;
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)&in;
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,