Date: Fri, 23 May 2014 05:15:17 +0000 (UTC) From: Neel Natu <neel@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r266573 - in head: sys/amd64/include sys/amd64/vmm sys/amd64/vmm/intel usr.sbin/bhyve Message-ID: <201405230515.s4N5FHoE019681@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
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);
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201405230515.s4N5FHoE019681>