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