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