diff options
Diffstat (limited to 'usr/src/test/bhyve-tests/tests/common/in_guest.c')
| -rw-r--r-- | usr/src/test/bhyve-tests/tests/common/in_guest.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/usr/src/test/bhyve-tests/tests/common/in_guest.c b/usr/src/test/bhyve-tests/tests/common/in_guest.c new file mode 100644 index 0000000000..31bebc0665 --- /dev/null +++ b/usr/src/test/bhyve-tests/tests/common/in_guest.c @@ -0,0 +1,532 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2022 Oxide Computer Company + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <strings.h> +#include <assert.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/segments.h> +#include <sys/psw.h> +#include <sys/controlregs.h> +#include <sys/sysmacros.h> +#include <sys/varargs.h> +#include <sys/debug.h> + +#include <sys/vmm.h> +#include <sys/vmm_dev.h> +#include <vmmapi.h> + +#include "in_guest.h" + + +#define PT_VALID 0x01 +#define PT_WRITABLE 0x02 +#define PT_WRITETHRU 0x08 +#define PT_NOCACHE 0x10 +#define PT_PAGESIZE 0x80 + +#define SEG_ACCESS_TYPE_MASK 0x1f +#define SEG_ACCESS_DPL_MASK 0x60 +#define SEG_ACCESS_P (1 << 7) +#define SEG_ACCESS_AVL (1 << 12) +#define SEG_ACCESS_L (1 << 13) +#define SEG_ACCESS_D (1 << 14) +#define SEG_ACCESS_G (1 << 15) +#define SEG_ACCESS_UNUSABLE (1 << 16) + + +/* + * Keep the test name and VM context around so the consumer is not required to + * pass either of them to us for subsequent test-related operations after the + * initialization has been performed. + * + * The test code is not designed to be reentrant at this point. + */ +static struct vmctx *test_vmctx = NULL; +static const char *test_name = NULL; + +static void +populate_identity_table(struct vmctx *ctx) +{ + uint64_t gpa, pte_loc; + + /* Set up 2MiB PTEs for everything up through 0xffffffff */ + for (gpa = 0, pte_loc = MEM_LOC_PAGE_TABLE_2M; + gpa < 0x100000000; + pte_loc += PAGE_SIZE) { + uint64_t *ptep = vm_map_gpa(ctx, pte_loc, PAGE_SIZE); + + for (uint_t i = 0; i < 512; i++, ptep++, gpa += 0x200000) { + *ptep = gpa | PT_VALID | PT_WRITABLE | PT_PAGESIZE; + /* Make traditional MMIO space uncachable */ + if (gpa >= 0xc0000000) { + *ptep |= PT_WRITETHRU | PT_NOCACHE; + } + } + } + assert(gpa == 0x100000000 && pte_loc == MEM_LOC_PAGE_TABLE_1G); + + uint64_t *pdep = vm_map_gpa(ctx, MEM_LOC_PAGE_TABLE_1G, PAGE_SIZE); + pdep[0] = MEM_LOC_PAGE_TABLE_2M | PT_VALID | PT_WRITABLE; + pdep[1] = (MEM_LOC_PAGE_TABLE_2M + PAGE_SIZE) | PT_VALID | PT_WRITABLE; + pdep[2] = + (MEM_LOC_PAGE_TABLE_2M + 2 * PAGE_SIZE) | PT_VALID | PT_WRITABLE; + pdep[3] = + (MEM_LOC_PAGE_TABLE_2M + 3 * PAGE_SIZE) | PT_VALID | PT_WRITABLE; + + pdep = vm_map_gpa(ctx, MEM_LOC_PAGE_TABLE_512G, PAGE_SIZE); + pdep[0] = MEM_LOC_PAGE_TABLE_1G | PT_VALID | PT_WRITABLE; +} + +static void +populate_desc_tables(struct vmctx *ctx) +{ + +} + +static void +test_cleanup(bool is_failure) +{ + if (test_vmctx != NULL) { + bool keep_on_fail = false; + + const char *keep_var; + if ((keep_var = getenv("KEEP_ON_FAIL")) != NULL) { + if (strlen(keep_var) != 0 && + strcmp(keep_var, "0") != 0) { + keep_on_fail = true; + } + } + + /* + * Destroy the instance unless the test failed and it was + * requested that we keep it around. + */ + if (!is_failure || !keep_on_fail) { + vm_destroy(test_vmctx); + } + test_vmctx = NULL; + } +} + +static void fail_finish(void) +{ + assert(test_name != NULL); + (void) printf("FAIL %s\n", test_name); + + test_cleanup(true); + exit(EXIT_FAILURE); +} + +void +test_fail_errno(int err, const char *msg) +{ + const char *err_str = strerror(err); + + (void) fprintf(stderr, "%s: %s\n", msg, err_str); + fail_finish(); +} + +void +test_fail_msg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void) vfprintf(stderr, fmt, ap); + + fail_finish(); +} + +void +test_fail_vmexit(const struct vm_exit *vexit) +{ + const char *hdr_fmt = "Unexpected %s exit:\n\t%%rip: %lx\n"; + + switch (vexit->exitcode) { + case VM_EXITCODE_INOUT: + (void) fprintf(stderr, hdr_fmt, "IN/OUT", vexit->rip); + (void) fprintf(stderr, + "\teax: %08x\n" + "\tport: %04x\n" + "\tbytes: %u\n" + "\tflags: %x\n", + vexit->u.inout.eax, + vexit->u.inout.port, + vexit->u.inout.bytes, + vexit->u.inout.flags); + break; + case VM_EXITCODE_MMIO: + (void) fprintf(stderr, hdr_fmt, "MMIO", vexit->rip); + (void) fprintf(stderr, + "\tbytes: %u\n" + "\ttype: %s\n" + "\tgpa: %x\n" + "\tdata: %016x\n", + vexit->u.mmio.bytes, + vexit->u.mmio.read == 0 ? "write" : "read", + vexit->u.mmio.gpa, + vexit->u.mmio.data); + break; + case VM_EXITCODE_VMX: + (void) fprintf(stderr, hdr_fmt, "VMX", vexit->rip); + (void) fprintf(stderr, + "\tstatus: %x\n" + "\treason: %x\n" + "\tqualification: %lx\n" + "\tinst_type: %x\n" + "\tinst_error: %x\n", + vexit->u.vmx.status, + vexit->u.vmx.exit_reason, + vexit->u.vmx.exit_qualification, + vexit->u.vmx.inst_type, + vexit->u.vmx.inst_error); + break; + case VM_EXITCODE_SVM: + (void) fprintf(stderr, hdr_fmt, "SVM", vexit->rip); + break; + case VM_EXITCODE_INST_EMUL: + (void) fprintf(stderr, hdr_fmt, "instruction emulation", + vexit->rip); + const uint_t len = vexit->u.inst_emul.num_valid > 0 ? + vexit->u.inst_emul.num_valid : 15; + (void) fprintf(stderr, "\tinstruction bytes: ["); + for (uint_t i = 0; i < len; i++) { + (void) fprintf(stderr, "%s%02x", + i == 0 ? "" : ", ", + vexit->u.inst_emul.inst[i]); + } + (void) fprintf(stderr, "]\n"); + break; + case VM_EXITCODE_SUSPENDED: + (void) fprintf(stderr, hdr_fmt, "suspend", vexit->rip); + switch (vexit->u.suspended.how) { + case VM_SUSPEND_RESET: + (void) fprintf(stderr, "\thow: reset"); + break; + case VM_SUSPEND_POWEROFF: + (void) fprintf(stderr, "\thow: poweroff"); + break; + case VM_SUSPEND_HALT: + (void) fprintf(stderr, "\thow: halt"); + break; + case VM_SUSPEND_TRIPLEFAULT: + (void) fprintf(stderr, "\thow: triple-fault"); + break; + default: + (void) fprintf(stderr, "\thow: unknown - %d", + vexit->u.suspended.how); + break; + } + break; + default: + (void) fprintf(stderr, "Unexpected code %d exit:\n" + "\t%%rip: %lx\n", vexit->exitcode, vexit->rip); + break; + } + fail_finish(); +} + +void +test_pass(void) +{ + assert(test_name != NULL); + (void) printf("PASS %s\n", test_name); + test_cleanup(false); + exit(EXIT_SUCCESS); +} + +static int +load_payload(struct vmctx *ctx) +{ + extern uint8_t payload_data; + extern uint32_t payload_size; + + const uint32_t len = payload_size; + const uint32_t cap = (MEM_TOTAL_SZ - MEM_LOC_PAYLOAD); + + if (len > cap) { + test_fail_msg("Payload size %u > capacity %u\n", len, cap); + } + + const size_t map_len = P2ROUNDUP(len, PAGE_SIZE); + void *outp = vm_map_gpa(ctx, MEM_LOC_PAYLOAD, map_len); + bcopy(&payload_data, outp, len); + + return (0); +} + +struct vmctx * +test_initialize(const char *tname) +{ + char vm_name[VM_MAX_NAMELEN]; + int err; + struct vmctx *ctx; + + assert(test_vmctx == NULL); + assert(test_name == NULL); + + test_name = strdup(tname); + (void) snprintf(vm_name, sizeof (vm_name), "bhyve-test-%s-%d", + test_name, getpid()); + + err = vm_create(vm_name, 0); + if (err != 0) { + test_fail_errno(err, "Could not create VM"); + } + + ctx = vm_open(vm_name); + if (ctx == NULL) { + test_fail_errno(errno, "Could not open VM"); + } + test_vmctx = ctx; + + err = vm_setup_memory(ctx, MEM_TOTAL_SZ, VM_MMAP_ALL); + if (err != 0) { + test_fail_errno(err, "Could not set up VM memory"); + } + + populate_identity_table(ctx); + populate_desc_tables(ctx); + + err = load_payload(ctx); + if (err != 0) { + test_fail_errno(err, "Could not load payload"); + } + + return (ctx); +} + +int +test_setup_vcpu(struct vmctx *ctx, int vcpu, uint64_t rip, uint64_t rsp) +{ + int err; + + err = vm_activate_cpu(ctx, vcpu); + if (err != 0 && err != EBUSY) { + return (err); + } + + /* + * Granularity bit important here for VMX validity: + * "If any bit in the limit field in the range 31:20 is 1, G must be 1" + */ + err = vm_set_desc(ctx, vcpu, VM_REG_GUEST_CS, 0, UINT32_MAX, + SDT_MEMERA | SEG_ACCESS_P | SEG_ACCESS_L | SEG_ACCESS_G); + if (err != 0) { + return (err); + } + + err = vm_set_desc(ctx, vcpu, VM_REG_GUEST_SS, 0, UINT32_MAX, + SDT_MEMRWA | SEG_ACCESS_P | SEG_ACCESS_L | + SEG_ACCESS_D | SEG_ACCESS_G); + if (err != 0) { + return (err); + } + + err = vm_set_desc(ctx, vcpu, VM_REG_GUEST_DS, 0, UINT32_MAX, + SDT_MEMRWA | SEG_ACCESS_P | SEG_ACCESS_D | SEG_ACCESS_G); + if (err != 0) { + return (err); + } + + /* + * While SVM will happilly run with an otherwise unusable TR, VMX + * includes it among its entry checks. + */ + err = vm_set_desc(ctx, vcpu, VM_REG_GUEST_TR, MEM_LOC_TSS, 0xff, + SDT_SYSTSSBSY | SEG_ACCESS_P); + if (err != 0) { + return (err); + } + err = vm_set_desc(ctx, vcpu, VM_REG_GUEST_GDTR, MEM_LOC_GDT, 0x1ff, 0); + if (err != 0) { + return (err); + } + err = vm_set_desc(ctx, vcpu, VM_REG_GUEST_IDTR, MEM_LOC_IDT, 0xfff, 0); + if (err != 0) { + return (err); + } + + /* Mark unused segments as explicitly unusable (for VMX) */ + const int unsable_segs[] = { + VM_REG_GUEST_ES, + VM_REG_GUEST_FS, + VM_REG_GUEST_GS, + VM_REG_GUEST_LDTR, + }; + for (uint_t i = 0; i < ARRAY_SIZE(unsable_segs); i++) { + err = vm_set_desc(ctx, vcpu, unsable_segs[i], 0, 0, + SEG_ACCESS_UNUSABLE); + if (err != 0) { + return (err); + } + } + + /* Place CPU directly in long mode */ + const int regnums[] = { + VM_REG_GUEST_CR0, + VM_REG_GUEST_CR3, + VM_REG_GUEST_CR4, + VM_REG_GUEST_EFER, + VM_REG_GUEST_RFLAGS, + VM_REG_GUEST_RIP, + VM_REG_GUEST_RSP, + VM_REG_GUEST_CS, + VM_REG_GUEST_SS, + VM_REG_GUEST_DS, + VM_REG_GUEST_TR, + }; + uint64_t regvals[] = { + CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_ET | CR0_TS | + CR0_MP | CR0_PE, + MEM_LOC_PAGE_TABLE_512G, + CR4_DE | CR4_PSE | CR4_PAE | CR4_MCE | CR4_PGE | CR4_FSGSBASE, + AMD_EFER_SCE | AMD_EFER_LME | AMD_EFER_LMA | AMD_EFER_NXE, + /* start with interrupts disabled */ + PS_MB1, + rip, + rsp, + (GDT_KCODE << 3), + (GDT_KDATA << 3), + (GDT_KDATA << 3), + (GDT_KTSS << 3), + }; + assert(ARRAY_SIZE(regnums) == ARRAY_SIZE(regvals)); + + err = vm_set_register_set(ctx, vcpu, ARRAY_SIZE(regnums), regnums, + regvals); + if (err != 0) { + return (err); + } + + err = vm_set_run_state(ctx, vcpu, VRS_RUN, 0); + if (err != 0) { + return (err); + } + + return (0); +} + +static enum vm_exit_kind +which_exit_kind(struct vm_entry *ventry, const struct vm_exit *vexit) +{ + const struct vm_inout *inout = &vexit->u.inout; + + switch (vexit->exitcode) { + case VM_EXITCODE_BOGUS: + case VM_EXITCODE_REQIDLE: + bzero(ventry, sizeof (ventry)); + return (VEK_REENTR); + case VM_EXITCODE_INOUT: + if (inout->port == IOP_TEST_RESULT && + (inout->flags & INOUT_IN) == 0) { + if (inout->eax == 0) { + return (VEK_TEST_PASS); + } else { + return (VEK_TEST_FAIL); + } + } + break; + default: + break; + } + return (VEK_UNHANDLED); +} + +enum vm_exit_kind +test_run_vcpu(struct vmctx *ctx, int vcpu, struct vm_entry *ventry, + struct vm_exit *vexit) +{ + int err; + + err = vm_run(ctx, vcpu, ventry, vexit); + if (err != 0) { + test_fail_errno(err, "Failure during vcpu entry"); + } + + return (which_exit_kind(ventry, vexit)); +} + +void +ventry_fulfill_inout(const struct vm_exit *vexit, struct vm_entry *ventry, + uint32_t data) +{ + VERIFY3U(vexit->exitcode, ==, VM_EXITCODE_INOUT); + + ventry->cmd = VEC_FULFILL_INOUT; + bcopy(&vexit->u.inout, &ventry->u.inout, sizeof (struct vm_inout)); + if ((ventry->u.inout.flags & INOUT_IN) != 0) { + ventry->u.inout.eax = data; + } +} + +void +ventry_fulfill_mmio(const struct vm_exit *vexit, struct vm_entry *ventry, + uint64_t data) +{ + VERIFY3U(vexit->exitcode, ==, VM_EXITCODE_MMIO); + + ventry->cmd = VEC_FULFILL_MMIO; + bcopy(&vexit->u.mmio, &ventry->u.mmio, sizeof (struct vm_mmio)); + if (ventry->u.mmio.read != 0) { + ventry->u.mmio.data = data; + } +} + +bool +vexit_match_inout(const struct vm_exit *vexit, bool is_read, uint16_t port, + uint_t len, uint32_t *valp) +{ + if (vexit->exitcode != VM_EXITCODE_INOUT) { + return (false); + } + + const uint_t flag = is_read ? INOUT_IN : 0; + if (vexit->u.inout.port != port || + vexit->u.inout.bytes != len || + (vexit->u.inout.flags & INOUT_IN) != flag) { + return (false); + } + + if (!is_read && valp != NULL) { + *valp = vexit->u.inout.eax; + } + return (true); +} + +bool +vexit_match_mmio(const struct vm_exit *vexit, bool is_read, uint64_t addr, + uint_t len, uint64_t *valp) +{ + if (vexit->exitcode != VM_EXITCODE_MMIO) { + return (false); + } + + if (vexit->u.mmio.gpa != addr || + vexit->u.mmio.bytes != len || + (vexit->u.mmio.read != 0) != is_read) { + return (false); + } + + if (!is_read && valp != NULL) { + *valp = vexit->u.mmio.data; + } + return (true); +} |
