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