1 /* $OpenBSD: i8259.c,v 1.20 2021/06/16 16:55:02 dv Exp $ */ 2 /* 3 * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <string.h> 19 20 #include <sys/types.h> 21 22 #include <dev/isa/isareg.h> 23 24 #include <machine/vmmvar.h> 25 26 #include <unistd.h> 27 #include <pthread.h> 28 29 #include "atomicio.h" 30 #include "i8259.h" 31 #include "vmd.h" 32 #include "vmm.h" 33 34 struct i8259 { 35 uint8_t irr; 36 uint8_t imr; 37 uint8_t isr; 38 uint8_t smm; 39 uint8_t poll; 40 uint8_t cur_icw; 41 uint8_t init_mode; 42 uint8_t vec; 43 uint8_t irq_conn; 44 uint8_t next_ocw_read; 45 uint8_t auto_eoi; 46 uint8_t rotate_auto_eoi; 47 uint8_t lowest_pri; 48 uint8_t asserted; 49 }; 50 51 /* Edge Level Control Registers */ 52 uint8_t elcr[2]; 53 54 #define PIC_IRR 0 55 #define PIC_ISR 1 56 57 /* Master and slave PICs */ 58 struct i8259 pics[2]; 59 pthread_mutex_t pic_mtx; 60 61 /* 62 * i8259_pic_name 63 * 64 * Converts a pic ID (MASTER, SLAVE} to a string, suitable for printing in 65 * debug or log messages. 66 * 67 * Parameters: 68 * picid: PIC ID 69 * 70 * Return value: 71 * string representation of the PIC ID supplied 72 */ 73 static const char * 74 i8259_pic_name(uint8_t picid) 75 { 76 switch (picid) { 77 case MASTER: return "master"; 78 case SLAVE: return "slave"; 79 default: return "unknown"; 80 } 81 } 82 83 /* 84 * i8259_init 85 * 86 * Initialize the emulated i8259 PIC. 87 */ 88 void 89 i8259_init(void) 90 { 91 memset(&pics, 0, sizeof(pics)); 92 pics[MASTER].cur_icw = 1; 93 pics[SLAVE].cur_icw = 1; 94 95 elcr[MASTER] = 0; 96 elcr[SLAVE] = 0; 97 98 if (pthread_mutex_init(&pic_mtx, NULL) != 0) 99 fatalx("unable to create pic mutex"); 100 } 101 102 /* 103 * i8259_is_pending 104 * 105 * Determine if an IRQ is pending on either the slave or master PIC. 106 * 107 * Return Values: 108 * 1 if an IRQ (any IRQ) is pending, 0 otherwise 109 */ 110 uint8_t 111 i8259_is_pending(void) 112 { 113 uint8_t master_pending; 114 uint8_t slave_pending; 115 116 mutex_lock(&pic_mtx); 117 master_pending = pics[MASTER].irr & ~(pics[MASTER].imr | (1 << 2)); 118 slave_pending = pics[SLAVE].irr & ~pics[SLAVE].imr; 119 mutex_unlock(&pic_mtx); 120 121 return (master_pending || slave_pending); 122 } 123 124 /* 125 * i8259_ack 126 * 127 * This function is called when the vcpu exits and is ready to accept an 128 * interrupt. 129 * 130 * Return values: 131 * interrupt vector to inject, 0xFFFF if no irq pending 132 */ 133 uint16_t 134 i8259_ack(void) 135 { 136 uint8_t high_prio_m, high_prio_s; 137 uint8_t i; 138 uint16_t ret; 139 140 ret = 0xFFFF; 141 142 mutex_lock(&pic_mtx); 143 144 if (pics[MASTER].asserted == 0 && pics[SLAVE].asserted == 0) { 145 log_warnx("%s: i8259 ack without assert?", __func__); 146 goto ret; 147 } 148 149 high_prio_m = pics[MASTER].lowest_pri + 1; 150 if (high_prio_m > 7) 151 high_prio_m = 0; 152 153 high_prio_s = pics[SLAVE].lowest_pri + 1; 154 if (high_prio_s > 7) 155 high_prio_s = 0; 156 157 i = high_prio_m; 158 do { 159 if ((pics[MASTER].irr & (1 << i)) && i != 2 && 160 !(pics[MASTER].imr & (1 << i))) { 161 /* Master PIC has highest prio and ready IRQ */ 162 pics[MASTER].irr &= ~(1 << i); 163 pics[MASTER].isr |= (1 << i); 164 165 if (pics[MASTER].irr == 0) 166 pics[MASTER].asserted = 0; 167 168 ret = i + pics[MASTER].vec; 169 goto ret; 170 } 171 172 i++; 173 174 if (i > 7) 175 i = 0; 176 177 } while (i != high_prio_m); 178 179 i = high_prio_s; 180 do { 181 if ((pics[SLAVE].irr & (1 << i)) && 182 !(pics[SLAVE].imr & (1 << i))) { 183 /* Slave PIC has highest prio and ready IRQ */ 184 pics[SLAVE].irr &= ~(1 << i); 185 pics[MASTER].irr &= ~(1 << 2); 186 187 pics[SLAVE].isr |= (1 << i); 188 pics[MASTER].isr |= (1 << 2); 189 190 if (pics[SLAVE].irr == 0) { 191 pics[SLAVE].asserted = 0; 192 if (pics[MASTER].irr == 0) 193 pics[MASTER].asserted = 0; 194 } 195 196 ret = i + pics[SLAVE].vec; 197 goto ret; 198 } 199 200 i++; 201 202 if (i > 7) 203 i = 0; 204 } while (i != high_prio_s); 205 206 log_warnx("%s: ack without pending irq?", __func__); 207 ret: 208 mutex_unlock(&pic_mtx); 209 return (ret); 210 } 211 212 /* 213 * i8259_assert_irq 214 * 215 * Asserts the IRQ specified 216 * 217 * Parameters: 218 * irq: the IRQ to assert 219 */ 220 void 221 i8259_assert_irq(uint8_t irq) 222 { 223 mutex_lock(&pic_mtx); 224 if (irq <= 7) { 225 if (!ISSET(pics[MASTER].imr, 1 << irq)) { 226 SET(pics[MASTER].irr, 1 << irq); 227 pics[MASTER].asserted = 1; 228 } 229 } else { 230 irq -= 8; 231 if (!ISSET(pics[SLAVE].imr, 1 << irq)) { 232 SET(pics[SLAVE].irr, 1 << irq); 233 pics[SLAVE].asserted = 1; 234 235 /* Assert cascade IRQ on master PIC */ 236 SET(pics[MASTER].irr, 1 << 2); 237 pics[MASTER].asserted = 1; 238 } 239 } 240 mutex_unlock(&pic_mtx); 241 } 242 243 /* 244 * i8259_deassert_irq 245 * 246 * Deasserts the IRQ specified 247 * 248 * Parameters: 249 * irq: the IRQ to deassert 250 */ 251 void 252 i8259_deassert_irq(uint8_t irq) 253 { 254 mutex_lock(&pic_mtx); 255 if (irq <= 7) { 256 if (elcr[MASTER] & (1 << irq)) 257 CLR(pics[MASTER].irr, 1 << irq); 258 } else { 259 irq -= 8; 260 if (elcr[SLAVE] & (1 << irq)) { 261 CLR(pics[SLAVE].irr, 1 << irq); 262 263 /* 264 * Deassert cascade IRQ on master if no IRQs on 265 * slave 266 */ 267 if (pics[SLAVE].irr == 0) 268 CLR(pics[MASTER].irr, 1 << 2); 269 } 270 } 271 mutex_unlock(&pic_mtx); 272 } 273 274 /* 275 * i8259_write_datareg 276 * 277 * Write to a specified data register in the emulated PIC during PIC 278 * initialization. The data write follows the state model in the i8259 (in 279 * other words, data is expected to be written in a specific order). 280 * 281 * Parameters: 282 * n: PIC to write to (MASTER/SLAVE) 283 * data: data to write 284 */ 285 static void 286 i8259_write_datareg(uint8_t n, uint8_t data) 287 { 288 struct i8259 *pic = &pics[n]; 289 290 if (pic->init_mode == 1) { 291 if (pic->cur_icw == 2) { 292 /* Set vector */ 293 log_debug("%s: %s pic, reset IRQ vector to 0x%x", 294 __func__, i8259_pic_name(n), data); 295 pic->vec = data; 296 } else if (pic->cur_icw == 3) { 297 /* Set IRQ interconnects */ 298 if (n == SLAVE && (data & 0xf8)) { 299 log_warnx("%s: %s pic invalid icw2 0x%x", 300 __func__, i8259_pic_name(n), data); 301 return; 302 } 303 pic->irq_conn = data; 304 } else if (pic->cur_icw == 4) { 305 if (!(data & ICW4_UP)) { 306 log_warnx("%s: %s pic init error: x86 bit " 307 "clear", __func__, i8259_pic_name(n)); 308 return; 309 } 310 311 if (data & ICW4_AEOI) { 312 log_warnx("%s: %s pic: aeoi mode set", 313 __func__, i8259_pic_name(n)); 314 pic->auto_eoi = 1; 315 return; 316 } 317 318 if (data & ICW4_MS) { 319 log_warnx("%s: %s pic init error: M/S mode", 320 __func__, i8259_pic_name(n)); 321 return; 322 } 323 324 if (data & ICW4_BUF) { 325 log_warnx("%s: %s pic init error: buf mode", 326 __func__, i8259_pic_name(n)); 327 return; 328 } 329 330 if (data & 0xe0) { 331 log_warnx("%s: %s pic init error: invalid icw4 " 332 " 0x%x", __func__, i8259_pic_name(n), data); 333 return; 334 } 335 } 336 337 pic->cur_icw++; 338 if (pic->cur_icw == 5) { 339 pic->cur_icw = 1; 340 pic->init_mode = 0; 341 } 342 } else 343 pic->imr = data; 344 } 345 346 /* 347 * i8259_specific_eoi 348 * 349 * Handles specific end of interrupt commands 350 * 351 * Parameters: 352 * n: PIC to deliver this EOI to 353 * data: interrupt to EOI 354 */ 355 static void 356 i8259_specific_eoi(uint8_t n, uint8_t data) 357 { 358 if (!(pics[n].isr & (1 << (data & 0x7)))) { 359 log_warnx("%s: %s pic specific eoi irq %d while not in" 360 " service", __func__, i8259_pic_name(n), (data & 0x7)); 361 } 362 363 pics[n].isr &= ~(1 << (data & 0x7)); 364 } 365 366 /* 367 * i8259_nonspecific_eoi 368 * 369 * Handles nonspecific end of interrupt commands 370 * XXX not implemented 371 */ 372 static void 373 i8259_nonspecific_eoi(uint8_t n, uint8_t data) 374 { 375 int i = 0; 376 377 while (i < 8) { 378 if ((pics[n].isr & (1 << (i & 0x7)))) { 379 i8259_specific_eoi(n, i); 380 return; 381 } 382 i++; 383 } 384 } 385 386 /* 387 * i8259_rotate_priority 388 * 389 * Rotates the interrupt priority on the specified PIC 390 * 391 * Parameters: 392 * n: PIC whose priority should be rotated 393 */ 394 static void 395 i8259_rotate_priority(uint8_t n) 396 { 397 pics[n].lowest_pri++; 398 if (pics[n].lowest_pri > 7) 399 pics[n].lowest_pri = 0; 400 } 401 402 /* 403 * i8259_write_cmdreg 404 * 405 * Write to the PIC command register 406 * 407 * Parameters: 408 * n: PIC whose command register should be written to 409 * data: data to write 410 */ 411 static void 412 i8259_write_cmdreg(uint8_t n, uint8_t data) 413 { 414 struct i8259 *pic = &pics[n]; 415 416 if (data & ICW1_INIT) { 417 /* Validate init params */ 418 if (!(data & ICW1_ICW4)) { 419 log_warnx("%s: %s pic init error: no ICW4 request", 420 __func__, i8259_pic_name(n)); 421 return; 422 } 423 424 if (data & (ICW1_IVA1 | ICW1_IVA2 | ICW1_IVA3)) { 425 log_warnx("%s: %s pic init error: IVA specified", 426 __func__, i8259_pic_name(n)); 427 return; 428 } 429 430 if (data & ICW1_SNGL) { 431 log_warnx("%s: %s pic init error: single pic mode", 432 __func__, i8259_pic_name(n)); 433 return; 434 } 435 436 if (data & ICW1_ADI) { 437 log_warnx("%s: %s pic init error: address interval", 438 __func__, i8259_pic_name(n)); 439 return; 440 } 441 442 if (data & ICW1_LTIM) { 443 log_warnx("%s: %s pic init error: level trigger mode", 444 __func__, i8259_pic_name(n)); 445 return; 446 } 447 448 pic->init_mode = 1; 449 pic->cur_icw = 2; 450 pic->imr = 0; 451 pic->isr = 0; 452 pic->irr = 0; 453 pic->asserted = 0; 454 pic->lowest_pri = 7; 455 pic->rotate_auto_eoi = 0; 456 return; 457 } else if (data & OCW_SELECT) { 458 /* OCW3 */ 459 if (data & OCW3_ACTION) { 460 if (data & OCW3_RR) { 461 if (data & OCW3_RIS) 462 pic->next_ocw_read = PIC_ISR; 463 else 464 pic->next_ocw_read = PIC_IRR; 465 } 466 } 467 468 if (data & OCW3_SMACTION) { 469 if (data & OCW3_SMM) { 470 pic->smm = 1; 471 /* XXX update intr here */ 472 } else 473 pic->smm = 0; 474 } 475 476 if (data & OCW3_POLL) { 477 pic->poll = 1; 478 /* XXX update intr here */ 479 } 480 481 return; 482 } else { 483 /* OCW2 */ 484 if (data & OCW2_EOI) { 485 /* 486 * An EOI command was received. It could be one of 487 * several different varieties: 488 * 489 * Nonspecific EOI (0x20) 490 * Specific EOI (0x60..0x67) 491 * Nonspecific EOI + rotate (0xA0) 492 * Specific EOI + rotate (0xE0..0xE7) 493 */ 494 switch (data) { 495 case OCW2_EOI: 496 i8259_nonspecific_eoi(n, data); 497 break; 498 case OCW2_SEOI ... OCW2_SEOI + 7: 499 i8259_specific_eoi(n, data); 500 break; 501 case OCW2_ROTATE_NSEOI: 502 i8259_nonspecific_eoi(n, data); 503 i8259_rotate_priority(n); 504 break; 505 case OCW2_ROTATE_SEOI ... OCW2_ROTATE_SEOI + 7: 506 i8259_specific_eoi(n, data); 507 i8259_rotate_priority(n); 508 break; 509 } 510 return; 511 } 512 513 if (data == OCW2_NOP) 514 return; 515 516 if ((data & OCW2_SET_LOWPRIO) == OCW2_SET_LOWPRIO) { 517 /* Set low priority value (bits 0-2) */ 518 pic->lowest_pri = data & 0x7; 519 return; 520 } 521 522 if (data == OCW2_ROTATE_AEOI_CLEAR) { 523 pic->rotate_auto_eoi = 0; 524 return; 525 } 526 527 if (data == OCW2_ROTATE_AEOI_SET) { 528 pic->rotate_auto_eoi = 1; 529 return; 530 } 531 532 return; 533 } 534 } 535 536 /* 537 * i8259_read_datareg 538 * 539 * Read the PIC's IMR 540 * 541 * Parameters: 542 * n: PIC to read 543 * 544 * Return value: 545 * selected PIC's IMR 546 */ 547 static uint8_t 548 i8259_read_datareg(uint8_t n) 549 { 550 struct i8259 *pic = &pics[n]; 551 552 return (pic->imr); 553 } 554 555 /* 556 * i8259_read_cmdreg 557 * 558 * Read the PIC's IRR or ISR, depending on the current PIC mode (value 559 * selected via the OCW3 command) 560 * 561 * Parameters: 562 * n: PIC to read 563 * 564 * Return value: 565 * selected PIC's IRR/ISR 566 */ 567 static uint8_t 568 i8259_read_cmdreg(uint8_t n) 569 { 570 struct i8259 *pic = &pics[n]; 571 572 if (pic->next_ocw_read == PIC_IRR) 573 return (pic->irr); 574 else if (pic->next_ocw_read == PIC_ISR) 575 return (pic->isr); 576 577 fatal("%s: invalid PIC config during cmdreg read", __func__); 578 } 579 580 /* 581 * i8259_io_write 582 * 583 * Callback to handle write I/O to the emulated PICs in the VM 584 * 585 * Parameters: 586 * vei: vm exit info for this I/O 587 */ 588 static void 589 i8259_io_write(struct vm_exit *vei) 590 { 591 uint16_t port = vei->vei.vei_port; 592 uint32_t data; 593 uint8_t n = 0; 594 595 get_input_data(vei, &data); 596 597 switch (port) { 598 case IO_ICU1: 599 case IO_ICU1 + 1: 600 n = MASTER; 601 break; 602 case IO_ICU2: 603 case IO_ICU2 + 1: 604 n = SLAVE; 605 break; 606 default: 607 fatal("%s: invalid port 0x%x", __func__, port); 608 } 609 610 mutex_lock(&pic_mtx); 611 if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1) 612 i8259_write_datareg(n, data); 613 else 614 i8259_write_cmdreg(n, data); 615 mutex_unlock(&pic_mtx); 616 } 617 618 /* 619 * i8259_io_read 620 * 621 * Callback to handle read I/O to the emulated PICs in the VM 622 * 623 * Parameters: 624 * vei: vm exit info for this I/O 625 * 626 * Return values: 627 * data that was read, based on the port information in 'vei' 628 */ 629 static uint8_t 630 i8259_io_read(struct vm_exit *vei) 631 { 632 uint16_t port = vei->vei.vei_port; 633 uint8_t n = 0; 634 uint8_t rv; 635 636 switch (port) { 637 case IO_ICU1: 638 case IO_ICU1 + 1: 639 n = MASTER; 640 break; 641 case IO_ICU2: 642 case IO_ICU2 + 1: 643 n = SLAVE; 644 break; 645 default: 646 fatal("%s: invalid port 0x%x", __func__, port); 647 } 648 649 mutex_lock(&pic_mtx); 650 if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1) 651 rv = i8259_read_datareg(n); 652 else 653 rv = i8259_read_cmdreg(n); 654 mutex_unlock(&pic_mtx); 655 656 return (rv); 657 } 658 659 /* 660 * vcpu_exit_i8259 661 * 662 * Top level exit handler for PIC operations 663 * 664 * Parameters: 665 * vrp: VCPU run parameters (contains exit information) for this PIC operation 666 * 667 * Return value: 668 * Always 0xFF (PIC read/writes don't generate interrupts directly) 669 */ 670 uint8_t 671 vcpu_exit_i8259(struct vm_run_params *vrp) 672 { 673 struct vm_exit *vei = vrp->vrp_exit; 674 675 if (vei->vei.vei_dir == VEI_DIR_OUT) { 676 i8259_io_write(vei); 677 } else { 678 set_return_data(vei, i8259_io_read(vei)); 679 } 680 681 return (0xFF); 682 } 683 684 /* 685 * pic_set_elcr 686 * 687 * Sets edge or level triggered mode for the given IRQ. Used internally 688 * by the vmd PCI setup code. Guest VMs writing to ELCRx will do so via 689 * vcpu_exit_elcr. 690 * 691 * Parameters: 692 * irq: IRQ (0-15) to set 693 * val: 0 if edge triggered mode, 1 if level triggered mode 694 */ 695 void 696 pic_set_elcr(uint8_t irq, uint8_t val) 697 { 698 if (irq > 15 || val > 1) 699 return; 700 701 log_debug("%s: setting %s triggered mode for irq %d", __func__, 702 val ? "level" : "edge", irq); 703 704 if (irq > 7) { 705 if (val) 706 elcr[SLAVE] |= (1 << (irq - 8)); 707 else 708 elcr[SLAVE] &= ~(1 << (irq - 8)); 709 } else { 710 if (val) 711 elcr[MASTER] |= (1 << irq); 712 else 713 elcr[MASTER] &= ~(1 << irq); 714 } 715 } 716 717 /* 718 * vcpu_exit_elcr 719 * 720 * Handler for the ELCRx registers 721 * 722 * Parameters: 723 * vrp: VCPU run parameters (contains exit information) for this ELCR I/O 724 * 725 * Return value: 726 * Always 0xFF (PIC read/writes don't generate interrupts directly) 727 */ 728 uint8_t 729 vcpu_exit_elcr(struct vm_run_params *vrp) 730 { 731 struct vm_exit *vei = vrp->vrp_exit; 732 uint8_t elcr_reg = vei->vei.vei_port - ELCR0; 733 734 if (elcr_reg > 1) { 735 log_debug("%s: invalid ELCR index %d", __func__, elcr_reg); 736 return (0xFF); 737 } 738 739 if (vei->vei.vei_dir == VEI_DIR_OUT) { 740 log_debug("%s: ELCR[%d] set to 0x%x", __func__, elcr_reg, 741 (uint8_t)vei->vei.vei_data); 742 elcr[elcr_reg] = (uint8_t)vei->vei.vei_data; 743 } else { 744 set_return_data(vei, elcr[elcr_reg]); 745 } 746 747 return (0xFF); 748 } 749 750 int 751 i8259_dump(int fd) 752 { 753 log_debug("%s: sending PIC", __func__); 754 if (atomicio(vwrite, fd, &pics, sizeof(pics)) != sizeof(pics)) { 755 log_warnx("%s: error writing PIC to fd", __func__); 756 return (-1); 757 } 758 759 log_debug("%s: sending ELCR", __func__); 760 if (atomicio(vwrite, fd, &elcr, sizeof(elcr)) != sizeof(elcr)) { 761 log_warnx("%s: error writing ELCR to fd", __func__); 762 return (-1); 763 } 764 return (0); 765 } 766 767 int 768 i8259_restore(int fd) 769 { 770 log_debug("%s: restoring PIC", __func__); 771 if (atomicio(read, fd, &pics, sizeof(pics)) != sizeof(pics)) { 772 log_warnx("%s: error reading PIC from fd", __func__); 773 return (-1); 774 } 775 776 log_debug("%s: restoring ELCR", __func__); 777 if (atomicio(read, fd, &elcr, sizeof(elcr)) != sizeof(elcr)) { 778 log_warnx("%s: error reading ELCR from fd", __func__); 779 return (-1); 780 } 781 782 if (pthread_mutex_init(&pic_mtx, NULL) != 0) 783 fatalx("unable to create pic mutex"); 784 785 return (0); 786 } 787