xref: /freebsd/usr.sbin/bhyve/riscv/vmexit.c (revision 24e4dcf4ba5e9dedcf89efd358ea3e1fe5867020)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2011 NetApp, Inc.
5  * All rights reserved.
6  * Copyright (c) 2024 Ruslan Bukin <br@bsdpad.com>
7  *
8  * This software was developed by the University of Cambridge Computer
9  * Laboratory (Department of Computer Science and Technology) under Innovate
10  * UK project 105694, "Digital Security by Design (DSbD) Technology Platform
11  * Prototype".
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/types.h>
36 #include <sys/cpuset.h>
37 
38 #include <machine/riscvreg.h>
39 #include <machine/cpu.h>
40 #include <machine/sbi.h>
41 #include <machine/vmm.h>
42 #include <machine/vmm_dev.h>
43 #include <machine/vmm_instruction_emul.h>
44 
45 #include <assert.h>
46 #include <errno.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <unistd.h>
51 
52 #include <vmmapi.h>
53 
54 #include "bhyverun.h"
55 #include "config.h"
56 #include "debug.h"
57 #include "mem.h"
58 #include "vmexit.h"
59 #include "riscv.h"
60 
61 #define	BHYVE_VERSION	((uint64_t)__FreeBSD_version)
62 #define	SBI_VERS_MAJOR	2
63 #define	SBI_VERS_MINOR	0
64 
65 static cpuset_t running_hartmask = CPUSET_T_INITIALIZER(0);
66 
67 void
68 vmexit_set_bsp(int hart_id)
69 {
70 
71 	CPU_SET_ATOMIC(hart_id, &running_hartmask);
72 }
73 
74 static int
75 vmexit_inst_emul(struct vmctx *ctx __unused, struct vcpu *vcpu,
76     struct vm_run *vmrun)
77 {
78 	struct vm_exit *vme;
79 	struct vie *vie;
80 	int err;
81 
82 	vme = vmrun->vm_exit;
83 	vie = &vme->u.inst_emul.vie;
84 
85 	err = emulate_mem(vcpu, vme->u.inst_emul.gpa, vie,
86 	    &vme->u.inst_emul.paging);
87 	if (err) {
88 		if (err == ESRCH) {
89 			EPRINTLN("Unhandled memory access to 0x%lx\n",
90 			    vme->u.inst_emul.gpa);
91 		}
92 		goto fail;
93 	}
94 
95 	return (VMEXIT_CONTINUE);
96 
97 fail:
98 	fprintf(stderr, "Failed to emulate instruction ");
99 	FPRINTLN(stderr, "at 0x%lx", vme->pc);
100 	return (VMEXIT_ABORT);
101 }
102 
103 static int
104 vmexit_suspend(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
105 {
106 	struct vm_exit *vme;
107 	enum vm_suspend_how how;
108 	int vcpuid = vcpu_id(vcpu);
109 
110 	vme = vmrun->vm_exit;
111 	how = vme->u.suspended.how;
112 
113 	fbsdrun_deletecpu(vcpuid);
114 
115 	switch (how) {
116 	case VM_SUSPEND_RESET:
117 		exit(0);
118 	case VM_SUSPEND_POWEROFF:
119 		if (get_config_bool_default("destroy_on_poweroff", false))
120 			vm_destroy(ctx);
121 		exit(1);
122 	case VM_SUSPEND_HALT:
123 		exit(2);
124 	case VM_SUSPEND_DESTROY:
125 		exit(4);
126 	default:
127 		fprintf(stderr, "vmexit_suspend: invalid reason %d\n", how);
128 		exit(100);
129 	}
130 
131 	/* NOT REACHED. */
132 
133 	return (0);
134 }
135 
136 static int
137 vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
138     struct vm_run *vmrun __unused)
139 {
140 
141 	/*
142 	 * XXX-MJ sleep for a short period to avoid chewing up the CPU in the
143 	 * window between activation of the vCPU thread and the
144 	 * SBI_HSM_HART_START request.
145 	 */
146 	usleep(1000);
147 	return (VMEXIT_CONTINUE);
148 }
149 
150 static int
151 vmexit_bogus(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
152     struct vm_run *vmrun __unused)
153 {
154 
155 	return (VMEXIT_CONTINUE);
156 }
157 
158 static int
159 vmm_sbi_probe_extension(int ext_id)
160 {
161 
162 	switch (ext_id) {
163 	case SBI_EXT_ID_HSM:
164 	case SBI_EXT_ID_TIME:
165 	case SBI_EXT_ID_IPI:
166 	case SBI_EXT_ID_RFNC:
167 	case SBI_EXT_ID_SRST:
168 	case SBI_CONSOLE_PUTCHAR:
169 	case SBI_CONSOLE_GETCHAR:
170 		break;
171 	default:
172 		return (0);
173 	}
174 
175 	return (1);
176 }
177 
178 static int
179 vmexit_ecall_hsm(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
180     struct vm_exit *vme)
181 {
182 	struct vcpu *newvcpu;
183 	uint64_t hart_id;
184 	int func_id;
185 	int error;
186 
187 	hart_id = vme->u.ecall.args[0];
188 	func_id = vme->u.ecall.args[6];
189 
190 	if (HART_TO_CPU(hart_id) >= (uint64_t)guest_ncpus)
191 		return (SBI_ERR_INVALID_PARAM);
192 
193 	newvcpu = fbsdrun_vcpu(HART_TO_CPU(hart_id));
194 	assert(newvcpu != NULL);
195 
196 	switch (func_id) {
197 	case SBI_HSM_HART_START:
198 		if (CPU_ISSET(hart_id, &running_hartmask))
199 			break;
200 
201 		/* Set hart ID. */
202 		error = vm_set_register(newvcpu, VM_REG_GUEST_A0, hart_id);
203 		assert(error == 0);
204 
205 		/* Set PC. */
206 		error = vm_set_register(newvcpu, VM_REG_GUEST_SEPC,
207 		    vme->u.ecall.args[1]);
208 		assert(error == 0);
209 
210 		/* Pass private data. */
211 		error = vm_set_register(newvcpu, VM_REG_GUEST_A1,
212 		    vme->u.ecall.args[2]);
213 		assert(error == 0);
214 
215 		vm_resume_cpu(newvcpu);
216 		CPU_SET_ATOMIC(hart_id, &running_hartmask);
217 		break;
218 	case SBI_HSM_HART_STOP:
219 		if (!CPU_ISSET(hart_id, &running_hartmask))
220 			break;
221 		CPU_CLR_ATOMIC(hart_id, &running_hartmask);
222 		vm_suspend_cpu(newvcpu);
223 		break;
224 	case SBI_HSM_HART_STATUS:
225 		/* TODO. */
226 		break;
227 	default:
228 		return (SBI_ERR_NOT_SUPPORTED);
229 	}
230 
231 	return (SBI_SUCCESS);
232 }
233 
234 static int
235 vmexit_ecall_base(struct vmctx *ctx __unused, struct vcpu *vcpu,
236     struct vm_exit *vme)
237 {
238 	int sbi_function_id;
239 	uint32_t val;
240 	int ext_id;
241 	int error;
242 
243 	sbi_function_id = vme->u.ecall.args[6];
244 
245 	switch (sbi_function_id) {
246 	case SBI_BASE_GET_SPEC_VERSION:
247 		val = SBI_VERS_MAJOR << SBI_SPEC_VERS_MAJOR_OFFSET;
248 		val |= SBI_VERS_MINOR << SBI_SPEC_VERS_MINOR_OFFSET;
249 		break;
250 	case SBI_BASE_GET_IMPL_ID:
251 		val = SBI_IMPL_ID_BHYVE;
252 		break;
253 	case SBI_BASE_GET_IMPL_VERSION:
254 		val = BHYVE_VERSION;
255 		break;
256 	case SBI_BASE_PROBE_EXTENSION:
257 		ext_id = vme->u.ecall.args[0];
258 		val = vmm_sbi_probe_extension(ext_id);
259 		break;
260 	case SBI_BASE_GET_MVENDORID:
261 		val = MVENDORID_UNIMPL;
262 		break;
263 	case SBI_BASE_GET_MARCHID:
264 		val = MARCHID_UNIMPL;
265 		break;
266 	case SBI_BASE_GET_MIMPID:
267 		val = 0;
268 		break;
269 	default:
270 		return (SBI_ERR_NOT_SUPPORTED);
271 	}
272 
273 	error = vm_set_register(vcpu, VM_REG_GUEST_A1, val);
274 	assert(error == 0);
275 
276 	return (SBI_SUCCESS);
277 }
278 
279 static int
280 vmexit_ecall_srst(struct vmctx *ctx, struct vm_exit *vme)
281 {
282 	enum vm_suspend_how how;
283 	int func_id;
284 	int type;
285 
286 	func_id = vme->u.ecall.args[6];
287 	type = vme->u.ecall.args[0];
288 
289 	switch (func_id) {
290 	case SBI_SRST_SYSTEM_RESET:
291 		switch (type) {
292 		case SBI_SRST_TYPE_SHUTDOWN:
293 		case SBI_SRST_TYPE_COLD_REBOOT:
294 		case SBI_SRST_TYPE_WARM_REBOOT:
295 			how = VM_SUSPEND_POWEROFF;
296 			vm_suspend(ctx, how);
297 			break;
298 		default:
299 			return (SBI_ERR_NOT_SUPPORTED);
300 		}
301 		break;
302 	default:
303 		return (SBI_ERR_NOT_SUPPORTED);
304 	}
305 
306 	return (SBI_SUCCESS);
307 }
308 
309 static int
310 vmexit_ecall(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
311 {
312 	int sbi_extension_id;
313 	struct vm_exit *vme;
314 	int error;
315 	int ret;
316 
317 	vme = vmrun->vm_exit;
318 
319 	sbi_extension_id = vme->u.ecall.args[7];
320 	switch (sbi_extension_id) {
321 	case SBI_EXT_ID_SRST:
322 		ret = vmexit_ecall_srst(ctx, vme);
323 		break;
324 	case SBI_EXT_ID_BASE:
325 		ret = vmexit_ecall_base(ctx, vcpu, vme);
326 		break;
327 	case SBI_EXT_ID_HSM:
328 		ret = vmexit_ecall_hsm(ctx, vcpu, vme);
329 		break;
330 	case SBI_CONSOLE_PUTCHAR:
331 	case SBI_CONSOLE_GETCHAR:
332 	default:
333 		/* Unknown SBI extension. */
334 		ret = SBI_ERR_NOT_SUPPORTED;
335 		break;
336 	}
337 
338 	error = vm_set_register(vcpu, VM_REG_GUEST_A0, ret);
339 	assert(error == 0);
340 
341 	return (VMEXIT_CONTINUE);
342 }
343 
344 
345 static int
346 vmexit_hyp(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
347     struct vm_run *vmrun)
348 {
349 	struct vm_exit *vme;
350 
351 	vme = vmrun->vm_exit;
352 
353 	printf("unhandled exception: scause %#lx\n", vme->u.hyp.scause);
354 
355 	return (VMEXIT_ABORT);
356 }
357 
358 const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
359 	[VM_EXITCODE_BOGUS]  = vmexit_bogus,
360 	[VM_EXITCODE_HYP] = vmexit_hyp,
361 	[VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
362 	[VM_EXITCODE_SUSPENDED] = vmexit_suspend,
363 	[VM_EXITCODE_DEBUG] = vmexit_debug,
364 	[VM_EXITCODE_ECALL] = vmexit_ecall,
365 };
366