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
should_eq_u32(const char * field_name,uint32_t a,uint32_t b)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
should_eq_u64(const char * field_name,uint64_t a,uint64_t b)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
should_geq_u64(const char * field_name,uint64_t a,uint64_t b)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
should_geq_i64(const char * field_name,int64_t a,int64_t b)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
test_valid_read_time_data(int vmfd,struct vdi_time_info_v1 * time_info)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
test_valid_write_time_data(int vmfd,struct vdi_time_info_v1 * time_info)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
test_invalid_read_time_data(int vmfd)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
test_invalid_write_time_data(int vmfd,struct vdi_time_info_v1 * src)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
test_invalid_freq(int vmfd,struct vdi_time_info_v1 * src)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
test_invalid_freq_amd(int vmfd,struct vdi_time_info_v1 * src)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
test_valid_freq_amd(int vmfd,struct vdi_time_info_v1 * src)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
test_invalid_freq_intel(int vmfd,struct vdi_time_info_v1 * src)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
test_invalid_host_times(int vmfd,struct vdi_time_info_v1 * src)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
test_invalid_boot_hrtime(int vmfd,struct vdi_time_info_v1 * src)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
test_valid_guest_tsc(int vmfd,struct vdi_time_info_v1 * src)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
test_valid_boot_hrtime(int vmfd,struct vdi_time_info_v1 * src)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
test_adjust(int vmfd,struct vdi_time_info_v1 * src)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
main(int argc,char * argv[])522 main(int argc, char *argv[])
523 {
524 const char *suite_name = basename(argv[0]);
525 struct vmctx *ctx;
526 struct vcpu *vcpu;
527
528 ctx = create_test_vm(suite_name);
529 if (ctx == NULL) {
530 errx(EXIT_FAILURE, "could not open test VM");
531 }
532 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
533 err(EXIT_FAILURE, "Could not open vcpu0");
534 }
535 if (vm_activate_cpu(vcpu) != 0) {
536 err(EXIT_FAILURE, "could not activate vcpu0");
537 }
538
539 const int vmfd = vm_get_device_fd(ctx);
540 const bool is_svm = cpu_vendor_amd();
541 struct vdi_time_info_v1 time_info;
542
543 /*
544 * Reads
545 */
546 /* do a valid read */
547 test_valid_read_time_data(vmfd, &time_info);
548
549 /* malformed read request */
550 test_invalid_read_time_data(vmfd);
551
552 /*
553 * Writes
554 *
555 * For the test writes, we reuse the data from the successful read,
556 * and change the request parameters as necessary to test specific
557 * behavior. This is sufficient for testing validation of individual
558 * parameters, as writing the exact data back from a read is allowed.
559 *
560 * The only platform-specific behavior is around changing the guest
561 * TSC frequency. If the guest frequency is the same as the host's,
562 * as it is for all VMs at boot, then no scaling is required, and thus
563 * the CPU vendor of the system, or its capability to scale a guest TSC,
564 * does not matter.
565 */
566
567 /* try writing back the data from the read */
568 test_valid_write_time_data(vmfd, &time_info);
569
570 /* malformed write request */
571 test_invalid_write_time_data(vmfd, &time_info);
572
573 /* invalid host time requests */
574 test_invalid_host_times(vmfd, &time_info);
575
576 /* invalid guest frequency requests */
577 test_invalid_freq(vmfd, &time_info);
578 if (is_svm) {
579 test_invalid_freq_amd(vmfd, &time_info);
580 } else {
581 test_invalid_freq_intel(vmfd, &time_info);
582 }
583
584 /* invalid boot_hrtime request */
585 test_invalid_boot_hrtime(vmfd, &time_info);
586
587 /* valid frequency scaling requests */
588 if (is_svm) {
589 test_valid_freq_amd(vmfd, &time_info);
590 }
591
592 /* valid guest TSC values */
593 test_valid_guest_tsc(vmfd, &time_info);
594
595 /* valid boot_hrtime values */
596 test_valid_boot_hrtime(vmfd, &time_info);
597
598 /* observe that host times and guest data are updated after a write */
599 test_adjust(vmfd, &time_info);
600
601 vm_vcpu_close(vcpu);
602 vm_destroy(ctx);
603 (void) printf("%s\tPASS\n", suite_name);
604 return (EXIT_SUCCESS);
605 }
606