1/*
2 * Basic support for controlling the 8259 Programmable Interrupt Controllers.
3 *
4 * Initially written by Michael Brown (mcb30).
5 */
6
7#include <etherboot.h>
8#include <pic8259.h>
9
10#ifdef DEBUG_IRQ
11#define DBG(...) printf ( __VA_ARGS__ )
12#else
13#define DBG(...)
14#endif
15
16/* Install a handler for the specified IRQ.  Address of previous
17 * handler will be stored in previous_handler.  Enabled/disabled state
18 * of IRQ will be preserved across call, therefore if the handler does
19 * chaining, ensure that either (a) IRQ is disabled before call, or
20 * (b) previous_handler points directly to the place that the handler
21 * picks up its chain-to address.
22 */
23
24int install_irq_handler ( irq_t irq, segoff_t *handler,
25			  uint8_t *previously_enabled,
26			  segoff_t *previous_handler ) {
27	segoff_t *irq_vector = IRQ_VECTOR ( irq );
28	*previously_enabled = irq_enabled ( irq );
29
30	if ( irq > IRQ_MAX ) {
31		DBG ( "Invalid IRQ number %d\n" );
32		return 0;
33	}
34
35	previous_handler->segment = irq_vector->segment;
36	previous_handler->offset = irq_vector->offset;
37	if ( *previously_enabled ) disable_irq ( irq );
38	DBG ( "Installing handler at %hx:%hx for IRQ %d, leaving %s\n",
39		  handler->segment, handler->offset, irq,
40		  ( *previously_enabled ? "enabled" : "disabled" ) );
41	DBG ( "...(previous handler at %hx:%hx)\n",
42		  previous_handler->segment, previous_handler->offset );
43	irq_vector->segment = handler->segment;
44	irq_vector->offset = handler->offset;
45	if ( *previously_enabled ) enable_irq ( irq );
46	return 1;
47}
48
49/* Remove handler for the specified IRQ.  Routine checks that another
50 * handler has not been installed that chains to handler before
51 * uninstalling handler.  Enabled/disabled state of the IRQ will be
52 * restored to that specified by previously_enabled.
53 */
54
55int remove_irq_handler ( irq_t irq, segoff_t *handler,
56			 uint8_t *previously_enabled,
57			 segoff_t *previous_handler ) {
58	segoff_t *irq_vector = IRQ_VECTOR ( irq );
59
60	if ( irq > IRQ_MAX ) {
61		DBG ( "Invalid IRQ number %d\n" );
62		return 0;
63	}
64	if ( ( irq_vector->segment != handler->segment ) ||
65	     ( irq_vector->offset != handler->offset ) ) {
66		DBG ( "Cannot remove handler for IRQ %d\n" );
67		return 0;
68	}
69
70	DBG ( "Removing handler for IRQ %d\n", irq );
71	disable_irq ( irq );
72	irq_vector->segment = previous_handler->segment;
73	irq_vector->offset = previous_handler->offset;
74	if ( *previously_enabled ) enable_irq ( irq );
75	return 1;
76}
77
78/* Send specific EOI(s).
79 */
80
81void send_specific_eoi ( irq_t irq ) {
82	DBG ( "Sending specific EOI for IRQ %d\n", irq );
83	outb ( ICR_EOI_SPECIFIC | ICR_VALUE(irq), ICR_REG(irq) );
84	if ( irq >= IRQ_PIC_CUTOFF ) {
85		outb ( ICR_EOI_SPECIFIC | ICR_VALUE(CHAINED_IRQ),
86		       ICR_REG(CHAINED_IRQ) );
87	}
88}
89
90/* Dump current 8259 status: enabled IRQs and handler addresses.
91 */
92
93#ifdef DEBUG_IRQ
94void dump_irq_status (void) {
95	int irq = 0;
96
97	for ( irq = 0; irq < 16; irq++ ) {
98		if ( irq_enabled ( irq ) ) {
99			printf ( "IRQ%d enabled, ISR at %hx:%hx\n", irq,
100				 IRQ_VECTOR(irq)->segment,
101				 IRQ_VECTOR(irq)->offset );
102		}
103	}
104}
105#endif
106
107/********************************************************************
108 * UNDI interrupt handling
109 * This essentially follows the defintion of the trivial interrupt
110 * handler routines. The text is assumed to locate in base memory.
111 */
112void (*undi_irq_handler)P((void)) = _undi_irq_handler;
113uint16_t volatile *undi_irq_trigger_count = &_undi_irq_trigger_count;
114segoff_t *undi_irq_chain_to = &_undi_irq_chain_to;
115uint8_t *undi_irq_chain = &_undi_irq_chain;
116irq_t undi_irq_installed_on = IRQ_NONE;
117
118/* UNDI entry point and irq, used by interrupt handler
119 */
120segoff_t *pxenv_undi_entrypointsp = &_pxenv_undi_entrypointsp;
121uint8_t *pxenv_undi_irq = &_pxenv_undi_irq;
122
123/* Previous trigger count for undi IRQ handler */
124static uint16_t undi_irq_previous_trigger_count = 0;
125
126/* Install the undi IRQ handler. Don't test as UNDI has not be opened.
127 */
128
129int install_undi_irq_handler ( irq_t irq, segoff_t entrypointsp ) {
130	segoff_t undi_irq_handler_segoff = SEGOFF(undi_irq_handler);
131
132	if ( undi_irq_installed_on != IRQ_NONE ) {
133		DBG ( "Can install undi IRQ handler only once\n" );
134		return 0;
135	}
136	if ( SEGMENT(undi_irq_handler) > 0xffff ) {
137		DBG ( "Trivial IRQ handler not in base memory\n" );
138		return 0;
139	}
140
141	DBG ( "Installing undi IRQ handler on IRQ %d\n", irq );
142	*pxenv_undi_entrypointsp = entrypointsp;
143	*pxenv_undi_irq = irq;
144	if ( ! install_irq_handler ( irq, &undi_irq_handler_segoff,
145				     undi_irq_chain,
146				     undi_irq_chain_to ) )
147		return 0;
148	undi_irq_installed_on = irq;
149
150	DBG ( "Disabling undi IRQ %d\n", irq );
151	disable_irq ( irq );
152	*undi_irq_trigger_count = 0;
153	undi_irq_previous_trigger_count = 0;
154	DBG ( "UNDI IRQ handler installed successfully\n" );
155	return 1;
156}
157
158/* Remove the undi IRQ handler.
159 */
160
161int remove_undi_irq_handler ( irq_t irq ) {
162	segoff_t undi_irq_handler_segoff = SEGOFF(undi_irq_handler);
163
164	if ( undi_irq_installed_on == IRQ_NONE ) return 1;
165	if ( irq != undi_irq_installed_on ) {
166		DBG ( "Cannot uninstall undi IRQ handler from IRQ %d; "
167		      "is installed on IRQ %d\n", irq,
168		      undi_irq_installed_on );
169		return 0;
170	}
171
172	if ( ! remove_irq_handler ( irq, &undi_irq_handler_segoff,
173				    undi_irq_chain,
174				    undi_irq_chain_to ) )
175		return 0;
176
177	if ( undi_irq_triggered ( undi_irq_installed_on ) ) {
178		DBG ( "Sending EOI for unwanted undi IRQ\n" );
179		send_specific_eoi ( undi_irq_installed_on );
180	}
181
182	undi_irq_installed_on = IRQ_NONE;
183	return 1;
184}
185
186/* Safe method to detect whether or not undi IRQ has been
187 * triggered.  Using this call avoids potential race conditions.  This
188 * call will return success only once per trigger.
189 */
190
191int undi_irq_triggered ( irq_t irq ) {
192	uint16_t undi_irq_this_trigger_count = *undi_irq_trigger_count;
193	int triggered = ( undi_irq_this_trigger_count -
194			  undi_irq_previous_trigger_count );
195
196	/* irq is not used at present, but we have it in the API for
197	 * future-proofing; in case we want the facility to have
198	 * multiple undi IRQ handlers installed simultaneously.
199	 *
200	 * Avoid compiler warning about unused variable.
201	 */
202	if ( irq == IRQ_NONE ) {};
203	undi_irq_previous_trigger_count = undi_irq_this_trigger_count;
204	return triggered ? 1 : 0;
205}
206