638 lines
13 KiB
C
638 lines
13 KiB
C
/*
|
|
|
|
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
|