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 <string.h> 18 19 #include <sys/types.h> 20 21 #include <dev/isa/isareg.h> 22 23 #include <machine/vmmvar.h> 24 25 #include "proc.h" 26 #include "i8259.h" 27 28 struct i8259 { 29 uint8_t irr; 30 uint8_t imr; 31 uint8_t isr; 32 uint8_t smm; 33 uint8_t poll; 34 uint8_t cur_icw; 35 uint8_t init_mode; 36 uint8_t vec; 37 uint8_t irq_conn; 38 uint8_t next_ocw_read; 39 uint8_t auto_eoi; 40 uint8_t rotate_auto_eoi; 41 uint8_t lowest_pri; 42 uint8_t asserted; 43 }; 44 45 #define PIC_IRR 0 46 #define PIC_ISR 1 47 48 /* Master and slave PICs */ 49 struct i8259 pics[2]; 50 51 /* 52 * i8259_init 53 * 54 * Initialize the emulated i8259 PIC. 55 */ 56 void 57 i8259_init(void) 58 { 59 memset(&pics, 0, sizeof(pics)); 60 pics[MASTER].cur_icw = 1; 61 pics[SLAVE].cur_icw = 1; 62 } 63 64 /* 65 * i8259_is_pending 66 * 67 * Determine if an IRQ is pending on either the slave or master PIC. 68 * 69 * Return Values: 70 * 1 if an IRQ (any IRQ) is pending, 0 otherwise 71 */ 72 uint8_t 73 i8259_is_pending(void) 74 { 75 uint8_t pending = 0; 76 uint8_t master_pending; 77 uint8_t slave_pending; 78 79 master_pending = pics[MASTER].irr & ~(pics[MASTER].imr | (1 << 2)); 80 slave_pending = pics[SLAVE].irr & ~pics[SLAVE].imr; 81 82 if (master_pending || slave_pending) 83 pending = 1; 84 85 return pending; 86 } 87 88 /* 89 * i8259_ack 90 * 91 * This function is called when the vcpu exits and is ready to accept an 92 * interrupt. 93 * 94 * Return values: 95 * interrupt vector to inject, 0xFFFF if no irq pending 96 */ 97 uint16_t 98 i8259_ack(void) 99 { 100 uint8_t high_prio_m, high_prio_s; 101 uint8_t i; 102 uint16_t ret; 103 104 ret = 0xFFFF; 105 106 if (pics[MASTER].asserted == 0 && pics[SLAVE].asserted == 0) 107 return (ret); 108 109 high_prio_m = pics[MASTER].lowest_pri + 1; 110 if (high_prio_m > 7) 111 high_prio_m = 0; 112 113 high_prio_s = pics[SLAVE].lowest_pri + 1; 114 if (high_prio_s > 7) 115 high_prio_s = 0; 116 117 i = high_prio_m; 118 do { 119 if ((pics[MASTER].irr & (1 << i)) && i != 2 && 120 !(pics[MASTER].imr & (1 << i))) { 121 /* Master PIC has highest prio and ready IRQ */ 122 pics[MASTER].irr &= ~(1 << i); 123 pics[MASTER].isr |= (1 << i); 124 125 if (pics[MASTER].irr == 0) 126 pics[MASTER].asserted = 0; 127 128 ret = i; 129 130 /* XXX - intr still needs |= 0xFF00 ?? */ 131 if (pics[MASTER].irr || pics[SLAVE].irr) 132 ret |= 0xFF00; 133 134 return ret; 135 } 136 137 i++; 138 139 if (i > 7) 140 i = 0; 141 142 } while (i != high_prio_m); 143 144 i = high_prio_s; 145 do { 146 if ((pics[SLAVE].irr & (1 << i)) && 147 !(pics[SLAVE].imr & (1 << i))) { 148 /* Slave PIC has highest prio and ready IRQ */ 149 pics[SLAVE].irr &= ~(1 << i); 150 pics[MASTER].irr &= ~(1 << 2); 151 152 pics[SLAVE].isr |= (1 << i); 153 pics[MASTER].isr |= (1 << 2); 154 155 if (pics[SLAVE].irr == 0) { 156 pics[SLAVE].asserted = 0; 157 if (pics[MASTER].irr == 0) 158 pics[MASTER].asserted = 0; 159 } 160 161 ret = i + 8; 162 /* XXX - intr still needs |= 0xFF00 ?? */ 163 if ((pics[MASTER].irr & ~0x4) || (pics[SLAVE].irr)) 164 ret |= 0xFF00; 165 166 return ret; 167 } 168 169 i++; 170 171 if (i > 7) 172 i = 0; 173 } while (i != high_prio_s); 174 175 return (0xFFFF); 176 } 177 178 /* 179 * i8259_assert_irq 180 * 181 * Asserts the IRQ specified 182 * 183 * Parameters: 184 * irq: the IRQ to assert 185 */ 186 void 187 i8259_assert_irq(uint8_t irq) 188 { 189 if (irq <= 7) { 190 if (pics[MASTER].imr & (1 << irq)) 191 return; 192 193 pics[MASTER].irr |= (1 << irq); 194 pics[MASTER].asserted = 1; 195 } else { 196 if (pics[SLAVE].imr & (1 << (irq - 8))) 197 return; 198 199 pics[SLAVE].irr |= (1 << (irq - 8)); 200 pics[SLAVE].asserted = 1; 201 202 /* Assert cascade IRQ on master PIC */ 203 pics[MASTER].irr |= (1 << 2); 204 pics[MASTER].asserted = 1; 205 } 206 } 207 208 /* 209 * i8259_deassert_irq 210 * 211 * Deasserts the IRQ specified 212 * 213 * Parameters: 214 * irq: the IRQ to deassert 215 */ 216 void 217 i8259_deassert_irq(uint8_t irq) 218 { 219 if (irq <= 7) 220 pics[MASTER].irr &= ~(1 << irq); 221 else { 222 pics[SLAVE].irr &= ~(1 << (irq - 8)); 223 224 /* Deassert cascade IRQ on master if no IRQs on slave */ 225 if (pics[SLAVE].irr == 0) 226 pics[MASTER].irr &= ~(1 << 2); 227 } 228 } 229 230 /* 231 * i8259_write_datareg 232 * 233 * Write to a specified data register in the emulated PIC during PIC 234 * initialization. The data write follows the state model in the i8259 (in 235 * other words, data is expected to be written in a specific order). 236 * 237 * Parameters: 238 * n: PIC to write to (MASTER/SLAVE) 239 * data: data to write 240 */ 241 static void 242 i8259_write_datareg(uint8_t n, uint8_t data) 243 { 244 struct i8259 *pic = &pics[n]; 245 246 if (pic->init_mode == 1) { 247 if (pic->cur_icw == 2) { 248 /* Set vector */ 249 pic->vec = data; 250 } else if (pic->cur_icw == 3) { 251 /* Set IRQ interconnects */ 252 if (n == SLAVE && (data & 0xf8)) { 253 log_warn("%s: pic %d invalid icw2 0x%x", 254 __func__, n, data); 255 return; 256 } 257 pic->irq_conn = data; 258 } else if (pic->cur_icw == 4) { 259 if (!(data & ICW4_UP)) { 260 log_warn("%s: pic %d init error: x86 bit " 261 "clear", __func__, n); 262 return; 263 } 264 265 if (data & ICW4_AEOI) { 266 log_warn("%s: pic %d: aeoi mode set", 267 __func__, n); 268 pic->auto_eoi = 1; 269 return; 270 } 271 272 if (data & ICW4_MS) { 273 log_warn("%s: pic %d init error: M/S mode", 274 __func__, n); 275 return; 276 } 277 278 if (data & ICW4_BUF) { 279 log_warn("%s: pic %d init error: buf mode", 280 __func__, n); 281 return; 282 } 283 284 if (data & 0xe0) { 285 log_warn("%s: pic %d init error: invalid icw4 " 286 " 0x%x", __func__, n, data); 287 return; 288 } 289 } 290 291 pic->cur_icw++; 292 if (pic->cur_icw == 5) { 293 pic->cur_icw = 1; 294 pic->init_mode = 0; 295 } 296 } else 297 pic->imr = data; 298 } 299 300 /* 301 * i8259_specific_eoi 302 * 303 * Handles specific end of interrupt commands 304 * 305 * Parameters: 306 * n: PIC to deliver this EOI to 307 * data: interrupt to EOI 308 */ 309 static void 310 i8259_specific_eoi(uint8_t n, uint8_t data) 311 { 312 uint8_t oldisr; 313 314 if (!(pics[n].isr & (1 << (data & 0x7)))) { 315 log_warn("%s: pic %d specific eoi irq %d while not in" 316 " service", __func__, n, (data & 0x7)); 317 } 318 319 oldisr = pics[n].isr; 320 pics[n].isr &= ~(1 << (data & 0x7)); 321 } 322 323 /* 324 * i8259_nonspecific_eoi 325 * 326 * Handles nonspecific end of interrupt commands 327 * XXX not implemented 328 */ 329 static void 330 i8259_nonspecific_eoi(uint8_t n, uint8_t data) 331 { 332 log_warn("%s: pic %d nonspecific eoi not supported", __func__, n); 333 } 334 335 /* 336 * i8259_rotate_priority 337 * 338 * Rotates the interrupt priority on the specified PIC 339 * 340 * Parameters: 341 * n: PIC whose priority should be rotated 342 */ 343 static void 344 i8259_rotate_priority(uint8_t n) 345 { 346 pics[n].lowest_pri++; 347 if (pics[n].lowest_pri > 7) 348 pics[n].lowest_pri = 0; 349 } 350 351 /* 352 * i8259_write_cmdreg 353 * 354 * Write to the PIC command register 355 * 356 * Parameters: 357 * n: PIC whose command register should be written to 358 * data: data to write 359 */ 360 static void 361 i8259_write_cmdreg(uint8_t n, uint8_t data) 362 { 363 struct i8259 *pic = &pics[n]; 364 365 if (data & ICW1_INIT) { 366 /* Validate init params */ 367 if (!(data & ICW1_ICW4)) { 368 log_warn("%s: pic %d init error: no ICW4 request", 369 __func__, n); 370 return; 371 } 372 373 if (data & (ICW1_IVA1 | ICW1_IVA2 | ICW1_IVA3)) { 374 log_warn("%s: pic %d init error: IVA specified", 375 __func__, n); 376 return; 377 } 378 379 if (data & ICW1_SNGL) { 380 log_warn("%s: pic %d init error: single pic mode", 381 __func__, n); 382 return; 383 } 384 385 if (data & ICW1_ADI) { 386 log_warn("%s: pic %d init error: address interval", 387 __func__, n); 388 return; 389 } 390 391 if (data & ICW1_LTIM) { 392 log_warn("%s: pic %d init error: level trigger mode", 393 __func__, n); 394 return; 395 } 396 397 pic->init_mode = 1; 398 pic->cur_icw = 2; 399 pic->imr = 0; 400 pic->isr = 0; 401 pic->irr = 0; 402 pic->asserted = 0; 403 pic->lowest_pri = 7; 404 pic->rotate_auto_eoi = 0; 405 return; 406 } else if (data & OCW_SELECT) { 407 /* OCW3 */ 408 if (data & OCW3_ACTION) { 409 if (data & OCW3_RR) { 410 if (data & OCW3_RIS) 411 pic->next_ocw_read = PIC_ISR; 412 else 413 pic->next_ocw_read = PIC_IRR; 414 } 415 } 416 417 if (data & OCW3_SMACTION) { 418 if (data & OCW3_SMM) { 419 pic->smm = 1; 420 /* XXX update intr here */ 421 } else 422 pic->smm = 0; 423 } 424 425 if (data & OCW3_POLL) { 426 pic->poll = 1; 427 /* XXX update intr here */ 428 } 429 430 return; 431 } else { 432 /* OCW2 */ 433 if (data & OCW2_EOI) { 434 /* 435 * An EOI command was received. It could be one of 436 * several different varieties: 437 * 438 * Nonspecific EOI (0x20) 439 * Specific EOI (0x60..0x67) 440 * Nonspecific EOI + rotate (0xA0) 441 * Specific EOI + rotate (0xE0..0xE7) 442 */ 443 switch (data) { 444 case OCW2_EOI: 445 i8259_nonspecific_eoi(n, data); 446 break; 447 case OCW2_SEOI ... OCW2_SEOI + 7: 448 i8259_specific_eoi(n, data); 449 break; 450 case OCW2_ROTATE_NSEOI: 451 i8259_nonspecific_eoi(n, data); 452 i8259_rotate_priority(n); 453 break; 454 case OCW2_ROTATE_SEOI ... OCW2_ROTATE_SEOI + 7: 455 i8259_specific_eoi(n, data); 456 i8259_rotate_priority(n); 457 break; 458 } 459 return; 460 } 461 462 if (data == OCW2_NOP) 463 return; 464 465 if ((data & OCW2_SET_LOWPRIO) == OCW2_SET_LOWPRIO) { 466 /* Set low priority value (bits 0-2) */ 467 pic->lowest_pri = data & 0x7; 468 return; 469 } 470 471 if (data == OCW2_ROTATE_AEOI_CLEAR) { 472 pic->rotate_auto_eoi = 0; 473 return; 474 } 475 476 if (data == OCW2_ROTATE_AEOI_SET) { 477 pic->rotate_auto_eoi = 1; 478 return; 479 } 480 481 return; 482 } 483 } 484 485 /* 486 * i8259_read_datareg 487 * 488 * Read the PIC's IMR 489 * 490 * Parameters: 491 * n: PIC to read 492 * 493 * Return value: 494 * selected PIC's IMR 495 */ 496 static uint8_t 497 i8259_read_datareg(uint8_t n) 498 { 499 struct i8259 *pic = &pics[n]; 500 501 return (pic->imr); 502 } 503 504 /* 505 * i8259_read_cmdreg 506 * 507 * Read the PIC's IRR or ISR, depending on the current PIC mode (value 508 * selected via the OCW3 command) 509 * 510 * Parameters: 511 * n: PIC to read 512 * 513 * Return value: 514 * selected PIC's IRR/ISR 515 */ 516 static uint8_t 517 i8259_read_cmdreg(uint8_t n) 518 { 519 struct i8259 *pic = &pics[n]; 520 521 if (pic->next_ocw_read == PIC_IRR) 522 return (pic->irr); 523 else if (pic->next_ocw_read == PIC_ISR) 524 return (pic->isr); 525 526 fatal("%s: invalid PIC config during cmdreg read", __func__); 527 } 528 529 /* 530 * i8259_io_write 531 * 532 * Callback to handle write I/O to the emulated PICs in the VM 533 * 534 * Parameters: 535 * vei: vm exit info for this I/O 536 */ 537 static void 538 i8259_io_write(union vm_exit *vei) 539 { 540 uint16_t port = vei->vei.vei_port; 541 uint8_t data = vei->vei.vei_data; 542 uint8_t n = 0; 543 544 switch (port) { 545 case IO_ICU1: 546 case IO_ICU1 + 1: 547 n = MASTER; 548 break; 549 case IO_ICU2: 550 case IO_ICU2 + 1: 551 n = SLAVE; 552 break; 553 default: 554 fatal("%s: invalid port 0x%x", __func__, port); 555 } 556 557 if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1) 558 i8259_write_datareg(n, data); 559 else 560 i8259_write_cmdreg(n, data); 561 562 } 563 564 /* 565 * i8259_io_read 566 * 567 * Callback to handle read I/O to the emulated PICs in the VM 568 * 569 * Parameters: 570 * vei: vm exit info for this I/O 571 * 572 * Return values: 573 * data that was read, based on the port information in 'vei' 574 */ 575 static uint8_t 576 i8259_io_read(union vm_exit *vei) 577 { 578 uint16_t port = vei->vei.vei_port; 579 uint8_t n = 0; 580 581 switch (port) { 582 case IO_ICU1: 583 case IO_ICU1 + 1: 584 n = MASTER; 585 break; 586 case IO_ICU2: 587 case IO_ICU2 + 1: 588 n = SLAVE; 589 break; 590 default: 591 fatal("%s: invalid port 0x%x", __func__, port); 592 } 593 594 if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1) 595 return i8259_read_datareg(n); 596 else 597 return i8259_read_cmdreg(n); 598 } 599 600 /* 601 * vcpu_exit_i8259 602 * 603 * Top level exit handler for PIC operations 604 * 605 * Parameters: 606 * vrp: VCPU run parameters (contains exit information) for this PIC operation 607 * 608 * Return value: 609 * Always 0xFF (PIC read/writes don't generate interrupts directly) 610 */ 611 uint8_t 612 vcpu_exit_i8259(struct vm_run_params *vrp) 613 { 614 union vm_exit *vei = vrp->vrp_exit; 615 616 if (vei->vei.vei_dir == VEI_DIR_OUT) { 617 i8259_io_write(vei); 618 } else { 619 vei->vei.vei_data = i8259_io_read(vei); 620 } 621 622 return (0xFF); 623 } 624