diff options
Diffstat (limited to 'usr.sbin/bhyve/inout.c')
-rw-r--r-- | usr.sbin/bhyve/inout.c | 151 |
1 files changed, 122 insertions, 29 deletions
diff --git a/usr.sbin/bhyve/inout.c b/usr.sbin/bhyve/inout.c index 5fbe99b..fe9e0d8 100644 --- a/usr.sbin/bhyve/inout.c +++ b/usr.sbin/bhyve/inout.c @@ -31,11 +31,21 @@ __FBSDID("$FreeBSD$"); #include <sys/param.h> #include <sys/linker_set.h> +#include <sys/_iovec.h> +#include <sys/mman.h> + +#include <x86/psl.h> +#include <x86/segments.h> + +#include <machine/vmm.h> +#include <machine/vmm_instruction_emul.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 +101,135 @@ register_default_iohandler(int start, int size) } 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, prot, rep; + uint32_t val; inout_func_t handler; void *arg; - int error; + int error, retval; + enum vm_reg_name idxreg; + uint64_t gla, index, iterations, count; + struct vm_inout_str *vis; + struct iovec iov[2]; + + 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; - } + flags = inout_handlers[port].flags; + arg = inout_handlers[port].arg; - if (!in) { - val = *eax & mask; + if (in) { + if (!(flags & IOPORT_F_IN)) + return (-1); + } else { + if (!(flags & IOPORT_F_OUT)) + return (-1); } - flags = inout_handlers[port].flags; - arg = inout_handlers[port].arg; + retval = 0; + if (vmexit->u.inout.string) { + vis = &vmexit->u.inout_str; + rep = vis->inout.rep; + addrsize = vis->addrsize; + prot = in ? PROT_WRITE : PROT_READ; + assert(addrsize == 2 || addrsize == 4 || addrsize == 8); + + /* Index register */ + idxreg = in ? VM_REG_GUEST_RDI : VM_REG_GUEST_RSI; + index = vis->index & vie_size2mask(addrsize); + + /* Count register */ + count = vis->count & vie_size2mask(addrsize); + + /* Limit number of back-to-back in/out emulations to 16 */ + iterations = MIN(count, 16); + while (iterations > 0) { + if (vie_calculate_gla(vis->paging.cpu_mode, + vis->seg_name, &vis->seg_desc, index, bytes, + addrsize, prot, &gla)) { + error = vm_inject_exception2(ctx, vcpu, + IDT_GP, 0); + assert(error == 0); + retval = INOUT_RESTART; + break; + } + + error = vm_gla2gpa(ctx, vcpu, &vis->paging, gla, bytes, + prot, iov, nitems(iov)); + assert(error == 0 || error == 1 || error == -1); + if (error) { + retval = (error == 1) ? INOUT_RESTART : + INOUT_ERROR; + break; + } + + if (vie_alignment_check(vis->paging.cpl, bytes, + vis->cr0, vis->rflags, gla)) { + error = vm_inject_exception2(ctx, vcpu, + IDT_AC, 0); + assert(error == 0); + return (INOUT_RESTART); + } + + val = 0; + if (!in) + vm_copyin(ctx, vcpu, iov, &val, bytes); + + retval = handler(ctx, vcpu, in, port, bytes, &val, arg); + if (retval != 0) + break; + + if (in) + vm_copyout(ctx, vcpu, &val, iov, bytes); + + /* Update index */ + if (vis->rflags & PSL_D) + index -= bytes; + else + index += bytes; + + count--; + iterations--; + } - if ((in && (flags & IOPORT_F_IN)) || (!in && (flags & IOPORT_F_OUT))) - error = (*handler)(ctx, vcpu, in, port, bytes, &val, arg); - else - error = -1; + /* 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); + } - if (!error && in) { - *eax &= ~mask; - *eax |= val & mask; + /* Restart the instruction if more iterations remain */ + if (retval == INOUT_OK && count != 0) + retval = INOUT_RESTART; + } else { + if (!in) { + val = vmexit->u.inout.eax & vie_size2mask(bytes); + } + retval = handler(ctx, vcpu, in, port, bytes, &val, arg); + if (retval == 0 && in) { + vmexit->u.inout.eax &= ~vie_size2mask(bytes); + vmexit->u.inout.eax |= val & vie_size2mask(bytes); + } } - - return (error); + return (retval); } void |