xref: /illumos-gate/usr/src/uts/intel/io/vmm/vmm_ioport.c (revision 32640292)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2014 Tycho Nightingale <tycho.nightingale@pluribusnetworks.com>
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 THE AUTHOR ``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 THE AUTHOR 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  * This file and its contents are supplied under the terms of the
30  * Common Development and Distribution License ("CDDL"), version 1.0.
31  * You may only use this file in accordance with the terms of version
32  * 1.0 of the CDDL.
33  *
34  * A full copy of the text of the CDDL should have accompanied this
35  * source.  A copy of the CDDL is also available via the Internet at
36  * http://www.illumos.org/license/CDDL.
37  *
38  * Copyright 2020 Oxide Computer Company
39  */
40 
41 #include <sys/cdefs.h>
42 
43 #include <sys/param.h>
44 #include <sys/systm.h>
45 
46 #include <machine/vmm.h>
47 
48 #include "vatpic.h"
49 #include "vatpit.h"
50 #include "vrtc.h"
51 #include "vmm_ioport.h"
52 
53 /* Arbitrary limit on entries per VM */
54 static uint_t ioport_entry_limit = 64;
55 
56 static void
vm_inout_def(ioport_entry_t * entries,uint_t i,uint16_t port,ioport_handler_t func,void * arg,uint16_t flags)57 vm_inout_def(ioport_entry_t *entries, uint_t i, uint16_t port,
58     ioport_handler_t func, void *arg, uint16_t flags)
59 {
60 	ioport_entry_t *ent = &entries[i];
61 
62 	if (i != 0) {
63 		const ioport_entry_t *prev = &entries[i - 1];
64 		/* ensure that entries are inserted in sorted order */
65 		VERIFY(prev->iope_port < port);
66 	}
67 	ent->iope_func = func;
68 	ent->iope_arg = arg;
69 	ent->iope_port = port;
70 	ent->iope_flags = flags;
71 }
72 
73 void
vm_inout_init(struct vm * vm,struct ioport_config * cfg)74 vm_inout_init(struct vm *vm, struct ioport_config *cfg)
75 {
76 	struct vatpit *pit = vm_atpit(vm);
77 	struct vatpic *pic = vm_atpic(vm);
78 	struct vrtc *rtc = vm_rtc(vm);
79 	const uint_t ndefault = 13;
80 	const uint16_t flag = IOPF_FIXED;
81 	ioport_entry_t *ents;
82 	uint_t i = 0;
83 
84 	VERIFY0(cfg->iop_entries);
85 	VERIFY0(cfg->iop_count);
86 
87 	ents = kmem_zalloc(ndefault * sizeof (ioport_entry_t), KM_SLEEP);
88 
89 	/* PIC (master): 0x20-0x21 */
90 	vm_inout_def(ents, i++, IO_ICU1, vatpic_master_handler, pic, flag);
91 	vm_inout_def(ents, i++, IO_ICU1 + ICU_IMR_OFFSET, vatpic_master_handler,
92 	    pic, flag);
93 
94 	/* PIT: 0x40-0x43 and 0x61 (ps2 tie-in) */
95 	vm_inout_def(ents, i++, TIMER_CNTR0, vatpit_handler, pit, flag);
96 	vm_inout_def(ents, i++, TIMER_CNTR1, vatpit_handler, pit, flag);
97 	vm_inout_def(ents, i++, TIMER_CNTR2, vatpit_handler, pit, flag);
98 	vm_inout_def(ents, i++, TIMER_MODE, vatpit_handler, pit, flag);
99 	vm_inout_def(ents, i++, NMISC_PORT, vatpit_nmisc_handler, pit, flag);
100 
101 	/* RTC: 0x70-0x71 */
102 	vm_inout_def(ents, i++, IO_RTC, vrtc_addr_handler, rtc, flag);
103 	vm_inout_def(ents, i++, IO_RTC + 1, vrtc_data_handler, rtc, flag);
104 
105 	/* PIC (slave): 0xa0-0xa1 */
106 	vm_inout_def(ents, i++, IO_ICU2, vatpic_slave_handler, pic, flag);
107 	vm_inout_def(ents, i++, IO_ICU2 + ICU_IMR_OFFSET, vatpic_slave_handler,
108 	    pic, flag);
109 
110 	/* PIC (ELCR): 0x4d0-0x4d1 */
111 	vm_inout_def(ents, i++, IO_ELCR1, vatpic_elc_handler, pic, flag);
112 	vm_inout_def(ents, i++, IO_ELCR2, vatpic_elc_handler, pic, flag);
113 
114 	VERIFY3U(i, ==, ndefault);
115 	cfg->iop_entries = ents;
116 	cfg->iop_count = ndefault;
117 }
118 
119 void
vm_inout_cleanup(struct vm * vm,struct ioport_config * cfg)120 vm_inout_cleanup(struct vm *vm, struct ioport_config *cfg)
121 {
122 	VERIFY(cfg->iop_entries);
123 	VERIFY(cfg->iop_count);
124 
125 	kmem_free(cfg->iop_entries,
126 	    sizeof (ioport_entry_t) * cfg->iop_count);
127 	cfg->iop_entries = NULL;
128 	cfg->iop_count = 0;
129 }
130 
131 static void
vm_inout_remove_at(uint_t idx,uint_t old_count,ioport_entry_t * old_ents,ioport_entry_t * new_ents)132 vm_inout_remove_at(uint_t idx, uint_t old_count, ioport_entry_t *old_ents,
133     ioport_entry_t *new_ents)
134 {
135 	uint_t new_count = old_count - 1;
136 
137 	VERIFY(old_count != 0);
138 	VERIFY(idx < old_count);
139 
140 	/* copy entries preceeding to-be-removed index */
141 	if (idx > 0) {
142 		bcopy(old_ents, new_ents, sizeof (ioport_entry_t) * idx);
143 	}
144 	/* copy entries following to-be-removed index */
145 	if (idx < new_count) {
146 		bcopy(&old_ents[idx + 1], &new_ents[idx],
147 		    sizeof (ioport_entry_t) * (new_count - idx));
148 	}
149 }
150 
151 static void
vm_inout_insert_space_at(uint_t idx,uint_t old_count,ioport_entry_t * old_ents,ioport_entry_t * new_ents)152 vm_inout_insert_space_at(uint_t idx, uint_t old_count, ioport_entry_t *old_ents,
153     ioport_entry_t *new_ents)
154 {
155 	uint_t new_count = old_count + 1;
156 
157 	VERIFY(idx < new_count);
158 
159 	/* copy entries preceeding index where space is to be added */
160 	if (idx > 0) {
161 		bcopy(old_ents, new_ents, sizeof (ioport_entry_t) * idx);
162 	}
163 	/* copy entries to follow added space */
164 	if (idx < new_count) {
165 		bcopy(&old_ents[idx], &new_ents[idx + 1],
166 		    sizeof (ioport_entry_t) * (old_count - idx));
167 	}
168 }
169 
170 int
vm_inout_attach(struct ioport_config * cfg,uint16_t port,uint16_t flags,ioport_handler_t func,void * arg)171 vm_inout_attach(struct ioport_config *cfg, uint16_t port, uint16_t flags,
172     ioport_handler_t func, void *arg)
173 {
174 	uint_t i, old_count, insert_idx;
175 	ioport_entry_t *old_ents;
176 
177 	if (cfg->iop_count >= ioport_entry_limit) {
178 		return (ENOSPC);
179 	}
180 
181 	old_count = cfg->iop_count;
182 	old_ents = cfg->iop_entries;
183 	for (insert_idx = i = 0; i < old_count; i++) {
184 		const ioport_entry_t *compare = &old_ents[i];
185 		if (compare->iope_port == port) {
186 			return (EEXIST);
187 		} else if (compare->iope_port < port) {
188 			insert_idx = i + 1;
189 		}
190 	}
191 
192 
193 	ioport_entry_t *new_ents;
194 	uint_t new_count = old_count + 1;
195 	new_ents = kmem_alloc(new_count * sizeof (ioport_entry_t), KM_SLEEP);
196 	vm_inout_insert_space_at(insert_idx, old_count, old_ents, new_ents);
197 
198 	new_ents[insert_idx].iope_func = func;
199 	new_ents[insert_idx].iope_arg = arg;
200 	new_ents[insert_idx].iope_port = port;
201 	new_ents[insert_idx].iope_flags = flags;
202 	new_ents[insert_idx].iope_pad = 0;
203 
204 	cfg->iop_entries = new_ents;
205 	cfg->iop_count = new_count;
206 	kmem_free(old_ents, old_count * sizeof (ioport_entry_t));
207 
208 	return (0);
209 }
210 
211 int
vm_inout_detach(struct ioport_config * cfg,uint16_t port,bool drv_hook,ioport_handler_t * old_func,void ** old_arg)212 vm_inout_detach(struct ioport_config *cfg, uint16_t port, bool drv_hook,
213     ioport_handler_t *old_func, void **old_arg)
214 {
215 	uint_t i, old_count, remove_idx;
216 	ioport_entry_t *old_ents;
217 
218 	old_count = cfg->iop_count;
219 	old_ents = cfg->iop_entries;
220 	VERIFY(old_count > 1);
221 	for (i = 0; i < old_count; i++) {
222 		const ioport_entry_t *compare = &old_ents[i];
223 		if (compare->iope_port != port) {
224 			continue;
225 		}
226 		/* fixed ports are not allowed to be detached at runtime */
227 		if ((compare->iope_flags & IOPF_FIXED) != 0) {
228 			return (EPERM);
229 		}
230 
231 		/*
232 		 * Driver-attached and bhyve-internal ioport hooks can only be
233 		 * removed by the respective party which attached them.
234 		 */
235 		if (drv_hook && (compare->iope_flags & IOPF_DRV_HOOK) == 0) {
236 			return (EPERM);
237 		} else if (!drv_hook &&
238 		    (compare->iope_flags & IOPF_DRV_HOOK) != 0) {
239 			return (EPERM);
240 		}
241 		break;
242 	}
243 	if (i == old_count) {
244 		return (ENOENT);
245 	}
246 	remove_idx = i;
247 
248 	if (old_func != NULL) {
249 		*old_func = cfg->iop_entries[remove_idx].iope_func;
250 	}
251 	if (old_arg != NULL) {
252 		*old_arg = cfg->iop_entries[remove_idx].iope_arg;
253 	}
254 
255 	ioport_entry_t *new_ents;
256 	uint_t new_count = old_count - 1;
257 	new_ents = kmem_alloc(new_count * sizeof (ioport_entry_t), KM_SLEEP);
258 	vm_inout_remove_at(remove_idx, old_count, old_ents, new_ents);
259 
260 	cfg->iop_entries = new_ents;
261 	cfg->iop_count = new_count;
262 	kmem_free(old_ents, old_count * sizeof (ioport_entry_t));
263 
264 	return (0);
265 }
266 
267 static ioport_entry_t *
vm_inout_find(const struct ioport_config * cfg,uint16_t port)268 vm_inout_find(const struct ioport_config *cfg, uint16_t port)
269 {
270 	const uint_t count = cfg->iop_count;
271 	ioport_entry_t *entries = cfg->iop_entries;
272 
273 	for (uint_t i = 0; i < count; i++) {
274 		if (entries[i].iope_port == port) {
275 			return (&entries[i]);
276 		}
277 	}
278 	return (NULL);
279 }
280 
281 int
vm_inout_access(struct ioport_config * cfg,bool in,uint16_t port,uint8_t bytes,uint32_t * val)282 vm_inout_access(struct ioport_config *cfg, bool in, uint16_t port,
283     uint8_t bytes, uint32_t *val)
284 {
285 	const ioport_entry_t *ent;
286 	int err;
287 
288 	ent = vm_inout_find(cfg, port);
289 	if (ent == NULL) {
290 		err = ESRCH;
291 	} else {
292 		err = ent->iope_func(ent->iope_arg, in, port, bytes, val);
293 	}
294 
295 	return (err);
296 }
297