xref: /illumos-gate/usr/src/test/bhyve-tests/tests/vmm/datarw_constraints.c (revision 9164a50bf932130cbb5097a16f6986873ce0e6e5)
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