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