xref: /openbsd/usr.sbin/vmd/i8259.c (revision 73471bf0)
1 /* $OpenBSD: i8259.c,v 1.20 2021/06/16 16:55:02 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 
34 struct 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 */
52 uint8_t elcr[2];
53 
54 #define PIC_IRR 0
55 #define PIC_ISR 1
56 
57 /* Master and slave PICs */
58 struct i8259 pics[2];
59 pthread_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  */
73 static const char *
74 i8259_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  */
88 void
89 i8259_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  */
110 uint8_t
111 i8259_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  */
133 uint16_t
134 i8259_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__);
207 ret:
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  */
220 void
221 i8259_assert_irq(uint8_t irq)
222 {
223 	mutex_lock(&pic_mtx);
224 	if (irq <= 7) {
225 		if (!ISSET(pics[MASTER].imr, 1 << irq)) {
226 			SET(pics[MASTER].irr, 1 << irq);
227 			pics[MASTER].asserted = 1;
228 		}
229 	} else {
230 		irq -= 8;
231 		if (!ISSET(pics[SLAVE].imr, 1 << irq)) {
232 			SET(pics[SLAVE].irr, 1 << irq);
233 			pics[SLAVE].asserted = 1;
234 
235 			/* Assert cascade IRQ on master PIC */
236 			SET(pics[MASTER].irr, 1 << 2);
237 			pics[MASTER].asserted = 1;
238 		}
239 	}
240 	mutex_unlock(&pic_mtx);
241 }
242 
243 /*
244  * i8259_deassert_irq
245  *
246  * Deasserts the IRQ specified
247  *
248  * Parameters:
249  *  irq: the IRQ to deassert
250  */
251 void
252 i8259_deassert_irq(uint8_t irq)
253 {
254 	mutex_lock(&pic_mtx);
255 	if (irq <= 7) {
256 		if (elcr[MASTER] & (1 << irq))
257 			CLR(pics[MASTER].irr, 1 << irq);
258 	} else {
259 		irq -= 8;
260 		if (elcr[SLAVE] & (1 << irq)) {
261 			CLR(pics[SLAVE].irr, 1 << irq);
262 
263 			/*
264 			 * Deassert cascade IRQ on master if no IRQs on
265 			 * slave
266 			 */
267 			if (pics[SLAVE].irr == 0)
268 				CLR(pics[MASTER].irr, 1 << 2);
269 		}
270 	}
271 	mutex_unlock(&pic_mtx);
272 }
273 
274 /*
275  * i8259_write_datareg
276  *
277  * Write to a specified data register in the emulated PIC during PIC
278  * initialization. The data write follows the state model in the i8259 (in
279  * other words, data is expected to be written in a specific order).
280  *
281  * Parameters:
282  *  n: PIC to write to (MASTER/SLAVE)
283  *  data: data to write
284  */
285 static void
286 i8259_write_datareg(uint8_t n, uint8_t data)
287 {
288 	struct i8259 *pic = &pics[n];
289 
290 	if (pic->init_mode == 1) {
291 		if (pic->cur_icw == 2) {
292 			/* Set vector */
293 			log_debug("%s: %s pic, reset IRQ vector to 0x%x",
294 			    __func__, i8259_pic_name(n), data);
295 			pic->vec = data;
296 		} else if (pic->cur_icw == 3) {
297 			/* Set IRQ interconnects */
298 			if (n == SLAVE && (data & 0xf8)) {
299 				log_warnx("%s: %s pic invalid icw2 0x%x",
300 				    __func__, i8259_pic_name(n), data);
301 				return;
302 			}
303 			pic->irq_conn = data;
304 		} else if (pic->cur_icw == 4) {
305 			if (!(data & ICW4_UP)) {
306 				log_warnx("%s: %s pic init error: x86 bit "
307 				    "clear", __func__, i8259_pic_name(n));
308 				return;
309 			}
310 
311 			if (data & ICW4_AEOI) {
312 				log_warnx("%s: %s pic: aeoi mode set",
313 				    __func__, i8259_pic_name(n));
314 				pic->auto_eoi = 1;
315 				return;
316 			}
317 
318 			if (data & ICW4_MS) {
319 				log_warnx("%s: %s pic init error: M/S mode",
320 				    __func__, i8259_pic_name(n));
321 				return;
322 			}
323 
324 			if (data & ICW4_BUF) {
325 				log_warnx("%s: %s pic init error: buf mode",
326 				    __func__, i8259_pic_name(n));
327 				return;
328 			}
329 
330 			if (data & 0xe0) {
331 				log_warnx("%s: %s pic init error: invalid icw4 "
332 				    " 0x%x", __func__, i8259_pic_name(n), data);
333 				return;
334 			}
335 		}
336 
337 		pic->cur_icw++;
338 		if (pic->cur_icw == 5) {
339 			pic->cur_icw = 1;
340 			pic->init_mode = 0;
341 		}
342 	} else
343 		pic->imr = data;
344 }
345 
346 /*
347  * i8259_specific_eoi
348  *
349  * Handles specific end of interrupt commands
350  *
351  * Parameters:
352  *  n: PIC to deliver this EOI to
353  *  data: interrupt to EOI
354  */
355 static void
356 i8259_specific_eoi(uint8_t n, uint8_t data)
357 {
358 	if (!(pics[n].isr & (1 << (data & 0x7)))) {
359 		log_warnx("%s: %s pic specific eoi irq %d while not in"
360 		    " service", __func__, i8259_pic_name(n), (data & 0x7));
361 	}
362 
363 	pics[n].isr &= ~(1 << (data & 0x7));
364 }
365 
366 /*
367  * i8259_nonspecific_eoi
368  *
369  * Handles nonspecific end of interrupt commands
370  * XXX not implemented
371  */
372 static void
373 i8259_nonspecific_eoi(uint8_t n, uint8_t data)
374 {
375 	int i = 0;
376 
377 	while (i < 8) {
378 		if ((pics[n].isr & (1 << (i & 0x7)))) {
379 			i8259_specific_eoi(n, i);
380 			return;
381 		}
382 		i++;
383 	}
384 }
385 
386 /*
387  * i8259_rotate_priority
388  *
389  * Rotates the interrupt priority on the specified PIC
390  *
391  * Parameters:
392  *  n: PIC whose priority should be rotated
393  */
394 static void
395 i8259_rotate_priority(uint8_t n)
396 {
397 	pics[n].lowest_pri++;
398 	if (pics[n].lowest_pri > 7)
399 		pics[n].lowest_pri = 0;
400 }
401 
402 /*
403  * i8259_write_cmdreg
404  *
405  * Write to the PIC command register
406  *
407  * Parameters:
408  *  n: PIC whose command register should be written to
409  *  data: data to write
410  */
411 static void
412 i8259_write_cmdreg(uint8_t n, uint8_t data)
413 {
414 	struct i8259 *pic = &pics[n];
415 
416 	if (data & ICW1_INIT) {
417 		/* Validate init params */
418 		if (!(data & ICW1_ICW4)) {
419 			log_warnx("%s: %s pic init error: no ICW4 request",
420 			    __func__, i8259_pic_name(n));
421 			return;
422 		}
423 
424 		if (data & (ICW1_IVA1 | ICW1_IVA2 | ICW1_IVA3)) {
425 			log_warnx("%s: %s pic init error: IVA specified",
426 			    __func__, i8259_pic_name(n));
427 			return;
428 		}
429 
430 		if (data & ICW1_SNGL) {
431 			log_warnx("%s: %s pic init error: single pic mode",
432 			    __func__, i8259_pic_name(n));
433 			return;
434 		}
435 
436 		if (data & ICW1_ADI) {
437 			log_warnx("%s: %s pic init error: address interval",
438 			    __func__, i8259_pic_name(n));
439 			return;
440 		}
441 
442 		if (data & ICW1_LTIM) {
443 			log_warnx("%s: %s pic init error: level trigger mode",
444 			    __func__, i8259_pic_name(n));
445 			return;
446 		}
447 
448 		pic->init_mode = 1;
449 		pic->cur_icw = 2;
450 		pic->imr = 0;
451 		pic->isr = 0;
452 		pic->irr = 0;
453 		pic->asserted = 0;
454 		pic->lowest_pri = 7;
455 		pic->rotate_auto_eoi = 0;
456 		return;
457 	} else if (data & OCW_SELECT) {
458 			/* OCW3 */
459 			if (data & OCW3_ACTION) {
460 				if (data & OCW3_RR) {
461 					if (data & OCW3_RIS)
462 						pic->next_ocw_read = PIC_ISR;
463 					else
464 						pic->next_ocw_read = PIC_IRR;
465 				}
466 			}
467 
468 			if (data & OCW3_SMACTION) {
469 				if (data & OCW3_SMM) {
470 					pic->smm = 1;
471 					/* XXX update intr here */
472 				} else
473 					pic->smm = 0;
474 			}
475 
476 			if (data & OCW3_POLL) {
477 				pic->poll = 1;
478 				/* XXX update intr here */
479 			}
480 
481 			return;
482 	} else {
483 		/* OCW2 */
484 		if (data & OCW2_EOI) {
485 			/*
486 			 * An EOI command was received. It could be one of
487 			 * several different varieties:
488 			 *
489 			 * Nonspecific EOI (0x20)
490 			 * Specific EOI (0x60..0x67)
491 			 * Nonspecific EOI + rotate (0xA0)
492 			 * Specific EOI + rotate (0xE0..0xE7)
493 			 */
494 			switch (data) {
495 			case OCW2_EOI:
496 				i8259_nonspecific_eoi(n, data);
497 				break;
498 			case OCW2_SEOI ... OCW2_SEOI + 7:
499 				i8259_specific_eoi(n, data);
500 				break;
501 			case OCW2_ROTATE_NSEOI:
502 				i8259_nonspecific_eoi(n, data);
503 				i8259_rotate_priority(n);
504 				break;
505 			case OCW2_ROTATE_SEOI ... OCW2_ROTATE_SEOI + 7:
506 				i8259_specific_eoi(n, data);
507 				i8259_rotate_priority(n);
508 				break;
509 			}
510 			return;
511 		}
512 
513 		if (data == OCW2_NOP)
514 			return;
515 
516 		if ((data & OCW2_SET_LOWPRIO) == OCW2_SET_LOWPRIO) {
517 			/* Set low priority value (bits 0-2) */
518 			pic->lowest_pri = data & 0x7;
519 			return;
520 		}
521 
522 		if (data == OCW2_ROTATE_AEOI_CLEAR) {
523 			pic->rotate_auto_eoi = 0;
524 			return;
525 		}
526 
527 		if (data == OCW2_ROTATE_AEOI_SET) {
528 			pic->rotate_auto_eoi = 1;
529 			return;
530 		}
531 
532 		return;
533 	}
534 }
535 
536 /*
537  * i8259_read_datareg
538  *
539  * Read the PIC's IMR
540  *
541  * Parameters:
542  *  n: PIC to read
543  *
544  * Return value:
545  *  selected PIC's IMR
546  */
547 static uint8_t
548 i8259_read_datareg(uint8_t n)
549 {
550 	struct i8259 *pic = &pics[n];
551 
552 	return (pic->imr);
553 }
554 
555 /*
556  * i8259_read_cmdreg
557  *
558  * Read the PIC's IRR or ISR, depending on the current PIC mode (value
559  * selected via the OCW3 command)
560  *
561  * Parameters:
562  *  n: PIC to read
563  *
564  * Return value:
565  *  selected PIC's IRR/ISR
566  */
567 static uint8_t
568 i8259_read_cmdreg(uint8_t n)
569 {
570 	struct i8259 *pic = &pics[n];
571 
572 	if (pic->next_ocw_read == PIC_IRR)
573 		return (pic->irr);
574 	else if (pic->next_ocw_read == PIC_ISR)
575 		return (pic->isr);
576 
577 	fatal("%s: invalid PIC config during cmdreg read", __func__);
578 }
579 
580 /*
581  * i8259_io_write
582  *
583  * Callback to handle write I/O to the emulated PICs in the VM
584  *
585  * Parameters:
586  *  vei: vm exit info for this I/O
587  */
588 static void
589 i8259_io_write(struct vm_exit *vei)
590 {
591 	uint16_t port = vei->vei.vei_port;
592 	uint32_t data;
593 	uint8_t n = 0;
594 
595 	get_input_data(vei, &data);
596 
597 	switch (port) {
598 	case IO_ICU1:
599 	case IO_ICU1 + 1:
600 		n = MASTER;
601 		break;
602 	case IO_ICU2:
603 	case IO_ICU2 + 1:
604 		n = SLAVE;
605 		break;
606 	default:
607 		fatal("%s: invalid port 0x%x", __func__, port);
608 	}
609 
610 	mutex_lock(&pic_mtx);
611 	if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1)
612 		i8259_write_datareg(n, data);
613 	else
614 		i8259_write_cmdreg(n, data);
615 	mutex_unlock(&pic_mtx);
616 }
617 
618 /*
619  * i8259_io_read
620  *
621  * Callback to handle read I/O to the emulated PICs in the VM
622  *
623  * Parameters:
624  *  vei: vm exit info for this I/O
625  *
626  * Return values:
627  *  data that was read, based on the port information in 'vei'
628  */
629 static uint8_t
630 i8259_io_read(struct vm_exit *vei)
631 {
632 	uint16_t port = vei->vei.vei_port;
633 	uint8_t n = 0;
634 	uint8_t rv;
635 
636 	switch (port) {
637 	case IO_ICU1:
638 	case IO_ICU1 + 1:
639 		n = MASTER;
640 		break;
641 	case IO_ICU2:
642 	case IO_ICU2 + 1:
643 		n = SLAVE;
644 		break;
645 	default:
646 		fatal("%s: invalid port 0x%x", __func__, port);
647 	}
648 
649 	mutex_lock(&pic_mtx);
650 	if (port == IO_ICU1 + 1 || port == IO_ICU2 + 1)
651 		rv = i8259_read_datareg(n);
652 	else
653 		rv = i8259_read_cmdreg(n);
654 	mutex_unlock(&pic_mtx);
655 
656 	return (rv);
657 }
658 
659 /*
660  * vcpu_exit_i8259
661  *
662  * Top level exit handler for PIC operations
663  *
664  * Parameters:
665  *  vrp: VCPU run parameters (contains exit information) for this PIC operation
666  *
667  * Return value:
668  *  Always 0xFF (PIC read/writes don't generate interrupts directly)
669  */
670 uint8_t
671 vcpu_exit_i8259(struct vm_run_params *vrp)
672 {
673 	struct vm_exit *vei = vrp->vrp_exit;
674 
675 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
676 		i8259_io_write(vei);
677 	} else {
678 		set_return_data(vei, i8259_io_read(vei));
679 	}
680 
681 	return (0xFF);
682 }
683 
684 /*
685  * pic_set_elcr
686  *
687  * Sets edge or level triggered mode for the given IRQ. Used internally
688  * by the vmd PCI setup code. Guest VMs writing to ELCRx will do so via
689  * vcpu_exit_elcr.
690  *
691  * Parameters:
692  *  irq: IRQ (0-15) to set
693  *  val: 0 if edge triggered mode, 1 if level triggered mode
694  */
695 void
696 pic_set_elcr(uint8_t irq, uint8_t val)
697 {
698 	if (irq > 15 || val > 1)
699 		return;
700 
701 	log_debug("%s: setting %s triggered mode for irq %d", __func__,
702 	    val ? "level" : "edge", irq);
703 
704 	if (irq > 7) {
705 		if (val)
706 			elcr[SLAVE] |= (1 << (irq - 8));
707 		else
708 			elcr[SLAVE] &= ~(1 << (irq - 8));
709 	} else {
710 		if (val)
711 			elcr[MASTER] |= (1 << irq);
712 		else
713 			elcr[MASTER] &= ~(1 << irq);
714 	}
715 }
716 
717 /*
718  * vcpu_exit_elcr
719  *
720  * Handler for the ELCRx registers
721  *
722  * Parameters:
723  *  vrp: VCPU run parameters (contains exit information) for this ELCR I/O
724  *
725  * Return value:
726  *  Always 0xFF (PIC read/writes don't generate interrupts directly)
727  */
728 uint8_t
729 vcpu_exit_elcr(struct vm_run_params *vrp)
730 {
731 	struct vm_exit *vei = vrp->vrp_exit;
732 	uint8_t elcr_reg = vei->vei.vei_port - ELCR0;
733 
734 	if (elcr_reg > 1) {
735 		log_debug("%s: invalid ELCR index %d", __func__, elcr_reg);
736 		return (0xFF);
737 	}
738 
739 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
740 		log_debug("%s: ELCR[%d] set to 0x%x", __func__, elcr_reg,
741 		    (uint8_t)vei->vei.vei_data);
742 		elcr[elcr_reg] = (uint8_t)vei->vei.vei_data;
743 	} else {
744 		set_return_data(vei, elcr[elcr_reg]);
745 	}
746 
747 	return (0xFF);
748 }
749 
750 int
751 i8259_dump(int fd)
752 {
753 	log_debug("%s: sending PIC", __func__);
754 	if (atomicio(vwrite, fd, &pics, sizeof(pics)) != sizeof(pics)) {
755 		log_warnx("%s: error writing PIC to fd", __func__);
756 		return (-1);
757 	}
758 
759 	log_debug("%s: sending ELCR", __func__);
760 	if (atomicio(vwrite, fd, &elcr, sizeof(elcr)) != sizeof(elcr)) {
761 		log_warnx("%s: error writing ELCR to fd", __func__);
762 		return (-1);
763 	}
764 	return (0);
765 }
766 
767 int
768 i8259_restore(int fd)
769 {
770 	log_debug("%s: restoring PIC", __func__);
771 	if (atomicio(read, fd, &pics, sizeof(pics)) != sizeof(pics)) {
772 		log_warnx("%s: error reading PIC from fd", __func__);
773 		return (-1);
774 	}
775 
776 	log_debug("%s: restoring ELCR", __func__);
777 	if (atomicio(read, fd, &elcr, sizeof(elcr)) != sizeof(elcr)) {
778 		log_warnx("%s: error reading ELCR from fd", __func__);
779 		return (-1);
780 	}
781 
782 	if (pthread_mutex_init(&pic_mtx, NULL) != 0)
783 		fatalx("unable to create pic mutex");
784 
785 	return (0);
786 }
787