svn commit: r266573 - in head: sys/amd64/include sys/amd64/vmm sys/amd64/vmm/intel usr.sbin/bhyve

Neel Natu neel at FreeBSD.org
Fri May 23 05:15:20 UTC 2014


Author: neel
Date: Fri May 23 05:15:17 2014
New Revision: 266573
URL: http://svnweb.freebsd.org/changeset/base/266573

Log:
  Add emulation of the "outsb" instruction. NetBSD guests use this to write to
  the UART FIFO.
  
  The emulation is constrained in a number of ways: 64-bit only, doesn't check
  for all exception conditions, limited to i/o ports emulated in userspace.
  
  Some of these constraints will be relaxed in followup commits.
  
  Requested by:	grehan
  Reviewed by:	tychon (partially and a much earlier version)

Modified:
  head/sys/amd64/include/vmm.h
  head/sys/amd64/include/vmm_instruction_emul.h
  head/sys/amd64/vmm/intel/vmx.c
  head/sys/amd64/vmm/vmm.c
  head/sys/amd64/vmm/vmm_instruction_emul.c
  head/sys/amd64/vmm/vmm_ioport.c
  head/sys/amd64/vmm/vmm_ioport.h
  head/sys/amd64/vmm/vmm_ktr.h
  head/usr.sbin/bhyve/bhyverun.c
  head/usr.sbin/bhyve/inout.c
  head/usr.sbin/bhyve/inout.h

Modified: head/sys/amd64/include/vmm.h
==============================================================================
--- head/sys/amd64/include/vmm.h	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/include/vmm.h	Fri May 23 05:15:17 2014	(r266573)
@@ -54,6 +54,7 @@ struct vmspace;
 struct vm_object;
 struct pmap;
 
+enum vm_reg_name;
 enum x2apic_state;
 
 typedef int	(*vmm_init_func_t)(int ipinum);
