summaryrefslogtreecommitdiff
path: root/usr
diff options
context:
space:
mode:
Diffstat (limited to 'usr')
-rw-r--r--usr/src/pkg/manifests/system-bhyve-tests.p5m1
-rw-r--r--usr/src/test/bhyve-tests/runfiles/default.run1
-rw-r--r--usr/src/test/bhyve-tests/tests/inst_emul/Makefile1
-rw-r--r--usr/src/test/bhyve-tests/tests/inst_emul/imul.c115
-rw-r--r--usr/src/test/bhyve-tests/tests/inst_emul/payload_imul.s59
-rw-r--r--usr/src/uts/intel/io/vmm/vmm_instruction_emul.c139
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;