/* * PROJECT: ReactOS Kernel * LICENSE: GPL - See COPYING in the top level directory * FILE: ntoskrnl/ke/i386/irqobj.c * PURPOSE: Manages the Kernel's IRQ support for external drivers, * for the purposes of connecting, disconnecting and setting * up ISRs for drivers. The backend behind the Io* Interrupt * routines. * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) */ /* INCLUDES *****************************************************************/ #include #define NDEBUG #include /* GLOBALS *******************************************************************/ ULONG KiISRTimeout = 55; USHORT KiISROverflow = 30000; extern ULONG NTAPI KiChainedDispatch2ndLvl(VOID); /* PRIVATE FUNCTIONS *********************************************************/ VOID NTAPI KiGetVectorDispatch(IN ULONG Vector, IN PDISPATCH_INFO Dispatch) { PKINTERRUPT_ROUTINE Handler; PVOID Current; UCHAR Type; UCHAR Entry; /* Check if this is a primary or 2nd-level dispatch */ Type = HalSystemVectorDispatchEntry(Vector, &Dispatch->FlatDispatch, &Dispatch->NoDispatch); ASSERT(Type == 0); /* Get the IDT entry for this vector */ Entry = HalVectorToIDTEntry(Vector); /* Setup the unhandled dispatch */ Dispatch->NoDispatch = (PVOID)(((ULONG_PTR)&KiStartUnexpectedRange) + (Entry - PRIMARY_VECTOR_BASE) * KiUnexpectedEntrySize); /* Setup the handlers */ Dispatch->InterruptDispatch = (PVOID)KiInterruptDispatch; Dispatch->FloatingDispatch = NULL; // Floating Interrupts are not supported Dispatch->ChainedDispatch = (PVOID)KiChainedDispatch; Dispatch->FlatDispatch = NULL; /* Get the current handler */ Current = KeQueryInterruptHandler(Vector); /* Set the interrupt */ Dispatch->Interrupt = CONTAINING_RECORD(Current, KINTERRUPT, DispatchCode); /* Check what this interrupt is connected to */ if ((PKINTERRUPT_ROUTINE)Current == Dispatch->NoDispatch) { /* Not connected */ Dispatch->Type = NoConnect; } else { /* Get the handler */ Handler = Dispatch->Interrupt->DispatchAddress; if (Handler == Dispatch->ChainedDispatch) { /* It's a chained interrupt */ Dispatch->Type = ChainConnect; } else if ((Handler == Dispatch->InterruptDispatch) || (Handler == Dispatch->FloatingDispatch)) { /* It's unchained */ Dispatch->Type = NormalConnect; } else { /* Unknown */ Dispatch->Type = UnknownConnect; } } } VOID NTAPI KiConnectVectorToInterrupt(IN PKINTERRUPT Interrupt, IN CONNECT_TYPE Type) { DISPATCH_INFO Dispatch; PKINTERRUPT_ROUTINE Handler; /* Get vector data */ KiGetVectorDispatch(Interrupt->Vector, &Dispatch); /* Check if we're only disconnecting */ if (Type == NoConnect) { /* Set the handler to NoDispatch */ Handler = Dispatch.NoDispatch; } else { /* Get the right handler */ Handler = (Type == NormalConnect) ? Dispatch.InterruptDispatch: Dispatch.ChainedDispatch; ASSERT(Interrupt->FloatingSave == FALSE); /* Set the handler */ Interrupt->DispatchAddress = Handler; /* Read note in trap.s -- patching not needed since JMP is static */ /* Now set the final handler address */ ASSERT(Dispatch.FlatDispatch == NULL); Handler = (PVOID)&Interrupt->DispatchCode; } /* Register the interrupt */ KeRegisterInterruptHandler(Interrupt->Vector, Handler); } FORCEINLINE DECLSPEC_NORETURN VOID KiExitInterrupt(IN PKTRAP_FRAME TrapFrame, IN KIRQL OldIrql, IN BOOLEAN Spurious) { /* Check if this was a real interrupt */ if (!Spurious) { /* It was, disable interrupts and restore the IRQL */ _disable(); HalEndSystemInterrupt(OldIrql, TrapFrame); } /* Now exit the trap */ KiEoiHelper(TrapFrame); } DECLSPEC_NORETURN VOID __cdecl KiUnexpectedInterrupt(VOID) { /* Crash the machine */ KeBugCheck(TRAP_CAUSE_UNKNOWN); } VOID FASTCALL KiUnexpectedInterruptTailHandler(IN PKTRAP_FRAME TrapFrame) { KIRQL OldIrql; /* Enter trap */ KiEnterInterruptTrap(TrapFrame); /* Increase interrupt count */ KeGetCurrentPrcb()->InterruptCount++; /* Start the interrupt */ if (HalBeginSystemInterrupt(HIGH_LEVEL, TrapFrame->ErrCode, &OldIrql)) { /* Warn user */ DPRINT1("\n\x7\x7!!! Unexpected Interrupt 0x%02lx !!!\n", TrapFrame->ErrCode); /* Now call the epilogue code */ KiExitInterrupt(TrapFrame, OldIrql, FALSE); } else { /* Now call the epilogue code */ KiExitInterrupt(TrapFrame, OldIrql, TRUE); } } typedef VOID (FASTCALL *PKI_INTERRUPT_DISPATCH)( IN PKTRAP_FRAME TrapFrame, IN PKINTERRUPT Interrupt ); VOID FASTCALL KiInterruptDispatch(IN PKTRAP_FRAME TrapFrame, IN PKINTERRUPT Interrupt) { KIRQL OldIrql; /* Increase interrupt count */ KeGetCurrentPrcb()->InterruptCount++; /* Begin the interrupt, making sure it's not spurious */ if (HalBeginSystemInterrupt(Interrupt->SynchronizeIrql, Interrupt->Vector, &OldIrql)) { /* Acquire interrupt lock */ KxAcquireSpinLock(Interrupt->ActualLock); /* Call the ISR */ Interrupt->ServiceRoutine(Interrupt, Interrupt->ServiceContext); /* Release interrupt lock */ KxReleaseSpinLock(Interrupt->ActualLock); /* Now call the epilogue code */ KiExitInterrupt(TrapFrame, OldIrql, FALSE); } else { /* Now call the epilogue code */ KiExitInterrupt(TrapFrame, OldIrql, TRUE); } } VOID FASTCALL KiChainedDispatch(IN PKTRAP_FRAME TrapFrame, IN PKINTERRUPT Interrupt) { KIRQL OldIrql, OldInterruptIrql = 0; BOOLEAN Handled; PLIST_ENTRY NextEntry, ListHead; /* Increase interrupt count */ KeGetCurrentPrcb()->InterruptCount++; /* Begin the interrupt, making sure it's not spurious */ if (HalBeginSystemInterrupt(Interrupt->Irql, Interrupt->Vector, &OldIrql)) { /* Get list pointers */ ListHead = &Interrupt->InterruptListEntry; NextEntry = ListHead; /* The head is an entry! */ while (TRUE) { /* Check if this interrupt's IRQL is higher than the current one */ if (Interrupt->SynchronizeIrql > Interrupt->Irql) { /* Raise to higher IRQL */ OldInterruptIrql = KfRaiseIrql(Interrupt->SynchronizeIrql); } /* Acquire interrupt lock */ KxAcquireSpinLock(Interrupt->ActualLock); /* Call the ISR */ Handled = Interrupt->ServiceRoutine(Interrupt, Interrupt->ServiceContext); /* Release interrupt lock */ KxReleaseSpinLock(Interrupt->ActualLock); /* Check if this interrupt's IRQL is higher than the current one */ if (Interrupt->SynchronizeIrql > Interrupt->Irql) { /* Lower the IRQL back */ ASSERT(OldInterruptIrql == Interrupt->Irql); KfLowerIrql(OldInterruptIrql); } /* Check if the interrupt got handled and it's level */ if ((Handled) && (Interrupt->Mode == LevelSensitive)) break; /* What's next? */ NextEntry = NextEntry->Flink; /* Is this the last one? */ if (NextEntry == ListHead) { /* Level should not have gotten here */ if (Interrupt->Mode == LevelSensitive) break; /* As for edge, we can only exit once nobody can handle the interrupt */ if (!Handled) break; } /* Get the interrupt object for the next pass */ Interrupt = CONTAINING_RECORD(NextEntry, KINTERRUPT, InterruptListEntry); } /* Now call the epilogue code */ KiExitInterrupt(TrapFrame, OldIrql, FALSE); } else { /* Now call the epilogue code */ KiExitInterrupt(TrapFrame, OldIrql, TRUE); } } VOID FASTCALL KiInterruptTemplateHandler(IN PKTRAP_FRAME TrapFrame, IN PKINTERRUPT Interrupt) { /* Enter interrupt frame */ KiEnterInterruptTrap(TrapFrame); /* Call the correct dispatcher */ ((PKI_INTERRUPT_DISPATCH)Interrupt->DispatchAddress)(TrapFrame, Interrupt); } /* PUBLIC FUNCTIONS **********************************************************/ /* * @implemented */ VOID NTAPI KeInitializeInterrupt(IN PKINTERRUPT Interrupt, IN PKSERVICE_ROUTINE ServiceRoutine, IN PVOID ServiceContext, IN PKSPIN_LOCK SpinLock, IN ULONG Vector, IN KIRQL Irql, IN KIRQL SynchronizeIrql, IN KINTERRUPT_MODE InterruptMode, IN BOOLEAN ShareVector, IN CHAR ProcessorNumber, IN BOOLEAN FloatingSave) { ULONG i; PULONG DispatchCode = &Interrupt->DispatchCode[0], Patch = DispatchCode; /* Set the Interrupt Header */ Interrupt->Type = InterruptObject; Interrupt->Size = sizeof(KINTERRUPT); /* Check if we got a spinlock */ if (SpinLock) { Interrupt->ActualLock = SpinLock; } else { /* Use the built-in one */ KeInitializeSpinLock(&Interrupt->SpinLock); Interrupt->ActualLock = &Interrupt->SpinLock; } /* Set the other settings */ Interrupt->ServiceRoutine = ServiceRoutine; Interrupt->ServiceContext = ServiceContext; Interrupt->Vector = Vector; Interrupt->Irql = Irql; Interrupt->SynchronizeIrql = SynchronizeIrql; Interrupt->Mode = InterruptMode; Interrupt->ShareVector = ShareVector; Interrupt->Number = ProcessorNumber; Interrupt->FloatingSave = FloatingSave; Interrupt->TickCount = MAXULONG; Interrupt->DispatchCount = MAXULONG; /* Loop the template in memory */ for (i = 0; i < DISPATCH_LENGTH; i++) { /* Copy the dispatch code */ *DispatchCode++ = ((PULONG)KiInterruptTemplate)[i]; } /* Jump to the last 4 bytes */ Patch = (PULONG)((ULONG_PTR)Patch + ((ULONG_PTR)&KiInterruptTemplateObject - (ULONG_PTR)KiInterruptTemplate) - 4); /* Apply the patch */ *Patch = PtrToUlong(Interrupt); /* Disconnect it at first */ Interrupt->Connected = FALSE; } /* * @implemented */ BOOLEAN NTAPI KeConnectInterrupt(IN PKINTERRUPT Interrupt) { BOOLEAN Connected, Error, Status; KIRQL Irql, OldIrql; UCHAR Number; ULONG Vector; DISPATCH_INFO Dispatch; /* Get data from interrupt */ Number = Interrupt->Number; Vector = Interrupt->Vector; Irql = Interrupt->Irql; /* Validate the settings */ if ((Irql > HIGH_LEVEL) || (Number >= KeNumberProcessors) || (Interrupt->SynchronizeIrql < Irql) || (Interrupt->FloatingSave)) { return FALSE; } /* Set defaults */ Connected = FALSE; Error = FALSE; /* Set the system affinity and acquire the dispatcher lock */ KeSetSystemAffinityThread(1 << Number); OldIrql = KiAcquireDispatcherLock(); /* Check if it's already been connected */ if (!Interrupt->Connected) { /* Get vector dispatching information */ KiGetVectorDispatch(Vector, &Dispatch); /* Check if the vector is already connected */ if (Dispatch.Type == NoConnect) { /* Do the connection */ Interrupt->Connected = Connected = TRUE; /* Initialize the list */ InitializeListHead(&Interrupt->InterruptListEntry); /* Connect and enable the interrupt */ KiConnectVectorToInterrupt(Interrupt, NormalConnect); Status = HalEnableSystemInterrupt(Vector, Irql, Interrupt->Mode); if (!Status) Error = TRUE; } else if ((Dispatch.Type != UnknownConnect) && (Interrupt->ShareVector) && (Dispatch.Interrupt->ShareVector) && (Dispatch.Interrupt->Mode == Interrupt->Mode)) { /* The vector is shared and the interrupts are compatible */ Interrupt->Connected = Connected = TRUE; /* * Verify the IRQL for chained connect, */ #if defined(CONFIG_SMP) ASSERT(Irql <= SYNCH_LEVEL); #else ASSERT(Irql <= (IPI_LEVEL - 2)); #endif /* Check if this is the first chain */ if (Dispatch.Type != ChainConnect) { /* This is not supported */ ASSERT(Dispatch.Interrupt->Mode != Latched); /* Setup the chainned handler */ KiConnectVectorToInterrupt(Dispatch.Interrupt, ChainConnect); } /* Insert into the interrupt list */ InsertTailList(&Dispatch.Interrupt->InterruptListEntry, &Interrupt->InterruptListEntry); } } /* Unlock the dispatcher and revert affinity */ KiReleaseDispatcherLock(OldIrql); KeRevertToUserAffinityThread(); /* Check if we failed while trying to connect */ if ((Connected) && (Error)) { DPRINT1("HalEnableSystemInterrupt failed\n"); KeDisconnectInterrupt(Interrupt); Connected = FALSE; } /* Return to caller */ return Connected; } /* * @implemented */ BOOLEAN NTAPI KeDisconnectInterrupt(IN PKINTERRUPT Interrupt) { KIRQL OldIrql, Irql; ULONG Vector; DISPATCH_INFO Dispatch; PKINTERRUPT NextInterrupt; BOOLEAN State; /* Set the affinity */ KeSetSystemAffinityThread(1 << Interrupt->Number); /* Lock the dispatcher */ OldIrql = KiAcquireDispatcherLock(); /* Check if it's actually connected */ State = Interrupt->Connected; if (State) { /* Get the vector and IRQL */ Irql = Interrupt->Irql; Vector = Interrupt->Vector; /* Get vector dispatch data */ KiGetVectorDispatch(Vector, &Dispatch); /* Check if it was chained */ if (Dispatch.Type == ChainConnect) { /* Check if the top-level interrupt is being removed */ #if defined(CONFIG_SMP) ASSERT(Irql <= SYNCH_LEVEL); #else ASSERT(Irql <= (IPI_LEVEL - 2)); #endif if (Interrupt == Dispatch.Interrupt) { /* Get the next one */ Dispatch.Interrupt = CONTAINING_RECORD(Dispatch.Interrupt-> InterruptListEntry.Flink, KINTERRUPT, InterruptListEntry); /* Reconnect it */ KiConnectVectorToInterrupt(Dispatch.Interrupt, ChainConnect); } /* Remove it */ RemoveEntryList(&Interrupt->InterruptListEntry); /* Get the next one */ NextInterrupt = CONTAINING_RECORD(Dispatch.Interrupt-> InterruptListEntry.Flink, KINTERRUPT, InterruptListEntry); /* Check if this is the only one left */ if (Dispatch.Interrupt == NextInterrupt) { /* Connect it in non-chained mode */ KiConnectVectorToInterrupt(Dispatch.Interrupt, NormalConnect); } } else { /* Only one left, disable and remove it */ HalDisableSystemInterrupt(Interrupt->Vector, Irql); KiConnectVectorToInterrupt(Interrupt, NoConnect); } /* Disconnect it */ Interrupt->Connected = FALSE; } /* Unlock the dispatcher and revert affinity */ KiReleaseDispatcherLock(OldIrql); KeRevertToUserAffinityThread(); /* Return to caller */ return State; } /* * @implemented */ BOOLEAN NTAPI KeSynchronizeExecution(IN OUT PKINTERRUPT Interrupt, IN PKSYNCHRONIZE_ROUTINE SynchronizeRoutine, IN PVOID SynchronizeContext OPTIONAL) { BOOLEAN Success; KIRQL OldIrql; /* Raise IRQL */ KeRaiseIrql(Interrupt->SynchronizeIrql, &OldIrql); /* Acquire interrupt spinlock */ KeAcquireSpinLockAtDpcLevel(Interrupt->ActualLock); /* Call the routine */ Success = SynchronizeRoutine(SynchronizeContext); /* Release lock */ KeReleaseSpinLockFromDpcLevel(Interrupt->ActualLock); /* Lower IRQL */ KeLowerIrql(OldIrql); /* Return status */ return Success; } /* EOF */