Standardised debug mechanism in place now.
[people/xl0/gpxe.git] / src / arch / i386 / core / pic8259.c
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 #include "realmode.h"
10
11 /* State of trivial IRQ handler */
12 irq_t trivial_irq_installed_on = IRQ_NONE;
13 static uint16_t trivial_irq_previous_trigger_count = 0;
14
15 /* The actual trivial IRQ handler
16  *
17  * Note: we depend on the C compiler not realising that we're putting
18  * variables in the ".text16" section and therefore not forcing them
19  * back to the ".data" section.  I don't see any reason to expect this
20  * behaviour to change.
21  *
22  * These must *not* be the first variables to appear in this file; the
23  * first variable to appear gets the ".data" directive.
24  */
25 RM_FRAGMENT(_trivial_irq_handler,
26         "pushw %bx\n\t"
27         "call  1f\n1:\tpopw %bx\n\t"   /* PIC access to variables */
28         "incw  %cs:(_trivial_irq_trigger_count-1b)(%bx)\n\t" 
29         "popw  %bx\n\t" 
30         "iret\n\t" 
31         "\n\t"
32         ".globl _trivial_irq_trigger_count\n\t"
33         "_trivial_irq_trigger_count: .short 0\n\t"
34         "\n\t"
35         ".globl _trivial_irq_chain_to\n\t"
36         "_trivial_irq_chain_to: .short 0,0\n\t"
37         "\n\t"
38         ".globl _trivial_irq_chain\n\t"
39         "_trivial_irq_chain: .byte 0\n\t"
40         );
41 extern volatile uint16_t _trivial_irq_trigger_count;
42 extern segoff_t _trivial_irq_chain_to;
43 extern int8_t _trivial_irq_chain;
44
45 /* Current locations of trivial IRQ handler.  These will change at
46  * runtime when relocation is used; the handler needs to be copied to
47  * base memory before being installed.
48  */
49 void (*trivial_irq_handler)P((void)) = _trivial_irq_handler;
50 uint16_t volatile *trivial_irq_trigger_count = &_trivial_irq_trigger_count;
51 segoff_t *trivial_irq_chain_to = &_trivial_irq_chain_to;
52 uint8_t *trivial_irq_chain = &_trivial_irq_chain;
53
54 /* Install a handler for the specified IRQ.  Address of previous
55  * handler will be stored in previous_handler.  Enabled/disabled state
56  * of IRQ will be preserved across call, therefore if the handler does
57  * chaining, ensure that either (a) IRQ is disabled before call, or
58  * (b) previous_handler points directly to the place that the handler
59  * picks up its chain-to address.
60  */
61
62 int install_irq_handler ( irq_t irq, segoff_t *handler,
63                           uint8_t *previously_enabled,
64                           segoff_t *previous_handler ) {
65         segoff_t *irq_vector = IRQ_VECTOR ( irq );
66         *previously_enabled = irq_enabled ( irq );
67
68         if ( irq > IRQ_MAX ) {
69                 DBG ( "Invalid IRQ number %d\n" );
70                 return 0;
71         }
72
73         previous_handler->segment = irq_vector->segment;
74         previous_handler->offset = irq_vector->offset;
75         if ( *previously_enabled ) disable_irq ( irq );
76         DBG ( "Installing handler at %hx:%hx for IRQ %d (vector 0000:%hx),"
77               " leaving %s\n",
78               handler->segment, handler->offset, irq, virt_to_phys(irq_vector),
79               ( *previously_enabled ? "enabled" : "disabled" ) );
80         DBG ( "...(previous handler at %hx:%hx)\n",
81               previous_handler->segment, previous_handler->offset );
82         irq_vector->segment = handler->segment;
83         irq_vector->offset = handler->offset;
84         if ( *previously_enabled ) enable_irq ( irq );
85         return 1;
86 }
87
88 /* Remove handler for the specified IRQ.  Routine checks that another
89  * handler has not been installed that chains to handler before
90  * uninstalling handler.  Enabled/disabled state of the IRQ will be
91  * restored to that specified by previously_enabled.
92  */
93
94 int remove_irq_handler ( irq_t irq, segoff_t *handler,
95                          uint8_t *previously_enabled,
96                          segoff_t *previous_handler ) {
97         segoff_t *irq_vector = IRQ_VECTOR ( irq );
98
99         if ( irq > IRQ_MAX ) {
100                 DBG ( "Invalid IRQ number %d\n" );
101                 return 0;
102         }
103         if ( ( irq_vector->segment != handler->segment ) ||
104              ( irq_vector->offset != handler->offset ) ) {
105                 DBG ( "Cannot remove handler for IRQ %d\n" );
106                 return 0;
107         }
108
109         DBG ( "Removing handler for IRQ %d\n", irq );
110         disable_irq ( irq );
111         irq_vector->segment = previous_handler->segment;
112         irq_vector->offset = previous_handler->offset;
113         if ( *previously_enabled ) enable_irq ( irq );
114         return 1;
115 }
116
117 /* Install the trivial IRQ handler.  This routine installs the
118  * handler, tests it and enables the IRQ.
119  */
120
121 int install_trivial_irq_handler ( irq_t irq ) {
122         segoff_t trivial_irq_handler_segoff = SEGOFF(trivial_irq_handler);
123         
124         if ( trivial_irq_installed_on != IRQ_NONE ) {
125                 DBG ( "Can install trivial IRQ handler only once\n" );
126                 return 0;
127         }
128         if ( SEGMENT(trivial_irq_handler) > 0xffff ) {
129                 DBG ( "Trivial IRQ handler not in base memory\n" );
130                 return 0;
131         }
132
133         DBG ( "Installing trivial IRQ handler on IRQ %d\n", irq );
134         if ( ! install_irq_handler ( irq, &trivial_irq_handler_segoff,
135                                      trivial_irq_chain,
136                                      trivial_irq_chain_to ) )
137                 return 0;
138         trivial_irq_installed_on = irq;
139
140         DBG ( "Testing trivial IRQ handler\n" );
141         disable_irq ( irq );
142         *trivial_irq_trigger_count = 0;
143         trivial_irq_previous_trigger_count = 0;
144         fake_irq ( irq );
145         if ( ! trivial_irq_triggered ( irq ) ) {
146                 DBG ( "Installation of trivial IRQ handler failed\n" );
147                 remove_trivial_irq_handler ( irq );
148                 return 0;
149         }
150         /* Send EOI just in case there was a leftover interrupt */
151         send_specific_eoi ( irq );
152         DBG ( "Trivial IRQ handler installed successfully\n" );
153         enable_irq ( irq );
154         return 1;
155 }
156
157 /* Remove the trivial IRQ handler.
158  */
159
160 int remove_trivial_irq_handler ( irq_t irq ) {
161         segoff_t trivial_irq_handler_segoff = SEGOFF(trivial_irq_handler);
162
163         if ( trivial_irq_installed_on == IRQ_NONE ) return 1;
164         if ( irq != trivial_irq_installed_on ) {
165                 DBG ( "Cannot uninstall trivial IRQ handler from IRQ %d; "
166                       "is installed on IRQ %d\n", irq,
167                       trivial_irq_installed_on );
168                 return 0;
169         }
170
171         if ( ! remove_irq_handler ( irq, &trivial_irq_handler_segoff,
172                                     trivial_irq_chain,
173                                     trivial_irq_chain_to ) )
174                 return 0;
175
176         if ( trivial_irq_triggered ( trivial_irq_installed_on ) ) {
177                 DBG ( "Sending EOI for unwanted trivial IRQ\n" );
178                 send_specific_eoi ( trivial_irq_installed_on );
179         }
180
181         trivial_irq_installed_on = IRQ_NONE;
182         return 1;
183 }
184
185 /* Safe method to detect whether or not trivial IRQ has been
186  * triggered.  Using this call avoids potential race conditions.  This
187  * call will return success only once per trigger.
188  */
189
190 int trivial_irq_triggered ( irq_t irq ) {
191         uint16_t trivial_irq_this_trigger_count = *trivial_irq_trigger_count;
192         int triggered = ( trivial_irq_this_trigger_count -
193                           trivial_irq_previous_trigger_count );
194         
195         /* irq is not used at present, but we have it in the API for
196          * future-proofing; in case we want the facility to have
197          * multiple trivial IRQ handlers installed simultaneously.
198          *
199          * Avoid compiler warning about unused variable.
200          */
201         if ( irq == IRQ_NONE ) {};
202         
203         trivial_irq_previous_trigger_count = trivial_irq_this_trigger_count;
204         return triggered ? 1 : 0;
205 }
206
207 /* Copy trivial IRQ handler to a new location.  Typically used to copy
208  * the handler into base memory; when relocation is being used we need
209  * to do this before installing the handler.
210  *
211  * Call with target=NULL in order to restore the handler to its
212  * original location.
213  */
214
215 int copy_trivial_irq_handler ( void *target, size_t target_size ) {
216         irq_t currently_installed_on = trivial_irq_installed_on;
217         uint32_t offset = ( target == NULL ? 0 :
218                             target - (void*)_trivial_irq_handler );
219
220         if (( target != NULL ) && ( target_size < TRIVIAL_IRQ_HANDLER_SIZE )) {
221                 DBG ( "Insufficient space to copy trivial IRQ handler\n" );
222                 return 0;
223         }
224
225         if ( currently_installed_on != IRQ_NONE ) {
226                 DBG ("WARNING: relocating trivial IRQ handler while in use\n");
227                 if ( ! remove_trivial_irq_handler ( currently_installed_on ) )
228                         return 0;
229         }
230
231         /* Do the actual copy */
232         if ( target != NULL ) {
233                 DBG ( "Copying trivial IRQ handler to %hx:%hx\n",
234                       SEGMENT(target), OFFSET(target) );
235                 memcpy ( target, _trivial_irq_handler,
236                          TRIVIAL_IRQ_HANDLER_SIZE );
237         } else {
238                 DBG ( "Restoring trivial IRQ handler to original location\n" );
239         }
240         /* Update all the pointers to structures within the handler */
241         trivial_irq_handler = ( void (*)P((void)) )
242                 ( (void*)_trivial_irq_handler + offset );
243         trivial_irq_trigger_count = (uint16_t*)
244                 ( (void*)&_trivial_irq_trigger_count + offset );
245         trivial_irq_chain_to = (segoff_t*)
246                 ( (void*)&_trivial_irq_chain_to + offset );
247         trivial_irq_chain = (uint8_t*)
248                 ( (void*)&_trivial_irq_chain + offset );
249
250         if ( currently_installed_on != IRQ_NONE ) {
251                 if ( ! install_trivial_irq_handler ( currently_installed_on ) )
252                         return 0;
253         }
254         return 1;
255 }
256
257 /* Send non-specific EOI(s).  This seems to be inherently unsafe.
258  */
259
260 void send_nonspecific_eoi ( irq_t irq ) {
261         DBG ( "Sending non-specific EOI for IRQ %d\n", irq );
262         if ( irq >= IRQ_PIC_CUTOFF ) {
263                 outb ( ICR_EOI_NON_SPECIFIC, PIC2_ICR );
264         }               
265         outb ( ICR_EOI_NON_SPECIFIC, PIC1_ICR );
266 }
267
268 /* Send specific EOI(s).
269  */
270
271 void send_specific_eoi ( irq_t irq ) {
272         DBG ( "Sending specific EOI for IRQ %d\n", irq );
273         outb ( ICR_EOI_SPECIFIC | ICR_VALUE(irq), ICR_REG(irq) );
274         if ( irq >= IRQ_PIC_CUTOFF ) {
275                 outb ( ICR_EOI_SPECIFIC | ICR_VALUE(CHAINED_IRQ),
276                        ICR_REG(CHAINED_IRQ) );
277         }
278 }
279
280 /* Fake an IRQ
281  */
282
283 void fake_irq ( irq_t irq ) {
284         struct {
285                 uint16_t int_number;
286         } PACKED in_stack;
287
288         /* Convert IRQ to INT number:
289          *
290          * subb $0x08,%cl       Invert bit 3, set bits 4-7 iff irq < 8
291          * xorb $0x70,%cl       Invert bits 4-6
292          * andb $0x7f,%cl       Clear bit 7
293          *
294          * No, it's not the most intuitive method, but I was proud to
295          * get it down to three lines of assembler when this routine
296          * was originally implemented in pcbios.S.
297          */
298         in_stack.int_number = ( ( irq - 8 ) ^ 0x70 ) & 0x7f;
299
300         RM_FRAGMENT(rm_fake_irq,
301                 "popw %ax\n\t"          /* %ax = INT number */
302                 "call 1f\n1:\tpop %bx\n\t"
303                 "movb %al, %cs:(2f-1b+1)(%bx)\n\t" /* Overwrite INT number..*/
304                 "\n2:\tint $0x00\n\t"               /* ..in this instruction */
305         );
306
307         real_call ( rm_fake_irq, &in_stack, NULL );
308 }
309
310 /* Dump current 8259 status: enabled IRQs and handler addresses.
311  */
312
313 #ifdef DEBUG_IRQ
314 void dump_irq_status ( void ) {
315         int irq = 0;
316         
317         for ( irq = 0; irq < 16; irq++ ) {
318                 if ( irq_enabled ( irq ) ) {
319                         printf ( "IRQ%d enabled, ISR at %hx:%hx\n", irq,
320                                  IRQ_VECTOR(irq)->segment,
321                                  IRQ_VECTOR(irq)->offset );
322                 }
323         }
324 }
325 #endif