vmm_ioport.c revision 266595
1/*-
2 * Copyright (c) 2014 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/amd64/vmm/vmm_ioport.c 266595 2014-05-23 19:59:14Z neel $");
29
30#include <sys/param.h>
31#include <sys/types.h>
32#include <sys/queue.h>
33#include <sys/cpuset.h>
34#include <sys/systm.h>
35
36#include <vm/vm.h>
37
38#include <machine/vmm.h>
39#include <x86/psl.h>
40
41#include "vatpic.h"
42#include "vatpit.h"
43#include "vmm_ioport.h"
44#include "vmm_ktr.h"
45
46#define	MAX_IOPORTS		1280
47
48ioport_handler_func_t ioport_handler[MAX_IOPORTS] = {
49	[TIMER_MODE] = vatpit_handler,
50	[TIMER_CNTR0] = vatpit_handler,
51	[TIMER_CNTR1] = vatpit_handler,
52	[TIMER_CNTR2] = vatpit_handler,
53	[NMISC_PORT] = vatpit_nmisc_handler,
54	[IO_ICU1] = vatpic_master_handler,
55	[IO_ICU1 + ICU_IMR_OFFSET] = vatpic_master_handler,
56	[IO_ICU2] = vatpic_slave_handler,
57	[IO_ICU2 + ICU_IMR_OFFSET] = vatpic_slave_handler,
58	[IO_ELCR1] = vatpic_elc_handler,
59	[IO_ELCR2] = vatpic_elc_handler,
60};
61
62#ifdef KTR
63static const char *
64inout_instruction(struct vm_exit *vmexit)
65{
66	int index;
67
68	static const char *iodesc[] = {
69		"outb", "outw", "outl",
70		"inb", "inw", "inl",
71		"outsb", "outsw", "outsd"
72		"insb", "insw", "insd",
73	};
74
75	switch (vmexit->u.inout.bytes) {
76	case 1:
77		index = 0;
78		break;
79	case 2:
80		index = 1;
81		break;
82	default:
83		index = 2;
84		break;
85	}
86
87	if (vmexit->u.inout.in)
88		index += 3;
89
90	if (vmexit->u.inout.string)
91		index += 6;
92
93	KASSERT(index < nitems(iodesc), ("%s: invalid index %d",
94	    __func__, index));
95
96	return (iodesc[index]);
97}
98#endif	/* KTR */
99
100static int
101emulate_inout_port(struct vm *vm, int vcpuid, struct vm_exit *vmexit,
102    bool *retu)
103{
104	ioport_handler_func_t handler;
105	uint32_t mask, val;
106	int error;
107
108	error = 0;
109	*retu = true;
110
111	if (vmexit->u.inout.port >= MAX_IOPORTS)
112		goto done;
113
114	handler = ioport_handler[vmexit->u.inout.port];
115	if (handler == NULL)
116		goto done;
117
118	mask = vie_size2mask(vmexit->u.inout.bytes);
119
120	if (!vmexit->u.inout.in) {
121		val = vmexit->u.inout.eax & mask;
122	}
123
124	error = (*handler)(vm, vcpuid, vmexit->u.inout.in,
125	    vmexit->u.inout.port, vmexit->u.inout.bytes, &val);
126
127	if (!error) {
128		*retu = false;
129		if (vmexit->u.inout.in) {
130			vmexit->u.inout.eax &= ~mask;
131			vmexit->u.inout.eax |= val & mask;
132			error = vm_set_register(vm, vcpuid,
133			    VM_REG_GUEST_RAX, vmexit->u.inout.eax);
134			KASSERT(error == 0, ("emulate_ioport: error %d "
135			    "setting guest rax register", error));
136		}
137	}
138done:
139	return (error);
140}
141
142static int
143emulate_inout_str(struct vm *vm, int vcpuid, struct vm_exit *vmexit, bool *retu)
144{
145	struct vm_inout_str *vis;
146	uint64_t gla, index, segbase;
147	int error, in;
148
149	vis = &vmexit->u.inout_str;
150	in = vis->inout.in;
151
152	/*
153	 * ins/outs VM exit takes precedence over the following error
154	 * conditions that would ordinarily be checked by the processor:
155	 *
156	 * - #GP(0) due to segment being unusable.
157	 * - #GP(0) due to memory operand effective address outside the limit
158	 *   of the segment.
159	 * - #AC(0) if alignment checking is enabled and an unaligned memory
160	 *   reference is made at CPL=3
161	 */
162
163	/*
164	 * XXX
165	 * inout string emulation only supported in 64-bit mode.
166	 *
167	 * The #GP(0) fault conditions described above don't apply in
168	 * 64-bit mode.
169	 */
170	if (vis->cpu_mode != CPU_MODE_64BIT) {
171		VCPU_CTR1(vm, vcpuid, "ins/outs not emulated in cpu mode %d",
172		    vis->cpu_mode);
173		return (EINVAL);
174	}
175
176	/*
177	 * XXX insb/insw/insd instructions not emulated at this time.
178	 */
179	if (in) {
180		VCPU_CTR0(vm, vcpuid, "ins emulation not implemented");
181		return (EINVAL);
182	}
183
184	segbase = vie_segbase(vis->seg_name, vis->cpu_mode, &vis->seg_desc);
185	index = vis->index & vie_size2mask(vis->addrsize);
186	gla = segbase + index;
187
188	/*
189	 * Verify that the computed linear address matches with the one
190	 * provided by hardware.
191	 */
192	if (vis->gla != VIE_INVALID_GLA) {
193		KASSERT(gla == vis->gla, ("%s: gla mismatch "
194		    "%#lx/%#lx", __func__, gla, vis->gla));
195	}
196	vis->gla = gla;
197
198	error = vmm_gla2gpa(vm, vcpuid, gla, vis->cr3, &vis->gpa,
199	    vis->paging_mode, vis->cpl, in ? VM_PROT_WRITE : VM_PROT_READ);
200	KASSERT(error == 0 || error == 1 || error == -1,
201	    ("%s: vmm_gla2gpa unexpected error %d", __func__, error));
202	if (error == -1) {
203		return (EFAULT);
204	} else if (error == 1) {
205		return (0);	/* Resume guest to handle page fault */
206	} else {
207		*retu = true;
208		return (0);	/* Return to userspace to finish emulation */
209	}
210}
211
212int
213vm_handle_inout(struct vm *vm, int vcpuid, struct vm_exit *vmexit, bool *retu)
214{
215	int bytes, error;
216
217	bytes = vmexit->u.inout.bytes;
218	KASSERT(bytes == 1 || bytes == 2 || bytes == 4,
219	    ("vm_handle_inout: invalid operand size %d", bytes));
220
221	if (vmexit->u.inout.string)
222		error = emulate_inout_str(vm, vcpuid, vmexit, retu);
223	else
224		error = emulate_inout_port(vm, vcpuid, vmexit, retu);
225
226	VCPU_CTR4(vm, vcpuid, "%s%s 0x%04x: %s",
227	    vmexit->u.inout.rep ? "rep " : "",
228	    inout_instruction(vmexit),
229	    vmexit->u.inout.port,
230	    error ? "error" : (*retu ? "userspace" : "handled"));
231
232	return (error);
233}
234