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 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 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 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 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 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 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 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 * 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 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