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