xref: /freebsd/usr.sbin/bhyve/amd64/inout.c (revision 4f8f43b06ed07e96a250855488cc531799d5b78f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2011 NetApp, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 #include <sys/param.h>
31 #include <sys/linker_set.h>
32 #include <sys/_iovec.h>
33 #include <sys/mman.h>
34 
35 #include <x86/psl.h>
36 
37 #include <machine/vmm.h>
38 #include <machine/vmm_instruction_emul.h>
39 #include <vmmapi.h>
40 
41 #include <stdio.h>
42 #include <string.h>
43 #include <assert.h>
44 
45 #include "bhyverun.h"
46 #include "config.h"
47 #include "inout.h"
48 
49 SET_DECLARE(inout_port_set, struct inout_port);
50 
51 #define	MAX_IOPORTS	(1 << 16)
52 
53 #define	VERIFY_IOPORT(port, size) \
54 	assert((port) >= 0 && (size) > 0 && ((port) + (size)) <= MAX_IOPORTS)
55 
56 static struct {
57 	const char	*name;
58 	int		flags;
59 	inout_func_t	handler;
60 	void		*arg;
61 } inout_handlers[MAX_IOPORTS];
62 
63 static int
64 default_inout(struct vmctx *ctx __unused, int in,
65     int port __unused, int bytes, uint32_t *eax, void *arg __unused)
66 {
67 	if (in) {
68 		switch (bytes) {
69 		case 4:
70 			*eax = 0xffffffff;
71 			break;
72 		case 2:
73 			*eax = 0xffff;
74 			break;
75 		case 1:
76 			*eax = 0xff;
77 			break;
78 		}
79 	}
80 
81 	return (0);
82 }
83 
84 static void
85 register_default_iohandler(int start, int size)
86 {
87 	struct inout_port iop;
88 
89 	VERIFY_IOPORT(start, size);
90 
91 	bzero(&iop, sizeof(iop));
92 	iop.name = "default";
93 	iop.port = start;
94 	iop.size = size;
95 	iop.flags = IOPORT_F_INOUT | IOPORT_F_DEFAULT;
96 	iop.handler = default_inout;
97 
98 	register_inout(&iop);
99 }
100 
101 int
102 emulate_inout(struct vmctx *ctx, struct vcpu *vcpu, struct vm_exit *vmexit)
103 {
104 	int addrsize, bytes, flags, in, port, prot, rep;
105 	uint32_t eax, val;
106 	inout_func_t handler;
107 	void *arg;
108 	int error, fault, retval;
109 	enum vm_reg_name idxreg;
110 	uint64_t gla, index, iterations, count;
111 	struct vm_inout_str *vis;
112 	struct iovec iov[2];
113 
114 	bytes = vmexit->u.inout.bytes;
115 	in = vmexit->u.inout.in;
116 	port = vmexit->u.inout.port;
117 
118 	assert(port < MAX_IOPORTS);
119 	assert(bytes == 1 || bytes == 2 || bytes == 4);
120 
121 	handler = inout_handlers[port].handler;
122 
123 	if (handler == default_inout &&
124 	    get_config_bool_default("x86.strictio", false))
125 		return (-1);
126 
127 	flags = inout_handlers[port].flags;
128 	arg = inout_handlers[port].arg;
129 
130 	if (in) {
131 		if (!(flags & IOPORT_F_IN))
132 			return (-1);
133 	} else {
134 		if (!(flags & IOPORT_F_OUT))
135 			return (-1);
136 	}
137 
138 	retval = 0;
139 	if (vmexit->u.inout.string) {
140 		vis = &vmexit->u.inout_str;
141 		rep = vis->inout.rep;
142 		addrsize = vis->addrsize;
143 		prot = in ? PROT_WRITE : PROT_READ;
144 		assert(addrsize == 2 || addrsize == 4 || addrsize == 8);
145 
146 		/* Index register */
147 		idxreg = in ? VM_REG_GUEST_RDI : VM_REG_GUEST_RSI;
148 		index = vis->index & vie_size2mask(addrsize);
149 
150 		/* Count register */
151 		count = vis->count & vie_size2mask(addrsize);
152 
153 		/* Limit number of back-to-back in/out emulations to 16 */
154 		iterations = MIN(count, 16);
155 		while (iterations > 0) {
156 			assert(retval == 0);
157 			if (vie_calculate_gla(vis->paging.cpu_mode,
158 			    vis->seg_name, &vis->seg_desc, index, bytes,
159 			    addrsize, prot, &gla)) {
160 				vm_inject_gp(vcpu);
161 				break;
162 			}
163 
164 			error = vm_copy_setup(vcpu, &vis->paging, gla,
165 			    bytes, prot, iov, nitems(iov), &fault);
166 			if (error) {
167 				retval = -1;  /* Unrecoverable error */
168 				break;
169 			} else if (fault) {
170 				retval = 0;  /* Resume guest to handle fault */
171 				break;
172 			}
173 
174 			if (vie_alignment_check(vis->paging.cpl, bytes,
175 			    vis->cr0, vis->rflags, gla)) {
176 				vm_inject_ac(vcpu, 0);
177 				break;
178 			}
179 
180 			val = 0;
181 			if (!in)
182 				vm_copyin(iov, &val, bytes);
183 
184 			retval = handler(ctx, in, port, bytes, &val, arg);
185 			if (retval != 0)
186 				break;
187 
188 			if (in)
189 				vm_copyout(&val, iov, bytes);
190 
191 			/* Update index */
192 			if (vis->rflags & PSL_D)
193 				index -= bytes;
194 			else
195 				index += bytes;
196 
197 			count--;
198 			iterations--;
199 		}
200 
201 		/* Update index register */
202 		error = vie_update_register(vcpu, idxreg, index, addrsize);
203 		assert(error == 0);
204 
205 		/*
206 		 * Update count register only if the instruction had a repeat
207 		 * prefix.
208 		 */
209 		if (rep) {
210 			error = vie_update_register(vcpu, VM_REG_GUEST_RCX,
211 			    count, addrsize);
212 			assert(error == 0);
213 		}
214 
215 		/* Restart the instruction if more iterations remain */
216 		if (retval == 0 && count != 0) {
217 			error = vm_restart_instruction(vcpu);
218 			assert(error == 0);
219 		}
220 	} else {
221 		eax = vmexit->u.inout.eax;
222 		val = eax & vie_size2mask(bytes);
223 		retval = handler(ctx, in, port, bytes, &val, arg);
224 		if (retval == 0 && in) {
225 			eax &= ~vie_size2mask(bytes);
226 			eax |= val & vie_size2mask(bytes);
227 			error = vm_set_register(vcpu, VM_REG_GUEST_RAX,
228 			    eax);
229 			assert(error == 0);
230 		}
231 	}
232 	return (retval);
233 }
234 
235 void
236 init_inout(void)
237 {
238 	struct inout_port **iopp, *iop;
239 
240 	/*
241 	 * Set up the default handler for all ports
242 	 */
243 	register_default_iohandler(0, MAX_IOPORTS);
244 
245 	/*
246 	 * Overwrite with specified handlers
247 	 */
248 	SET_FOREACH(iopp, inout_port_set) {
249 		iop = *iopp;
250 		assert(iop->port < MAX_IOPORTS);
251 		inout_handlers[iop->port].name = iop->name;
252 		inout_handlers[iop->port].flags = iop->flags;
253 		inout_handlers[iop->port].handler = iop->handler;
254 		inout_handlers[iop->port].arg = NULL;
255 	}
256 }
257 
258 int
259 register_inout(struct inout_port *iop)
260 {
261 	int i;
262 
263 	VERIFY_IOPORT(iop->port, iop->size);
264 
265 	/*
266 	 * Verify that the new registration is not overwriting an already
267 	 * allocated i/o range.
268 	 */
269 	if ((iop->flags & IOPORT_F_DEFAULT) == 0) {
270 		for (i = iop->port; i < iop->port + iop->size; i++) {
271 			if ((inout_handlers[i].flags & IOPORT_F_DEFAULT) == 0)
272 				return (-1);
273 		}
274 	}
275 
276 	for (i = iop->port; i < iop->port + iop->size; i++) {
277 		inout_handlers[i].name = iop->name;
278 		inout_handlers[i].flags = iop->flags;
279 		inout_handlers[i].handler = iop->handler;
280 		inout_handlers[i].arg = iop->arg;
281 	}
282 
283 	return (0);
284 }
285 
286 int
287 unregister_inout(struct inout_port *iop)
288 {
289 
290 	VERIFY_IOPORT(iop->port, iop->size);
291 	assert(inout_handlers[iop->port].name == iop->name);
292 
293 	register_default_iohandler(iop->port, iop->size);
294 
295 	return (0);
296 }
297