vmm(4): send all port io emulation to userland

Simplify things by sending any io exits from IN/OUT instructions
to userland instead of trying to emulate anything in the kernel.
vmm was sending most pertinent exits to vmd anyways, so this
functionally changes little.

An added benefit is this solves an issue reported by tb@ where i386
OpenBSD guests would probe for a pc keyboard repeatedly and cause
excessive vm exits. (The emulation in vmm was not properly handling
these port reads.)

While here, make the assignment of the VEI_DIR_{IN,OUT} enum values
not assume the underlying integer the compiler may assign.

ok mlarkin@
This commit is contained in:
dv 2022-09-01 22:01:40 +00:00
parent 0dd33aec28
commit 33de4f9469
5 changed files with 32 additions and 111 deletions

View File

@ -1,4 +1,4 @@
/* $OpenBSD: vmm.c,v 1.320 2022/08/30 17:09:21 dv Exp $ */
/* $OpenBSD: vmm.c,v 1.321 2022/09/01 22:01:40 dv Exp $ */
/*
* Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org>
*
@ -5408,7 +5408,8 @@ svm_handle_exit(struct vcpu *vcpu)
update_rip = 1;
break;
case SVM_VMEXIT_IOIO:
ret = svm_handle_inout(vcpu);
if (svm_handle_inout(vcpu) == 0)
ret = EAGAIN;
update_rip = 1;
break;
case SVM_VMEXIT_HLT:
@ -5492,7 +5493,8 @@ vmx_handle_exit(struct vcpu *vcpu)
update_rip = 1;
break;
case VMX_EXIT_IO:
ret = vmx_handle_inout(vcpu);
if (vmx_handle_inout(vcpu) == 0)
ret = EAGAIN;
update_rip = 1;
break;
case VMX_EXIT_EXTINT:
@ -6034,22 +6036,17 @@ vmm_get_guest_cpu_mode(struct vcpu *vcpu)
*
* Exit handler for IN/OUT instructions.
*
* The vmm can handle certain IN/OUTS without exiting to vmd, but most of these
* will be passed to vmd for completion.
*
* Parameters:
* vcpu: The VCPU where the IN/OUT instruction occurred
*
* Return values:
* 0: if successful
* EINVAL: an invalid IN/OUT instruction was encountered
* EAGAIN: return to vmd - more processing needed in userland
*/
int
svm_handle_inout(struct vcpu *vcpu)
{
uint64_t insn_length, exit_qual;
int ret;
struct vmcb *vmcb = (struct vmcb *)vcpu->vc_control_va;
insn_length = vmcb->v_exitinfo2 - vmcb->v_rip;
@ -6062,7 +6059,10 @@ svm_handle_inout(struct vcpu *vcpu)
exit_qual = vmcb->v_exitinfo1;
/* Bit 0 - direction */
vcpu->vc_exit.vei.vei_dir = (exit_qual & 0x1);
if (exit_qual & 0x1)
vcpu->vc_exit.vei.vei_dir = VEI_DIR_IN;
else
vcpu->vc_exit.vei.vei_dir = VEI_DIR_OUT;
/* Bit 2 - string instruction? */
vcpu->vc_exit.vei.vei_string = (exit_qual & 0x4) >> 2;
/* Bit 3 - REP prefix? */
@ -6083,52 +6083,7 @@ svm_handle_inout(struct vcpu *vcpu)
vcpu->vc_gueststate.vg_rip += insn_length;
/*
* The following ports usually belong to devices owned by vmd.
* Return EAGAIN to signal help needed from userspace (vmd).
* Return 0 to indicate we don't care about this port.
*
* XXX something better than a hardcoded list here, maybe
* configure via vmd via the device list in vm create params?
*/
switch (vcpu->vc_exit.vei.vei_port) {
case IO_ICU1 ... IO_ICU1 + 1:
case 0x40 ... 0x43:
case PCKBC_AUX:
case IO_RTC ... IO_RTC + 1:
case IO_ICU2 ... IO_ICU2 + 1:
case 0x3f8 ... 0x3ff:
case ELCR0 ... ELCR1:
case 0x500 ... 0x511:
case 0x514:
case 0x518:
case 0xcf8:
case 0xcfc ... 0xcff:
case VMM_PCI_IO_BAR_BASE ... VMM_PCI_IO_BAR_END:
ret = EAGAIN;
break;
default:
/* Read from unsupported ports returns FFs */
if (vcpu->vc_exit.vei.vei_dir == 1) {
switch(vcpu->vc_exit.vei.vei_size) {
case 1:
vcpu->vc_gueststate.vg_rax |= 0xFF;
vmcb->v_rax |= 0xFF;
break;
case 2:
vcpu->vc_gueststate.vg_rax |= 0xFFFF;
vmcb->v_rax |= 0xFFFF;
break;
case 4:
vcpu->vc_gueststate.vg_rax |= 0xFFFFFFFF;
vmcb->v_rax |= 0xFFFFFFFF;
break;
}
}
ret = 0;
}
return (ret);
return (0);
}
/*
@ -6136,14 +6091,17 @@ svm_handle_inout(struct vcpu *vcpu)
*
* Exit handler for IN/OUT instructions.
*
* The vmm can handle certain IN/OUTS without exiting to vmd, but most of these
* will be passed to vmd for completion.
* Parameters:
* vcpu: The VCPU where the IN/OUT instruction occurred
*
* Return values:
* 0: if successful
* EINVAL: invalid IN/OUT instruction or vmread failures occurred
*/
int
vmx_handle_inout(struct vcpu *vcpu)
{
uint64_t insn_length, exit_qual;
int ret;
if (vmread(VMCS_INSTRUCTION_LENGTH, &insn_length)) {
printf("%s: can't obtain instruction length\n", __func__);
@ -6164,7 +6122,10 @@ vmx_handle_inout(struct vcpu *vcpu)
/* Bits 0:2 - size of exit */
vcpu->vc_exit.vei.vei_size = (exit_qual & 0x7) + 1;
/* Bit 3 - direction */
vcpu->vc_exit.vei.vei_dir = (exit_qual & 0x8) >> 3;
if ((exit_qual & 0x8) >> 3)
vcpu->vc_exit.vei.vei_dir = VEI_DIR_IN;
else
vcpu->vc_exit.vei.vei_dir = VEI_DIR_OUT;
/* Bit 4 - string instruction? */
vcpu->vc_exit.vei.vei_string = (exit_qual & 0x10) >> 4;
/* Bit 5 - REP prefix? */
@ -6178,44 +6139,7 @@ vmx_handle_inout(struct vcpu *vcpu)
vcpu->vc_gueststate.vg_rip += insn_length;
/*
* The following ports usually belong to devices owned by vmd.
* Return EAGAIN to signal help needed from userspace (vmd).
* Return 0 to indicate we don't care about this port.
*
* XXX something better than a hardcoded list here, maybe
* configure via vmd via the device list in vm create params?
*/
switch (vcpu->vc_exit.vei.vei_port) {
case IO_ICU1 ... IO_ICU1 + 1:
case 0x40 ... 0x43:
case PCKBC_AUX:
case IO_RTC ... IO_RTC + 1:
case IO_ICU2 ... IO_ICU2 + 1:
case 0x3f8 ... 0x3ff:
case ELCR0 ... ELCR1:
case 0x500 ... 0x511:
case 0x514:
case 0x518:
case 0xcf8:
case 0xcfc ... 0xcff:
case VMM_PCI_IO_BAR_BASE ... VMM_PCI_IO_BAR_END:
ret = EAGAIN;
break;
default:
/* Read from unsupported ports returns FFs */
if (vcpu->vc_exit.vei.vei_dir == VEI_DIR_IN) {
if (vcpu->vc_exit.vei.vei_size == 4)
vcpu->vc_gueststate.vg_rax |= 0xFFFFFFFF;
else if (vcpu->vc_exit.vei.vei_size == 2)
vcpu->vc_gueststate.vg_rax |= 0xFFFF;
else if (vcpu->vc_exit.vei.vei_size == 1)
vcpu->vc_gueststate.vg_rax |= 0xFF;
}
ret = 0;
}
return (ret);
return (0);
}
/*

View File

@ -1,4 +1,4 @@
/* $OpenBSD: vmmvar.h,v 1.80 2022/08/30 17:09:21 dv Exp $ */
/* $OpenBSD: vmmvar.h,v 1.81 2022/09/01 22:01:40 dv Exp $ */
/*
* Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org>
*
@ -339,13 +339,6 @@ enum {
VMM_CPU_MODE_UNKNOWN,
};
/*
* Port definitions not found elsewhere
*/
#define PCKBC_AUX 0x61
#define ELCR0 0x4D0
#define ELCR1 0x4D1
/*
* vm exit data
* vm_exit_inout : describes an IN/OUT exit

View File

@ -1,4 +1,4 @@
/* $OpenBSD: i8253.h,v 1.9 2018/04/26 17:10:10 mlarkin Exp $ */
/* $OpenBSD: i8253.h,v 1.10 2022/09/01 22:01:40 dv Exp $ */
/*
* Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
*
@ -26,6 +26,7 @@
#define TIMER_RB_C0 0x2 /* read back channel 0 */
#define TIMER_RB_C1 0x4 /* read back channel 1 */
#define TIMER_RB_C2 0x8 /* read back channel 1 */
#define PCKBC_AUX 0x61 /* PC keyboard aux port for i8253 misc access */
/* i8253 registers */
struct i8253_channel {

View File

@ -1,4 +1,4 @@
/* $OpenBSD: i8259.h,v 1.4 2018/04/27 12:15:10 mlarkin Exp $ */
/* $OpenBSD: i8259.h,v 1.5 2022/09/01 22:01:40 dv Exp $ */
/*
* Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
*
@ -19,8 +19,11 @@
#include <machine/vmmvar.h>
#define MASTER 0
#define SLAVE 1
#define MASTER 0
#define SLAVE 1
#define ELCR0 0x4D0
#define ELCR1 0x4D1
#define ICW1_ICW4 (0x1 << 0)
#define ICW1_SNGL (0x1 << 1)

View File

@ -1,4 +1,4 @@
/* $OpenBSD: vm.c,v 1.72 2022/08/30 17:09:21 dv Exp $ */
/* $OpenBSD: vm.c,v 1.73 2022/09/01 22:01:40 dv Exp $ */
/*
* Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
@ -1636,7 +1636,7 @@ vcpu_exit_inout(struct vm_run_params *vrp)
if (ioports_map[vei->vei.vei_port] != NULL)
intr = ioports_map[vei->vei.vei_port](vrp);
else if (vei->vei.vei_dir == VEI_DIR_IN)
set_return_data(vei, 0xFFFFFFFF);
set_return_data(vei, 0xFFFFFFFF);
if (intr != 0xFF)
vcpu_assert_pic_irq(vrp->vrp_vm_id, vrp->vrp_vcpu_id, intr);