GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/vmd/i8259.c Lines: 0 237 0.0 %
Date: 2017-11-07 Branches: 0 154 0.0 %

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