286ef5e267e67f45872b2999b827d7fc6945edb1
[reactos.git] / ntoskrnl / ke / i386 / irq.c
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 1998, 1999, 2000, 2001, 2002 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 /* $Id$
20  *
21  * PROJECT:         ReactOS kernel
22  * FILE:            ntoskrnl/ke/i386/irq.c
23  * PURPOSE:         IRQ handling
24  * PROGRAMMER:      David Welch (welch@mcmail.com)
25  * UPDATE HISTORY:
26  *             29/05/98: Created
27  */
28
29 /*
30  * NOTE: In general the PIC interrupt priority facilities are used to
31  * preserve the NT IRQL semantics, global interrupt disables are only used
32  * to keep the PIC in a consistent state
33  *
34  */
35
36 /* INCLUDES ****************************************************************/
37
38 #include <ddk/ntddk.h>
39 #include <roscfg.h>
40 #include <internal/ke.h>
41 #include <internal/ps.h>
42 #include <internal/i386/segment.h>
43 #include <internal/pool.h>
44
45 #ifdef MP
46 #include <internal/hal/mps.h>
47 #endif /* MP */
48
49 #define NDEBUG
50 #include <internal/debug.h>
51
52 /* GLOBALS *****************************************************************/
53
54 #ifdef MP
55
56 /* 
57  * FIXME: This does not work if we have more than 24 IRQs (ie. more than one 
58  * I/O APIC) 
59  */
60 #define VECTOR2IRQ(vector) (((vector) - 0x31) / 8)
61 #define VECTOR2IRQL(vector) (4 + VECTOR2IRQ(vector))
62
63 #define IRQ_BASE  FIRST_DEVICE_VECTOR
64 #define NR_IRQS   0x100 - 0x30
65
66 #define __STR(x) #x
67 #define STR(x) __STR(x)
68
69 #define INT_NAME(intnum) _KiUnexpectedInterrupt##intnum
70 #define INT_NAME2(intnum) KiUnexpectedInterrupt##intnum
71
72 #define BUILD_COMMON_INTERRUPT_HANDLER() \
73 __asm__( \
74   "_KiCommonInterrupt:\n\t" \
75   "cld\n\t" \
76   "pushl %ds\n\t" \
77   "pushl %es\n\t" \
78   "pushl %fs\n\t" \
79   "pushl %gs\n\t" \
80   "movl $0xceafbeef,%eax\n\t" \
81   "pushl %eax\n\t" \
82   "movl $" STR(KERNEL_DS) ",%eax\n\t" \
83   "movl %eax,%ds\n\t" \
84   "movl %eax,%es\n\t" \
85   "movl $" STR(PCR_SELECTOR) ",%eax\n\t" \
86   "movl %eax,%fs\n\t" \
87   "pushl %esp\n\t" \
88   "pushl %ebx\n\t" \
89   "call _KiInterruptDispatch\n\t" \
90   "popl %eax\n\t" \
91   "popl %eax\n\t" \
92   "popl %eax\n\t" \
93   "popl %gs\n\t" \
94   "popl %fs\n\t" \
95   "popl %es\n\t" \
96   "popl %ds\n\t" \
97   "popa\n\t" \
98   "iret\n\t");
99
100 #define BUILD_INTERRUPT_HANDLER(intnum) \
101 VOID INT_NAME2(intnum)(VOID); \
102 __asm__( \
103   STR(INT_NAME(intnum)) ":\n\t" \
104   "pusha\n\t" \
105   "movl $0x" STR(intnum) ",%ebx\n\t" \
106   "jmp _KiCommonInterrupt");
107
108
109 /* Interrupt handlers and declarations */
110
111 #define B(x,y) \
112   BUILD_INTERRUPT_HANDLER(x##y)
113
114 #define B16(x) \
115   B(x,0) B(x,1) B(x,2) B(x,3) \
116   B(x,4) B(x,5) B(x,6) B(x,7) \
117   B(x,8) B(x,9) B(x,A) B(x,B) \
118   B(x,C) B(x,D) B(x,E) B(x,F)
119
120
121 BUILD_COMMON_INTERRUPT_HANDLER()
122 B16(3) B16(4) B16(5) B16(6)
123 B16(7) B16(8) B16(9) B16(A)
124 B16(B) B16(C) B16(D) B16(E)
125 B16(F)
126
127 #undef B
128 #undef B16
129
130
131 /* Interrupt handler list */
132
133 #define L(x,y) \
134   (ULONG)& INT_NAME2(x##y)
135
136 #define L16(x) \
137         L(x,0), L(x,1), L(x,2), L(x,3), \
138         L(x,4), L(x,5), L(x,6), L(x,7), \
139         L(x,8), L(x,9), L(x,A), L(x,B), \
140         L(x,C), L(x,D), L(x,E), L(x,F)
141
142 static ULONG irq_handler[NR_IRQS] = {
143   L16(3), L16(4), L16(5), L16(6),
144   L16(7), L16(8), L16(9), L16(A),
145   L16(B), L16(C), L16(D), L16(E),
146   L16(F)
147 };
148
149 #undef L
150 #undef L16
151
152 #else /* MP */
153
154 #define NR_IRQS         (16)
155 #define IRQ_BASE        (0x40)
156
157  void irq_handler_0(void);
158  void irq_handler_1(void);
159  void irq_handler_2(void);
160  void irq_handler_3(void);
161  void irq_handler_4(void);
162  void irq_handler_5(void);
163  void irq_handler_6(void);
164  void irq_handler_7(void);
165  void irq_handler_8(void);
166  void irq_handler_9(void);
167  void irq_handler_10(void);
168  void irq_handler_11(void);
169  void irq_handler_12(void);
170  void irq_handler_13(void);
171  void irq_handler_14(void);
172  void irq_handler_15(void);
173
174 static unsigned int irq_handler[NR_IRQS]=
175         {
176                 (int)&irq_handler_0,
177                 (int)&irq_handler_1,
178                 (int)&irq_handler_2,
179                 (int)&irq_handler_3,
180                 (int)&irq_handler_4,
181                 (int)&irq_handler_5,
182                 (int)&irq_handler_6,
183                 (int)&irq_handler_7,
184                 (int)&irq_handler_8,
185                 (int)&irq_handler_9,
186                 (int)&irq_handler_10,
187                 (int)&irq_handler_11,
188                 (int)&irq_handler_12,
189                 (int)&irq_handler_13,
190                 (int)&irq_handler_14,
191                 (int)&irq_handler_15,
192         };
193
194 #endif /* MP */
195
196 /*
197  * PURPOSE: Object describing each isr 
198  * NOTE: The data in this table is only modified at passsive level but can
199  * be accessed at any irq level.
200  */
201
202 static LIST_ENTRY isr_table[NR_IRQS]={{NULL,NULL},};
203 static PKSPIN_LOCK isr_lock[NR_IRQS] = {NULL,};
204 static KSPIN_LOCK isr_table_lock = {0,};
205
206 #define TAG_ISR_LOCK     TAG('I', 'S', 'R', 'L')
207 #define TAG_KINTERRUPT   TAG('K', 'I', 'S', 'R')
208
209 /* FUNCTIONS ****************************************************************/
210
211 #define PRESENT (0x8000)
212 #define I486_INTERRUPT_GATE (0xe00)
213
214 VOID KeInitInterrupts (VOID)
215 {
216    int i;
217
218 #ifdef MP
219
220    /*
221     * Setup the IDT entries to point to the interrupt handlers
222     */
223    for (i=0;i<NR_IRQS;i++)
224      {
225         KiIdt[0x30+i].a=(irq_handler[i]&0xffff)+(KERNEL_CS<<16);
226         KiIdt[0x30+i].b=(irq_handler[i]&0xffff0000)+PRESENT+
227                             I486_INTERRUPT_GATE;
228         InitializeListHead(&isr_table[i]);
229      }
230
231 #else
232
233    /*
234     * Setup the IDT entries to point to the interrupt handlers
235     */
236    for (i=0;i<NR_IRQS;i++)
237      {
238         KiIdt[IRQ_BASE+i].a=(irq_handler[i]&0xffff)+(KERNEL_CS<<16);
239         KiIdt[IRQ_BASE+i].b=(irq_handler[i]&0xffff0000)+PRESENT+
240                             I486_INTERRUPT_GATE;
241         InitializeListHead(&isr_table[i]);
242      }
243
244 #endif
245
246 }
247
248 typedef struct _KIRQ_TRAPFRAME
249 {
250    ULONG Magic;
251    ULONG Fs;
252    ULONG Es;
253    ULONG Ds;
254    ULONG Eax;
255    ULONG Ecx;
256    ULONG Edx;
257    ULONG Ebx;
258    ULONG Esp;
259    ULONG Ebp;
260    ULONG Esi;
261    ULONG Edi;
262    ULONG Eip;
263    ULONG Cs;
264    ULONG Eflags;
265 } KIRQ_TRAPFRAME, *PKIRQ_TRAPFRAME;
266
267 #ifdef DBG
268
269 VOID
270 KeIRQTrapFrameToTrapFrame(PKIRQ_TRAPFRAME IrqTrapFrame,
271   PKTRAP_FRAME TrapFrame)
272 {
273    TrapFrame->Fs     = IrqTrapFrame->Fs;
274    TrapFrame->Fs     = IrqTrapFrame->Es;
275    TrapFrame->Ds     = IrqTrapFrame->Ds;
276    TrapFrame->Eax    = IrqTrapFrame->Eax;
277    TrapFrame->Ecx    = IrqTrapFrame->Ecx;
278    TrapFrame->Edx    = IrqTrapFrame->Edx;
279    TrapFrame->Ebx    = IrqTrapFrame->Ebx;
280    TrapFrame->Esp    = IrqTrapFrame->Esp;
281    TrapFrame->Ebp    = IrqTrapFrame->Ebp;
282    TrapFrame->Esi    = IrqTrapFrame->Esi;
283    TrapFrame->Edi    = IrqTrapFrame->Edi;
284    TrapFrame->Eip    = IrqTrapFrame->Eip;
285    TrapFrame->Cs     = IrqTrapFrame->Cs;
286    TrapFrame->Eflags = IrqTrapFrame->Eflags;
287 }
288
289 #endif
290
291 #ifdef MP
292
293 VOID
294 KiInterruptDispatch (ULONG Vector, PKIRQ_TRAPFRAME Trapframe)
295 /*
296  * FUNCTION: Calls the irq specific handler for an irq
297  * ARGUMENTS:
298  *         Vector    = Interrupt vector
299  *         Trapframe = CPU context
300  */
301 {
302    KIRQL old_level;
303    PKINTERRUPT isr;
304    PLIST_ENTRY current;
305    ULONG irq;
306
307 #ifdef DBG
308
309    KTRAP_FRAME KernelTrapFrame;
310
311    KeIRQTrapFrameToTrapFrame(Trapframe, &KernelTrapFrame);
312    KeGetCurrentThread()->TrapFrame = &KernelTrapFrame;
313
314 #endif /* DBG */
315
316    DPRINT("I(%d) ", Vector);
317
318    /*
319     * Notify the rest of the kernel of the raised irq level
320     */
321    HalBeginSystemInterrupt (Vector,
322                             VECTOR2IRQL(Vector),
323                             &old_level);
324
325    irq = VECTOR2IRQ(Vector);
326
327    /*
328     * Enable interrupts
329     * NOTE: Only higher priority interrupts will get through
330     */
331    __asm__("sti\n\t");
332
333    if (irq == 0)
334      {
335        if (KeGetCurrentProcessorNumber() == 0)
336          {
337        KiUpdateSystemTime(old_level, Trapframe->Eip);
338          }
339      }
340    else
341      {
342       DPRINT("KiInterruptDispatch(Vector %d)\n", Vector);
343       /*
344        * Iterate the list until one of the isr tells us its device interrupted
345        */
346       current = isr_table[irq].Flink;
347       isr = CONTAINING_RECORD(current,KINTERRUPT,Entry);
348       //DPRINT("current %x isr %x\n",current,isr);
349       while (current!=(&isr_table[irq]) && 
350              !isr->ServiceRoutine(isr,isr->ServiceContext))
351         {
352            current = current->Flink;
353            isr = CONTAINING_RECORD(current,KINTERRUPT,Entry);
354            //DPRINT("current %x isr %x\n",current,isr);
355         }
356     }
357    /*
358     * Disable interrupts
359     */
360    __asm__("cli\n\t");
361
362    /*
363     * Unmask the related irq
364     */
365    HalEnableSystemInterrupt (Vector, 0, 0);
366
367    /*
368     * If the processor level will drop below dispatch level on return then
369     * issue a DPC queue drain interrupt
370     */
371
372    __asm__("sti\n\t");
373
374    if (old_level < DISPATCH_LEVEL)
375      {
376
377   HalEndSystemInterrupt (DISPATCH_LEVEL, 0);
378
379         if (KeGetCurrentThread() != NULL)
380           {
381              KeGetCurrentThread()->LastEip = Trapframe->Eip;
382           }
383         KiDispatchInterrupt();
384         if (KeGetCurrentThread() != NULL &&
385             KeGetCurrentThread()->Alerted[1] != 0 &&
386             Trapframe->Cs != KERNEL_CS)
387           {
388             HalEndSystemInterrupt (APC_LEVEL, 0);
389             KiDeliverNormalApc();
390           }
391     }
392
393   HalEndSystemInterrupt (old_level, 0);
394 }
395
396 #else /* MP */
397
398 VOID STDCALL
399 KiInterruptDispatch2 (ULONG Irq, KIRQL old_level)
400 /*
401  * FUNCTION: Calls all the interrupt handlers for a given irq.
402  * ARGUMENTS:
403  *        Irq - The number of the irq to call handlers for.
404  *        old_level - The irql of the processor when the irq took place.
405  * NOTES: Must be called at DIRQL.
406  */
407 {
408   PKINTERRUPT isr;
409   PLIST_ENTRY current;
410
411   if (Irq == 0)
412     {
413       KiUpdateSystemTime(old_level, 0);
414     }
415   else
416     {
417       /*
418        * Iterate the list until one of the isr tells us its device interrupted
419        */
420       current = isr_table[Irq].Flink;
421       isr = CONTAINING_RECORD(current,KINTERRUPT,Entry);
422       while (current != &isr_table[Irq] && 
423              !isr->ServiceRoutine(isr, isr->ServiceContext))
424         {
425           current = current->Flink;
426           isr = CONTAINING_RECORD(current,KINTERRUPT,Entry);
427         }
428    }
429 }
430
431 VOID 
432 KiInterruptDispatch (ULONG irq, PKIRQ_TRAPFRAME Trapframe)
433 /*
434  * FUNCTION: Calls the irq specific handler for an irq
435  * ARGUMENTS:
436  *         irq = IRQ that has interrupted
437  */
438 {
439    KIRQL old_level;
440
441    /*
442     * At this point we have interrupts disabled, nothing has been done to
443     * the PIC.
444     */
445
446    /*
447     * Notify the rest of the kernel of the raised irq level. For the
448     * default HAL this will send an EOI to the PIC and alter the IRQL.
449     */
450    if (!HalBeginSystemInterrupt (irq + IRQ_BASE,
451                                  PROFILE_LEVEL - irq,
452                                  &old_level))
453      {
454        return;
455      }
456
457    /*
458     * Enable interrupts
459     * NOTE: Only higher priority interrupts will get through
460     */
461    __asm__("sti\n\t");
462
463    /*
464     * Actually call the ISR.
465     */
466    KiInterruptDispatch2(irq, old_level);
467
468    /*
469     * End the system interrupt.
470     */
471    HalEndSystemInterrupt (old_level, 0);
472
473    /*
474     * Maybe do a reschedule as well.
475     */
476    if (old_level < DISPATCH_LEVEL && irq == 0)
477      {
478        PsDispatchThread(THREAD_STATE_READY);
479      }
480 }
481
482 #endif /* MP */
483
484 static VOID 
485 KeDumpIrqList(VOID)
486 {
487    PKINTERRUPT current;
488    PLIST_ENTRY current_entry;
489    unsigned int i;
490    
491    for (i=0;i<NR_IRQS;i++)
492      {
493         DPRINT("For irq %x ",i);
494         current_entry = isr_table[i].Flink;
495         current = CONTAINING_RECORD(current_entry,KINTERRUPT,Entry);
496         while (current_entry!=(&isr_table[i]))
497           {
498              DPRINT("Isr %x ",current);
499              current_entry = current_entry->Flink;
500              current = CONTAINING_RECORD(current_entry,KINTERRUPT,Entry);
501           }
502         DPRINT("\n",0);
503      }
504 }
505
506 NTSTATUS STDCALL
507 KeConnectInterrupt(PKINTERRUPT InterruptObject)
508 {
509    KIRQL oldlvl;
510    KIRQL synch_oldlvl;
511    PKINTERRUPT ListHead;
512    ULONG Vector;
513
514    DPRINT("KeConnectInterrupt()\n");
515
516    Vector = InterruptObject->Vector;
517
518    /*
519     * Acquire the table spinlock
520     */
521    KeAcquireSpinLock(&isr_table_lock,&oldlvl);
522    
523    /*
524     * Check if the vector is already in use that we can share it
525     */
526    ListHead = CONTAINING_RECORD(isr_table[Vector].Flink,KINTERRUPT,Entry);
527    if (!IsListEmpty(&isr_table[Vector]) &&
528        (InterruptObject->Shareable == FALSE || ListHead->Shareable==FALSE))
529      {
530         KeReleaseSpinLock(&isr_table_lock,oldlvl);
531         return(STATUS_INVALID_PARAMETER);
532      }
533    else
534      {
535         isr_lock[Vector] =
536           ExAllocatePoolWithTag(NonPagedPool, sizeof(KSPIN_LOCK),
537                                 TAG_ISR_LOCK);
538         KeInitializeSpinLock(isr_lock[Vector]);
539      }
540
541    InterruptObject->IrqLock = isr_lock[Vector];
542
543    KeRaiseIrql(InterruptObject->SynchLevel,&synch_oldlvl);
544    KeAcquireSpinLockAtDpcLevel(InterruptObject->IrqLock);
545    DPRINT("%x %x\n",isr_table[Vector].Flink,isr_table[Vector].Blink);
546    InsertTailList(&isr_table[Vector],&InterruptObject->Entry);
547    DPRINT("%x %x\n",InterruptObject->Entry.Flink,
548           InterruptObject->Entry.Blink);
549    KeReleaseSpinLockFromDpcLevel(InterruptObject->IrqLock);
550    KeLowerIrql(synch_oldlvl);
551    
552    /*
553     * Release the table spinlock
554     */
555    KeReleaseSpinLock(&isr_table_lock,oldlvl);
556    
557    KeDumpIrqList();
558
559    return STATUS_SUCCESS;
560 }
561
562
563 VOID STDCALL
564 KeDisconnectInterrupt(PKINTERRUPT InterruptObject)
565 /*
566  * FUNCTION: Releases a drivers isr
567  * ARGUMENTS:
568  *        InterruptObject = isr to release
569  */
570 {
571    KIRQL oldlvl;
572    
573    KeRaiseIrql(InterruptObject->SynchLevel,&oldlvl);
574    KeAcquireSpinLockAtDpcLevel(InterruptObject->IrqLock);
575    RemoveEntryList(&InterruptObject->Entry);
576    KeReleaseSpinLockFromDpcLevel(InterruptObject->IrqLock);
577    KeLowerIrql(oldlvl);
578 }
579
580
581 NTSTATUS
582 STDCALL
583 KeInitializeInterrupt(PKINTERRUPT InterruptObject,
584                       PKSERVICE_ROUTINE ServiceRoutine,
585                       PVOID ServiceContext,
586                       PKSPIN_LOCK SpinLock,
587                       ULONG Vector,
588                       KIRQL Irql,
589                       KIRQL SynchronizeIrql,
590                       KINTERRUPT_MODE InterruptMode,
591                       BOOLEAN ShareVector,
592                       KAFFINITY ProcessorEnableMask,
593                       BOOLEAN FloatingSave)
594 {
595    InterruptObject->ServiceContext = ServiceContext;
596    InterruptObject->ServiceRoutine = ServiceRoutine;
597    InterruptObject->Vector = Vector;
598    InterruptObject->ProcessorEnableMask = ProcessorEnableMask;
599    InterruptObject->SynchLevel = SynchronizeIrql;
600    InterruptObject->Shareable = ShareVector;
601    InterruptObject->FloatingSave = FALSE;
602
603    return STATUS_SUCCESS;
604 }
605
606
607 NTSTATUS STDCALL
608 IoConnectInterrupt(PKINTERRUPT* InterruptObject,
609                    PKSERVICE_ROUTINE ServiceRoutine,
610                    PVOID ServiceContext,
611                    PKSPIN_LOCK SpinLock,
612                    ULONG Vector,
613                    KIRQL Irql,
614                    KIRQL SynchronizeIrql,
615                    KINTERRUPT_MODE InterruptMode,
616                    BOOLEAN ShareVector,
617                    KAFFINITY ProcessorEnableMask,
618                    BOOLEAN FloatingSave)
619 /*
620  * FUNCTION: Registers a driver's isr to be called when its device interrupts
621  * ARGUMENTS:
622  *        InterruptObject (OUT) = Points to the interrupt object created on 
623  *                                return
624  *        ServiceRoutine = Routine to be called when the device interrupts
625  *        ServiceContext = Parameter to be passed to ServiceRoutine
626  *        SpinLock = Initalized spinlock that will be used to synchronize
627  *                   access between the isr and other driver routines. This is
628  *                   required if the isr handles more than one vector or the
629  *                   driver has more than one isr
630  *        Vector = Interrupt vector to allocate 
631  *                 (returned from HalGetInterruptVector)
632  *        Irql = DIRQL returned from HalGetInterruptVector
633  *        SynchronizeIrql = DIRQL at which the isr will execute. This must
634  *                          be the highest of all the DIRQLs returned from
635  *                          HalGetInterruptVector if the driver has multiple
636  *                          isrs
637  *        InterruptMode = Specifies if the interrupt is LevelSensitive or
638  *                        Latched
639  *        ShareVector = Specifies if the vector can be shared
640  *        ProcessorEnableMask = Processors on the isr can run
641  *        FloatingSave = TRUE if the floating point stack should be saved when
642  *                       the isr runs. Must be false for x86 drivers
643  * RETURNS: Status
644  * IRQL: PASSIVE_LEVEL
645  */
646 {
647    PKINTERRUPT Interrupt;
648    NTSTATUS Status = STATUS_SUCCESS;
649    
650    ASSERT_IRQL(PASSIVE_LEVEL);
651    
652    DPRINT("IoConnectInterrupt(Vector %x)\n",Vector);
653    
654    /*
655     * Check the parameters
656     */
657    if (Vector >= NR_IRQS)
658      {
659         return(STATUS_INVALID_PARAMETER);
660      }
661    if (FloatingSave == TRUE)
662      {
663         return(STATUS_INVALID_PARAMETER);
664      }
665    
666    /*
667     * Initialize interrupt object
668     */
669    Interrupt=ExAllocatePoolWithTag(NonPagedPool,sizeof(KINTERRUPT),
670                                    TAG_KINTERRUPT);
671    if (Interrupt==NULL)
672      {
673         return(STATUS_INSUFFICIENT_RESOURCES);
674      }
675
676    Status = KeInitializeInterrupt(Interrupt,
677                                   ServiceRoutine,
678                                   ServiceContext,
679                                   SpinLock,
680                                   Vector,
681                                   Irql,
682                                   SynchronizeIrql,
683                                   InterruptMode,
684                                   ShareVector,
685                                   ProcessorEnableMask,
686                                   FloatingSave);
687    if (!NT_SUCCESS(Status))
688      {
689         ExFreePool(Interrupt);
690         return Status;
691      }
692
693    Status = KeConnectInterrupt(Interrupt);
694    if (!NT_SUCCESS(Status))
695      {
696         ExFreePool(Interrupt);
697         return Status;
698      }
699
700    *InterruptObject = Interrupt;
701
702    return(STATUS_SUCCESS);
703 }
704
705
706 VOID STDCALL
707 IoDisconnectInterrupt(PKINTERRUPT InterruptObject)
708 /*
709  * FUNCTION: Releases a drivers isr
710  * ARGUMENTS:
711  *        InterruptObject = isr to release
712  */
713 {
714   KeDisconnectInterrupt(InterruptObject);
715   ExFreePool(InterruptObject);
716 }
717
718 /* EOF */