1 /* $OpenBSD: vcpu.c,v 1.8 2024/08/23 12:56:26 anton Exp $ */
2
3 /*
4 * Copyright (c) 2022 Dave Voutila <dv@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/mman.h>
22
23 #include <machine/specialreg.h>
24 #include <machine/vmmvar.h>
25
26 #include <dev/vmm/vmm.h>
27
28 #include <err.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #define KIB 1024
37 #define MIB (1UL << 20)
38 #define GIB (1024 * MIB)
39 #define VMM_NODE "/dev/vmm"
40
41 #define LOW_MEM 0
42 #define UPPER_MEM 1
43
44 #define PCKBC_AUX 0x61
45 #define PCJR_DISKCTRL 0xF0
46
47 const char *VM_NAME = "regress";
48
49 const uint8_t PUSHW_DX[] = { 0x66, 0x52 }; // pushw %dx
50 const uint8_t INS[] = { 0x6C }; // ins es:[di],dx
51 const uint8_t IN_PCJR[] = { 0xE4, 0xF0 }; // in 0xF0
52
53 /* Originally from vmd(8)'s vm.c */
54 const struct vcpu_reg_state vcpu_init_flat16 = {
55 .vrs_gprs[VCPU_REGS_RFLAGS] = 0x2,
56 .vrs_gprs[VCPU_REGS_RIP] = 0xFFF0,
57 .vrs_gprs[VCPU_REGS_RDX] = PCKBC_AUX, /* Port used by INS */
58 .vrs_gprs[VCPU_REGS_RSP] = 0x800, /* Set our stack in low mem. */
59 .vrs_crs[VCPU_REGS_CR0] = 0x60000010,
60 .vrs_sregs[VCPU_REGS_CS] = { 0xF000, 0xFFFF, 0x0093, 0xFFFF0000},
61 .vrs_sregs[VCPU_REGS_DS] = { 0x0, 0xFFFF, 0x0093, 0x0},
62 .vrs_sregs[VCPU_REGS_ES] = { 0x0, 0xFFFF, 0x0093, 0x0},
63 .vrs_sregs[VCPU_REGS_FS] = { 0x0, 0xFFFF, 0x0093, 0x0},
64 .vrs_sregs[VCPU_REGS_GS] = { 0x0, 0xFFFF, 0x0093, 0x0},
65 .vrs_sregs[VCPU_REGS_SS] = { 0x0, 0xFFFF, 0x0093, 0x0},
66 .vrs_gdtr = { 0x0, 0xFFFF, 0x0082, 0x0},
67 .vrs_idtr = { 0x0, 0xFFFF, 0x0082, 0x0},
68 .vrs_sregs[VCPU_REGS_LDTR] = { 0x0, 0xFFFF, 0x0082, 0x0},
69 .vrs_sregs[VCPU_REGS_TR] = { 0x0, 0xFFFF, 0x008B, 0x0},
70 .vrs_drs[VCPU_REGS_DR6] = 0xFFFF0FF0,
71 .vrs_drs[VCPU_REGS_DR7] = 0x400,
72 .vrs_crs[VCPU_REGS_XCR0] = XFEATURE_X87,
73 };
74
75 struct intr_handler {
76 uint16_t offset;
77 uint16_t segment;
78 };
79
80 const struct intr_handler ivt[256] = {
81 [VMM_EX_GP] = { .segment = 0x0, .offset = 0x0B5D },
82 };
83
84 int
main(int argc,char ** argv)85 main(int argc, char **argv)
86 {
87 struct vm_create_params vcp;
88 struct vm_exit *exit = NULL;
89 struct vm_info_params vip;
90 struct vm_info_result *info = NULL, *ours = NULL;
91 struct vm_resetcpu_params vresetp;
92 struct vm_run_params vrunp;
93 struct vm_terminate_params vtp;
94 struct vm_sharemem_params vsp;
95
96 struct vm_mem_range *vmr;
97 int fd, ret = 1;
98 size_t i;
99 off_t off, reset = 0xFFFFFFF0, stack = 0x800;
100 void *p;
101
102 fd = open(VMM_NODE, O_RDWR);
103 if (fd == -1)
104 err(1, "open %s", VMM_NODE);
105
106 /*
107 * 1. Create our VM with 1 vcpu and 64 MiB of memory.
108 */
109 memset(&vcp, 0, sizeof(vcp));
110 strlcpy(vcp.vcp_name, VM_NAME, sizeof(vcp.vcp_name));
111 vcp.vcp_ncpus = 1;
112
113 /* Split into two ranges, similar to how vmd(8) might do it. */
114 vcp.vcp_nmemranges = 2;
115 vcp.vcp_memranges[LOW_MEM].vmr_gpa = 0x0;
116 vcp.vcp_memranges[LOW_MEM].vmr_size = 640 * KIB;
117 vcp.vcp_memranges[UPPER_MEM].vmr_size = (64 * MIB) - (640 * KIB);
118 vcp.vcp_memranges[UPPER_MEM].vmr_gpa = (4 * GIB)
119 - vcp.vcp_memranges[UPPER_MEM].vmr_size;
120
121 /* Allocate and Initialize our guest memory. */
122 for (i = 0; i < vcp.vcp_nmemranges; i++) {
123 vmr = &vcp.vcp_memranges[i];
124 if (vmr->vmr_size % 2 != 0)
125 errx(1, "memory ranges must be multiple of 2");
126
127 p = mmap(NULL, vmr->vmr_size, PROT_READ | PROT_WRITE,
128 MAP_PRIVATE | MAP_ANON, -1, 0);
129 if (p == MAP_FAILED)
130 err(1, "mmap");
131
132 vmr->vmr_va = (vaddr_t)p;
133 printf("created mapped region %zu: { gpa: 0x%08lx, size: %lu,"
134 " hva: 0x%lx }\n", i, vmr->vmr_gpa, vmr->vmr_size,
135 vmr->vmr_va);
136
137 /* Fill with int3 instructions. */
138 memset(p, 0xcc, vmr->vmr_size);
139
140 if (i == LOW_MEM) {
141 /* Write our IVT. */
142 memcpy(p, &ivt, sizeof(ivt));
143
144 /*
145 * Set up a #GP handler that does a read from a
146 * non-existent PC Jr. Disk Controller.
147 */
148 p = (uint8_t*)((uint8_t*)p + 0xb5d);
149 memcpy(p, IN_PCJR, sizeof(IN_PCJR));
150 } else {
151 /*
152 * Write our code to the reset vector:
153 * PUSHW %dx ; inits the stack
154 * INS dx, es:[di] ; read from port in dx
155 */
156 off = reset - vmr->vmr_gpa;
157 p = (uint8_t*)p + off;
158 memcpy(p, PUSHW_DX, sizeof(PUSHW_DX));
159 p = (uint8_t*)p + sizeof(PUSHW_DX);
160 memcpy(p, INS, sizeof(INS));
161 }
162 }
163
164 if (ioctl(fd, VMM_IOC_CREATE, &vcp) == -1)
165 err(1, "VMM_IOC_CREATE");
166 printf("created vm %d named \"%s\"\n", vcp.vcp_id, vcp.vcp_name);
167
168 /*
169 * 2. Check we can create shared memory mappings.
170 */
171 memset(&vsp, 0, sizeof(vsp));
172 vsp.vsp_nmemranges = vcp.vcp_nmemranges;
173 memcpy(&vsp.vsp_memranges, &vcp.vcp_memranges,
174 sizeof(vsp.vsp_memranges));
175 vsp.vsp_vm_id = vcp.vcp_id;
176
177 /* Find some new va ranges... */
178 for (i = 0; i < vsp.vsp_nmemranges; i++) {
179 vmr = &vsp.vsp_memranges[i];
180 p = mmap(NULL, vmr->vmr_size, PROT_READ | PROT_WRITE,
181 MAP_PRIVATE | MAP_ANON, -1, 0);
182 if (p == MAP_FAILED)
183 err(1, "mmap");
184 vmr->vmr_va = (vaddr_t)p;
185 }
186
187 /* Release our mappings so vmm can replace them. */
188 for (i = 0; i < vsp.vsp_nmemranges; i++) {
189 vmr = &vsp.vsp_memranges[i];
190 munmap((void*)vmr->vmr_va, vmr->vmr_size);
191 }
192
193 /* Perform the shared mapping. */
194 if (ioctl(fd, VMM_IOC_SHAREMEM, &vsp) == -1)
195 err(1, "VMM_IOC_SHAREMEM");
196 printf("created shared memory mappings\n");
197
198 /* We should see our reset vector instructions in the new mappings. */
199 for (i = 0; i < vsp.vsp_nmemranges; i++) {
200 vmr = &vsp.vsp_memranges[i];
201 p = (void*)vmr->vmr_va;
202
203 if (i == LOW_MEM) {
204 /* Check if our IVT is there. */
205 if (memcmp(&ivt, p, sizeof(ivt)) != 0) {
206 warnx("invalid ivt");
207 goto out;
208 }
209 } else {
210 /* Check our code at the reset vector. */
211
212 }
213 printf("checked shared region %zu: { gpa: 0x%08lx, size: %lu,"
214 " hva: 0x%lx }\n", i, vmr->vmr_gpa, vmr->vmr_size,
215 vmr->vmr_va);
216 }
217 printf("validated shared memory mappings\n");
218
219 /*
220 * 3. Check that our VM exists.
221 */
222 memset(&vip, 0, sizeof(vip));
223 vip.vip_size = 0;
224 info = NULL;
225
226 if (ioctl(fd, VMM_IOC_INFO, &vip) == -1) {
227 warn("VMM_IOC_INFO(1)");
228 goto out;
229 }
230
231 if (vip.vip_size == 0) {
232 warn("no vms found");
233 goto out;
234 }
235
236 info = malloc(vip.vip_size);
237 if (info == NULL) {
238 warn("malloc");
239 goto out;
240 }
241
242 /* Second request that retrieves the VMs. */
243 vip.vip_info = info;
244 if (ioctl(fd, VMM_IOC_INFO, &vip) == -1) {
245 warn("VMM_IOC_INFO(2)");
246 goto out;
247 }
248
249 for (i = 0; i * sizeof(*info) < vip.vip_size; i++) {
250 if (info[i].vir_id == vcp.vcp_id) {
251 ours = &info[i];
252 break;
253 }
254 }
255 if (ours == NULL) {
256 warn("failed to find vm %uz", vcp.vcp_id);
257 goto out;
258 }
259
260 if (ours->vir_id != vcp.vcp_id) {
261 warnx("expected vm id %uz, got %uz", vcp.vcp_id, ours->vir_id);
262 goto out;
263 }
264 if (strncmp(ours->vir_name, VM_NAME, strlen(VM_NAME)) != 0) {
265 warnx("expected vm name \"%s\", got \"%s\"", VM_NAME,
266 ours->vir_name);
267 goto out;
268 }
269 printf("found vm %d named \"%s\"\n", vcp.vcp_id, ours->vir_name);
270 ours = NULL;
271
272 /*
273 * 4. Reset our VCPU and initialize register state.
274 */
275 memset(&vresetp, 0, sizeof(vresetp));
276 vresetp.vrp_vm_id = vcp.vcp_id;
277 vresetp.vrp_vcpu_id = 0; /* XXX SP */
278 memcpy(&vresetp.vrp_init_state, &vcpu_init_flat16,
279 sizeof(vcpu_init_flat16));
280
281 if (ioctl(fd, VMM_IOC_RESETCPU, &vresetp) == -1) {
282 warn("VMM_IOC_RESETCPU");
283 goto out;
284 }
285 printf("reset vcpu %d for vm %d\n", vresetp.vrp_vcpu_id,
286 vresetp.vrp_vm_id);
287
288 /*
289 * 5. Run the vcpu, expecting an immediate exit for IO assist.
290 */
291 exit = malloc(sizeof(*exit));
292 if (exit == NULL) {
293 warn("failed to allocate memory for vm_exit");
294 goto out;
295 }
296
297 memset(&vrunp, 0, sizeof(vrunp));
298 vrunp.vrp_exit = exit;
299 vrunp.vrp_vcpu_id = 0; /* XXX SP */
300 vrunp.vrp_vm_id = vcp.vcp_id;
301 vrunp.vrp_irqready = 1;
302
303 if (ioctl(fd, VMM_IOC_RUN, &vrunp) == -1) {
304 warn("VMM_IOC_RUN");
305 goto out;
306 }
307
308 if (vrunp.vrp_vm_id != vcp.vcp_id) {
309 warnx("expected vm id %uz, got %uz", vcp.vcp_id,
310 vrunp.vrp_vm_id);
311 goto out;
312 }
313
314 switch (vrunp.vrp_exit_reason) {
315 case SVM_VMEXIT_IOIO:
316 case VMX_EXIT_IO:
317 printf("vcpu %d on vm %d exited for io assist @ ip = 0x%llx, "
318 "cs.base = 0x%llx, ss.base = 0x%llx, rsp = 0x%llx\n",
319 vrunp.vrp_vcpu_id, vrunp.vrp_vm_id,
320 vrunp.vrp_exit->vrs.vrs_gprs[VCPU_REGS_RIP],
321 vrunp.vrp_exit->vrs.vrs_sregs[VCPU_REGS_CS].vsi_base,
322 vrunp.vrp_exit->vrs.vrs_sregs[VCPU_REGS_SS].vsi_base,
323 vrunp.vrp_exit->vrs.vrs_gprs[VCPU_REGS_RSP]);
324 break;
325 default:
326 warnx("unexpected vm exit reason: 0%04x",
327 vrunp.vrp_exit_reason);
328 goto out;
329 }
330
331 exit = vrunp.vrp_exit;
332 if (exit->vei.vei_port != PCKBC_AUX) {
333 warnx("expected io port to be PCKBC_AUX, got 0x%02x",
334 exit->vei.vei_port);
335 goto out;
336 }
337 if (exit->vei.vei_string != 1) {
338 warnx("expected string instruction (INS)");
339 goto out;
340 } else
341 printf("got expected string instruction\n");
342
343 /* Advance RIP? */
344 printf("insn_len = %u\n", exit->vei.vei_insn_len);
345 exit->vrs.vrs_gprs[VCPU_REGS_RIP] += exit->vei.vei_insn_len;
346
347 /*
348 * Inject a #GP and see if we end up at our isr.
349 */
350 vrunp.vrp_inject.vie_vector = VMM_EX_GP;
351 vrunp.vrp_inject.vie_errorcode = 0x11223344;
352 vrunp.vrp_inject.vie_type = VCPU_INJECT_EX;
353 printf("injecting exception 0x%x\n", vrunp.vrp_inject.vie_vector);
354 if (ioctl(fd, VMM_IOC_RUN, &vrunp) == -1) {
355 warn("VMM_IOC_RUN 2");
356 goto out;
357 }
358
359 switch (vrunp.vrp_exit_reason) {
360 case SVM_VMEXIT_IOIO:
361 case VMX_EXIT_IO:
362 printf("vcpu %d on vm %d exited for io assist @ ip = 0x%llx, "
363 "cs.base = 0x%llx\n", vrunp.vrp_vcpu_id, vrunp.vrp_vm_id,
364 vrunp.vrp_exit->vrs.vrs_gprs[VCPU_REGS_RIP],
365 vrunp.vrp_exit->vrs.vrs_sregs[VCPU_REGS_CS].vsi_base);
366 break;
367 default:
368 warnx("unexpected vm exit reason: 0%04x",
369 vrunp.vrp_exit_reason);
370 goto out;
371 }
372
373 if (exit->vei.vei_port != PCJR_DISKCTRL) {
374 warnx("expected NMI handler to poke PCJR_DISKCTLR, got 0x%02x",
375 exit->vei.vei_port);
376 printf("rip = 0x%llx\n", exit->vrs.vrs_gprs[VCPU_REGS_RIP]);
377 goto out;
378 }
379 printf("exception handler called\n");
380
381 /*
382 * If we made it here, we're close to passing. Any failures during
383 * cleanup will reset ret back to non-zero.
384 */
385 ret = 0;
386
387 out:
388 printf("--- RESET VECTOR @ gpa 0x%llx ---\n", reset);
389 for (i=0; i<10; i++) {
390 if (i > 0)
391 printf(" ");
392 printf("%02x", *(uint8_t*)
393 (vsp.vsp_memranges[UPPER_MEM].vmr_va + off + i));
394 }
395 printf("\n--- STACK @ gpa 0x%llx ---\n", stack);
396 for (i=0; i<16; i++) {
397 if (i > 0)
398 printf(" ");
399 printf("%02x", *(uint8_t*)(vsp.vsp_memranges[LOW_MEM].vmr_va
400 + stack - i - 1));
401 }
402 printf("\n");
403
404 /*
405 * 6. Terminate our VM and clean up.
406 */
407 memset(&vtp, 0, sizeof(vtp));
408 vtp.vtp_vm_id = vcp.vcp_id;
409 if (ioctl(fd, VMM_IOC_TERM, &vtp) == -1) {
410 warn("VMM_IOC_TERM");
411 ret = 1;
412 } else
413 printf("terminated vm %d\n", vtp.vtp_vm_id);
414
415 close(fd);
416 free(info);
417 free(exit);
418
419 /* Unmap memory. */
420 for (i = 0; i < vcp.vcp_nmemranges; i++) {
421 vmr = &vcp.vcp_memranges[i];
422 if (vmr->vmr_va) {
423 if (munmap((void *)vmr->vmr_va, vmr->vmr_size)) {
424 warn("failed to unmap orginal region %zu @ hva "
425 "0x%lx", i, vmr->vmr_va);
426 ret = 1;
427 } else
428 printf("unmapped origin region %zu @ hva "
429 "0x%lx\n", i, vmr->vmr_va);
430 }
431 vmr = &vsp.vsp_memranges[i];
432 if (vmr->vmr_va) {
433 if (munmap((void *)vmr->vmr_va, vmr->vmr_size)) {
434 warn("failed to unmap shared region %zu @ hva "
435 "0x%lx", i, vmr->vmr_va);
436 ret = 1;
437 } else
438 printf("unmapped shared region %zu @ hva "
439 "0x%lx\n", i, vmr->vmr_va);
440 }
441 }
442
443 return (ret);
444 }
445