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