xref: /openbsd-src/usr.sbin/vmd/i8259.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
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