master
c 424 lines 11.6 KB
Raw
1 /*
2 * QEMU MSHV support
3 *
4 * Copyright Microsoft, Corp. 2025
5 *
6 * Authors:
7 * Magnus Kulke <magnuskulke@microsoft.com>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 *
11 */
12
13 #include "qemu/osdep.h"
14 #include "qemu/error-report.h"
15 #include "qapi/error.h"
16 #include "linux/mshv.h"
17 #include "system/address-spaces.h"
18 #include "system/mshv.h"
19 #include "system/mshv_int.h"
20 #include "hw/hyperv/hvhdk_mini.h"
21 #include "system/physmem.h"
22 #include "exec/memattrs.h"
23 #include <sys/ioctl.h>
24 #include "trace.h"
25
26 static int set_guest_memory(int vm_fd,
27 const struct mshv_user_mem_region *region)
28 {
29 int ret;
30
31 ret = ioctl(vm_fd, MSHV_SET_GUEST_MEMORY, region);
32 if (ret < 0) {
33 error_report("failed to set guest memory");
34 return -errno;
35 }
36
37 return 0;
38 }
39
40 static int map_or_unmap(int vm_fd, const MshvMemoryRegion *mr, bool map)
41 {
42 struct mshv_user_mem_region region = {0};
43
44 region.guest_pfn = mr->guest_phys_addr >> MSHV_PAGE_SHIFT;
45 region.size = mr->memory_size;
46 region.userspace_addr = mr->userspace_addr;
47
48 if (!map) {
49 region.flags |= (1 << MSHV_SET_MEM_BIT_UNMAP);
50 trace_mshv_unmap_memory(mr->userspace_addr, mr->guest_phys_addr,
51 mr->memory_size);
52 return set_guest_memory(vm_fd, &region);
53 }
54
55 region.flags = BIT(MSHV_SET_MEM_BIT_EXECUTABLE);
56 if (!mr->readonly) {
57 region.flags |= BIT(MSHV_SET_MEM_BIT_WRITABLE);
58 }
59
60 trace_mshv_map_memory(mr->userspace_addr, mr->guest_phys_addr,
61 mr->memory_size);
62 return set_guest_memory(vm_fd, &region);
63 }
64
65 static int handle_unmapped_mmio_region_read(uint64_t gpa, uint64_t size,
66 uint8_t *data)
67 {
68 warn_report("read from unmapped mmio region gpa=0x%lx size=%lu", gpa, size);
69
70 if (size == 0 || size > 8) {
71 error_report("invalid size %lu for reading from unmapped mmio region",
72 size);
73 return -1;
74 }
75
76 memset(data, 0xFF, size);
77
78 return 0;
79 }
80
81 int mshv_guest_mem_read(uint64_t gpa, uint8_t *data, uintptr_t size,
82 bool is_secure_mode, bool instruction_fetch)
83 {
84 int ret;
85 MemTxAttrs memattr = { .secure = is_secure_mode };
86
87 if (instruction_fetch) {
88 trace_mshv_insn_fetch(gpa, size);
89 } else {
90 trace_mshv_mem_read(gpa, size);
91 }
92
93 ret = address_space_rw(&address_space_memory, gpa, memattr, (void *)data,
94 size, false);
95 if (ret == MEMTX_OK) {
96 return 0;
97 }
98
99 if (ret == MEMTX_DECODE_ERROR) {
100 return handle_unmapped_mmio_region_read(gpa, size, data);
101 }
102
103 error_report("failed to read guest memory at 0x%lx", gpa);
104 return -1;
105 }
106
107 int mshv_guest_mem_write(uint64_t gpa, const uint8_t *data, uintptr_t size,
108 bool is_secure_mode)
109 {
110 int ret;
111 MemTxAttrs memattr = { .secure = is_secure_mode };
112
113 trace_mshv_mem_write(gpa, size);
114 ret = address_space_rw(&address_space_memory, gpa, memattr, (void *)data,
115 size, true);
116 if (ret == MEMTX_OK) {
117 return 0;
118 }
119
120 if (ret == MEMTX_DECODE_ERROR) {
121 warn_report("write to unmapped mmio region gpa=0x%lx size=%lu", gpa,
122 size);
123 return 0;
124 }
125
126 error_report("Failed to write guest memory");
127 return -1;
128 }
129
130 static int set_memory(const MshvMemoryRegion *mshv_mr, bool add)
131 {
132 int ret = 0;
133
134 if (!mshv_mr) {
135 error_report("Invalid mshv_mr");
136 return -1;
137 }
138
139 trace_mshv_set_memory(add, mshv_mr->guest_phys_addr,
140 mshv_mr->memory_size,
141 mshv_mr->userspace_addr, mshv_mr->readonly,
142 ret);
143 return map_or_unmap(mshv_state->vm, mshv_mr, add);
144 }
145
146 /*
147 * Calculate and align the start address and the size of the section.
148 * Return the size. If the size is 0, the aligned section is empty.
149 */
150 static hwaddr align_section(MemoryRegionSection *section, hwaddr *start)
151 {
152 hwaddr size = int128_get64(section->size);
153 hwaddr delta, aligned;
154
155 /*
156 * works in page size chunks, but the function may be called
157 * with sub-page size and unaligned start address. Pad the start
158 * address to next and truncate size to previous page boundary.
159 */
160 aligned = ROUND_UP(section->offset_within_address_space,
161 qemu_real_host_page_size());
162 delta = aligned - section->offset_within_address_space;
163 *start = aligned;
164 if (delta > size) {
165 return 0;
166 }
167
168 return (size - delta) & qemu_real_host_page_mask();
169 }
170
171 void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section,
172 bool add)
173 {
174 int ret = 0;
175 MemoryRegion *area = section->mr;
176 bool writable = !area->readonly && !area->rom_device;
177 hwaddr start_addr, mr_offset, size;
178 void *ram;
179 MshvMemoryRegion mshv_mr = {0};
180
181 size = align_section(section, &start_addr);
182 trace_mshv_set_phys_mem(add, section->mr->name, start_addr);
183
184 /*
185 * If the memory device is a writable non-ram area, we do not
186 * want to map it into the guest memory. If it is not a ROM device,
187 * we want to remove mshv memory mapping, so accesses will trap.
188 */
189 if (!memory_region_is_ram(area)) {
190 if (writable) {
191 return;
192 } else if (!area->romd_mode) {
193 add = false;
194 }
195 }
196
197 if (!size) {
198 return;
199 }
200
201 mr_offset = section->offset_within_region + start_addr -
202 section->offset_within_address_space;
203
204 ram = memory_region_get_ram_ptr(area) + mr_offset;
205
206 mshv_mr.guest_phys_addr = start_addr;
207 mshv_mr.memory_size = size;
208 mshv_mr.readonly = !writable;
209 mshv_mr.userspace_addr = (uint64_t)ram;
210
211 ret = set_memory(&mshv_mr, add);
212 if (ret < 0) {
213 error_report("Failed to set memory region");
214 abort();
215 }
216 }
217
218 static int enable_dirty_page_tracking(int vm_fd)
219 {
220 int ret;
221 struct hv_input_set_partition_property in = {0};
222 struct mshv_root_hvcall args = {0};
223
224 in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING;
225 in.property_value = 1;
226
227 args.code = HVCALL_SET_PARTITION_PROPERTY;
228 args.in_sz = sizeof(in);
229 args.in_ptr = (uint64_t)&in;
230
231 ret = mshv_hvcall(vm_fd, &args);
232 if (ret < 0) {
233 error_report("Failed to enable dirty page tracking: %s",
234 strerror(errno));
235 return -1;
236 }
237
238 return 0;
239 }
240
241 /*
242 * Retrieve dirty page bitmap for a GPA range, clearing the dirty bits
243 * atomically. Large ranges are handled in batches.
244 */
245 static int get_dirty_log(int vm_fd, uint64_t base_pfn, uint64_t page_count,
246 unsigned long *bitmap, size_t bitmap_size)
247 {
248 uint64_t batch, bitmap_offset, completed = 0;
249 struct mshv_gpap_access_bitmap args = {0};
250 int ret;
251
252 QEMU_BUILD_BUG_ON(MSHV_DIRTY_PAGES_BATCH_SIZE % BITS_PER_LONG != 0);
253 assert(bitmap_size >= ROUND_UP(page_count, BITS_PER_LONG) / 8);
254
255 while (completed < page_count) {
256 batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed);
257 bitmap_offset = completed / BITS_PER_LONG;
258
259 args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY;
260 args.access_op = MSHV_GPAP_ACCESS_OP_CLEAR;
261 args.page_count = batch;
262 args.gpap_base = base_pfn + completed;
263 args.bitmap_ptr = (uint64_t)(bitmap + bitmap_offset);
264
265 ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args);
266 if (ret < 0) {
267 error_report("Failed to get dirty log (base_pfn=0x%" PRIx64
268 " batch=%" PRIu64 "): %s",
269 base_pfn + completed, batch, strerror(errno));
270 return -1;
271 }
272 completed += batch;
273 }
274
275 return 0;
276 }
277
278 bool mshv_log_global_start(MemoryListener *listener, Error **errp)
279 {
280 int ret;
281
282 ret = enable_dirty_page_tracking(mshv_state->vm);
283 if (ret < 0) {
284 error_setg_errno(errp, -ret, "Failed to enable dirty page tracking");
285 return false;
286 }
287 return true;
288 }
289
290 static int disable_dirty_page_tracking(int vm_fd)
291 {
292 int ret;
293 struct hv_input_set_partition_property in = {0};
294 struct mshv_root_hvcall args = {0};
295
296 in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING;
297 in.property_value = 0;
298
299 args.code = HVCALL_SET_PARTITION_PROPERTY;
300 args.in_sz = sizeof(in);
301 args.in_ptr = (uint64_t)&in;
302
303 ret = mshv_hvcall(vm_fd, &args);
304 if (ret < 0) {
305 error_report("Failed to disable dirty page tracking: %s",
306 strerror(errno));
307 return -1;
308 }
309
310 return 0;
311 }
312
313 static int set_dirty_pages(int vm_fd, uint64_t base_pfn, uint64_t page_count)
314 {
315 uint64_t batch, completed = 0;
316 unsigned long bitmap[MSHV_DIRTY_PAGES_BATCH_SIZE / BITS_PER_LONG];
317 struct mshv_gpap_access_bitmap args = {0};
318 int ret;
319
320 while (completed < page_count) {
321 batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed);
322
323 args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY;
324 args.access_op = MSHV_GPAP_ACCESS_OP_SET;
325 args.page_count = batch;
326 args.gpap_base = base_pfn + completed;
327 args.bitmap_ptr = (uint64_t)bitmap;
328
329 ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args);
330 if (ret < 0) {
331 error_report("Failed to set dirty pages (base_pfn=0x%" PRIx64
332 " batch=%" PRIu64 "): %s",
333 base_pfn + completed, batch, strerror(errno));
334 return -1;
335 }
336 completed += batch;
337 }
338
339 return 0;
340 }
341
342 static bool set_dirty_bits_cb(Int128 start, Int128 len, const MemoryRegion *mr,
343 hwaddr offset_in_region, void *opaque)
344 {
345 int ret, *errp = opaque;
346 hwaddr gpa, size;
347 uint64_t page_count, base_pfn;
348
349 gpa = int128_get64(start);
350 size = int128_get64(len);
351 page_count = size >> MSHV_PAGE_SHIFT;
352 base_pfn = gpa >> MSHV_PAGE_SHIFT;
353
354 if (!mr->ram || mr->readonly) {
355 return false;
356 }
357
358 if (page_count == 0) {
359 return false;
360 }
361
362 ret = set_dirty_pages(mshv_state->vm, base_pfn, page_count);
363
364 /* true aborts the iteration, which is what we want if there's an error */
365 if (ret < 0) {
366 *errp = ret;
367 return true;
368 }
369
370 return false;
371 }
372
373 void mshv_log_global_stop(MemoryListener *listener)
374 {
375 int err = 0;
376 /* MSHV requires all dirty bits to be set before disabling tracking. */
377 FlatView *fv = address_space_to_flatview(&address_space_memory);
378 flatview_for_each_range(fv, set_dirty_bits_cb, &err);
379
380 if (err < 0) {
381 error_report("Failed to set dirty bits before disabling tracking");
382 }
383
384 disable_dirty_page_tracking(mshv_state->vm);
385 }
386
387 void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section)
388 {
389 hwaddr size, start_addr, mr_offset;
390 uint64_t page_count, base_pfn;
391 size_t bitmap_size;
392 unsigned long *bitmap;
393 ram_addr_t ram_addr;
394 int ret;
395 MemoryRegion *mr = section->mr;
396
397 if (!memory_region_is_ram(mr) || memory_region_is_rom(mr)) {
398 return;
399 }
400
401 size = align_section(section, &start_addr);
402 if (!size) {
403 return;
404 }
405
406 page_count = size >> MSHV_PAGE_SHIFT;
407 base_pfn = start_addr >> MSHV_PAGE_SHIFT;
408 bitmap_size = ROUND_UP(page_count, BITS_PER_LONG) / 8;
409 bitmap = g_malloc0(bitmap_size);
410
411 ret = get_dirty_log(mshv_state->vm, base_pfn, page_count, bitmap,
412 bitmap_size);
413 if (ret < 0) {
414 g_free(bitmap);
415 return;
416 }
417
418 mr_offset = section->offset_within_region + start_addr -
419 section->offset_within_address_space;
420 ram_addr = memory_region_get_ram_addr(mr) + mr_offset;
421
422 physical_memory_set_dirty_lebitmap(bitmap, ram_addr, page_count);
423 g_free(bitmap);
424 }