reactos/ntoskrnl/ke/powerpc/ppc_irq.c

802 lines
21 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS kernel
* FILE: ntoskrnl/ke/powerpc/ppc_irq.c
* PURPOSE: IRQ handling
*
* PROGRAMMERS: David Welch (welch@mcmail.com)
*/
/*
* NOTE: In general the PIC interrupt priority facilities are used to
* preserve the NT IRQL semantics, global interrupt disables are only used
* to keep the PIC in a consistent state
*
*/
/* INCLUDES ****************************************************************/
#include <ntoskrnl.h>
#include <ppcmmu/mmu.h>
#define NDEBUG
#include <debug.h>
KDPC KiExpireTimerDpc;
extern ULONG KiMaximumDpcQueueDepth;
extern ULONG KiMinimumDpcRate;
extern ULONG KiAdjustDpcThreshold;
extern ULONG KiIdealDpcRate;
extern LONG KiTickOffset;
extern ULONG KeMaximumIncrement;
extern ULONG KeMinimumIncrement;
extern ULONG KeTimeAdjustment;
extern void PearPCDebug(int ch);
/* GLOBALS *****************************************************************/
/* Interrupt handler list */
#define NR_TRAPS 16
#ifdef CONFIG_SMP
#define INT_NAME2(intnum) KiUnexpectedInterrupt##intnum
#define BUILD_INTERRUPT_HANDLER(intnum) \
VOID INT_NAME2(intnum)(VOID);
#define D(x,y) \
BUILD_INTERRUPT_HANDLER(x##y)
#define D16(x) \
D(x,0) D(x,1) D(x,2) D(x,3) \
D(x,4) D(x,5) D(x,6) D(x,7) \
D(x,8) D(x,9) D(x,A) D(x,B) \
D(x,C) D(x,D) D(x,E) D(x,F)
D16(3) D16(4) D16(5) D16(6)
D16(7) D16(8) D16(9) D16(A)
D16(B) D16(C) D16(D) D16(E)
D16(F)
#define L(x,y) \
(ULONG)& INT_NAME2(x##y)
#define L16(x) \
L(x,0), L(x,1), L(x,2), L(x,3), \
L(x,4), L(x,5), L(x,6), L(x,7), \
L(x,8), L(x,9), L(x,A), L(x,B), \
L(x,C), L(x,D), L(x,E), L(x,F)
static ULONG irq_handler[ROUND_UP(NR_TRAPS, 16)] = {
L16(3), L16(4), L16(5), L16(6),
L16(7), L16(8), L16(9), L16(A),
L16(B), L16(C), L16(D), L16(E)
};
#undef L
#undef L16
#undef D
#undef D16
#else /* CONFIG_SMP */
void trap_handler_0(void);
void trap_handler_1(void);
void trap_handler_2(void);
void trap_handler_3(void);
void trap_handler_4(void);
void trap_handler_5(void);
void trap_handler_6(void);
void trap_handler_7(void);
void trap_handler_8(void);
void trap_handler_9(void);
void trap_handler_10(void);
void trap_handler_11(void);
void trap_handler_12(void);
void trap_handler_13(void);
void trap_handler_14(void);
void trap_handler_15(void);
static unsigned int trap_handler[NR_TRAPS] __attribute__((unused)) =
{
(int)&trap_handler_0,
(int)&trap_handler_1,
(int)&trap_handler_2,
(int)&trap_handler_3,
(int)&trap_handler_4,
(int)&trap_handler_5,
(int)&trap_handler_6,
(int)&trap_handler_7,
(int)&trap_handler_8,
(int)&trap_handler_9,
(int)&trap_handler_10,
(int)&trap_handler_11,
(int)&trap_handler_12,
(int)&trap_handler_13,
(int)&trap_handler_14,
(int)&trap_handler_15,
};
#endif /* CONFIG_SMP */
/*
* PURPOSE: Object describing each isr
* NOTE: The data in this table is only modified at passsive level but can
* be accessed at any irq level.
*/
typedef struct
{
LIST_ENTRY ListHead;
KSPIN_LOCK Lock;
ULONG Count;
}
ISR_TABLE, *PISR_TABLE;
#ifdef CONFIG_SMP
static ISR_TABLE IsrTable[NR_TRAPS][MAXIMUM_PROCESSORS];
#else
static ISR_TABLE IsrTable[NR_TRAPS][1];
#endif
#define TAG_ISR_LOCK 'LRSI'
/* FUNCTIONS ****************************************************************/
VOID
INIT_FUNCTION
NTAPI
KeInitInterrupts (VOID)
{
int i, j;
/*
* Setup the IDT entries to point to the interrupt handlers
*/
for (i=0;i<NR_TRAPS;i++)
{
#ifdef CONFIG_SMP
for (j = 0; j < MAXIMUM_PROCESSORS; j++)
#else
j = 0;
#endif
{
InitializeListHead(&IsrTable[i][j].ListHead);
KeInitializeSpinLock(&IsrTable[i][j].Lock);
IsrTable[i][j].Count = 0;
}
}
}
static VOID
KeIRQTrapFrameToTrapFrame(PKIRQ_TRAPFRAME IrqTrapFrame,
PKTRAP_FRAME TrapFrame)
{
}
static VOID
KeTrapFrameToIRQTrapFrame(PKTRAP_FRAME TrapFrame,
PKIRQ_TRAPFRAME IrqTrapFrame)
{
}
/*
* NOTE: On Windows this function takes exactly one parameter and EBP is
* guaranteed to point to KTRAP_FRAME. The function is used only
* by HAL, so there's no point in keeping that prototype.
*
* @implemented
*/
VOID
NTAPI
KeUpdateRunTime(IN PKTRAP_FRAME TrapFrame,
IN KIRQL Irql)
{
PKPRCB Prcb = KeGetCurrentPrcb();
PKTHREAD CurrentThread;
PKPROCESS CurrentProcess;
/* Make sure we don't go further if we're in early boot phase. */
if (!(Prcb) || !(Prcb->CurrentThread)) return;
/* Get the current thread and process */
CurrentThread = Prcb->CurrentThread;
CurrentProcess = CurrentThread->ApcState.Process;
/* Check if we came from user mode */
if (TrapFrame->PreviousMode != KernelMode)
{
/* Update user times */
CurrentThread->UserTime++;
InterlockedIncrement((PLONG)&CurrentProcess->UserTime);
Prcb->UserTime++;
}
else
{
/* Check IRQ */
if (Irql > DISPATCH_LEVEL)
{
/* This was an interrupt */
Prcb->InterruptTime++;
}
else if ((Irql < DISPATCH_LEVEL) || !(Prcb->DpcRoutineActive))
{
/* This was normal kernel time */
CurrentThread->KernelTime++;
InterlockedIncrement((PLONG)&CurrentProcess->KernelTime);
}
else if (Irql == DISPATCH_LEVEL)
{
/* This was DPC time */
Prcb->DpcTime++;
}
/* Update CPU kernel time in all cases */
Prcb->KernelTime++;
}
/* Set the last DPC Count and request rate */
Prcb->DpcLastCount = Prcb->DpcData[0].DpcCount;
Prcb->DpcRequestRate = ((Prcb->DpcData[0].DpcCount - Prcb->DpcLastCount) +
Prcb->DpcRequestRate) / 2;
/* Check if we should request a DPC */
if ((Prcb->DpcData[0].DpcQueueDepth) && !(Prcb->DpcRoutineActive))
{
/* Request one */
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
/* Update the depth if needed */
if ((Prcb->DpcRequestRate < KiIdealDpcRate) &&
(Prcb->MaximumDpcQueueDepth > 1))
{
/* Decrease the maximum depth by one */
Prcb->MaximumDpcQueueDepth--;
}
}
else
{
/* Decrease the adjustment threshold */
if (!(--Prcb->AdjustDpcThreshold))
{
/* We've hit 0, reset it */
Prcb->AdjustDpcThreshold = KiAdjustDpcThreshold;
/* Check if we've hit queue maximum */
if (KiMaximumDpcQueueDepth != Prcb->MaximumDpcQueueDepth)
{
/* Increase maximum by one */
Prcb->MaximumDpcQueueDepth++;
}
}
}
/*
* If we're at end of quantum request software interrupt. The rest
* is handled in KiDispatchInterrupt.
*
* NOTE: If one stays at DISPATCH_LEVEL for a long time the DPC routine
* which checks for quantum end will not be executed and decrementing
* the quantum here can result in overflow. This is not a problem since
* we don't care about the quantum value anymore after the QuantumEnd
* flag is set.
*/
if ((CurrentThread->Quantum -= 3) <= 0)
{
Prcb->QuantumEnd = TRUE;
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
}
}
/*
* NOTE: On Windows this function takes exactly zero parameters and EBP is
* guaranteed to point to KTRAP_FRAME. Also [esp+0] contains an IRQL.
* The function is used only by HAL, so there's no point in keeping
* that prototype.
*
* @implemented
*/
VOID
NTAPI
KeUpdateSystemTime(IN PKTRAP_FRAME TrapFrame,
IN KIRQL Irql,
IN ULONG Increment)
{
LONG OldOffset;
LARGE_INTEGER Time;
ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL);
/* Update interrupt time */
Time.LowPart = SharedUserData->InterruptTime.LowPart;
Time.HighPart = SharedUserData->InterruptTime.High1Time;
Time.QuadPart += Increment;
SharedUserData->InterruptTime.High2Time = Time.u.HighPart;
SharedUserData->InterruptTime.LowPart = Time.u.LowPart;
SharedUserData->InterruptTime.High1Time = Time.u.HighPart;
/* Increase the tick offset */
KiTickOffset -= Increment;
OldOffset = KiTickOffset;
/* Check if this isn't a tick yet */
if (KiTickOffset > 0)
{
/* Expire timers */
KeInsertQueueDpc(&KiExpireTimerDpc, 0, 0);
}
else
{
/* Setup time structure for system time */
Time.LowPart = SharedUserData->SystemTime.LowPart;
Time.HighPart = SharedUserData->SystemTime.High1Time;
Time.QuadPart += KeTimeAdjustment;
SharedUserData->SystemTime.High2Time = Time.HighPart;
SharedUserData->SystemTime.LowPart = Time.LowPart;
SharedUserData->SystemTime.High1Time = Time.HighPart;
/* Setup time structure for tick time */
Time.LowPart = KeTickCount.LowPart;
Time.HighPart = KeTickCount.High1Time;
Time.QuadPart += 1;
KeTickCount.High2Time = Time.HighPart;
KeTickCount.LowPart = Time.LowPart;
KeTickCount.High1Time = Time.HighPart;
SharedUserData->TickCount.High2Time = Time.HighPart;
SharedUserData->TickCount.LowPart = Time.LowPart;
SharedUserData->TickCount.High1Time = Time.HighPart;
/* Queue a DPC that will expire timers */
KeInsertQueueDpc(&KiExpireTimerDpc, 0, 0);
}
/* Update process and thread times */
if (OldOffset <= 0)
{
/* This was a tick, calculate the next one */
KiTickOffset += KeMaximumIncrement;
KeUpdateRunTime(TrapFrame, Irql);
}
}
VOID NTAPI
KiInterruptDispatch2 (ULONG vector, KIRQL old_level)
/*
* FUNCTION: Calls all the interrupt handlers for a given irq.
* ARGUMENTS:
* vector - The number of the vector to call handlers for.
* old_level - The irql of the processor when the irq took place.
* NOTES: Must be called at DIRQL.
*/
{
PKINTERRUPT isr;
PLIST_ENTRY current;
KIRQL oldlvl;
PISR_TABLE CurrentIsr;
DPRINT("I(0x%.08x, 0x%.08x)\n", vector, old_level);
/*
* Iterate the list until one of the isr tells us its device interrupted
*/
CurrentIsr = &IsrTable[vector][(ULONG)KeGetCurrentProcessorNumber()];
KiAcquireSpinLock(&CurrentIsr->Lock);
CurrentIsr->Count++;
current = CurrentIsr->ListHead.Flink;
while (current != &CurrentIsr->ListHead)
{
isr = CONTAINING_RECORD(current,KINTERRUPT,InterruptListEntry);
oldlvl = KeAcquireInterruptSpinLock(isr);
if (isr->ServiceRoutine(isr, isr->ServiceContext))
{
KeReleaseInterruptSpinLock(isr, oldlvl);
break;
}
KeReleaseInterruptSpinLock(isr, oldlvl);
current = current->Flink;
}
KiReleaseSpinLock(&CurrentIsr->Lock);
}
VOID
KiInterruptDispatch3 (ULONG vector, PKIRQ_TRAPFRAME Trapframe)
/*
* FUNCTION: Calls the irq specific handler for an irq
* ARGUMENTS:
* irq = IRQ that has interrupted
*/
{
KIRQL old_level;
KTRAP_FRAME KernelTrapFrame;
PKTHREAD CurrentThread;
PKTRAP_FRAME OldTrapFrame=NULL;
/*
* At this point we have interrupts disabled, nothing has been done to
* the PIC.
*/
KeGetCurrentPrcb()->InterruptCount++;
/*
* Notify the rest of the kernel of the raised irq level. For the
* default HAL this will send an EOI to the PIC and alter the IRQL.
*/
if (!HalBeginSystemInterrupt (vector,
vector,
&old_level))
{
return;
}
/*
* Enable interrupts
* NOTE: Only higher priority interrupts will get through
*/
_enable();
#ifndef CONFIG_SMP
if (vector == 0)
{
KeIRQTrapFrameToTrapFrame(Trapframe, &KernelTrapFrame);
KeUpdateSystemTime(&KernelTrapFrame, old_level, 100000);
}
else
#endif
{
/*
* Actually call the ISR.
*/
KiInterruptDispatch2(vector, old_level);
}
/*
* End the system interrupt.
*/
_disable();
if (old_level==PASSIVE_LEVEL)
{
HalEndSystemInterrupt (APC_LEVEL, 0);
CurrentThread = KeGetCurrentThread();
if (CurrentThread!=NULL && CurrentThread->ApcState.UserApcPending)
{
if (CurrentThread->TrapFrame == NULL)
{
OldTrapFrame = CurrentThread->TrapFrame;
KeIRQTrapFrameToTrapFrame(Trapframe, &KernelTrapFrame);
CurrentThread->TrapFrame = &KernelTrapFrame;
}
_enable();
KiDeliverApc(UserMode, NULL, NULL);
_disable();
ASSERT(KeGetCurrentThread() == CurrentThread);
if (CurrentThread->TrapFrame == &KernelTrapFrame)
{
KeTrapFrameToIRQTrapFrame(&KernelTrapFrame, Trapframe);
CurrentThread->TrapFrame = OldTrapFrame;
}
}
KeLowerIrql(PASSIVE_LEVEL);
}
else
{
HalEndSystemInterrupt (old_level, 0);
}
}
static VOID
KeDumpIrqList(VOID)
{
PKINTERRUPT current;
PLIST_ENTRY current_entry;
LONG i, j;
KIRQL oldlvl;
BOOLEAN printed;
for (i=0;i<NR_TRAPS;i++)
{
printed = FALSE;
KeRaiseIrql(i,&oldlvl);
for (j=0; j < KeNumberProcessors; j++)
{
KiAcquireSpinLock(&IsrTable[i][j].Lock);
current_entry = IsrTable[i][j].ListHead.Flink;
current = CONTAINING_RECORD(current_entry,KINTERRUPT,InterruptListEntry);
while (current_entry!=&(IsrTable[i][j].ListHead))
{
if (printed == FALSE)
{
printed = TRUE;
DPRINT("For irq %x:\n",i);
}
DPRINT(" Isr %x\n",current);
current_entry = current_entry->Flink;
current = CONTAINING_RECORD(current_entry,KINTERRUPT,InterruptListEntry);
}
KiReleaseSpinLock(&IsrTable[i][j].Lock);
}
KeLowerIrql(oldlvl);
}
}
/*
* @implemented
*/
BOOLEAN
NTAPI
KeConnectInterrupt(PKINTERRUPT InterruptObject)
{
KIRQL oldlvl,synch_oldlvl;
PKINTERRUPT ListHead;
ULONG Vector;
PISR_TABLE CurrentIsr;
BOOLEAN Result;
DPRINT("KeConnectInterrupt()\n");
Vector = InterruptObject->Vector;
if (Vector < 0 || Vector >= NR_TRAPS)
return FALSE;
ASSERT (InterruptObject->Number < KeNumberProcessors);
KeSetSystemAffinityThread(1 << InterruptObject->Number);
CurrentIsr = &IsrTable[Vector][(ULONG)InterruptObject->Number];
KeRaiseIrql(Vector,&oldlvl);
KiAcquireSpinLock(&CurrentIsr->Lock);
/*
* Check if the vector is already in use that we can share it
*/
if (!IsListEmpty(&CurrentIsr->ListHead))
{
ListHead = CONTAINING_RECORD(CurrentIsr->ListHead.Flink,KINTERRUPT,InterruptListEntry);
if (InterruptObject->ShareVector == FALSE || ListHead->ShareVector==FALSE)
{
KiReleaseSpinLock(&CurrentIsr->Lock);
KeLowerIrql(oldlvl);
KeRevertToUserAffinityThread();
return FALSE;
}
}
synch_oldlvl = KeAcquireInterruptSpinLock(InterruptObject);
DPRINT("%x %x\n",CurrentIsr->ListHead.Flink, CurrentIsr->ListHead.Blink);
Result = HalEnableSystemInterrupt(Vector, InterruptObject->Irql, InterruptObject->Mode);
if (Result)
{
InsertTailList(&CurrentIsr->ListHead,&InterruptObject->InterruptListEntry);
DPRINT("%x %x\n",InterruptObject->InterruptListEntry.Flink, InterruptObject->InterruptListEntry.Blink);
}
InterruptObject->Connected = TRUE;
KeReleaseInterruptSpinLock(InterruptObject, synch_oldlvl);
/*
* Release the table spinlock
*/
KiReleaseSpinLock(&CurrentIsr->Lock);
KeLowerIrql(oldlvl);
KeDumpIrqList();
KeRevertToUserAffinityThread();
return Result;
}
/*
* @implemented
*
* FUNCTION: Releases a drivers isr
* ARGUMENTS:
* InterruptObject = isr to release
*/
BOOLEAN
NTAPI
KeDisconnectInterrupt(PKINTERRUPT InterruptObject)
{
KIRQL oldlvl,synch_oldlvl;
PISR_TABLE CurrentIsr;
BOOLEAN State;
DPRINT1("KeDisconnectInterrupt\n");
ASSERT (InterruptObject->Number < KeNumberProcessors);
/* Set the affinity */
KeSetSystemAffinityThread(1 << InterruptObject->Number);
/* Get the ISR Tabe */
CurrentIsr = &IsrTable[InterruptObject->Vector]
[(ULONG)InterruptObject->Number];
/* Raise IRQL to required level and lock table */
KeRaiseIrql(InterruptObject->Vector,&oldlvl);
KiAcquireSpinLock(&CurrentIsr->Lock);
/* Check if it's actually connected */
if ((State = InterruptObject->Connected))
{
/* Lock the Interrupt */
synch_oldlvl = KeAcquireInterruptSpinLock(InterruptObject);
/* Remove this one, and check if all are gone */
RemoveEntryList(&InterruptObject->InterruptListEntry);
if (IsListEmpty(&CurrentIsr->ListHead))
{
/* Completely Disable the Interrupt */
HalDisableSystemInterrupt(InterruptObject->Vector, InterruptObject->Irql);
}
/* Disconnect it */
InterruptObject->Connected = FALSE;
/* Release the interrupt lock */
KeReleaseInterruptSpinLock(InterruptObject, synch_oldlvl);
}
/* Release the table spinlock */
KiReleaseSpinLock(&CurrentIsr->Lock);
KeLowerIrql(oldlvl);
/* Go back to default affinity */
KeRevertToUserAffinityThread();
/* Return Old Interrupt State */
return State;
}
/*
* @implemented
*/
VOID
NTAPI
KeInitializeInterrupt(PKINTERRUPT Interrupt,
PKSERVICE_ROUTINE ServiceRoutine,
PVOID ServiceContext,
PKSPIN_LOCK SpinLock,
ULONG Vector,
KIRQL Irql,
KIRQL SynchronizeIrql,
KINTERRUPT_MODE InterruptMode,
BOOLEAN ShareVector,
CHAR ProcessorNumber,
BOOLEAN FloatingSave)
{
/* 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;
/* Disconnect it at first */
Interrupt->Connected = FALSE;
}
VOID KePrintInterruptStatistic(VOID)
{
LONG i, j;
for (j = 0; j < KeNumberProcessors; j++)
{
DPRINT1("CPU%d:\n", j);
for (i = 0; i < NR_TRAPS; i++)
{
if (IsrTable[i][j].Count)
{
DPRINT1(" Irq %x(%d): %d\n", i, i, IsrTable[i][j].Count);
}
}
}
}
BOOLEAN
NTAPI
KeDisableInterrupts(VOID)
{
ULONG Flags = 0;
BOOLEAN Return;
Flags = __readmsr();
Return = (Flags & 0x8000) ? TRUE: FALSE;
/* Disable interrupts */
_disable();
return Return;
}
ULONG
NTAPI
KdpServiceDispatcher(ULONG Service, PCHAR Buffer, ULONG Length);
typedef ULONG (*PSYSCALL_FUN)
(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG,ULONG);
VOID
NTAPI
KiSystemService(ppc_trap_frame_t *trap_frame)
{
int i;
PKSYSTEM_ROUTINE SystemRoutine;
PSYSCALL_FUN SyscallFunction;
switch(trap_frame->gpr[0])
{
case 0x10000: /* DebugService */
for( i = 0; i < trap_frame->gpr[5]; i++ )
{
PearPCDebug(((PCHAR)trap_frame->gpr[4])[i]);
WRITE_PORT_UCHAR((PVOID)0x800003f8, ((PCHAR)trap_frame->gpr[4])[i]);
}
trap_frame->gpr[3] = KdpServiceDispatcher
(trap_frame->gpr[3],
(PCHAR)trap_frame->gpr[4],
trap_frame->gpr[5]);
break;
case 0xf0000: /* Thread startup */
/* XXX how to use UserThread (gpr[6]) */
SystemRoutine = (PKSYSTEM_ROUTINE)trap_frame->gpr[3];
SystemRoutine((PKSTART_ROUTINE)trap_frame->gpr[4],
(PVOID)trap_frame->gpr[5]);
break;
/* Handle a normal system call */
default:
SyscallFunction =
((PSYSCALL_FUN*)KeServiceDescriptorTable
[trap_frame->gpr[0] >> 12].Base)[trap_frame->gpr[0] & 0xfff];
trap_frame->gpr[3] = SyscallFunction
(trap_frame->gpr[3],
trap_frame->gpr[4],
trap_frame->gpr[5],
trap_frame->gpr[6],
trap_frame->gpr[7],
trap_frame->gpr[8],
trap_frame->gpr[9],
trap_frame->gpr[10],
trap_frame->gpr[11],
trap_frame->gpr[12]);
break;
}
}
/* EOF */