|
|
/*
Copyright (c) 1992 Microsoft Corporation
Module Name:
atktimer.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 <atalk.h>
#pragma hdrstop
#define FILENUM ATKTIMER
// Discardable code after Init time
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, AtalkTimerInit)
#pragma alloc_text(PAGEINIT, AtalkTimerFlushAndStop)
#endif
/*** AtalkTimerInit
* * Initialize the timer component for the appletalk stack. */ NTSTATUS AtalkTimerInit( VOID ) { BOOLEAN TimerStarted;
// Initialize the timer and its associated Dpc and kick it off
KeInitializeEvent(&atalkTimerStopEvent, NotificationEvent, FALSE); KeInitializeTimer(&atalkTimer); INITIALIZE_SPIN_LOCK(&atalkTimerLock); KeInitializeDpc(&atalkTimerDpc, atalkTimerDpcRoutine, NULL); atalkTimerTick.QuadPart = ATALK_TIMER_TICK; TimerStarted = KeSetTimer(&atalkTimer, atalkTimerTick, &atalkTimerDpc); ASSERT(!TimerStarted);
return STATUS_SUCCESS; }
/*** AtalkTimerScheduleEvent
* * Insert an event in the timer event list. If the list is empty, then * fire off a timer. The time is specified in ticks. Each tick is currently * 100ms. It may not be zero or negative. The internal timer also fires at * 100ms granularity. */ VOID FASTCALL AtalkTimerScheduleEvent( IN PTIMERLIST pList // TimerList to use for queuing
) { KIRQL OldIrql;
DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO, ("AtalkTimerScheduleEvent: pList %lx\n", pList));
ASSERT(VALID_TMR(pList)); ASSERT (pList->tmr_Routine != NULL); ASSERT (pList->tmr_AbsTime != 0);
if (!atalkTimerStopped) { ACQUIRE_SPIN_LOCK(&atalkTimerLock, &OldIrql); // Enqueue this handler
atalkTimerEnqueue(pList);
RELEASE_SPIN_LOCK(&atalkTimerLock, OldIrql); } else { DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_FATAL, ("AtalkTimerScheduleEvent: Called after Flush !!\n")); } }
/*** atalkTimerDpcRoutine
* * 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. */ LOCAL VOID atalkTimerDpcRoutine( IN PKDPC pKDpc, IN PVOID pContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) { PTIMERLIST pList; BOOLEAN TimerStarted; LONG ReQueue;
pKDpc; pContext; SystemArgument1; SystemArgument2;
if (atalkTimerStopped) { DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_ERR, ("atalkTimerDpc: Enetered after Flush !!!\n")); return; }
ACQUIRE_SPIN_LOCK_DPC(&atalkTimerLock);
AtalkTimerCurrentTick ++; // Update our relative time
// We should never be here if we have no work to do
if ((atalkTimerList != NULL)) { // Careful here. If two guys wanna go off together - let them !!
if (atalkTimerList->tmr_RelDelta != 0) (atalkTimerList->tmr_RelDelta)--; // Dispatch the entry if it is ready to go
pList = atalkTimerList; if (pList->tmr_RelDelta == 0) { ASSERT(VALID_TMR(pList));
// Unlink from the list
// AtalkUnlinkDouble(pList, tmr_Next, tmr_Prev);
atalkTimerList = pList->tmr_Next; if (atalkTimerList != NULL) atalkTimerList->tmr_Prev = &atalkTimerList;
pList->tmr_Queued = FALSE; pList->tmr_Running = TRUE; atalkTimerRunning = TRUE;
DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO, ("atalkTimerDpcRoutine: Dispatching %lx\n", pList->tmr_Routine));
RELEASE_SPIN_LOCK_DPC(&atalkTimerLock);
ReQueue = (*pList->tmr_Routine)(pList, FALSE);
ACQUIRE_SPIN_LOCK_DPC(&atalkTimerLock);
atalkTimerRunning = FALSE;
if (ReQueue != ATALK_TIMER_NO_REQUEUE) { ASSERT(VALID_TMR(pList));
pList->tmr_Running = FALSE; if (pList->tmr_CancelIt) { DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO, ("atalkTimerDpcRoutine: Delayed cancel for %lx\n", pList));
RELEASE_SPIN_LOCK_DPC(&atalkTimerLock);
ReQueue = (*pList->tmr_Routine)(pList, TRUE);
ACQUIRE_SPIN_LOCK_DPC(&atalkTimerLock);
ASSERT(ReQueue == ATALK_TIMER_NO_REQUEUE); } else { if (ReQueue != ATALK_TIMER_REQUEUE) pList->tmr_AbsTime = (USHORT)ReQueue; atalkTimerEnqueue(pList); } } } }
RELEASE_SPIN_LOCK_DPC(&atalkTimerLock);
if (!atalkTimerStopped) { TimerStarted = KeSetTimer(&atalkTimer, atalkTimerTick, &atalkTimerDpc); ASSERT(!TimerStarted); } else { KeSetEvent(&atalkTimerStopEvent, IO_NETWORK_INCREMENT, FALSE); } }
/*** atalkTimerEnqueue
* * 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. */ VOID FASTCALL atalkTimerEnqueue( IN PTIMERLIST pListNew ) { PTIMERLIST pList, *ppList; USHORT 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 = &atalkTimerList; (pList = *ppList) != NULL; ppList = &pList->tmr_Next) { ASSERT(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; }
pListNew->tmr_Queued = TRUE; pListNew->tmr_Cancelled = FALSE; pListNew->tmr_CancelIt = FALSE; }
/*** AtalkTimerFlushAndStop
* * 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. */ VOID AtalkTimerFlushAndStop( VOID ) { PTIMERLIST pList; LONG ReQueue; KIRQL OldIrql; BOOLEAN Wait;
ASSERT (KeGetCurrentIrql() == LOW_LEVEL);
DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_ERR, ("AtalkTimerFlushAndStop: Entered\n"));
KeCancelTimer(&atalkTimer);
// The timer routines assume they are being called at DISPATCH level.
// Raise our Irql for this routine.
KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);
ACQUIRE_SPIN_LOCK_DPC(&atalkTimerLock);
atalkTimerStopped = TRUE; Wait = atalkTimerRunning;
// Dispatch all entries right away
while (atalkTimerList != NULL) { pList = atalkTimerList; ASSERT(VALID_TMR(pList)); atalkTimerList = pList->tmr_Next;
DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO, ("atalkTimerFlushAndStop: Dispatching %lx\n", pList->tmr_Routine));
pList->tmr_Queued = FALSE; pList->tmr_Running = TRUE;
RELEASE_SPIN_LOCK_DPC(&atalkTimerLock);
ReQueue = (*pList->tmr_Routine)(pList, TRUE);
ASSERT (ReQueue == ATALK_TIMER_NO_REQUEUE);
pList->tmr_Running = FALSE; ACQUIRE_SPIN_LOCK_DPC(&atalkTimerLock); }
RELEASE_SPIN_LOCK_DPC(&atalkTimerLock);
KeLowerIrql(OldIrql);
if (Wait) { // Wait for any timer events that are currently running. Only an MP issue
KeWaitForSingleObject(&atalkTimerStopEvent, Executive, KernelMode, TRUE, NULL); } }
/*** AtalkTimerCancelEvent
* * Cancel a previously scheduled timer event, if it hasn't fired already. */ BOOLEAN FASTCALL AtalkTimerCancelEvent( IN PTIMERLIST pList, IN PDWORD pdwOldState ) { KIRQL OldIrql; BOOLEAN Cancelled = FALSE; DWORD OldState=ATALK_TIMER_QUEUED;
ACQUIRE_SPIN_LOCK(&atalkTimerLock, &OldIrql);
// If this is not running, unlink it from the list
// adjusting relative deltas carefully
if (pList->tmr_Queued) { ASSERT (!(pList->tmr_Running));
OldState = ATALK_TIMER_QUEUED;
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;
// pointing to timer being removed? fix it!
if (atalkTimerList == pList) { atalkTimerList = pList->tmr_Next; }
Cancelled = pList->tmr_Cancelled = TRUE;
pList->tmr_Queued = FALSE;
} else if (pList->tmr_Running) { DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_ERR, ("AtalkTimerCancelEvent: %lx Running, cancel set\n", pList->tmr_Routine)); pList->tmr_CancelIt = TRUE; // Set to cancel after handler returns.
OldState = ATALK_TIMER_RUNNING; }
RELEASE_SPIN_LOCK(&atalkTimerLock, OldIrql);
if (pdwOldState) { *pdwOldState = OldState; }
return Cancelled; }
#if DBG
VOID AtalkTimerDumpList( VOID ) { PTIMERLIST pList; ULONG CumTime = 0;
DBGPRINT(DBG_COMP_DUMP, DBG_LEVEL_FATAL, ("TIMER LIST: (Times are in 100ms units)\n")); DBGPRINT(DBG_COMP_DUMP, DBG_LEVEL_FATAL, ("\tTime(Abs) Time(Rel) Routine Address TimerList\n"));
ACQUIRE_SPIN_LOCK_DPC(&atalkTimerLock);
for (pList = atalkTimerList; pList != NULL; pList = pList->tmr_Next) { CumTime += pList->tmr_RelDelta; DBGPRINT(DBG_COMP_DUMP, DBG_LEVEL_FATAL, ("\t %5d %5ld %lx %lx\n", pList->tmr_AbsTime, CumTime, pList->tmr_Routine, pList)); }
RELEASE_SPIN_LOCK_DPC(&atalkTimerLock); }
#endif
|