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