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