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