summaryrefslogtreecommitdiffstats
path: root/src/drivers/pc80/pc/i8259.c
blob: 4261d5bec4088615deb3f945ea1e8eb5ca4dac61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2009 coresystems GmbH
 * Copyright (C) 2013 Sage Electronic Engineering, LLC.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <arch/io.h>
#include <pc80/i8259.h>
#include <console/console.h>

/* Read the current PIC IRQ mask */
u16 pic_read_irq_mask(void)
{
	u16 mask;
	int i;

	mask = inb(MASTER_PIC_OCW1) | (inb(SLAVE_PIC_OCW1) << 8);

	printk(BIOS_DEBUG, "8259 PIC: OCW1 IRQ Mask: 0x%x\n", mask);
	printk(BIOS_SPEW, "\tEnabled IRQs (0 = Unmasked, 1 = Masked off):\n"
			"\t\tMaster\t\tSlave\n");
	for (i = 0; i <= 7; i++) {
		printk(BIOS_SPEW, "\t\tIRQ%X: %x\t\tIRQ%X: %x\n",
				i, (mask >> i) & 1, i + 8, (mask >> (i + 8)) & 1);
	}
	return mask;
}

/*
 * Write an IRQ mask to the PIC:
 * IRQA is bit 0xA in the 16 bit bitmask (OCW1)
 */
void pic_write_irq_mask(u16 mask)
{
	outb(mask, MASTER_PIC_OCW1);
	outb(mask >> 8, SLAVE_PIC_OCW1);
}

/*
 * The PIC IRQs default to masked off
 * Allow specific IRQs to be enabled (1)
 * or disabled by (0) the user
 */
void pic_irq_enable(u8 int_num, u8 mask)
{
	pic_write_irq_mask(pic_read_irq_mask() & ~(mask << int_num));
	pic_read_irq_mask();
}

void setup_i8259(void)
{
	/* A write to ICW1 starts the Interrupt Controller Initialization
	 * Sequence. This implicitly causes the following to happen:
	 *   - Interrupt Mask register is cleared
	 *   - Priority 7 is assigned to IRQ7 input
	 *   - Slave mode address is set to 7
	 *   - Special mask mode is cleared
	 *
	 * We send the initialization sequence to both the master and
	 * slave i8259 controller.
	 */
	outb(ICW_SELECT|IC4, MASTER_PIC_ICW1);
	outb(ICW_SELECT|IC4, SLAVE_PIC_ICW1);

	/* Now the interrupt controller expects us to write to ICW2. */
	outb(INT_VECTOR_MASTER | IRQ0, MASTER_PIC_ICW2);
	outb(INT_VECTOR_SLAVE  | IRQ8, SLAVE_PIC_ICW2);

	/* Now the interrupt controller expects us to write to ICW3.
	 *
	 * The normal scenario is to set up cascading on IRQ2 on the master
	 * i8259 and assign the slave ID 2 to the slave i8259.
	 */
	outb(CASCADED_PIC, MASTER_PIC_ICW3);
	outb(SLAVE_ID, SLAVE_PIC_ICW3);

	/* Now the interrupt controller expects us to write to ICW4.
	 *
	 * We switch both i8259 to microprocessor mode because they're
	 * operating as part of an x86 architecture based chipset
	 */
	outb(MICROPROCESSOR_MODE, MASTER_PIC_ICW2);
	outb(MICROPROCESSOR_MODE, SLAVE_PIC_ICW2);

	/* Now clear the interrupts through OCW1.
	 * First we mask off all interrupts on the slave interrupt controller
	 * then we mask off all interrupts but interrupt 2 on the master
	 * controller. This way the cascading stays alive.
	 */
	outb(ALL_IRQS, SLAVE_PIC_OCW1);
	outb(ALL_IRQS & ~IRQ2, MASTER_PIC_OCW1);
}

/**
 * @brief Configure IRQ triggering in the i8259 compatible Interrupt Controller.
 *
 * Switch a certain interrupt to be level / edge triggered.
 *
 * @param int_num legacy interrupt number (3-7, 9-15)
 * @param is_level_triggered 1 for level triggered interrupt, 0 for edge
 *        triggered interrupt
 */
void i8259_configure_irq_trigger(int int_num, int is_level_triggered)
{
	u16 int_bits = inb(ELCR1) | (((u16)inb(ELCR2)) << 8);

	if (is_level_triggered)
		int_bits |= (1 << int_num);
	else
		int_bits &= ~(1 << int_num);

	/* Write new values */
	outb((u8)(int_bits & 0xff), ELCR1);
	outb((u8)(int_bits >> 8), ELCR2);

#ifdef PARANOID_IRQ_TRIGGERS
	/* Try reading back the new values. This seems like an error
	 * but it is not. */
	if (inb(ELCR1) != (int_bits & 0xff)) {
		printk(BIOS_ERR, "%s: lower order bits are wrong: want 0x%x, got 0x%x\n",
				__func__, (int_bits & 0xff), inb(ELCR1));
	}

	if (inb(ELCR2) != (int_bits >> 8)) {
		printk(BIOS_ERR, "%s: higher order bits are wrong: want 0x%x, got 0x%x\n",
				__func__, (int_bits>>8), inb(ELCR2));
	}
#endif
}