1 /* 2 * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include <sys/types.h> 18 19 #include <dev/ic/comreg.h> 20 21 #include <machine/vmmvar.h> 22 23 #include <errno.h> 24 #include <event.h> 25 #include <pthread.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 #include "ns8250.h" 30 #include "proc.h" 31 #include "vmd.h" 32 #include "vmm.h" 33 34 extern char *__progname; 35 struct ns8250_dev com1_dev; 36 37 static void com_rcv_event(int, short, void *); 38 static void com_rcv(struct ns8250_dev *, uint32_t, uint32_t); 39 40 void 41 ns8250_init(int fd, uint32_t vmid) 42 { 43 int ret; 44 45 memset(&com1_dev, 0, sizeof(com1_dev)); 46 ret = pthread_mutex_init(&com1_dev.mutex, NULL); 47 if (ret) { 48 errno = ret; 49 fatal("could not initialize com1 mutex"); 50 } 51 com1_dev.fd = fd; 52 com1_dev.irq = 4; 53 com1_dev.rcv_pending = 0; 54 55 event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST, 56 com_rcv_event, (void *)(uint64_t)vmid); 57 event_add(&com1_dev.event, NULL); 58 } 59 60 static void 61 com_rcv_event(int fd, short kind, void *arg) 62 { 63 mutex_lock(&com1_dev.mutex); 64 65 /* 66 * We already have other data pending to be received. The data that 67 * has become available now will be moved to the com port later. 68 */ 69 if (com1_dev.rcv_pending) { 70 mutex_unlock(&com1_dev.mutex); 71 return; 72 } 73 74 if (com1_dev.regs.lsr & LSR_RXRDY) 75 com1_dev.rcv_pending = 1; 76 else { 77 com_rcv(&com1_dev, (uintptr_t)arg, 0); 78 79 /* If pending interrupt, inject */ 80 if ((com1_dev.regs.iir & 0x1) == 0) { 81 /* XXX: vcpu_id */ 82 vcpu_assert_pic_irq((uintptr_t)arg, 0, com1_dev.irq); 83 } 84 } 85 86 mutex_unlock(&com1_dev.mutex); 87 } 88 89 /* 90 * com_rcv 91 * 92 * Move received byte into com data register. 93 * Must be called with the mutex of the com device acquired 94 */ 95 static void 96 com_rcv(struct ns8250_dev *com, uint32_t vm_id, uint32_t vcpu_id) 97 { 98 char ch; 99 ssize_t sz; 100 101 /* 102 * Is there a new character available on com1? 103 * If so, consume the character, buffer it into the com1 data register 104 * assert IRQ4, and set the line status register RXRDY bit. 105 */ 106 sz = read(com->fd, &ch, sizeof(char)); 107 if (sz == -1) { 108 /* 109 * If we get EAGAIN, we'll retry and get the character later. 110 * This error can happen when typing two characters at once 111 * at the keyboard, for example. 112 */ 113 if (errno != EAGAIN) 114 log_warn("unexpected read error on com device"); 115 } else if (sz != 1) 116 log_warnx("unexpected read return value on com device"); 117 else { 118 com->regs.lsr |= LSR_RXRDY; 119 com->regs.data = ch; 120 /* XXX these ier and iir bits should be IER_x and IIR_x */ 121 if (com->regs.ier & 0x1) { 122 com->regs.iir |= (2 << 1); 123 com->regs.iir &= ~0x1; 124 } 125 } 126 127 com->rcv_pending = fd_hasdata(com->fd); 128 } 129 130 131 /* 132 * vcpu_process_com_data 133 * 134 * Emulate in/out instructions to the com1 (ns8250) UART data register 135 * 136 * Parameters: 137 * vei: vm exit information from vmm(4) containing information on the in/out 138 * instruction being performed 139 * 140 * Return value: 141 * interrupt to inject, or 0xFF if nothing to inject 142 */ 143 uint8_t 144 vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id) 145 { 146 /* 147 * vei_dir == VEI_DIR_OUT : out instruction 148 * 149 * The guest wrote to the data register. Since we are emulating a 150 * no-fifo chip, write the character immediately to the pty and 151 * assert TXRDY in IIR (if the guest has requested TXRDY interrupt 152 * reporting) 153 */ 154 if (vei->vei.vei_dir == VEI_DIR_OUT) { 155 write(com1_dev.fd, &vei->vei.vei_data, 1); 156 if (com1_dev.regs.ier & 0x2) { 157 /* Set TXRDY */ 158 com1_dev.regs.iir |= IIR_TXRDY; 159 /* Set "interrupt pending" (IIR low bit cleared) */ 160 com1_dev.regs.iir &= ~0x1; 161 } 162 } else { 163 /* 164 * vei_dir == VEI_DIR_IN : in instruction 165 * 166 * The guest read from the data register. Check to see if 167 * there is data available (RXRDY) and if so, consume the 168 * input data and return to the guest. Also clear the 169 * interrupt info register regardless. 170 */ 171 if (com1_dev.regs.lsr & LSR_RXRDY) { 172 vei->vei.vei_data = com1_dev.regs.data; 173 com1_dev.regs.data = 0x0; 174 com1_dev.regs.lsr &= ~LSR_RXRDY; 175 } else { 176 /* XXX should this be com1_dev.data or 0xff? */ 177 vei->vei.vei_data = com1_dev.regs.data; 178 log_warnx("guest reading com1 when not ready"); 179 } 180 181 /* Reading the data register always clears RXRDY from IIR */ 182 com1_dev.regs.iir &= ~IIR_RXRDY; 183 184 /* 185 * Clear "interrupt pending" by setting IIR low bit to 1 186 * if no interrupt are pending 187 */ 188 if (com1_dev.regs.iir == 0x0) 189 com1_dev.regs.iir = 0x1; 190 191 if (com1_dev.rcv_pending) 192 com_rcv(&com1_dev, vm_id, vcpu_id); 193 } 194 195 /* If pending interrupt, make sure it gets injected */ 196 if ((com1_dev.regs.iir & 0x1) == 0) 197 return (com1_dev.irq); 198 return (0xFF); 199 } 200 201 /* 202 * vcpu_process_com_lcr 203 * 204 * Emulate in/out instructions to the com1 (ns8250) UART line control register 205 * 206 * Paramters: 207 * vei: vm exit information from vmm(4) containing information on the in/out 208 * instruction being performed 209 */ 210 void 211 vcpu_process_com_lcr(union vm_exit *vei) 212 { 213 /* 214 * vei_dir == VEI_DIR_OUT : out instruction 215 * 216 * Write content to line control register 217 */ 218 if (vei->vei.vei_dir == VEI_DIR_OUT) { 219 com1_dev.regs.lcr = (uint8_t)vei->vei.vei_data; 220 } else { 221 /* 222 * vei_dir == VEI_DIR_IN : in instruction 223 * 224 * Read line control register 225 */ 226 vei->vei.vei_data = com1_dev.regs.lcr; 227 } 228 } 229 230 /* 231 * vcpu_process_com_iir 232 * 233 * Emulate in/out instructions to the com1 (ns8250) UART interrupt information 234 * register. Note that writes to this register actually are to a different 235 * register, the FCR (FIFO control register) that we don't emulate but still 236 * consume the data provided. 237 * 238 * Parameters: 239 * vei: vm exit information from vmm(4) containing information on the in/out 240 * instruction being performed 241 */ 242 void 243 vcpu_process_com_iir(union vm_exit *vei) 244 { 245 /* 246 * vei_dir == VEI_DIR_OUT : out instruction 247 * 248 * Write to FCR 249 */ 250 if (vei->vei.vei_dir == VEI_DIR_OUT) { 251 com1_dev.regs.fcr = vei->vei.vei_data; 252 } else { 253 /* 254 * vei_dir == VEI_DIR_IN : in instruction 255 * 256 * Read IIR. Reading the IIR resets the TXRDY bit in the IIR 257 * after the data is read. 258 */ 259 vei->vei.vei_data = com1_dev.regs.iir; 260 com1_dev.regs.iir &= ~IIR_TXRDY; 261 262 /* 263 * Clear "interrupt pending" by setting IIR low bit to 1 264 * if no interrupts are pending 265 */ 266 if (com1_dev.regs.iir == 0x0) 267 com1_dev.regs.iir = 0x1; 268 } 269 } 270 271 /* 272 * vcpu_process_com_mcr 273 * 274 * Emulate in/out instructions to the com1 (ns8250) UART modem control 275 * register. 276 * 277 * Parameters: 278 * vei: vm exit information from vmm(4) containing information on the in/out 279 * instruction being performed 280 */ 281 void 282 vcpu_process_com_mcr(union vm_exit *vei) 283 { 284 /* 285 * vei_dir == VEI_DIR_OUT : out instruction 286 * 287 * Write to MCR 288 */ 289 if (vei->vei.vei_dir == VEI_DIR_OUT) { 290 com1_dev.regs.mcr = vei->vei.vei_data; 291 } else { 292 /* 293 * vei_dir == VEI_DIR_IN : in instruction 294 * 295 * Read from MCR 296 */ 297 vei->vei.vei_data = com1_dev.regs.mcr; 298 } 299 } 300 301 /* 302 * vcpu_process_com_lsr 303 * 304 * Emulate in/out instructions to the com1 (ns8250) UART line status register. 305 * 306 * Parameters: 307 * vei: vm exit information from vmm(4) containing information on the in/out 308 * instruction being performed 309 */ 310 void 311 vcpu_process_com_lsr(union vm_exit *vei) 312 { 313 /* 314 * vei_dir == VEI_DIR_OUT : out instruction 315 * 316 * Write to LSR. This is an illegal operation, so we just log it and 317 * continue. 318 */ 319 if (vei->vei.vei_dir == VEI_DIR_OUT) { 320 log_warnx("%s: LSR UART write 0x%x unsupported", 321 __progname, vei->vei.vei_data); 322 } else { 323 /* 324 * vei_dir == VEI_DIR_IN : in instruction 325 * 326 * Read from LSR. We always report TXRDY and TSRE since we 327 * can process output characters immediately (at any time). 328 */ 329 vei->vei.vei_data = com1_dev.regs.lsr | LSR_TSRE | LSR_TXRDY; 330 } 331 } 332 333 /* 334 * vcpu_process_com_msr 335 * 336 * Emulate in/out instructions to the com1 (ns8250) UART modem status register. 337 * 338 * Parameters: 339 * vei: vm exit information from vmm(4) containing information on the in/out 340 * instruction being performed 341 */ 342 void 343 vcpu_process_com_msr(union vm_exit *vei) 344 { 345 /* 346 * vei_dir == VEI_DIR_OUT : out instruction 347 * 348 * Write to MSR. This is an illegal operation, so we just log it and 349 * continue. 350 */ 351 if (vei->vei.vei_dir == VEI_DIR_OUT) { 352 log_warnx("%s: MSR UART write 0x%x unsupported", 353 __progname, vei->vei.vei_data); 354 } else { 355 /* 356 * vei_dir == VEI_DIR_IN : in instruction 357 * 358 * Read from MSR. We always report DCD, DSR, and CTS. 359 */ 360 vei->vei.vei_data = 361 com1_dev.regs.lsr | MSR_DCD | MSR_DSR | MSR_CTS; 362 } 363 } 364 365 /* 366 * vcpu_process_com_scr 367 * 368 * Emulate in/out instructions to the com1 (ns8250) UART scratch register. The 369 * scratch register is sometimes used to distinguish an 8250 from a 16450, 370 * and/or used to distinguish submodels of the 8250 (eg 8250A, 8250B). We 371 * simulate an "original" 8250 by forcing the scratch register to return data 372 * on read that is different from what was written. 373 * 374 * Parameters: 375 * vei: vm exit information from vmm(4) containing information on the in/out 376 * instruction being performed 377 */ 378 void 379 vcpu_process_com_scr(union vm_exit *vei) 380 { 381 /* 382 * vei_dir == VEI_DIR_OUT : out instruction 383 * 384 * Write to SCR 385 */ 386 if (vei->vei.vei_dir == VEI_DIR_OUT) { 387 com1_dev.regs.scr = vei->vei.vei_data; 388 } else { 389 /* 390 * vei_dir == VEI_DIR_IN : in instruction 391 * 392 * Read from SCR. To make sure we don't accidentally simulate 393 * a real scratch register, we negate what was written on 394 * subsequent readback. 395 */ 396 vei->vei.vei_data = ~com1_dev.regs.scr; 397 } 398 } 399 400 /* 401 * vcpu_process_com_ier 402 * 403 * Emulate in/out instructions to the com1 (ns8250) UART interrupt enable 404 * register. 405 * 406 * Parameters: 407 * vei: vm exit information from vmm(4) containing information on the in/out 408 * instruction being performed 409 */ 410 void 411 vcpu_process_com_ier(union vm_exit *vei) 412 { 413 /* 414 * vei_dir == VEI_DIR_OUT : out instruction 415 * 416 * Write to IER 417 */ 418 if (vei->vei.vei_dir == VEI_DIR_OUT) { 419 com1_dev.regs.ier = vei->vei.vei_data; 420 } else { 421 /* 422 * vei_dir == VEI_DIR_IN : in instruction 423 * 424 * Read from IER 425 */ 426 vei->vei.vei_data = com1_dev.regs.ier; 427 } 428 } 429 430 /* 431 * vcpu_exit_com 432 * 433 * Process com1 (ns8250) UART exits. vmd handles most basic 8250 434 * features with the exception of the divisor latch (eg, no baud 435 * rate support) 436 * 437 * Parameters: 438 * vrp: vcpu run parameters containing guest state for this exit 439 * 440 * Return value: 441 * Interrupt to inject to the guest VM, or 0xFF if no interrupt should 442 * be injected. 443 */ 444 uint8_t 445 vcpu_exit_com(struct vm_run_params *vrp) 446 { 447 uint8_t intr = 0xFF; 448 union vm_exit *vei = vrp->vrp_exit; 449 450 mutex_lock(&com1_dev.mutex); 451 452 switch (vei->vei.vei_port) { 453 case COM1_LCR: 454 vcpu_process_com_lcr(vei); 455 break; 456 case COM1_IER: 457 vcpu_process_com_ier(vei); 458 break; 459 case COM1_IIR: 460 vcpu_process_com_iir(vei); 461 break; 462 case COM1_MCR: 463 vcpu_process_com_mcr(vei); 464 break; 465 case COM1_LSR: 466 vcpu_process_com_lsr(vei); 467 break; 468 case COM1_MSR: 469 vcpu_process_com_msr(vei); 470 break; 471 case COM1_SCR: 472 vcpu_process_com_scr(vei); 473 break; 474 case COM1_DATA: 475 intr = vcpu_process_com_data(vei, vrp->vrp_vm_id, 476 vrp->vrp_vcpu_id); 477 break; 478 } 479 480 mutex_unlock(&com1_dev.mutex); 481 return (intr); 482 } 483