xref: /illumos-gate/usr/src/test/bhyve-tests/tests/vmm/time_data.c (revision cbc1ee827389bb051e180a9bb8fe62c8136b0548)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2023 Oxide Computer Company
14  */
15 
16 /*
17  * VMM Time Data interface tests
18  *
19  * Note: requires `vmm_allow_state_writes` to be set
20  */
21 
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <libgen.h>
25 #include <errno.h>
26 #include <err.h>
27 
28 #include <sys/vmm_dev.h>
29 #include <sys/vmm_data.h>
30 #include <vmmapi.h>
31 
32 #include "common.h"
33 
34 
35 /*
36  * Constants from svm.c, redefined here for convenience
37  */
38 #define	AMD_TSC_MIN_FREQ	500000000
39 #define	AMD_TSC_MAX_FREQ_RATIO	15
40 
41 
42 static void
43 should_eq_u32(const char *field_name, uint32_t a, uint32_t b)
44 {
45 	if (a != b) {
46 		errx(EXIT_FAILURE, "unexpected %s %u != %u",
47 		    field_name, a, b);
48 	}
49 }
50 
51 static void
52 should_eq_u64(const char *field_name, uint64_t a, uint64_t b)
53 {
54 	if (a != b) {
55 		errx(EXIT_FAILURE, "unexpected %s %lu != %lu",
56 		    field_name, a, b);
57 	}
58 }
59 
60 /* a should be >= b */
61 static void
62 should_geq_u64(const char *field_name, uint64_t a, uint64_t b)
63 {
64 	if (a < b) {
65 		errx(EXIT_FAILURE, "unexpected %s %lu < %lu",
66 		    field_name, a, b);
67 	}
68 }
69 static void
70 should_geq_i64(const char *field_name, int64_t a, int64_t b)
71 {
72 	if (a < b) {
73 		errx(EXIT_FAILURE, "unexpected %s %ld < %ld",
74 		    field_name, a, b);
75 	}
76 }
77 
78 /*
79  * Test a valid VMM_DATA_READ of time data
80  */
81 static void
82 test_valid_read_time_data(int vmfd, struct vdi_time_info_v1 *time_info)
83 {
84 	struct vm_data_xfer xfer = {
85 		.vdx_class = VDC_VMM_TIME,
86 		.vdx_version = 1,
87 		.vdx_len = sizeof (struct vdi_time_info_v1),
88 		.vdx_data = time_info,
89 	};
90 
91 	if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
92 		errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed");
93 	}
94 }
95 
96 /*
97  * Test valid VMM_DATA_WRITE of time data
98  */
99 static void
100 test_valid_write_time_data(int vmfd, struct vdi_time_info_v1 *time_info)
101 {
102 	struct vm_data_xfer xfer = {
103 		.vdx_class = VDC_VMM_TIME,
104 		.vdx_version = 1,
105 		.vdx_len = sizeof (struct vdi_time_info_v1),
106 		.vdx_data = time_info,
107 	};
108 
109 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
110 		int error;
111 		error = errno;
112 
113 		if (error == EPERM) {
114 			warn("VMM_DATA_WRITE got EPERM: is "
115 			    "vmm_allow_state_writes set?");
116 		}
117 
118 		errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed");
119 	}
120 }
121 
122 /*
123  * Test malformed VMM_DATA_READ time data requests
124  */
125 static void
126 test_invalid_read_time_data(int vmfd)
127 {
128 	struct vdi_time_info_v1 res;
129 
130 	/* check error case: invalid vdr_len */
131 	struct vm_data_xfer xfer = {
132 		.vdx_class = VDC_VMM_TIME,
133 		.vdx_version = 1,
134 		.vdx_len = 0,
135 		.vdx_data = &res,
136 	};
137 	int error;
138 
139 	if (ioctl(vmfd, VM_DATA_READ, &xfer) == 0) {
140 		errx(EXIT_FAILURE,
141 		    "invalid VMM_DATA_READ of time info should fail");
142 	}
143 	error = errno;
144 	if (error != ENOSPC) {
145 		errx(EXIT_FAILURE, "test_invalid_read_time_data: "
146 		    "expected ENOSPC errno, got %d", error);
147 	}
148 	/* expected vdx_result_len should be communicated out */
149 	should_eq_u32("vdx_result_len", xfer.vdx_result_len,
150 	    sizeof (struct vdi_time_info_v1));
151 }
152 
153 /*
154  * Test malformed VMM_DATA_WRITE time data requests
155  */
156 static void
157 test_invalid_write_time_data(int vmfd, struct vdi_time_info_v1 *src)
158 {
159 	/* invalid vdx_len */
160 	struct vm_data_xfer xfer = {
161 		.vdx_class = VDC_VMM_TIME,
162 		.vdx_version = 1,
163 		.vdx_len = 0,
164 		.vdx_data = src,
165 	};
166 	int error;
167 
168 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
169 		errx(EXIT_FAILURE,
170 		    "invalid VMM_DATA_WRITE of time info should fail");
171 	}
172 	error = errno;
173 	if (error != ENOSPC) {
174 		errx(EXIT_FAILURE, "test_invalid_write_time_data: "
175 		    "expected ENOSPC errno, got %d", error);
176 	}
177 	/* expected vdx_result_len should be communicated out */
178 	should_eq_u32("vdx_result_len", xfer.vdx_result_len,
179 	    sizeof (struct vdi_time_info_v1));
180 }
181 
182 /*
183  * Test platform-independent invalid frequency ratio requests
184  */
185 static void
186 test_invalid_freq(int vmfd, struct vdi_time_info_v1 *src)
187 {
188 	/* guest frequency of 0 always invalid */
189 	struct vdi_time_info_v1 invalid = {
190 		.vt_guest_freq = 0,
191 		.vt_guest_tsc = src->vt_guest_tsc,
192 		.vt_boot_hrtime = src->vt_boot_hrtime,
193 		.vt_hrtime = src->vt_hrtime,
194 		.vt_hres_sec = src->vt_hres_sec,
195 		.vt_hres_ns = src->vt_hres_ns,
196 	};
197 	struct vm_data_xfer xfer = {
198 		.vdx_class = VDC_VMM_TIME,
199 		.vdx_version = 1,
200 		.vdx_len = sizeof (struct vdi_time_info_v1),
201 		.vdx_data = &invalid,
202 	};
203 	int error;
204 
205 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
206 		errx(EXIT_FAILURE,
207 		    "invalid VMM_DATA_WRITE of time info (vt_guest_freq = 0) "
208 		    "should fail");
209 	}
210 	error = errno;
211 	if (error != EINVAL) {
212 		errx(EXIT_FAILURE, "test_invalid_freq: \
213 		    expected EINVAL errno, got %d", error);
214 	}
215 }
216 
217 /*
218  * Test invalid AMD-specific frequency ratio requests
219  */
220 static void
221 test_invalid_freq_amd(int vmfd, struct vdi_time_info_v1 *src)
222 {
223 	struct vdi_time_info_v1 invalid = {
224 		.vt_guest_freq = src->vt_guest_freq,
225 		.vt_guest_tsc = src->vt_guest_tsc,
226 		.vt_boot_hrtime = src->vt_boot_hrtime,
227 		.vt_hrtime = src->vt_hrtime,
228 		.vt_hres_sec = src->vt_hres_sec,
229 		.vt_hres_ns = src->vt_hres_ns,
230 	};
231 	struct vm_data_xfer xfer = {
232 		.vdx_class = VDC_VMM_TIME,
233 		.vdx_version = 1,
234 		.vdx_len = sizeof (struct vdi_time_info_v1),
235 		.vdx_data = &invalid,
236 	};
237 	int error;
238 
239 	/* minimum guest frequency - 1 */
240 	invalid.vt_guest_freq = AMD_TSC_MIN_FREQ - 1;
241 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
242 		errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
243 		    "(min AMD guest freq) should fail");
244 	}
245 	error = errno;
246 	if (error != EINVAL) {
247 		errx(EXIT_FAILURE, "test_invalid_freq_amd (< min freq) "
248 		    "expected EINVAL errno, got %d", error);
249 	}
250 
251 	/* ratio >= max ratio */
252 	invalid.vt_guest_freq = src->vt_guest_freq * AMD_TSC_MAX_FREQ_RATIO;
253 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
254 		errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
255 		    "(AMD guest freq ratio too large) should fail");
256 	}
257 	error = errno;
258 	if (error != EINVAL) {
259 		errx(EXIT_FAILURE, "test_invalid_freq_amd (> max freq) "
260 		    "expected EINVAL errno, got %d", error);
261 	}
262 }
263 
264 /*
265  * Test valid AMD-specific frequency ratio requests
266  */
267 static void
268 test_valid_freq_amd(int vmfd, struct vdi_time_info_v1 *src)
269 {
270 	struct vdi_time_info_v1 res;
271 	int error;
272 
273 	/* minimum frequency */
274 	struct vdi_time_info_v1 valid = {
275 		.vt_guest_freq = AMD_TSC_MIN_FREQ,
276 		.vt_guest_tsc = src->vt_guest_tsc,
277 		.vt_boot_hrtime = src->vt_boot_hrtime,
278 		.vt_hrtime = src->vt_hrtime,
279 		.vt_hres_sec = src->vt_hres_sec,
280 		.vt_hres_ns = src->vt_hres_ns,
281 	};
282 	struct vm_data_xfer xfer = {
283 		.vdx_class = VDC_VMM_TIME,
284 		.vdx_version = 1,
285 		.vdx_len = sizeof (struct vdi_time_info_v1),
286 		.vdx_data = &valid,
287 	};
288 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
289 		error = errno;
290 		errx(EXIT_FAILURE, "valid VMM_DATA_WRITE of time info "
291 		    "(min AMD guest frequency) should succeed, errno=%d",
292 		    error);
293 	}
294 	/* verify the frequency was changed */
295 	test_valid_read_time_data(vmfd, &res);
296 	should_eq_u64("vt_guest_freq", res.vt_guest_freq, valid.vt_guest_freq);
297 
298 
299 	/* maximum frequency */
300 	valid.vt_guest_freq = src->vt_guest_freq * AMD_TSC_MAX_FREQ_RATIO - 1;
301 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
302 		error = errno;
303 		errx(EXIT_FAILURE, "valid VMM_DATA_WRITE of time info "
304 		    "(max AMD guest frequency) should succeed, errno=%d",
305 		    error);
306 	}
307 	/* verify the frequency was changed */
308 	test_valid_read_time_data(vmfd, &res);
309 	should_eq_u64("vt_guest_freq", res.vt_guest_freq, valid.vt_guest_freq);
310 }
311 
312 /*
313  * Test invalid Intel-specific frequency ratio requests
314  */
315 static void
316 test_invalid_freq_intel(int vmfd, struct vdi_time_info_v1 *src)
317 {
318 	/*
319 	 * As Intel is not currently supported, any frequency that differs from
320 	 * the host should be rejected.
321 	 */
322 	struct vdi_time_info_v1 invalid = {
323 		.vt_guest_freq = src->vt_guest_freq + 1,
324 		.vt_guest_tsc = src->vt_guest_tsc,
325 		.vt_boot_hrtime = src->vt_boot_hrtime,
326 		.vt_hrtime = src->vt_hrtime,
327 		.vt_hres_sec = src->vt_hres_sec,
328 		.vt_hres_ns = src->vt_hres_ns,
329 	};
330 	struct vm_data_xfer xfer = {
331 		.vdx_class = VDC_VMM_TIME,
332 		.vdx_version = 1,
333 		.vdx_len = sizeof (struct vdi_time_info_v1),
334 		.vdx_data = &invalid,
335 	};
336 	int error;
337 
338 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
339 		errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
340 		    "(intel scaling required) should fail");
341 	}
342 	error = errno;
343 	if (error != EPERM) {
344 		errx(EXIT_FAILURE, "test_invalid_freq_intel: "
345 		    "expected EPERM errno, got %d", error);
346 	}
347 }
348 
349 /*
350  * Test that an hrtime from the future is not accepted
351  */
352 static void
353 test_invalid_host_times(int vmfd, struct vdi_time_info_v1 *src)
354 {
355 	struct vdi_time_info_v1 invalid = {
356 		.vt_guest_freq = src->vt_guest_freq,
357 		.vt_guest_tsc = src->vt_guest_tsc,
358 		.vt_boot_hrtime = src->vt_boot_hrtime,
359 		.vt_hrtime = src->vt_hrtime,
360 		.vt_hres_sec = src->vt_hres_sec,
361 		.vt_hres_ns = src->vt_hres_ns,
362 	};
363 	struct vm_data_xfer xfer = {
364 		.vdx_class = VDC_VMM_TIME,
365 		.vdx_version = 1,
366 		.vdx_len = sizeof (struct vdi_time_info_v1),
367 		.vdx_data = &invalid,
368 	};
369 	int error;
370 
371 	/* hrtime + 500 seconds */
372 	invalid.vt_hrtime += 500000000000;
373 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
374 		errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
375 		    "(hrtime in the future) should fail");
376 	}
377 	error = errno;
378 	if (error != EINVAL) {
379 		errx(EXIT_FAILURE, "test_invalid_host_times: "
380 		    "expected EINVAL errno, got %d", error);
381 	}
382 }
383 
384 /*
385  * Test that a boot_hrtime from the future is not accepted
386  */
387 static void
388 test_invalid_boot_hrtime(int vmfd, struct vdi_time_info_v1 *src)
389 {
390 	struct vdi_time_info_v1 invalid = {
391 		.vt_guest_freq = src->vt_guest_freq,
392 		.vt_guest_tsc = src->vt_guest_tsc,
393 		.vt_boot_hrtime = src->vt_boot_hrtime,
394 		.vt_hrtime = src->vt_hrtime,
395 		.vt_hres_sec = src->vt_hres_sec,
396 		.vt_hres_ns = src->vt_hres_ns,
397 	};
398 	struct vm_data_xfer xfer = {
399 		.vdx_class = VDC_VMM_TIME,
400 		.vdx_version = 1,
401 		.vdx_len = sizeof (struct vdi_time_info_v1),
402 		.vdx_data = &invalid,
403 	};
404 	int error;
405 
406 	/* boot_hrtime = hrtime + 500 seconds */
407 	invalid.vt_boot_hrtime += src->vt_hrtime + 500000000000;
408 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
409 		errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info "
410 		    "(boot_hrtime in the future) should fail");
411 	}
412 	error = errno;
413 	if (error != EINVAL) {
414 		errx(EXIT_FAILURE, "test_invalid_boot_hrtime: "
415 		    "expected EINVAL errno, got %d", error);
416 	}
417 }
418 
419 /*
420  * Test that a different guest TSC is accepted. There are no constraints on what
421  * this value can be.
422  */
423 static void
424 test_valid_guest_tsc(int vmfd, struct vdi_time_info_v1 *src)
425 {
426 	/* arbitrary guest TSC in the future */
427 	struct vdi_time_info_v1 valid = {
428 		.vt_guest_freq = src->vt_guest_freq,
429 		.vt_guest_tsc = src->vt_guest_tsc + 500000000000,
430 		.vt_boot_hrtime = src->vt_boot_hrtime,
431 		.vt_hrtime = src->vt_hrtime,
432 		.vt_hres_sec = src->vt_hres_sec,
433 		.vt_hres_ns = src->vt_hres_ns,
434 	};
435 	test_valid_write_time_data(vmfd, &valid);
436 
437 	/* read it back */
438 	struct vdi_time_info_v1 res;
439 	test_valid_read_time_data(vmfd, &res);
440 
441 	/*
442 	 * The guest TSC may have been adjusted by the kernel, but it should
443 	 * be at least what was supplied.
444 	 */
445 	should_geq_u64("vt_guest_tsc", res.vt_guest_tsc, valid.vt_guest_tsc);
446 
447 }
448 
449 /*
450  * Test that a different boot_hrtime is accepted.
451  */
452 static void
453 test_valid_boot_hrtime(int vmfd, struct vdi_time_info_v1 *src)
454 {
455 	struct vdi_time_info_v1 res;
456 
457 	/* boot_hrtime < 0 */
458 	struct vdi_time_info_v1 valid = {
459 		.vt_guest_freq = src->vt_guest_freq,
460 		.vt_guest_tsc = src->vt_guest_tsc,
461 		.vt_boot_hrtime = -100000000000,
462 		.vt_hrtime = src->vt_hrtime,
463 		.vt_hres_sec = src->vt_hres_sec,
464 		.vt_hres_ns = src->vt_hres_ns,
465 	};
466 	test_valid_write_time_data(vmfd, &valid);
467 
468 	/* read it back */
469 	test_valid_read_time_data(vmfd, &res);
470 
471 	/*
472 	 * The boot_hrtime may have been adjusted by the kernel, but it should
473 	 * be at least what was supplied.
474 	 */
475 	should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime);
476 
477 
478 	/* repeat for boot_hrtime = 0 */
479 	valid.vt_boot_hrtime = 0;
480 	test_valid_write_time_data(vmfd, &valid);
481 	test_valid_read_time_data(vmfd, &res);
482 	should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime);
483 
484 	/* repeat for boot_hrtime > 0 */
485 	valid.vt_boot_hrtime = src->vt_boot_hrtime + 1;
486 	test_valid_write_time_data(vmfd, &valid);
487 	test_valid_read_time_data(vmfd, &res);
488 	should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime);
489 }
490 
491 /*
492  * Coarsely test that interface is making adjustments to the host times and
493  * guest time values.
494  */
495 static void
496 test_adjust(int vmfd, struct vdi_time_info_v1 *src)
497 {
498 	struct vdi_time_info_v1 res;
499 	test_valid_write_time_data(vmfd, src);
500 
501 	/* read it back */
502 	test_valid_read_time_data(vmfd, &res);
503 
504 	/*
505 	 * hrtime, hrestime, and guest TSC should all have moved forward
506 	 */
507 	should_geq_i64("vt_hrtime", res.vt_hrtime, src->vt_hrtime);
508 
509 	if (src->vt_hres_sec == res.vt_hres_sec) {
510 		/* ns should be higher */
511 		should_geq_u64("vt_hres_ns", res.vt_hres_ns, src->vt_hres_ns);
512 	} else if (src->vt_hres_sec > res.vt_hres_sec) {
513 		errx(EXIT_FAILURE, "test_adjust: hrestime went backwards");
514 	}
515 
516 	should_geq_u64("vt_guest_tsc", res.vt_guest_tsc, src->vt_guest_tsc);
517 
518 }
519 
520 
521 int
522 main(int argc, char *argv[])
523 {
524 	const char *suite_name = basename(argv[0]);
525 	struct vmctx *ctx;
526 
527 	ctx = create_test_vm(suite_name);
528 	if (ctx == NULL) {
529 		errx(EXIT_FAILURE, "could not open test VM");
530 	}
531 	if (vm_activate_cpu(ctx, 0) != 0) {
532 		err(EXIT_FAILURE, "could not activate vcpu0");
533 	}
534 
535 	const int vmfd = vm_get_device_fd(ctx);
536 	const bool is_svm = cpu_vendor_amd();
537 	struct vdi_time_info_v1 time_info;
538 
539 	/*
540 	 * Reads
541 	 */
542 	/* do a valid read */
543 	test_valid_read_time_data(vmfd, &time_info);
544 
545 	/* malformed read request */
546 	test_invalid_read_time_data(vmfd);
547 
548 	/*
549 	 * Writes
550 	 *
551 	 * For the test writes, we reuse the data from the successful read,
552 	 * and change the request parameters as necessary to test specific
553 	 * behavior. This is sufficient for testing validation of individual
554 	 * parameters, as writing the exact data back from a read is allowed.
555 	 *
556 	 * The only platform-specific behavior is around changing the guest
557 	 * TSC frequency. If the guest frequency is the same as the host's,
558 	 * as it is for all VMs at boot, then no scaling is required, and thus
559 	 * the CPU vendor of the system, or its capability to scale a guest TSC,
560 	 * does not matter.
561 	 */
562 
563 	/* try writing back the data from the read */
564 	test_valid_write_time_data(vmfd, &time_info);
565 
566 	/* malformed write request */
567 	test_invalid_write_time_data(vmfd, &time_info);
568 
569 	/* invalid host time requests */
570 	test_invalid_host_times(vmfd, &time_info);
571 
572 	/* invalid guest frequency requests */
573 	test_invalid_freq(vmfd, &time_info);
574 	if (is_svm) {
575 		test_invalid_freq_amd(vmfd, &time_info);
576 	} else {
577 		test_invalid_freq_intel(vmfd, &time_info);
578 	}
579 
580 	/* invalid boot_hrtime request */
581 	test_invalid_boot_hrtime(vmfd, &time_info);
582 
583 	/* valid frequency scaling requests */
584 	if (is_svm) {
585 		test_valid_freq_amd(vmfd, &time_info);
586 	}
587 
588 	/* valid guest TSC values */
589 	test_valid_guest_tsc(vmfd, &time_info);
590 
591 	/* valid boot_hrtime values */
592 	test_valid_boot_hrtime(vmfd, &time_info);
593 
594 	/* observe that host times and guest data are updated after a write */
595 	test_adjust(vmfd, &time_info);
596 
597 	vm_destroy(ctx);
598 	(void) printf("%s\tPASS\n", suite_name);
599 	return (EXIT_SUCCESS);
600 }
601