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 <stdbool.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <stdlib.h>
20 #include <strings.h>
21 #include <libgen.h>
22 #include <assert.h>
23 #include <errno.h>
24
25 #include <sys/types.h>
26 #include <sys/sysmacros.h>
27 #include <sys/debug.h>
28 #include <sys/vmm.h>
29 #include <sys/vmm_dev.h>
30 #include <vmmapi.h>
31
32 #include "in_guest.h"
33
34 enum test_state {
35 SEEKING_IN,
36 SEEKING_OUT,
37 SEEKING_END,
38 DONE,
39 };
40
41 bool
advance_test_state(const struct vm_exit * vexit,struct vm_entry * ventry,struct vcpu * vcpu,enum vm_exit_kind kind,enum test_state * state,const bool have_decodeassist)42 advance_test_state(const struct vm_exit *vexit, struct vm_entry *ventry,
43 struct vcpu *vcpu, enum vm_exit_kind kind, enum test_state *state,
44 const bool have_decodeassist)
45 {
46 if (*state != DONE && kind == VEK_REENTR) {
47 return (true);
48 }
49
50 switch (*state) {
51 case SEEKING_IN:
52 if (kind != VEK_UNHANDLED) {
53 break;
54 }
55
56 if (have_decodeassist) {
57 if (vexit->exitcode != VM_EXITCODE_INOUT) {
58 break;
59 }
60
61 const bool match_inb =
62 vexit_match_inout(vexit, true, 0x55aa, 1, NULL);
63 if (!match_inb) {
64 break;
65 }
66
67 ventry_fulfill_inout(vexit, ventry, 0xee);
68 } else {
69 if (vexit->exitcode != VM_EXITCODE_INST_EMUL) {
70 break;
71 }
72
73 const bool match_inb =
74 vexit->u.inst_emul.num_valid >= 2 &&
75 vexit->u.inst_emul.inst[0] == 0xf3 &&
76 vexit->u.inst_emul.inst[1] == 0x6c;
77 if (!match_inb) {
78 break;
79 }
80
81 int err = vm_set_register(vcpu, 0, vexit->rip + 2);
82 if (err != 0) {
83 test_fail_errno(err, "failed to set %rip");
84 }
85 }
86 *state = SEEKING_OUT;
87 return (true);
88 case SEEKING_OUT:
89 if (kind != VEK_UNHANDLED) {
90 break;
91 }
92
93 if (have_decodeassist) {
94 if (vexit->exitcode != VM_EXITCODE_INOUT) {
95 break;
96 }
97
98 const bool match_outb =
99 vexit_match_inout(vexit, false, 0x55aa, 1, NULL);
100 if (!match_outb) {
101 break;
102 }
103
104 ventry_fulfill_inout(vexit, ventry, 0);
105 } else {
106 if (vexit->exitcode != VM_EXITCODE_INST_EMUL) {
107 break;
108 }
109
110 const bool match_outb =
111 vexit->u.inst_emul.num_valid >= 2 &&
112 vexit->u.inst_emul.inst[0] == 0xf3 &&
113 vexit->u.inst_emul.inst[1] == 0x6e;
114 if (!match_outb) {
115 break;
116 }
117
118 int err = vm_set_register(vcpu, 0, vexit->rip + 2);
119 if (err != 0) {
120 test_fail_errno(err, "failed to set %rip");
121 }
122 }
123 *state = SEEKING_END;
124 return (true);
125 case SEEKING_END:
126 if (kind == VEK_TEST_PASS) {
127 *state = DONE;
128 return (true);
129 }
130 break;
131 case DONE:
132 break;
133 }
134
135 return (false);
136 }
137
138 int
main(int argc,char * argv[])139 main(int argc, char *argv[])
140 {
141 const char *test_suite_name = basename(argv[0]);
142 struct vmctx *ctx = NULL;
143 struct vcpu *vcpu;
144 int err;
145
146 ctx = test_initialize(test_suite_name);
147
148 /*
149 * Guest execution of `in` and `out` instructions (and their repeatable
150 * string versions) can have substantially different outcomes depending
151 * on available hardware support.
152 *
153 * For simple `in` or `out`, `byhve` will cause a VM exit with exit
154 * code `VM_EXITCODE_INOUT`. This is the simplest case, and not
155 * currently exercised in this test.
156 *
157 * For `ins` and `outs`, decoding the exact operation is more complex.
158 * For processors with the DecodeAssist feature, we rely on the
159 * processor to do that decoding and provide information about the
160 * trapped instruction. As a result, `ins` and `outs` on such processors
161 * result in a VM exit with code `VM_EXITCODE_INOUT`.
162 *
163 * Finally, if DecodeAssist is *not* available, we don't currently do
164 * any in-kernel disassembly to backfill the missing functionality. So
165 * instead, `ins`/`outs` on processors without DecodeAssist result in a
166 * VM exit with code `VM_EXITCODE_INST_EMUL`.
167 *
168 * Since the kernel behavior varies based on hardware features, detect
169 * DecodeAssist here as well to check for what we expect the kernel to
170 * do on this processor.
171 */
172 uint_t regs[4];
173 do_cpuid(0x8000000a, regs);
174 const bool have_decodeassist = (regs[3] & (1 << 7)) != 0;
175
176 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
177 test_fail_errno(errno, "Could not open vcpu0");
178 }
179
180 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
181 if (err != 0) {
182 test_fail_errno(err, "Could not initialize vcpu0");
183 }
184
185 struct vm_entry ventry = { 0 };
186 struct vm_exit vexit = { 0 };
187
188 enum test_state state = SEEKING_IN;
189
190 do {
191 const enum vm_exit_kind kind =
192 test_run_vcpu(vcpu, &ventry, &vexit);
193
194 const bool exit_ok = advance_test_state(&vexit, &ventry, vcpu,
195 kind, &state, have_decodeassist);
196
197 if (!exit_ok) {
198 test_fail_vmexit(&vexit);
199 break;
200 }
201 } while (state != DONE);
202
203 test_pass();
204 }
205