xref: /freebsd/usr.sbin/bhyve/riscv/vmexit.c (revision 5036d9652a5701d00e9e40ea942c278e9f77d33d)
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 
51 #include <vmmapi.h>
52 
53 #include "bhyverun.h"
54 #include "config.h"
55 #include "debug.h"
56 #include "mem.h"
57 #include "vmexit.h"
58 #include "riscv.h"
59 
60 #define	BHYVE_VERSION	((uint64_t)__FreeBSD_version)
61 #define	SBI_VERS_MAJOR	2
62 #define	SBI_VERS_MINOR	0
63 
64 static cpuset_t running_hartmask = CPUSET_T_INITIALIZER(0);
65 
66 void
67 vmexit_set_bsp(int hart_id)
68 {
69 
70 	CPU_SET_ATOMIC(hart_id, &running_hartmask);
71 }
72 
73 static int
74 vmexit_inst_emul(struct vmctx *ctx __unused, struct vcpu *vcpu,
75     struct vm_run *vmrun)
76 {
77 	struct vm_exit *vme;
78 	struct vie *vie;
79 	int err;
80 
81 	vme = vmrun->vm_exit;
82 	vie = &vme->u.inst_emul.vie;
83 
84 	err = emulate_mem(vcpu, vme->u.inst_emul.gpa, vie,
85 	    &vme->u.inst_emul.paging);
86 	if (err) {
87 		if (err == ESRCH) {
88 			EPRINTLN("Unhandled memory access to 0x%lx\n",
89 			    vme->u.inst_emul.gpa);
90 		}
91 		goto fail;
92 	}
93 
94 	return (VMEXIT_CONTINUE);
95 
96 fail:
97 	fprintf(stderr, "Failed to emulate instruction ");
98 	FPRINTLN(stderr, "at 0x%lx", vme->pc);
99 	return (VMEXIT_ABORT);
100 }
101 
102 static int
103 vmexit_suspend(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
104 {
105 	struct vm_exit *vme;
106 	enum vm_suspend_how how;
107 	int vcpuid = vcpu_id(vcpu);
108 
109 	vme = vmrun->vm_exit;
110 	how = vme->u.suspended.how;
111 
112 	fbsdrun_deletecpu(vcpuid);
113 
114 	switch (how) {
115 	case VM_SUSPEND_RESET:
116 		exit(0);
117 	case VM_SUSPEND_POWEROFF:
118 		if (get_config_bool_default("destroy_on_poweroff", false))
119 			vm_destroy(ctx);
120 		exit(1);
121 	case VM_SUSPEND_HALT:
122 		exit(2);
123 	default:
124 		fprintf(stderr, "vmexit_suspend: invalid reason %d\n", how);
125 		exit(100);
126 	}
127 
128 	/* NOT REACHED. */
129 
130 	return (0);
131 }
132 
133 static int
134 vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
135     struct vm_run *vmrun __unused)
136 {
137 
138 	return (VMEXIT_CONTINUE);
139 }
140 
141 static int
142 vmexit_bogus(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
143     struct vm_run *vmrun __unused)
144 {
145 
146 	return (VMEXIT_CONTINUE);
147 }
148 
149 static int
150 vmm_sbi_probe_extension(int ext_id)
151 {
152 
153 	switch (ext_id) {
154 	case SBI_EXT_ID_HSM:
155 	case SBI_EXT_ID_TIME:
156 	case SBI_EXT_ID_IPI:
157 	case SBI_EXT_ID_RFNC:
158 	case SBI_EXT_ID_SRST:
159 	case SBI_CONSOLE_PUTCHAR:
160 	case SBI_CONSOLE_GETCHAR:
161 		break;
162 	default:
163 		return (0);
164 	}
165 
166 	return (1);
167 }
168 
169 static void
170 vmexit_ecall_time(struct vmctx *ctx __unused, struct vm_exit *vme __unused)
171 {
172 
173 }
174 
175 static void
176 vmexit_ecall_hsm(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
177     struct vm_exit *vme)
178 {
179 	struct vcpu *newvcpu;
180 	uint64_t hart_id;
181 	int func_id;
182 	int error;
183 	int ret;
184 
185 	hart_id = vme->u.ecall.args[0];
186 	func_id = vme->u.ecall.args[6];
187 
188 	ret = -1;
189 
190 	if (HART_TO_CPU(hart_id) >= (uint64_t)guest_ncpus)
191 		goto done;
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 		vm_resume_cpu(newvcpu);
211 		CPU_SET_ATOMIC(hart_id, &running_hartmask);
212 
213 		ret = 0;
214 		break;
215 	case SBI_HSM_HART_STOP:
216 		if (!CPU_ISSET(hart_id, &running_hartmask))
217 			break;
218 		CPU_CLR_ATOMIC(hart_id, &running_hartmask);
219 		vm_suspend_cpu(newvcpu);
220 		ret = 0;
221 		break;
222 	case SBI_HSM_HART_STATUS:
223 		/* TODO. */
224 		break;
225 	default:
226 		break;
227 	}
228 
229 done:
230 	error = vm_set_register(vcpu, VM_REG_GUEST_A0, ret);
231 	assert(error == 0);
232 }
233 
234 static void
235 vmexit_ecall_base(struct vmctx *ctx __unused, struct vcpu *vcpu,
236     struct vm_exit *vme)
237 {
238 	int sbi_function_id;
239 	int ext_id;
240 	int error;
241 	uint32_t val;
242 	int ret;
243 
244 	sbi_function_id = vme->u.ecall.args[6];
245 
246 	ret = 0;
247 
248 	switch (sbi_function_id) {
249 	case SBI_BASE_GET_SPEC_VERSION:
250 		val = SBI_VERS_MAJOR << SBI_SPEC_VERS_MAJOR_OFFSET;
251 		val |= SBI_VERS_MINOR << SBI_SPEC_VERS_MINOR_OFFSET;
252 		break;
253 	case SBI_BASE_GET_IMPL_ID:
254 		val = SBI_IMPL_ID_BHYVE;
255 		break;
256 	case SBI_BASE_GET_IMPL_VERSION:
257 		val = BHYVE_VERSION;
258 		break;
259 	case SBI_BASE_PROBE_EXTENSION:
260 		ext_id = vme->u.ecall.args[0];
261 		val = vmm_sbi_probe_extension(ext_id);
262 		break;
263 	case SBI_BASE_GET_MVENDORID:
264 		val = MVENDORID_UNIMPL;
265 		break;
266 	case SBI_BASE_GET_MARCHID:
267 		val = MARCHID_UNIMPL;
268 		break;
269 	case SBI_BASE_GET_MIMPID:
270 		val = 0;
271 		break;
272 	default:
273 		ret = 1;
274 		break;
275 	}
276 
277 	error = vm_set_register(vcpu, VM_REG_GUEST_A0, ret);
278 	assert(error == 0);
279 
280 	if (ret == 0) {
281 		error = vm_set_register(vcpu, VM_REG_GUEST_A1, val);
282 		assert(error == 0);
283 	}
284 }
285 
286 static void
287 vmexit_ecall_srst(struct vmctx *ctx, struct vm_exit *vme)
288 {
289 	enum vm_suspend_how how;
290 	int func_id;
291 	int type;
292 
293 	func_id = vme->u.ecall.args[6];
294 	type = vme->u.ecall.args[0];
295 
296 	switch (func_id) {
297 	case SBI_SRST_SYSTEM_RESET:
298 		switch (type) {
299 		case SBI_SRST_TYPE_SHUTDOWN:
300 		case SBI_SRST_TYPE_COLD_REBOOT:
301 		case SBI_SRST_TYPE_WARM_REBOOT:
302 			how = VM_SUSPEND_POWEROFF;
303 			vm_suspend(ctx, how);
304 			break;
305 		default:
306 			break;
307 		}
308 	default:
309 		break;
310 	}
311 }
312 
313 static int
314 vmexit_ecall(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
315 {
316 	int sbi_extension_id;
317 	struct vm_exit *vme;
318 
319 	vme = vmrun->vm_exit;
320 
321 	sbi_extension_id = vme->u.ecall.args[7];
322 	switch (sbi_extension_id) {
323 	case SBI_EXT_ID_SRST:
324 		vmexit_ecall_srst(ctx, vme);
325 		break;
326 	case SBI_EXT_ID_BASE:
327 		vmexit_ecall_base(ctx, vcpu, vme);
328 		break;
329 	case SBI_EXT_ID_TIME:
330 		vmexit_ecall_time(ctx, vme);
331 		break;
332 	case SBI_EXT_ID_HSM:
333 		vmexit_ecall_hsm(ctx, vcpu, vme);
334 		break;
335 	case SBI_CONSOLE_PUTCHAR:
336 	case SBI_CONSOLE_GETCHAR:
337 	default:
338 		/* Unknown SBI extension. */
339 		break;
340 	}
341 
342 	return (VMEXIT_CONTINUE);
343 }
344 
345 
346 static int
347 vmexit_hyp(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
348     struct vm_run *vmrun)
349 {
350 	struct vm_exit *vme;
351 
352 	vme = vmrun->vm_exit;
353 
354 	printf("unhandled exception: scause %#lx\n", vme->u.hyp.scause);
355 
356 	return (VMEXIT_ABORT);
357 }
358 
359 const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
360 	[VM_EXITCODE_BOGUS]  = vmexit_bogus,
361 	[VM_EXITCODE_HYP] = vmexit_hyp,
362 	[VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
363 	[VM_EXITCODE_SUSPENDED] = vmexit_suspend,
364 	[VM_EXITCODE_DEBUG] = vmexit_debug,
365 	[VM_EXITCODE_ECALL] = vmexit_ecall,
366 };
367