xref: /illumos-gate/usr/src/test/bhyve-tests/tests/vmm/datarw_vcpu.c (revision a1d41cf940fc4cda50098ad61e6a78b19c7483cd)
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 #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_u64(const char *field_name, uint64_t a, uint64_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 check_inval_field(int vmfd, uint32_t ident, uint64_t val)
46 {
47 	struct vdi_field_entry_v1 field = {
48 		.vfe_ident = ident,
49 		.vfe_value = val,
50 	};
51 	struct vm_data_xfer vdx = {
52 		.vdx_class = VDC_VMM_ARCH,
53 		.vdx_version = 1,
54 		.vdx_len = sizeof (field),
55 		.vdx_data = &field,
56 	};
57 
58 	if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) {
59 		err(EXIT_FAILURE, "vmm_data_write should have failed");
60 	}
61 	int err = errno;
62 	if (err != EINVAL) {
63 		errx(EXIT_FAILURE, "expected EINVAL errno, got %d", err);
64 	}
65 }
66 
67 static void
68 do_data_write(int vmfd, struct vm_data_xfer *vdx)
69 {
70 	if (ioctl(vmfd, VM_DATA_WRITE, vdx) != 0) {
71 		err(EXIT_FAILURE, "valid vmm_data_write failed");
72 	}
73 	if (vdx->vdx_result_len != vdx->vdx_len) {
74 		errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u",
75 		    vdx->vdx_len, vdx->vdx_result_len);
76 	}
77 }
78 
79 static void
80 do_data_read(int vmfd, struct vm_data_xfer *vdx)
81 {
82 	if (ioctl(vmfd, VM_DATA_READ, vdx) != 0) {
83 		err(EXIT_FAILURE, "valid vmm_data_read failed");
84 	}
85 	if (vdx->vdx_result_len != vdx->vdx_len) {
86 		errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u",
87 		    vdx->vdx_len, vdx->vdx_result_len);
88 	}
89 }
90 
91 int
92 main(int argc, char *argv[])
93 {
94 	const char *suite_name = basename(argv[0]);
95 	struct vmctx *ctx;
96 
97 	ctx = create_test_vm(suite_name);
98 	if (ctx == NULL) {
99 		errx(EXIT_FAILURE, "could not open test VM");
100 	}
101 
102 	if (vm_activate_cpu(ctx, 0) != 0) {
103 		err(EXIT_FAILURE, "could not activate vcpu0");
104 	}
105 
106 	const int vmfd = vm_get_device_fd(ctx);
107 
108 	/* Pause the instance before attempting to manipulate vcpu data */
109 	if (ioctl(vmfd, VM_PAUSE, 0) != 0) {
110 		err(EXIT_FAILURE, "VM_PAUSE failed");
111 	}
112 
113 	struct vdi_field_entry_v1 fields[4] = {
114 		{ .vfe_ident = VAI_PEND_NMI },
115 		{ .vfe_ident = VAI_PEND_EXTINT },
116 		{ .vfe_ident = VAI_PEND_EXCP },
117 		{ .vfe_ident = VAI_PEND_INTINFO },
118 	};
119 
120 	struct vm_data_xfer vdx = {
121 		.vdx_class = VDC_VMM_ARCH,
122 		.vdx_version = 1,
123 		.vdx_flags = VDX_FLAG_READ_COPYIN,
124 		.vdx_len = sizeof (fields),
125 		.vdx_data = &fields,
126 	};
127 
128 	/* Fetch arch state first */
129 	do_data_read(vmfd, &vdx);
130 
131 	/* All of these should be zeroed on a fresh vcpu */
132 	should_eq_u64("VAI_PEND_NMI", fields[0].vfe_value, 0);
133 	should_eq_u64("VAI_PEND_EXTINT", fields[1].vfe_value, 0);
134 	should_eq_u64("VAI_PEND_EXCP", fields[2].vfe_value, 0);
135 	should_eq_u64("VAI_PEND_INTINFO", fields[3].vfe_value, 0);
136 
137 	/* Light up those fields */
138 	fields[0].vfe_value = 1;
139 	fields[1].vfe_value = 1;
140 	fields[2].vfe_value = VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP;
141 	fields[3].vfe_value = VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80;
142 	do_data_write(vmfd, &vdx);
143 
144 	/*
145 	 * Flip the order (just for funsies) and re-query to check that we still
146 	 * get the expected state.
147 	 */
148 	fields[0].vfe_ident = VAI_PEND_INTINFO;
149 	fields[1].vfe_ident = VAI_PEND_EXCP;
150 	fields[2].vfe_ident = VAI_PEND_EXTINT;
151 	fields[3].vfe_ident = VAI_PEND_NMI;
152 	do_data_read(vmfd, &vdx);
153 
154 	should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value,
155 	    VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80);
156 	should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value,
157 	    VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP);
158 	should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 1);
159 	should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 1);
160 
161 
162 	/* NMI-typed exception with the wrong vector */
163 	check_inval_field(vmfd, VAI_PEND_INTINFO,
164 	    VM_INTINFO_VALID | VM_INTINFO_NMI | 0xd);
165 
166 	/* Hardware exception with a bad vector (>= 32) */
167 	check_inval_field(vmfd, VAI_PEND_INTINFO,
168 	    VM_INTINFO_VALID | VM_INTINFO_HWEXCP | 0x40);
169 
170 	/* Non-HW event injected into HW exception field */
171 	check_inval_field(vmfd, VAI_PEND_EXCP,
172 	    VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0xd);
173 
174 	/* Zero out the values again */
175 	fields[0].vfe_value = 0;
176 	fields[1].vfe_value = 0;
177 	fields[2].vfe_value = 0;
178 	fields[3].vfe_value = 0;
179 	do_data_write(vmfd, &vdx);
180 
181 	/* And confirm that it took */
182 	do_data_read(vmfd, &vdx);
183 	should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 0);
184 	should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 0);
185 	should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 0);
186 	should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 0);
187 
188 	vm_destroy(ctx);
189 	(void) printf("%s\tPASS\n", suite_name);
190 	return (EXIT_SUCCESS);
191 }
192