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 
install_irq_handler(irq_t irq,segoff_t * handler,uint8_t * previously_enabled,segoff_t * previous_handler)24 int 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 
remove_irq_handler(irq_t irq,segoff_t * handler,uint8_t * previously_enabled,segoff_t * previous_handler)55 int 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 
send_specific_eoi(irq_t irq)81 void 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
dump_irq_status(void)94 void 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  */
112 void (*undi_irq_handler)P((void)) = _undi_irq_handler;
113 uint16_t volatile *undi_irq_trigger_count = &_undi_irq_trigger_count;
114 segoff_t *undi_irq_chain_to = &_undi_irq_chain_to;
115 uint8_t *undi_irq_chain = &_undi_irq_chain;
116 irq_t undi_irq_installed_on = IRQ_NONE;
117 
118 /* UNDI entry point and irq, used by interrupt handler
119  */
120 segoff_t *pxenv_undi_entrypointsp = &_pxenv_undi_entrypointsp;
121 uint8_t *pxenv_undi_irq = &_pxenv_undi_irq;
122 
123 /* Previous trigger count for undi IRQ handler */
124 static 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 
install_undi_irq_handler(irq_t irq,segoff_t entrypointsp)129 int 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 
remove_undi_irq_handler(irq_t irq)161 int 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 
undi_irq_triggered(irq_t irq)191 int 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