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