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