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