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
vmexit_set_bsp(int hart_id)68 vmexit_set_bsp(int hart_id)
69 {
70
71 CPU_SET_ATOMIC(hart_id, &running_hartmask);
72 }
73
74 static int
vmexit_inst_emul(struct vmctx * ctx __unused,struct vcpu * vcpu,struct vm_run * vmrun)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
vmexit_suspend(struct vmctx * ctx,struct vcpu * vcpu,struct vm_run * vmrun)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
vmexit_debug(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_run * vmrun __unused)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
vmexit_bogus(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_run * vmrun __unused)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
vmm_sbi_probe_extension(int ext_id)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
vmexit_ecall_hsm(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_exit * vme)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
vmexit_ecall_base(struct vmctx * ctx __unused,struct vcpu * vcpu,struct vm_exit * vme)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
vmexit_ecall_srst(struct vmctx * ctx,struct vm_exit * vme)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
vmexit_ecall(struct vmctx * ctx,struct vcpu * vcpu,struct vm_run * vmrun)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
vmexit_hyp(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_run * vmrun)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