1/* $OpenBSD: i8259.c,v 1.22 2023/09/01 19:42:26 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 34struct 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 */ 52uint8_t elcr[2]; 53 54#define PIC_IRR 0 55#define PIC_ISR 1 56 57/* Master and slave PICs */ 58struct i8259 pics[2]; 59pthread_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 */ 73static const char * 74i8259_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 */ 88void 89i8259_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 */ 110uint8_t 111i8259_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 */ 133uint16_t 134i8259_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__); 207ret: 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 */ 220void 221i8259_assert_irq(uint8_t irq) 222{ 223 mutex_lock(&pic_mtx); 224 if (irq <= 7) { 225 SET(pics[MASTER].irr, 1 << irq); 226 pics[MASTER].asserted = 1; 227 } else { 228 irq -= 8; 229 SET(pics[SLAVE].irr, 1 << irq); 230 pics[SLAVE].asserted = 1; 231 232 /* Assert cascade IRQ on master PIC */ 233 SET(pics[MASTER].irr, 1 << 2); 234 pics[MASTER].asserted = 1; 235 } 236 mutex_unlock(&pic_mtx); 237} 238 239/* 240 * i8259_deassert_irq 241 * 242 * Deasserts the IRQ specified 243 * 244 * Parameters: 245 * irq: the IRQ to deassert 246 */ 247void 248i8259_deassert_irq(uint8_t irq) 249{ 250 mutex_lock(&pic_mtx); 251 if (irq <= 7) { 252 if (elcr[MASTER] & (1 << irq)) 253 CLR(pics[MASTER].irr, 1 << irq); 254 } else { 255 irq -= 8; 256 if (elcr[SLAVE] & (1 << irq)) { 257 CLR(pics[SLAVE].irr, 1 << irq); 258 259 /* 260 * Deassert cascade IRQ on master if no IRQs on 261 * slave 262 */ 263 if (pics[SLAVE].irr == 0) 264 CLR(pics[MASTER].irr, 1 << 2); 265 } 266 } 267 mutex_unlock(&pic_mtx); 268} 269 270/* 271 * i8259_write_datareg 272 * 273 * Write to a specified data register in the emulated PIC during PIC 274 * initialization. The data write follows the state model in the i8259 (in 275 * other words, data is expected to be written in a specific order). 276 * 277 * Parameters: 278 * n: PIC to write to (MASTER/SLAVE) 279 * data: data to write 280 */ 281static void 282i8259_write_datareg(uint8_t n, uint8_t data) 283{ 284 struct i8259 *pic = &pics[n]; 285 286 if (pic->init_mode == 1) { 287 if (pic->cur_icw == 2) { 288 /* Set vector */ 289 log_debug("%s: %s pic, reset IRQ vector to 0x%x", 290 __func__, i8259_pic_name(n), data); 291 pic->vec = data; 292 } else if (pic->cur_icw == 3) { 293 /* Set IRQ interconnects */ 294 if (n == SLAVE && (data & 0xf8)) { 295 log_warnx("%s: %s pic invalid icw2 0x%x", 296 __func__, i8259_pic_name(n), data); 297 return; 298 } 299 pic->irq_conn = data; 300 } else if (pic->cur_icw == 4) { 301 if (!(data & ICW4_UP)) { 302 log_warnx("%s: %s pic init error: x86 bit " 303 "clear", __func__, i8259_pic_name(n)); 304 return; 305 } 306 307 if (data & ICW4_AEOI) { 308 log_warnx("%s: %s pic: aeoi mode set", 309 __func__, i8259_pic_name(n)); 310 pic->auto_eoi = 1; 311 return; 312 } 313 314 if (data & ICW4_MS) { 315 log_warnx("%s: %s pic init error: M/S mode", 316 __func__, i8259_pic_name(n)); 317 return; 318 } 319 320 if (data & ICW4_BUF) { 321 log_warnx("%s: %s pic init error: buf mode", 322 __func__, i8259_pic_name(n)); 323 return; 324 } 325 326 if (data & 0xe0) { 327 log_warnx("%s: %s pic init error: invalid icw4 " 328 " 0x%x", __func__, i8259_pic_name(n), data); 329 return; 330 } 331 } 332 333 pic->cur_icw++; 334 if (pic->cur_icw == 5) { 335 pic->cur_icw = 1; 336 pic->init_mode = 0; 337 } 338 } else 339 pic->imr = data; 340} 341 342/* 343 * i8259_specific_eoi 344 * 345 * Handles specific end of interrupt commands 346 * 347 * Parameters: 348 * n: PIC to deliver this EOI to 349 * data: interrupt to EOI 350 */ 351static void 352i8259_specific_eoi(uint8_t n, uint8_t data) 353{ 354 if (!(pics[n].isr & (1 << (data & 0x7)))) { 355 log_warnx("%s: %s pic specific eoi irq %d while not in" 356 " service", __func__, i8259_pic_name(n), (data & 0x7)); 357 } 358 359 pics[n].isr &= ~(1 << (data & 0x7)); 360} 361 362/* 363 * i8259_nonspecific_eoi 364 * 365 * Handles nonspecific end of interrupt commands 366 * XXX not implemented 367 */ 368static void 369i8259_nonspecific_eoi(uint8_t n, uint8_t data) 370{ 371 int i = 0; 372 373 while (i < 8) { 374 if ((pics[n].isr & (1 << (i & 0x7)))) { 375 i8259_specific_eoi(n, i); 376 return; 377 } 378 i++; 379 } 380} 381 382/* 383 * i8259_rotate_priority 384 * 385 * Rotates the interrupt priority on the specified PIC 386 * 387 * Parameters: 388 * n: PIC whose priority should be rotated 389 */ 390static void 391i8259_rotate_priority(uint8_t n) 392{ 393 pics[n].lowest_pri++; 394 if (pics[n].lowest_pri > 7) 395 pics[n].lowest_pri = 0; 396} 397 398/* 399 * i8259_write_cmdreg 400 * 401 * Write to the PIC command register 402 * 403 * Parameters: 404 * n: PIC whose command register should be written to 405 * data: data to write 406 */ 407static void 408i8259_write_cmdreg(uint8_t n, uint8_t data) 409{ 410 struct i8259 *pic = &pics[n]; 411 412 if (data & ICW1_INIT) { 413 /* Validate init params */ 414 if (!(data & ICW1_ICW4)) { 415 log_warnx("%s: %s pic init error: no ICW4 request", 416 __func__, i8259_pic_name(n)); 417 return; 418 } 419 420 if (data & (ICW1_IVA1 | ICW1_IVA2 | ICW1_IVA3)) { 421 log_warnx("%s: %s pic init error: IVA specified", 422 __func__, i8259_pic_name(n)); 423 return; 424 } 425 426 if (data & ICW1_SNGL) { 427 log_warnx("%s: %s pic init error: single pic mode", 428 __func__, i8259_pic_name(n)); 429 return; 430 } 431 432 if (data & ICW1_ADI) { 433 log_warnx("%s: %s pic init error: address interval", 434 __func__, i8259_pic_name(n)); 435 return; 436 } 437 438 if (data & ICW1_LTIM) { 439 log_warnx("%s: %s pic init error: level trigger mode", 440 __func__, i8259_pic_name(n)); 441 return; 442 } 443 444 pic->init_mode = 1; 445 pic->cur_icw = 2; 446 pic->imr = 0; 447 pic->isr = 0; 448 pic->irr = 0; 449 pic->asserted = 0; 450 pic->lowest_pri = 7; 451 pic->rotate_auto_eoi = 0; 452 return; 453 } else if (data & OCW_SELECT) { 454 /* OCW3 */ 455 if (data & OCW3_ACTION) { 456 if (data & OCW3_RR) { 457 if (data & OCW3_RIS) 458 pic->next_ocw_read = PIC_ISR; 459 else 460 pic->next_ocw_read = PIC_IRR; 461 } 462 } 463 464 if (data & OCW3_SMACTION) { 465 if (data & OCW3_SMM) { 466 pic->smm = 1; 467 /* XXX update intr here */ 468 } else 469 pic->smm = 0; 470 } 471 472 if (data & OCW3_POLL) { 473 pic->poll = 1; 474 /* XXX update intr here */ 475 } 476 477 return; 478 } else { 479 /* OCW2 */ 480 if (data & OCW2_EOI) { 481 /* 482 * An EOI command was received. It could be one of 483 * several different varieties: 484 * 485 * Nonspecific EOI (0x20) 486 * Specific EOI (0x60..0x67) 487 * Nonspecific EOI + rotate (0xA0) 488 * Specific EOI + rotate (0xE0..0xE7) 489 */ 490 switch (data) { 491 case OCW2_EOI: 492 i8259_nonspecific_eoi(n, data); 493 break; 494 case OCW2_SEOI ... OCW2_SEOI + 7: 495 i8259_specific_eoi(n, data); 496 break; 497 case OCW2_ROTATE_NSEOI: 498 i8259_nonspecific_eoi(n, data); 499 i8259_rotate_priority(n); 500 break; 501 case OCW2_ROTATE_SEOI ... OCW2_ROTATE_SEOI + 7: 502 i8259_specific_eoi(n, data); 503 i8259_rotate_priority(n); 504 break; 505 } 506 return; 507 } 508 509 if (data == OCW2_NOP) 510 return; 511 512 if ((data & OCW2_SET_LOWPRIO) == OCW2_SET_LOWPRIO) { 513 /* Set low priority value (bits 0-2) */ 514 pic->lowest_pri = data & 0x7; 515 return; 516 } 517 518 if (data == OCW2_ROTATE_AEOI_CLEAR) { 519 pic->rotate_auto_eoi = 0; 520 return; 521 } 522 523 if (data == OCW2_ROTATE_AEOI_SET) { 524 pic->rotate_auto_eoi = 1; 525 return; 526 } 527 528 return; 529 } 530} 531 532/* 533 * i8259_read_datareg 534 * 535 * Read the PIC's IMR 536 * 537 * Parameters: 538 * n: PIC to read 539 * 540 * Return value: 541 * selected PIC's IMR 542 */ 543static uint8_t 544i8259_read_datareg(uint8_t n) 545{ 546 struct i8259 *pic = &pics[n]; 547 548 return (pic->imr); 549} 550 551/* 552 * i8259_read_cmdreg 553 * 554 * Read the PIC's IRR or ISR, depending on the current PIC mode (value 555 * selected via the OCW3 command) 556 * 557 * Parameters: 558 * n: PIC to read 559 * 560 * Return value: 561 * selected PIC's IRR/ISR 562 */ 563static uint8_t 564i8259_read_cmdreg(uint8_t n) 565{ 566 struct i8259 *pic = &pics[n]; 567 568 if (pic->next_ocw_read == PIC_IRR) 569 return (pic->irr); 570 else if (pic->next_ocw_read == PIC_ISR) 571 return (pic->isr); 572 573 fatal("%s: invalid PIC config during cmdreg read", __func__); 574} 575 576/* 577 * i8259_io_write 578 * 579 * Callback to handle write I/O to the emulated PICs in the VM 580 * 581 * Parameters: 582 * vei: vm exit info for this I/O 583 */ 584static void 585i8259_io_write(struct vm_exit *vei) 586{ 587 uint16_t port = vei->vei.vei_port; 588 uint32_t data = 0; 589 uint8_t n = 0; 590 591 get_input_data(vei, &data); 592 593 switch (port) { 594 case IO_ICU1: 595 case IO_ICU1 + 1: 596 n = MASTER; 597 break; 598 case IO_ICU2: 599 case IO_ICU2 + 1: 600 n = SLAVE; 601 break; 602 default: 603 fatal("%s: invalid port 0x%x", __func__, port); 604 } 605 606 mutex_lock(&pic_mtx); 607 if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1) 608 i8259_write_datareg(n, data); 609 else 610 i8259_write_cmdreg(n, data); 611 mutex_unlock(&pic_mtx); 612} 613 614/* 615 * i8259_io_read 616 * 617 * Callback to handle read I/O to the emulated PICs in the VM 618 * 619 * Parameters: 620 * vei: vm exit info for this I/O 621 * 622 * Return values: 623 * data that was read, based on the port information in 'vei' 624 */ 625static uint8_t 626i8259_io_read(struct vm_exit *vei) 627{ 628 uint16_t port = vei->vei.vei_port; 629 uint8_t n = 0; 630 uint8_t rv; 631 632 switch (port) { 633 case IO_ICU1: 634 case IO_ICU1 + 1: 635 n = MASTER; 636 break; 637 case IO_ICU2: 638 case IO_ICU2 + 1: 639 n = SLAVE; 640 break; 641 default: 642 fatal("%s: invalid port 0x%x", __func__, port); 643 } 644 645 mutex_lock(&pic_mtx); 646 if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1) 647 rv = i8259_read_datareg(n); 648 else 649 rv = i8259_read_cmdreg(n); 650 mutex_unlock(&pic_mtx); 651 652 return (rv); 653} 654 655/* 656 * vcpu_exit_i8259 657 * 658 * Top level exit handler for PIC operations 659 * 660 * Parameters: 661 * vrp: VCPU run parameters (contains exit information) for this PIC operation 662 * 663 * Return value: 664 * Always 0xFF (PIC read/writes don't generate interrupts directly) 665 */ 666uint8_t 667vcpu_exit_i8259(struct vm_run_params *vrp) 668{ 669 struct vm_exit *vei = vrp->vrp_exit; 670 671 if (vei->vei.vei_dir == VEI_DIR_OUT) { 672 i8259_io_write(vei); 673 } else { 674 set_return_data(vei, i8259_io_read(vei)); 675 } 676 677 return (0xFF); 678} 679 680/* 681 * pic_set_elcr 682 * 683 * Sets edge or level triggered mode for the given IRQ. Used internally 684 * by the vmd PCI setup code. Guest VMs writing to ELCRx will do so via 685 * vcpu_exit_elcr. 686 * 687 * Parameters: 688 * irq: IRQ (0-15) to set 689 * val: 0 if edge triggered mode, 1 if level triggered mode 690 */ 691void 692pic_set_elcr(uint8_t irq, uint8_t val) 693{ 694 if (irq > 15 || val > 1) 695 return; 696 697 log_debug("%s: setting %s triggered mode for irq %d", __func__, 698 val ? "level" : "edge", irq); 699 700 if (irq > 7) { 701 if (val) 702 elcr[SLAVE] |= (1 << (irq - 8)); 703 else 704 elcr[SLAVE] &= ~(1 << (irq - 8)); 705 } else { 706 if (val) 707 elcr[MASTER] |= (1 << irq); 708 else 709 elcr[MASTER] &= ~(1 << irq); 710 } 711} 712 713/* 714 * vcpu_exit_elcr 715 * 716 * Handler for the ELCRx registers 717 * 718 * Parameters: 719 * vrp: VCPU run parameters (contains exit information) for this ELCR I/O 720 * 721 * Return value: 722 * Always 0xFF (PIC read/writes don't generate interrupts directly) 723 */ 724uint8_t 725vcpu_exit_elcr(struct vm_run_params *vrp) 726{ 727 struct vm_exit *vei = vrp->vrp_exit; 728 uint8_t elcr_reg = vei->vei.vei_port - ELCR0; 729 730 if (elcr_reg > 1) { 731 log_debug("%s: invalid ELCR index %d", __func__, elcr_reg); 732 return (0xFF); 733 } 734 735 if (vei->vei.vei_dir == VEI_DIR_OUT) { 736 log_debug("%s: ELCR[%d] set to 0x%x", __func__, elcr_reg, 737 (uint8_t)vei->vei.vei_data); 738 elcr[elcr_reg] = (uint8_t)vei->vei.vei_data; 739 } else { 740 set_return_data(vei, elcr[elcr_reg]); 741 } 742 743 return (0xFF); 744} 745 746int 747i8259_dump(int fd) 748{ 749 log_debug("%s: sending PIC", __func__); 750 if (atomicio(vwrite, fd, &pics, sizeof(pics)) != sizeof(pics)) { 751 log_warnx("%s: error writing PIC to fd", __func__); 752 return (-1); 753 } 754 755 log_debug("%s: sending ELCR", __func__); 756 if (atomicio(vwrite, fd, &elcr, sizeof(elcr)) != sizeof(elcr)) { 757 log_warnx("%s: error writing ELCR to fd", __func__); 758 return (-1); 759 } 760 return (0); 761} 762 763int 764i8259_restore(int fd) 765{ 766 log_debug("%s: restoring PIC", __func__); 767 if (atomicio(read, fd, &pics, sizeof(pics)) != sizeof(pics)) { 768 log_warnx("%s: error reading PIC from fd", __func__); 769 return (-1); 770 } 771 772 log_debug("%s: restoring ELCR", __func__); 773 if (atomicio(read, fd, &elcr, sizeof(elcr)) != sizeof(elcr)) { 774 log_warnx("%s: error reading ELCR from fd", __func__); 775 return (-1); 776 } 777 778 if (pthread_mutex_init(&pic_mtx, NULL) != 0) 779 fatalx("unable to create pic mutex"); 780 781 return (0); 782} 783