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 /* 17 * Check that supporting information for a VM_EXITCODE_SUSPENDED exit is correct 18 * for a vCPU-specific event (triple-fault). 19 */ 20 21 #include <stdio.h> 22 #include <unistd.h> 23 #include <stdlib.h> 24 #include <strings.h> 25 #include <libgen.h> 26 #include <assert.h> 27 #include <pthread.h> 28 #include <errno.h> 29 30 #include <sys/types.h> 31 #include <sys/sysmacros.h> 32 #include <sys/debug.h> 33 #include <sys/vmm.h> 34 #include <sys/vmm_dev.h> 35 #include <vmmapi.h> 36 37 #include "in_guest.h" 38 39 #define VCPU0_STACK (MEM_LOC_STACK) 40 #define VCPU1_STACK (MEM_LOC_STACK - 0x1000) 41 42 struct vcpu_thread_ctx { 43 struct vcpu *vcpu; 44 enum vm_suspend_how *howp; 45 int *sourcep; 46 }; 47 48 static void * 49 vcpu0_thread(void *arg) 50 { 51 struct vcpu_thread_ctx *vtc = arg; 52 struct vcpu *vcpu = vtc->vcpu; 53 54 struct vm_entry ventry = { 0 }; 55 struct vm_exit vexit = { 0 }; 56 57 58 do { 59 const enum vm_exit_kind kind = 60 test_run_vcpu(vcpu, &ventry, &vexit); 61 switch (kind) { 62 case VEK_REENTR: 63 break; 64 case VEK_UNHANDLED: 65 if (vexit.exitcode != VM_EXITCODE_SUSPENDED) { 66 test_fail_vmexit(&vexit); 67 } 68 *vtc->howp = vexit.u.suspended.how; 69 *vtc->sourcep = vexit.u.suspended.source; 70 return (NULL); 71 default: 72 test_fail_vmexit(&vexit); 73 } 74 } while (true); 75 } 76 77 static void 78 vcpu0_setup(struct vcpu *vcpu) 79 { 80 int err; 81 82 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, VCPU0_STACK); 83 if (err != 0) { 84 test_fail_errno(err, "Could not initialize vcpu0"); 85 } 86 err = vm_set_register(vcpu, VM_REG_GUEST_RDI, 0); 87 if (err != 0) { 88 test_fail_errno(err, "failed to set %rdi"); 89 } 90 } 91 92 static pthread_t 93 vcpu0_spawn(struct vcpu_thread_ctx *vtc) 94 { 95 pthread_t tid; 96 if (pthread_create(&tid, NULL, vcpu0_thread, (void *)vtc) != 0) { 97 test_fail_errno(errno, "could not create thread for vcpu0"); 98 } 99 100 return (tid); 101 } 102 103 static void 104 vcpu0_join(pthread_t tid) 105 { 106 void *status = NULL; 107 if (pthread_join(tid, &status) != 0) { 108 test_fail_errno(errno, "could not join thread for vcpu0"); 109 } 110 assert(status == NULL); 111 } 112 113 static void 114 test_plain_suspend(struct vmctx *ctx, struct vcpu *vcpu, 115 enum vm_suspend_how test_how) 116 { 117 enum vm_suspend_how how; 118 int source; 119 struct vcpu_thread_ctx vcpu0 = { 120 .vcpu = vcpu, 121 .howp = &how, 122 .sourcep = &source, 123 }; 124 pthread_t tid; 125 int err; 126 127 vcpu0_setup(vcpu); 128 tid = vcpu0_spawn(&vcpu0); 129 err = vm_suspend(ctx, test_how); 130 if (err != 0) { 131 test_fail_errno(err, "vm_suspend() failure"); 132 } 133 vcpu0_join(tid); 134 135 if (how != test_how) { 136 test_fail_msg("Unexpected suspend how %d != %d\n", 137 how, test_how); 138 } 139 if (source != -1) { 140 test_fail_msg("Unexpected suspend source %d != %d\n", 141 source, -1); 142 } 143 144 /* Reset VM for another test */ 145 test_reinitialize(ctx, 0); 146 } 147 148 static void 149 test_emitted_triplefault(struct vmctx *ctx, struct vcpu *vcpu) 150 { 151 enum vm_suspend_how vcpu0_how; 152 int vcpu0_source; 153 struct vcpu_thread_ctx vcpu0 = { 154 .vcpu = vcpu, 155 .howp = &vcpu0_how, 156 .sourcep = &vcpu0_source, 157 }; 158 struct vcpu *vcpu1; 159 int err; 160 pthread_t tid; 161 162 vcpu0_setup(vcpu); 163 164 if ((vcpu1 = vm_vcpu_open(ctx, 1)) == NULL) { 165 test_fail_errno(errno, "Could not open vcpu1"); 166 } 167 168 /* Setup vCPU1 like vCPU0, but with ID of 1 in %rdi */ 169 err = test_setup_vcpu(vcpu1, MEM_LOC_PAYLOAD, VCPU1_STACK); 170 if (err != 0) { 171 test_fail_errno(err, "Could not initialize vcpu1"); 172 } 173 err = vm_set_register(vcpu1, VM_REG_GUEST_RDI, 1); 174 if (err != 0) { 175 test_fail_errno(err, "failed to set %rdi"); 176 } 177 178 /* 179 * Get vcpu0 running on a separate thread, ready to have its day 180 * "ruined" by a triple-fault on vcpu1 181 */ 182 tid = vcpu0_spawn(&vcpu0); 183 184 struct vm_entry ventry = { 0 }; 185 struct vm_exit vexit = { 0 }; 186 do { 187 const enum vm_exit_kind kind = 188 test_run_vcpu(vcpu1, &ventry, &vexit); 189 switch (kind) { 190 case VEK_REENTR: 191 break; 192 case VEK_UNHANDLED: { 193 /* expect immediate triple-fault from ud2a */ 194 if (vexit.exitcode != VM_EXITCODE_SUSPENDED) { 195 test_fail_vmexit(&vexit); 196 } 197 vcpu0_join(tid); 198 const enum vm_suspend_how vcpu1_how = 199 vexit.u.suspended.how; 200 const int vcpu1_source = vexit.u.suspended.source; 201 202 if (vcpu0_how != VM_SUSPEND_TRIPLEFAULT || 203 vcpu0_how != vcpu1_how) { 204 test_fail_msg("Unexpected 'how' for " 205 "triple-fault: vcpu0=%d, vcpu1=%d, " 206 "expected=%d", 207 vcpu0_how, vcpu1_how, 208 VM_SUSPEND_TRIPLEFAULT); 209 } 210 if (vcpu0_source != 1 || 211 vcpu0_source != vcpu1_source) { 212 test_fail_msg("Unexpected 'source' for " 213 "triple-fault: vcpu0=%d, vcpu1=%d, " 214 "expected=%d", 215 vcpu0_source, vcpu1_source, 1); 216 } 217 return; 218 } 219 220 default: 221 test_fail_vmexit(&vexit); 222 break; 223 } 224 } while (true); 225 } 226 227 int 228 main(int argc, char *argv[]) 229 { 230 const char *test_suite_name = basename(argv[0]); 231 struct vmctx *ctx = NULL; 232 struct vcpu *vcpu; 233 234 ctx = test_initialize(test_suite_name); 235 236 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 237 test_fail_errno(errno, "Could not open vcpu0"); 238 } 239 240 /* 241 * Try injecting the various suspend types, and confirm that vcpu0 exits 242 * with the expected details. 243 */ 244 test_plain_suspend(ctx, vcpu, VM_SUSPEND_RESET); 245 test_plain_suspend(ctx, vcpu, VM_SUSPEND_POWEROFF); 246 test_plain_suspend(ctx, vcpu, VM_SUSPEND_HALT); 247 248 /* 249 * Let vCPU1 generate a triple-fault, and confirm that it is emitted by 250 * both exiting vCPU threads, with the proper details. 251 */ 252 test_emitted_triplefault(ctx, vcpu); 253 254 test_pass(); 255 } 256