xref: /illumos-gate/usr/src/test/bhyve-tests/tests/inst_emul/inout.c (revision 3fe455549728ac525df3be56130ad8e075d645d7)
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
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
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