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 2024 Oxide Computer Company 14 */ 15 16 #include <stdio.h> 17 #include <unistd.h> 18 #include <stdlib.h> 19 #include <fcntl.h> 20 #include <libgen.h> 21 #include <sys/stat.h> 22 #include <errno.h> 23 #include <err.h> 24 #include <assert.h> 25 #include <sys/sysmacros.h> 26 #include <stdbool.h> 27 28 #include <sys/vmm.h> 29 #include <sys/vmm_dev.h> 30 #include <sys/vmm_data.h> 31 #include <vmmapi.h> 32 33 #include "common.h" 34 35 static void 36 should_eq_u32(const char *field_name, uint32_t a, uint32_t b) 37 { 38 if (a != b) { 39 errx(EXIT_FAILURE, "unexpected %s %u != %u", 40 field_name, a, b); 41 } 42 } 43 44 static void 45 test_size_boundaries(int vmfd) 46 { 47 uint8_t buf[sizeof (struct vdi_atpic_v1) + sizeof (int)]; 48 struct vm_data_xfer vdx = { 49 .vdx_class = VDC_ATPIC, 50 .vdx_version = 1, 51 .vdx_len = sizeof (struct vdi_atpic_v1), 52 .vdx_data = buf, 53 }; 54 55 /* Attempt a valid-sized read first */ 56 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 57 err(EXIT_FAILURE, "valid VM_DATA_READ failed"); 58 } 59 should_eq_u32("vdx_result_len", vdx.vdx_result_len, 60 sizeof (struct vdi_atpic_v1)); 61 62 /* And check that we can write it back */ 63 vdx.vdx_result_len = 0; 64 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { 65 err(EXIT_FAILURE, "valid VM_DATA_WRITE failed"); 66 } 67 68 /* ... then too-small ... */ 69 vdx.vdx_len = sizeof (struct vdi_atpic_v1) - sizeof (int); 70 vdx.vdx_result_len = 0; 71 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { 72 errx(EXIT_FAILURE, "invalid VM_DATA_READ should have failed"); 73 } 74 int error = errno; 75 if (error != ENOSPC) { 76 errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error); 77 } 78 /* the "correct" vdx_result_len should still be communicated out */ 79 should_eq_u32("vdx_result_len", vdx.vdx_result_len, 80 sizeof (struct vdi_atpic_v1)); 81 82 /* Repeat with too-small write */ 83 vdx.vdx_result_len = 0; 84 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { 85 errx(EXIT_FAILURE, "invalid VM_DATA_WRITE should have failed"); 86 } 87 error = errno; 88 if (error != ENOSPC) { 89 errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error); 90 } 91 should_eq_u32("vdx_result_len", vdx.vdx_result_len, 92 sizeof (struct vdi_atpic_v1)); 93 94 /* 95 * ... and too-big to round it out. 96 * 97 * This should pass, but still set vdx_result_len to the actual length 98 */ 99 vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int); 100 vdx.vdx_result_len = 0; 101 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 102 err(EXIT_FAILURE, "too-large (but valid) VM_DATA_READ failed"); 103 } 104 should_eq_u32("vdx_result_len", vdx.vdx_result_len, 105 sizeof (struct vdi_atpic_v1)); 106 107 /* ... and repeated as a write */ 108 vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int); 109 vdx.vdx_result_len = 0; 110 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { 111 err(EXIT_FAILURE, 112 "too-large (but valid) VM_DATA_WRITE failed"); 113 } 114 should_eq_u32("vdx_result_len", vdx.vdx_result_len, 115 sizeof (struct vdi_atpic_v1)); 116 } 117 118 struct class_test_case { 119 uint16_t ctc_class; 120 uint16_t ctc_version; 121 }; 122 123 static void 124 test_vm_classes(int vmfd) 125 { 126 const struct class_test_case cases[] = { 127 { VDC_VERSION, 1 }, 128 { VDC_VMM_ARCH, 1 }, 129 { VDC_IOAPIC, 1 }, 130 { VDC_ATPIT, 1 }, 131 { VDC_ATPIC, 1 }, 132 { VDC_HPET, 1 }, 133 { VDC_PM_TIMER, 1 }, 134 { VDC_RTC, 2 }, 135 { VDC_VMM_TIME, 1 }, 136 }; 137 138 /* A page should be large enough for all classes (for now) */ 139 const size_t bufsz = PAGESIZE; 140 uint8_t *buf = malloc(bufsz); 141 142 for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) { 143 struct vm_data_xfer vdx = { 144 .vdx_class = cases[i].ctc_class, 145 .vdx_version = cases[i].ctc_version, 146 .vdx_len = bufsz, 147 .vdx_data = buf, 148 .vdx_vcpuid = -1, 149 }; 150 151 /* First do a read */ 152 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 153 err(EXIT_FAILURE, 154 "VM_DATA_READ failed class:%u version:%u", 155 vdx.vdx_class, vdx.vdx_version); 156 } 157 if (vdx.vdx_class == VDC_VERSION || 158 vdx.vdx_class == VDC_VMM_ARCH) { 159 /* 160 * Skip classes which contain some (or all) bits which 161 * are read-only. 162 */ 163 continue; 164 } 165 166 /* Write the same data back */ 167 vdx.vdx_len = vdx.vdx_result_len; 168 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { 169 err(EXIT_FAILURE, 170 "VM_DATA_WRITE failed class:%u version:%u", 171 vdx.vdx_class, vdx.vdx_version); 172 } 173 } 174 free(buf); 175 } 176 177 static void 178 test_vcpu_classes(int vmfd) 179 { 180 const struct class_test_case cases[] = { 181 { VDC_MSR, 1 }, 182 { VDC_LAPIC, 1 }, 183 { VDC_VMM_ARCH, 1 }, 184 185 /* 186 * Although these classes are per-vCPU, they have not yet been 187 * implemented in the vmm-data system, so are ignored for now: 188 * 189 * - VDC_REGISTER 190 * - VDC_FPU 191 * - VDC_LAPIC 192 */ 193 }; 194 195 /* A page should be large enough for all classes (for now) */ 196 const size_t bufsz = PAGESIZE; 197 uint8_t *buf = malloc(bufsz); 198 199 for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) { 200 struct vm_data_xfer vdx = { 201 .vdx_class = cases[i].ctc_class, 202 .vdx_version = cases[i].ctc_version, 203 .vdx_len = bufsz, 204 .vdx_data = buf, 205 .vdx_vcpuid = 0, 206 }; 207 208 /* First do a read */ 209 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 210 err(EXIT_FAILURE, 211 "VM_DATA_READ failed class:%u version:%u", 212 vdx.vdx_class, vdx.vdx_version); 213 } 214 215 if (vdx.vdx_class == VDC_VMM_ARCH) { 216 /* 217 * There are some read-only fields in VMM_ARCH which we 218 * do not want to attempt to write back. 219 */ 220 continue; 221 } 222 223 /* Write the same data back */ 224 vdx.vdx_len = vdx.vdx_result_len; 225 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { 226 err(EXIT_FAILURE, 227 "VM_DATA_WRITE failed class:%u version:%u", 228 vdx.vdx_class, vdx.vdx_version); 229 } 230 } 231 free(buf); 232 } 233 234 static void 235 test_bogus_class(int vmfd) 236 { 237 const size_t bufsz = PAGESIZE; 238 uint8_t *buf = malloc(bufsz); 239 240 struct vm_data_xfer vdx = { 241 .vdx_class = 10000, 242 .vdx_version = 1, 243 .vdx_len = bufsz, 244 .vdx_data = buf, 245 }; 246 247 /* Try to read with an absurd data class */ 248 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { 249 errx(EXIT_FAILURE, 250 "VM_DATA_READ should fail for absurd vdx_class"); 251 } 252 253 /* Same for data version */ 254 vdx.vdx_class = VDC_VERSION; 255 vdx.vdx_version = 10000; 256 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { 257 errx(EXIT_FAILURE, 258 "VM_DATA_READ should fail for absurd vdx_version"); 259 } 260 261 free(buf); 262 } 263 264 static void 265 test_vcpuid_combos(int vmfd) 266 { 267 const size_t bufsz = PAGESIZE; 268 uint8_t *buf = malloc(bufsz); 269 270 struct vm_data_xfer vdx = { 271 .vdx_class = VDC_LAPIC, 272 .vdx_version = 1, 273 .vdx_len = bufsz, 274 .vdx_data = buf, 275 }; 276 277 /* Try with -1 sentinel, too-negative, and too-positive values */ 278 const int bad_per_vcpu[] = { -1, -5, 1000 }; 279 for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) { 280 vdx.vdx_vcpuid = bad_per_vcpu[i]; 281 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { 282 errx(EXIT_FAILURE, 283 "VM_DATA_READ should fail for bad vcpuid %d", 284 vdx.vdx_vcpuid); 285 } 286 } 287 288 /* 289 * Valid vcpuid should be fine still. Reading valid data into the 290 * buffer will be useful to subsequently test writes. 291 */ 292 vdx.vdx_vcpuid = 0; 293 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 294 err(EXIT_FAILURE, "failed VM_DATA_READ with valid vcpuid"); 295 } 296 297 /* Repeat the same checks for writes */ 298 for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) { 299 vdx.vdx_vcpuid = bad_per_vcpu[i]; 300 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { 301 errx(EXIT_FAILURE, 302 "VM_DATA_WRITE should fail for bad vcpuid %d", 303 vdx.vdx_vcpuid); 304 } 305 } 306 307 vdx.vdx_vcpuid = 0; 308 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { 309 err(EXIT_FAILURE, "failed VM_DATA_WRITE with valid vcpuid"); 310 } 311 312 vdx.vdx_class = VDC_VERSION; 313 vdx.vdx_version = 1; 314 315 /* 316 * VM-wide classes should work fine with the -1 sentinel. For now, 317 * passing an otherwise valid vcpuid will still work, but that id is 318 * ignored. 319 */ 320 const int good_vm_wide[] = { -1, 0, 1 }; 321 for (uint_t i = 0; i < ARRAY_SIZE(good_vm_wide); i++) { 322 vdx.vdx_vcpuid = good_vm_wide[i]; 323 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 324 err(EXIT_FAILURE, 325 "failed VM-wide VM_DATA_READ with vcpuid %d", 326 vdx.vdx_vcpuid); 327 } 328 } 329 330 /* Bogus values should still fail */ 331 const int bad_vm_wide[] = { -5, 1000 }; 332 for (uint_t i = 0; i < ARRAY_SIZE(bad_vm_wide); i++) { 333 vdx.vdx_vcpuid = bad_vm_wide[i]; 334 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { 335 errx(EXIT_FAILURE, 336 "VM_DATA_READ should fail for bad vcpuid %d", 337 vdx.vdx_vcpuid); 338 } 339 } 340 341 free(buf); 342 } 343 344 static void 345 test_vcpuid_time(int vmfd) 346 { 347 struct vdi_time_info_v1 data; 348 struct vm_data_xfer vdx = { 349 .vdx_class = VDC_VMM_TIME, 350 .vdx_version = 1, 351 .vdx_len = sizeof (data), 352 .vdx_data = &data, 353 }; 354 355 /* This should work with the system-wide vcpuid */ 356 vdx.vdx_vcpuid = -1; 357 if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { 358 err(EXIT_FAILURE, "VM_DATA_READ failed for valid vcpuid"); 359 } 360 361 /* But fail for other vcpuids */ 362 vdx.vdx_vcpuid = 0; 363 if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { 364 err(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d", 365 vdx.vdx_vcpuid); 366 } 367 368 /* 369 * Perform same check for writes 370 * 371 * Normally this would require care to handle hosts which lack frequency 372 * scaling functionality, but since we are writing back the same data, 373 * the guest frequency should match that of the host, requiring no real 374 * scaling be done for the instance. 375 */ 376 vdx.vdx_vcpuid = -1; 377 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { 378 err(EXIT_FAILURE, "VM_DATA_WRITE failed for valid vcpuid"); 379 } 380 vdx.vdx_vcpuid = 0; 381 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { 382 errx(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d", 383 vdx.vdx_vcpuid); 384 } 385 } 386 387 int 388 main(int argc, char *argv[]) 389 { 390 const char *suite_name = basename(argv[0]); 391 struct vmctx *ctx; 392 393 ctx = create_test_vm(suite_name); 394 if (ctx == NULL) { 395 errx(EXIT_FAILURE, "could not open test VM"); 396 } 397 398 /* 399 * Check that vmm_data import/export facility is robust in the face of 400 * potentially invalid inputs 401 */ 402 const int vmfd = vm_get_device_fd(ctx); 403 404 /* Test varies edge cases around data transfer sizes */ 405 test_size_boundaries(vmfd); 406 407 /* Check that known VM-wide data classes can be accessed */ 408 test_vm_classes(vmfd); 409 410 /* Check that known per-vCPU data classes can be accessed */ 411 test_vcpu_classes(vmfd); 412 413 /* Try some bogus class/version combos */ 414 test_bogus_class(vmfd); 415 416 /* Try some weird vdx_vcpuid cases */ 417 test_vcpuid_combos(vmfd); 418 419 /* VMM_TIME is picky about vcpuid */ 420 test_vcpuid_time(vmfd); 421 422 vm_destroy(ctx); 423 (void) printf("%s\tPASS\n", suite_name); 424 return (EXIT_SUCCESS); 425 } 426