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