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
main(int argc,char * argv[])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