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)
This commit is contained in:
parent
c5e423dd2e
commit
d17b5104a9
@ -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 vcpuid); /* general protection fault */
|
||||
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;
|
||||
|
@ -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, int cpuid, uint64_t gpa, struct vie *vie,
|
||||
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 vcpuid, uint64_t gla, uint64_t cr3,
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -185,6 +185,8 @@ SYSCTL_UINT(_hw_vmm_vmx, OID_AUTO, vpid_alloc_failed, CTLFLAG_RD,
|
||||
*/
|
||||
#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, int vcpuid, struct vm_exit *vmexit)
|
||||
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 vcpu, struct vm_exit *vmexit)
|
||||
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:
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ vie_read_bytereg(void *vm, int vcpuid, struct vie *vie, uint8_t *rval)
|
||||
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, int cpuid, uint64_t gla,
|
||||
|
||||
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 */
|
||||
|
@ -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,31 +59,63 @@ ioport_handler_func_t ioport_handler[MAX_IOPORTS] = {
|
||||
[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)
|
||||
{
|
||||
int index;
|
||||
|
||||
static const char *iodesc[] = {
|
||||
"outb", "outw", "outl",
|
||||
"inb", "inw", "inl",
|
||||
"outsb", "outsw", "outsd"
|
||||
"insb", "insw", "insd",
|
||||
};
|
||||
|
||||
switch (vmexit->u.inout.bytes) {
|
||||
case 1:
|
||||
index = 0;
|
||||
break;
|
||||
case 2:
|
||||
index = 1;
|
||||
break;
|
||||
default:
|
||||
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)
|
||||
return (-1);
|
||||
goto done;
|
||||
|
||||
handler = ioport_handler[vmexit->u.inout.port];
|
||||
if (handler == NULL)
|
||||
return (-1);
|
||||
goto done;
|
||||
|
||||
switch (vmexit->u.inout.bytes) {
|
||||
case 1:
|
||||
mask = 0xff;
|
||||
break;
|
||||
case 2:
|
||||
mask = 0xffff;
|
||||
break;
|
||||
default:
|
||||
mask = 0xffffffff;
|
||||
break;
|
||||
}
|
||||
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, struct vm_exit *vmexit)
|
||||
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);
|
||||
}
|
||||
|
@ -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_ */
|
||||
|
@ -48,6 +48,10 @@ CTR4(KTR_VMM, "vm %s[%d]: " format, vm_name((vm)), (vcpuid), (p1), (p2))
|
||||
#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)))
|
||||
|
||||
|
@ -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 vm_exit *vmexit, int *pvcpu)
|
||||
|
||||
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,
|
||||
|
@ -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, 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, 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);
|
||||
}
|
||||
|
||||
return (error);
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user