summaryrefslogtreecommitdiffstats
path: root/private/ntos/tdi/isnp/spx/spxtimer.c
diff options
context:
space:
mode:
Diffstat (limited to 'private/ntos/tdi/isnp/spx/spxtimer.c')
-rw-r--r--private/ntos/tdi/isnp/spx/spxtimer.c637
1 files changed, 637 insertions, 0 deletions
diff --git a/private/ntos/tdi/isnp/spx/spxtimer.c b/private/ntos/tdi/isnp/spx/spxtimer.c
new file mode 100644
index 000000000..bdb4e1d7f
--- /dev/null
+++ b/private/ntos/tdi/isnp/spx/spxtimer.c
@@ -0,0 +1,637 @@
+/*
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ spxtimer.c
+
+Abstract:
+
+ This file implements the timer routines used by the stack.
+
+Author:
+
+ Jameel Hyder (jameelh@microsoft.com)
+ Nikhil Kamkolkar (nikhilk@microsoft.com)
+
+
+Revision History:
+ 23 Feb 1993 Initial Version
+
+Notes: Tab stop: 4
+--*/
+
+#include "precomp.h"
+#pragma hdrstop
+
+// Define module number for event logging entries
+#define FILENUM SPXTIMER
+
+// Discardable code after Init time
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(INIT, SpxTimerInit)
+#endif
+
+// Globals for this module
+PTIMERLIST spxTimerList = NULL;
+PTIMERLIST spxTimerTable[TIMER_HASH_TABLE] = {0};
+PTIMERLIST spxTimerActive = NULL;
+CTELock spxTimerLock = {0};
+LARGE_INTEGER spxTimerTick = {0};
+KTIMER spxTimer = {0};
+KDPC spxTimerDpc = {0};
+ULONG spxTimerId = 1;
+LONG spxTimerCount = 0;
+USHORT spxTimerDispatchCount = 0;
+BOOLEAN spxTimerStopped = FALSE;
+
+
+NTSTATUS
+SpxTimerInit(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Initialize the timer component for the appletalk stack.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+{
+#if !defined(_PNP_POWER)
+ BOOLEAN TimerStarted;
+#endif !_PNP_POWER
+
+ // Initialize the timer and its associated Dpc. timer will be kicked
+ // off when we get the first card arrival notification from ipx
+ KeInitializeTimer(&spxTimer);
+ CTEInitLock(&spxTimerLock);
+ KeInitializeDpc(&spxTimerDpc, spxTimerDpcRoutine, NULL);
+ spxTimerTick = RtlConvertLongToLargeInteger(SPX_TIMER_TICK);
+#if !defined(_PNP_POWER)
+ TimerStarted = KeSetTimer(&spxTimer,
+ spxTimerTick,
+ &spxTimerDpc);
+ CTEAssert(!TimerStarted);
+#endif !_PNP_POWER
+ return STATUS_SUCCESS;
+}
+
+
+
+
+ULONG
+SpxTimerScheduleEvent(
+ IN TIMER_ROUTINE Worker, // Routine to invoke when time expires
+ IN ULONG MsTime, // Schedule after this much time
+ IN PVOID pContext // Context(s) to pass to the routine
+ )
+/*++
+
+Routine Description:
+
+ Insert an event in the timer event list. If the list is empty, then
+ fire off a timer. The time is specified in ms. We convert to ticks.
+ Each tick is currently 100ms. It may not be zero or negative. The internal
+ timer fires at 100ms granularity.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+{
+ PTIMERLIST pList;
+ CTELockHandle lockHandle;
+ ULONG DeltaTime;
+ ULONG Id = 0;
+
+ // Convert to ticks.
+ DeltaTime = MsTime/SPX_MS_TO_TICKS;
+ if (DeltaTime == 0)
+ {
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerScheduleEvent: Converting %ld to ticks %ld\n",
+ MsTime, DeltaTime));
+
+ DeltaTime = 1;
+ }
+
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerScheduleEvent: Converting %ld to ticks %ld\n",
+ MsTime, DeltaTime));
+
+ // Negative or Zero DeltaTime is invalid.
+ CTEAssert (DeltaTime > 0);
+
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerScheduleEvent: Routine %lx, Time %d, Context %lx\n",
+ Worker, DeltaTime, pContext));
+
+ CTEGetLock(&spxTimerLock, &lockHandle);
+
+ if (spxTimerStopped)
+ {
+ DBGPRINT(SYSTEM, FATAL,
+ ("SpxTimerScheduleEvent: Called after Flush !!\n"));
+ }
+
+ else do
+ {
+ pList = SpxBPAllocBlock(BLKID_TIMERLIST);
+
+ if (pList == NULL)
+ {
+ break;
+ }
+
+#if DBG
+ pList->tmr_Signature = TMR_SIGNATURE;
+#endif
+ pList->tmr_Cancelled = FALSE;
+ pList->tmr_Worker = Worker;
+ pList->tmr_AbsTime = DeltaTime;
+ pList->tmr_Context = pContext;
+
+ Id = pList->tmr_Id = spxTimerId++;
+
+ // Take care of wrap around
+ if (spxTimerId == 0)
+ spxTimerId = 1;
+
+ // Enqueue this handler
+ spxTimerEnqueue(pList);
+ } while (FALSE);
+
+ CTEFreeLock(&spxTimerLock, lockHandle);
+
+ return Id;
+}
+
+
+
+VOID
+spxTimerDpcRoutine(
+ IN PKDPC pKDpc,
+ IN PVOID pContext,
+ IN PVOID SystemArgument1,
+ IN PVOID SystemArgument2
+ )
+/*++
+
+Routine Description:
+
+ This is called in at DISPATCH_LEVEL when the timer expires. The entry at
+ the head of the list is decremented and if ZERO unlinked and dispatched.
+ If the list is non-empty, the timer is fired again.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+{
+ PTIMERLIST pList, *ppList;
+ BOOLEAN TimerStarted;
+ ULONG ReEnqueueTime;
+ CTELockHandle lockHandle;
+
+ pKDpc; pContext; SystemArgument1; SystemArgument2;
+
+#if defined(_PNP_POWER)
+ CTEGetLock(&spxTimerLock, &lockHandle);
+ if (spxTimerStopped)
+ {
+ DBGPRINT(SYSTEM, ERR,
+ ("spxTimerDpc: Enetered after Flush !!!\n"));
+
+ CTEFreeLock(&spxTimerLock, lockHandle);
+ return;
+ }
+#else
+ if (spxTimerStopped)
+ {
+ DBGPRINT(SYSTEM, ERR,
+ ("spxTimerDpc: Enetered after Flush !!!\n"));
+ return;
+ }
+
+ CTEGetLock(&spxTimerLock, &lockHandle);
+#endif _PNP_POWER
+
+ SpxTimerCurrentTime ++; // Update our relative time
+
+#ifdef PROFILING
+ // This is the only place where this is changed. And it always increases.
+ SpxStatistics.stat_ElapsedTime = SpxTimerCurrentTime;
+#endif
+
+ // We should never be here if we have no work to do
+ if ((spxTimerList != NULL))
+ {
+ // Careful here. If two guys wanna go off together - let them !!
+ if (spxTimerList->tmr_RelDelta != 0)
+ (spxTimerList->tmr_RelDelta)--;
+
+ // Dispatch the entry if it is ready to go
+ if (spxTimerList->tmr_RelDelta == 0)
+ {
+ pList = spxTimerList;
+ CTEAssert(VALID_TMR(pList));
+
+ // Unlink from the list
+ spxTimerList = pList->tmr_Next;
+ if (spxTimerList != NULL)
+ spxTimerList->tmr_Prev = &spxTimerList;
+
+ // Unlink from the hash table now
+ for (ppList = &spxTimerTable[pList->tmr_Id % TIMER_HASH_TABLE];
+ *ppList != NULL;
+ ppList = &((*ppList)->tmr_Overflow))
+ {
+ CTEAssert(VALID_TMR(*ppList));
+ if (*ppList == pList)
+ {
+ *ppList = pList->tmr_Overflow;
+ break;
+ }
+ }
+
+ CTEAssert (*ppList == pList->tmr_Overflow);
+
+ DBGPRINT(SYSTEM, INFO,
+ ("spxTimerDpcRoutine: Dispatching %lx\n",
+ pList->tmr_Worker));
+
+ spxTimerDispatchCount ++;
+ spxTimerCount --;
+ spxTimerActive = pList;
+ CTEFreeLock(&spxTimerLock, lockHandle);
+
+ // If reenqueue time is 0, do not requeue. If 1, then requeue with
+ // current value, else use value specified.
+ ReEnqueueTime = (*pList->tmr_Worker)(pList->tmr_Context, FALSE);
+ DBGPRINT(SYSTEM, INFO,
+ ("spxTimerDpcRoutine: Reenequeu time %lx.%lx\n",
+ ReEnqueueTime, pList->tmr_AbsTime));
+
+ CTEGetLock(&spxTimerLock, &lockHandle);
+
+ spxTimerActive = NULL;
+ spxTimerDispatchCount --;
+
+ if (ReEnqueueTime != TIMER_DONT_REQUEUE)
+ {
+ // If this chappie was cancelled while it was running
+ // and it wants to be re-queued, do it right away.
+ if (pList->tmr_Cancelled)
+ {
+ (*pList->tmr_Worker)(pList->tmr_Context, FALSE);
+ SpxBPFreeBlock(pList, BLKID_TIMERLIST);
+ }
+ else
+ {
+ if (ReEnqueueTime != TIMER_REQUEUE_CUR_VALUE)
+ {
+ pList->tmr_AbsTime = ReEnqueueTime/SPX_MS_TO_TICKS;
+ if (pList->tmr_AbsTime == 0)
+ {
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerDispatch: Requeue at %ld\n",
+ pList->tmr_AbsTime));
+ }
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerDispatch: Requeue at %ld.%ld\n",
+ ReEnqueueTime, pList->tmr_AbsTime));
+ }
+
+ spxTimerEnqueue(pList);
+ }
+ }
+ else
+ {
+ SpxBPFreeBlock(pList, BLKID_TIMERLIST);
+ }
+ }
+ }
+
+#if defined(_PNP_POWER)
+ if (!spxTimerStopped)
+ {
+ TimerStarted = KeSetTimer(&spxTimer,
+ spxTimerTick,
+ &spxTimerDpc);
+
+ // it is possible that while we were here in Dpc, PNP_ADD_DEVICE
+ // restarted the timer, so this assert is commented out for PnP
+// CTEAssert(!TimerStarted);
+ }
+
+ CTEFreeLock(&spxTimerLock, lockHandle);
+#else
+ CTEFreeLock(&spxTimerLock, lockHandle);
+
+ if (!spxTimerStopped)
+ {
+ TimerStarted = KeSetTimer(&spxTimer,
+ spxTimerTick,
+ &spxTimerDpc);
+ CTEAssert(!TimerStarted);
+ }
+#endif _PNP_POWER
+}
+
+
+VOID
+spxTimerEnqueue(
+ IN PTIMERLIST pListNew
+ )
+/*++
+
+Routine Description:
+
+ Here is a thesis on the code that follows.
+
+ The timer events are maintained as a list which the timer dpc routine
+ looks at every timer tick. The list is maintained in such a way that only
+ the head of the list needs to be updated every tick i.e. the entire list
+ is never scanned. The way this is achieved is by keeping delta times
+ relative to the previous entry.
+
+ Every timer tick, the relative time at the head of the list is decremented.
+ When that goes to ZERO, the head of the list is unlinked and dispatched.
+
+ To give an example, we have the following events queued at time slots
+ X Schedule A after 10 ticks.
+ X+3 Schedule B after 5 ticks.
+ X+5 Schedule C after 4 ticks.
+ X+8 Schedule D after 6 ticks.
+
+ So A will schedule at X+10, B at X+8 (X+3+5), C at X+9 (X+5+4) and
+ D at X+14 (X+8+6).
+
+ The above example covers all the situations.
+
+ - NULL List.
+ - Inserting at head of list.
+ - Inserting in the middle of the list.
+ - Appending to the list tail.
+
+ The list will look as follows.
+
+ BEFORE AFTER
+ ------ -----
+
+ X Head -->| Head -> A(10) ->|
+ A(10)
+
+ X+3 Head -> A(7) ->| Head -> B(5) -> A(2) ->|
+ B(5)
+
+ X+5 Head -> B(3) -> A(2) ->| Head -> B(3) -> C(1) -> A(1) ->|
+ C(4)
+
+ X+8 Head -> C(1) -> A(1) ->| Head -> C(1) -> A(1) -> D(4) ->|
+ D(6)
+
+ The granularity is one tick. THIS MUST BE CALLED WITH THE TIMER LOCK HELD.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+{
+ PTIMERLIST pList, *ppList;
+ ULONG DeltaTime = pListNew->tmr_AbsTime;
+
+ // The DeltaTime is adjusted in every pass of the loop to reflect the
+ // time after the previous entry that the new entry will schedule.
+ for (ppList = &spxTimerList;
+ (pList = *ppList) != NULL;
+ ppList = &pList->tmr_Next)
+ {
+ CTEAssert(VALID_TMR(pList));
+ if (DeltaTime <= pList->tmr_RelDelta)
+ {
+ pList->tmr_RelDelta -= DeltaTime;
+ break;
+ }
+ DeltaTime -= pList->tmr_RelDelta;
+ }
+
+
+ // Link this in the chain
+ pListNew->tmr_RelDelta = DeltaTime;
+ pListNew->tmr_Next = pList;
+ pListNew->tmr_Prev = ppList;
+ *ppList = pListNew;
+ if (pList != NULL)
+ {
+ pList->tmr_Prev = &pListNew->tmr_Next;
+ }
+
+ // Now link it in the hash table
+ pListNew->tmr_Overflow = spxTimerTable[pListNew->tmr_Id % TIMER_HASH_TABLE];
+ spxTimerTable[pListNew->tmr_Id % TIMER_HASH_TABLE] = pListNew;
+ spxTimerCount ++;
+}
+
+
+
+
+VOID
+SpxTimerFlushAndStop(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Force all entries in the timer queue to be dispatched immediately. No
+ more queue'ing of timer routines is permitted after this. The timer
+ essentially shuts down.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+{
+ PTIMERLIST pList;
+ CTELockHandle lockHandle;
+
+ CTEAssert (KeGetCurrentIrql() == LOW_LEVEL);
+
+ DBGPRINT(SYSTEM, ERR,
+ ("SpxTimerFlushAndStop: Entered\n"));
+
+ CTEGetLock(&spxTimerLock, &lockHandle);
+
+ spxTimerStopped = TRUE;
+
+ KeCancelTimer(&spxTimer);
+
+ if (spxTimerList != NULL)
+ {
+ // Dispatch all entries right away
+ while (spxTimerList != NULL)
+ {
+ pList = spxTimerList;
+ CTEAssert(VALID_TMR(pList));
+ spxTimerList = pList->tmr_Next;
+
+ DBGPRINT(SYSTEM, INFO,
+ ("spxTimerFlushAndStop: Dispatching %lx\n",
+ pList->tmr_Worker));
+
+ // The timer routines assume they are being called at DISPATCH
+ // level. This is OK since we are calling with SpinLock held.
+
+ (*pList->tmr_Worker)(pList->tmr_Context, TRUE);
+
+ spxTimerCount --;
+ SpxBPFreeBlock(pList, BLKID_TIMERLIST);
+ }
+ RtlZeroMemory(spxTimerTable, sizeof(spxTimerTable));
+ }
+
+ CTEFreeLock(&spxTimerLock, lockHandle);
+
+ // Wait for all timer routines to complete
+ while (spxTimerDispatchCount != 0)
+ {
+ SpxSleep(SPX_TIMER_WAIT);
+ }
+}
+
+
+
+
+BOOLEAN
+SpxTimerCancelEvent(
+ IN ULONG TimerId,
+ IN BOOLEAN ReEnqueue
+ )
+/*++
+
+Routine Description:
+
+ Cancel a previously scheduled timer event, if it hasn't fired already.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+{
+ PTIMERLIST pList, *ppList;
+ CTELockHandle lockHandle;
+
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerCancelEvent: Entered for TimerId %ld\n", TimerId));
+
+ CTEAssert(TimerId != 0);
+
+ CTEGetLock(&spxTimerLock, &lockHandle);
+
+ for (ppList = &spxTimerTable[TimerId % TIMER_HASH_TABLE];
+ (pList = *ppList) != NULL;
+ ppList = &pList->tmr_Overflow)
+ {
+ CTEAssert(VALID_TMR(pList));
+ // If we find it, cancel it
+ if (pList->tmr_Id == TimerId)
+ {
+ // Unlink this from the hash table
+ *ppList = pList->tmr_Overflow;
+
+ // ... and from the list
+ if (pList->tmr_Next != NULL)
+ {
+ pList->tmr_Next->tmr_RelDelta += pList->tmr_RelDelta;
+ pList->tmr_Next->tmr_Prev = pList->tmr_Prev;
+ }
+ *(pList->tmr_Prev) = pList->tmr_Next;
+
+ spxTimerCount --;
+ if (ReEnqueue)
+ spxTimerEnqueue(pList);
+ else SpxBPFreeBlock(pList, BLKID_TIMERLIST);
+ break;
+ }
+ }
+
+ // If we could not find it in the list, see if it currently running.
+ // If so mark him to not reschedule itself, only if reenqueue was false.
+ if (pList == NULL)
+ {
+ if ((spxTimerActive != NULL) &&
+ (spxTimerActive->tmr_Id == TimerId) &&
+ !ReEnqueue)
+ {
+ spxTimerActive->tmr_Cancelled = TRUE;
+ }
+ }
+
+ CTEFreeLock(&spxTimerLock, lockHandle);
+
+ DBGPRINT(SYSTEM, INFO,
+ ("SpxTimerCancelEvent: %s for Id %ld\n",
+ (pList != NULL) ? "Success" : "Failure", TimerId));
+
+ return (pList != NULL);
+}
+
+
+
+
+#if DBG
+
+VOID
+SpxTimerDumpList(
+ VOID
+ )
+{
+ PTIMERLIST pList;
+ ULONG CumTime = 0;
+ CTELockHandle lockHandle;
+
+ DBGPRINT(DUMP, FATAL,
+ ("TIMER LIST: (Times are in %dms units\n", 1000));
+ DBGPRINT(DUMP, FATAL,
+ ("\tTimerId Time(Abs) Time(Rel) Routine Address\n"));
+
+ CTEGetLock(&spxTimerLock, &lockHandle);
+
+ for (pList = spxTimerList;
+ pList != NULL;
+ pList = pList->tmr_Next)
+ {
+ CumTime += pList->tmr_RelDelta;
+ DBGPRINT(DUMP, FATAL,
+ ("\t% 6lx %5d %5ld %lx\n",
+ pList->tmr_Id, pList->tmr_AbsTime, CumTime, pList->tmr_Worker));
+ }
+
+ CTEFreeLock(&spxTimerLock, lockHandle);
+}
+
+#endif