From a944cd34c45fc18969b81eb0f62c7fff39c771a3 Mon Sep 17 00:00:00 2001 From: dv Date: Wed, 20 Apr 2022 12:49:20 +0000 Subject: [PATCH] Add vmm(4) regress, disabled for now. ok rob@ --- regress/sys/arch/amd64/vmm/Makefile | 9 + regress/sys/arch/amd64/vmm/vcpu.c | 290 ++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 regress/sys/arch/amd64/vmm/Makefile create mode 100644 regress/sys/arch/amd64/vmm/vcpu.c diff --git a/regress/sys/arch/amd64/vmm/Makefile b/regress/sys/arch/amd64/vmm/Makefile new file mode 100644 index 00000000000..e7886aea765 --- /dev/null +++ b/regress/sys/arch/amd64/vmm/Makefile @@ -0,0 +1,9 @@ + +PROGS = vcpu +CFLAGS = -O2 -Wall + +REGRESS_TARGETS += run-regress-vcpu +run-regress-vcpu: vcpu + ${SUDO} ./vcpu + +.include diff --git a/regress/sys/arch/amd64/vmm/vcpu.c b/regress/sys/arch/amd64/vmm/vcpu.c new file mode 100644 index 00000000000..7f0fd97802c --- /dev/null +++ b/regress/sys/arch/amd64/vmm/vcpu.c @@ -0,0 +1,290 @@ + +/* + * Copyright (c) 2022 Dave Voutila + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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); +} -- 2.20.1