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 2022 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 <err.h> 22 #include <errno.h> 23 #include <strings.h> 24 25 #include <sys/vmm.h> 26 #include <sys/vmm_dev.h> 27 #include <vmmapi.h> 28 29 #include "common.h" 30 31 int 32 main(int argc, char *argv[]) 33 { 34 const char *suite_name = basename(argv[0]); 35 struct vmctx *ctx; 36 37 ctx = create_test_vm(suite_name); 38 if (ctx == NULL) { 39 perror("could open test VM"); 40 return (EXIT_FAILURE); 41 } 42 int vmfd = vm_get_device_fd(ctx); 43 44 struct vm_vcpu_cpuid_config cfg = { 0 }; 45 struct vcpu_cpuid_entry *entries = NULL; 46 47 if (ioctl(vmfd, VM_GET_CPUID, &cfg) != 0) { 48 err(EXIT_FAILURE, "ioctl(VM_GET_CPUID) failed"); 49 } 50 if (cfg.vvcc_flags != VCC_FLAG_LEGACY_HANDLING) { 51 errx(EXIT_FAILURE, 52 "cpuid handling did not default to legacy-style"); 53 } 54 55 cfg.vvcc_flags = ~VCC_FLAG_LEGACY_HANDLING; 56 if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { 57 errx(EXIT_FAILURE, 58 "ioctl(VM_SET_CPUID) did not reject invalid flags"); 59 } 60 61 entries = calloc(VMM_MAX_CPUID_ENTRIES + 1, 62 sizeof (struct vcpu_cpuid_entry)); 63 if (entries == NULL) { 64 errx(EXIT_FAILURE, "could not allocate cpuid entries"); 65 } 66 67 cfg.vvcc_flags = VCC_FLAG_LEGACY_HANDLING; 68 cfg.vvcc_nent = 1; 69 cfg.vvcc_entries = entries; 70 if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { 71 errx(EXIT_FAILURE, 72 "ioctl(VM_SET_CPUID) did not reject entries when " 73 "legacy-style handling was requested"); 74 } 75 76 cfg.vvcc_flags = 0; 77 cfg.vvcc_nent = VMM_MAX_CPUID_ENTRIES + 1; 78 if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { 79 errx(EXIT_FAILURE, 80 "ioctl(VM_SET_CPUID) did not reject excessive entry count"); 81 } 82 83 cfg.vvcc_nent = 1; 84 entries[0].vce_flags = ~0; 85 if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { 86 errx(EXIT_FAILURE, 87 "ioctl(VM_SET_CPUID) did not invalid entry flags"); 88 } 89 entries[0].vce_flags = 0; 90 91 /* Actually set some entries to use for GET_CPUID testing */ 92 const uint_t valid_entries = (VMM_MAX_CPUID_ENTRIES / 2); 93 for (uint_t i = 0; i < valid_entries; i++) { 94 entries[i].vce_function = i; 95 } 96 cfg.vvcc_nent = valid_entries; 97 if (ioctl(vmfd, VM_SET_CPUID, &cfg) != 0) { 98 err(EXIT_FAILURE, 99 "ioctl(VM_SET_CPUID) unable to set valid entries"); 100 } 101 102 /* Try with no entries buffer */ 103 bzero(&cfg, sizeof (cfg)); 104 if (ioctl(vmfd, VM_GET_CPUID, &cfg) == 0 || errno != E2BIG) { 105 errx(EXIT_FAILURE, 106 "ioctl(VM_GET_CPUID) did not fail absent buffer"); 107 } 108 if (cfg.vvcc_nent != valid_entries) { 109 errx(EXIT_FAILURE, 110 "ioctl(VM_GET_CPUID) did not emit entry count " 111 "(expected %u, got %u)", valid_entries, cfg.vvcc_nent); 112 } 113 114 /* Try with too-small entries buffer */ 115 cfg.vvcc_nent = 1; 116 cfg.vvcc_entries = entries; 117 bzero(entries, valid_entries * sizeof (struct vcpu_cpuid_entry)); 118 if (ioctl(vmfd, VM_GET_CPUID, &cfg) == 0 || errno != E2BIG) { 119 errx(EXIT_FAILURE, 120 "ioctl(VM_GET_CPUID) did not fail too-small buffer"); 121 } 122 if (cfg.vvcc_nent != valid_entries) { 123 errx(EXIT_FAILURE, 124 "ioctl(VM_GET_CPUID) did not emit entry count " 125 "(expected %u, got %u)", valid_entries, cfg.vvcc_nent); 126 } 127 128 /* Try with adequate entries buffer */ 129 cfg.vvcc_nent = valid_entries; 130 if (ioctl(vmfd, VM_GET_CPUID, &cfg) != 0) { 131 err(EXIT_FAILURE, "ioctl(VM_GET_CPUID) failed"); 132 } 133 if (cfg.vvcc_nent != valid_entries) { 134 errx(EXIT_FAILURE, 135 "ioctl(VM_GET_CPUID) did not emit entry count " 136 "(expected %u, got %u)", valid_entries, cfg.vvcc_nent); 137 } 138 for (uint_t i = 0; i < valid_entries; i++) { 139 if (entries[i].vce_function != i) { 140 errx(EXIT_FAILURE, "unexpected entry contents"); 141 } 142 } 143 144 /* 145 * The legacy handling is simply using the host values with certain 146 * modifications (masking, etc) applied. The base leaf should be 147 * exactly the same as we read from the host. 148 * 149 * Since a bhyve compat header has an inline-asm cpuid wrapper, use that 150 * for now for querying the host 151 */ 152 struct vm_legacy_cpuid legacy = { 0 }; 153 if (ioctl(vmfd, VM_LEGACY_CPUID, &legacy) != 0) { 154 err(EXIT_FAILURE, "ioctl(VM_CPUID_LEGACY) failed"); 155 } 156 157 uint32_t basic_cpuid[4]; 158 cpuid_count(0, 0, basic_cpuid); 159 if (basic_cpuid[0] != legacy.vlc_eax || 160 basic_cpuid[1] != legacy.vlc_ebx || 161 basic_cpuid[2] != legacy.vlc_ecx || 162 basic_cpuid[3] != legacy.vlc_edx) { 163 errx(EXIT_FAILURE, "legacy cpuid mismatch"); 164 } 165 166 vm_destroy(ctx); 167 (void) printf("%s\tPASS\n", suite_name); 168 return (EXIT_SUCCESS); 169 } 170