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 *
vcpu0_thread(void * arg)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
vcpu0_setup(struct vcpu * vcpu)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
vcpu0_spawn(struct vcpu_thread_ctx * vtc)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
vcpu0_join(pthread_t tid)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
test_plain_suspend(struct vmctx * ctx,struct vcpu * vcpu,enum vm_suspend_how test_how)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
test_emitted_triplefault(struct vmctx * ctx,struct vcpu * vcpu)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
main(int argc,char * argv[])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