# # 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 # .PARALLEL: $(SUBDIRS) SUBDIRS = cmd runfiles tests include $(SRC)/test/Makefile.com # # 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 $(SRC)/Makefile.master include $(SRC)/test/Makefile.com ROOTOPTPKG = $(ROOT)/opt/bhyve-tests ROOTBIN = $(ROOTOPTPKG)/bin PROGS = bhyvetest CMDS = $(PROGS:%=$(ROOTBIN)/%) $(CMDS) : FILEMODE = 0555 all lint clean clobber: install: $(CMDS) $(CMDS): $(ROOTBIN) $(ROOTBIN): $(INS.dir) $(ROOTBIN)/%: %.ksh $(INS.rename) #!/usr/bin/ksh # # 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 # TEST_DIR="/opt/bhyve-tests" RUNNER="/opt/test-runner/bin/run" while getopts c: c; do case $c in 'c') RUN_FILE=$OPTARG if [[ ! -f $RUN_FILE ]]; then echo "Cannot read file: $RUN_FILE" exit 1 fi ;; esac done shift $((OPTIND - 1)) if [[ -z $RUN_FILE ]]; then RUN_FILE="$TEST_DIR/runfiles/default.run" fi exec $RUNNER -c $RUN_FILE # # 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 $(SRC)/Makefile.master SRCS = default.run ROOTOPTPKG = $(ROOT)/opt/bhyve-tests RUNFILES = $(ROOTOPTPKG)/runfiles OUTPUTS = $(SRCS:%=$(RUNFILES)/%) $(OUTPUTS) : FILEMODE = 0444 all: $(SRCS) install: $(OUTPUTS) clean lint clobber: $(OUTPUTS): $(RUNFILES) $(SRCS) $(RUNFILES): $(INS.dir) $(RUNFILES)/%: % $(INS.file) # # 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 2025 Oxide Computer Company [DEFAULT] pre = verbose = False quiet = False timeout = 60 post = outputdir = /var/tmp/test_results [/opt/bhyve-tests/tests/vmm] user = root tests = [ 'auto_destruct', 'cpuid_ioctl', 'datarw_constraints', 'datarw_msrs', 'datarw_vcpu', 'default_capabs', 'drv_hold', 'fpu_getset', 'import_vlapic', 'interface_version', 'legacy_destruct', 'maxcpu', 'mem_devmem', 'mem_high', 'mem_partial', 'mem_seg_map', 'npt_ops', 'pause_resume', 'self_destruct' ] [/opt/bhyve-tests/tests/kdev] user = root tests = [ 'vatpit_freq', 'vhpet_freq', 'vlapic_freq', 'vlapic_freq_periodic', 'vlapic_mmio_access', 'vlapic_msr_access', 'vpmtmr_freq', 'vrtc_ops', 'wrmsr_tsc', 'rdmsr_tsc' ] [/opt/bhyve-tests/tests/inst_emul] user = root tests = [ 'cpuid', 'cpuid_guest_state', 'imul', 'rdmsr', 'wrmsr', 'triple_fault', 'exit_paging', 'page_dirty', 'exit_consistent', 'suspend_info', 'vcpu_barrier' ] [/opt/bhyve-tests/tests/viona] user = root pre = setup post = cleanup tests = [ 'interface_version', 'create_delete', 'link_params' ] # Tests of userspace mevent system, built from cmd/bhyve [/opt/bhyve-tests/tests/mevent] tests = ['lists_delete', 'read_disable', 'read_pause', 'read_requeue', 'vnode_file'] [/opt/bhyve-tests/tests/mevent/vnode_zvol] user = root # # 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 # .PARALLEL: $(SUBDIRS) SUBDIRS = inst_emul kdev perf viona vmm include $(SRC)/test/Makefile.com # # 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 PAYLOAD_CLEANFILES = payload_start.o \ $(PAYLOADS:%=payload_%) \ $(PAYLOADS:%=payload_%.o) \ $(PAYLOADS:%=pobj_%.o) \ $(PAYLOADS:%=pobj_%.s) # Hammerhead: Set AS_CPPFLAGS globally (not just target-specific) so the # built-in %.o: %.S rule also gets the include path AS_CPPFLAGS += -I../common # Without a real runtime in the payload, the stack protector must be disabled # Hammerhead: Use colon (not :=) for target-specific variables in GNU Make. # dmake's := is conditional assignment, but GNU Make's := is simple expansion # which silently creates a regular variable instead of a target-specific one. $(PAYLOADS:%=payload_%.o): STACKPROTECT = none # Like our own kernel, prevent the compiler from using the FPU via SIMD $(PAYLOADS:%=payload_%.o): CFLAGS64 += $(STAND_FLAGS_64) payload_%: payload_start.o payload_%.o $(LD) -dn -e _start -M ../common/Mapfile.payload -o $@ $^ pobj_%.s: payload_% @echo " .data" > $@ @echo " .globl payload_data" >> $@ @echo "payload_data:" >> $@ $(ELFEXTRACT) $^ >> $@ @echo " .size payload_data, [.-payload_data]" >> $@ @echo " .align 4" >> $@ @echo " .globl payload_size" >> $@ @echo " .size payload_size, 4" >> $@ @echo "payload_size:" >> $@ @echo " .data" >> $@ @echo " .long [.-payload_data]" >> $@ pobj_%.o: pobj_%.s $(COMPILE.s) -o $@ $^ $(POST_PROCESS) %.o: ../common/%.S $(COMPILE.s) -o $@ $^ $(POST_PROCESS) %.o: ../common/%.c $(COMPILE.c) -o $@ $^ $(POST_PROCESS) # # CDDL HEADER START # # 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. # # CDDL HEADER END # # # Copyright 2022 Oxide Computer Company # $mapfile_version 2 # The .eh_frame data was ending up in front of the .text segment, causing issues # when the guest attempted to start its payload NULL_SEGMENT discard { ASSIGN_SECTION eh_discard { IS_NAME = .eh_frame; }; }; LOAD_SEGMENT payload { FLAGS = READ WRITE EXECUTE; VADDR = 0x800000; PADDR = 0x800000; ALIGN = 0x1000; # Make sure that payload_start.s`_start is the first thing in .text segment, # since when we "boot", that is where we want to begin running. ASSIGN_SECTION is_start_text { IS_NAME = .text; FILE_BASENAME = payload_start.o; }; ASSIGN_SECTION is_text { IS_NAME = .text; }; ASSIGN_SECTION is_alloc { FLAGS = ALLOC; }; IS_ORDER = is_start_text is_text is_alloc; }; /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include /* * Generate name for test VM based on the name of the test suite (and the pid). */ void name_test_vm(const char *test_suite_name, char *outp) { (void) snprintf(outp, VM_MAX_NAMELEN, "bhyve-test-%s-%d", test_suite_name, getpid()); } /* * Create a test VM. The name of the test suite will be used to derive the name * of the instance. */ struct vmctx * create_test_vm(const char *test_suite_name) { char name[VM_MAX_NAMELEN]; int res; name_test_vm(test_suite_name, name); res = vm_create(name, 0); if (res != 0) { return (NULL); } return (vm_open(name)); } /* * Given a segment ID, length, and name, allocate a memseg in the given VM. */ int alloc_memseg(struct vmctx *ctx, int segid, size_t len, const char *name) { struct vm_memseg memseg = { .segid = segid, .len = len, }; (void) strlcpy(memseg.name, name, sizeof (memseg.name)); int fd = vm_get_device_fd(ctx); return (ioctl(fd, VM_ALLOC_MEMSEG, &memseg)); } /* * Open the vmm_drv_test device. */ int open_drv_test(void) { return (open("/dev/vmm_drv_test", O_RDWR)); } /* * Test if VMM instance exists (and is not being destroyed). */ bool check_instance_usable(const char *suite_name) { char vm_name[VM_MAX_NAMELEN]; char vm_path[MAXPATHLEN]; name_test_vm(suite_name, vm_name); (void) snprintf(vm_path, sizeof (vm_path), "/dev/vmm/%s", vm_name); int fd = open(vm_path, O_RDWR, 0); if (fd < 0) { return (false); } const int destroy_pending = ioctl(fd, VM_DESTROY_PENDING, 0); (void) close(fd); return (destroy_pending == 0); } /* * Does an instance exist in /dev/vmm? (No check for in-progress destroy) */ bool check_instance_exists(const char *suite_name) { char vm_name[VM_MAX_NAMELEN]; char vm_path[MAXPATHLEN]; name_test_vm(suite_name, vm_name); (void) snprintf(vm_path, sizeof (vm_path), "/dev/vmm/%s", vm_name); return (access(vm_path, F_OK) == 0); } /* * Destroy a VMM instance via the vmmctl device. */ int destroy_instance(const char *suite_name) { int ctl_fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR); if (ctl_fd < 0) { return (-1); } struct vm_destroy_req req; name_test_vm(suite_name, req.name); if (ioctl(ctl_fd, VMM_DESTROY_VM, &req) != 0) { /* Preserve the destroy error across the close() */ int err = errno; (void) close(ctl_fd); errno = err; return (-1); } else { (void) close(ctl_fd); return (0); } } /* * Returns true if running on AMD */ bool cpu_vendor_amd(void) { uint_t regs[4]; char cpu_vendor[13]; do_cpuid(0, regs); ((uint_t *)&cpu_vendor)[0] = regs[1]; ((uint_t *)&cpu_vendor)[1] = regs[3]; ((uint_t *)&cpu_vendor)[2] = regs[2]; cpu_vendor[12] = '\0'; return (strcmp(cpu_vendor, "AuthenticAMD") == 0 || strcmp(cpu_vendor, "HygonGenuine") == 0); } /* * 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 2023 Oxide Computer Company */ #ifndef _COMMON_H_ #define _COMMON_H_ void name_test_vm(const char *, char *); struct vmctx *create_test_vm(const char *); int alloc_memseg(struct vmctx *, int, size_t, const char *); int open_drv_test(void); bool check_instance_usable(const char *); bool check_instance_exists(const char *); int destroy_instance(const char *); bool cpu_vendor_amd(void); #define PROT_ALL (PROT_READ | PROT_WRITE | PROT_EXEC) #endif /* _COMMON_H_ */ /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 uint64_t test_msg_addr = 0; static int setup_rom(struct vmctx *ctx) { const size_t seg_sz = 0x1000; const uintptr_t seg_addr = MEM_LOC_ROM; const int fd = vm_get_device_fd(ctx); int err; struct vm_memseg memseg = { .segid = VM_BOOTROM, .len = 0x1000, }; (void) strlcpy(memseg.name, "testrom", sizeof (memseg.name)); err = ioctl(fd, VM_ALLOC_MEMSEG, &memseg); if (err != 0) { return (err); } err = vm_mmap_memseg(ctx, seg_addr, VM_BOOTROM, 0, seg_sz, PROT_READ | PROT_EXEC); return (err); } 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) { } 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_name = NULL; 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(void) { fail_finish(); } 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_RDMSR: (void) fprintf(stderr, hdr_fmt, "RDMSR", vexit->rip); (void) fprintf(stderr, "\tcode: %08x\n", vexit->u.msr.code); break; case VM_EXITCODE_WRMSR: (void) fprintf(stderr, hdr_fmt, "WRMSR", vexit->rip); (void) fprintf(stderr, "\tcode: %08x\n" "\twval: %016lx\n", vexit->u.msr.code, vexit->u.msr.wval); 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); } const char * test_msg_get(struct vmctx *ctx) { /* Disregard if the message address is still NULL */ const uint64_t msg_addr = test_msg_addr; if (msg_addr == 0) { return (NULL); } /* * We want to try to map up to one page after the specified message * address, keeping in mind the end of lowmem. (The payload, and * thus message, is assumed to be in lowmem at this time.) */ const uint64_t lowmem_end = vm_get_lowmem_size(ctx); const uint64_t msg_map_end = MIN(msg_addr + PAGE_SIZE, lowmem_end); if (msg_map_end >= lowmem_end || msg_map_end <= msg_addr) { return (NULL); } const uint64_t max_msg_len = msg_map_end - msg_addr; /* * Get the mapping to that guest memory. This assumes that the payload * has provided a guest-physical address to us. */ const char *result = vm_map_gpa(ctx, msg_addr, max_msg_len); if (result == NULL) { return (NULL); } /* Demand a NUL-terminated string shorter than the map limit */ if (strnlen(result, max_msg_len) >= max_msg_len) { return (NULL); } return (result); } void test_msg_print(struct vmctx *ctx) { const char *payload_msg = test_msg_get(ctx); if (payload_msg != NULL) { (void) fprintf(stderr, "MSG: %s\n", payload_msg); } } 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); } static struct vmctx * test_initialize_opts(const char *tname, uint64_t create_flags, bool is_plain) { 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, create_flags); 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; /* No further setup required for a "plain" instance */ if (is_plain) { return (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"); } err = setup_rom(ctx); if (err != 0) { test_fail_errno(err, "Could not set up VM ROM segment"); } 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); } struct vmctx * test_initialize(const char *tname) { return (test_initialize_opts(tname, 0, false)); } struct vmctx * test_initialize_plain(const char *tname) { return (test_initialize_opts(tname, 0, true)); } struct vmctx * test_initialize_flags(const char *tname, uint64_t create_flags) { return (test_initialize_opts(tname, create_flags, false)); } void test_reinitialize(struct vmctx *ctx, uint64_t flags) { int err; if ((err = vm_reinit(ctx, flags)) != 0) { test_fail_errno(err, "Could not reinit VM"); } /* Reload tables and payload in case they were altered */ populate_identity_table(ctx); populate_desc_tables(ctx); err = load_payload(ctx); if (err != 0) { test_fail_errno(err, "Could not load payload"); } } int test_setup_vcpu(struct vcpu *vcpu, uint64_t rip, uint64_t rsp) { int err; err = vm_activate_cpu(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(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(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(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(vcpu, VM_REG_GUEST_TR, MEM_LOC_TSS, 0xff, SDT_SYSTSSBSY | SEG_ACCESS_P); if (err != 0) { return (err); } err = vm_set_desc(vcpu, VM_REG_GUEST_GDTR, MEM_LOC_GDT, 0x1ff, 0); if (err != 0) { return (err); } err = vm_set_desc(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(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(vcpu, ARRAY_SIZE(regnums), regnums, regvals); if (err != 0) { return (err); } err = vm_set_run_state(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: 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 == TEST_RESULT_PASS) { return (VEK_TEST_PASS); } else { return (VEK_TEST_FAIL); } } if (inout->port == IOP_TEST_MSG && (inout->flags & INOUT_IN) == 0 && inout->bytes == 4) { test_msg_addr = inout->eax; ventry_fulfill_inout(vexit, ventry, 0); return (VEK_TEST_MSG); } break; default: break; } return (VEK_UNHANDLED); } enum vm_exit_kind test_run_vcpu(struct vcpu *vcpu, struct vm_entry *ventry, struct vm_exit *vexit) { int err; err = vm_run(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); } /* * 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 2024 Oxide Computer Company */ #ifndef _IN_GUEST_H_ #define _IN_GUEST_H_ #include "payload_common.h" struct vmctx *test_initialize(const char *); struct vmctx *test_initialize_plain(const char *); struct vmctx *test_initialize_flags(const char *, uint64_t); void test_reinitialize(struct vmctx *, uint64_t); void test_cleanup(bool); void test_fail(void); void test_fail_errno(int err, const char *msg); void test_fail_msg(const char *fmt, ...); void test_fail_vmexit(const struct vm_exit *vexit); void test_pass(void); const char *test_msg_get(struct vmctx *); void test_msg_print(struct vmctx *); int test_setup_vcpu(struct vcpu *, uint64_t, uint64_t); enum vm_exit_kind { /* Otherwise empty vmexit which should result in immediate re-entry */ VEK_REENTR, /* Write to IOP_TEST_RESULT port with success value (0) */ VEK_TEST_PASS, /* Write to IOP_TEST_RESULT port with failure value (non-zero) */ VEK_TEST_FAIL, /* Payload emitted a message via IOP_TEST_MSG port */ VEK_TEST_MSG, /* Test specific logic must handle exit data */ VEK_UNHANDLED, }; enum vm_exit_kind test_run_vcpu(struct vcpu *, struct vm_entry *, struct vm_exit *); void ventry_fulfill_inout(const struct vm_exit *, struct vm_entry *, uint32_t); void ventry_fulfill_mmio(const struct vm_exit *, struct vm_entry *, uint64_t); bool vexit_match_inout(const struct vm_exit *, bool, uint16_t, uint_t, uint32_t *); bool vexit_match_mmio(const struct vm_exit *, bool, uint64_t, uint_t, uint64_t *); #endif /* _IN_GUEST_H_ */ /* * 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 2024 Oxide Computer Company */ #include /* * Appease the linker for tests which want to use the in-guest framework for * convenience without actually building a payload themselves. */ uint8_t payload_data = 0; uint32_t payload_size = 0; /* * 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 2023 Oxide Computer Company */ #ifndef _PAYLOAD_COMMON_H_ #define _PAYLOAD_COMMON_H_ #define MEM_TOTAL_SZ (64 * 1024 * 1024) /* 2MiB-page entries for identity-mapped table at 2MiB */ #define MEM_LOC_PAGE_TABLE_2M 0x200000 #define MEM_LOC_PAGE_TABLE_1G 0x204000 #define MEM_LOC_PAGE_TABLE_512G 0x205000 #define MEM_LOC_GDT 0x206000 #define MEM_LOC_TSS 0x206200 #define MEM_LOC_IDT 0x207000 #define MEM_LOC_HEAP 0x400000 #define MEM_LOC_STACK 0x7fff00 #define MEM_LOC_PAYLOAD 0x800000 #define MEM_LOC_ROM 0xffff000 /* IO port set aside for emitting test result */ #define IOP_TEST_RESULT 0xef00U /* IO port set aside for emitting test message strings */ #define IOP_TEST_MSG 0xef08U /* IO port set aside for emitting test value */ #define IOP_TEST_VALUE 0xef10U /* IO port set aside for inputting test param(s) */ #define IOP_TEST_PARAM IOP_TEST_PARAM0 #define IOP_TEST_PARAM0 0xef20U #define IOP_TEST_PARAM1 0xef21U #define IOP_TEST_PARAM2 0xef22U #define IOP_TEST_PARAM3 0xef23U /* Expected values emitted through IOP_TEST_RESULT */ #define TEST_RESULT_PASS 0 #define TEST_RESULT_FAIL 1 #endif /* _PAYLOAD_COMMON_H_ */ /* * 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 /* .text .globl _start _start: jmp start */ ENTRY_NP(_start) jmp start SET_SIZE(_start) /* * 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 2025 Oxide Computer Company */ #include #include "payload_common.h" /* void outb(uint16_t port, uint8_t val) */ ENTRY(outb) movw %di, %dx movb %sil, %al outb (%dx) ret SET_SIZE(outb) /* void outw(uint16_t port, uint16_t val) */ ENTRY(outw) movw %di, %dx movw %si, %ax outw (%dx) ret SET_SIZE(outb) /* void outl(uint16_t port, uint32_t val) */ ENTRY(outl) movw %di, %dx movl %esi, %eax outl (%dx) ret SET_SIZE(outl) /* uint8_t inb(uint16_t port) */ ENTRY(inb) movw %di, %dx inb (%dx) ret SET_SIZE(inb) /* uint16_t inw(uint16_t port) */ ENTRY(inw) movw %di, %dx inw (%dx) ret SET_SIZE(inw) /* uint32_t inl(uint16_t port) */ ENTRY(inl) movw %di, %dx inl (%dx) ret SET_SIZE(inl) /* uint64_t rdmsr(uint32_t msr) */ ENTRY(rdmsr) movl %edi, %ecx rdmsr shlq $32, %rdx orq %rdx, %rax ret SET_SIZE(rdmsr) /* void wrmsr(uint32_t msr, uint64_t val) */ ENTRY(wrmsr) movq %rsi, %rdx shrq $32, %rdx movl %esi, %eax movl %edi, %ecx wrmsr ret SET_SIZE(wrmsr) /* void cpuid(uint32_t in_eax, uint32_t in_ecx, uint32_t *out_regs) */ ENTRY(cpuid) pushq %rbx movl %edi, %eax movl %esi, %ecx movq %rdx, %r8 cpuid movl %eax, (%r8) movl %ebx, 4(%r8) movl %ecx, 8(%r8) movl %edx, 12(%r8) popq %rbx ret SET_SIZE(cpuid) /* uint64_t rdtsc(void) */ ENTRY(rdtsc) rdtsc shlq $32, %rdx orq %rdx, %rax ret SET_SIZE(rdtsc) /* void ud2a(void) */ ENTRY(ud2a) ud2a SET_SIZE(ud2a) /* void setcr4(uint64_t) */ ENTRY(setcr4) movq %rdi, %cr4 ret SET_SIZE(setcr4) /* uint64_t getcr4(void) */ ENTRY(getcr4) movq %cr4, %rax ret SET_SIZE(getcr4) /* void setxcr(uint32_t, uint64_t) */ ENTRY(setxcr) movq %rsi, %rdx shrq $32, %rdx movl %esi, %eax movl %edi, %ecx #xsetbv .byte 0x0f,0x01,0xd1 ret SET_SIZE(setxcr) /* uint64_t getxcr(uint32_t) */ ENTRY(getxcr) movl %edi, %ecx #xgetbv .byte 0x0f,0x01,0xd0 shlq $32, %rdx orq %rdx, %rax ret SET_SIZE(getxcr) /* void test_result_pass(void) */ ENTRY(test_result_pass) movl $IOP_TEST_RESULT, %edi movl $TEST_RESULT_PASS, %esi call outb ret SET_SIZE(test_result_pass) /* void test_result_fail(void) */ ENTRY(test_result_fail) movl $IOP_TEST_RESULT, %edi movl $TEST_RESULT_FAIL, %esi call outb ret SET_SIZE(test_result_fail) /* void test_msg(const char *) */ ENTRY(test_msg) /* * Message address is assumed to be in lower 32-bits, since that is where * the payload is currently being mapped. */ movl %edi, %esi movl $IOP_TEST_MSG, %edi call outl ret SET_SIZE(test_msg) /* * 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 2025 Oxide Computer Company */ #ifndef _PAYLOAD_UTILS_H_ #define _PAYLOAD_UTILS_H_ #include #include void outb(uint16_t, uint8_t); void outw(uint16_t, uint16_t); void outl(uint16_t, uint32_t); uint8_t inb(uint16_t); uint16_t inw(uint16_t); uint32_t inl(uint16_t); uint64_t rdmsr(uint32_t); void wrmsr(uint32_t, uint64_t); void cpuid(uint32_t, uint32_t, uint32_t *); uint64_t rdtsc(void); void ud2a(void); void setcr4(uint64_t); uint64_t getcr4(void); void setxcr(uint32_t, uint64_t); uint64_t getxcr(uint32_t); void test_result_pass(void); void test_result_fail(void); void test_msg(const char *); #define __STR2(x) #x #define __STR(x) __STR2(x) #define TEST_ABORT(msg) \ do { \ test_msg(__FILE__ ":" __STR(__LINE__) " - " msg); \ test_result_fail(); \ } while (0) #endif /* _PAYLOAD_UTILS_H_ */ # # 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 2025 Oxide Computer Company include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/Makefile.cmd.64 include $(SRC)/test/Makefile.com PROG = rdmsr \ wrmsr \ imul \ cpuid \ cpuid_guest_state \ inout # These should probably go in the `vmm` tests, but since they depend on # in-guest payloads, it is easier to build them here. PROG += triple_fault \ exit_paging \ page_dirty \ exit_consistent \ suspend_info \ vcpu_barrier # C-based payloads need additional utils object CPAYLOADS = cpuid \ cpuid_guest_state \ suspend_info PAYLOADS = $(PROG) include ../Makefile.in_guest COMMON_OBJS = in_guest.o common.o CLEANFILES = $(COMMON_OBJS) $(PAYLOAD_CLEANFILES) payload_utils.o CLOBBERFILES = $(PROG) ROOTOPTPKG = $(ROOT)/opt/bhyve-tests TESTDIR = $(ROOTOPTPKG)/tests/inst_emul CMDS = $(PROG:%=$(TESTDIR)/%) $(CMDS) : FILEMODE = 0555 CSTD= $(CSTD_GNU99) CPPFLAGS = -I$(COMPAT)/bhyve -I$(CONTRIB)/bhyve \ -I$(COMPAT)/bhyve/amd64 -I$(CONTRIB)/bhyve/amd64 \ $(CPPFLAGS.master) \ -I$(SRC)/uts/intel/io/vmm \ -I$(SRC)/uts/intel \ -I../common ASFLAGS += -D_ASM ASFLAGS64 += -D_ASM $(PROG) : LDLIBS += -lvmmapi all: $(PROG) install: all $(CMDS) clean: -$(RM) $(CLEANFILES) clobber: clean -$(RM) $(CLOBBERFILES) $(CMDS): $(TESTDIR) $(PROG) $(TESTDIR): $(INS.dir) $(TESTDIR)/%: % $(INS.file) # Hammerhead: Cancel GNU Make built-in compile-and-link rule (%: %.c) %: %.c %: %.c pobj_%.o $(COMMON_OBJS) $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) %: %.o $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) $(CPAYLOADS:%=payload_%): payload_utils.o /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" static const struct vcpu_cpuid_entry test_entries[] = { { .vce_function = 0, .vce_eax = 5, .vce_ebx = 0x74737552, .vce_edx = 0x4f206465, .vce_ecx = 0x65646978, }, /* basic "std" leaf */ { .vce_function = 1, .vce_eax = 0x100, }, /* skip 2 for a hole */ /* leaf with index matching */ { .vce_function = 3, .vce_index = 0, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x300, }, { .vce_function = 3, .vce_index = 1, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x301, }, /* leaf with index matching and a hole */ { .vce_function = 4, .vce_index = 0, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x400, }, { .vce_function = 4, .vce_index = 2, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x402, }, /* terminal "std" leaf */ { .vce_function = 5, .vce_eax = 5, .vce_ebx = 5, .vce_edx = 5, .vce_ecx = 5, }, /* base "extended" leaf */ { .vce_function = 0x80000000, .vce_eax = 0x80000001, }, /* index-match "extended" leaves */ { .vce_function = 0x80000001, .vce_index = 0x0, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x8000, }, { .vce_function = 0x80000001, .vce_index = 0x1, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x8001, }, }; int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } /* Start with test data using Intel-style fallback */ int vmfd = vm_get_device_fd(ctx); struct vm_vcpu_cpuid_config cfg = { .vvcc_vcpuid = 0, .vvcc_flags = VCC_FLAG_INTEL_FALLBACK, .vvcc_nent = ARRAY_SIZE(test_entries), /* We trust the ioctl not to alter this const value */ .vvcc_entries = (struct vcpu_cpuid_entry *)test_entries, }; err = ioctl(vmfd, VM_SET_CPUID, &cfg); if (err != 0) { test_fail_errno(err, "ioctl(VM_SET_CPUID) failed"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_TEST_PASS: test_pass(); break; case VEK_TEST_FAIL: test_fail_msg("failed result %rip: %x", vexit.rip); break; case VEK_UNHANDLED: { uint32_t val; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &val)) { /* * The payload has requested switch to AMD-style * fallback to run the second half of the test. */ cfg.vvcc_flags = 0; err = ioctl(vmfd, VM_SET_CPUID, &cfg); if (err != 0) { test_fail_errno(err, "ioctl(VM_SET_CPUID) failed"); } ventry_fulfill_inout(&vexit, &ventry, 0); } else { test_fail_vmexit(&vexit); } break; } default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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. */ /* * This test verifies that CPUID emulation properly adjusts output values that * vary with the state of the calling processor. * * Copyright 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "cpuid_guest_state.h" static const struct vcpu_cpuid_entry test_entries[] = { { .vce_function = 0, .vce_eax = TEST_CPUID_0_EAX, .vce_ebx = TEST_CPUID_0_EBX, .vce_ecx = TEST_CPUID_0_ECX, .vce_edx = TEST_CPUID_0_EDX, }, /* * Leaf 1 has the following variable bits: * * - ecx bit 27: Set only if ecx bit 26 is set and the guest has enabled * XSAVE instructions in cr4. * - edx bit 9: Set only if the local APIC is enabled in * IA32_APIC_BASE MSR (0x1B). * * eax and ebx should be unmodified. */ { .vce_function = 1, .vce_eax = TEST_CPUID_1_EAX, .vce_ebx = TEST_CPUID_1_EBX, .vce_ecx = TEST_CPUID_1_ECX, .vce_edx = TEST_CPUID_1_EDX, }, /* * Leaf D index 0 returns supported extended processor feature state * information in eax, ecx, and edx. ebx receives the size of the * XSAVE feature area for the features the guest has enabled in xcr0. * This value is not populated in the table; instead it is read by * the host with the guest's xcr0 value still intact. * * Advertise support for x87, SSE, and AVX in eax. These features have * well-known save area sizes (0x240 bytes for x87/SSE, 0x100 bytes * for AVX). */ { .vce_function = 0xD, .vce_index = 0, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = TEST_CPUID_D_0_EAX, .vce_ebx = 0, .vce_ecx = XSAVE_AREA_SIZE_MAX, .vce_edx = 0, }, /* * Leaf D index 1 is similar to index 0, except that the required size * in ebx includes the space needed to support features enabled in the * IA32_XSS MSR (0xDA0). * * On Intel platforms, the value returned in ebx also depends on whether * the appropriate state-save instructions are advertised in eax. This * behavior is not directly tested here since it is host-platform * dependent; instead this test value for eax advertises all extant * state-save instructions. */ { .vce_function = 0xD, .vce_index = 1, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = TEST_CPUID_D_1_EAX, .vce_ebx = 0, .vce_ecx = 0, .vce_edx = 0, } }; /* * Entries used to test what happens when the guest specifies an invalid CPUID * leaf/subleaf. In this array: * * - Exclude leaf 1 to test that accessing a missing leaf whose number is less * than the maximum present leaf returns all 0s (even if a fixup would be * applied had the requested leaf been present). * - Include leaf D to test that fallback to the maximum standard leaf still * applies fixups. */ static const struct vcpu_cpuid_entry fallback_test_entries[] = { { .vce_function = 0, .vce_eax = TEST_CPUID_0_EAX, .vce_ebx = TEST_CPUID_0_EBX, .vce_ecx = TEST_CPUID_0_ECX, .vce_edx = TEST_CPUID_0_EDX, }, { .vce_function = 0xD, .vce_index = 0, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = TEST_CPUID_D_0_EAX, .vce_ebx = 0, .vce_ecx = XSAVE_AREA_SIZE_MAX, .vce_edx = 0, }, { .vce_function = 0xD, .vce_index = 1, .vce_flags = VCE_FLAG_MATCH_INDEX, .vce_eax = 0x0000000F, .vce_ebx = 0, .vce_ecx = 0, .vce_edx = 0, } }; int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; enum vm_exit_kind kind; /* * Rely on legacy CPUID emulation for the first part of the test, but * go ahead and grab the appropriate descriptor and set up the control * structure needed for later explicit emulation. */ int vmfd = vm_get_device_fd(ctx); struct { char *name; bool pass_expected; } phases[4] = { {"legacy emulation mode", false}, {"explicit mode", false}, {"explicit mode w/Intel fallback enabled", false}, {"fallback variations", true} }; for (int i = 0; i < ARRAY_SIZE(phases); i++) { kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: test_fail_msg("unexpected exit in %s", phases[i].name); goto done; case VEK_TEST_PASS: if (phases[i].pass_expected) { test_pass(); } else { test_fail_msg("unexpected pass from %s", phases[i].name); } goto done; case VEK_TEST_FAIL: test_fail_msg("failed result in %s, %rip: %x", phases[i].name, vexit.rip); goto done; case VEK_UNHANDLED: { uint32_t finished_phase; if (!vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &finished_phase)) { test_fail_vmexit(&vexit); goto done; } if (finished_phase != i) { test_fail_vmexit(&vexit); goto done; } break; } default: test_fail_vmexit(&vexit); break; } /* * The driver got past this variation. Check the phase number * to see how to set up explicit CPUID for the next variation. */ struct vm_vcpu_cpuid_config cfg = { .vvcc_vcpuid = 0, .vvcc_flags = 0, .vvcc_nent = ARRAY_SIZE(test_entries), /* We trust the ioctl not to alter this const value */ .vvcc_entries = (struct vcpu_cpuid_entry *)test_entries, }; switch (i) { case 0: /* Nothing special to do for the first explicit test. */ break; case 1: cfg.vvcc_flags = VCC_FLAG_INTEL_FALLBACK; break; case 2: cfg.vvcc_flags = VCC_FLAG_INTEL_FALLBACK; cfg.vvcc_nent = ARRAY_SIZE(fallback_test_entries); cfg.vvcc_entries = (struct vcpu_cpuid_entry *)fallback_test_entries; break; default: test_fail_msg("phase %d fell through without passing", i); goto done; } err = ioctl(vmfd, VM_SET_CPUID, &cfg); if (err != 0) { test_fail_errno(err, "ioctl(VM_SET_CPUID) failed"); } ventry_fulfill_inout(&vexit, &ventry, 0); } done: return (0); } /* * 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. */ /* * Definitions shared by the harness and payload halves of the cpuid_guest_state * test. * * Copyright 2025 Oxide Computer Company */ #ifndef _CPUID_GUEST_STATE_H #define _CPUID_GUEST_STATE_H /* * Fixed test values for leaves 0 and 1, taken from a representative test VM on * an AMD host machine. * * Leaf 0's values are never modified by guest CPU state. Although this * particular test doesn't rely on the value in eax to determine what other * leaves to query, set it to 0xD to match the largest leaf value used by the * test. * * Leaf 1's eax and ebx values are also fixed. Set them to representative values * to help verify that the leaf 1 fixup logic doesn't change these outputs. */ #define TEST_CPUID_0_EAX 0x0000000D #define TEST_CPUID_0_EBX 0x74737552 #define TEST_CPUID_0_ECX 0x65646978 #define TEST_CPUID_0_EDX 0x4F206465 #define TEST_CPUID_1_EAX 0x00A50F00 #define TEST_CPUID_1_EBX 0x01010800 /* * Leave bit 27 (OSXSAVE) cleared; it should be set if XSAVE is enabled in CR4 * even if it wasn't set in the original explicit value. */ #define TEST_CPUID_1_ECX 0xF6D83203 /* * Leave bit 9 (APIC enabled) set; it should be cleared if the guest disables * the APIC via the appropriate MSR. */ #define TEST_CPUID_1_EDX 0x178BFBFF /* The sizes needed for various XSAVE area regions. */ #define XSAVE_AREA_SIZE_BASE 0x240 #define XSAVE_AREA_SIZE_AVX 0x100 #define XSAVE_AREA_SIZE_MAX (XSAVE_AREA_SIZE_BASE + XSAVE_AREA_SIZE_AVX) /* Advertise that x87, SSE, and AVX support are present in leaf D index 0. */ #define TEST_CPUID_D_0_EAX 0x00000007 /* Advertise in leaf D index 1 that all state save instructions are present. */ #define TEST_CPUID_D_1_EAX 0x0000000F #endif /* !_CPUID_GUEST_STATE_H */ /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" static void run_until_unhandled(struct vcpu *vcpu, struct vm_entry *ventry, struct vm_exit *vexit) { do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, ventry, vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: return; default: /* * We are not expecting the payload to use any of the * pass/fail/messaging facilities during this test. */ test_fail_vmexit(vexit); break; } } while (true); } static void repeat_consistent_exit(struct vcpu *vcpu, struct vm_entry *ventry, struct vm_exit *vexit, uint64_t expected_rip) { ventry->cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; if (vm_run(vcpu, ventry, vexit) != 0) { test_fail_errno(errno, "Failure during vcpu entry"); } if (vexit->rip != expected_rip) { test_fail_msg( "Unexpected forward progress when vCPU already consistent"); } } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; /* * Let the payload run until it reaches the first userspace exit which * requires actual handling */ run_until_unhandled(vcpu, &ventry, &vexit); if (vexit.exitcode != VM_EXITCODE_RDMSR) { test_fail_vmexit(&vexit); } uint64_t rcx = 0, rip = 0; if (vm_get_register(vcpu, VM_REG_GUEST_RCX, &rcx) != 0) { test_fail_errno(errno, "Could not read guest %rcx"); } if (vm_get_register(vcpu, VM_REG_GUEST_RIP, &rip) != 0) { test_fail_errno(errno, "Could not read guest %rip"); } /* Paranoia: confirm that in-register %rip matches vm_exit data */ if (rip != vexit.rip) { test_fail_msg( "vm_exit`rip does not match in-kernel %rip: %lx != %lx", rip, vexit.rip); } /* Request a consistent exit */ ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; if (vm_run(vcpu, &ventry, &vexit) != 0) { test_fail_errno(errno, "Failure during vcpu entry"); } /* * We expect the consistent exit to have completed the instruction * emulation for the rdmsr (just move the %rip forward, since its left * to userspace to update %rax:%rdx) and emit the BOGUS exitcode. */ if (vexit.exitcode != VM_EXITCODE_BOGUS) { test_fail_msg("Unexpected exitcode: %d != %d", vexit.exitcode, VM_EXITCODE_BOGUS); } /* * Check that the %rip moved forward only the 2 bytes expected for a * rdmsr opcode. */ if (vexit.rip != (rip + 2)) { test_fail_msg("Exited at unexpected %rip: %lx != %lx", vexit.rip, rip + 2); } /* * Repeat entry with consistency request. This should not make any * forward progress since the vCPU is already in a consistent state. */ repeat_consistent_exit(vcpu, &ventry, &vexit, vexit.rip); /* Let the vCPU continue on to the next exit condition */ ventry.cmd = VEC_DEFAULT; run_until_unhandled(vcpu, &ventry, &vexit); const uint64_t read_addr = 0xc0000000; const uint_t read_len = 4; if (!vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) { test_fail_vmexit(&vexit); } rip = vexit.rip; /* * An attempt to push the vCPU to a consistent state without first * fulfilling the MMIO should just result in the same MMIO exit. */ ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT; if (vm_run(vcpu, &ventry, &vexit) != 0) { test_fail_errno(errno, "Failure during vcpu entry"); } if (vexit.rip != rip || !vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) { test_fail_msg( "Unexpected forward progress during MMIO emulation"); } /* Fulfill the MMIO and attempt another consistent exit */ ventry_fulfill_mmio(&vexit, &ventry, 0); ventry.cmd |= VEC_FLAG_EXIT_CONSISTENT; if (vm_run(vcpu, &ventry, &vexit) != 0) { test_fail_errno(errno, "Failure during vcpu entry"); } /* With current payload, we expect a 3-byte mov instruction */ if (vexit.rip != (rip + 3)) { test_fail_msg("Exited at unexpected %rip: %lx != %lx", vexit.rip, rip + 3); } /* * And again, check that vCPU remains at that %rip once its state has * been made consistent. */ repeat_consistent_exit(vcpu, &ventry, &vexit, vexit.rip); test_pass(); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; const uintptr_t expected_gpa = MEM_LOC_ROM; const int expected_ftype = PROT_WRITE; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: if (vexit.exitcode != VM_EXITCODE_PAGING) { test_fail_vmexit(&vexit); } if (vexit.u.paging.gpa != expected_gpa) { test_fail_msg("gpa %08x != %08x\n", vexit.u.paging.gpa, expected_gpa); } if (vexit.u.paging.fault_type != expected_ftype) { test_fail_msg("fault_type %x != %x\n", vexit.u.paging.fault_type, expected_ftype); } test_pass(); break; default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #define MMIO_TEST_BASE 0x10001000 #define MMIO_TEST_END 0x10002000 static bool handle_test_mmio(const struct vm_exit *vexit, struct vm_entry *ventry) { /* expecting only reads */ if (vexit->u.mmio.read == 0) { return (false); } /* expecting only in the [0x10001000 - 0x10002000) range */ if (vexit->u.mmio.gpa < MMIO_TEST_BASE || vexit->u.mmio.gpa >= MMIO_TEST_END) { return (false); } /* * Emit a pattern of the lowest 16 bits of the address, ascending by the * 2-byte stride for every 2 additional bytes, as the result. * * For example, an 8-byte read of 0x00001234 would result in: * 0x123a123812361234 being returned */ const uint16_t addr = vexit->u.mmio.gpa; uint64_t val = 0; switch (vexit->u.mmio.bytes) { case 8: val |= (uint64_t)(addr + 6) << 48; val |= (uint64_t)(addr + 4) << 32; /* FALLTHROUGH */ case 4: val |= (uint32_t)(addr + 2) << 16; /* FALLTHROUGH */ case 2: val |= addr; break; default: /* expect only 2/4/8-byte reads */ return (false); } ventry_fulfill_mmio(vexit, ventry, val); return (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: if (!handle_test_mmio(&vexit, &ventry)) { test_fail_vmexit(&vexit); } break; case VEK_TEST_PASS: test_pass(); break; case VEK_TEST_FAIL: default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" enum test_state { SEEKING_IN, SEEKING_OUT, SEEKING_END, DONE, }; bool advance_test_state(const struct vm_exit *vexit, struct vm_entry *ventry, struct vcpu *vcpu, enum vm_exit_kind kind, enum test_state *state, const bool have_decodeassist) { if (*state != DONE && kind == VEK_REENTR) { return (true); } switch (*state) { case SEEKING_IN: if (kind != VEK_UNHANDLED) { break; } if (have_decodeassist) { if (vexit->exitcode != VM_EXITCODE_INOUT) { break; } const bool match_inb = vexit_match_inout(vexit, true, 0x55aa, 1, NULL); if (!match_inb) { break; } ventry_fulfill_inout(vexit, ventry, 0xee); } else { if (vexit->exitcode != VM_EXITCODE_INST_EMUL) { break; } const bool match_inb = vexit->u.inst_emul.num_valid >= 2 && vexit->u.inst_emul.inst[0] == 0xf3 && vexit->u.inst_emul.inst[1] == 0x6c; if (!match_inb) { break; } int err = vm_set_register(vcpu, 0, vexit->rip + 2); if (err != 0) { test_fail_errno(err, "failed to set %rip"); } } *state = SEEKING_OUT; return (true); case SEEKING_OUT: if (kind != VEK_UNHANDLED) { break; } if (have_decodeassist) { if (vexit->exitcode != VM_EXITCODE_INOUT) { break; } const bool match_outb = vexit_match_inout(vexit, false, 0x55aa, 1, NULL); if (!match_outb) { break; } ventry_fulfill_inout(vexit, ventry, 0); } else { if (vexit->exitcode != VM_EXITCODE_INST_EMUL) { break; } const bool match_outb = vexit->u.inst_emul.num_valid >= 2 && vexit->u.inst_emul.inst[0] == 0xf3 && vexit->u.inst_emul.inst[1] == 0x6e; if (!match_outb) { break; } int err = vm_set_register(vcpu, 0, vexit->rip + 2); if (err != 0) { test_fail_errno(err, "failed to set %rip"); } } *state = SEEKING_END; return (true); case SEEKING_END: if (kind == VEK_TEST_PASS) { *state = DONE; return (true); } break; case DONE: break; } return (false); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); /* * Guest execution of `in` and `out` instructions (and their repeatable * string versions) can have substantially different outcomes depending * on available hardware support. * * For simple `in` or `out`, `byhve` will cause a VM exit with exit * code `VM_EXITCODE_INOUT`. This is the simplest case, and not * currently exercised in this test. * * For `ins` and `outs`, decoding the exact operation is more complex. * For processors with the DecodeAssist feature, we rely on the * processor to do that decoding and provide information about the * trapped instruction. As a result, `ins` and `outs` on such processors * result in a VM exit with code `VM_EXITCODE_INOUT`. * * Finally, if DecodeAssist is *not* available, we don't currently do * any in-kernel disassembly to backfill the missing functionality. So * instead, `ins`/`outs` on processors without DecodeAssist result in a * VM exit with code `VM_EXITCODE_INST_EMUL`. * * Since the kernel behavior varies based on hardware features, detect * DecodeAssist here as well to check for what we expect the kernel to * do on this processor. */ uint_t regs[4]; do_cpuid(0x8000000a, regs); const bool have_decodeassist = (regs[3] & (1 << 7)) != 0; if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; enum test_state state = SEEKING_IN; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); const bool exit_ok = advance_test_state(&vexit, &ventry, vcpu, kind, &state, have_decodeassist); if (!exit_ok) { test_fail_vmexit(&vexit); break; } } while (state != DONE); test_pass(); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "in_guest.h" #define PAGE_SZ 4096 #define DIRTY_BITMAP_SZ (MEM_TOTAL_SZ / (PAGE_SZ * 8)) static void read_dirty_bitmap(struct vmctx *ctx, uint8_t *bitmap) { struct vmm_dirty_tracker track = { .vdt_start_gpa = 0, .vdt_len = MEM_TOTAL_SZ, .vdt_pfns = (void *)bitmap, }; int err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track); if (err != 0) { test_fail_errno(errno, "Could not get dirty page bitmap"); } } static uint8_t popc8(uint8_t val) { uint8_t cnt; for (cnt = 0; val != 0; val &= (val - 1)) { cnt++; } return (cnt); } static uint_t count_dirty_pages(const uint8_t *bitmap) { uint_t count = 0; for (uint_t i = 0; i < DIRTY_BITMAP_SZ; i++) { count += popc8(bitmap[i]); } return (count); } void check_supported(const char *test_suite_name) { char name[VM_MAX_NAMELEN]; int err; name_test_vm(test_suite_name, name); err = vm_create(name, VCF_TRACK_DIRTY); if (err == 0) { /* * We created the VM successfully, so we know that dirty page * tracking is supported. */ err = destroy_instance(test_suite_name); if (err != 0) { (void) fprintf(stderr, "Could not destroy VM: %s\n", strerror(errno)); (void) printf("FAIL %s\n", test_suite_name); exit(EXIT_FAILURE); } } else if (errno == ENOTSUP) { (void) printf( "Skipping test: dirty page tracking not supported\n"); (void) printf("PASS %s\n", test_suite_name); exit(EXIT_SUCCESS); } else { /* * Ignore any other errors, they'll be caught by subsequent * test routines. */ } } void test_dirty_tracking_disabled(const char *test_suite_name) { struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 }; struct vmm_dirty_tracker track = { .vdt_start_gpa = 0, .vdt_len = MEM_TOTAL_SZ, .vdt_pfns = (void *)dirty_bitmap, }; /* Create VM without VCF_TRACK_DIRTY flag */ ctx = test_initialize_flags(test_suite_name, 0); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } /* Try to query for dirty pages */ err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track); if (err == 0) { test_fail_msg("VM_TRACK_DIRTY_PAGES succeeded unexpectedly\n"); } else if (errno != EPERM) { test_fail_errno(errno, "VM_TRACK_DIRTY_PAGES failed with unexpected error"); } test_cleanup(false); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; /* Skip test if CPU doesn't support HW A/D tracking */ check_supported(test_suite_name); /* Test for expected error with dirty tracking disabled */ test_dirty_tracking_disabled(test_suite_name); ctx = test_initialize_flags(test_suite_name, VCF_TRACK_DIRTY); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 }; /* Clear pages which were dirtied as part of initialization */ read_dirty_bitmap(ctx, dirty_bitmap); if (count_dirty_pages(dirty_bitmap) == 0) { test_fail_msg("no pages dirtied during setup\n"); } /* * With nothing running, and the old dirty bits cleared, the NPT should * now be devoid of pages marked dirty. */ read_dirty_bitmap(ctx, dirty_bitmap); if (count_dirty_pages(dirty_bitmap) != 0) { test_fail_msg("pages still dirty after clear\n"); } /* Dirty a page through the segvmm mapping. */ uint8_t *dptr = vm_map_gpa(ctx, MEM_LOC_STACK, 1); *dptr = 1; /* Check that it was marked as such */ read_dirty_bitmap(ctx, dirty_bitmap); if (count_dirty_pages(dirty_bitmap) != 1) { test_fail_msg("direct access did not dirty page\n"); } if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) { test_fail_msg("unexpected page dirtied\n"); } /* Dirty it again to check shootdown logic */ *dptr = 2; if (count_dirty_pages(dirty_bitmap) != 1) { test_fail_msg("subsequent direct access did not dirty page\n"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_TEST_PASS: /* * By now, the guest should have dirtied that page * directly via hardware-accelerated path. */ read_dirty_bitmap(ctx, dirty_bitmap); /* * The guest will dirty more than the page it is * explicitly writing to: it must mark its own page * tables with accessed/dirty bits too. */ if (count_dirty_pages(dirty_bitmap) <= 1) { test_fail_msg( "in-guest access did not dirty page\n"); } if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) { test_fail_msg("expected page not dirtied\n"); } test_pass(); break; default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 "payload_common.h" #include "payload_utils.h" int leaf_cmp(const uint32_t *a, const uint32_t *b) { return (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]); } const uint32_t expected_base[] = { 5, 0x74737552, 0x65646978, 0x4f206465 }; struct test_case { uint32_t func; uint32_t idx; uint32_t val_eax; int fallback; }; const struct test_case cases[] = { /* basic leaf match */ { .func = 1, .val_eax = 0x100, }, /* index matching */ { .func = 3, .idx = 0, .val_eax = 0x300, }, { .func = 3, .idx = 1, .val_eax = 0x301, }, /* leaf match with hole */ { .func = 4, .idx = 0, .val_eax = 0x400, }, { .func = 4, .idx = 2, .val_eax = 0x402, }, /* last std leaf */ { .func = 5, .val_eax = 0x5, }, /* invalid leaf */ { .func = 2, .val_eax = 0, }, /* invalid index */ { .func = 3, .idx = 2, .val_eax = 0, }, { .func = 4, .idx = 1, .val_eax = 0x0, }, { .func = 4, .idx = 0xffff, .val_eax = 0x0, }, /* basic extd leaf match */ { .func = 0x80000000, .val_eax = 0x80000001, }, /* basic extd index match */ { .func = 0x80000001, .idx = 0, .val_eax = 0x8000, }, { .func = 0x80000001, .idx = 1, .val_eax = 0x8001, }, /* zeroed for invalid index */ { .func = 0x80000001, .idx = 5, .val_eax = 0, }, /* fallback beyond std leaf */ { .func = 6, .fallback = 1, }, /* fallback beyond extd leaf */ { .func = 0x80000002, .fallback = 1, }, }; #define NCASES (sizeof (cases) / sizeof (cases[0])) void do_test(int intel_fallback) { uint32_t regs[4]; uint32_t expected_fallback[4] = { 0 }; cpuid(0, 0, regs); if (!leaf_cmp(regs, expected_base)) { outb(IOP_TEST_RESULT, TEST_RESULT_FAIL); } if (intel_fallback) { cpuid(regs[0], 0, expected_fallback); } for (uint_t i = 0; i < NCASES; i++) { cpuid(cases[i].func, cases[i].idx, regs); if (cases[i].fallback != 0) { if (!leaf_cmp(regs, expected_fallback)) { outb(IOP_TEST_RESULT, TEST_RESULT_FAIL); } } else { if (regs[0] != cases[i].val_eax) { outb(IOP_TEST_RESULT, TEST_RESULT_FAIL); } } } } void start(void) { /* Check results expecting Intel-style fallback */ do_test(1); /* Notify userspace component to change fallback style */ outl(IOP_TEST_VALUE, 0); /* Check results expecting AMD-style fallback */ do_test(0); /* If all is well by this point, indicate success */ outb(IOP_TEST_RESULT, TEST_RESULT_PASS); } /* * 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 2025 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" #include "cpuid_guest_state.h" #define CPUID_APIC 0x00000200 #define CPUID2_XSAVE 0x04000000 #define CPUID2_OSXSAVE 0x08000000 #define APICBASE_ENABLED 0x00000800 #define CR4_OSXSAVE 0x00040000 #define XCR0_X87_SSE 0x00000003 #define XCR0_AVX 0x00000004 int leaf_cmp(const uint32_t *a, const uint32_t *b) { return (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]); } const uint32_t expected_base[] = { TEST_CPUID_0_EAX, TEST_CPUID_0_EBX, TEST_CPUID_0_ECX, TEST_CPUID_0_EDX }; /* Verifies that leaf 1 returns the test suite's explicitly-set values. */ void test_leaf_1_explicit() { uint32_t regs[4]; cpuid(1, 0, regs); if (regs[0] != TEST_CPUID_1_EAX || regs[1] != TEST_CPUID_1_EBX || regs[3] != TEST_CPUID_1_EDX) { test_result_fail(); } } /* Verifies that leaf 1 ecx's value varies as cr4's OSXSAVE bit changes. */ void test_leaf_1_osxsave() { uint32_t regs[4]; uint32_t orig_cr4 = getcr4(); setcr4(getcr4() & ~CR4_OSXSAVE); cpuid(1, 0, regs); if (regs[2] != (TEST_CPUID_1_ECX & ~CPUID2_OSXSAVE)) { test_result_fail(); } /* Turn OSXSAVE back on and check that it reappears in CPUID. */ setcr4(getcr4() | CR4_OSXSAVE); cpuid(1, 0, regs); if (regs[2] != (TEST_CPUID_1_ECX | CPUID2_OSXSAVE)) { test_result_fail(); } setcr4(orig_cr4); } /* Verifies that leaf 1 edx's value varies as the APIC enable flag changes. */ void test_leaf_1_apic() { uint32_t regs[4]; wrmsr(0x1B, rdmsr(0x1B) & ~APICBASE_ENABLED); cpuid(1, 0, regs); if (regs[3] != (TEST_CPUID_1_EDX & ~CPUID_APIC)) { test_result_fail(); } wrmsr(0x1B, rdmsr(0x1B) | APICBASE_ENABLED); cpuid(1, 0, regs); if (regs[3] != (TEST_CPUID_1_EDX | CPUID_APIC)) { test_result_fail(); } } /* Verifies that leaf D subleaf 0 ebx's value changes as XCR0 changes. */ void test_leaf_d_index_0(bool using_explicit) { uint32_t regs[4]; uint64_t orig_cr4 = getcr4(); uint64_t orig_xcr0; setcr4(getcr4() | CR4_OSXSAVE); orig_xcr0 = getxcr(0); cpuid(0xD, 0, regs); if (using_explicit) { if (regs[0] != TEST_CPUID_D_0_EAX || regs[2] != XSAVE_AREA_SIZE_MAX || regs[3] != 0) { test_result_fail(); } } setxcr(0, XCR0_X87_SSE); cpuid(0xD, 0, regs); if (regs[1] != XSAVE_AREA_SIZE_BASE) { test_result_fail(); } setxcr(0, XCR0_X87_SSE | XCR0_AVX); cpuid(0xD, 0, regs); if (regs[1] != XSAVE_AREA_SIZE_BASE + XSAVE_AREA_SIZE_AVX) { test_result_fail(); } setxcr(0, orig_xcr0); setcr4(orig_cr4); } /* Verifies that leaf D subleaf 1 ebx's value changes as XCR0 changes. */ void test_leaf_d_index_1(bool using_explicit, bool via_fallback) { uint32_t leaf; uint32_t regs[4]; uint64_t orig_cr4 = getcr4(); uint64_t orig_xcr0; if (via_fallback) { leaf = 0xE; } else { leaf = 0xD; } setcr4(getcr4() | CR4_OSXSAVE); orig_xcr0 = getxcr(0); cpuid(leaf, 1, regs); if (using_explicit) { if (regs[0] != TEST_CPUID_D_1_EAX || regs[2] != 0 || regs[3] != 0) { test_result_fail(); } } setxcr(0, XCR0_X87_SSE); cpuid(leaf, 1, regs); if (regs[1] != XSAVE_AREA_SIZE_BASE) { test_result_fail(); } setxcr(0, XCR0_X87_SSE | XCR0_AVX); cpuid(leaf, 1, regs); if (regs[1] != XSAVE_AREA_SIZE_BASE + XSAVE_AREA_SIZE_AVX) { test_result_fail(); } setxcr(0, orig_xcr0); setcr4(orig_cr4); } /* * Tests that CPUID returns the expected values for leaves that are either * expected to be present on the host or that are explicitly supplied by the * test harness. */ void do_basic_test(bool using_explicit) { uint32_t regs[4]; /* * The specific values returned by leaves 0 and 1 are host-dependent, * so only check them when explicitly overriding host CPUID. */ if (using_explicit) { cpuid(0, 0, regs); if (!leaf_cmp(regs, expected_base)) { test_result_fail(); } test_leaf_1_explicit(); } test_leaf_1_osxsave(); test_leaf_1_apic(); test_leaf_d_index_0(using_explicit); test_leaf_d_index_1(using_explicit, false); } /* * Tests that CPUID leaves are specialized properly when reached by the * "fallback" behavior described in the Intel SDM. The expected behavior is: * * - Querying a leaf that is not present but is less than the maximum * supported standard leaf should return all zeroes. * - Querying a leaf that is greater than the maximum supported standard leaf * should return the values from the maximum supported leaf. */ void do_fallback_test() { uint32_t regs[4]; uint32_t zero_cpuid[4] = {0, 0, 0, 0}; /* * The fallback entries contain only leaf 0 and leaf D. Leaf 1 should * return zeroes even if the OSXSAVE bit is set in cr4. */ setcr4(getcr4() | CR4_OSXSAVE); cpuid(1, 0, regs); if (!leaf_cmp(regs, zero_cpuid)) { test_result_fail(); } /* * bhyve's fallback implementation currently falls back to the highest * subleaf for a given leaf and not the guest's requested subleaf. This * means that fallback can't be used to test leaf D subleaf 0 (since * it resolves to subleaf 1 instead). */ test_leaf_d_index_1(true, true); } void start(void) { do_basic_test(false); outl(IOP_TEST_VALUE, 0); do_basic_test(true); outl(IOP_TEST_VALUE, 1); do_basic_test(true); outl(IOP_TEST_VALUE, 2); do_fallback_test(); test_result_pass(); } /* * 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 2023 Oxide Computer Company */ #include ENTRY(start) movl $0x12345678, %ecx xorl %eax, %eax xorl %edx, %edx /* Incur an exit for an unhandled rdmsr */ rdmsr nop nop /* Incur an exit for MMIO */ movl $0xc0000000, %eax movl (%eax), %ecx nop nop hlt SET_SIZE(start) /* * 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 #include "payload_common.h" ENTRY(start) /* Attempt to write to ROM, which should result in paging exit */ xorl %eax, %eax movq %rax, MEM_LOC_ROM /* This should not be reached */ movw $IOP_TEST_RESULT, %dx movb $TEST_RESULT_FAIL, %al outb (%dx) hlt SET_SIZE(start) /* * 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 #include "payload_common.h" ENTRY(start) /* check that the mmio values we expect are emitted */ movw 0x10001234, %ax cmpw $0x1234, %ax jne fail movl 0x10001232, %eax cmpl $0x12341232, %eax jne fail movq 0x10001230, %rax movq $0x1236123412321230, %rdx cmpq %rdx, %rax jne fail /* attempt the imul at 2/4/8 byte widths */ movl $0x2, %eax imulw 0x10001234, %ax cmpw $0x2468, %ax jne fail movl $0x2, %eax imull 0x10001232, %eax cmpl $0x24682464, %eax jne fail movl $0x10, %eax imulq 0x10001230, %rax movq $0x2361234123212300, %rcx cmpq %rcx, %rax jne fail movw $IOP_TEST_RESULT, %dx movb $TEST_RESULT_PASS, %al outb (%dx) hlt fail: movw $IOP_TEST_RESULT, %dx movb $TEST_RESULT_FAIL, %al outb (%dx) hlt SET_SIZE(start) /* * 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 2024 Oxide Computer Company */ #include #include "payload_common.h" ENTRY(start) /* * `ins` and `outs` access memory through rdi, so reserve a few * bytes of stack and make rdi point into it so we don't clobber * something important along the way. */ subq %rsp, 8 mov %rsp, %rdi /* * Port 0x55aa is just a convenient value we can check after the * exit. */ movw $0x55aa, %dx movl $0x01, %ecx rep insb movl $0x01, %ecx rep outsb movw $IOP_TEST_RESULT, %dx movb $TEST_RESULT_PASS, %al outb (%dx) hlt SET_SIZE(start) /* * 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 #include "payload_common.h" ENTRY(start) /* Dirty the stack page once again */ xorl %eax, %eax movq %rax, MEM_LOC_STACK movw $IOP_TEST_RESULT, %dx movb $TEST_RESULT_PASS, %al outb (%dx) hlt SET_SIZE(start) /* * 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 ENTRY(start) /* * Pad test value with garbage to make sure it is properly trimmed off * when the emulation handles the exit. */ movq $0xff01020304, %rcx rdmsr hlt SET_SIZE(start) /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" void start(uint_t vcpuid) { if (vcpuid == 0) { while (1) { /* spin, waiting for other vCPU to triple-fault */ } } else { /* Since no IDT is configured, ud2a should incur triple-fault */ ud2a(); } } /* * 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 ENTRY(start) /* Without a populated IDT, this should immediately triple-fault */ ud2a hlt SET_SIZE(start) /* * 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 2023 Oxide Computer Company */ #include ENTRY(start) /* * Empty payload, since userspace configuration of the vCPU should * prevent any code from being executed in guest context. */ ud2a SET_SIZE(start) /* * 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 ENTRY(start) /* * Pad test values with garbage to make sure it is properly trimmed off * when the emulation handles the exit. */ movq $0xff01020304, %rcx movq $0xff05060708, %rdx movq $0xff090a0b0c, %rax wrmsr hlt SET_SIZE(start) /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; const uint32_t expected_code = 0x01020304; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: /* Look for wrmsr of test value */ if (vexit.exitcode != VM_EXITCODE_RDMSR) { test_fail_vmexit(&vexit); } if (vexit.u.msr.code != expected_code) { test_fail_msg("code %08x != %08x\n", vexit.u.msr.code, expected_code); } test_pass(); break; case VEK_TEST_PASS: case VEK_TEST_FAIL: default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 2023 Oxide Computer Company */ /* * Check that supporting information for a VM_EXITCODE_SUSPENDED exit is correct * for a vCPU-specific event (triple-fault). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #define VCPU0_STACK (MEM_LOC_STACK) #define VCPU1_STACK (MEM_LOC_STACK - 0x1000) struct vcpu_thread_ctx { struct vcpu *vcpu; enum vm_suspend_how *howp; int *sourcep; }; static void * vcpu0_thread(void *arg) { struct vcpu_thread_ctx *vtc = arg; struct vcpu *vcpu = vtc->vcpu; struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: if (vexit.exitcode != VM_EXITCODE_SUSPENDED) { test_fail_vmexit(&vexit); } *vtc->howp = vexit.u.suspended.how; *vtc->sourcep = vexit.u.suspended.source; return (NULL); default: test_fail_vmexit(&vexit); } } while (true); } static void vcpu0_setup(struct vcpu *vcpu) { int err; err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, VCPU0_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } err = vm_set_register(vcpu, VM_REG_GUEST_RDI, 0); if (err != 0) { test_fail_errno(err, "failed to set %rdi"); } } static pthread_t vcpu0_spawn(struct vcpu_thread_ctx *vtc) { pthread_t tid; if (pthread_create(&tid, NULL, vcpu0_thread, (void *)vtc) != 0) { test_fail_errno(errno, "could not create thread for vcpu0"); } return (tid); } static void vcpu0_join(pthread_t tid) { void *status = NULL; if (pthread_join(tid, &status) != 0) { test_fail_errno(errno, "could not join thread for vcpu0"); } assert(status == NULL); } static void test_plain_suspend(struct vmctx *ctx, struct vcpu *vcpu, enum vm_suspend_how test_how) { enum vm_suspend_how how; int source; struct vcpu_thread_ctx vcpu0 = { .vcpu = vcpu, .howp = &how, .sourcep = &source, }; pthread_t tid; int err; vcpu0_setup(vcpu); tid = vcpu0_spawn(&vcpu0); err = vm_suspend(ctx, test_how); if (err != 0) { test_fail_errno(err, "vm_suspend() failure"); } vcpu0_join(tid); if (how != test_how) { test_fail_msg("Unexpected suspend how %d != %d\n", how, test_how); } if (source != -1) { test_fail_msg("Unexpected suspend source %d != %d\n", source, -1); } /* Reset VM for another test */ test_reinitialize(ctx, 0); } static void test_emitted_triplefault(struct vmctx *ctx, struct vcpu *vcpu) { enum vm_suspend_how vcpu0_how; int vcpu0_source; struct vcpu_thread_ctx vcpu0 = { .vcpu = vcpu, .howp = &vcpu0_how, .sourcep = &vcpu0_source, }; struct vcpu *vcpu1; int err; pthread_t tid; vcpu0_setup(vcpu); if ((vcpu1 = vm_vcpu_open(ctx, 1)) == NULL) { test_fail_errno(errno, "Could not open vcpu1"); } /* Setup vCPU1 like vCPU0, but with ID of 1 in %rdi */ err = test_setup_vcpu(vcpu1, MEM_LOC_PAYLOAD, VCPU1_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu1"); } err = vm_set_register(vcpu1, VM_REG_GUEST_RDI, 1); if (err != 0) { test_fail_errno(err, "failed to set %rdi"); } /* * Get vcpu0 running on a separate thread, ready to have its day * "ruined" by a triple-fault on vcpu1 */ tid = vcpu0_spawn(&vcpu0); struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu1, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: { /* expect immediate triple-fault from ud2a */ if (vexit.exitcode != VM_EXITCODE_SUSPENDED) { test_fail_vmexit(&vexit); } vcpu0_join(tid); const enum vm_suspend_how vcpu1_how = vexit.u.suspended.how; const int vcpu1_source = vexit.u.suspended.source; if (vcpu0_how != VM_SUSPEND_TRIPLEFAULT || vcpu0_how != vcpu1_how) { test_fail_msg("Unexpected 'how' for " "triple-fault: vcpu0=%d, vcpu1=%d, " "expected=%d", vcpu0_how, vcpu1_how, VM_SUSPEND_TRIPLEFAULT); } if (vcpu0_source != 1 || vcpu0_source != vcpu1_source) { test_fail_msg("Unexpected 'source' for " "triple-fault: vcpu0=%d, vcpu1=%d, " "expected=%d", vcpu0_source, vcpu1_source, 1); } return; } default: test_fail_vmexit(&vexit); break; } } while (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } /* * Try injecting the various suspend types, and confirm that vcpu0 exits * with the expected details. */ test_plain_suspend(ctx, vcpu, VM_SUSPEND_RESET); test_plain_suspend(ctx, vcpu, VM_SUSPEND_POWEROFF); test_plain_suspend(ctx, vcpu, VM_SUSPEND_HALT); /* * Let vCPU1 generate a triple-fault, and confirm that it is emitted by * both exiting vCPU threads, with the proper details. */ test_emitted_triplefault(ctx, vcpu); test_pass(); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; const enum vm_suspend_how expected_how = VM_SUSPEND_TRIPLEFAULT; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: /* We expect to immediately triple-fault */ if (vexit.exitcode != VM_EXITCODE_SUSPENDED) { test_fail_vmexit(&vexit); } if (vexit.u.suspended.how != expected_how) { test_fail_msg("suspend_how %d != %d\n", vexit.u.suspended.how, expected_how); } test_pass(); break; case VEK_TEST_PASS: case VEK_TEST_FAIL: default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "in_guest.h" static pthread_t vcpu0_tid; static bool timed_out = false; static void * vcpu0_thread(void *arg) { struct vcpu *vcpu = arg; struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { int err = vm_run(vcpu, &ventry, &vexit); if (err != 0) { test_fail_errno(err, "error during vm_run()"); } switch (vexit.exitcode) { case VM_EXITCODE_BOGUS: /* We expect a BOGUS exit from the barrier */ return (NULL); default: test_fail_vmexit(&vexit); } } while (true); } static void sigalrm_handler(int sig) { (void) pthread_cancel(vcpu0_tid); timed_out = true; } static void configure_timeout(void) { struct sigaction sa = { .sa_handler = sigalrm_handler, }; struct sigaction old_sa; if (sigaction(SIGALRM, &sa, &old_sa) != 0) { test_fail_errno(errno, "could not prep signal handling for bad access"); } /* set a simple 1s-in-the-future alarm */ (void) alarm(1); } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; int err; ctx = test_initialize(suite_name); assert(ctx != NULL); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } /* Activate vcpu0 as if it were running */ err = vm_activate_cpu(vcpu); if (err != 0) { test_fail_errno(err, "could not activate vcpu0"); } /* * Set unorthodox run-state for vcpu0: wait-for-SIPI * This way it will dawdle in the kernel during VM_RUN, despite there * being no code to execute. Normally the emulated APIC would not allow * a CPU to SIPI itself, making this state impossible to reach. */ err = vm_set_run_state(vcpu, VRS_INIT, 0); if (err != 0) { test_fail_errno(err, "could not set vcpu0 run_state"); } /* Get the vCPU thread running (and stuck in the kernel)... */ if (pthread_create(&vcpu0_tid, NULL, vcpu0_thread, (void *)vcpu) != 0) { test_fail_errno(errno, "could not create thread for vcpu0"); } /* configure a timeout in case the barrier failed */ configure_timeout(); /* ... then issue our barrier: */ err = vm_vcpu_barrier(vcpu); if (err != 0) { test_fail_errno(err, "failed to issue vcpu barrier"); } void *status = NULL; if (pthread_join(vcpu0_tid, &status) != 0) { test_fail_errno(errno, "could not join thread for vcpu0"); } /* cancel any timeout now that thread was joined */ (void) alarm(0); if (timed_out) { test_fail_msg("timed out while waiting for barrier\n"); } test_pass(); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; const uint32_t expected_code = 0x01020304; const uint64_t expected_wval = 0x05060708090a0b0c; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: /* Look for wrmsr of test value */ if (vexit.exitcode != VM_EXITCODE_WRMSR) { test_fail_vmexit(&vexit); } if (vexit.u.msr.code != expected_code) { test_fail_msg("code %08x != %08x\n", vexit.u.msr.code, expected_code); } if (vexit.u.msr.wval != expected_wval) { test_fail_msg("wval %lx != %lx\n", vexit.u.msr.wval, expected_wval); } test_pass(); break; case VEK_TEST_PASS: case VEK_TEST_FAIL: default: test_fail_vmexit(&vexit); break; } } while (true); } # # 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 2023 Oxide Computer Company include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/Makefile.cmd.64 include $(SRC)/test/Makefile.com PROG = vpmtmr_freq \ vhpet_freq \ vlapic_freq \ vlapic_freq_periodic \ vlapic_mmio_access \ vlapic_msr_access \ vatpit_freq \ vrtc_ops # These should probably go in the `vmm` tests, but since they depend on # in-guest payloads, it is easier to build them here. PROG += guest_tsc_adjust \ tsc_freq_ctrl \ wrmsr_tsc \ rdmsr_tsc PAYLOADS = $(PROG) include ../Makefile.in_guest COMMON_OBJS = in_guest.o common.o CLEANFILES = $(COMMON_OBJS) $(PAYLOAD_CLEANFILES) payload_utils.o CLOBBERFILES = $(PROG) ROOTOPTPKG = $(ROOT)/opt/bhyve-tests TESTDIR = $(ROOTOPTPKG)/tests/kdev CMDS = $(PROG:%=$(TESTDIR)/%) $(CMDS) : FILEMODE = 0555 CSTD= $(CSTD_GNU99) CPPFLAGS = -I$(COMPAT)/bhyve -I$(CONTRIB)/bhyve \ -I$(COMPAT)/bhyve/amd64 -I$(CONTRIB)/bhyve/amd64 \ $(CPPFLAGS.master) \ -I$(SRC)/uts/intel/io/vmm \ -I$(SRC)/uts/intel \ -I../common ASFLAGS += -D_ASM ASFLAGS64 += -D_ASM CFLAGS = -m64 $(PROG) : LDLIBS += -lvmmapi all: $(PROG) install: all $(CMDS) clean: -$(RM) $(CLEANFILES) clobber: clean -$(RM) $(CLOBBERFILES) $(CMDS): $(TESTDIR) $(PROG) $(TESTDIR): $(INS.dir) $(TESTDIR)/%: % $(INS.file) # Hammerhead: Cancel GNU Make built-in compile-and-link rule (%: %.c) %: %.c %: %.c pobj_%.o $(COMMON_OBJS) $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) %: %.o $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) $(PAYLOADS:%=payload_%): payload_utils.o /* * 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 2023 Oxide Computer Company */ /* * Test that adjusting the guest TSC with VMM time data interface is visible * in guest. * * Note: requires `vmm_allow_state_writes` to be set */ #include #include #include #include #include #include #include #include #include "in_guest.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } const int vmfd = vm_get_device_fd(ctx); /* Read time data to get baseline guest time values */ struct vdi_time_info_v1 time_info; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &time_info, }; if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed"); } /* Change the guest TSC to a much larger value */ uint64_t expect_tsc = 500000000000; time_info.vt_guest_tsc = expect_tsc; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { int error; error = errno; if (error == EPERM) { warn("VMM_DATA_WRITE got EPERM: is " "vmm_allow_state_writes set?"); } errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; bool half_read = false; uint64_t tsc; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t val; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &val)) { if (!half_read) { /* low 32-bits of TSC first */ tsc = val; half_read = true; ventry_fulfill_inout(&vexit, &ventry, 0); } else { /* high 32-bits of TSC */ tsc |= ((uint64_t)val << 32); /* * Check that the TSC reading is at least the * absurdly high value it was set to. */ if (tsc >= expect_tsc) { (void) printf("tsc=%ld\n", tsc); test_pass(); } else { test_fail_msg("TSC %lu < %lu\n", tsc, expect_tsc); } } } else { test_fail_vmexit(&vexit); } } while (true); } /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" #define UINT32_MAX 0xffffffff void start(void) { /* loop for as long as the host wants */ for (;;) { uint64_t tsc = rdtsc(); outl(IOP_TEST_VALUE, (uint32_t)(UINT32_MAX & tsc)); outl(IOP_TEST_VALUE, (uint32_t)(UINT32_MAX & (tsc >> 32))); } } /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define UINT32_MAX 0xffffffff #define MSR_TSC 0x10 void start(void) { /* write a value to the TSC */ wrmsr(MSR_TSC, TSC_TARGET_WRVAL); /* loop for as long as the host wants */ for (;;) { uint64_t tsc = rdmsr(MSR_TSC); outl(IOP_TEST_VALUE, UINT32_MAX & tsc); outl(IOP_TEST_VALUE, UINT32_MAX & (tsc >> 32)); } } /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" #define UINT32_MAX 0xffffffff void start(void) { /* read the period */ uint32_t period = inl(IOP_TEST_VALUE); /* loop for as long as the host wants */ for (;;) { uint64_t start, end; start = rdtsc(); outl(IOP_TEST_VALUE, UINT32_MAX & start); outl(IOP_TEST_VALUE, UINT32_MAX & (start >> 32)); do { end = rdtsc(); /* wait for enough ticks to pass */ } while ((end - start) < period); } } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" void timer0_reset(void) { /* * Configure timer 0 for interrupt-on-terminal-count mode, and prepare * it to be loaded with the high and low bytes. */ outb(IOP_ATPIT_CMD, 0x30); /* Load timer with max value (0xffff) */ outb(IOP_ATPIT_C0, 0xff); outb(IOP_ATPIT_C0, 0xff); } uint16_t timer0_read(void) { uint16_t val; /* Latch timer0 */ outb(IOP_ATPIT_CMD, 0x00); /* Read low and high bytes */ val = inb(IOP_ATPIT_C0); val |= (uint16_t)inb(IOP_ATPIT_C0) << 8; return (val); } void start(void) { /* loop for as long as the host wants */ for (;;) { uint16_t start, end; timer0_reset(); start = timer0_read(); outw(IOP_TEST_VALUE, start); do { end = timer0_read(); /* wait for enough ticks to pass */ } while (end > (start - ATPIT_TARGET_TICKS)); outw(IOP_TEST_VALUE, end); } } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define HPET_OFF_CONFIG 0x10 #define HPET_OFF_MAIN_COUNT_LOW 0xf0 #define HPET_CONFIG_ENABLE 1 static void write_hpet(uint_t reg, uint32_t value) { volatile uint32_t *ptr = (uint32_t *)(MMIO_HPET_BASE + reg); *ptr = value; } static uint32_t read_hpet_main_low(void) { volatile uint32_t *ptr = (uint32_t *)(MMIO_HPET_BASE + HPET_OFF_MAIN_COUNT_LOW); return (*ptr); } void start(void) { write_hpet(HPET_OFF_CONFIG, HPET_CONFIG_ENABLE); /* loop for as long as the host wants */ for (;;) { uint32_t start, end; start = read_hpet_main_low(); outl(IOP_TEST_VALUE, start); do { end = read_hpet_main_low(); /* wait for enough ticks to pass */ } while (end < (start + HPET_TARGET_TICKS)); outl(IOP_TEST_VALUE, end); } } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define LAPIC_OFF_SVR 0xf0 #define LAPIC_OFF_TIMER_ICR 0x380 #define LAPIC_OFF_TIMER_CCR 0x390 #define LAPIC_OFF_TIMER_DCR 0x3e0 #define LAPIC_SVR_ENABLE 0x100 static void write_vlapic(uint_t reg, uint32_t value) { volatile uint32_t *ptr = (uint32_t *)(MMIO_LAPIC_BASE + reg); *ptr = value; } static uint32_t read_vlapic(uint_t reg) { volatile uint32_t *ptr = (uint32_t *)(MMIO_LAPIC_BASE + reg); return (*ptr); } static uint32_t divisor_to_dcr(uint32_t inp) { switch (inp) { case 1: return (0xb); case 2: return (0x0); case 4: return (0x1); case 8: return (0x2); case 16: return (0x3); case 32: return (0x8); case 64: return (0x9); case 128: return (0xa); default: /* fail immediate if divisor is out of range */ outl(IOP_TEST_VALUE, 1); return (0xff); } } void start(void) { write_vlapic(LAPIC_OFF_SVR, LAPIC_SVR_ENABLE); /* loop for as long as the host wants */ for (;;) { uint32_t divisor; uint32_t start, end; divisor = inl(IOP_TEST_PARAM); write_vlapic(LAPIC_OFF_TIMER_DCR, divisor_to_dcr(divisor)); write_vlapic(LAPIC_OFF_TIMER_ICR, 0xffffffff); start = read_vlapic(LAPIC_OFF_TIMER_CCR); outl(IOP_TEST_VALUE, start); uint32_t target = start - LAPIC_TARGET_TICKS; do { end = read_vlapic(LAPIC_OFF_TIMER_CCR); /* wait for enough ticks to pass */ } while (end > target); outl(IOP_TEST_VALUE, end); } } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define LAPIC_OFF_SVR 0xf0 #define LAPIC_OFF_LVT_TIMER 0x320 #define LAPIC_OFF_TIMER_ICR 0x380 #define LAPIC_OFF_TIMER_CCR 0x390 #define LAPIC_OFF_TIMER_DCR 0x3e0 #define LAPIC_LVT_MASKED (1 << 16) #define LAPIC_LVT_PERIODIC (1 << 17) #define LAPIC_SVR_ENABLE 0x100 static void write_vlapic(uint_t reg, uint32_t value) { volatile uint32_t *ptr = (uint32_t *)(MMIO_LAPIC_BASE + reg); *ptr = value; } static uint32_t read_vlapic(uint_t reg) { volatile uint32_t *ptr = (uint32_t *)(MMIO_LAPIC_BASE + reg); return (*ptr); } static uint32_t divisor_to_dcr(uint32_t inp) { switch (inp) { case 1: return (0xb); case 2: return (0x0); case 4: return (0x1); case 8: return (0x2); case 16: return (0x3); case 32: return (0x8); case 64: return (0x9); case 128: return (0xa); default: /* fail immediate if divisor is out of range */ outl(IOP_TEST_VALUE, 1); return (0xff); } } void start(void) { write_vlapic(LAPIC_OFF_SVR, LAPIC_SVR_ENABLE); /* * Configure the LAPIC timer for periodic operation, but leave the * interrupt itself masked. */ write_vlapic(LAPIC_OFF_LVT_TIMER, LAPIC_LVT_MASKED | LAPIC_LVT_PERIODIC); /* loop for as long as the host wants */ for (;;) { const uint16_t divisor = inw(IOP_TEST_PARAM0); const uint16_t loop_count = inw(IOP_TEST_PARAM1); write_vlapic(LAPIC_OFF_TIMER_DCR, divisor_to_dcr(divisor)); write_vlapic(LAPIC_OFF_TIMER_ICR, LAPIC_TARGET_TICKS); uint32_t start, end, count = 0; start = read_vlapic(LAPIC_OFF_TIMER_CCR); outl(IOP_TEST_VALUE, start); uint32_t prev = start; do { end = read_vlapic(LAPIC_OFF_TIMER_CCR); /* timer period rolled over */ if (end > prev) { count++; } prev = end; } while (count < loop_count); outl(IOP_TEST_VALUE, end); } } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define MSR_APICBASE 0x1b #define APICBASE_X2APIC (1 << 10) static void write_vlapic(uint_t reg, uint32_t value) { volatile uint32_t *ptr = (uint32_t *)(MMIO_LAPIC_BASE + reg); *ptr = value; } static uint32_t read_vlapic(uint_t reg) { volatile uint32_t *ptr = (uint32_t *)(MMIO_LAPIC_BASE + reg); return (*ptr); } static void barrier(void) { asm volatile("": : :"memory"); } void start(void) { uint64_t base = rdmsr(MSR_APICBASE); if ((base & APICBASE_X2APIC) != 0) { /* bail if the host has enabled x2apic for us */ outb(IOP_TEST_RESULT, TEST_RESULT_FAIL); } /* Access the "normal" register offsets */ for (uint_t reg = 0; reg < 0x1000; reg += 16) { uint32_t val; /* * This ignores the fact that some register offsets are reserved * (such as 0x3a0-0x3d0 and 0x3f0-0xff0) while others may be * read-only or write-only. For the time being, we know that * the emulation in bhyve will not emit errors or faults for * such indiscretions committed via MMIO. */ val = read_vlapic(reg); write_vlapic(reg, val); } /* * Scan through byte-wise, even though such behavior is undefined as far * as a to-specification LAPIC is concerned. */ for (uint_t off = 0; off < 0x1000; off++) { volatile uint8_t *ptr = (uint8_t *)(MMIO_LAPIC_BASE + off); uint8_t val; val = *ptr; barrier(); *ptr = val; } /* If we made it this far without an exception, it is a win */ outb(IOP_TEST_RESULT, TEST_RESULT_PASS); } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define MSR_APICBASE 0x1b #define MSR_X2APIC_BASE 0x800 #define MSR_X2APIC_MAX 0x8ff #define APICBASE_X2APIC (1 << 10) static bool reg_readable(uint32_t reg) { switch (reg) { case 0x802: /* ID */ case 0x803: /* VER */ case 0x808: /* TPR */ case 0x809: /* APR */ case 0x80a: /* PPR */ case 0x80c: /* RRR */ case 0x80d: /* LDR */ case 0x80e: /* DFR */ case 0x80f: /* SVR */ case 0x810 ... 0x817: /* ISR */ case 0x818 ... 0x81f: /* TMR */ case 0x820 ... 0x827: /* IRR */ case 0x828: /* ESR */ case 0x82f: /* LVT_CMCI */ case 0x830: /* ICR */ case 0x832: /* LVT_TIMER */ case 0x833: /* LVT_THERMAL */ case 0x834: /* LVT_PERF */ case 0x835: /* LVT_LINT0 */ case 0x836: /* LVT_LINT1 */ case 0x837: /* LVT_ERROR */ case 0x838: /* TIMER_ICR */ case 0x839: /* TIMER_CCR */ case 0x83e: /* TIMER_DCR */ return (true); default: return (false); } } static bool reg_writable(uint32_t reg) { switch (reg) { case 0x802: /* ID */ case 0x808: /* TPR */ case 0x80b: /* EOI */ case 0x80d: /* LDR */ case 0x80e: /* DFR */ case 0x80f: /* SVR */ case 0x828: /* ESR */ case 0x82f: /* LVT_CMCI */ case 0x830: /* ICR */ case 0x832: /* LVT_TIMER */ case 0x833: /* LVT_THERMAL */ case 0x834: /* LVT_PERF */ case 0x835: /* LVT_LINT0 */ case 0x836: /* LVT_LINT1 */ case 0x837: /* LVT_ERROR */ case 0x838: /* TIMER_ICR */ case 0x83e: /* TIMER_DCR */ case 0x83f: /* SELF_IPI */ return (true); default: return (false); } } void start(void) { uint64_t base = rdmsr(MSR_APICBASE); if ((base & APICBASE_X2APIC) == 0) { /* bail if the host has not enabled x2apic for us */ outb(IOP_TEST_RESULT, TEST_RESULT_FAIL); } for (uint32_t msr = MSR_X2APIC_BASE; msr <= MSR_X2APIC_MAX; msr++) { uint64_t val = 0; if (reg_readable(msr)) { val = rdmsr(msr); } if (reg_writable(msr)) { if (msr == 0x828) { /* * While the LAPIC is in x2APIC mode, writes to * the ESR must carry a value of 0. */ val = 0; } wrmsr(msr, val); } } /* If we made it this far without a #GP, it counts as a win */ outb(IOP_TEST_RESULT, TEST_RESULT_PASS); } /* * 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 "payload_common.h" #include "payload_utils.h" #include "test_defs.h" void start(void) { /* loop for as long as the host wants */ for (;;) { uint32_t start, end; start = inl(IOP_PMTMR); outl(IOP_TEST_VALUE, start); do { end = inl(IOP_PMTMR); /* wait for enough ticks to pass */ } while (end < (start + PMTMR_TARGET_TICKS)); outl(IOP_TEST_VALUE, end); } } /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" #include "test_defs.h" /* Convenience definitions for RTC offsets */ #define RTC_SEC 0x00 #define RTC_MIN 0x02 #define RTC_HOUR 0x04 #define RTC_DAY 0x07 #define RTC_MONTH 0x08 #define RTC_YEAR 0x09 #define RTC_CENTURY 0x32 #define RTC_REGA 0x0a #define RTC_REGB 0x0b #define RTC_REGC 0x0c #define RTC_REGD 0x0d #define REGA_DIVIDER_32K 0x20 #define REGA_DIVIDER_DIS 0x70 #define REGA_PERIOD_512HZ 0x07 #define REGA_PERIOD_128HZ 0x09 #define REGB_HALT 0x80 #define REGB_IE_PERIODIC 0x40 #define REGB_IE_ALARM 0x20 #define REGB_IE_UPDATE 0x10 #define REGB_DATA_BIN 0x04 #define REGB_24HR 0x02 #define REGB_DST 0x01 #define REGC_IRQ 0x80 #define REGC_PERIODIC 0x40 #define REGC_ALARM 0x20 #define REGC_UPDATE 0x10 #define PPM_THRESHOLD 500 #define ABS(x) ((x) < 0 ? -(x) : (x)) static uint8_t rtc_last_off = 0xff; static uint8_t rtc_read(uint8_t off) { if (off != rtc_last_off) { outb(IOP_RTC_ADDR, off); rtc_last_off = off; } return (inb(IOP_RTC_DATA)); } static void rtc_write(uint8_t off, uint8_t data) { if (off != rtc_last_off) { outb(IOP_RTC_ADDR, off); rtc_last_off = off; } return (outb(IOP_RTC_DATA, data)); } static uint8_t wait_for_flag(uint8_t mask) { uint8_t regc; do { regc = rtc_read(RTC_REGC); } while ((regc & mask) == 0); return (regc); } /* Prepare the subordinate PIC to process interrupts from RTC */ static void atpic_init(void) { /* ICW1: INIT | ICW4 */ outb(IOP_ATPIC_SCMD, 0x11); /* ICW2: vector offset (useless in context) */ outb(IOP_ATPIC_SDATA, 0x20); /* ICW3: cascade info (ignored) */ outb(IOP_ATPIC_SDATA, 0x00); /* ICW3: 8086_MODE | AEOI */ outb(IOP_ATPIC_SDATA, 0x03); /* No masked bits */ outb(IOP_ATPIC_SDATA, 0x00); } /* Poll the subordinate PIC for an IRQ */ static uint8_t atpit_poll_for_intr(void) { uint8_t val = 0; do { /* OCW3: POLL */ outb(IOP_ATPIC_SCMD, 0x0c); val = inb(IOP_ATPIC_SDATA); } while ((val & 0x80) == 0); return (val); } static void test_periodic_polling(void) { /* Halt the RTC to prep for test of periodic timer */ rtc_write(RTC_REGA, REGA_DIVIDER_DIS); rtc_write(RTC_REGB, REGB_HALT); /* Clear any pending event flags */ (void) rtc_read(RTC_REGC); test_msg("testing periodic (polling)"); /* Release divider to run, configuring a 512Hz periodic timer */ rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ); rtc_write(RTC_REGB, 0); /* Count periodic firings until the next time update */ uint_t periodic_fire = 0; uint8_t events = 0; do { events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC); if ((events & REGC_PERIODIC) != 0) { periodic_fire++; } } while ((events & REGC_UPDATE) == 0); /* * In the 500ms between releasing the divider and the first time update, * we expect 256 firings of the 512Hz periodic timer. */ if (periodic_fire != 256) { TEST_ABORT("unexpected periodic firing count at 512Hz"); } /* Change the periodic timer to 128Hz */ rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_128HZ); /* Count periodic firings until the next time update */ periodic_fire = 0; do { events = wait_for_flag(REGC_UPDATE | REGC_PERIODIC); if ((events & REGC_PERIODIC) != 0) { periodic_fire++; } } while ((events & REGC_UPDATE) == 0); /* * With 1s between time updates, we expect 128 firings for the * reconfigured 128Hz periodic timer. */ if (periodic_fire != 128) { TEST_ABORT("unexpected periodic firing count at 128Hz"); } } static void test_periodic_interrupts(void) { /* Halt the RTC to prep for test of periodic timer */ rtc_write(RTC_REGA, REGA_DIVIDER_DIS); rtc_write(RTC_REGB, REGB_HALT); /* Clear any pending event flags */ (void) rtc_read(RTC_REGC); test_msg("testing periodic (interrupts)"); /* * The RTC IRQ is routed on line 8, which corresponds to pin 0 on the * subordinate PIC. Initialize it now so we can poll for interrupts. */ atpic_init(); /* Release divider to run, configuring a 512Hz periodic timer */ rtc_write(RTC_REGA, REGA_DIVIDER_32K | REGA_PERIOD_512HZ); /* Enable interrupts for periodic timer and 1Hz update */ rtc_write(RTC_REGB, REGB_IE_PERIODIC | REGB_IE_UPDATE); /* Count periodic firings until the next time update */ uint_t periodic_fire = 0; uint8_t events = 0; do { const uint8_t irq = atpit_poll_for_intr(); if (irq != 0x80) { /* * RTC is pin 0 on the subordinate PIC chip, so we * expect only the interrupt-present bit set */ TEST_ABORT("spurious interrupt on PIC"); } events = rtc_read(RTC_REGC); /* Since we waited for the interrupt, the flag should be here */ if ((events & REGC_IRQ) == 0) { TEST_ABORT("missing IRQ flag in regc"); } if ((events & REGC_PERIODIC) != 0) { periodic_fire++; } } while ((events & REGC_UPDATE) == 0); /* * Like the polling periodic test, we expect 256 firings of the 512Hz * timer between the release of the divider and the first update. */ if (periodic_fire != 256) { TEST_ABORT("unexpected periodic firing count at 512Hz"); } /* Disable periodic configuration from RTC */ rtc_write(RTC_REGA, REGA_DIVIDER_DIS); rtc_write(RTC_REGB, REGB_HALT); } void start(void) { /* * Initialize RTC to known state: * - rega: divider and periodic timer disabled * - regb: updates halted, intr disabled, 24hr time, binary fmt, no DST * - regc: cleared (by read) */ rtc_write(RTC_REGA, REGA_DIVIDER_DIS); rtc_write(RTC_REGB, REGB_HALT | REGB_DATA_BIN | REGB_24HR); (void) rtc_read(RTC_REGC); /* Start at 1970 epoch */ rtc_write(RTC_DAY, 1); rtc_write(RTC_MONTH, 1); rtc_write(RTC_YEAR, 70); rtc_write(RTC_CENTURY, 19); rtc_write(RTC_HOUR, 0); rtc_write(RTC_MIN, 0); rtc_write(RTC_SEC, 0); uint64_t start, end; /* * After allowing the divider to run, and enabling time updates, we * expect a 500ms delay until the first update to the date/time data. * Measure this with the TSC, even though we do not have a calibration * for its frequency. */ rtc_write(RTC_REGA, REGA_DIVIDER_32K); start = rdtsc(); rtc_write(RTC_REGB, REGB_DATA_BIN | REGB_24HR); if (rtc_read(RTC_REGC) != 0) { TEST_ABORT("unexpected flags set in regC"); } test_msg("waiting for first update"); (void) wait_for_flag(REGC_UPDATE); end = rdtsc(); const uint64_t tsc_500ms = end - start; start = end; /* Expect the clock to read 00:00:01 after the first update */ if (rtc_read(RTC_SEC) != 1) { TEST_ABORT("did not find 01 in seconds field"); } /* Wait for another update to pass by */ test_msg("waiting for second update"); (void) wait_for_flag(REGC_UPDATE); end = rdtsc(); const uint64_t tsc_1s = end - start; /* Expect the clock to read 00:00:02 after the second update */ if (rtc_read(RTC_SEC) != 2) { TEST_ABORT("did not find 02 in seconds field"); } /* * Determine ratio between the intervals which should be 500ms and * 1000ms long, as measured by the TSC. */ int64_t ppm_delta = (int64_t)(tsc_500ms * 2 * 1000000) / tsc_1s; ppm_delta = ABS(ppm_delta - 1000000); if (ppm_delta > PPM_THRESHOLD) { TEST_ABORT("clock update timing outside threshold"); } /* Put RTC in 12-hr, BCD-formatted mode */ rtc_write(RTC_REGA, REGA_DIVIDER_DIS); rtc_write(RTC_REGB, REGB_HALT); /* Set time to 11:59:59, prepping for roll-over into noon */ rtc_write(RTC_HOUR, 0x11); rtc_write(RTC_MIN, 0x59); rtc_write(RTC_SEC, 0x59); /* Release the clock to run again */ rtc_write(RTC_REGA, REGA_DIVIDER_32K); rtc_write(RTC_REGB, 0); /* Wait for it to tick over */ test_msg("waiting for noon tick-over"); (void) wait_for_flag(REGC_UPDATE); if (rtc_read(RTC_SEC) != 0) { TEST_ABORT("invalid RTC_SEC value"); } if (rtc_read(RTC_MIN) != 0) { TEST_ABORT("invalid RTC_MIN value"); } /* Hour field should now hold 0x12 (BCD noon) | 0x80 (PM flag) */ if (rtc_read(RTC_HOUR) != 0x92) { TEST_ABORT("invalid RTC_HOUR value"); } test_periodic_polling(); test_periodic_interrupts(); /* * TODO - Add additional tests: * - alarm interrupts */ /* Happy for now */ test_result_pass(); } /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" #include "test_defs.h" #define UINT32_MAX 0xffffffff #define MSR_TSC 0x10 void start(void) { /* write a value to the TSC */ wrmsr(0x10, TSC_TARGET_WRVAL); /* loop for as long as the host wants */ for (;;) { uint64_t tsc = rdtsc(); outl(IOP_TEST_VALUE, UINT32_MAX & tsc); outl(IOP_TEST_VALUE, UINT32_MAX & (tsc >> 32)); } } /* * 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 2023 Oxide Computer Company */ /* * Test guest reads of the TSC via rdmsr (following a write to the TSC) */ #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; bool half_read = false; uint64_t tsc; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t val; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &val)) { if (!half_read) { /* low 32-bits of TSC first */ tsc = val; half_read = true; ventry_fulfill_inout(&vexit, &ventry, 0); } else { /* high 32-bits of TSC */ tsc |= ((uint64_t)val << 32); printf("tsc=%lu\n", tsc); /* * Check that the TSC reading is at least the * high value it was set to by the guest. * * If we wanted to be more precise about it, we * could get the host frequency and calculate * ppm error. * */ if (tsc < TSC_TARGET_WRVAL) { test_fail_msg("TSC %lu < %lu", tsc, TSC_TARGET_WRVAL); } else { test_pass(); } } } else { test_fail_vmexit(&vexit); } } while (true); } /* * 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 2023 Oxide Computer Company */ #ifndef _TEST_DEFS_H_ #define _TEST_DEFS_H_ #define IOP_PMTMR 0x408 #define IOP_ATPIT_C0 0x40 #define IOP_ATPIT_CMD 0x43 #define IOP_ATPIC_MCMD 0x20 #define IOP_ATPIC_MDATA 0x21 #define IOP_ATPIC_SCMD 0xa0 #define IOP_ATPIC_SDATA 0xa1 #define IOP_RTC_ADDR 0x70 #define IOP_RTC_DATA 0x71 #define MMIO_HPET_BASE 0xfed00000UL #define MMIO_LAPIC_BASE 0xfee00000UL #define PMTMR_FREQ 3579545 #define PMTMR_TARGET_TICKS (PMTMR_FREQ / 10) #define HPET_FREQ (1 << 24) #define HPET_TARGET_TICKS (HPET_FREQ / 10) #define LAPIC_FREQ (128 * 1024 * 1024) #define LAPIC_TARGET_TICKS (LAPIC_FREQ / 50) #define ATPIT_FREQ 1193182 #define ATPIT_TARGET_TICKS (ATPIT_FREQ / 50) #define TSC_TARGET_WRVAL 500000000000 #endif /* _TEST_DEFS_H_ */ /* * 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 2023 Oxide Computer Company */ /* * Test guest frequency control * * Note: requires `vmm_allow_state_writes` to be set, and only on AMD */ #include #include #include #include #include #include #include #include #include #include "common.h" #include "in_guest.h" #define PPM_MARGIN 200 typedef struct tsc_reading { hrtime_t when; uint64_t tsc; } tsc_reading_t; static bool check_reading(tsc_reading_t r1, tsc_reading_t r2, uint64_t guest_freq, uint64_t min_ticks, uint64_t tick_margin, uint32_t ppm_margin) { hrtime_t time_delta = r2.when - r1.when; uint64_t tick_delta = r2.tsc - r1.tsc; /* check that number of ticks seen is valid */ if (tick_delta < min_ticks) { test_fail_msg("inadequate passage of guest TSC ticks %u < %u\n", tick_delta, min_ticks); } else if ((tick_delta - min_ticks) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, min_ticks + tick_margin); return (false); } /* compute ppm error and validate */ hrtime_t time_target = (tick_delta * NANOSEC) / guest_freq; hrtime_t offset; if (time_delta < time_target) { offset = time_target - time_delta; } else { offset = time_delta - time_target; } uint64_t ppm = (offset * 1000000) / time_target; (void) printf("%u ticks in %lu ns (error %lu ppm)\n", tick_delta, time_delta, ppm); if (ppm > ppm_margin) { (void) printf("UNACCEPTABLE!\n"); return (false); } return (true); } void do_freq_test(uint64_t guest_freq, uint8_t per_sec, uint8_t seconds, const int vmfd, struct vcpu *vcpu, struct vdi_time_info_v1 *src) { /* configure the guest to have the desired frequency */ struct vdi_time_info_v1 time_info = { .vt_guest_freq = guest_freq, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &time_info, }; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { int error; error = errno; if (error == EPERM) { warn("VMM_DATA_WRITE got EPERM: is " "vmm_allow_state_writes set?"); } errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed"); } /* * Run the test: * - ask the guest to report the TSC every 1/per_sec seconds, in terms * of guest ticks * - collect readings for `seconds` seconds, along with host hrtime * - to avoid additional latency in the readings, process readings at * the end: check for error in the number of ticks reported and the * ppm based on host hrtime */ uint64_t guest_ticks = guest_freq / per_sec; const uint32_t nreadings = per_sec * seconds + 1; tsc_reading_t tsc_readings[nreadings]; /* 5 percent margin */ const uint32_t tick_margin = guest_ticks / 20; bool half_read = false; uint64_t cur_tsc; uint32_t count = 0; struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { if (count >= nreadings) { /* test completed: check results */ for (int i = 1; i < nreadings; i++) { if (!check_reading(tsc_readings[i-1], tsc_readings[i], guest_freq, guest_ticks, tick_margin, PPM_MARGIN)) { test_fail_msg("freq test failed"); } } break; } const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t val; if (vexit_match_inout(&vexit, true, IOP_TEST_VALUE, 4, &val)) { /* test setup: tell guest how often to report its tsc */ ventry_fulfill_inout(&vexit, &ventry, guest_ticks); } else if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &val)) { /* * Get reported guest TSC in two 32-bit parts, with the * lower bits coming in first. */ if (!half_read) { /* lower bits */ cur_tsc = val; half_read = true; ventry_fulfill_inout(&vexit, &ventry, 0); } else { /* upper bits */ cur_tsc |= ((uint64_t)val << 32); tsc_readings[count].when = gethrtime(); tsc_readings[count].tsc = cur_tsc; half_read = false; cur_tsc = 0; count++; ventry_fulfill_inout(&vexit, &ventry, 0); } } else { test_fail_vmexit(&vexit); } } while (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } const int vmfd = vm_get_device_fd(ctx); const bool is_svm = cpu_vendor_amd(); if (!is_svm) { test_fail_msg("intel not supported\n"); } /* Read time data to get baseline guest time values */ struct vdi_time_info_v1 time_info; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &time_info, }; if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed"); } uint64_t host_freq = time_info.vt_guest_freq; uint64_t guest_freq = host_freq; /* measure each test frequency 10x per sec, for 1 second */ const uint8_t per_sec = 10; const uint8_t seconds = 1; /* 2x host frequency */ guest_freq = host_freq * 2; (void) printf("testing 2x host_freq: guest_freq=%lu, host_freq=%lu\n", guest_freq, host_freq); do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info); /* reset guest */ vm_vcpu_close(vcpu); test_cleanup(false); ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } /* 0.5x host frequency */ guest_freq = host_freq / 2; (void) printf("testing 0.5x host_freq: guest_freq=%lu, host_freq=%lu\n", guest_freq, host_freq); do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info); /* reset guest */ vm_vcpu_close(vcpu); test_cleanup(false); ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } /* 1/3 host frequency */ guest_freq = host_freq / 3; (void) printf("testing 1/3 host_freq: guest_freq=%lu, host_freq=%lu\n", guest_freq, host_freq); do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info); /* reset guest */ vm_vcpu_close(vcpu); test_cleanup(false); ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } /* 1x host frequency */ guest_freq = host_freq; (void) printf("testing 1x host_freq: guest_freq=%lu, host_freq=%lu\n", guest_freq, host_freq); do_freq_test(guest_freq, per_sec, seconds, vmfd, vcpu, &time_info); test_pass(); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" typedef struct reading { hrtime_t when; uint16_t value; } reading_t; static bool check_reading(reading_t before, reading_t after, uint_t tick_margin, uint_t ppm_margin) { hrtime_t time_delta = after.when - before.when; uint16_t tick_delta; tick_delta = before.value - after.value; /* is the number of ticks OK? */ if (tick_delta < ATPIT_TARGET_TICKS) { test_fail_msg("inadequate passage of ticks %u < %u\n", tick_delta, ATPIT_TARGET_TICKS); } else if ((tick_delta - ATPIT_TARGET_TICKS) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, ATPIT_TARGET_TICKS + tick_margin); return (false); } hrtime_t time_target = (tick_delta * NANOSEC) / ATPIT_FREQ; hrtime_t offset; if (time_delta < time_target) { offset = time_target - time_delta; } else { offset = time_delta - time_target; } uint64_t ppm = (offset * 1000000) / time_target; (void) printf("margin limits: ticks=%u ppm=%lu\n", tick_margin, ppm_margin); (void) printf("%u ticks in %lu ns (error %lu ppm)\n", tick_delta, time_delta, ppm); if (ppm > ppm_margin) { (void) printf("UNACCEPTABLE!\n"); return (false); } return (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; reading_t readings[2]; uint_t nread = 0; uint_t nrepeat = 0; /* * Since the PIT is slower to read back (requiring 3 emulated reads), * operate with a more loose ticks margin. */ const uint_t margin_ticks = MAX(1, ATPIT_TARGET_TICKS / 2500); const uint_t margin_ppm = 400; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t v; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 2, &v)) { readings[nread].when = gethrtime(); readings[nread].value = v; ventry_fulfill_inout(&vexit, &ventry, 0); nread++; if (nread != 2) { continue; } if (check_reading(readings[0], readings[1], margin_ticks, margin_ppm)) { test_pass(); } else { nrepeat++; if (nrepeat < 3) { nread = 0; (void) printf("retry %u\n", nrepeat); continue; } test_fail_msg("bad result after %u retries\n", nrepeat); } } else { test_fail_vmexit(&vexit); } } while (true); return (0); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" typedef struct reading { hrtime_t when; uint32_t value; } reading_t; static bool check_reading(reading_t before, reading_t after, uint_t tick_margin, uint_t ppm_margin) { hrtime_t time_delta = after.when - before.when; uint32_t tick_delta; if (after.value < before.value) { /* handle rollover */ tick_delta = (UINT32_MAX - before.value) + after.value; } else { tick_delta = after.value - before.value; } /* is the number of ticks OK? */ if (tick_delta < HPET_TARGET_TICKS) { test_fail_msg("inadequate passage of ticks %u < %u\n", tick_delta, HPET_TARGET_TICKS); } else if ((tick_delta - HPET_TARGET_TICKS) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, HPET_TARGET_TICKS + tick_margin); return (false); } hrtime_t time_target = (tick_delta * NANOSEC) / HPET_FREQ; hrtime_t offset; if (time_delta < time_target) { offset = time_target - time_delta; } else { offset = time_delta - time_target; } uint64_t ppm = (offset * 1000000) / time_target; (void) printf("margin limits: ticks=%u ppm=%lu\n", tick_margin, ppm_margin); (void) printf("%u ticks in %lu ns (error %lu ppm)\n", tick_delta, time_delta, ppm); if (ppm > ppm_margin) { (void) printf("UNACCEPTABLE!\n"); return (false); } return (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; reading_t readings[2]; uint_t nread = 0; uint_t nrepeat = 0; const uint_t margin_ticks = MAX(1, HPET_TARGET_TICKS / 10000); const uint_t margin_ppm = 400; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t v; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &v)) { readings[nread].when = gethrtime(); readings[nread].value = v; ventry_fulfill_inout(&vexit, &ventry, 0); nread++; if (nread != 2) { continue; } if (check_reading(readings[0], readings[1], margin_ticks, margin_ppm)) { test_pass(); } else { nrepeat++; if (nrepeat < 3) { nread = 0; (void) printf("retry %u\n", nrepeat); continue; } test_fail_msg("bad result after %u retries\n", nrepeat); } } else { test_fail_vmexit(&vexit); } } while (true); return (0); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" typedef struct reading { hrtime_t when; uint32_t value; } reading_t; static bool check_reading(reading_t before, reading_t after, uint_t divisor, uint_t tick_margin, uint_t ppm_margin) { hrtime_t time_delta = after.when - before.when; uint32_t tick_delta; /* * The ticks margin should shrink proportionally to how coarsely the * timer clock is being divided. */ tick_margin /= divisor; /* timer is counting down, so act appropriately */ if (after.value > before.value) { /* handle rollover */ tick_delta = (UINT32_MAX - after.value) + before.value; } else { tick_delta = before.value - after.value; } /* is the number of ticks OK? */ if (tick_delta < LAPIC_TARGET_TICKS) { test_fail_msg("inadequate passage of ticks %u < %u\n", tick_delta, LAPIC_TARGET_TICKS); } else if ((tick_delta - LAPIC_TARGET_TICKS) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, LAPIC_TARGET_TICKS + tick_margin); return (false); } hrtime_t time_target = (tick_delta * NANOSEC * divisor) / LAPIC_FREQ; hrtime_t offset; if (time_delta < time_target) { offset = time_target - time_delta; } else { offset = time_delta - time_target; } uint64_t ppm = (offset * 1000000) / time_target; (void) printf("params: tick_margin=%u ppm_margin=%lu divisor=%u\n", tick_margin, ppm_margin, divisor); (void) printf("%u ticks in %lu ns (error %lu ppm)\n", tick_delta, time_delta, ppm); if (ppm > ppm_margin) { (void) printf("UNACCEPTABLE!\n"); return (false); } return (true); } static void test_for_divisor(struct vcpu *vcpu, uint_t divisor, struct vm_entry *ventry, struct vm_exit *vexit) { reading_t readings[2]; uint_t nread = 0; uint_t nrepeat = 0; const uint_t margin_ticks = MAX(1, LAPIC_TARGET_TICKS / 5000); const uint_t margin_ppm = 400; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, ventry, vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(vexit); } /* input the divisor */ if (vexit_match_inout(vexit, true, IOP_TEST_PARAM, 4, NULL)) { ventry_fulfill_inout(vexit, ventry, divisor); continue; } uint32_t v; if (vexit_match_inout(vexit, false, IOP_TEST_VALUE, 4, &v)) { readings[nread].when = gethrtime(); readings[nread].value = v; ventry_fulfill_inout(vexit, ventry, 0); nread++; if (nread != 2) { continue; } if (check_reading(readings[0], readings[1], divisor, margin_ticks, margin_ppm)) { (void) printf("good result\n"); return; } else { nrepeat++; if (nrepeat < 3) { nread = 0; (void) printf("retry %u\n", nrepeat); continue; } test_fail_msg("bad result after %u retries\n", nrepeat); } } else { test_fail_vmexit(vexit); } } while (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; test_for_divisor(vcpu, 2, &ventry, &vexit); test_for_divisor(vcpu, 4, &ventry, &vexit); test_for_divisor(vcpu, 16, &ventry, &vexit); test_pass(); return (0); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" typedef struct reading { hrtime_t when; uint32_t value; } reading_t; static bool check_reading(reading_t before, reading_t after, uint_t divisor, uint_t loops, uint_t tick_margin, uint_t ppm_margin) { const hrtime_t time_delta = after.when - before.when; /* * The ticks margin should shrink proportionally to how coarsely the * timer clock is being divided. */ tick_margin /= divisor; /* * The 'before' measurement includes the ticks which occurred between * programming the timer and taking the first reading. The 'after' * measurement includes the number of loops (each consisting of the * target tick count) plus however many ticks had transpired since the * most recent roll-over. */ const uint32_t tick_delta = loops * LAPIC_TARGET_TICKS + before.value - after.value; const uint32_t tick_target = loops * LAPIC_TARGET_TICKS; /* is the number of ticks OK? */ if (tick_delta < tick_target) { if ((tick_target - tick_delta) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, tick_target - tick_margin); } } else if ((tick_delta - tick_target) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, tick_target + tick_margin); return (false); } hrtime_t time_target = (tick_delta * NANOSEC * divisor) / LAPIC_FREQ; hrtime_t offset; if (time_delta < time_target) { offset = time_target - time_delta; } else { offset = time_delta - time_target; } uint64_t ppm = (offset * 1000000) / time_target; (void) printf("params: tick_margin=%u ppm_margin=%lu divisor=%u\n", tick_margin, ppm_margin, divisor); (void) printf("%u ticks in %lu ns (error %lu ppm)\n", tick_delta, time_delta, ppm); if (ppm > ppm_margin) { (void) printf("UNACCEPTABLE!\n"); return (false); } return (true); } static void run_test(struct vcpu *vcpu, uint_t divisor, uint_t loops, struct vm_entry *ventry, struct vm_exit *vexit) { reading_t readings[2]; uint_t nread = 0; uint_t nrepeat = 0; const uint_t margin_ticks = MAX(1, LAPIC_TARGET_TICKS / 5000); const uint_t margin_ppm = 400; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, ventry, vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(vexit); } /* input the divisor (bits 0-15) and loop count (bits 16-31) */ if (vexit_match_inout(vexit, true, IOP_TEST_PARAM0, 2, NULL)) { ventry_fulfill_inout(vexit, ventry, divisor); continue; } /* input the loop count */ if (vexit_match_inout(vexit, true, IOP_TEST_PARAM1, 2, NULL)) { ventry_fulfill_inout(vexit, ventry, loops); continue; } uint32_t v; if (vexit_match_inout(vexit, false, IOP_TEST_VALUE, 4, &v)) { readings[nread].when = gethrtime(); readings[nread].value = v; ventry_fulfill_inout(vexit, ventry, 0); nread++; if (nread != 2) { continue; } if (check_reading(readings[0], readings[1], divisor, loops, margin_ticks, margin_ppm)) { (void) printf("good result\n"); return; } else { nrepeat++; if (nrepeat < 3) { nread = 0; (void) printf("retry %u\n", nrepeat); continue; } test_fail_msg("bad result after %u retries\n", nrepeat); } } else { test_fail_vmexit(vexit); } } while (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; run_test(vcpu, 4, 3, &ventry, &vexit); run_test(vcpu, 2, 4, &ventry, &vexit); test_pass(); return (0); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" const char *strict_name = "STRICT_APICV"; static bool strict_apicv(void) { const char *strict_val; if ((strict_val = getenv(strict_name)) != NULL) { if (strlen(strict_val) != 0 && strcmp(strict_val, "0") != 0) { return (true); } } return (false); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } /* * Although x2APIC should be off by default, make doubly sure by * explicitly setting it so. */ err = vm_set_x2apic_state(vcpu, X2APIC_DISABLED); if (err != 0) { test_fail_errno(err, "Could not disable x2apic on vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_TEST_PASS: test_pass(); break; case VEK_TEST_FAIL: test_fail_msg("payload signaled failure"); break; case VEK_UNHANDLED: /* * Not all APICv-accelerated accesses are properly * handled by the in-kernel emulation today. * (See: illumos #13847). * * To allow this test to be useful on systems without * APICv, we suppress such failures unless explicitly * strict handling is requested. */ if (vexit.exitcode == VM_EXITCODE_VMX && (vexit.u.vmx.exit_reason == 44 || vexit.u.vmx.exit_reason == 56)) { if (strict_apicv()) { test_fail_vmexit(&vexit); } (void) fprintf(stderr, "Ignoring APICv access issue\n" "If strictness is desired, " "run with %s=1 in env\n", strict_name); test_pass(); } test_fail_vmexit(&vexit); break; default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } err = vm_set_x2apic_state(vcpu, X2APIC_ENABLED); if (err != 0) { test_fail_errno(err, "Could not enable x2apic on vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_TEST_PASS: test_pass(); break; case VEK_TEST_FAIL: test_fail_msg("payload signaled failure"); break; case VEK_UNHANDLED: default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" typedef struct reading { hrtime_t when; uint32_t value; } reading_t; static bool check_reading(reading_t before, reading_t after, uint_t tick_margin, uint_t ppm_margin) { hrtime_t time_delta = after.when - before.when; uint32_t tick_delta; if (after.value < before.value) { /* handle rollover */ tick_delta = (UINT32_MAX - before.value) + after.value; } else { tick_delta = after.value - before.value; } /* is the number of ticks OK? */ if (tick_delta < PMTMR_TARGET_TICKS) { test_fail_msg("inadequate passage of ticks %u < %u\n", tick_delta, PMTMR_TARGET_TICKS); } else if ((tick_delta - PMTMR_TARGET_TICKS) > tick_margin) { (void) printf("%u ticks outside margin %u\n", tick_delta, PMTMR_TARGET_TICKS + tick_margin); return (false); } hrtime_t time_target = (tick_delta * NANOSEC) / PMTMR_FREQ; hrtime_t offset; if (time_delta < time_target) { offset = time_target - time_delta; } else { offset = time_delta - time_target; } uint64_t ppm = (offset * 1000000) / time_target; (void) printf("margin limits: ticks=%u ppm=%lu\n", tick_margin, ppm_margin); (void) printf("%u ticks in %lu ns (error %lu ppm)\n", tick_delta, time_delta, ppm); if (ppm > ppm_margin) { (void) printf("UNACCEPTABLE!\n"); return (false); } return (true); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = vm_pmtmr_set_location(ctx, IOP_PMTMR); if (err != 0) { test_fail_errno(err, "Could not place pmtmr"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; reading_t readings[2]; uint_t nread = 0; uint_t nrepeat = 0; const uint_t margin_ticks = MAX(1, PMTMR_TARGET_TICKS / 10000); const uint_t margin_ppm = 400; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t v; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &v)) { readings[nread].when = gethrtime(); readings[nread].value = vexit.u.inout.eax; ventry_fulfill_inout(&vexit, &ventry, 0); nread++; if (nread != 2) { continue; } if (check_reading(readings[0], readings[1], margin_ticks, margin_ppm)) { test_pass(); } else { nrepeat++; if (nrepeat < 3) { nread = 0; (void) printf("retry %u\n", nrepeat); continue; } test_fail_msg("bad result after %u retries\n", nrepeat); } } else { test_fail_vmexit(&vexit); } } while (true); return (0); } /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" static bool timed_out = false; static void sigalrm_handler(int sig) { timed_out = true; } static void configure_timeout(uint_t seconds) { struct sigaction sa = { .sa_handler = sigalrm_handler, }; struct sigaction old_sa; if (sigaction(SIGALRM, &sa, &old_sa) != 0) { test_fail_errno(errno, "could not prep signal handling for bad access"); } (void) alarm(seconds); } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } timespec_t ts = { 0 }; err = vm_rtc_settime(ctx, &ts); if (err != 0) { test_fail_errno(err, "Could zero out RTC time"); } /* A successful payload should be wrapped up well before 8 seconds */ configure_timeout(8); struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (timed_out) { test_fail_msg("test timed out\n"); } switch (kind) { case VEK_REENTR: break; case VEK_TEST_PASS: test_pass(); break; case VEK_TEST_FAIL: test_fail(); break; case VEK_TEST_MSG: test_msg_print(ctx); break; default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 2023 Oxide Computer Company */ /* * Test guest writes to the TSC */ #include #include #include #include #include #include #include #include #include "in_guest.h" #include "test_defs.h" int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; bool half_read = false; uint64_t tsc; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); if (kind == VEK_REENTR) { continue; } else if (kind != VEK_UNHANDLED) { test_fail_vmexit(&vexit); } uint32_t val; if (vexit_match_inout(&vexit, false, IOP_TEST_VALUE, 4, &val)) { if (!half_read) { /* low 32-bits of TSC first */ tsc = val; half_read = true; ventry_fulfill_inout(&vexit, &ventry, 0); } else { /* high 32-bits of TSC */ tsc |= ((uint64_t)val << 32); /* * Check that the TSC reading is at least the * high value it was set to by the guest. * * If we wanted to be more precise about it, we * could get the host frequency and calculate * ppm error. * */ if (tsc < TSC_TARGET_WRVAL) { test_fail_msg("TSC %lu < %lu", tsc, TSC_TARGET_WRVAL); } else { test_pass(); } } } else { test_fail_vmexit(&vexit); } } while (true); } # # 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 2023 Oxide Computer Company include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/Makefile.cmd.64 include $(SRC)/test/Makefile.com PROG = entry_exit # C-based payloads need additional utils object CPAYLOADS = entry_exit PAYLOADS = $(PROG) include ../Makefile.in_guest COMMON_OBJS = in_guest.o common.o CLEANFILES = $(COMMON_OBJS) $(PAYLOAD_CLEANFILES) payload_utils.o CLOBBERFILES = $(PROG) ROOTOPTPKG = $(ROOT)/opt/bhyve-tests TESTDIR = $(ROOTOPTPKG)/tests/perf CMDS = $(PROG:%=$(TESTDIR)/%) $(CMDS) : FILEMODE = 0555 CSTD= $(CSTD_GNU99) CPPFLAGS = -I$(COMPAT)/bhyve -I$(CONTRIB)/bhyve \ -I$(COMPAT)/bhyve/amd64 -I$(CONTRIB)/bhyve/amd64 \ $(CPPFLAGS.master) \ -I$(SRC)/uts/intel/io/vmm \ -I$(SRC)/uts/intel \ -I../common ASFLAGS += -D_ASM ASFLAGS64 += -D_ASM $(PROG) : LDLIBS += -lvmmapi all: $(PROG) install: all $(CMDS) clean: -$(RM) $(CLEANFILES) clobber: clean -$(RM) $(CLOBBERFILES) $(CMDS): $(TESTDIR) $(PROG) $(TESTDIR): $(INS.dir) $(TESTDIR)/%: % $(INS.file) # Hammerhead: Cancel GNU Make built-in compile-and-link rule (%: %.c) %: %.c %: %.c pobj_%.o $(COMMON_OBJS) $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) %: %.o $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) $(CPAYLOADS:%=payload_%): payload_utils.o /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "in_guest.h" static uint32_t opt_repeat_count = 1000; static bool opt_summarize = false; /* Names of the test phases running in guest context */ static const char *test_metric_idents[] = { "MSR", "PIO", "MMIO", }; /* Track which test phase the guest is executing */ static uint_t current_test = 0; /* Cache the queried CPU frequency */ static uint64_t cpu_freq = 0; static uint64_t query_cpu_freq(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); struct vdi_time_info_v1 time_info; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &time_info, }; if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed"); } return (time_info.vt_guest_freq); } static double cycles_to_ns(uint64_t cycles) { return ((cycles * 1000000000.0) / cpu_freq); } static void print_result(struct vmctx *ctx, uintptr_t gpa, uint_t test_idx) { if (test_idx >= ARRAY_SIZE(test_metric_idents)) { test_fail_msg("unexpected test iteration"); return; } const uint64_t *data = vm_map_gpa(ctx, gpa, opt_repeat_count * sizeof (uint64_t)); assert(data != NULL); printf("%s", test_metric_idents[test_idx]); if (opt_summarize) { double sum = 0.0; for (uint32_t i = 0; i < opt_repeat_count; i++) { sum += cycles_to_ns(data[i]); } printf(",%0.2f", sum / opt_repeat_count); } else { for (uint32_t i = 0; i < opt_repeat_count; i++) { printf(",%0.2f", cycles_to_ns(data[i])); } } printf("\n"); } static void handle_exit(struct vmctx *ctx, const struct vm_exit *vexit, struct vm_entry *ventry) { uint32_t outval; if (vexit_match_inout(vexit, true, IOP_TEST_PARAM0, 4, NULL)) { ventry_fulfill_inout(vexit, ventry, opt_repeat_count); return; } if (vexit_match_inout(vexit, false, IOP_TEST_VALUE, 4, &outval)) { ventry_fulfill_inout(vexit, ventry, 0); print_result(ctx, (uintptr_t)outval, current_test); /* proceed to next test */ current_test++; return; } test_fail_vmexit(vexit); } static void usage(const char *progname, int status) { char *base = strdup(progname); (void) printf("usage: %s [args]\n" "\t-n \tNumber of repetitions (default: 1000)\n" "\t-s\t\tSummarize (average) results\n" "\t-h\t\tPrint this help\n", basename(base)); exit(status); } static void parse_args(int argc, char *argv[]) { int c; unsigned long num_parsed; while ((c = getopt(argc, argv, ":hsn:")) != -1) { switch (c) { case 'h': usage(argv[0], EXIT_SUCCESS); break; case 's': opt_summarize = true; break; case 'n': errno = 0; num_parsed = strtoul(optarg, NULL, 10); if (num_parsed == 0 && errno != 0) { perror("Invalid repeat count"); usage(argv[0], EXIT_FAILURE); } if (num_parsed <= 0 || num_parsed > UINT32_MAX) { (void) printf( "Repeat count must be between 1 - %lu\n", UINT32_MAX); usage(argv[0], EXIT_FAILURE); } opt_repeat_count = num_parsed; break; case ':': (void) printf("Missing argument for option '%c'\n", optopt); usage(argv[0], EXIT_FAILURE); break; case '?': (void) printf("Unrecognized option '%c'\n", optopt); usage(argv[0], EXIT_FAILURE); break; } } } int main(int argc, char *argv[]) { const char *test_suite_name = basename(argv[0]); struct vmctx *ctx = NULL; struct vcpu *vcpu; int err; parse_args(argc, argv); ctx = test_initialize(test_suite_name); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { test_fail_errno(errno, "Could not open vcpu0"); } err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); if (err != 0) { test_fail_errno(err, "Could not initialize vcpu0"); } cpu_freq = query_cpu_freq(ctx); struct vm_entry ventry = { 0 }; struct vm_exit vexit = { 0 }; do { const enum vm_exit_kind kind = test_run_vcpu(vcpu, &ventry, &vexit); switch (kind) { case VEK_REENTR: break; case VEK_UNHANDLED: handle_exit(ctx, &vexit, &ventry); break; case VEK_TEST_PASS: /* * Skip the normal "PASS" message, since the consumer is * interested in the data itself. */ exit(EXIT_SUCCESS); break; case VEK_TEST_MSG: test_msg_print(ctx); break; case VEK_TEST_FAIL: default: test_fail_vmexit(&vexit); break; } } while (true); } /* * 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 2023 Oxide Computer Company */ #include "payload_common.h" #include "payload_utils.h" /* Arbitrary 2MB limit to keep heap away from stack */ #define HEAP_LIMIT 0x200000 #define MSR_APICBASE 0x01b #define IOP_ICU1 0x20 #define ADDR_IOAPIC_BASE 0xfec00000 typedef struct test_data { uint32_t count; uint64_t *data; } test_data_t; static void zero_data(const test_data_t *td) { for (uint32_t i = 0; i < td->count; i++) { td->data[i] = 0; } } static void output_data(const test_data_t *td, uint64_t tsc_start) { for (uint32_t i = td->count - 1; i > 0; i--) { td->data[i] -= td->data[i - 1]; } td->data[0] -= tsc_start; /* * Output the low 32-bits of the data pointers, since that is adequate * while the test resides wholly in lowmem. */ outl(IOP_TEST_VALUE, (uint32_t)(uintptr_t)td->data); } static uint32_t mmio_read4(volatile uint32_t *ptr) { return (*ptr); } /* * For a relatively cheap exit, rdmsr(APICBASE) should be suitable, since its * emulation is dead simple and LAPIC-related MSR operations are handled within * the tight confines of the SVM/VMX vmrun loop. */ static void do_test_rdmsr(const test_data_t *td) { zero_data(td); const uint64_t tsc_start = rdtsc(); for (uint32_t i = 0; i < td->count; i++) { (void) rdmsr(MSR_APICBASE); td->data[i] = rdtsc(); } output_data(td, tsc_start); } /* * For a moderately priced exit, an IO port read from the ATPIC should suffice. * This will take us out of the SVM/VMX vmrun loop and into the instruction * emulation, but the instruction fetch/decode should already be taken care of * by the hardware, and no further memory (guest) accesses are required. */ static void do_test_inb(const test_data_t *td) { zero_data(td); const uint64_t tsc_start = rdtsc(); for (uint32_t i = 0; i < td->count; i++) { (void) inb(IOP_ICU1); td->data[i] = rdtsc(); } output_data(td, tsc_start); } /* * For a more expensive exit, read from the selector register in the IOAPIC. * The device emulation is handled in-kernel, but the instruction will need to * (potentially) fetched and decoded. */ static void do_test_mmio_cheap(const test_data_t *td) { zero_data(td); volatile uint32_t *ioapic_regsel = (void *)(uintptr_t)ADDR_IOAPIC_BASE; const uint64_t tsc_start = rdtsc(); for (uint32_t i = 0; i < td->count; i++) { (void) mmio_read4(ioapic_regsel); td->data[i] = rdtsc(); } output_data(td, tsc_start); } void start(void) { /* Get the number of repetitions per test */ const uint32_t count = inl(IOP_TEST_PARAM0); if (count * sizeof (uint64_t) > HEAP_LIMIT) { test_msg("excessive test count for memory sz"); test_result_fail(); return; } test_data_t td = { .count = count, .data = (uint64_t *)(uintptr_t)MEM_LOC_HEAP, }; do_test_rdmsr(&td); do_test_inb(&td); do_test_mmio_cheap(&td); test_result_pass(); } # # 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 2024 Oxide Computer Company include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/Makefile.cmd.64 include $(SRC)/test/Makefile.com PROGS = interface_version create_delete link_params SCRIPTS = setup cleanup COMMON_OBJS = in_guest.o common.o viona_suite.o null_payload.o CLEAN_OBJS = $(PROGS:%=%.o) $(COMMON_OBJS) ROOTOPTPKG = $(ROOT)/opt/bhyve-tests TESTDIR = $(ROOTOPTPKG)/tests/viona CMDS = \ $(PROGS:%=$(TESTDIR)/%) \ $(SCRIPTS:%=$(TESTDIR)/%) $(CMDS) : FILEMODE = 0555 CSTD= $(CSTD_GNU99) CPPFLAGS = -I$(COMPAT)/bhyve -I$(CONTRIB)/bhyve \ -I$(COMPAT)/bhyve/amd64 -I$(CONTRIB)/bhyve/amd64 \ $(CPPFLAGS.master) \ -I$(SRC)/uts/intel/io/vmm \ -I$(SRC)/uts/intel \ -I../common $(PROGS) : LDLIBS += -lvmmapi -ldladm link_params : LDLIBS += -lnvpair all: $(PROGS) install: all $(CMDS) clean: -$(RM) $(CLEAN_OBJS) clobber: clean -$(RM) $(PROGS) $(CMDS): $(TESTDIR) $(TESTDIR): $(INS.dir) $(TESTDIR)/%: % $(INS.file) $(TESTDIR)/%: %.ksh $(INS.rename) # Hammerhead: Cancel GNU Make built-in compile-and-link rule (%: %.c) %: %.c %.o: ../common/%.c $(COMPILE.c) -o $@ $^ $(POST_PROCESS) %: %.o $(COMMON_OBJS) $(LINK.c) -o $@ $^ $(LDLIBS) $(POST_PROCESS) #! /usr/bin/ksh # # # 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 2024 Oxide Computer Company # STF_TOOLS="/opt/test-runner/stf" . ${STF_TOOLS}/contrib/include/logapi.shlib TEST_NIC="bhyvetest_viona0" if dladm show-simnet ${TEST_NIC} > /dev/null 2>&1; then log_must dladm delete-simnet ${TEST_NIC} exit ${STF_PASS} else log_pass "simnet link ${TEST_NIC} already absent" fi /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include "common.h" #include "in_guest.h" #include "viona_suite.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = test_initialize_plain(suite_name); if (ctx == NULL) { test_fail_errno(errno, "could not open test VM"); } int vfd = open_viona(); if (vfd < 0) { test_fail_errno(errno, "could not open viona device"); } datalink_id_t dlid; dladm_status_t dls = query_dlid(VIONA_TEST_IFACE_NAME, &dlid); if (dls != DLADM_STATUS_OK) { char errbuf[DLADM_STRSIZE]; test_fail_msg("could not query datalink id for %s: %s", VIONA_TEST_IFACE_NAME, dladm_status2str(dls, errbuf)); } vioc_create_t create_ioc = { .c_linkid = dlid, .c_vmfd = vm_get_device_fd(ctx), }; if (ioctl(vfd, VNA_IOC_CREATE, &create_ioc) != 0) { test_fail_errno(errno, "failed to create link on viona device"); } if (ioctl(vfd, VNA_IOC_DELETE, 0) != 0) { test_fail_errno(errno, "failed to delete link on viona device"); } test_pass(); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include "viona_suite.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); int ctl_fd = open(VIONA_DEV, O_EXCL | O_RDWR); if (ctl_fd < 0) { perror("could not open viona device"); return (EXIT_FAILURE); } int version = ioctl(ctl_fd, VNA_IOC_VERSION, 0); if (version < 0) { perror("VNA_IOC_VERSION ioctl failed"); return (EXIT_FAILURE); } if (version != VIONA_CURRENT_INTERFACE_VERSION) { (void) fprintf(stderr, "kernel version %d != expected %d\n", version, VIONA_CURRENT_INTERFACE_VERSION); return (EXIT_FAILURE); } (void) close(ctl_fd); (void) printf("%s\tPASS\n", suite_name); return (0); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "in_guest.h" #include "viona_suite.h" #define PARAM_BUF_SZ VIONA_MAX_PARAM_NVLIST_SZ const char *expected_params[] = { "tx_copy_data", "tx_header_pad" }; static void print_errors(vioc_set_params_t *vsp) { if (vsp->vsp_error_sz == 0) { return; } nvlist_t *nverr = NULL; if (nvlist_unpack(vsp->vsp_error, vsp->vsp_error_sz, &nverr, 0) != 0) { return; } (void) fprintf(stderr, "vioc_set_params errors:\n"); nvlist_print(stderr, nverr); nvlist_free(nverr); } static void test_set_param_errors(int vfd) { vioc_set_params_t set_param = { .vsp_param_sz = VIONA_MAX_PARAM_NVLIST_SZ + 1, }; if (ioctl(vfd, VNA_IOC_SET_PARAMS, &set_param) == 0) { test_fail_msg("SET_PARAMS should fail for too-big size"); } char bogus_nvlist[256]; arc4random_buf(bogus_nvlist, sizeof (bogus_nvlist)); set_param.vsp_param = bogus_nvlist; set_param.vsp_param_sz = sizeof (bogus_nvlist); if (ioctl(vfd, VNA_IOC_SET_PARAMS, &set_param) == 0) { test_fail_msg("SET_PARAMS should fail invalid nvlist"); } /* * Assemble parameters which should be rejected: * - One of the wrong nvpair data type * - A tx_header_pad outside the valid range * - A wholly unrecognized field */ nvlist_t *nvl = fnvlist_alloc(); fnvlist_add_uint32(nvl, "tx_copy_data", 0); fnvlist_add_uint16(nvl, "tx_header_pad", UINT16_MAX); fnvlist_add_boolean_value(nvl, "widdly_scuds", false); uint8_t errbuf[512]; set_param.vsp_param = fnvlist_pack(nvl, &set_param.vsp_param_sz); set_param.vsp_error = errbuf; set_param.vsp_error_sz = sizeof (errbuf); if (ioctl(vfd, VNA_IOC_SET_PARAMS, &set_param) == 0) { test_fail_msg("SET_PARAMS should fail on invalid params"); } nvlist_free(nvl); free(set_param.vsp_param); nvlist_t *error_nvl = fnvlist_unpack(set_param.vsp_error, set_param.vsp_error_sz); const char *err_params[] = { "tx_copy_data", "tx_header_pad", "widdly_scuds" }; for (uint_t i = 0; i < ARRAY_SIZE(err_params); i++) { const char *name = err_params[i]; if (!nvlist_exists(error_nvl, name)) { print_errors(&set_param); test_fail_msg("missing SET_PARAMS error for field %s\n", name); } } nvlist_free(error_nvl); } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = test_initialize_plain(suite_name); if (ctx == NULL) { test_fail_errno(errno, "could not open test VM"); } int vfd = open_viona(); if (vfd < 0) { test_fail_errno(errno, "could not open viona device"); } /* * Getting default parameters should work before the viona device is * associated with a link and vmm */ void *param_buf = malloc(PARAM_BUF_SZ); if (param_buf == NULL) { test_fail_errno(errno, "could not allocate param buffer"); } vioc_get_params_t get_param = { .vgp_param = param_buf, .vgp_param_sz = PARAM_BUF_SZ, }; if (ioctl(vfd, VNA_IOC_DEFAULT_PARAMS, &get_param) != 0) { test_fail_errno(errno, "ioctl(VNA_IOC_DEFAULT_PARAMS) failed"); } nvlist_t *params = NULL; if (nvlist_unpack(param_buf, get_param.vgp_param_sz, ¶ms, 0) != 0) { test_fail_errno(errno, "nvlist_unpack() failed"); } /* Are all the presented default parameters ones we expect? */ nvpair_t *nvp = NULL; while ((nvp = nvlist_next_nvpair(params, nvp)) != NULL) { bool found = false; const char *pname = nvpair_name(nvp); for (uint_t i = 0; i < ARRAY_SIZE(expected_params); i++) { if (strcmp(pname, expected_params[i]) == 0) { found = true; break; } } if (!found) { test_fail_msg("unexpected parameter %s", pname); } } datalink_id_t dlid; dladm_status_t dls = query_dlid(VIONA_TEST_IFACE_NAME, &dlid); if (dls != DLADM_STATUS_OK) { char errbuf[DLADM_STRSIZE]; test_fail_msg("could not query datalink id for %s: %s", VIONA_TEST_IFACE_NAME, dladm_status2str(dls, errbuf)); } vioc_create_t create_ioc = { .c_linkid = dlid, .c_vmfd = vm_get_device_fd(ctx), }; if (ioctl(vfd, VNA_IOC_CREATE, &create_ioc) != 0) { test_fail_errno(errno, "failed to create link on viona device"); } /* * Based on the parameters we got from the defaults, build a new set of * parameters to set on the link which are slighly different. */ nvlist_t *new_params = fnvlist_alloc(); fnvlist_add_boolean_value(new_params, "tx_copy_data", !fnvlist_lookup_boolean_value(params, "tx_copy_data")); fnvlist_add_uint16(new_params, "tx_header_pad", fnvlist_lookup_uint16(params, "tx_header_pad") + 32); uint8_t errbuf[256]; vioc_set_params_t set_param = { .vsp_error = errbuf, .vsp_error_sz = sizeof (errbuf) }; if (nvlist_pack(new_params, (char **)&set_param.vsp_param, &set_param.vsp_param_sz, NV_ENCODE_NATIVE, 0) != 0) { test_fail_errno(errno, "nvlist_pack() failed"); } nvlist_free(params); nvlist_free(new_params); if (ioctl(vfd, VNA_IOC_SET_PARAMS, &set_param) != 0) { print_errors(&set_param); test_fail_errno(errno, "ioctl(VNA_IOC_SET_PARAMS) failed"); } free(set_param.vsp_param); test_set_param_errors(vfd); test_pass(); return (EXIT_SUCCESS); } #! /usr/bin/ksh # # # 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 2024 Oxide Computer Company # STF_TOOLS="/opt/test-runner/stf" . ${STF_TOOLS}/contrib/include/logapi.shlib TEST_NIC="bhyvetest_viona0" if dladm show-simnet ${TEST_NIC} > /dev/null 2>&1; then log_pass "simnet link ${TEST_NIC} already exists" else log_must dladm create-simnet ${TEST_NIC} exit ${STF_PASS} fi /* * 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 2024 Oxide Computer Company */ #include #include #include #include "viona_suite.h" int open_viona(void) { return (open(VIONA_DEV, O_RDWR)); } /* Convenience helper to get datalink_id_t from an interface name */ dladm_status_t query_dlid(const char *name, datalink_id_t *dlid) { dladm_handle_t hdl; dladm_status_t err; err = dladm_open(&hdl); if (err != DLADM_STATUS_OK) { dladm_close(hdl); return (err); } err = dladm_name2info(hdl, name, dlid, NULL, NULL, NULL); dladm_close(hdl); return (err); } /* * 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 2024 Oxide Computer Company */ #ifndef _VIONA_SUITE_H #define _VIONA_SUITE_H #include /* * Shared definitions for tests included in viona suite of tests. */ /* * Name of simnet link create viona instances upon. * * This is created and destroyed by the setup.ksh/cleanup.ksh scripts, and the * name must be kept in sync with them. */ #define VIONA_TEST_IFACE_NAME "bhyvetest_viona0" #define VIONA_DEV "/dev/viona" int open_viona(void); dladm_status_t query_dlid(const char *, datalink_id_t *); #endif /* _VIONA_SUITE_H */ # # 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 2023 Oxide Computer Company include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/Makefile.cmd.64 include $(SRC)/test/Makefile.com PROG = mem_partial \ mem_seg_map \ mem_high \ mem_devmem \ fpu_getset \ interface_version \ check_iommu \ auto_destruct \ legacy_destruct \ self_destruct \ drv_hold \ cpuid_ioctl \ default_capabs \ datarw_constraints \ datarw_msrs \ datarw_vcpu \ pause_resume \ import_vlapic \ time_data \ maxcpu \ npt_ops COMMON_OBJS = common.o CLEAN_OBJS = $(PROG:%=%.o) ROOTOPTPKG = $(ROOT)/opt/bhyve-tests TESTDIR = $(ROOTOPTPKG)/tests/vmm CMDS = $(PROG:%=$(TESTDIR)/%) $(CMDS) : FILEMODE = 0555 CSTD= $(CSTD_GNU99) CPPFLAGS = -I$(COMPAT)/bhyve -I$(CONTRIB)/bhyve \ -I$(COMPAT)/bhyve/amd64 -I$(CONTRIB)/bhyve/amd64 \ $(CPPFLAGS.master) \ -I$(SRC)/uts/intel/io/vmm \ -I$(SRC)/uts/intel \ -I../common $(PROG) : LDLIBS += -lvmmapi all: $(PROG) install: all $(CMDS) clean: -$(RM) $(CLEAN_OBJS) $(COMMON_OBJS) clobber: clean -$(RM) $(PROG) $(PROG): $(COMMON_OBJS) $(CMDS): $(TESTDIR) $(PROG) $(TESTDIR): $(INS.dir) $(TESTDIR)/%: % $(INS.file) # Hammerhead: Cancel GNU Make built-in compile-and-link rule (%: %.c) # Forces use of our two-step compile then link-with-COMMON_OBJS approach %: %.c %.o: ../common/%.c $(COMPILE.c) -o $@ $^ $(POST_PROCESS) %: %.o $(LINK.c) -o $@ $< $(COMMON_OBJS) $(LDLIBS) $(POST_PROCESS) /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could open test VM"); } /* * It would be odd if we had the freshly created VM instance, but it did * not appear to exist. */ assert(check_instance_usable(suite_name)); /* Make sure that auto-destruct is off */ if (ioctl(vm_get_device_fd(ctx), VM_SET_AUTODESTRUCT, 0) != 0) { errx(EXIT_FAILURE, "could not disable auto-destruct"); } vm_close(ctx); if (!check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance missing after close"); } ctx = NULL; if (destroy_instance(suite_name) != 0) { errx(EXIT_FAILURE, "could not clean up instance"); } /* Now repeat that process, but enable auto-destruct */ ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could open test VM"); } if (ioctl(vm_get_device_fd(ctx), VM_SET_AUTODESTRUCT, 1) != 0) { errx(EXIT_FAILURE, "could not enable auto-destruct"); } vm_close(ctx); ctx = NULL; /* At this point, the instance should be gone */ if (check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance did not auto-destruct as expected"); } /* * Repeat the test again, but establish a vmm_drv hold first. * The instance should auto-destruct when the hold is released. */ ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could open test VM"); } if (ioctl(vm_get_device_fd(ctx), VM_SET_AUTODESTRUCT, 1) != 0) { errx(EXIT_FAILURE, "could not enable auto-destruct"); } int vdtfd = open_drv_test(); if (vdtfd < 0) { errx(EXIT_FAILURE, "could open drv_test device"); } if (ioctl(vdtfd, VDT_IOC_HOLD, vm_get_device_fd(ctx)) != 0) { errx(EXIT_FAILURE, "could not hold VM from vmm_drv device"); } vm_close(ctx); ctx = NULL; /* * With the vmm_drv hold remaining on the instance, we expect it to * exist, but not be usable (due to in-progress destroy). */ if (!check_instance_exists(suite_name)) { err(EXIT_FAILURE, "instance completed auto-destruct despite " "existing vmm_drv hold"); } if (check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance still usable despite close() after " "auto-destroy configured"); } if (ioctl(vdtfd, VDT_IOC_RELE, 0) != 0) { errx(EXIT_FAILURE, "could not release VM from vmm_drv device"); } if (check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance did not complete destruction " "after vmm_drv release"); } (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); int ctl_fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR); if (ctl_fd < 0) { perror("could not open vmmctl device"); return (EXIT_FAILURE); } int res = ioctl(ctl_fd, VMM_CHECK_IOMMU, 0); if (res < 0) { perror("VMM_CHECK_IOMMU ioctl failed"); return (EXIT_FAILURE); } (void) close(ctl_fd); (void) printf("%s\tPASS\n", suite_name); return (0); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could open test VM"); return (EXIT_FAILURE); } int vmfd = vm_get_device_fd(ctx); struct vm_vcpu_cpuid_config cfg = { 0 }; struct vcpu_cpuid_entry *entries = NULL; if (ioctl(vmfd, VM_GET_CPUID, &cfg) != 0) { err(EXIT_FAILURE, "ioctl(VM_GET_CPUID) failed"); } if (cfg.vvcc_flags != VCC_FLAG_LEGACY_HANDLING) { errx(EXIT_FAILURE, "cpuid handling did not default to legacy-style"); } cfg.vvcc_flags = ~VCC_FLAG_LEGACY_HANDLING; if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { errx(EXIT_FAILURE, "ioctl(VM_SET_CPUID) did not reject invalid flags"); } entries = calloc(VMM_MAX_CPUID_ENTRIES + 1, sizeof (struct vcpu_cpuid_entry)); if (entries == NULL) { errx(EXIT_FAILURE, "could not allocate cpuid entries"); } cfg.vvcc_flags = VCC_FLAG_LEGACY_HANDLING; cfg.vvcc_nent = 1; cfg.vvcc_entries = entries; if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { errx(EXIT_FAILURE, "ioctl(VM_SET_CPUID) did not reject entries when " "legacy-style handling was requested"); } cfg.vvcc_flags = 0; cfg.vvcc_nent = VMM_MAX_CPUID_ENTRIES + 1; if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { errx(EXIT_FAILURE, "ioctl(VM_SET_CPUID) did not reject excessive entry count"); } cfg.vvcc_nent = 1; entries[0].vce_flags = ~0; if (ioctl(vmfd, VM_SET_CPUID, &cfg) == 0) { errx(EXIT_FAILURE, "ioctl(VM_SET_CPUID) did not invalid entry flags"); } entries[0].vce_flags = 0; /* Actually set some entries to use for GET_CPUID testing */ const uint_t valid_entries = (VMM_MAX_CPUID_ENTRIES / 2); for (uint_t i = 0; i < valid_entries; i++) { entries[i].vce_function = i; } cfg.vvcc_nent = valid_entries; if (ioctl(vmfd, VM_SET_CPUID, &cfg) != 0) { err(EXIT_FAILURE, "ioctl(VM_SET_CPUID) unable to set valid entries"); } /* Try with no entries buffer */ bzero(&cfg, sizeof (cfg)); if (ioctl(vmfd, VM_GET_CPUID, &cfg) == 0 || errno != E2BIG) { errx(EXIT_FAILURE, "ioctl(VM_GET_CPUID) did not fail absent buffer"); } if (cfg.vvcc_nent != valid_entries) { errx(EXIT_FAILURE, "ioctl(VM_GET_CPUID) did not emit entry count " "(expected %u, got %u)", valid_entries, cfg.vvcc_nent); } /* Try with too-small entries buffer */ cfg.vvcc_nent = 1; cfg.vvcc_entries = entries; bzero(entries, valid_entries * sizeof (struct vcpu_cpuid_entry)); if (ioctl(vmfd, VM_GET_CPUID, &cfg) == 0 || errno != E2BIG) { errx(EXIT_FAILURE, "ioctl(VM_GET_CPUID) did not fail too-small buffer"); } if (cfg.vvcc_nent != valid_entries) { errx(EXIT_FAILURE, "ioctl(VM_GET_CPUID) did not emit entry count " "(expected %u, got %u)", valid_entries, cfg.vvcc_nent); } /* Try with adequate entries buffer */ cfg.vvcc_nent = valid_entries; if (ioctl(vmfd, VM_GET_CPUID, &cfg) != 0) { err(EXIT_FAILURE, "ioctl(VM_GET_CPUID) failed"); } if (cfg.vvcc_nent != valid_entries) { errx(EXIT_FAILURE, "ioctl(VM_GET_CPUID) did not emit entry count " "(expected %u, got %u)", valid_entries, cfg.vvcc_nent); } for (uint_t i = 0; i < valid_entries; i++) { if (entries[i].vce_function != i) { errx(EXIT_FAILURE, "unexpected entry contents"); } } /* * The legacy handling is simply using the host values with certain * modifications (masking, etc) applied. The base leaf should be * exactly the same as we read from the host. * * Since a bhyve compat header has an inline-asm cpuid wrapper, use that * for now for querying the host */ struct vm_legacy_cpuid legacy = { 0 }; if (ioctl(vmfd, VM_LEGACY_CPUID, &legacy) != 0) { err(EXIT_FAILURE, "ioctl(VM_CPUID_LEGACY) failed"); } uint32_t basic_cpuid[4]; cpuid_count(0, 0, basic_cpuid); if (basic_cpuid[0] != legacy.vlc_eax || basic_cpuid[1] != legacy.vlc_ebx || basic_cpuid[2] != legacy.vlc_ecx || basic_cpuid[3] != legacy.vlc_edx) { errx(EXIT_FAILURE, "legacy cpuid mismatch"); } vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" static void should_eq_u32(const char *field_name, uint32_t a, uint32_t b) { if (a != b) { errx(EXIT_FAILURE, "unexpected %s %u != %u", field_name, a, b); } } static void test_size_boundaries(int vmfd) { uint8_t buf[sizeof (struct vdi_atpic_v1) + sizeof (int)]; struct vm_data_xfer vdx = { .vdx_class = VDC_ATPIC, .vdx_version = 1, .vdx_len = sizeof (struct vdi_atpic_v1), .vdx_data = buf, }; /* Attempt a valid-sized read first */ if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "valid VM_DATA_READ failed"); } should_eq_u32("vdx_result_len", vdx.vdx_result_len, sizeof (struct vdi_atpic_v1)); /* And check that we can write it back */ vdx.vdx_result_len = 0; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { err(EXIT_FAILURE, "valid VM_DATA_WRITE failed"); } /* ... then too-small ... */ vdx.vdx_len = sizeof (struct vdi_atpic_v1) - sizeof (int); vdx.vdx_result_len = 0; if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { errx(EXIT_FAILURE, "invalid VM_DATA_READ should have failed"); } int error = errno; if (error != ENOSPC) { errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error); } /* the "correct" vdx_result_len should still be communicated out */ should_eq_u32("vdx_result_len", vdx.vdx_result_len, sizeof (struct vdi_atpic_v1)); /* Repeat with too-small write */ vdx.vdx_result_len = 0; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { errx(EXIT_FAILURE, "invalid VM_DATA_WRITE should have failed"); } error = errno; if (error != ENOSPC) { errx(EXIT_FAILURE, "expected ENOSPC errno, got %d", error); } should_eq_u32("vdx_result_len", vdx.vdx_result_len, sizeof (struct vdi_atpic_v1)); /* * ... and too-big to round it out. * * This should pass, but still set vdx_result_len to the actual length */ vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int); vdx.vdx_result_len = 0; if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "too-large (but valid) VM_DATA_READ failed"); } should_eq_u32("vdx_result_len", vdx.vdx_result_len, sizeof (struct vdi_atpic_v1)); /* ... and repeated as a write */ vdx.vdx_len = sizeof (struct vdi_atpic_v1) + sizeof (int); vdx.vdx_result_len = 0; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { err(EXIT_FAILURE, "too-large (but valid) VM_DATA_WRITE failed"); } should_eq_u32("vdx_result_len", vdx.vdx_result_len, sizeof (struct vdi_atpic_v1)); } struct class_test_case { uint16_t ctc_class; uint16_t ctc_version; }; static void test_vm_classes(int vmfd) { const struct class_test_case cases[] = { { VDC_VERSION, 1 }, { VDC_VMM_ARCH, 1 }, { VDC_IOAPIC, 1 }, { VDC_ATPIT, 1 }, { VDC_ATPIC, 1 }, { VDC_HPET, 1 }, { VDC_PM_TIMER, 1 }, { VDC_RTC, 2 }, { VDC_VMM_TIME, 1 }, }; /* A page should be large enough for all classes (for now) */ const size_t bufsz = PAGESIZE; uint8_t *buf = malloc(bufsz); for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) { struct vm_data_xfer vdx = { .vdx_class = cases[i].ctc_class, .vdx_version = cases[i].ctc_version, .vdx_len = bufsz, .vdx_data = buf, .vdx_vcpuid = -1, }; /* First do a read */ if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "VM_DATA_READ failed class:%u version:%u", vdx.vdx_class, vdx.vdx_version); } if (vdx.vdx_class == VDC_VERSION || vdx.vdx_class == VDC_VMM_ARCH) { /* * Skip classes which contain some (or all) bits which * are read-only. */ continue; } /* Write the same data back */ vdx.vdx_len = vdx.vdx_result_len; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { err(EXIT_FAILURE, "VM_DATA_WRITE failed class:%u version:%u", vdx.vdx_class, vdx.vdx_version); } } free(buf); } static void test_vcpu_classes(int vmfd) { const struct class_test_case cases[] = { { VDC_MSR, 1 }, { VDC_LAPIC, 1 }, { VDC_VMM_ARCH, 1 }, /* * Although these classes are per-vCPU, they have not yet been * implemented in the vmm-data system, so are ignored for now: * * - VDC_REGISTER * - VDC_FPU * - VDC_LAPIC */ }; /* A page should be large enough for all classes (for now) */ const size_t bufsz = PAGESIZE; uint8_t *buf = malloc(bufsz); for (uint_t i = 0; i < ARRAY_SIZE(cases); i++) { struct vm_data_xfer vdx = { .vdx_class = cases[i].ctc_class, .vdx_version = cases[i].ctc_version, .vdx_len = bufsz, .vdx_data = buf, .vdx_vcpuid = 0, }; /* First do a read */ if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "VM_DATA_READ failed class:%u version:%u", vdx.vdx_class, vdx.vdx_version); } if (vdx.vdx_class == VDC_VMM_ARCH) { /* * There are some read-only fields in VMM_ARCH which we * do not want to attempt to write back. */ continue; } /* Write the same data back */ vdx.vdx_len = vdx.vdx_result_len; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { err(EXIT_FAILURE, "VM_DATA_WRITE failed class:%u version:%u", vdx.vdx_class, vdx.vdx_version); } } free(buf); } static void test_bogus_class(int vmfd) { const size_t bufsz = PAGESIZE; uint8_t *buf = malloc(bufsz); struct vm_data_xfer vdx = { .vdx_class = 10000, .vdx_version = 1, .vdx_len = bufsz, .vdx_data = buf, }; /* Try to read with an absurd data class */ if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { errx(EXIT_FAILURE, "VM_DATA_READ should fail for absurd vdx_class"); } /* Same for data version */ vdx.vdx_class = VDC_VERSION; vdx.vdx_version = 10000; if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { errx(EXIT_FAILURE, "VM_DATA_READ should fail for absurd vdx_version"); } free(buf); } static void test_vcpuid_combos(int vmfd) { const size_t bufsz = PAGESIZE; uint8_t *buf = malloc(bufsz); struct vm_data_xfer vdx = { .vdx_class = VDC_LAPIC, .vdx_version = 1, .vdx_len = bufsz, .vdx_data = buf, }; /* Try with -1 sentinel, too-negative, and too-positive values */ const int bad_per_vcpu[] = { -1, -5, 1000 }; for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) { vdx.vdx_vcpuid = bad_per_vcpu[i]; if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { errx(EXIT_FAILURE, "VM_DATA_READ should fail for bad vcpuid %d", vdx.vdx_vcpuid); } } /* * Valid vcpuid should be fine still. Reading valid data into the * buffer will be useful to subsequently test writes. */ vdx.vdx_vcpuid = 0; if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "failed VM_DATA_READ with valid vcpuid"); } /* Repeat the same checks for writes */ for (uint_t i = 0; i < ARRAY_SIZE(bad_per_vcpu); i++) { vdx.vdx_vcpuid = bad_per_vcpu[i]; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { errx(EXIT_FAILURE, "VM_DATA_WRITE should fail for bad vcpuid %d", vdx.vdx_vcpuid); } } vdx.vdx_vcpuid = 0; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { err(EXIT_FAILURE, "failed VM_DATA_WRITE with valid vcpuid"); } vdx.vdx_class = VDC_VERSION; vdx.vdx_version = 1; /* * VM-wide classes should work fine with the -1 sentinel. For now, * passing an otherwise valid vcpuid will still work, but that id is * ignored. */ const int good_vm_wide[] = { -1, 0, 1 }; for (uint_t i = 0; i < ARRAY_SIZE(good_vm_wide); i++) { vdx.vdx_vcpuid = good_vm_wide[i]; if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "failed VM-wide VM_DATA_READ with vcpuid %d", vdx.vdx_vcpuid); } } /* Bogus values should still fail */ const int bad_vm_wide[] = { -5, 1000 }; for (uint_t i = 0; i < ARRAY_SIZE(bad_vm_wide); i++) { vdx.vdx_vcpuid = bad_vm_wide[i]; if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { errx(EXIT_FAILURE, "VM_DATA_READ should fail for bad vcpuid %d", vdx.vdx_vcpuid); } } free(buf); } static void test_vcpuid_time(int vmfd) { struct vdi_time_info_v1 data; struct vm_data_xfer vdx = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (data), .vdx_data = &data, }; /* This should work with the system-wide vcpuid */ vdx.vdx_vcpuid = -1; if (ioctl(vmfd, VM_DATA_READ, &vdx) != 0) { err(EXIT_FAILURE, "VM_DATA_READ failed for valid vcpuid"); } /* But fail for other vcpuids */ vdx.vdx_vcpuid = 0; if (ioctl(vmfd, VM_DATA_READ, &vdx) == 0) { err(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d", vdx.vdx_vcpuid); } /* * Perform same check for writes * * Normally this would require care to handle hosts which lack frequency * scaling functionality, but since we are writing back the same data, * the guest frequency should match that of the host, requiring no real * scaling be done for the instance. */ vdx.vdx_vcpuid = -1; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) != 0) { err(EXIT_FAILURE, "VM_DATA_WRITE failed for valid vcpuid"); } vdx.vdx_vcpuid = 0; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { errx(EXIT_FAILURE, "VM_DATA_READ should fail for vcpuid %d", vdx.vdx_vcpuid); } } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } /* * Check that vmm_data import/export facility is robust in the face of * potentially invalid inputs */ const int vmfd = vm_get_device_fd(ctx); /* Test varies edge cases around data transfer sizes */ test_size_boundaries(vmfd); /* Check that known VM-wide data classes can be accessed */ test_vm_classes(vmfd); /* Check that known per-vCPU data classes can be accessed */ test_vcpu_classes(vmfd); /* Try some bogus class/version combos */ test_bogus_class(vmfd); /* Try some weird vdx_vcpuid cases */ test_vcpuid_combos(vmfd); /* VMM_TIME is picky about vcpuid */ test_vcpuid_time(vmfd); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" static void do_data_write(int vmfd, struct vm_data_xfer *vdx) { if (ioctl(vmfd, VM_DATA_WRITE, vdx) != 0) { err(EXIT_FAILURE, "valid vmm_data_write failed"); } if (vdx->vdx_result_len != vdx->vdx_len) { errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u", vdx->vdx_len, vdx->vdx_result_len); } } static void do_data_read(int vmfd, struct vm_data_xfer *vdx) { if (ioctl(vmfd, VM_DATA_READ, vdx) != 0) { err(EXIT_FAILURE, "valid vmm_data_read failed"); } if (vdx->vdx_result_len != vdx->vdx_len) { errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u", vdx->vdx_len, vdx->vdx_result_len); } } static uint32_t query_data_size(int vmfd, struct vm_data_xfer *vdx) { vdx->vdx_len = 0; vdx->vdx_data = NULL; vdx->vdx_flags = 0; if (ioctl(vmfd, VM_DATA_READ, vdx) == 0) { errx(EXIT_FAILURE, "expected VM_DATA_READ to fail for size query"); } if (errno != ENOSPC) { err(EXIT_FAILURE, "expected ENOSPC error for VM_DATA_READ size query"); } return (vdx->vdx_result_len); } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "could not open vcpu0"); } if (vm_activate_cpu(vcpu) != 0) { err(EXIT_FAILURE, "could not activate vcpu0"); } const int vmfd = vm_get_device_fd(ctx); /* Pause the instance before attempting to manipulate vcpu data */ if (ioctl(vmfd, VM_PAUSE, 0) != 0) { err(EXIT_FAILURE, "VM_PAUSE failed"); } struct vm_data_xfer vdx = { .vdx_class = VDC_MSR, .vdx_version = 1, .vdx_vcpuid = 0, }; const uint32_t msr_sz = query_data_size(vmfd, &vdx); const uint32_t msr_count = msr_sz / sizeof (struct vdi_field_entry_v1); struct vdi_field_entry_v1 *entries = calloc(msr_count, sizeof (struct vdi_field_entry_v1)); if (entries == NULL) { err(EXIT_FAILURE, "could not allocate space for MSR data"); } /* Attempt to read all the (default) entries */ vdx.vdx_data = entries; vdx.vdx_len = msr_sz; do_data_read(vmfd, &vdx); /* Spot check a few MSRs which we expect to be present */ struct expected_msr { const char *name; uint32_t msr; bool present; } spot_check[] = { { .msr = MSR_AMD_EFER, .name = "EFER" }, { .msr = REG_TSC, .name = "TSC" }, { .msr = MSR_AMD_CSTAR, .name = "CSTAR" }, { .msr = MSR_AMD_KGSBASE, .name = "KGSBASE" }, }; for (uint_t i = 0; i < msr_count; i++) { for (uint_t j = 0; j < ARRAY_SIZE(spot_check); j++) { if (spot_check[j].msr == entries[i].vfe_ident) { spot_check[j].present = true; } } } for (uint_t j = 0; j < ARRAY_SIZE(spot_check); j++) { if (!spot_check[j].present) { errx(EXIT_FAILURE, "did not find %s(%x) MSR in VM_DATA_READ results", spot_check[j].name, spot_check[j].msr); } } /* Attempt to write those same values back to the instance */ do_data_write(vmfd, &vdx); free(entries); entries = NULL; /* Do a targeted read of a few values */ struct vdi_field_entry_v1 small_list[] = { { .vfe_ident = REG_TSC }, { .vfe_ident = MSR_INTC_SEP_EIP }, { .vfe_ident = REG_PAT }, }; vdx.vdx_data = small_list; vdx.vdx_len = sizeof (small_list); vdx.vdx_flags = VDX_FLAG_READ_COPYIN; do_data_read(vmfd, &vdx); /* * Test access to DEBUGCTL and LBR-related MSRs on AMD. * * Because support for these varies between CPUs, they are (currently) * not included in the default set of MSRs emitted by a blanket read of * MSRs via the vmm-data interface. */ if (cpu_vendor_amd()) { struct vdi_field_entry_v1 dbg_entries[] = { { .vfe_ident = MSR_DEBUGCTL }, { .vfe_ident = MSR_LBR_FROM }, { .vfe_ident = MSR_LBR_TO }, { .vfe_ident = MSR_LEX_FROM }, { .vfe_ident = MSR_LEX_TO }, }; vdx.vdx_data = &dbg_entries; vdx.vdx_len = sizeof (dbg_entries); vdx.vdx_flags = VDX_FLAG_READ_COPYIN; do_data_read(vmfd, &vdx); vdx.vdx_flags = 0; do_data_write(vmfd, &vdx); } vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" static void should_eq_u64(const char *field_name, uint64_t a, uint64_t b) { if (a != b) { errx(EXIT_FAILURE, "unexpected %s %" PRIu64 " != %" PRIu64, field_name, a, b); } } static void check_inval_field(int vmfd, uint32_t ident, uint64_t val) { struct vdi_field_entry_v1 field = { .vfe_ident = ident, .vfe_value = val, }; struct vm_data_xfer vdx = { .vdx_class = VDC_VMM_ARCH, .vdx_version = 1, .vdx_len = sizeof (field), .vdx_data = &field, }; if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { err(EXIT_FAILURE, "vmm_data_write should have failed"); } int err = errno; if (err != EINVAL) { errx(EXIT_FAILURE, "expected EINVAL errno, got %d", err); } } static void do_data_write(int vmfd, struct vm_data_xfer *vdx) { if (ioctl(vmfd, VM_DATA_WRITE, vdx) != 0) { err(EXIT_FAILURE, "valid vmm_data_write failed"); } if (vdx->vdx_result_len != vdx->vdx_len) { errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u", vdx->vdx_len, vdx->vdx_result_len); } } static void do_data_read(int vmfd, struct vm_data_xfer *vdx) { if (ioctl(vmfd, VM_DATA_READ, vdx) != 0) { err(EXIT_FAILURE, "valid vmm_data_read failed"); } if (vdx->vdx_result_len != vdx->vdx_len) { errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u", vdx->vdx_len, vdx->vdx_result_len); } } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "Could not open vcpu0"); } if (vm_activate_cpu(vcpu) != 0) { err(EXIT_FAILURE, "could not activate vcpu0"); } const int vmfd = vm_get_device_fd(ctx); /* Pause the instance before attempting to manipulate vcpu data */ if (ioctl(vmfd, VM_PAUSE, 0) != 0) { err(EXIT_FAILURE, "VM_PAUSE failed"); } struct vdi_field_entry_v1 fields[4] = { { .vfe_ident = VAI_PEND_NMI }, { .vfe_ident = VAI_PEND_EXTINT }, { .vfe_ident = VAI_PEND_EXCP }, { .vfe_ident = VAI_PEND_INTINFO }, }; struct vm_data_xfer vdx = { .vdx_class = VDC_VMM_ARCH, .vdx_version = 1, .vdx_flags = VDX_FLAG_READ_COPYIN, .vdx_len = sizeof (fields), .vdx_data = &fields, }; /* Fetch arch state first */ do_data_read(vmfd, &vdx); /* All of these should be zeroed on a fresh vcpu */ should_eq_u64("VAI_PEND_NMI", fields[0].vfe_value, 0); should_eq_u64("VAI_PEND_EXTINT", fields[1].vfe_value, 0); should_eq_u64("VAI_PEND_EXCP", fields[2].vfe_value, 0); should_eq_u64("VAI_PEND_INTINFO", fields[3].vfe_value, 0); /* Light up those fields */ fields[0].vfe_value = 1; fields[1].vfe_value = 1; fields[2].vfe_value = VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP; fields[3].vfe_value = VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80; do_data_write(vmfd, &vdx); /* * Flip the order (just for funsies) and re-query to check that we still * get the expected state. */ fields[0].vfe_ident = VAI_PEND_INTINFO; fields[1].vfe_ident = VAI_PEND_EXCP; fields[2].vfe_ident = VAI_PEND_EXTINT; fields[3].vfe_ident = VAI_PEND_NMI; do_data_read(vmfd, &vdx); should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80); should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP); should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 1); should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 1); /* NMI-typed exception with the wrong vector */ check_inval_field(vmfd, VAI_PEND_INTINFO, VM_INTINFO_VALID | VM_INTINFO_NMI | 0xd); /* Hardware exception with a bad vector (>= 32) */ check_inval_field(vmfd, VAI_PEND_INTINFO, VM_INTINFO_VALID | VM_INTINFO_HWEXCP | 0x40); /* Non-HW event injected into HW exception field */ check_inval_field(vmfd, VAI_PEND_EXCP, VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0xd); /* Zero out the values again */ fields[0].vfe_value = 0; fields[1].vfe_value = 0; fields[2].vfe_value = 0; fields[3].vfe_value = 0; do_data_write(vmfd, &vdx); /* And confirm that it took */ do_data_read(vmfd, &vdx); should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 0); should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 0); should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 0); should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 0); vm_vcpu_close(vcpu); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" static void check_caps(struct vcpu *vcpu) { struct capcheck { enum vm_cap_type cap; bool enabled; } checks[] = { { .cap = VM_CAP_HALT_EXIT, .enabled = true, }, { .cap = VM_CAP_PAUSE_EXIT, .enabled = false, } }; for (uint_t i = 0; i < ARRAY_SIZE(checks); i++) { const char *capname = vm_capability_type2name(checks[i].cap); int val; if (vm_get_capability(vcpu, checks[i].cap, &val) != 0) { err(EXIT_FAILURE, "could not query %s", capname); } const bool actual = (val != 0); if (actual != checks[i].enabled) { errx(EXIT_FAILURE, "cap %s unexpected state %d != %d", capname, actual, checks[i].enabled); } } } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "Could not open vcpu0"); } /* Check the capabs on a freshly created instance */ check_caps(vcpu); /* Force the instance through a reinit before checking them again */ if (vm_reinit(ctx, 0) != 0) { err(EXIT_FAILURE, "vm_reinit failed"); } check_caps(vcpu); vm_vcpu_close(vcpu); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could open test VM"); return (EXIT_FAILURE); } int vdtfd = open_drv_test(); if (vdtfd < 0) { perror("could open drv_test device"); vm_destroy(ctx); return (EXIT_FAILURE); } int err; err = ioctl(vdtfd, VDT_IOC_HOLD, vm_get_device_fd(ctx)); if (err != 0) { perror("could not establish drv hold on VM"); vm_destroy(ctx); return (EXIT_FAILURE); } err = ioctl(vdtfd, VDT_IOC_RELE, 0); if (err != 0) { perror("could not release drv hold on VM"); vm_destroy(ctx); return (EXIT_FAILURE); } vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" /* Minimal xsave state area (sans any AVX storage) */ struct xsave_min { struct fxsave_state legacy; struct xsave_header header; }; CTASSERT(sizeof (struct xsave_min) == MIN_XSAVE_SIZE); struct avx_state { /* 16 x 128-bit: high portions of the ymm registers */ uint64_t ymm[32]; }; static bool get_fpu(int fd, struct vm_fpu_state *req) { int res = ioctl(fd, VM_GET_FPU, req); if (res != 0) { perror("could not read FPU for vCPU"); return (false); } return (true); } static bool set_fpu(int fd, struct vm_fpu_state *req) { int res = ioctl(fd, VM_SET_FPU, req); if (res != 0) { perror("could not write FPU for vCPU"); return (false); } return (true); } static bool check_sse(int fd, const struct vm_fpu_desc *desc, void *fpu_area, size_t fpu_size) { /* Make sure the x87/MMX/SSE state is described as present */ bool found_fp = false, found_sse = false; for (uint_t i = 0; i < desc->vfd_num_entries; i++) { const struct vm_fpu_desc_entry *ent = &desc->vfd_entry_data[i]; switch (ent->vfde_feature) { case XFEATURE_LEGACY_FP: found_fp = true; if (ent->vfde_off != 0 || ent->vfde_size != sizeof (struct fxsave_state)) { (void) fprintf(stderr, "unexpected entity for %x: " "size=%x off=%x\n", ent->vfde_feature, ent->vfde_size, ent->vfde_off); return (false); } break; case XFEATURE_SSE: found_sse = true; if (ent->vfde_off != 0 || ent->vfde_size != sizeof (struct fxsave_state)) { (void) fprintf(stderr, "unexpected entity for %x: " "size=%x off=%x\n", ent->vfde_feature, ent->vfde_size, ent->vfde_off); return (false); } break; } } if (!found_fp || !found_sse) { (void) fprintf(stderr, "did not find x87 and SSE area " "descriptors as expected in initial FPU\n"); return (false); } struct vm_fpu_state req = { .vcpuid = 0, .buf = fpu_area, .len = fpu_size, }; if (!get_fpu(fd, &req)) { return (false); } struct xsave_min *xs = fpu_area; /* * Executing this test on a freshly-created instance, we expect the FPU * to only have the legacy and SSE features present in its active state. */ if (xs->header.xsh_xstate_bv != (XFEATURE_LEGACY_FP | XFEATURE_SSE)) { (void) fprintf(stderr, "bad xstate_bv %lx, expected %lx", xs->header.xsh_xstate_bv, (XFEATURE_LEGACY_FP | XFEATURE_SSE)); return (false); } /* load some SSE values to check for a get/set cycle */ uint64_t *xmm = (void *)&xs->legacy.fx_xmm[0]; xmm[0] = UINT64_MAX; xmm[2] = 1; if (!set_fpu(fd, &req)) { return (false); } /* check that those values made it in/out of the guest FPU */ bzero(fpu_area, fpu_size); if (!get_fpu(fd, &req)) { return (false); } if (xmm[0] != UINT64_MAX || xmm[2] != 1) { (void) fprintf(stderr, "SSE test registers not saved\n"); return (false); } /* Make sure that a bogus MXCSR value is rejected */ xs->legacy.fx_mxcsr = UINT32_MAX; int res = ioctl(fd, VM_SET_FPU, &req); if (res == 0) { (void) fprintf(stderr, "write of invalid MXCSR erroneously allowed\n"); return (false); } return (true); } static bool check_avx(int fd, const struct vm_fpu_desc *desc, void *fpu_area, size_t fpu_size) { bool found_avx = false; size_t avx_size, avx_off; for (uint_t i = 0; i < desc->vfd_num_entries; i++) { const struct vm_fpu_desc_entry *ent = &desc->vfd_entry_data[i]; if (ent->vfde_feature == XFEATURE_AVX) { found_avx = true; avx_size = ent->vfde_size; avx_off = ent->vfde_off; break; } } if (!found_avx) { (void) printf("AVX capability not found on host CPU, " "skipping related tests\n"); return (true); } if (avx_size != sizeof (struct avx_state)) { (void) fprintf(stderr, "unexpected AVX state size: %x, " "expected %x\n", avx_size, sizeof (struct avx_state)); return (false); } if ((avx_off + avx_size) > fpu_size) { (void) fprintf(stderr, "AVX data falls outside fpu size: " "%x > %x\n", avx_off + avx_size, fpu_size); return (false); } struct xsave_min *xs = fpu_area; struct avx_state *avx = fpu_area + avx_off; /* do a simple data round-trip */ struct vm_fpu_state req = { .vcpuid = 0, .buf = fpu_area, .len = fpu_size, }; if (!get_fpu(fd, &req)) { return (false); } /* With AVX unused so far, we expect it to be absent from the BV */ if (xs->header.xsh_xstate_bv != (XFEATURE_LEGACY_FP | XFEATURE_SSE)) { (void) fprintf(stderr, "bad xstate_bv %lx, expected %lx\n", xs->header.xsh_xstate_bv, (XFEATURE_LEGACY_FP | XFEATURE_SSE)); return (false); } avx->ymm[0] = UINT64_MAX; avx->ymm[2] = 2; /* first write without asserting AVX in BV */ if (!set_fpu(fd, &req)) { return (false); } /* And check that the AVX state stays empty */ bzero(fpu_area, fpu_size); if (!get_fpu(fd, &req)) { return (false); } if (xs->header.xsh_xstate_bv != (XFEATURE_LEGACY_FP | XFEATURE_SSE)) { (void) fprintf(stderr, "xstate_bv changed unexpectedly %lx\n", xs->header.xsh_xstate_bv); return (false); } if (avx->ymm[0] != 0 || avx->ymm[2] != 0) { (void) fprintf(stderr, "YMM state changed unexpectedly " "%lx %lx\n", avx->ymm[0], avx->ymm[2]); return (false); } /* Now write YMM and set the appropriate AVX BV state */ avx->ymm[0] = UINT64_MAX; avx->ymm[2] = 2; xs->header.xsh_xstate_bv |= XFEATURE_AVX; if (!set_fpu(fd, &req)) { return (false); } /* ... and now check that it stuck */ bzero(fpu_area, fpu_size); if (!get_fpu(fd, &req)) { return (false); } if ((xs->header.xsh_xstate_bv & XFEATURE_AVX) == 0) { (void) fprintf(stderr, "AVX missing from xstate_bv %lx\n", xs->header.xsh_xstate_bv); return (false); } if (avx->ymm[0] != UINT64_MAX || avx->ymm[2] != 2) { (void) fprintf(stderr, "YMM state not preserved " "%lx != %lx | %lx != %lx\n", avx->ymm[0], UINT64_MAX, avx->ymm[2], 2); return (false); } return (true); } int main(int argc, char *argv[]) { struct vmctx *ctx; int res, fd; const char *suite_name = basename(argv[0]); ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could not open test VM"); return (EXIT_FAILURE); } fd = vm_get_device_fd(ctx); struct vm_fpu_desc_entry entries[64]; struct vm_fpu_desc desc = { .vfd_entry_data = entries, .vfd_num_entries = 64, }; res = ioctl(fd, VM_DESC_FPU_AREA, &desc); if (res != 0) { perror("could not query fpu area description"); goto bail; } /* Make sure the XSAVE area described for this machine is reasonable */ if (desc.vfd_num_entries == 0) { (void) fprintf(stderr, "no FPU description entries found\n"); goto bail; } if (desc.vfd_req_size < MIN_XSAVE_SIZE) { (void) fprintf(stderr, "required XSAVE size %lu < " "expected %lu\n", desc.vfd_req_size, MIN_XSAVE_SIZE); goto bail; } const size_t fpu_size = desc.vfd_req_size; void *fpu_area = malloc(fpu_size); if (fpu_area == NULL) { perror("could not allocate fpu area"); goto bail; } bzero(fpu_area, fpu_size); if (!check_sse(fd, &desc, fpu_area, fpu_size)) { goto bail; } if (!check_avx(fd, &desc, fpu_area, fpu_size)) { goto bail; } /* mission accomplished */ vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); bail: vm_destroy(ctx); (void) printf("%s\tFAIL\n", suite_name); return (EXIT_FAILURE); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #define APIC_ADDR_TIMER_ICR 0xfee00380 #define APIC_ADDR_TIMER_CCR 0xfee00390 #define TIMER_TEST_VAL 0x10000 static void test_ccr_clamp(int vmfd, struct vcpu *vcpu) { /* Pause the instance before attempting to manipulate vlapic data */ if (ioctl(vmfd, VM_PAUSE, 0) != 0) { err(EXIT_FAILURE, "VM_PAUSE failed"); } struct vdi_lapic_v1 lapic_data; struct vm_data_xfer xfer = { .vdx_vcpuid = 0, .vdx_class = VDC_LAPIC, .vdx_version = 1, .vdx_len = sizeof (lapic_data), .vdx_data = &lapic_data, }; /* Read the existing lapic data to get a baseline */ if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { err(EXIT_FAILURE, "VM_DATA_READ of lapic failed"); } /* Writing that exact same data back should be fine */ if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { err(EXIT_FAILURE, "VM_DATA_WRITE of lapic failed"); } /* Simulate ICR being loaded with a meaningful (but short) value */ lapic_data.vl_lapic.vlp_icr_timer = TIMER_TEST_VAL; /* * Pretend as if timer is scheduled to fire 100s (in the future) after * VM boot time. With the ICR value, this should trigger the overage * detection and clamping. */ lapic_data.vl_timer_target = 1000000000UL * 100; /* Try to write the outlandish timer result */ if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { err(EXIT_FAILURE, "VM_DATA_WRITE of lapic failed"); } /* * The timer will not actually be scheduled (and thus observable via * CCR) until the instance is resumed... */ if (ioctl(vmfd, VM_RESUME, 0) != 0) { err(EXIT_FAILURE, "VM_RESUME failed"); } /* Now simulate a read of CCR from that LAPIC */ uint64_t ccr_value = 0; int error = vm_readwrite_kernemu_device(vcpu, APIC_ADDR_TIMER_CCR, false, 4, &ccr_value); if (error != 0) { err(EXIT_FAILURE, "could not emulate MMIO of LAPIC CCR"); } if (ccr_value != TIMER_TEST_VAL) { errx(EXIT_FAILURE, "CCR not clamped: %lx != %x", ccr_value, TIMER_TEST_VAL); } } static void test_timer_icr_constraints(int vmfd, struct vcpu *vcpu) { /* Pause instance before our shenanigans */ if (ioctl(vmfd, VM_PAUSE, 0) != 0) { err(EXIT_FAILURE, "VM_PAUSE failed"); } /* Load a TIMER_ICR value */ uint64_t icr_value = 1 << 30; int error = vm_readwrite_kernemu_device(vcpu, APIC_ADDR_TIMER_CCR, true, 4, &icr_value); if (error != 0) { err(EXIT_FAILURE, "failed to load timer ICR value"); } struct vdi_lapic_v1 lapic_data; struct vm_data_xfer xfer = { .vdx_vcpuid = 0, .vdx_class = VDC_LAPIC, .vdx_version = 1, .vdx_len = sizeof (lapic_data), .vdx_data = &lapic_data, }; if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { err(EXIT_FAILURE, "VM_DATA_READ of lapic failed"); } /* Confirm that ICR value is set, and timer is scheduled */ if (lapic_data.vl_lapic.vlp_icr_timer == 0) { errx(EXIT_FAILURE, "ICR_TIMER is 0"); } if (lapic_data.vl_timer_target == 0) { errx(EXIT_FAILURE, "vlapic timer not scheduled"); } /* Reset vCPU to clear timer state from LAPIC */ if (vcpu_reset(vcpu) != 0) { err(EXIT_FAILURE, "vcpu_reset() failed"); } /* Re-read vlapic, and confirm zeroed bits */ if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { err(EXIT_FAILURE, "VM_DATA_READ of lapic failed"); } if (lapic_data.vl_lapic.vlp_icr_timer != 0) { errx(EXIT_FAILURE, "ICR_TIMER is not 0"); } if (lapic_data.vl_timer_target != 0) { errx(EXIT_FAILURE, "vlapic timer should not be scheduled"); } /* * Try to load a vlapic payload with timer scheduled but icr_timer still * zeroed out. */ lapic_data.vl_timer_target = 1 << 20; lapic_data.vl_lapic.vlp_icr_timer = 0; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "VM_DATA_WRITE of invalid lapic data should fail"); } } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "Could not open vcpu0"); } if (vm_activate_cpu(vcpu) != 0) { err(EXIT_FAILURE, "could not activate vcpu0"); } const int vmfd = vm_get_device_fd(ctx); test_ccr_clamp(vmfd, vcpu); test_timer_icr_constraints(vmfd, vcpu); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); int ctl_fd = open(VMM_CTL_DEV, O_EXCL | O_RDWR); if (ctl_fd < 0) { perror("could not open vmmctl device"); } int version = ioctl(ctl_fd, VMM_INTERFACE_VERSION, 0); if (version < 0) { perror("VMM_INTERFACE_VERSION ioctl failed"); } if (version != VMM_CURRENT_INTERFACE_VERSION) { (void) fprintf(stderr, "kernel version %d != expected %d\n", version, VMM_CURRENT_INTERFACE_VERSION); return (EXIT_FAILURE); } (void) close(ctl_fd); /* Query the version via an instance fd as well */ struct vmctx *ctx = create_test_vm(suite_name); if (ctx == NULL) { err(EXIT_FAILURE, "could not open test VM"); } version = ioctl(vm_get_device_fd(ctx), VMM_INTERFACE_VERSION, 0); if (version < 0) { err(EXIT_FAILURE, "VMM_INTERFACE_VERSION ioctl failed on vmm fd"); } if (version != VMM_CURRENT_INTERFACE_VERSION) { errx(EXIT_FAILURE, "kernel version %d != expected %d", version, VMM_CURRENT_INTERFACE_VERSION); } vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (0); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could open test VM"); } /* * It would be odd if we had the freshly created VM instance, but it did * not appear to exist. */ assert(check_instance_usable(suite_name)); vm_close(ctx); /* Instance should remain, even though we closed it */ if (!check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance missing after vm_close()"); } /* * The common destroy_instance() uses the "legacy" destruction mechanism * via the vmmctl device. */ if (destroy_instance(suite_name) != 0) { errx(EXIT_FAILURE, "ioctl(VMM_DESTROY_VM) failed"); } /* Instance should be gone at this point */ if (check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance still accessible after destroy"); } (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } /* Query VM_MAXCPU equivalent via VM topology */ uint16_t sockets, cores, threads, maxcpus; if (vm_get_topology(ctx, &sockets, &cores, &threads, &maxcpus) != 0) { err(EXIT_FAILURE, "could not query maxcpu via vm_get_topology()"); } for (int i = 0; i < maxcpus; i++) { struct vcpu *vcpu; uint64_t val = 0; if ((vcpu = vm_vcpu_open(ctx, i)) == NULL) { err(EXIT_FAILURE, "could not open vcpu %d", i); } /* Check that all valid vCPUs can be activated... */ if (vm_activate_cpu(vcpu) != 0) { err(EXIT_FAILURE, "could not activate vcpu %d", i); } /* and that we can do something basic (like read a register) */ if (vm_get_register(vcpu, VM_REG_GUEST_RAX, &val) != 0) { err(EXIT_FAILURE, "could not read %%rax on vcpu %d", i); } vm_vcpu_close(vcpu); } /* Check some bogus inputs as well */ const int bad_inputs[] = {-1, maxcpus, maxcpus + 1}; for (uint_t i = 0; i < ARRAY_SIZE(bad_inputs); i++) { const int vcpuid = bad_inputs[i]; /* * There is no logic (currently) in vm_vcpu_open() checking * vcpuid for validity, so we must use a further operation to * perform a useful test. */ struct vcpu *vcpu = vm_vcpu_open(ctx, vcpuid); if (vcpu == NULL) { errx(EXIT_FAILURE, "unexpected failure from vm_vcpu_open()"); } if (vm_activate_cpu(vcpu) == 0) { err(EXIT_FAILURE, "unexpected VM_ACTIVATE success for bad vcpuid %d", vcpuid); } vm_vcpu_close(vcpu); } vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include "common.h" enum test_segs { SEG_LOWMEM = 0, SEG_BOOTROM = 1, }; #define PAGE_CNT 2 #define PAGE_SZ 4096 #define SEG_SZ (PAGE_CNT * PAGE_SZ) #define WHOLE_SZ (SEG_SZ * 2) #define TESTVAL_LOWMEM 0x1000ffff #define TESTVAL_BOOTROM 0x2000eeee int main(int argc, char *argv[]) { struct vmctx *ctx; int res, fd; const char *suite_name = basename(argv[0]); ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could open test VM"); return (1); } fd = vm_get_device_fd(ctx); res = alloc_memseg(ctx, SEG_LOWMEM, SEG_SZ, ""); if (res != 0) { perror("could not alloc lowmem seg"); goto bail; } res = alloc_memseg(ctx, SEG_BOOTROM, SEG_SZ, "bootrom"); if (res != 0) { perror("could not alloc bootrom seg"); goto bail; } res = vm_mmap_memseg(ctx, 0, SEG_LOWMEM, 0, SEG_SZ, PROT_ALL); if (res != 0) { perror("could not map lowmem into vmspace"); goto bail; } res = vm_mmap_memseg(ctx, SEG_SZ, SEG_BOOTROM, 0, SEG_SZ, PROT_READ); if (res != 0) { perror("could not map bootrom into vmspace"); goto bail; } void *guest_mem; guest_mem = mmap(NULL, WHOLE_SZ, PROT_READ, MAP_SHARED, fd, 0); if (guest_mem == MAP_FAILED) { perror("could not mmap guest-physical memory"); goto bail; } void *direct_lowmem, *direct_bootrom; off_t seg_obj_off; res = vm_get_devmem_offset(ctx, SEG_LOWMEM, &seg_obj_off); if (res != 0) { perror("could not find mapping offset for lowmem seg"); goto bail; } direct_lowmem = mmap(NULL, SEG_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, seg_obj_off); if (direct_lowmem == MAP_FAILED) { perror("could not mmap lowmem directly"); goto bail; } res = vm_get_devmem_offset(ctx, SEG_BOOTROM, &seg_obj_off); if (res != 0) { perror("could not find mapping offset for lowmem seg"); goto bail; } direct_bootrom = mmap(NULL, SEG_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, seg_obj_off); if (direct_bootrom == MAP_FAILED) { perror("could not mmap bootrom directly"); goto bail; } uint32_t *datap; datap = direct_lowmem; *datap = TESTVAL_LOWMEM; datap = direct_bootrom; *datap = TESTVAL_BOOTROM; /* check that data written though direct access is as expected */ datap = guest_mem; if (*datap != TESTVAL_LOWMEM) { (void) fprintf(stderr, "unexpected data in lowmem %x != %x\n", *datap, TESTVAL_LOWMEM); goto bail; } datap = (guest_mem + SEG_SZ); if (*datap != TESTVAL_BOOTROM) { (void) fprintf(stderr, "unexpected data in bootrom %x != %x\n", *datap, TESTVAL_BOOTROM); goto bail; } /* unmap access mappings */ res = munmap(guest_mem, WHOLE_SZ); if (res != 0) { perror("could not munmap vmspace"); goto bail; } res = munmap(direct_lowmem, SEG_SZ); if (res != 0) { perror("could not munmap lowmem object"); goto bail; } res = munmap(direct_bootrom, SEG_SZ); if (res != 0) { perror("could not munmap bootrom object"); goto bail; } /* mission accomplished */ vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (0); bail: vm_destroy(ctx); return (1); } /* * 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 2025 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #define TEST_SEGID 0 #define PAGE_SZ 4096 #define MBYTE (1024 * 1024) #define GBYTE (1024 * MBYTE) #define MAP_OFF ((512UL * GBYTE) - PAGE_SZ) #define SEG_SZ (4 * MBYTE) #define PAGE_CNT (SEG_SZ / PAGE_SZ) int main(int argc, char *argv[]) { struct vmctx *ctx; int res; const char *suite_name = basename(argv[0]); ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could open test VM"); return (1); } res = alloc_memseg(ctx, TEST_SEGID, SEG_SZ, "test_seg"); if (res != 0) { perror("could not alloc memseg"); goto bail; } res = vm_mmap_memseg(ctx, MAP_OFF, TEST_SEGID, 0, SEG_SZ, PROT_ALL); if (res != 0) { perror("could not map memseg into vmspace"); goto bail; } res = vm_munmap_memseg(ctx, MAP_OFF, SEG_SZ); if (res != 0) { perror("could not unmap memseg"); goto bail; } /* mission accomplished */ vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (0); bail: vm_destroy(ctx); return (1); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include "common.h" /* Half of a leaf page table is 256 pages */ #define LOWER_SZ (256 * 4096) #define UPPER_SZ LOWER_SZ #define TOTAL_SZ (LOWER_SZ + UPPER_SZ) #define LOWER_OFF 0 #define UPPER_OFF LOWER_SZ enum test_memsegs { MSEG_LOW = 0, MSEG_HIGH = 1, }; static sigjmp_buf segv_env; void sigsegv_handler(int sig) { siglongjmp(segv_env, 1); } int main(int argc, char *argv[]) { struct vmctx *ctx; int res, fd; void *guest_mem; const char *suite_name = basename(argv[0]); ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could open test VM"); return (1); } fd = vm_get_device_fd(ctx); res = alloc_memseg(ctx, MSEG_LOW, LOWER_SZ, "mseg_low"); if (res != 0) { perror("could not alloc low memseg"); goto bail; } res = alloc_memseg(ctx, MSEG_HIGH, UPPER_SZ, "mseg_high"); if (res != 0) { perror("could not alloc high memseg"); goto bail; } res = vm_mmap_memseg(ctx, LOWER_OFF, MSEG_LOW, 0, LOWER_SZ, PROT_ALL); if (res != 0) { perror("could not map low memseg"); goto bail; } res = vm_mmap_memseg(ctx, UPPER_OFF, MSEG_HIGH, 0, UPPER_SZ, PROT_ALL); if (res != 0) { perror("could not map high memseg"); goto bail; } guest_mem = mmap(NULL, TOTAL_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (guest_mem == MAP_FAILED) { perror("could not mmap guest memory"); goto bail; } /* Fill memory with 0xff */ for (uintptr_t gpa = 0; gpa < TOTAL_SZ; gpa++) { uint8_t *ptr = guest_mem + gpa; *ptr = 0xff; } /* Unmap the lower memseg */ res = vm_munmap_memseg(ctx, LOWER_OFF, LOWER_SZ); if (guest_mem == NULL) { perror("could not unmap lower memseg"); goto bail; } /* Confirm upper contents are still correct/accessible */ for (uintptr_t gpa = UPPER_OFF; gpa < UPPER_OFF + UPPER_SZ; gpa++) { uint8_t *ptr = guest_mem + gpa; if (*ptr != 0xff) { (void) printf("invalid mem contents at GPA %lx: %x\n", gpa, *ptr); goto bail; } *ptr = 0xee; } /* * Attempt to access the lower contents, which should result in an * expected (and thus handled) SIGSEGV. */ struct sigaction sa = { .sa_handler = sigsegv_handler, }; struct sigaction old_sa; res = sigaction(SIGSEGV, &sa, &old_sa); if (res != 0) { perror("could not prep signal handling for bad access"); goto bail; } if (sigsetjmp(segv_env, 1) == 0) { volatile uint8_t *ptr = guest_mem; /* * This access to the guest space should fail, since the memseg * covering the lower part of the VM space has been unmapped. */ uint8_t tmp = *ptr; (void) printf("access to %p (%x) should have failed\n", tmp); goto bail; } /* * Unmap and remap the space so any cached entries are dropped for the * portion we expect is still accessible. */ res = munmap(guest_mem, TOTAL_SZ); if (res != 0) { perror("could not unmap lower memseg"); goto bail; } guest_mem = mmap(NULL, TOTAL_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (guest_mem == MAP_FAILED) { perror("could not re-mmap guest memory"); goto bail; } /* Check the upper portion for accessibility. */ if (sigsetjmp(segv_env, 1) == 0) { volatile uint8_t *ptr = guest_mem + UPPER_OFF; uint8_t tmp = *ptr; if (tmp != 0xee) { (void) printf("unexpected value at %p (%x)\n", ptr, tmp); goto bail; } res = sigaction(SIGSEGV, &old_sa, NULL); if (res != 0) { perror("could not restore SIGSEGV handler"); goto bail; } } else { (void) printf("unexpected fault in upper mapping\n"); goto bail; } /* Unmap the upper memseg */ res = vm_munmap_memseg(ctx, UPPER_OFF, UPPER_SZ); if (guest_mem == NULL) { perror("could not unmap upper memseg"); goto bail; } /* mission accomplished */ (void) printf("%s\tPASS\n", suite_name); vm_destroy(ctx); return (0); bail: vm_destroy(ctx); return (1); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include "common.h" #define TEST_SEGID 0 #define PAGE_CNT 1024 #define PAGE_SZ 4096 #define SEG_SZ (PAGE_CNT * PAGE_SZ) int main(int argc, char *argv[]) { struct vmctx *ctx; int res, fd; void *seg_obj, *guest_mem; const char *suite_name = basename(argv[0]); ctx = create_test_vm(suite_name); if (ctx == NULL) { perror("could open test VM"); return (1); } fd = vm_get_device_fd(ctx); res = alloc_memseg(ctx, TEST_SEGID, SEG_SZ, "test_seg"); if (res != 0) { perror("could not alloc memseg"); goto bail; } off_t seg_obj_off; res = vm_get_devmem_offset(ctx, TEST_SEGID, &seg_obj_off); if (res != 0) { perror("could not find mapping offset for seg object"); goto bail; } seg_obj = mmap(NULL, SEG_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, seg_obj_off); if (seg_obj == MAP_FAILED) { perror("could not mmap seg object"); goto bail; } /* populate with initial data */ for (uint_t i = 0; i < PAGE_CNT; i++) { uint64_t *p = (uint64_t *)((uintptr_t)seg_obj + i * PAGE_SZ); *p = i; } res = vm_mmap_memseg(ctx, 0, TEST_SEGID, 0, SEG_SZ, PROT_ALL); if (res != 0) { perror("could not map memseg into vmspace"); goto bail; } guest_mem = mmap(NULL, SEG_SZ, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (seg_obj == MAP_FAILED) { perror("could not mmap vmspace"); goto bail; } /* check data and access though vmspace */ for (uint_t i = 0; i < PAGE_CNT; i++) { const uint64_t off = i * PAGE_SZ; uint64_t *p = (uint64_t *)((uintptr_t)guest_mem + off); const uint64_t val = *p; if (val != i) { (void) printf("%lu != %u at gpa:%lx\n", val, i, off); goto bail; } /* leave a change behind */ *p = val * 2; } /* check changes made through vmspace */ for (uint_t i = 0; i < PAGE_CNT; i++) { const uint64_t off = i * PAGE_SZ; uint64_t *p = (uint64_t *)((uintptr_t)seg_obj + off); const uint_t expected = i * 2; const uint64_t val = *p; if (val != expected) { (void) printf("%lu != %u at gpa:%lx\n", val, expected, off); goto bail; } } /* unmap access mappings */ res = munmap(guest_mem, SEG_SZ); if (res != 0) { perror("could not munmap vmspace"); goto bail; } res = munmap(seg_obj, SEG_SZ); if (res != 0) { perror("could not munmap seg object"); goto bail; } /* mission accomplished */ vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (0); bail: vm_destroy(ctx); return (1); } /* * 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 2024 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #define PAGESZ 4096 #define TEST_PAGE_COUNT 256 #define TEST_MEM_SZ (PAGESZ * 256) static struct vmctx * check_vmm_capability(const char *tname) { char vmname[VM_MAX_NAMELEN]; name_test_vm(tname, vmname); int res = vm_create(vmname, VCF_TRACK_DIRTY); if (res != 0) { if (errno == ENOTSUP) { (void) fprintf(stderr, "VMM lacks dirty page tracking capability"); (void) printf("%s\tSKIP\n", tname); exit(EXIT_SUCCESS); } err(EXIT_FAILURE, "could not create VM"); } struct vmctx *ctx = vm_open(vmname); if (ctx == NULL) { err(EXIT_FAILURE, "could not open test VM"); } return (ctx); } static void expect_errno(int expected) { if (errno != expected) { errx(EXIT_FAILURE, "unexpected errno %d != %d", errno, expected); } } static uint8_t popc8(uint8_t val) { uint8_t cnt; for (cnt = 0; val != 0; val &= (val - 1)) { cnt++; } return (cnt); } static uint_t legacy_clear_dirty(struct vmctx *ctx) { uint8_t bitmap[TEST_PAGE_COUNT / 8] = { 0 }; struct vmm_dirty_tracker req = { .vdt_start_gpa = 0, .vdt_len = TEST_MEM_SZ, .vdt_pfns = bitmap, }; if (ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &req) != 0) { err(EXIT_FAILURE, "VM_TRACK_DIRTY_PAGES failed"); } uint_t bits_set = 0; for (uint_t i = 0; i < (TEST_PAGE_COUNT / 8); i++) { bits_set += popc8(bitmap[i]); } return (bits_set); } static void do_npt_op(int vmfd, struct vm_npt_operation *vno) { if (ioctl(vmfd, VM_NPT_OPERATION, vno) != 0) { err(EXIT_FAILURE, "VM_NPT_OPERATION failed"); } } static void test_legacy(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); uint8_t *datap = vm_map_gpa(ctx, 0, PAGESZ); /* dirty the first page */ *datap = 0xff; uint8_t bitmap[TEST_PAGE_COUNT / 8] = { 0 }; struct vmm_dirty_tracker req = { .vdt_start_gpa = 0, .vdt_len = TEST_MEM_SZ, .vdt_pfns = bitmap, }; if (ioctl(vmfd, VM_TRACK_DIRTY_PAGES, &req) != 0) { err(EXIT_FAILURE, "VM_TRACK_DIRTY_PAGES failed"); } if (bitmap[0] != 1) { errx(EXIT_FAILURE, "first page not marked dirty"); } for (uint_t i = 1; i < (TEST_PAGE_COUNT / 8); i++) { if (bitmap[i] != 0) { errx(EXIT_FAILURE, "unexpected non-zero entry: bitmap[%u] = %x\n", i, bitmap[i]); } } } static void test_toggle_tracking(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); struct vm_npt_operation vno = { .vno_operation = VNO_OP_GET_TRACK_DIRTY, .vno_gpa = 0, .vno_len = 0, }; /* * Since the VM was created with VCF_TRACK_DIRTY set, dirty tracking * should already be active. */ if (ioctl(vmfd, VM_NPT_OPERATION, &vno) != 1) { errx(EXIT_FAILURE, "expected dirty tracking to be active"); } vno.vno_operation = VNO_OP_DIS_TRACK_DIRTY; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) != 0) { err(EXIT_FAILURE, "VM_NPT_OPERATION failed"); } vno.vno_operation = VNO_OP_GET_TRACK_DIRTY; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) != 0) { errx(EXIT_FAILURE, "expected dirty tracking to be inactive"); } vno.vno_operation = VNO_OP_EN_TRACK_DIRTY; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) != 0) { err(EXIT_FAILURE, "VM_NPT_OPERATION failed"); } vno.vno_operation = VNO_OP_GET_TRACK_DIRTY; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) != 1) { errx(EXIT_FAILURE, "expected dirty tracking to be active again"); } } static void test_inval_args(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); struct vm_npt_operation vno = { 0 }; /* invalid vno_operation */ vno.vno_operation = ~0; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) == 0) { err(EXIT_FAILURE, "unexpected VM_NPT_OPERATION success"); } expect_errno(EINVAL); /* valid operation, but gpa which is not page-aligned */ vno.vno_operation = VNO_OP_GET_DIRTY | VNO_FLAG_BITMAP_IN; vno.vno_gpa = 0x100; vno.vno_len = PAGESZ; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) == 0) { err(EXIT_FAILURE, "unexpected VM_NPT_OPERATION success"); } expect_errno(EINVAL); /* gpa is page-aligned, but len isn't */ vno.vno_gpa = 0; vno.vno_len = PAGESZ + 0x100; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) == 0) { err(EXIT_FAILURE, "unexpected VM_NPT_OPERATION success"); } expect_errno(EINVAL); /* overflowing region */ vno.vno_gpa = 0xffffffffffffe000; vno.vno_len = 512 * PAGESZ; if (ioctl(vmfd, VM_NPT_OPERATION, &vno) == 0) { err(EXIT_FAILURE, "unexpected VM_NPT_OPERATION success"); } expect_errno(EOVERFLOW); } static void test_op_get_dirty(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); uint8_t *datap = vm_map_gpa(ctx, 0, TEST_MEM_SZ); /* Use legacy mechanism to ensure dirty bits are clear to start */ (void) legacy_clear_dirty(ctx); /* Dirty the first page out of every 8 */ for (uint_t i = 0; i < TEST_MEM_SZ; i += (PAGESZ * 8)) { datap[i] = 0xff; } uint8_t bits[TEST_PAGE_COUNT / 8] = { 0 }; struct vm_npt_operation vno = { .vno_gpa = 0, .vno_len = TEST_MEM_SZ, .vno_operation = VNO_OP_GET_DIRTY | VNO_FLAG_BITMAP_OUT, .vno_bitmap = bits, }; do_npt_op(vmfd, &vno); for (uint_t i = 0; i < TEST_PAGE_COUNT / 8; i++) { if (bits[i] != 0x01) { errx(EXIT_FAILURE, "unexpected dirty bits %02x at base gpa %08x", bits[i], i * PAGESZ * 8); } } /* Clear those bits again */ (void) legacy_clear_dirty(ctx); /* And check that they are zeroed now */ do_npt_op(vmfd, &vno); for (uint_t i = 0; i < TEST_PAGE_COUNT / 8; i++) { if (bits[i] != 0) { errx(EXIT_FAILURE, "unexpected dirty bits %02x at base gpa %08x", bits[i], i * PAGESZ * 8); } } } static void test_op_set_dirty(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); /* Use legacy mechanism to ensure dirty bits are clear to start */ (void) legacy_clear_dirty(ctx); /* Mark first 17 pages as dirty */ uint8_t bits[TEST_PAGE_COUNT / 8] = { 0xff, 0xff, 0x80 }; struct vm_npt_operation vno = { .vno_gpa = 0, .vno_len = TEST_MEM_SZ, .vno_operation = VNO_OP_SET_DIRTY | VNO_FLAG_BITMAP_IN, .vno_bitmap = bits, }; do_npt_op(vmfd, &vno); uint_t legacy_dirty = legacy_clear_dirty(ctx); if (legacy_dirty != 17) { errx(EXIT_FAILURE, "unexpected dirty count after OP_SET_DIRTY"); } } #define BMAP_IDX(gpa) ((gpa) / (PAGESZ * 8)) #define BMAP_BIT(gpa) (((gpa) / PAGESZ) % 8) static void test_op_reset_dirty(struct vmctx *ctx) { const int vmfd = vm_get_device_fd(ctx); uint8_t *datap = vm_map_gpa(ctx, 0, TEST_MEM_SZ); /* Use legacy mechanism to ensure dirty bits are clear to start */ (void) legacy_clear_dirty(ctx); /* Dirty the front half of memory */ for (uintptr_t gpa = 0; gpa < (TEST_MEM_SZ / 2); gpa += PAGESZ) { datap[gpa] = 0xff; } uint8_t bits[TEST_PAGE_COUNT / 8] = { 0 }; /* Mark bitmap for every other page, starting at 0 */ for (uintptr_t gpa = 0; gpa < TEST_MEM_SZ; gpa += (2 * PAGESZ)) { bits[BMAP_IDX(gpa)] |= (1 << BMAP_BIT(gpa)); } struct vm_npt_operation vno = { .vno_gpa = 0, .vno_len = TEST_MEM_SZ, .vno_operation = VNO_OP_RESET_DIRTY | VNO_FLAG_BITMAP_IN | VNO_FLAG_BITMAP_OUT, .vno_bitmap = bits, }; do_npt_op(vmfd, &vno); /* Check that pages marked dirty were reported back as such */ for (uintptr_t gpa = 0; gpa < TEST_MEM_SZ; gpa += PAGESZ) { const bool is_even_page = (BMAP_BIT(gpa) % 2) == 0; const bool is_dirty = (bits[BMAP_IDX(gpa)] & (1 << BMAP_BIT(gpa))) != 0; /* Even pages in the first half should be set */ if (is_even_page && gpa < (TEST_MEM_SZ / 2) && !is_dirty) { errx(EXIT_FAILURE, "missing dirty bit set at gpa %08lx", gpa); } /* Odd pages and even pages in second half should be unset */ if (is_dirty && (!is_even_page || gpa >= (TEST_MEM_SZ / 2))) { errx(EXIT_FAILURE, "unexpected dirty bit set at gpa %08lx", gpa); } } /* * With half of the pages dirtied at first, and then half of those reset * from dirty in the NPT operation, we expect 1/4 to be remaining. */ uint_t remaining_dirty = legacy_clear_dirty(ctx); if (remaining_dirty != (TEST_PAGE_COUNT / 4)) { errx(EXIT_FAILURE, "expected %u pages remaining dirty, found %u", TEST_PAGE_COUNT / 2, remaining_dirty); } } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; ctx = check_vmm_capability(suite_name); if (vm_setup_memory(ctx, TEST_MEM_SZ, VM_MMAP_ALL) != 0) { err(EXIT_FAILURE, "could not setup VM memory"); } /* Test "legacy" VM_TRACK_DIRTY_PAGES mechanism first */ test_legacy(ctx); /* Confirm that dirty tracking can be queried and toggled on/off */ test_toggle_tracking(ctx); /* Check some invalid argument conditions */ test_inval_args(ctx); /* Can dirty bits be queried with VNO_OP_GET_DIRTY */ test_op_get_dirty(ctx); /* Can dirty bits be set with VNO_OP_SET_DIRTY */ test_op_set_dirty(ctx); /* * Can dirty bits be reset (simultaneously queried and cleared ) * with VNO_OP_RESET_DIRTY */ test_op_reset_dirty(ctx); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" bool check_paused(struct vmctx *ctx) { struct vdi_field_entry_v1 entry = { .vfe_ident = VAI_VM_IS_PAUSED, }; struct vm_data_xfer xfer = { .vdx_vcpuid = -1, .vdx_class = VDC_VMM_ARCH, .vdx_version = 1, .vdx_len = sizeof (entry), .vdx_data = &entry, .vdx_flags = VDX_FLAG_READ_COPYIN, }; const int vmfd = vm_get_device_fd(ctx); if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { err(EXIT_FAILURE, "error reading pause state"); } return (entry.vfe_value != 0); } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "Could not open vcpu0"); } if (vm_activate_cpu(vcpu) != 0) { err(EXIT_FAILURE, "could not activate vcpu0"); } const int vmfd = vm_get_device_fd(ctx); int error; /* Instance should not be paused after initial creation */ if (check_paused(ctx)) { errx(EXIT_FAILURE, "VM unexpectedly in paused state"); } if (ioctl(vmfd, VM_PAUSE, 0) != 0) { err(EXIT_FAILURE, "VM_PAUSE failed"); } /* Now we should observe the instance as paused */ if (!check_paused(ctx)) { errx(EXIT_FAILURE, "VM no in expected paused state"); } /* Pausing an already-paused instanced should result in EALREADY */ if (ioctl(vmfd, VM_PAUSE, 0) == 0) { errx(EXIT_FAILURE, "VM_PAUSE should have failed"); } error = errno; if (error != EALREADY) { errx(EXIT_FAILURE, "VM_PAUSE unexpected errno: %d != %d", EALREADY, error); } /* A VM_RUN attempted now should fail with EBUSY */ struct vm_entry ventry = { .cmd = 0, }; struct vm_exit vexit = { 0 }; if (vm_run(vcpu, &ventry, &vexit) == 0) { errx(EXIT_FAILURE, "VM_RUN should have failed"); } error = errno; if (error != EBUSY) { errx(EXIT_FAILURE, "VM_RUN unexpected errno: %d != %d", EBUSY, error); } if (ioctl(vmfd, VM_RESUME, 0) != 0) { err(EXIT_FAILURE, "VM_RESUME failed"); } /* Now we should observe the instance as no longer paused */ if (check_paused(ctx)) { errx(EXIT_FAILURE, "VM unexpectedly in paused state"); } /* Resuming an already-running instanced should result in EALREADY */ if (ioctl(vmfd, VM_RESUME, 0) == 0) { errx(EXIT_FAILURE, "VM_RESUME should have failed"); } error = errno; if (error != EALREADY) { errx(EXIT_FAILURE, "VM_RESUME unexpected errno: %d != %d", EALREADY, error); } vm_vcpu_close(vcpu); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "common.h" int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could open test VM"); } /* * It would be odd if we had the freshly created VM instance, but it did * not appear to exist. */ assert(check_instance_usable(suite_name)); if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "Could not open vcpu0"); } /* Ensure sure that auto-destruct is off */ if (ioctl(vm_get_device_fd(ctx), VM_SET_AUTODESTRUCT, 0) != 0) { errx(EXIT_FAILURE, "could not disable auto-destruct"); } if (ioctl(vm_get_device_fd(ctx), VM_DESTROY_SELF, 0) != 0) { errx(EXIT_FAILURE, "ioctl(VM_DESTROY_SELF) failed"); } /* * Since we still hold the instance open, we expect it to still exist in * /dev/vmm, but be useless for further operations */ if (!check_instance_exists(suite_name)) { err(EXIT_FAILURE, "instance missing after unfinished destroy"); } /* Attempt an operation on our still-open handle */ uint64_t reg = 0; if (vm_get_register(vcpu, VM_REG_GUEST_RAX, ®) == 0) { err(EXIT_FAILURE, "VM_GET_REGISTER succeeded despite instance destruction"); } /* Check usability via the dedicated ioctl */ if (check_instance_usable(suite_name)) { err(EXIT_FAILURE, "instance not reporting in-progress destruction"); } vm_vcpu_close(vcpu); vm_close(ctx); ctx = NULL; /* Make doubly-sure the VM is gone after close */ if (check_instance_exists(suite_name)) { err(EXIT_FAILURE, "instance still accessible after destroy"); } (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); } /* * 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 2023 Oxide Computer Company */ /* * VMM Time Data interface tests * * Note: requires `vmm_allow_state_writes` to be set */ #include #include #include #include #include #include #include #include #include "common.h" /* * Constants from svm.c, redefined here for convenience */ #define AMD_TSC_MIN_FREQ 500000000 #define AMD_TSC_MAX_FREQ_RATIO 15 static void should_eq_u32(const char *field_name, uint32_t a, uint32_t b) { if (a != b) { errx(EXIT_FAILURE, "unexpected %s %u != %u", field_name, a, b); } } static void should_eq_u64(const char *field_name, uint64_t a, uint64_t b) { if (a != b) { errx(EXIT_FAILURE, "unexpected %s %lu != %lu", field_name, a, b); } } /* a should be >= b */ static void should_geq_u64(const char *field_name, uint64_t a, uint64_t b) { if (a < b) { errx(EXIT_FAILURE, "unexpected %s %lu < %lu", field_name, a, b); } } static void should_geq_i64(const char *field_name, int64_t a, int64_t b) { if (a < b) { errx(EXIT_FAILURE, "unexpected %s %ld < %ld", field_name, a, b); } } /* * Test a valid VMM_DATA_READ of time data */ static void test_valid_read_time_data(int vmfd, struct vdi_time_info_v1 *time_info) { struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = time_info, }; if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) { errx(EXIT_FAILURE, "VMM_DATA_READ of time info failed"); } } /* * Test valid VMM_DATA_WRITE of time data */ static void test_valid_write_time_data(int vmfd, struct vdi_time_info_v1 *time_info) { struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = time_info, }; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { int error; error = errno; if (error == EPERM) { warn("VMM_DATA_WRITE got EPERM: is " "vmm_allow_state_writes set?"); } errx(EXIT_FAILURE, "VMM_DATA_WRITE of time info failed"); } } /* * Test malformed VMM_DATA_READ time data requests */ static void test_invalid_read_time_data(int vmfd) { struct vdi_time_info_v1 res; /* check error case: invalid vdr_len */ struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = 0, .vdx_data = &res, }; int error; if (ioctl(vmfd, VM_DATA_READ, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_READ of time info should fail"); } error = errno; if (error != ENOSPC) { errx(EXIT_FAILURE, "test_invalid_read_time_data: " "expected ENOSPC errno, got %d", error); } /* expected vdx_result_len should be communicated out */ should_eq_u32("vdx_result_len", xfer.vdx_result_len, sizeof (struct vdi_time_info_v1)); } /* * Test malformed VMM_DATA_WRITE time data requests */ static void test_invalid_write_time_data(int vmfd, struct vdi_time_info_v1 *src) { /* invalid vdx_len */ struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = 0, .vdx_data = src, }; int error; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info should fail"); } error = errno; if (error != ENOSPC) { errx(EXIT_FAILURE, "test_invalid_write_time_data: " "expected ENOSPC errno, got %d", error); } /* expected vdx_result_len should be communicated out */ should_eq_u32("vdx_result_len", xfer.vdx_result_len, sizeof (struct vdi_time_info_v1)); } /* * Test platform-independent invalid frequency ratio requests */ static void test_invalid_freq(int vmfd, struct vdi_time_info_v1 *src) { /* guest frequency of 0 always invalid */ struct vdi_time_info_v1 invalid = { .vt_guest_freq = 0, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &invalid, }; int error; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info (vt_guest_freq = 0) " "should fail"); } error = errno; if (error != EINVAL) { errx(EXIT_FAILURE, "test_invalid_freq: \ expected EINVAL errno, got %d", error); } } /* * Test invalid AMD-specific frequency ratio requests */ static void test_invalid_freq_amd(int vmfd, struct vdi_time_info_v1 *src) { struct vdi_time_info_v1 invalid = { .vt_guest_freq = src->vt_guest_freq, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &invalid, }; int error; /* minimum guest frequency - 1 */ invalid.vt_guest_freq = AMD_TSC_MIN_FREQ - 1; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info " "(min AMD guest freq) should fail"); } error = errno; if (error != EINVAL) { errx(EXIT_FAILURE, "test_invalid_freq_amd (< min freq) " "expected EINVAL errno, got %d", error); } /* ratio >= max ratio */ invalid.vt_guest_freq = src->vt_guest_freq * AMD_TSC_MAX_FREQ_RATIO; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info " "(AMD guest freq ratio too large) should fail"); } error = errno; if (error != EINVAL) { errx(EXIT_FAILURE, "test_invalid_freq_amd (> max freq) " "expected EINVAL errno, got %d", error); } } /* * Test valid AMD-specific frequency ratio requests */ static void test_valid_freq_amd(int vmfd, struct vdi_time_info_v1 *src) { struct vdi_time_info_v1 res; int error; /* minimum frequency */ struct vdi_time_info_v1 valid = { .vt_guest_freq = AMD_TSC_MIN_FREQ, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &valid, }; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { error = errno; errx(EXIT_FAILURE, "valid VMM_DATA_WRITE of time info " "(min AMD guest frequency) should succeed, errno=%d", error); } /* verify the frequency was changed */ test_valid_read_time_data(vmfd, &res); should_eq_u64("vt_guest_freq", res.vt_guest_freq, valid.vt_guest_freq); /* maximum frequency */ valid.vt_guest_freq = src->vt_guest_freq * AMD_TSC_MAX_FREQ_RATIO - 1; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) { error = errno; errx(EXIT_FAILURE, "valid VMM_DATA_WRITE of time info " "(max AMD guest frequency) should succeed, errno=%d", error); } /* verify the frequency was changed */ test_valid_read_time_data(vmfd, &res); should_eq_u64("vt_guest_freq", res.vt_guest_freq, valid.vt_guest_freq); } /* * Test invalid Intel-specific frequency ratio requests */ static void test_invalid_freq_intel(int vmfd, struct vdi_time_info_v1 *src) { /* * As Intel is not currently supported, any frequency that differs from * the host should be rejected. */ struct vdi_time_info_v1 invalid = { .vt_guest_freq = src->vt_guest_freq + 1, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &invalid, }; int error; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info " "(intel scaling required) should fail"); } error = errno; if (error != EPERM) { errx(EXIT_FAILURE, "test_invalid_freq_intel: " "expected EPERM errno, got %d", error); } } /* * Test that an hrtime from the future is not accepted */ static void test_invalid_host_times(int vmfd, struct vdi_time_info_v1 *src) { struct vdi_time_info_v1 invalid = { .vt_guest_freq = src->vt_guest_freq, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &invalid, }; int error; /* hrtime + 500 seconds */ invalid.vt_hrtime += 500000000000; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info " "(hrtime in the future) should fail"); } error = errno; if (error != EINVAL) { errx(EXIT_FAILURE, "test_invalid_host_times: " "expected EINVAL errno, got %d", error); } } /* * Test that a boot_hrtime from the future is not accepted */ static void test_invalid_boot_hrtime(int vmfd, struct vdi_time_info_v1 *src) { struct vdi_time_info_v1 invalid = { .vt_guest_freq = src->vt_guest_freq, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; struct vm_data_xfer xfer = { .vdx_class = VDC_VMM_TIME, .vdx_version = 1, .vdx_len = sizeof (struct vdi_time_info_v1), .vdx_data = &invalid, }; int error; /* boot_hrtime = hrtime + 500 seconds */ invalid.vt_boot_hrtime += src->vt_hrtime + 500000000000; if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) { errx(EXIT_FAILURE, "invalid VMM_DATA_WRITE of time info " "(boot_hrtime in the future) should fail"); } error = errno; if (error != EINVAL) { errx(EXIT_FAILURE, "test_invalid_boot_hrtime: " "expected EINVAL errno, got %d", error); } } /* * Test that a different guest TSC is accepted. There are no constraints on what * this value can be. */ static void test_valid_guest_tsc(int vmfd, struct vdi_time_info_v1 *src) { /* arbitrary guest TSC in the future */ struct vdi_time_info_v1 valid = { .vt_guest_freq = src->vt_guest_freq, .vt_guest_tsc = src->vt_guest_tsc + 500000000000, .vt_boot_hrtime = src->vt_boot_hrtime, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; test_valid_write_time_data(vmfd, &valid); /* read it back */ struct vdi_time_info_v1 res; test_valid_read_time_data(vmfd, &res); /* * The guest TSC may have been adjusted by the kernel, but it should * be at least what was supplied. */ should_geq_u64("vt_guest_tsc", res.vt_guest_tsc, valid.vt_guest_tsc); } /* * Test that a different boot_hrtime is accepted. */ static void test_valid_boot_hrtime(int vmfd, struct vdi_time_info_v1 *src) { struct vdi_time_info_v1 res; /* boot_hrtime < 0 */ struct vdi_time_info_v1 valid = { .vt_guest_freq = src->vt_guest_freq, .vt_guest_tsc = src->vt_guest_tsc, .vt_boot_hrtime = -100000000000, .vt_hrtime = src->vt_hrtime, .vt_hres_sec = src->vt_hres_sec, .vt_hres_ns = src->vt_hres_ns, }; test_valid_write_time_data(vmfd, &valid); /* read it back */ test_valid_read_time_data(vmfd, &res); /* * The boot_hrtime may have been adjusted by the kernel, but it should * be at least what was supplied. */ should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime); /* repeat for boot_hrtime = 0 */ valid.vt_boot_hrtime = 0; test_valid_write_time_data(vmfd, &valid); test_valid_read_time_data(vmfd, &res); should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime); /* repeat for boot_hrtime > 0 */ valid.vt_boot_hrtime = src->vt_boot_hrtime + 1; test_valid_write_time_data(vmfd, &valid); test_valid_read_time_data(vmfd, &res); should_geq_i64("boot_hrtime", res.vt_boot_hrtime, valid.vt_boot_hrtime); } /* * Coarsely test that interface is making adjustments to the host times and * guest time values. */ static void test_adjust(int vmfd, struct vdi_time_info_v1 *src) { struct vdi_time_info_v1 res; test_valid_write_time_data(vmfd, src); /* read it back */ test_valid_read_time_data(vmfd, &res); /* * hrtime, hrestime, and guest TSC should all have moved forward */ should_geq_i64("vt_hrtime", res.vt_hrtime, src->vt_hrtime); if (src->vt_hres_sec == res.vt_hres_sec) { /* ns should be higher */ should_geq_u64("vt_hres_ns", res.vt_hres_ns, src->vt_hres_ns); } else if (src->vt_hres_sec > res.vt_hres_sec) { errx(EXIT_FAILURE, "test_adjust: hrestime went backwards"); } should_geq_u64("vt_guest_tsc", res.vt_guest_tsc, src->vt_guest_tsc); } int main(int argc, char *argv[]) { const char *suite_name = basename(argv[0]); struct vmctx *ctx; struct vcpu *vcpu; ctx = create_test_vm(suite_name); if (ctx == NULL) { errx(EXIT_FAILURE, "could not open test VM"); } if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { err(EXIT_FAILURE, "Could not open vcpu0"); } if (vm_activate_cpu(vcpu) != 0) { err(EXIT_FAILURE, "could not activate vcpu0"); } const int vmfd = vm_get_device_fd(ctx); const bool is_svm = cpu_vendor_amd(); struct vdi_time_info_v1 time_info; /* * Reads */ /* do a valid read */ test_valid_read_time_data(vmfd, &time_info); /* malformed read request */ test_invalid_read_time_data(vmfd); /* * Writes * * For the test writes, we reuse the data from the successful read, * and change the request parameters as necessary to test specific * behavior. This is sufficient for testing validation of individual * parameters, as writing the exact data back from a read is allowed. * * The only platform-specific behavior is around changing the guest * TSC frequency. If the guest frequency is the same as the host's, * as it is for all VMs at boot, then no scaling is required, and thus * the CPU vendor of the system, or its capability to scale a guest TSC, * does not matter. */ /* try writing back the data from the read */ test_valid_write_time_data(vmfd, &time_info); /* malformed write request */ test_invalid_write_time_data(vmfd, &time_info); /* invalid host time requests */ test_invalid_host_times(vmfd, &time_info); /* invalid guest frequency requests */ test_invalid_freq(vmfd, &time_info); if (is_svm) { test_invalid_freq_amd(vmfd, &time_info); } else { test_invalid_freq_intel(vmfd, &time_info); } /* invalid boot_hrtime request */ test_invalid_boot_hrtime(vmfd, &time_info); /* valid frequency scaling requests */ if (is_svm) { test_valid_freq_amd(vmfd, &time_info); } /* valid guest TSC values */ test_valid_guest_tsc(vmfd, &time_info); /* valid boot_hrtime values */ test_valid_boot_hrtime(vmfd, &time_info); /* observe that host times and guest data are updated after a write */ test_adjust(vmfd, &time_info); vm_vcpu_close(vcpu); vm_destroy(ctx); (void) printf("%s\tPASS\n", suite_name); return (EXIT_SUCCESS); }