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 <strings.h> 20 #include <libgen.h> 21 #include <assert.h> 22 #include <errno.h> 23 24 #include <sys/types.h> 25 #include <sys/sysmacros.h> 26 #include <sys/debug.h> 27 #include <sys/vmm.h> 28 #include <sys/vmm_dev.h> 29 #include <vmmapi.h> 30 31 #include "in_guest.h" 32 33 static void 34 run_until_unhandled(struct vcpu *vcpu, struct vm_entry *ventry, 35 struct vm_exit *vexit) 36 { 37 do { 38 const enum vm_exit_kind kind = 39 test_run_vcpu(vcpu, ventry, vexit); 40 switch (kind) { 41 case VEK_REENTR: 42 break; 43 case VEK_UNHANDLED: 44 return; 45 default: 46 /* 47 * We are not expecting the payload to use any of the 48 * pass/fail/messaging facilities during this test. 49 */ 50 test_fail_vmexit(vexit); 51 break; 52 } 53 } while (true); 54 } 55 56 static void 57 repeat_consistent_exit(struct vcpu *vcpu, struct vm_entry *ventry, 58 struct vm_exit *vexit, uint64_t expected_rip) 59 { 60 ventry->cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; 61 if (vm_run(vcpu, ventry, vexit) != 0) { 62 test_fail_errno(errno, "Failure during vcpu entry"); 63 } 64 if (vexit->rip != expected_rip) { 65 test_fail_msg( 66 "Unexpected forward progress when vCPU already consistent"); 67 } 68 } 69 70 int 71 main(int argc, char *argv[]) 72 { 73 const char *test_suite_name = basename(argv[0]); 74 struct vmctx *ctx = NULL; 75 struct vcpu *vcpu; 76 int err; 77 78 ctx = test_initialize(test_suite_name); 79 80 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 81 test_fail_errno(errno, "Could not open vcpu0"); 82 } 83 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 84 if (err != 0) { 85 test_fail_errno(err, "Could not initialize vcpu0"); 86 } 87 88 struct vm_entry ventry = { 0 }; 89 struct vm_exit vexit = { 0 }; 90 91 /* 92 * Let the payload run until it reaches the first userspace exit which 93 * requires actual handling 94 */ 95 run_until_unhandled(vcpu, &ventry, &vexit); 96 if (vexit.exitcode != VM_EXITCODE_RDMSR) { 97 test_fail_vmexit(&vexit); 98 } 99 uint64_t rcx = 0, rip = 0; 100 if (vm_get_register(vcpu, VM_REG_GUEST_RCX, &rcx) != 0) { 101 test_fail_errno(errno, "Could not read guest %rcx"); 102 } 103 if (vm_get_register(vcpu, VM_REG_GUEST_RIP, &rip) != 0) { 104 test_fail_errno(errno, "Could not read guest %rip"); 105 } 106 /* Paranoia: confirm that in-register %rip matches vm_exit data */ 107 if (rip != vexit.rip) { 108 test_fail_msg( 109 "vm_exit`rip does not match in-kernel %rip: %lx != %lx", 110 rip, vexit.rip); 111 } 112 113 /* Request a consistent exit */ 114 ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; 115 if (vm_run(vcpu, &ventry, &vexit) != 0) { 116 test_fail_errno(errno, "Failure during vcpu entry"); 117 } 118 119 /* 120 * We expect the consistent exit to have completed the instruction 121 * emulation for the rdmsr (just move the %rip forward, since its left 122 * to userspace to update %rax:%rdx) and emit the BOGUS exitcode. 123 */ 124 if (vexit.exitcode != VM_EXITCODE_BOGUS) { 125 test_fail_msg("Unexpected exitcode: %d != %d", 126 vexit.exitcode, VM_EXITCODE_BOGUS); 127 } 128 129 /* 130 * Check that the %rip moved forward only the 2 bytes expected for a 131 * rdmsr opcode. 132 */ 133 if (vexit.rip != (rip + 2)) { 134 test_fail_msg("Exited at unexpected %rip: %lx != %lx", 135 vexit.rip, rip + 2); 136 } 137 138 /* 139 * Repeat entry with consistency request. This should not make any 140 * forward progress since the vCPU is already in a consistent state. 141 */ 142 repeat_consistent_exit(vcpu, &ventry, &vexit, vexit.rip); 143 144 /* Let the vCPU continue on to the next exit condition */ 145 ventry.cmd = VEC_DEFAULT; 146 run_until_unhandled(vcpu, &ventry, &vexit); 147 148 const uint64_t read_addr = 0xc0000000; 149 const uint_t read_len = 4; 150 if (!vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) { 151 test_fail_vmexit(&vexit); 152 } 153 rip = vexit.rip; 154 155 /* 156 * An attempt to push the vCPU to a consistent state without first 157 * fulfilling the MMIO should just result in the same MMIO exit. 158 */ 159 ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; 160 if (vm_run(vcpu, &ventry, &vexit) != 0) { 161 test_fail_errno(errno, "Failure during vcpu entry"); 162 } 163 if (vexit.rip != rip || 164 !vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) { 165 test_fail_msg( 166 "Unexpected forward progress during MMIO emulation"); 167 } 168 169 /* Fulfill the MMIO and attempt another consistent exit */ 170 ventry_fulfill_mmio(&vexit, &ventry, 0); 171 ventry.cmd |= VEC_FLAG_EXIT_CONSISTENT; 172 if (vm_run(vcpu, &ventry, &vexit) != 0) { 173 test_fail_errno(errno, "Failure during vcpu entry"); 174 } 175 176 /* With current payload, we expect a 3-byte mov instruction */ 177 if (vexit.rip != (rip + 3)) { 178 test_fail_msg("Exited at unexpected %rip: %lx != %lx", 179 vexit.rip, rip + 3); 180 } 181 182 /* 183 * And again, check that vCPU remains at that %rip once its state has 184 * been made consistent. 185 */ 186 repeat_consistent_exit(vcpu, &ventry, &vexit, vexit.rip); 187 188 test_pass(); 189 } 190