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