diff options
Diffstat (limited to 'usr')
-rw-r--r-- | usr/src/pkg/manifests/system-bhyve-tests.p5m | 1 | ||||
-rw-r--r-- | usr/src/test/bhyve-tests/runfiles/default.run | 1 | ||||
-rw-r--r-- | usr/src/test/bhyve-tests/tests/inst_emul/Makefile | 1 | ||||
-rw-r--r-- | usr/src/test/bhyve-tests/tests/inst_emul/imul.c | 115 | ||||
-rw-r--r-- | usr/src/test/bhyve-tests/tests/inst_emul/payload_imul.s | 59 | ||||
-rw-r--r-- | usr/src/uts/intel/io/vmm/vmm_instruction_emul.c | 139 |
6 files changed, 316 insertions, 0 deletions
diff --git a/usr/src/pkg/manifests/system-bhyve-tests.p5m b/usr/src/pkg/manifests/system-bhyve-tests.p5m index 4dbd417557..9c98c34969 100644 --- a/usr/src/pkg/manifests/system-bhyve-tests.p5m +++ b/usr/src/pkg/manifests/system-bhyve-tests.p5m @@ -33,6 +33,7 @@ dir path=opt/bhyve-tests/tests dir path=opt/bhyve-tests/tests/inst_emul file path=opt/bhyve-tests/tests/inst_emul/cpuid mode=0555 file path=opt/bhyve-tests/tests/inst_emul/exit_paging mode=0555 +file path=opt/bhyve-tests/tests/inst_emul/imul mode=0555 file path=opt/bhyve-tests/tests/inst_emul/rdmsr mode=0555 file path=opt/bhyve-tests/tests/inst_emul/triple_fault mode=0555 file path=opt/bhyve-tests/tests/inst_emul/wrmsr mode=0555 diff --git a/usr/src/test/bhyve-tests/runfiles/default.run b/usr/src/test/bhyve-tests/runfiles/default.run index f68ac6c719..f424652655 100644 --- a/usr/src/test/bhyve-tests/runfiles/default.run +++ b/usr/src/test/bhyve-tests/runfiles/default.run @@ -53,6 +53,7 @@ tests = [ user = root tests = [ 'cpuid', + 'imul', 'rdmsr', 'wrmsr', 'triple_fault', diff --git a/usr/src/test/bhyve-tests/tests/inst_emul/Makefile b/usr/src/test/bhyve-tests/tests/inst_emul/Makefile index 023a416ae9..37a8b62b4f 100644 --- a/usr/src/test/bhyve-tests/tests/inst_emul/Makefile +++ b/usr/src/test/bhyve-tests/tests/inst_emul/Makefile @@ -17,6 +17,7 @@ include $(SRC)/test/Makefile.com PROG = rdmsr \ wrmsr \ + imul \ cpuid # These should probably go in the `vmm` tests, but since they depend on diff --git a/usr/src/test/bhyve-tests/tests/inst_emul/imul.c b/usr/src/test/bhyve-tests/tests/inst_emul/imul.c new file mode 100644 index 0000000000..d75cc67592 --- /dev/null +++ b/usr/src/test/bhyve-tests/tests/inst_emul/imul.c @@ -0,0 +1,115 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2022 Oxide Computer Company + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <strings.h> +#include <libgen.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/sysmacros.h> +#include <sys/debug.h> +#include <sys/vmm.h> +#include <sys/vmm_dev.h> +#include <vmmapi.h> + +#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; + int err; + + ctx = test_initialize(test_suite_name); + + err = test_setup_vcpu(ctx, 0, 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(ctx, 0, &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); +} diff --git a/usr/src/test/bhyve-tests/tests/inst_emul/payload_imul.s b/usr/src/test/bhyve-tests/tests/inst_emul/payload_imul.s new file mode 100644 index 0000000000..44da654ea6 --- /dev/null +++ b/usr/src/test/bhyve-tests/tests/inst_emul/payload_imul.s @@ -0,0 +1,59 @@ +/* + * 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 <sys/asm_linkage.h> +#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) diff --git a/usr/src/uts/intel/io/vmm/vmm_instruction_emul.c b/usr/src/uts/intel/io/vmm/vmm_instruction_emul.c index 14c78f411d..9fbb923cd9 100644 --- a/usr/src/uts/intel/io/vmm/vmm_instruction_emul.c +++ b/usr/src/uts/intel/io/vmm/vmm_instruction_emul.c @@ -182,6 +182,7 @@ enum { VIE_OP_TYPE_TEST, VIE_OP_TYPE_BEXTR, VIE_OP_TYPE_CLTS, + VIE_OP_TYPE_MUL, VIE_OP_TYPE_LAST }; @@ -220,6 +221,10 @@ static const struct vie_op two_byte_opcodes[256] = { .op_byte = 0xAE, .op_type = VIE_OP_TYPE_TWOB_GRP15, }, + [0xAF] = { + .op_byte = 0xAF, + .op_type = VIE_OP_TYPE_MUL, + }, [0xB6] = { .op_byte = 0xB6, .op_type = VIE_OP_TYPE_MOVZX, @@ -419,6 +424,25 @@ static enum vm_reg_name gpr_map[16] = { VM_REG_GUEST_R15 }; +static const char *gpr_name_map[][16] = { + [1] = { + "a[hl]", "c[hl]", "d[hl]", "b[hl]", "spl", "bpl", "sil", "dil", + "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b", + }, + [2] = { + "ax", "cx", "dx", "bx", "sp", "bp", "si", "di", + "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w", + }, + [4] = { + "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", + "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d", + }, + [8] = { + "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + }, +}; + static enum vm_reg_name cr_map[16] = { VM_REG_GUEST_CR0, VM_REG_LAST, @@ -477,6 +501,14 @@ vie_regnum_map(uint8_t regnum) return (gpr_map[regnum]); } +const char * +vie_regnum_name(uint8_t regnum, uint8_t size) +{ + VERIFY3U(regnum, <, 16); + VERIFY(size == 1 || size == 2 || size == 4 || size == 8); + return (gpr_name_map[size][regnum]); +} + static void vie_calc_bytereg(struct vie *vie, enum vm_reg_name *reg, int *lhbr) { @@ -679,6 +711,40 @@ getaddflags(int opsize, uint64_t x, uint64_t y) } /* + * Macro creation of functions getimulflags{16,32,64} + */ +/* BEGIN CSTYLED */ +#define GETIMULFLAGS(sz) \ +static ulong_t \ +getimulflags##sz(uint##sz##_t x, uint##sz##_t y) \ +{ \ + ulong_t rflags; \ + \ + __asm __volatile("imul %2,%1; pushfq; popq %0" : \ + "=r" (rflags), "+r" (x) : "m" (y)); \ + return (rflags); \ +} struct __hack +/* END CSTYLED */ + +GETIMULFLAGS(16); +GETIMULFLAGS(32); +GETIMULFLAGS(64); + +static ulong_t +getimulflags(int opsize, uint64_t x, uint64_t y) +{ + KASSERT(opsize == 2 || opsize == 4 || opsize == 8, + ("getimulflags: invalid operand size %d", opsize)); + + if (opsize == 2) + return (getimulflags16(x, y)); + else if (opsize == 4) + return (getimulflags32(x, y)); + else + return (getimulflags64(x, y)); +} + +/* * Return the status flags that would result from doing (x & y). */ /* BEGIN CSTYLED */ @@ -1843,6 +1909,76 @@ vie_emulate_sub(struct vie *vie, struct vm *vm, int vcpuid, uint64_t gpa) } static int +vie_emulate_mul(struct vie *vie, struct vm *vm, int vcpuid, uint64_t gpa) +{ + int error, size; + uint64_t rflags, rflags2, val1, val2; + __int128_t nval; + enum vm_reg_name reg; + ulong_t (*getflags)(int, uint64_t, uint64_t) = NULL; + + size = vie->opsize; + error = EINVAL; + + switch (vie->op.op_byte) { + case 0xAF: + /* + * Multiply the contents of a destination register by + * the contents of a register or memory operand and + * put the signed result in the destination register. + * + * AF/r IMUL r16, r/m16 + * AF/r IMUL r32, r/m32 + * REX.W + AF/r IMUL r64, r/m64 + */ + + getflags = getimulflags; + + /* get the first operand */ + reg = gpr_map[vie->reg]; + error = vm_get_register(vm, vcpuid, reg, &val1); + if (error != 0) + break; + + /* get the second operand */ + error = vie_mmio_read(vie, vm, vcpuid, gpa, &val2, size); + if (error != 0) + break; + + /* perform the operation and write the result */ + nval = (int64_t)val1 * (int64_t)val2; + + error = vie_update_register(vm, vcpuid, reg, nval, size); + + DTRACE_PROBE4(vie__imul, + const char *, vie_regnum_name(vie->reg, size), + uint64_t, val1, uint64_t, val2, __uint128_t, nval); + + break; + default: + break; + } + + if (error == 0) { + rflags2 = getflags(size, val1, val2); + error = vm_get_register(vm, vcpuid, VM_REG_GUEST_RFLAGS, + &rflags); + if (error) + return (error); + + rflags &= ~RFLAGS_STATUS_BITS; + rflags |= rflags2 & RFLAGS_STATUS_BITS; + error = vie_update_register(vm, vcpuid, VM_REG_GUEST_RFLAGS, + rflags, 8); + + DTRACE_PROBE2(vie__imul__rflags, + uint64_t, rflags, uint64_t, rflags2); + } + + return (error); +} + +static int vie_emulate_stack_op(struct vie *vie, struct vm *vm, int vcpuid, uint64_t gpa) { struct vm_copyinfo copyinfo[2]; @@ -2244,6 +2380,9 @@ vie_emulate_mmio(struct vie *vie, struct vm *vm, int vcpuid) case VIE_OP_TYPE_BEXTR: error = vie_emulate_bextr(vie, vm, vcpuid, gpa); break; + case VIE_OP_TYPE_MUL: + error = vie_emulate_mul(vie, vm, vcpuid, gpa); + break; default: error = EINVAL; break; |