reactos/ntoskrnl/ke/i386/irqobj.c

612 lines
17 KiB
C
Raw Normal View History

/*
* 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 <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* 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();
[HAL]: Rewrite IRQL handling. Alex's original code (lately translated to C) was a copy of the MicroChannel (MCA), Checked-Build HAL, an unexplained choice considering MCA is not supported or even available anymore. Windows, on machines with a PIC, uses a mechanism called Lazy IRQL, in which the PIC is only programmed "lazily", meaning that lowering and raising the IRQL does not actually change the interrupt mask. Therefore, lower priority interrupts will still come in at high IRQL. At this point, the HAL will detect this, only now mask the PICs, and lie that the lower interrupt was "spurious", while setting a pending bit. When the IRQL is lowered, the bit is detected, and a software/delayed "INT" is done with the correct IRQ vector number. More details are available in the typical resources. [HAL]: Implement support for Level interrupts, which are used by certain EISA cards, and more particularly, all PCI hardware. Level interrupts were not previously handled correctly, being treated as edge/latched interrupts instead. [NTOS/HAL]: Remove VDM Alert KPCR hack (which was buggy). Now the PKTRAP_FRAME is passed as a parameter to HalpEndSoftwareInterrupt/HalEndSystemInterrupt. This also removes the HalpNestedTrap ASM hack, since the call can now be done in C. [PERF]: On real machines, writing the PIC mask is a relatively expensive I/O operation, and IRQL lower/raise can happen hundreds of times a second. Lazy IRQL provides an important optimization. [PERF]: Correctly handling level interrupts as level interrupts allows for faster, and more efficient, IRQ handling. svn path=/trunk/; revision=45320
2010-01-29 21:10:33 +00:00
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
{
/* This means we'll be usin 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 */