Add vmm(4) regress, disabled for now. ok rob@
authordv <dv@openbsd.org>
Wed, 20 Apr 2022 12:49:20 +0000 (12:49 +0000)
committerdv <dv@openbsd.org>
Wed, 20 Apr 2022 12:49:20 +0000 (12:49 +0000)
regress/sys/arch/amd64/vmm/Makefile [new file with mode: 0644]
regress/sys/arch/amd64/vmm/vcpu.c [new file with mode: 0644]

diff --git a/regress/sys/arch/amd64/vmm/Makefile b/regress/sys/arch/amd64/vmm/Makefile
new file mode 100644 (file)
index 0000000..e7886ae
--- /dev/null
@@ -0,0 +1,9 @@
+
+PROGS =                vcpu
+CFLAGS =               -O2 -Wall
+
+REGRESS_TARGETS +=     run-regress-vcpu
+run-regress-vcpu: vcpu
+       ${SUDO} ./vcpu
+
+.include <bsd.regress.mk>
diff --git a/regress/sys/arch/amd64/vmm/vcpu.c b/regress/sys/arch/amd64/vmm/vcpu.c
new file mode 100644 (file)
index 0000000..7f0fd97
--- /dev/null
@@ -0,0 +1,290 @@
+
+/*
+ * Copyright (c) 2022 Dave Voutila <dv@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <machine/specialreg.h>
+#include <machine/vmmvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define KIB            1024
+#define MIB            (1 << 20)
+#define VMM_NODE       "/dev/vmm"
+
+const char             *VM_NAME = "regress";
+
+/* Originally from vmd(8)'s vm.c */
+const struct vcpu_reg_state vcpu_init_flat16 = {
+       .vrs_gprs[VCPU_REGS_RFLAGS] = 0x2,
+       .vrs_gprs[VCPU_REGS_RIP] = 0xFFF0,
+       .vrs_gprs[VCPU_REGS_RSP] = 0x0,
+       .vrs_crs[VCPU_REGS_CR0] = 0x60000010,
+       .vrs_crs[VCPU_REGS_CR3] = 0,
+       .vrs_sregs[VCPU_REGS_CS] = { 0xF000, 0xFFFF, 0x809F, 0xF0000},
+       .vrs_sregs[VCPU_REGS_DS] = { 0x0, 0xFFFF, 0x8093, 0x0},
+       .vrs_sregs[VCPU_REGS_ES] = { 0x0, 0xFFFF, 0x8093, 0x0},
+       .vrs_sregs[VCPU_REGS_FS] = { 0x0, 0xFFFF, 0x8093, 0x0},
+       .vrs_sregs[VCPU_REGS_GS] = { 0x0, 0xFFFF, 0x8093, 0x0},
+       .vrs_sregs[VCPU_REGS_SS] = { 0x0, 0xFFFF, 0x8093, 0x0},
+       .vrs_gdtr = { 0x0, 0xFFFF, 0x0, 0x0},
+       .vrs_idtr = { 0x0, 0xFFFF, 0x0, 0x0},
+       .vrs_sregs[VCPU_REGS_LDTR] = { 0x0, 0xFFFF, 0x0082, 0x0},
+       .vrs_sregs[VCPU_REGS_TR] = { 0x0, 0xFFFF, 0x008B, 0x0},
+       .vrs_msrs[VCPU_REGS_EFER] = 0ULL,
+       .vrs_drs[VCPU_REGS_DR0] = 0x0,
+       .vrs_drs[VCPU_REGS_DR1] = 0x0,
+       .vrs_drs[VCPU_REGS_DR2] = 0x0,
+       .vrs_drs[VCPU_REGS_DR3] = 0x0,
+       .vrs_drs[VCPU_REGS_DR6] = 0xFFFF0FF0,
+       .vrs_drs[VCPU_REGS_DR7] = 0x400,
+       .vrs_msrs[VCPU_REGS_STAR] = 0ULL,
+       .vrs_msrs[VCPU_REGS_LSTAR] = 0ULL,
+       .vrs_msrs[VCPU_REGS_CSTAR] = 0ULL,
+       .vrs_msrs[VCPU_REGS_SFMASK] = 0ULL,
+       .vrs_msrs[VCPU_REGS_KGSBASE] = 0ULL,
+       .vrs_crs[VCPU_REGS_XCR0] = XCR0_X87
+};
+
+int
+main(int argc, char **argv)
+{
+       struct vm_create_params          vcp;
+       struct vm_exit                  *exit = NULL;
+       struct vm_info_params            vip;
+       struct vm_info_result           *info = NULL, *ours = NULL;
+       struct vm_resetcpu_params        vresetp;
+       struct vm_run_params             vrunp;
+       struct vm_terminate_params       vtp;
+
+       struct vm_mem_range             *vmr;
+       int                              fd, ret = 1;
+       size_t                           i, j;
+       void                            *p;
+
+       fd = open(VMM_NODE, O_RDWR);
+       if (fd == -1) {
+               if (errno == ENODEV) {
+                       warn("SKIPPED");
+                       return (0);
+               }
+               err(1, "open");
+       }
+
+       /*
+        * 1. Create our VM with 1 vcpu and 2 MiB of memory.
+        */
+       memset(&vcp, 0, sizeof(vcp));
+       strlcpy(vcp.vcp_name, VM_NAME, sizeof(vcp.vcp_name));
+       vcp.vcp_ncpus = 1;
+
+       /* Split into two ranges, similar to how vmd(8) might do it. */
+       vcp.vcp_nmemranges = 2;
+       vcp.vcp_memranges[0].vmr_gpa = 0x0;
+       vcp.vcp_memranges[0].vmr_size = 640 * KIB;
+       vcp.vcp_memranges[1].vmr_gpa = 640 * KIB;
+       vcp.vcp_memranges[1].vmr_size = (2 * MIB) - (640 * KIB);
+
+       /* Allocate memory. */
+       for (i = 0; i < vcp.vcp_nmemranges; i++) {
+               vmr = &vcp.vcp_memranges[i];
+               p = mmap(NULL, vmr->vmr_size, PROT_READ | PROT_WRITE,
+                   MAP_PRIVATE | MAP_ANON, -1, 0);
+               if (p == MAP_FAILED)
+                       err(1, "mmap");
+
+               /*
+                * Fill with 2-byte IN instructions that read from what would
+                * be an ancient XT PC Keyboard status port. These reads will
+                * trigger vm exits.
+                */
+               if (vmr->vmr_size % 2 != 0)
+                       errx(1, "memory ranges must be multiple of 2");
+               for (j = 0; j < vmr->vmr_size; j += 2) {
+                       ((uint8_t*)p)[j + 0] = 0xE4;
+                       ((uint8_t*)p)[j + 1] = PCKBC_AUX;
+               }
+               vmr->vmr_va = (vaddr_t)p;
+               printf("mapped region %zu: { gpa: 0x%08lx, size: %lu }\n",
+                   i, vmr->vmr_gpa, vmr->vmr_size);
+       }
+
+       if (ioctl(fd, VMM_IOC_CREATE, &vcp) == -1)
+               err(1, "VMM_IOC_CREATE");
+       printf("created vm %d named \"%s\"\n", vcp.vcp_id, vcp.vcp_name);
+
+       /*
+        * 2. Check that our VM exists.
+        */
+       memset(&vip, 0, sizeof(vip));
+       vip.vip_size = 0;
+       info = NULL;
+
+       if (ioctl(fd, VMM_IOC_INFO, &vip) == -1) {
+               warn("VMM_IOC_INFO(1)");
+               goto out;
+       }
+
+       if (vip.vip_size == 0) {
+               warn("no vms found");
+               goto out;
+       }
+
+       info = malloc(vip.vip_size);
+       if (info == NULL) {
+               warn("malloc");
+               goto out;
+       }
+
+       /* Second request that retrieves the VMs. */
+       vip.vip_info = info;
+       if (ioctl(fd, VMM_IOC_INFO, &vip) == -1) {
+               warn("VMM_IOC_INFO(2)");
+               goto out;
+       }
+
+       for (int i = 0; i * sizeof(*info) < vip.vip_size; i++) {
+               if (info[i].vir_id == vcp.vcp_id) {
+                       ours = &info[i];
+                       break;
+               }
+       }
+       if (ours == NULL) {
+               warn("failed to find vm %uz\n", vcp.vcp_id);
+               goto out;
+       }
+
+       if (ours->vir_id != vcp.vcp_id) {
+               warnx("expected vm id %uz, got %uz", vcp.vcp_id, ours->vir_id);
+               goto out;
+       }
+       if (strncmp(ours->vir_name, VM_NAME, strlen(VM_NAME)) != 0) {
+               warnx("expected vm name \"%s\", got \"%s\"", VM_NAME,
+                   ours->vir_name);
+               goto out;
+       }
+       printf("found vm %d named \"%s\"\n", vcp.vcp_id, ours->vir_name);
+       ours = NULL;
+
+       /*
+        * 3. Reset our VCPU and initialize register state.
+        */
+       memset(&vresetp, 0, sizeof(vresetp));
+       vresetp.vrp_vm_id = vcp.vcp_id;
+       vresetp.vrp_vcpu_id = 0;        /* XXX SP */
+       memcpy(&vresetp.vrp_init_state, &vcpu_init_flat16,
+           sizeof(vcpu_init_flat16));
+
+       if (ioctl(fd, VMM_IOC_RESETCPU, &vresetp) == -1) {
+               warn("VMM_IOC_RESETCPU");
+               goto out;
+       }
+       printf("reset vcpu %d for vm %d\n", vresetp.vrp_vcpu_id,
+           vresetp.vrp_vm_id);
+
+       /*
+        * 4. Run the vcpu, expecting an immediate exit for IO assist.
+        */
+       exit = malloc(sizeof(*exit));
+       if (exit == NULL) {
+               warn("failed to allocate memory for vm_exit");
+               goto out;
+       }
+
+       memset(&vrunp, 0, sizeof(vrunp));
+       vrunp.vrp_exit = exit;
+       vrunp.vrp_vcpu_id = 0;          /* XXX SP */
+       vrunp.vrp_vm_id = vcp.vcp_id;
+       vrunp.vrp_irq = 0x0;
+       vrunp.vrp_irqready = 1;
+
+       if (ioctl(fd, VMM_IOC_RUN, &vrunp) == -1) {
+               warn("VMM_IOC_RUN");
+               goto out;
+       }
+
+       if (vrunp.vrp_vm_id != vcp.vcp_id) {
+               warnx("expected vm id %uz, got %uz\n", vcp.vcp_id,
+                   vrunp.vrp_vm_id);
+               goto out;
+       }
+
+       switch (vrunp.vrp_exit_reason) {
+       case SVM_VMEXIT_IOIO:
+       case VMX_EXIT_IO:
+               printf("vcpu %d on vm %d exited for io assist\n",
+                   vrunp.vrp_vcpu_id, vrunp.vrp_vm_id);
+               break;
+       default:
+               warnx("unexpected vm exit reason: 0%04x",
+                   vrunp.vrp_exit_reason);
+               goto out;
+       }
+
+       exit = vrunp.vrp_exit;
+       if (exit->vei.vei_port != PCKBC_AUX) {
+               warnx("expected io port to be PCKBC_AUX, got 0x%02x",
+                   exit->vei.vei_port);
+               goto out;
+       }
+
+       /*
+        * If we made it here, we're close to passing. Any failures during
+        * cleanup will reset ret back to non-zero.
+        */
+       ret = 0;
+
+out:
+       /*
+        * 5. Terminate our VM and clean up.
+        */
+       memset(&vtp, 0, sizeof(vtp));
+       vtp.vtp_vm_id = vcp.vcp_id;
+       if (ioctl(fd, VMM_IOC_TERM, &vtp) == -1) {
+               warn("VMM_IOC_TERM");
+               ret = 1;
+       } else
+               printf("terminated vm %d\n", vtp.vtp_vm_id);
+
+       close(fd);
+       free(info);
+       free(exit);
+
+       /* Unmap memory. */
+       for (i = 0; i < vcp.vcp_nmemranges; i++) {
+               vmr = &vcp.vcp_memranges[i];
+               if (vmr->vmr_va) {
+                       if (munmap((void *)vmr->vmr_va, vmr->vmr_size)) {
+                               warn("failed to unmap region %zu at 0x%08lx",
+                                   i, vmr->vmr_va);
+                               ret = 1;
+                       } else
+                               printf("unmapped region %zu @ gpa 0x%08lx\n",
+                                   i, vmr->vmr_gpa);
+               }
+       }
+
+       return (ret);
+}