master
c 189 lines 4.86 KB
Raw
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 }