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
should_eq_u64(const char * field_name,uint64_t a,uint64_t b)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 %" PRIu64 " != %" PRIu64,
40 		    field_name, a, b);
41 	}
42 }
43 
44 static void
check_inval_field(int vmfd,uint32_t ident,uint64_t val)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
do_data_write(int vmfd,struct vm_data_xfer * vdx)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
do_data_read(int vmfd,struct vm_data_xfer * vdx)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
main(int argc,char * argv[])92 main(int argc, char *argv[])
93 {
94 	const char *suite_name = basename(argv[0]);
95 	struct vmctx *ctx;
96 	struct vcpu *vcpu;
97 
98 	ctx = create_test_vm(suite_name);
99 	if (ctx == NULL) {
100 		errx(EXIT_FAILURE, "could not open test VM");
101 	}
102 
103 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
104 		err(EXIT_FAILURE, "Could not open vcpu0");
105 	}
106 
107 	if (vm_activate_cpu(vcpu) != 0) {
108 		err(EXIT_FAILURE, "could not activate vcpu0");
109 	}
110 
111 	const int vmfd = vm_get_device_fd(ctx);
112 
113 	/* Pause the instance before attempting to manipulate vcpu data */
114 	if (ioctl(vmfd, VM_PAUSE, 0) != 0) {
115 		err(EXIT_FAILURE, "VM_PAUSE failed");
116 	}
117 
118 	struct vdi_field_entry_v1 fields[4] = {
119 		{ .vfe_ident = VAI_PEND_NMI },
120 		{ .vfe_ident = VAI_PEND_EXTINT },
121 		{ .vfe_ident = VAI_PEND_EXCP },
122 		{ .vfe_ident = VAI_PEND_INTINFO },
123 	};
124 
125 	struct vm_data_xfer vdx = {
126 		.vdx_class = VDC_VMM_ARCH,
127 		.vdx_version = 1,
128 		.vdx_flags = VDX_FLAG_READ_COPYIN,
129 		.vdx_len = sizeof (fields),
130 		.vdx_data = &fields,
131 	};
132 
133 	/* Fetch arch state first */
134 	do_data_read(vmfd, &vdx);
135 
136 	/* All of these should be zeroed on a fresh vcpu */
137 	should_eq_u64("VAI_PEND_NMI", fields[0].vfe_value, 0);
138 	should_eq_u64("VAI_PEND_EXTINT", fields[1].vfe_value, 0);
139 	should_eq_u64("VAI_PEND_EXCP", fields[2].vfe_value, 0);
140 	should_eq_u64("VAI_PEND_INTINFO", fields[3].vfe_value, 0);
141 
142 	/* Light up those fields */
143 	fields[0].vfe_value = 1;
144 	fields[1].vfe_value = 1;
145 	fields[2].vfe_value = VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP;
146 	fields[3].vfe_value = VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80;
147 	do_data_write(vmfd, &vdx);
148 
149 	/*
150 	 * Flip the order (just for funsies) and re-query to check that we still
151 	 * get the expected state.
152 	 */
153 	fields[0].vfe_ident = VAI_PEND_INTINFO;
154 	fields[1].vfe_ident = VAI_PEND_EXCP;
155 	fields[2].vfe_ident = VAI_PEND_EXTINT;
156 	fields[3].vfe_ident = VAI_PEND_NMI;
157 	do_data_read(vmfd, &vdx);
158 
159 	should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value,
160 	    VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80);
161 	should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value,
162 	    VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP);
163 	should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 1);
164 	should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 1);
165 
166 
167 	/* NMI-typed exception with the wrong vector */
168 	check_inval_field(vmfd, VAI_PEND_INTINFO,
169 	    VM_INTINFO_VALID | VM_INTINFO_NMI | 0xd);
170 
171 	/* Hardware exception with a bad vector (>= 32) */
172 	check_inval_field(vmfd, VAI_PEND_INTINFO,
173 	    VM_INTINFO_VALID | VM_INTINFO_HWEXCP | 0x40);
174 
175 	/* Non-HW event injected into HW exception field */
176 	check_inval_field(vmfd, VAI_PEND_EXCP,
177 	    VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0xd);
178 
179 	/* Zero out the values again */
180 	fields[0].vfe_value = 0;
181 	fields[1].vfe_value = 0;
182 	fields[2].vfe_value = 0;
183 	fields[3].vfe_value = 0;
184 	do_data_write(vmfd, &vdx);
185 
186 	/* And confirm that it took */
187 	do_data_read(vmfd, &vdx);
188 	should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 0);
189 	should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 0);
190 	should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 0);
191 	should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 0);
192 
193 	vm_vcpu_close(vcpu);
194 	vm_destroy(ctx);
195 	(void) printf("%s\tPASS\n", suite_name);
196 	return (EXIT_SUCCESS);
197 }
198