467 lines
10 KiB
C
467 lines
10 KiB
C
|
/*
|
|||
|
|
|||
|
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
|
|||
|
|