xref: /illumos-gate/usr/src/test/bhyve-tests/tests/inst_emul/suspend_info.c (revision fc910014e8a32a65612105835a10995f2c13d942)
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