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 default:
125 fprintf(stderr, "vmexit_suspend: invalid reason %d\n", how);
126 exit(100);
127 }
128
129 /* NOT REACHED. */
130
131 return (0);
132 }
133
134 static int
vmexit_debug(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_run * vmrun __unused)135 vmexit_debug(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
136 struct vm_run *vmrun __unused)
137 {
138
139 /*
140 * XXX-MJ sleep for a short period to avoid chewing up the CPU in the
141 * window between activation of the vCPU thread and the
142 * SBI_HSM_HART_START request.
143 */
144 usleep(1000);
145 return (VMEXIT_CONTINUE);
146 }
147
148 static int
vmexit_bogus(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_run * vmrun __unused)149 vmexit_bogus(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
150 struct vm_run *vmrun __unused)
151 {
152
153 return (VMEXIT_CONTINUE);
154 }
155
156 static int
vmm_sbi_probe_extension(int ext_id)157 vmm_sbi_probe_extension(int ext_id)
158 {
159
160 switch (ext_id) {
161 case SBI_EXT_ID_HSM:
162 case SBI_EXT_ID_TIME:
163 case SBI_EXT_ID_IPI:
164 case SBI_EXT_ID_RFNC:
165 case SBI_EXT_ID_SRST:
166 case SBI_CONSOLE_PUTCHAR:
167 case SBI_CONSOLE_GETCHAR:
168 break;
169 default:
170 return (0);
171 }
172
173 return (1);
174 }
175
176 static int
vmexit_ecall_hsm(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_exit * vme)177 vmexit_ecall_hsm(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
178 struct vm_exit *vme)
179 {
180 struct vcpu *newvcpu;
181 uint64_t hart_id;
182 int func_id;
183 int error;
184
185 hart_id = vme->u.ecall.args[0];
186 func_id = vme->u.ecall.args[6];
187
188 if (HART_TO_CPU(hart_id) >= (uint64_t)guest_ncpus)
189 return (SBI_ERR_INVALID_PARAM);
190
191 newvcpu = fbsdrun_vcpu(HART_TO_CPU(hart_id));
192 assert(newvcpu != NULL);
193
194 switch (func_id) {
195 case SBI_HSM_HART_START:
196 if (CPU_ISSET(hart_id, &running_hartmask))
197 break;
198
199 /* Set hart ID. */
200 error = vm_set_register(newvcpu, VM_REG_GUEST_A0, hart_id);
201 assert(error == 0);
202
203 /* Set PC. */
204 error = vm_set_register(newvcpu, VM_REG_GUEST_SEPC,
205 vme->u.ecall.args[1]);
206 assert(error == 0);
207
208 /* Pass private data. */
209 error = vm_set_register(newvcpu, VM_REG_GUEST_A1,
210 vme->u.ecall.args[2]);
211 assert(error == 0);
212
213 vm_resume_cpu(newvcpu);
214 CPU_SET_ATOMIC(hart_id, &running_hartmask);
215 break;
216 case SBI_HSM_HART_STOP:
217 if (!CPU_ISSET(hart_id, &running_hartmask))
218 break;
219 CPU_CLR_ATOMIC(hart_id, &running_hartmask);
220 vm_suspend_cpu(newvcpu);
221 break;
222 case SBI_HSM_HART_STATUS:
223 /* TODO. */
224 break;
225 default:
226 return (SBI_ERR_NOT_SUPPORTED);
227 }
228
229 return (SBI_SUCCESS);
230 }
231
232 static int
vmexit_ecall_base(struct vmctx * ctx __unused,struct vcpu * vcpu,struct vm_exit * vme)233 vmexit_ecall_base(struct vmctx *ctx __unused, struct vcpu *vcpu,
234 struct vm_exit *vme)
235 {
236 int sbi_function_id;
237 uint32_t val;
238 int ext_id;
239 int error;
240
241 sbi_function_id = vme->u.ecall.args[6];
242
243 switch (sbi_function_id) {
244 case SBI_BASE_GET_SPEC_VERSION:
245 val = SBI_VERS_MAJOR << SBI_SPEC_VERS_MAJOR_OFFSET;
246 val |= SBI_VERS_MINOR << SBI_SPEC_VERS_MINOR_OFFSET;
247 break;
248 case SBI_BASE_GET_IMPL_ID:
249 val = SBI_IMPL_ID_BHYVE;
250 break;
251 case SBI_BASE_GET_IMPL_VERSION:
252 val = BHYVE_VERSION;
253 break;
254 case SBI_BASE_PROBE_EXTENSION:
255 ext_id = vme->u.ecall.args[0];
256 val = vmm_sbi_probe_extension(ext_id);
257 break;
258 case SBI_BASE_GET_MVENDORID:
259 val = MVENDORID_UNIMPL;
260 break;
261 case SBI_BASE_GET_MARCHID:
262 val = MARCHID_UNIMPL;
263 break;
264 case SBI_BASE_GET_MIMPID:
265 val = 0;
266 break;
267 default:
268 return (SBI_ERR_NOT_SUPPORTED);
269 }
270
271 error = vm_set_register(vcpu, VM_REG_GUEST_A1, val);
272 assert(error == 0);
273
274 return (SBI_SUCCESS);
275 }
276
277 static int
vmexit_ecall_srst(struct vmctx * ctx,struct vm_exit * vme)278 vmexit_ecall_srst(struct vmctx *ctx, struct vm_exit *vme)
279 {
280 enum vm_suspend_how how;
281 int func_id;
282 int type;
283
284 func_id = vme->u.ecall.args[6];
285 type = vme->u.ecall.args[0];
286
287 switch (func_id) {
288 case SBI_SRST_SYSTEM_RESET:
289 switch (type) {
290 case SBI_SRST_TYPE_SHUTDOWN:
291 case SBI_SRST_TYPE_COLD_REBOOT:
292 case SBI_SRST_TYPE_WARM_REBOOT:
293 how = VM_SUSPEND_POWEROFF;
294 vm_suspend(ctx, how);
295 break;
296 default:
297 return (SBI_ERR_NOT_SUPPORTED);
298 }
299 break;
300 default:
301 return (SBI_ERR_NOT_SUPPORTED);
302 }
303
304 return (SBI_SUCCESS);
305 }
306
307 static int
vmexit_ecall(struct vmctx * ctx,struct vcpu * vcpu,struct vm_run * vmrun)308 vmexit_ecall(struct vmctx *ctx, struct vcpu *vcpu, struct vm_run *vmrun)
309 {
310 int sbi_extension_id;
311 struct vm_exit *vme;
312 int error;
313 int ret;
314
315 vme = vmrun->vm_exit;
316
317 sbi_extension_id = vme->u.ecall.args[7];
318 switch (sbi_extension_id) {
319 case SBI_EXT_ID_SRST:
320 ret = vmexit_ecall_srst(ctx, vme);
321 break;
322 case SBI_EXT_ID_BASE:
323 ret = vmexit_ecall_base(ctx, vcpu, vme);
324 break;
325 case SBI_EXT_ID_HSM:
326 ret = vmexit_ecall_hsm(ctx, vcpu, vme);
327 break;
328 case SBI_CONSOLE_PUTCHAR:
329 case SBI_CONSOLE_GETCHAR:
330 default:
331 /* Unknown SBI extension. */
332 ret = SBI_ERR_NOT_SUPPORTED;
333 break;
334 }
335
336 error = vm_set_register(vcpu, VM_REG_GUEST_A0, ret);
337 assert(error == 0);
338
339 return (VMEXIT_CONTINUE);
340 }
341
342
343 static int
vmexit_hyp(struct vmctx * ctx __unused,struct vcpu * vcpu __unused,struct vm_run * vmrun)344 vmexit_hyp(struct vmctx *ctx __unused, struct vcpu *vcpu __unused,
345 struct vm_run *vmrun)
346 {
347 struct vm_exit *vme;
348
349 vme = vmrun->vm_exit;
350
351 printf("unhandled exception: scause %#lx\n", vme->u.hyp.scause);
352
353 return (VMEXIT_ABORT);
354 }
355
356 const vmexit_handler_t vmexit_handlers[VM_EXITCODE_MAX] = {
357 [VM_EXITCODE_BOGUS] = vmexit_bogus,
358 [VM_EXITCODE_HYP] = vmexit_hyp,
359 [VM_EXITCODE_INST_EMUL] = vmexit_inst_emul,
360 [VM_EXITCODE_SUSPENDED] = vmexit_suspend,
361 [VM_EXITCODE_DEBUG] = vmexit_debug,
362 [VM_EXITCODE_ECALL] = vmexit_ecall,
363 };
364