@@ -238,6 +239,8 @@ void vm_inject_gp(struct vm *vm, int vcp
 void vm_inject_ud(struct vm *vm, int vcpuid); /* undefined instruction fault */
 void vm_inject_pf(struct vm *vm, int vcpuid, int error_code); /* page fault */
 
+enum vm_reg_name vm_segment_name(int seg_encoding);
+
 #endif	/* KERNEL */
 
 #include <machine/vmm_instruction_emul.h>
@@ -336,22 +339,43 @@ enum vm_exitcode {
 	VM_EXITCODE_RENDEZVOUS,
 	VM_EXITCODE_IOAPIC_EOI,
 	VM_EXITCODE_SUSPENDED,
+	VM_EXITCODE_INOUT_STR,
 	VM_EXITCODE_MAX
 };
 
+struct vm_inout {
+	uint16_t	bytes:3;	/* 1 or 2 or 4 */
+	uint16_t	in:1;
+	uint16_t	string:1;
+	uint16_t	rep:1;
+	uint16_t	port;
+	uint32_t	eax;		/* valid for out */
+};
+
+struct vm_inout_str {
+	struct vm_inout	inout;		/* must be the first element */
+	enum vie_cpu_mode cpu_mode;
+	enum vie_paging_mode paging_mode;
+	uint64_t	rflags;
+	uint64_t	cr0;
+	uint64_t	cr3;
+	uint64_t	index;
+	uint64_t	count;		/* rep=1 (%rcx), rep=0 (1) */
+	int		cpl;
+	int		addrsize;
+	enum vm_reg_name seg_name;
+	struct seg_desc seg_desc;
+	uint64_t	gla;		/* may be set to VIE_INVALID_GLA */
+	uint64_t	gpa;
+};
+
 struct vm_exit {
 	enum vm_exitcode	exitcode;
 	int			inst_length;	/* 0 means unknown */
 	uint64_t		rip;
 	union {
-		struct {
-			uint16_t	bytes:3;	/* 1 or 2 or 4 */
-			uint16_t	in:1;		/* out is 0, in is 1 */
-			uint16_t	string:1;
-			uint16_t	rep:1;
-			uint16_t	port;
-			uint32_t	eax;		/* valid for out */
-		} inout;
+		struct vm_inout	inout;
+		struct vm_inout_str inout_str;
 		struct {
 			uint64_t	gpa;
 			int		fault_type;

Modified: head/sys/amd64/include/vmm_instruction_emul.h
==============================================================================
--- head/sys/amd64/include/vmm_instruction_emul.h	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/include/vmm_instruction_emul.h	Fri May 23 05:15:17 2014	(r266573)
@@ -29,6 +29,8 @@
 #ifndef	_VMM_INSTRUCTION_EMUL_H_
 #define	_VMM_INSTRUCTION_EMUL_H_
 
+enum vm_reg_name;
+
 enum vie_cpu_mode {
 	CPU_MODE_COMPATIBILITY,		/* IA-32E mode (CS.L = 0) */
 	CPU_MODE_64BIT,			/* IA-32E mode (CS.L = 1) */
@@ -111,6 +113,9 @@ int vmm_emulate_instruction(void *vm, in
 			    mem_region_read_t mrr, mem_region_write_t mrw,
 			    void *mrarg);
 
+int vie_update_register(void *vm, int vcpuid, enum vm_reg_name reg,
+    uint64_t val, int size);
+
 #ifdef _KERNEL
 /*
  * APIs to fetch and decode the instruction from nested page fault handler.
@@ -134,6 +139,11 @@ int vmm_gla2gpa(struct vm *vm, int vcpui
 
 void vie_init(struct vie *vie);
 
+uint64_t vie_size2mask(int size);
+
+uint64_t vie_segbase(enum vm_reg_name segment, enum vie_cpu_mode cpu_mode,
+    const struct seg_desc *desc);
+
 /*
  * Decode the instruction fetched into 'vie' so it can be emulated.
  *

Modified: head/sys/amd64/vmm/intel/vmx.c
==============================================================================
--- head/sys/amd64/vmm/intel/vmx.c	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/vmm/intel/vmx.c	Fri May 23 05:15:17 2014	(r266573)
@@ -185,6 +185,8 @@ SYSCTL_UINT(_hw_vmm_vmx, OID_AUTO, vpid_
  */
 #define	APIC_ACCESS_ADDRESS	0xFFFFF000
 
+static int vmx_getdesc(void *arg, int vcpu, int reg, struct seg_desc *desc);
+static int vmx_getreg(void *arg, int vcpu, int reg, uint64_t *retval);
 static void vmx_inject_pir(struct vlapic *vlapic);
 
 #ifdef KTR
@@ -530,7 +532,7 @@ static int
 vmx_init(int ipinum)
 {
 	int error, use_tpr_shadow;
-	uint64_t fixed0, fixed1, feature_control;
+	uint64_t basic, fixed0, fixed1, feature_control;
 	uint32_t tmp, procbased2_vid_bits;
 
 	/* CPUID.1:ECX[bit 5] must be 1 for processor to support VMX */
@@ -550,6 +552,17 @@ vmx_init(int ipinum)
 		return (ENXIO);
 	}
 
+	/*
+	 * Verify capabilities MSR_VMX_BASIC:
+	 * - bit 54 indicates support for INS/OUTS decoding
+	 */
+	basic = rdmsr(MSR_VMX_BASIC);
+	if ((basic & (1UL << 54)) == 0) {
+		printf("vmx_init: processor does not support desired basic "
+		    "capabilities\n");
+		return (EINVAL);
+	}
+
 	/* Check support for primary processor-based VM-execution controls */
 	error = vmx_set_ctlreg(MSR_VMX_PROCBASED_CTLS,
 			       MSR_VMX_TRUE_PROCBASED_CTLS,
@@ -1528,6 +1541,71 @@ vmx_paging_mode(void)
 		return (PAGING_MODE_PAE);
 }
 
+static uint64_t
+inout_str_index(struct vmx *vmx, int vcpuid, int in)
+{
+	uint64_t val;
+	int error;
+	enum vm_reg_name reg;
+
+	reg = in ? VM_REG_GUEST_RDI : VM_REG_GUEST_RSI;
+	error = vmx_getreg(vmx, vcpuid, reg, &val);
+	KASSERT(error == 0, ("%s: vmx_getreg error %d", __func__, error));
+	return (val);
+}
+
+static uint64_t
+inout_str_count(struct vmx *vmx, int vcpuid, int rep)
+{
+	uint64_t val;
+	int error;
+
+	if (rep) {
+		error = vmx_getreg(vmx, vcpuid, VM_REG_GUEST_RCX, &val);
+		KASSERT(!error, ("%s: vmx_getreg error %d", __func__, error));
+	} else {
+		val = 1;
+	}
+	return (val);
+}
+
+static int
+inout_str_addrsize(uint32_t inst_info)
+{
+	uint32_t size;
+
+	size = (inst_info >> 7) & 0x7;
+	switch (size) {
+	case 0:
+		return (2);	/* 16 bit */
+	case 1:
+		return (4);	/* 32 bit */
+	case 2:
+		return (8);	/* 64 bit */
+	default:
+		panic("%s: invalid size encoding %d", __func__, size);
+	}
+}
+
+static void
+inout_str_seginfo(struct vmx *vmx, int vcpuid, uint32_t inst_info, int in,
+    struct vm_inout_str *vis)
+{
+	int error, s;
+
+	if (in) {
+		vis->seg_name = VM_REG_GUEST_ES;
+	} else {
+		s = (inst_info >> 15) & 0x7;
+		vis->seg_name = vm_segment_name(s);
+	}
+
+	error = vmx_getdesc(vmx, vcpuid, vis->seg_name, &vis->seg_desc);
+	KASSERT(error == 0, ("%s: vmx_getdesc error %d", __func__, error));
+
+	/* XXX modify svm.c to update bit 16 of seg_desc.access (unusable) */
+}
+
 static void
 vmexit_inst_emul(struct vm_exit *vmexit, uint64_t gpa, uint64_t gla)
 {
@@ -1749,10 +1827,12 @@ vmx_handle_apic_access(struct vmx *vmx, 
 static int
 vmx_exit_process(struct vmx *vmx, int vcpu, struct vm_exit *vmexit)
 {
-	int error, handled;
+	int error, handled, in;
 	struct vmxctx *vmxctx;
 	struct vlapic *vlapic;
-	uint32_t eax, ecx, edx, idtvec_info, idtvec_err, intr_info, reason;
+	struct vm_inout_str *vis;
+	uint32_t eax, ecx, edx, idtvec_info, idtvec_err, intr_info, inst_info;
+	uint32_t reason;
 	uint64_t qual, gpa;
 	bool retu;
 
@@ -1909,15 +1989,26 @@ vmx_exit_process(struct vmx *vmx, int vc
 		vmm_stat_incr(vmx->vm, vcpu, VMEXIT_INOUT, 1);
 		vmexit->exitcode = VM_EXITCODE_INOUT;
 		vmexit->u.inout.bytes = (qual & 0x7) + 1;
-		vmexit->u.inout.in = (qual & 0x8) ? 1 : 0;
+		vmexit->u.inout.in = in = (qual & 0x8) ? 1 : 0;
 		vmexit->u.inout.string = (qual & 0x10) ? 1 : 0;
 		vmexit->u.inout.rep = (qual & 0x20) ? 1 : 0;
 		vmexit->u.inout.port = (uint16_t)(qual >> 16);
 		vmexit->u.inout.eax = (uint32_t)(vmxctx->guest_rax);
-		error = emulate_ioport(vmx->vm, vcpu, vmexit);
-		if (error == 0)  {
-			handled = 1;
-			vmxctx->guest_rax = vmexit->u.inout.eax;
+		if (vmexit->u.inout.string) {
+			inst_info = vmcs_read(VMCS_EXIT_INSTRUCTION_INFO);
+			vmexit->exitcode = VM_EXITCODE_INOUT_STR;
+			vis = &vmexit->u.inout_str;
+			vis->cpu_mode = vmx_cpu_mode();
+			vis->paging_mode = vmx_paging_mode();
+			vis->rflags = vmcs_read(VMCS_GUEST_RFLAGS);
+			vis->cr0 = vmcs_read(VMCS_GUEST_CR0);
+			vis->cr3 = vmcs_read(VMCS_GUEST_CR3);
+			vis->cpl = vmx_cpl();
+			vis->index = inout_str_index(vmx, vcpu, in);
+			vis->count = inout_str_count(vmx, vcpu, vis->inout.rep);
+			vis->addrsize = inout_str_addrsize(inst_info);
+			inout_str_seginfo(vmx, vcpu, inst_info, in, vis);
+			vis->gla = vmcs_gla();
 		}
 		break;
 	case EXIT_REASON_CPUID:

Modified: head/sys/amd64/vmm/vmm.c
==============================================================================
--- head/sys/amd64/vmm/vmm.c	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/vmm/vmm.c	Fri May 23 05:15:17 2014	(r266573)
@@ -63,6 +63,7 @@ __FBSDID("$FreeBSD$");
 #include <machine/vmm.h>
 #include <machine/vmm_dev.h>
 
+#include "vmm_ioport.h"
 #include "vmm_ktr.h"
 #include "vmm_host.h"
 #include "vmm_mem.h"
@@ -1354,6 +1355,10 @@ restart:
 		case VM_EXITCODE_INST_EMUL:
 			error = vm_handle_inst_emul(vm, vcpuid, &retu);
 			break;
+		case VM_EXITCODE_INOUT:
+		case VM_EXITCODE_INOUT_STR:
+			error = vm_handle_inout(vm, vcpuid, vme, &retu);
+			break;
 		default:
 			retu = true;	/* handled in userland */
 			break;
@@ -1874,3 +1879,20 @@ vm_atpit(struct vm *vm)
 {
 	return (vm->vatpit);
 }
+
+enum vm_reg_name
+vm_segment_name(int seg)
+{
+	static enum vm_reg_name seg_names[] = {
+		VM_REG_GUEST_ES,
+		VM_REG_GUEST_CS,
+		VM_REG_GUEST_SS,
+		VM_REG_GUEST_DS,
+		VM_REG_GUEST_FS,
+		VM_REG_GUEST_GS
+	};
+
+	KASSERT(seg >= 0 && seg < nitems(seg_names),
+	    ("%s: invalid segment encoding %d", __func__, seg));
+	return (seg_names[seg]);
+}

Modified: head/sys/amd64/vmm/vmm_instruction_emul.c
==============================================================================
--- head/sys/amd64/vmm/vmm_instruction_emul.c	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/vmm/vmm_instruction_emul.c	Fri May 23 05:15:17 2014	(r266573)
@@ -206,7 +206,7 @@ vie_read_bytereg(void *vm, int vcpuid, s
 	return (error);
 }
 
-static int
+int
 vie_update_register(void *vm, int vcpuid, enum vm_reg_name reg,
 		    uint64_t val, int size)
 {
@@ -1218,4 +1218,50 @@ vmm_decode_instruction(struct vm *vm, in
 
 	return (0);
 }
+
+uint64_t
+vie_size2mask(int size)
+{
+	KASSERT(size == 1 || size == 2 || size == 4 || size == 8,
+	    ("vie_size2mask: invalid size %d", size));
+	return (size2mask[size]);
+}
+
+uint64_t
+vie_segbase(enum vm_reg_name seg, enum vie_cpu_mode cpu_mode,
+    const struct seg_desc *desc)
+{
+	int basesize;
+
+	basesize = 4;	/* default segment width in bytes */
+
+	switch (seg) {
+	case VM_REG_GUEST_ES:
+	case VM_REG_GUEST_CS:
+	case VM_REG_GUEST_SS:
+	case VM_REG_GUEST_DS:
+		if (cpu_mode == CPU_MODE_64BIT) {
+			/*
+			 * Segments having an implicit base address of 0
+			 * in 64-bit mode.
+			 */
+			return (0);
+		}
+		break;
+	case VM_REG_GUEST_FS:
+	case VM_REG_GUEST_GS:
+		if (cpu_mode == CPU_MODE_64BIT) {
+			/*
+			 * In 64-bit mode the FS and GS base address is 8 bytes
+			 * wide.
+			 */
+			basesize = 8;
+		}
+		break;
+	default:
+		panic("%s: invalid segment register %d", __func__, seg);
+	}
+
+	return (desc->base & size2mask[basesize]);
+}
 #endif	/* _KERNEL */

Modified: head/sys/amd64/vmm/vmm_ioport.c
==============================================================================
--- head/sys/amd64/vmm/vmm_ioport.c	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/vmm/vmm_ioport.c	Fri May 23 05:15:17 2014	(r266573)
@@ -33,11 +33,15 @@ __FBSDID("$FreeBSD$");
 #include <sys/cpuset.h>
 #include <sys/systm.h>
 
+#include <vm/vm.h>
+
 #include <machine/vmm.h>
+#include <x86/psl.h>
 
 #include "vatpic.h"
 #include "vatpit.h"
 #include "vmm_ioport.h"
+#include "vmm_ktr.h"
 
 #define	MAX_IOPORTS		1280
 
@@ -55,32 +59,64 @@ ioport_handler_func_t ioport_handler[MAX
 	[IO_ELCR2] = vatpic_elc_handler,
 };
 
-int
-emulate_ioport(struct vm *vm, int vcpuid, struct vm_exit *vmexit)
+#ifdef KTR
+static const char *
+inout_instruction(struct vm_exit *vmexit)
 {
-	ioport_handler_func_t handler;
-	uint32_t mask, val;
-	int error;
+	int index;
 
-	if (vmexit->u.inout.port >= MAX_IOPORTS)
-		return (-1);
-
-	handler = ioport_handler[vmexit->u.inout.port];
-	if (handler == NULL)
-		return (-1);
+	static const char *iodesc[] = {
+		"outb", "outw", "outl",
+		"inb", "inw", "inl",
+		"outsb", "outsw", "outsd"
+		"insb", "insw", "insd",
+	};
 
 	switch (vmexit->u.inout.bytes) {
 	case 1:
-		mask = 0xff;
+		index = 0;
 		break;
 	case 2:
-		mask = 0xffff;
+		index = 1;
 		break;
 	default:
-		mask = 0xffffffff;
+		index = 2;
 		break;
 	}
 
+	if (vmexit->u.inout.in)
+		index += 3;
+
+	if (vmexit->u.inout.string)
+		index += 6;
+
+	KASSERT(index < nitems(iodesc), ("%s: invalid index %d",
+	    __func__, index));
+
+	return (iodesc[index]);
+}
+#endif	/* KTR */
+
+static int
+emulate_inout_port(struct vm *vm, int vcpuid, struct vm_exit *vmexit,
+    bool *retu)
+{
+	ioport_handler_func_t handler;
+	uint32_t mask, val;
+	int error;
+
+	error = 0;
+	*retu = true;
+
+	if (vmexit->u.inout.port >= MAX_IOPORTS)
+		goto done;
+
+	handler = ioport_handler[vmexit->u.inout.port];
+	if (handler == NULL)
+		goto done;
+
+	mask = vie_size2mask(vmexit->u.inout.bytes);
+
 	if (!vmexit->u.inout.in) {
 		val = vmexit->u.inout.eax & mask;
 	}
@@ -88,10 +124,121 @@ emulate_ioport(struct vm *vm, int vcpuid
 	error = (*handler)(vm, vcpuid, vmexit->u.inout.in,
 	    vmexit->u.inout.port, vmexit->u.inout.bytes, &val);
 
-	if (!error && vmexit->u.inout.in) {
-		vmexit->u.inout.eax &= ~mask;
-		vmexit->u.inout.eax |= val & mask;
+	if (!error) {
+		*retu = false;
+		if (vmexit->u.inout.in) {
+			vmexit->u.inout.eax &= ~mask;
+			vmexit->u.inout.eax |= val & mask;
+			error = vm_set_register(vm, vcpuid,
+			    VM_REG_GUEST_RAX, vmexit->u.inout.eax);
+			KASSERT(error == 0, ("emulate_ioport: error %d "
+			    "setting guest rax register", error));
+		}
+	}
+done:
+	return (error);
+}
+
+static int
+emulate_inout_str(struct vm *vm, int vcpuid, struct vm_exit *vmexit, bool *retu)
+{
+	struct vm_inout_str *vis;
+	uint64_t gla, index, segbase;
+	int bytes, error, in;
+
+	vis = &vmexit->u.inout_str;
+	in = vis->inout.in;
+
+	/*
+	 * ins/outs VM exit takes precedence over the following error
+	 * conditions that would ordinarily be checked by the processor:
+	 *
+	 * - #GP(0) due to segment being unusable.
+	 * - #GP(0) due to memory operand effective address outside the limit
+	 *   of the segment.
+	 * - #AC(0) if alignment checking is enabled and an unaligned memory
+	 *   reference is made at CPL=3
+	 */
+
+	/*
+	 * XXX
+	 * inout string emulation only supported in 64-bit mode and only
+	 * for byte instructions.
+	 *
+	 * The #GP(0) fault conditions described above don't apply in
+	 * 64-bit mode.
+	 *
+	 * The #AC(0) fault condition described above does not apply
+	 * because byte accesses don't have alignment constraints.
+	 */
+	if (vis->cpu_mode != CPU_MODE_64BIT) { 
+		VCPU_CTR1(vm, vcpuid, "ins/outs not emulated in cpu mode %d",
+		    vis->cpu_mode);
+		return (EINVAL);
+	}
+
+	bytes = vis->inout.bytes;
+	if (bytes != 1) {
+		VCPU_CTR1(vm, vcpuid, "ins/outs operand size %d not supported",
+		    bytes);
+		return (EINVAL);
 	}
 
+	/*
+	 * XXX insb/insw/insd instructions not emulated at this time.
+	 */
+	if (in) {
+		VCPU_CTR0(vm, vcpuid, "ins emulation not implemented");
+		return (EINVAL);
+	}
+
+	segbase = vie_segbase(vis->seg_name, vis->cpu_mode, &vis->seg_desc);
+	index = vis->index & vie_size2mask(vis->addrsize);
+	gla = segbase + index;
+
+	/*
+	 * Verify that the computed linear address matches with the one
+	 * provided by hardware.
+	 */
+	if (vis->gla != VIE_INVALID_GLA) {
+		KASSERT(gla == vis->gla, ("%s: gla mismatch "
+		    "%#lx/%#lx", __func__, gla, vis->gla));
+	}
+	vis->gla = gla;
+
+	error = vmm_gla2gpa(vm, vcpuid, gla, vis->cr3, &vis->gpa,
+	    vis->paging_mode, vis->cpl, in ? VM_PROT_WRITE : VM_PROT_READ);
+	KASSERT(error == 0 || error == 1 || error == -1,
+	    ("%s: vmm_gla2gpa unexpected error %d", __func__, error));
+	if (error == -1) {
+		return (EFAULT);
+	} else if (error == 1) {
+		return (0);	/* Resume guest to handle page fault */
+	} else {
+		*retu = true;
+		return (0);	/* Return to userspace to finish emulation */
+	}
+}
+
+int
+vm_handle_inout(struct vm *vm, int vcpuid, struct vm_exit *vmexit, bool *retu)
+{
+	int bytes, error;
+
+	bytes = vmexit->u.inout.bytes;
+	KASSERT(bytes == 1 || bytes == 2 || bytes == 4,
+	    ("vm_handle_inout: invalid operand size %d", bytes));
+
+	if (vmexit->u.inout.string)
+		error = emulate_inout_str(vm, vcpuid, vmexit, retu);
+	else
+		error = emulate_inout_port(vm, vcpuid, vmexit, retu);
+
+	VCPU_CTR4(vm, vcpuid, "%s%s 0x%04x: %s",
+	    vmexit->u.inout.rep ? "rep " : "",
+	    inout_instruction(vmexit),
+	    vmexit->u.inout.port,
+	    error ? "error" : (*retu ? "userspace" : "handled"));
+
 	return (error);
 }

Modified: head/sys/amd64/vmm/vmm_ioport.h
==============================================================================
--- head/sys/amd64/vmm/vmm_ioport.h	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/vmm/vmm_ioport.h	Fri May 23 05:15:17 2014	(r266573)
@@ -32,6 +32,6 @@
 typedef int (*ioport_handler_func_t)(void *vm, int vcpuid,
     bool in, int port, int bytes, uint32_t *val);
 
-int emulate_ioport(struct vm *vm, int vcpuid, struct vm_exit *vmexit);
+int vm_handle_inout(struct vm *vm, int vcpuid, struct vm_exit *vme, bool *retu);
 
 #endif	/* _VMM_IOPORT_H_ */

Modified: head/sys/amd64/vmm/vmm_ktr.h
==============================================================================
--- head/sys/amd64/vmm/vmm_ktr.h	Fri May 23 05:04:50 2014	(r266572)
+++ head/sys/amd64/vmm/vmm_ktr.h	Fri May 23 05:15:17 2014	(r266573)
@@ -48,6 +48,10 @@ CTR4(KTR_VMM, "vm %s[%d]: " format, vm_n
 #define	VCPU_CTR3(vm, vcpuid, format, p1, p2, p3)			\
 CTR5(KTR_VMM, "vm %s[%d]: " format, vm_name((vm)), (vcpuid), (p1), (p2), (p3))
 
+#define	VCPU_CTR4(vm, vcpuid, format, p1, p2, p3, p4)			\
+CTR6(KTR_VMM, "vm %s[%d]: " format, vm_name((vm)), (vcpuid),		\
+    (p1), (p2), (p3), (p4))
+
 #define	VM_CTR0(vm, format)						\
 CTR1(KTR_VMM, "vm %s: " format, vm_name((vm)))
 

Modified: head/usr.sbin/bhyve/bhyverun.c
==============================================================================
--- head/usr.sbin/bhyve/bhyverun.c	Fri May 23 05:04:50 2014	(r266572)
+++ head/usr.sbin/bhyve/bhyverun.c	Fri May 23 05:15:17 2014	(r266573)
@@ -288,33 +288,34 @@ static int
 vmexit_inout(struct vmctx *ctx, struct vm_exit *vme, int *pvcpu)
 {
 	int error;
-	int bytes, port, in, out;
-	uint32_t eax;
+	int bytes, port, in, out, string;
 	int vcpu;
 
 	vcpu = *pvcpu;
 
 	port = vme->u.inout.port;
 	bytes = vme->u.inout.bytes;
-	eax = vme->u.inout.eax;
+	string = vme->u.inout.string;
 	in = vme->u.inout.in;
 	out = !in;
 
-	/* We don't deal with these */
-	if (vme->u.inout.string || vme->u.inout.rep)
-		return (VMEXIT_ABORT);
-
         /* Extra-special case of host notifications */
-        if (out && port == GUEST_NIO_PORT)
-                return (vmexit_handle_notify(ctx, vme, pvcpu, eax));
+        if (out && port == GUEST_NIO_PORT) {
+                error = vmexit_handle_notify(ctx, vme, pvcpu, vme->u.inout.eax);
+		return (error);
+	}
 
-	error = emulate_inout(ctx, vcpu, in, port, bytes, &eax, strictio);
-	if (error == INOUT_OK && in)
-		error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RAX, eax);
+	error = emulate_inout(ctx, vcpu, vme, strictio);
+	if (error == INOUT_OK && in && !string) {
+		error = vm_set_register(ctx, vcpu, VM_REG_GUEST_RAX,
+		    vme->u.inout.eax);
+	}
 
 	switch (error) {
 	case INOUT_OK:
 		return (VMEXIT_CONTINUE);
+	case INOUT_RESTART:
+		return (VMEXIT_RESTART);
 	case INOUT_RESET:
 		stats.io_reset++;
 		return (VMEXIT_RESET);
@@ -514,6 +515,7 @@ vmexit_suspend(struct vmctx *ctx, struct
 
 static vmexit_handler_t handler[VM_EXITCODE_MAX] = {
 	[VM_EXITCODE_INOUT]  = vmexit_inout,
+	[VM_EXITCODE_INOUT_STR]  = vmexit_inout,
 	[VM_EXITCODE_VMX]    = vmexit_vmx,
 	[VM_EXITCODE_BOGUS]  = vmexit_bogus,
 	[VM_EXITCODE_RDMSR]  = vmexit_rdmsr,

Modified: head/usr.sbin/bhyve/inout.c
==============================================================================
--- head/usr.sbin/bhyve/inout.c	Fri May 23 05:04:50 2014	(r266572)
+++ head/usr.sbin/bhyve/inout.c	Fri May 23 05:15:17 2014	(r266573)
@@ -32,10 +32,16 @@ __FBSDID("$FreeBSD$");
 #include <sys/param.h>
 #include <sys/linker_set.h>
 
+#include <x86/psl.h>
+
+#include <machine/vmm.h>
+#include <vmmapi.h>
+
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
 
+#include "bhyverun.h"
 #include "inout.h"
 
 SET_DECLARE(inout_port_set, struct inout_port);
@@ -91,52 +97,127 @@ register_default_iohandler(int start, in
 }
 
 int
-emulate_inout(struct vmctx *ctx, int vcpu, int in, int port, int bytes,
-	      uint32_t *eax, int strict)
+emulate_inout(struct vmctx *ctx, int vcpu, struct vm_exit *vmexit, int strict)
 {
-	int flags;
-	uint32_t mask, val;
+	int addrsize, bytes, flags, in, port, rep;
+	uint64_t gpa, gpaend;
+	uint32_t val;
 	inout_func_t handler;
 	void *arg;
-	int error;
+	char *gva;
+	int error, retval;
+	enum vm_reg_name idxreg;
+	uint64_t index, count;
+	struct vm_inout_str *vis;
+
+	static uint64_t size2mask[] = {
+		[1] = 0xff,
+		[2] = 0xffff,
+		[4] = 0xffffffff,
+		[8] = 0xffffffffffffffff,
+	};
+
+	bytes = vmexit->u.inout.bytes;
+	in = vmexit->u.inout.in;
+	port = vmexit->u.inout.port;
 
 	assert(port < MAX_IOPORTS);
+	assert(bytes == 1 || bytes == 2 || bytes == 4);
 
 	handler = inout_handlers[port].handler;
 
 	if (strict && handler == default_inout)
 		return (-1);
 
-	switch (bytes) {
-	case 1:
-		mask = 0xff;
-		break;
-	case 2:
-		mask = 0xffff;
-		break;
-	default:
-		mask = 0xffffffff;
-		break;
-	}
-
-	if (!in) {
-		val = *eax & mask;
-	}
-
 	flags = inout_handlers[port].flags;
 	arg = inout_handlers[port].arg;
 
-	if ((in && (flags & IOPORT_F_IN)) || (!in && (flags & IOPORT_F_OUT)))
-		error = (*handler)(ctx, vcpu, in, port, bytes, &val, arg);
-	else
-		error = -1;
-
-	if (!error && in) {
-		*eax &= ~mask;
-		*eax |= val & mask;
-	}
+	if (in) {
+		if (!(flags & IOPORT_F_IN))
+			return (-1);
+	} else {
+		if (!(flags & IOPORT_F_OUT))
+			return (-1);
+	}
+
+	retval = 0;
+	if (vmexit->u.inout.string) {
+		vis = &vmexit->u.inout_str;
+		rep = vis->inout.rep;
+		addrsize = vis->addrsize;
+		assert(addrsize == 2 || addrsize == 4 || addrsize == 8);
+
+		/* Index register */
+		idxreg = in ? VM_REG_GUEST_RDI : VM_REG_GUEST_RSI;
+		index = vis->index & size2mask[addrsize];
+
+		/* Count register */
+		count = vis->count & size2mask[addrsize];
+
+		gpa = vis->gpa;
+		gpaend = rounddown(gpa + PAGE_SIZE, PAGE_SIZE);
+		gva = paddr_guest2host(ctx, gpa, gpaend - gpa);
+
+		while (count != 0 && gpa < gpaend) {
+			/*
+			 * XXX this may not work for unaligned accesses because
+			 * the last access on the page may spill over into the
+			 * adjacent page in the linear address space. This is a
+			 * problem because we don't have a gla2gpa() mapping of
+			 * this adjacent page.
+			 */
+			assert(gpaend - gpa >= bytes);
+
+			val = 0;
+			if (!in)
+				bcopy(gva, &val, bytes);
+
+			retval = handler(ctx, vcpu, in, port, bytes, &val, arg);
+			if (retval != 0)
+				break;
+
+			if (in)
+				bcopy(&val, gva, bytes);
+
+			/* Update index */
+			if (vis->rflags & PSL_D)
+				index -= bytes;
+			else
+				index += bytes;
+
+			count--;
+			gva += bytes;
+			gpa += bytes;
+		}
+
+		/* Update index register */
+		error = vie_update_register(ctx, vcpu, idxreg, index, addrsize);
+		assert(error == 0);
+
+		/*
+		 * Update count register only if the instruction had a repeat
+		 * prefix.
+		 */
+		if (rep) {
+			error = vie_update_register(ctx, vcpu, VM_REG_GUEST_RCX,
+			    count, addrsize);
+			assert(error == 0);
+		}
 
-	return (error);
+		/* Restart the instruction if more iterations remain */
+		if (retval == INOUT_OK && count != 0)
+			retval = INOUT_RESTART;
+	} else {
+		if (!in) {
+			val = vmexit->u.inout.eax & size2mask[bytes];
+		}
+		retval = handler(ctx, vcpu, in, port, bytes, &val, arg);
+		if (retval == 0 && in) {
+			vmexit->u.inout.eax &= ~size2mask[bytes];
+			vmexit->u.inout.eax |= val & size2mask[bytes];
+		}
+	}
+	return (retval);
 }
 
 void

Modified: head/usr.sbin/bhyve/inout.h
==============================================================================
--- head/usr.sbin/bhyve/inout.h	Fri May 23 05:04:50 2014	(r266572)
+++ head/usr.sbin/bhyve/inout.h	Fri May 23 05:15:17 2014	(r266573)
@@ -32,12 +32,14 @@
 #include <sys/linker_set.h>
 
 struct vmctx;
+struct vm_exit;
 
 /* Handler return values. */
 #define	INOUT_ERROR	-1
 #define	INOUT_OK	0
-#define	INOUT_RESET	1
-#define	INOUT_POWEROFF	2
+#define	INOUT_RESTART	1
+#define	INOUT_RESET	2
+#define	INOUT_POWEROFF	3
 
 typedef int (*inout_func_t)(struct vmctx *ctx, int vcpu, int in, int port,
 			    int bytes, uint32_t *eax, void *arg);
@@ -72,8 +74,8 @@ struct inout_port {
 	DATA_SET(inout_port_set, __CONCAT(__inout_port, __LINE__))
 	
 void	init_inout(void);
-int	emulate_inout(struct vmctx *, int vcpu, int in, int port, int bytes,
-		      uint32_t *eax, int strict);
+int	emulate_inout(struct vmctx *, int vcpu, struct vm_exit *vmexit,
+		      int strict);
 int	register_inout(struct inout_port *iop);
 int	unregister_inout(struct inout_port *iop);
 void	init_bvmcons(void);


More information about the svn-src-head mailing list