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 vmctx *ctx, struct vm_entry *ventry, 35 struct vm_exit *vexit) 36 { 37 do { 38 const enum vm_exit_kind kind = 39 test_run_vcpu(ctx, 0, 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 vmctx *ctx, 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(ctx, 0, 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 int err; 76 77 ctx = test_initialize(test_suite_name); 78 79 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 80 if (err != 0) { 81 test_fail_errno(err, "Could not initialize vcpu0"); 82 } 83 84 struct vm_entry ventry = { 0 }; 85 struct vm_exit vexit = { 0 }; 86 87 /* 88 * Let the payload run until it reaches the first userspace exit which 89 * requires actual handling 90 */ 91 run_until_unhandled(ctx, &ventry, &vexit); 92 if (vexit.exitcode != VM_EXITCODE_RDMSR) { 93 test_fail_vmexit(&vexit); 94 } 95 uint64_t rcx = 0, rip = 0; 96 if (vm_get_register(ctx, 0, VM_REG_GUEST_RCX, &rcx) != 0) { 97 test_fail_errno(errno, "Could not read guest %rcx"); 98 } 99 if (vm_get_register(ctx, 0, VM_REG_GUEST_RIP, &rip) != 0) { 100 test_fail_errno(errno, "Could not read guest %rip"); 101 } 102 /* Paranoia: confirm that in-register %rip matches vm_exit data */ 103 if (rip != vexit.rip) { 104 test_fail_msg( 105 "vm_exit`rip does not match in-kernel %rip: %lx != %lx", 106 rip, vexit.rip); 107 } 108 109 /* Request a consistent exit */ 110 ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; 111 if (vm_run(ctx, 0, &ventry, &vexit) != 0) { 112 test_fail_errno(errno, "Failure during vcpu entry"); 113 } 114 115 /* 116 * We expect the consistent exit to have completed the instruction 117 * emulation for the rdmsr (just move the %rip forward, since its left 118 * to userspace to update %rax:%rdx) and emit the BOGUS exitcode. 119 */ 120 if (vexit.exitcode != VM_EXITCODE_BOGUS) { 121 test_fail_msg("Unexpected exitcode: %d != %d", 122 vexit.exitcode, VM_EXITCODE_BOGUS); 123 } 124 125 /* 126 * Check that the %rip moved forward only the 2 bytes expected for a 127 * rdmsr opcode. 128 */ 129 if (vexit.rip != (rip + 2)) { 130 test_fail_msg("Exited at unexpected %rip: %lx != %lx", 131 vexit.rip, rip + 2); 132 } 133 134 /* 135 * Repeat entry with consistency request. This should not make any 136 * forward progress since the vCPU is already in a consistent state. 137 */ 138 repeat_consistent_exit(ctx, &ventry, &vexit, vexit.rip); 139 140 /* Let the vCPU continue on to the next exit condition */ 141 ventry.cmd = VEC_DEFAULT; 142 run_until_unhandled(ctx, &ventry, &vexit); 143 144 const uint64_t read_addr = 0xc0000000; 145 const uint_t read_len = 4; 146 if (!vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) { 147 test_fail_vmexit(&vexit); 148 } 149 rip = vexit.rip; 150 151 /* 152 * An attempt to push the vCPU to a consistent state without first 153 * fulfilling the MMIO should just result in the same MMIO exit. 154 */ 155 ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; 156 if (vm_run(ctx, 0, &ventry, &vexit) != 0) { 157 test_fail_errno(errno, "Failure during vcpu entry"); 158 } 159 if (vexit.rip != rip || 160 !vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) { 161 test_fail_msg( 162 "Unexpected forward progress during MMIO emulation"); 163 } 164 165 /* Fulfill the MMIO and attempt another consistent exit */ 166 ventry_fulfill_mmio(&vexit, &ventry, 0); 167 ventry.cmd |= VEC_FLAG_EXIT_CONSISTENT; 168 if (vm_run(ctx, 0, &ventry, &vexit) != 0) { 169 test_fail_errno(errno, "Failure during vcpu entry"); 170 } 171 172 /* With current payload, we expect a 3-byte mov instruction */ 173 if (vexit.rip != (rip + 3)) { 174 test_fail_msg("Exited at unexpected %rip: %lx != %lx", 175 vexit.rip, rip + 3); 176 } 177 178 /* 179 * And again, check that vCPU remains at that %rip once its state has 180 * been made consistent. 181 */ 182 repeat_consistent_exit(ctx, &ventry, &vexit, vexit.rip); 183 184 test_pass(); 185 } 186