You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
756 lines
20 KiB
756 lines
20 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
llctimr.c
|
|
|
|
Abstract:
|
|
|
|
This module contains code that implements a lightweight timer system
|
|
for the data link driver.
|
|
|
|
This module gets control once in 40 ms when a DPC timer expires.
|
|
The routine scans the device context's link database, looking for timers
|
|
that have expired, and for those that have expired, their expiration
|
|
routines are executed.
|
|
|
|
This is how timers work in DLC:
|
|
|
|
Each adapter has a singly-linked list of timer ticks (terminated by NULL).
|
|
A tick just specifies work to be done at a certain time in the future.
|
|
Ticks are ordered by increasing time (multiples of 40 mSec). The work
|
|
list that has to be performed when a tick comes due is described by a
|
|
doubly-linked list of timers (LLC_TIMER) that the tick structure points
|
|
at through the pFront field. For each timer added to a tick's list, the
|
|
tick reference count is incremented; it is decremented when a timer is
|
|
removed. When the reference count is decremented to zero, the timer
|
|
tick is unlinked and deallocated
|
|
|
|
Every 40 mSec a kernel timer fires and executes our DPC routine
|
|
(ScanTimersDpc). This grabs the requisite spinlocks and searches through
|
|
all timer ticks on all adapter context structures looking for work to
|
|
do
|
|
|
|
Pictorially:
|
|
|
|
+---------+ --> other adapter contexts
|
|
+--------| Adapter |
|
|
| +---------+
|
|
|
|
|
+-> +------+---> +------+---> 0 (end of singly-linked list)
|
|
| Tick | | Tick |
|
|
| | | |
|
|
+------+ +------+
|
|
| ^
|
|
| +------------+-------------+
|
|
v | | |
|
|
+--> +-------+---> +-------+---> +-------+-----+
|
|
| +--| Timer | <---| Timer | <---| Timer | <-+ |
|
|
| | +-------+ +-------+ +-------+ | |
|
|
| | | |
|
|
| +------------------------------------------+ |
|
|
+----------------------------------------------+
|
|
|
|
The procedures in this module can be called only when SendSpinLock is set.
|
|
|
|
Contents:
|
|
ScanTimersDpc
|
|
LlcInitializeTimerSystem
|
|
LlcTerminateTimerSystem
|
|
TerminateTimer
|
|
InitializeLinkTimers
|
|
InitializeTimer
|
|
StartTimer
|
|
StopTimer
|
|
|
|
Author:
|
|
|
|
Antti Saarenheimo (o-anttis) 30-MAY-1991
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
28-Apr-1994 rfirth
|
|
|
|
* Changed to use single driver-level spinlock
|
|
|
|
* Added useful picture & description above to aid any other poor saps -
|
|
er - programmers - who get tricked into - er - who are lucky enough
|
|
to work on DLC
|
|
|
|
--*/
|
|
|
|
#include <llc.h>
|
|
|
|
//
|
|
// DLC timer tick is 40 ms !!!
|
|
//
|
|
|
|
#define TIMER_DELTA 400000L
|
|
|
|
//
|
|
// global data
|
|
//
|
|
|
|
ULONG AbsoluteTime = 0;
|
|
BOOLEAN DlcIsTerminating = FALSE;
|
|
BOOLEAN DlcTerminated = FALSE;
|
|
|
|
//
|
|
// private data
|
|
//
|
|
|
|
static LARGE_INTEGER DueTime = { (ULONG) -TIMER_DELTA, (ULONG) -1 };
|
|
static KTIMER SystemTimer;
|
|
static KDPC TimerSystemDpc;
|
|
|
|
|
|
VOID
|
|
ScanTimersDpc(
|
|
IN PKDPC Dpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called at DISPATCH_LEVEL by the system at regular
|
|
intervals to determine if any link-level timers have expired, and
|
|
if they have, to execute their expiration routines.
|
|
|
|
Arguments:
|
|
|
|
Dpc - Ignored
|
|
DeferredContext - Ignored
|
|
SystemArgument1 - Ignored
|
|
SystemArgument2 - Ignored
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLLC_TIMER pTimer;
|
|
PADAPTER_CONTEXT pAdapterContext;
|
|
PLLC_TIMER pNextTimer;
|
|
PTIMER_TICK pTick;
|
|
PTIMER_TICK pNextTick;
|
|
BOOLEAN boolRunBackgroundProcess;
|
|
KIRQL irql;
|
|
|
|
UNREFERENCED_PARAMETER(DeferredContext);
|
|
UNREFERENCED_PARAMETER(Dpc);
|
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
ASSUME_IRQL(DISPATCH_LEVEL);
|
|
|
|
AbsoluteTime++;
|
|
|
|
//
|
|
// The global spinlock keeps the adapters alive over this
|
|
//
|
|
|
|
ACQUIRE_DRIVER_LOCK();
|
|
|
|
ACQUIRE_LLC_LOCK(irql);
|
|
|
|
//
|
|
// scan timer queues for all adapters
|
|
//
|
|
|
|
for (pAdapterContext = pAdapters; pAdapterContext; pAdapterContext = pAdapterContext->pNext) {
|
|
|
|
boolRunBackgroundProcess = FALSE;
|
|
|
|
ACQUIRE_SPIN_LOCK(&pAdapterContext->SendSpinLock);
|
|
|
|
//
|
|
// The timer ticks are protected by a reference counter
|
|
//
|
|
|
|
for (pTick = pAdapterContext->pTimerTicks; pTick; pTick = pNextTick) {
|
|
|
|
if (pTick->pFront) {
|
|
|
|
//
|
|
// This keeps the tick alive, we cannot use spin lock,
|
|
// because the timers are called and deleted within
|
|
// SendSpinLock. (=> deadlock)
|
|
//
|
|
|
|
pTick->ReferenceCount++;
|
|
|
|
//
|
|
// Send spin lock prevents anybody to remove a timer
|
|
// when we are processing it.
|
|
//
|
|
|
|
for (pTimer = pTick->pFront;
|
|
pTimer && pTimer->ExpirationTime <= AbsoluteTime;
|
|
pTimer = pNextTimer) {
|
|
|
|
if ( (pNextTimer = pTimer->pNext) == pTick->pFront) {
|
|
pNextTimer = NULL;
|
|
}
|
|
|
|
//
|
|
// DLC driver needs a timer tick every 0.5 second to
|
|
// implement timer services defined by the API
|
|
//
|
|
|
|
if (pTick->Input == LLC_TIMER_TICK_EVENT) {
|
|
|
|
RELEASE_SPIN_LOCK(&pAdapterContext->SendSpinLock);
|
|
|
|
((PBINDING_CONTEXT)pTimer->hContext)->pfEventIndication(
|
|
((PBINDING_CONTEXT)pTimer->hContext)->hClientContext,
|
|
NULL,
|
|
LLC_TIMER_TICK_EVENT,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
ACQUIRE_SPIN_LOCK(&pAdapterContext->SendSpinLock);
|
|
|
|
StartTimer(pTimer);
|
|
|
|
} else {
|
|
StopTimer(pTimer);
|
|
RunStateMachineCommand(
|
|
pTimer->hContext,
|
|
pTick->Input
|
|
);
|
|
boolRunBackgroundProcess = TRUE;
|
|
}
|
|
}
|
|
|
|
pNextTick = pTick->pNext;
|
|
|
|
//
|
|
// Delete the timer tick, if there are no references to it.
|
|
//
|
|
|
|
if ((--pTick->ReferenceCount) == 0) {
|
|
|
|
//
|
|
// The timers are in a single entry list!
|
|
//
|
|
|
|
RemoveFromLinkList((PVOID*)&pAdapterContext->pTimerTicks, pTick);
|
|
|
|
FREE_MEMORY_ADAPTER(pTick);
|
|
}
|
|
} else {
|
|
pNextTick = pTick->pNext;
|
|
}
|
|
}
|
|
|
|
if (boolRunBackgroundProcess) {
|
|
BackgroundProcessAndUnlock(pAdapterContext);
|
|
} else {
|
|
RELEASE_SPIN_LOCK(&pAdapterContext->SendSpinLock);
|
|
}
|
|
}
|
|
|
|
RELEASE_LLC_LOCK(irql);
|
|
|
|
RELEASE_DRIVER_LOCK();
|
|
|
|
//
|
|
// Start up the timer again. Note that because we start the timer
|
|
// after doing work (above), the timer values will slip somewhat,
|
|
// depending on the load on the protocol. This is entirely acceptable
|
|
// and will prevent us from using the timer DPC in two different
|
|
// threads of execution.
|
|
//
|
|
|
|
if (!DlcIsTerminating) {
|
|
|
|
ASSUME_IRQL(ANY_IRQL);
|
|
|
|
KeSetTimer(&SystemTimer, DueTime, &TimerSystemDpc);
|
|
} else {
|
|
DlcTerminated = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
LlcInitializeTimerSystem(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the lightweight timer system for the
|
|
data link driver.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSUME_IRQL(PASSIVE_LEVEL);
|
|
|
|
KeInitializeDpc(&TimerSystemDpc, ScanTimersDpc, NULL);
|
|
KeInitializeTimer(&SystemTimer);
|
|
KeSetTimer(&SystemTimer, DueTime, &TimerSystemDpc);
|
|
}
|
|
|
|
|
|
VOID
|
|
LlcTerminateTimerSystem(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine terminates the timer system of the data link driver.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSUME_IRQL(PASSIVE_LEVEL);
|
|
|
|
DlcIsTerminating = TRUE;
|
|
|
|
//
|
|
// if KeCancelTimer returns FALSE then the timer was not set. Assume the DPC
|
|
// is either waiting to be scheduled or is already in progress
|
|
//
|
|
|
|
if (!KeCancelTimer(&SystemTimer)) {
|
|
|
|
//
|
|
// if timer is not set, wait for DPC to complete
|
|
//
|
|
|
|
while (!DlcTerminated) {
|
|
|
|
//
|
|
// wait 40 milliseconds - period of DLC's tick
|
|
//
|
|
|
|
LlcSleep(40000);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
TerminateTimer(
|
|
IN PADAPTER_CONTEXT pAdapterContext,
|
|
IN PLLC_TIMER pTimer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Terminate a timer tick by stopping pTimer (remove it from the tick's active
|
|
timer list). If pTimer was the last timer on the tick's list then unlink and
|
|
deallocate the timer tick.
|
|
|
|
This routine assumes that if a timer (LLC_TIMER) has a non-NULL pointer to
|
|
a tick (TIMER_TICK) then the timer tick owns the timer (i.e. the timer is
|
|
started) and this ownership is reflected in the reference count. Even if a
|
|
timer is stopped, if its pointer to the timer tick 'object' is valid then
|
|
the timer tick still owns the timer
|
|
|
|
Arguments:
|
|
|
|
pAdapterContext - adapter context which owns ticks/timers
|
|
pTimer - timer tick object of a link station
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN timerActive;
|
|
PTIMER_TICK pTick;
|
|
|
|
ASSUME_IRQL(DISPATCH_LEVEL);
|
|
|
|
//
|
|
// Timer may not always be initialized, when this is called
|
|
// from the cleanup processing of a failed OpenAdapter call.
|
|
//
|
|
|
|
if (!pTimer->pTimerTick) {
|
|
return FALSE;
|
|
}
|
|
|
|
pTick = pTimer->pTimerTick;
|
|
timerActive = StopTimer(pTimer);
|
|
|
|
//
|
|
// if that was the last timer on the list for this tick then remove the
|
|
// tick from the list and deallocate it
|
|
//
|
|
|
|
if (!--pTick->ReferenceCount) {
|
|
|
|
RemoveFromLinkList((PVOID*)&pAdapterContext->pTimerTicks, pTick);
|
|
|
|
FREE_MEMORY_ADAPTER(pTick);
|
|
|
|
}
|
|
return timerActive;
|
|
}
|
|
|
|
|
|
DLC_STATUS
|
|
InitializeLinkTimers(
|
|
IN OUT PDATA_LINK pLink
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes a timer tick objects of a link station.
|
|
|
|
Arguments:
|
|
|
|
pAdapterContext - the device context
|
|
pLink - the link context
|
|
|
|
Return Value:
|
|
|
|
DLC_STATUS
|
|
Success - STATUS_SUCCESS
|
|
Failure - DLC_STATUS_NO_MEMORY
|
|
out of system memory
|
|
|
|
--*/
|
|
|
|
{
|
|
DLC_STATUS LlcStatus;
|
|
|
|
PADAPTER_CONTEXT pAdapterContext = pLink->Gen.pAdapterContext;
|
|
|
|
ASSUME_IRQL(DISPATCH_LEVEL);
|
|
|
|
LlcStatus = InitializeTimer(pAdapterContext,
|
|
&pLink->T1,
|
|
pLink->TimerT1,
|
|
pAdapterContext->ConfigInfo.TimerTicks.T1TickOne,
|
|
pAdapterContext->ConfigInfo.TimerTicks.T1TickTwo,
|
|
T1_Expired,
|
|
pLink,
|
|
pLink->AverageResponseTime,
|
|
FALSE
|
|
);
|
|
if (LlcStatus != STATUS_SUCCESS) {
|
|
return LlcStatus;
|
|
}
|
|
|
|
LlcStatus = InitializeTimer(pAdapterContext,
|
|
&pLink->T2,
|
|
pLink->TimerT2,
|
|
pAdapterContext->ConfigInfo.TimerTicks.T2TickOne,
|
|
pAdapterContext->ConfigInfo.TimerTicks.T2TickTwo,
|
|
T2_Expired,
|
|
pLink,
|
|
0, // T2 is not based on the response time
|
|
FALSE
|
|
);
|
|
if (LlcStatus != STATUS_SUCCESS) {
|
|
return LlcStatus;
|
|
}
|
|
|
|
LlcStatus = InitializeTimer(pAdapterContext,
|
|
&pLink->Ti,
|
|
pLink->TimerTi,
|
|
pAdapterContext->ConfigInfo.TimerTicks.TiTickOne,
|
|
pAdapterContext->ConfigInfo.TimerTicks.TiTickTwo,
|
|
Ti_Expired,
|
|
pLink,
|
|
pLink->AverageResponseTime,
|
|
TRUE
|
|
);
|
|
return LlcStatus;
|
|
}
|
|
|
|
|
|
DLC_STATUS
|
|
InitializeTimer(
|
|
IN PADAPTER_CONTEXT pAdapterContext,
|
|
IN OUT PLLC_TIMER pTimer,
|
|
IN UCHAR TickCount,
|
|
IN UCHAR TickOne,
|
|
IN UCHAR TickTwo,
|
|
IN UINT Input,
|
|
IN PVOID hContextHandle,
|
|
IN UINT ResponseDelay,
|
|
IN BOOLEAN StartNewTimer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes a timer tick objects of a link station.
|
|
|
|
Arguments:
|
|
|
|
pTimer - timer tick object of a link station
|
|
TickCount - DLC ticks, see DLC documentation (or code)
|
|
TickOne - see DLC documentation
|
|
TickTwo - see DLC documentation
|
|
Input - the used state machine input, when the timer expires
|
|
hContextHandle - context handle when the state machine is called
|
|
StartNewTimer - set if the timer must be started when it is initialized
|
|
for the first time. Subsequent times, the timer keeps its
|
|
old state
|
|
ResponseDelay - an optional base value that is added to the timer value
|
|
|
|
Return Value:
|
|
|
|
DLC_STATUS
|
|
Success - STATUS_SUCCESS
|
|
Failure - DLC_STATUS_NO_MEMORY
|
|
out of system memory
|
|
|
|
--*/
|
|
|
|
{
|
|
UINT DeltaTime;
|
|
PTIMER_TICK pTick;
|
|
|
|
ASSUME_IRQL(DISPATCH_LEVEL);
|
|
|
|
//
|
|
// All times are multiples of 40 milliseconds
|
|
// (I am not sure how portable is this design)
|
|
// See LAN Manager Network Device Driver Guide
|
|
// ('Remoteboot protocol') for further details
|
|
// about TickOne and TickTwo
|
|
// We have already checked, that the
|
|
// timer tick count is less than 11.
|
|
//
|
|
|
|
DeltaTime = (TickCount > 5 ? (UINT)(TickCount - 5) * (UINT)TickTwo
|
|
: (UINT)TickCount * (UINT)TickOne);
|
|
|
|
//
|
|
// We discard the low bits in the reponse delay.
|
|
//
|
|
|
|
DeltaTime += (ResponseDelay & 0xfff0);
|
|
|
|
//
|
|
// Return immediately, if the old value is the
|
|
// same as the new one (T2 link station is reinitialized
|
|
// unnecessary, when the T1 and Ti timers are retuned
|
|
// for changed response time.
|
|
//
|
|
|
|
if (pTimer->pTimerTick && (pTimer->pTimerTick->DeltaTime == DeltaTime)) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Try to find a timer tick object having the same delta time and input
|
|
//
|
|
|
|
for (pTick = pAdapterContext->pTimerTicks; pTick; pTick = pTick->pNext) {
|
|
if ((pTick->DeltaTime == DeltaTime) && (pTick->Input == (USHORT)Input)) {
|
|
break;
|
|
}
|
|
}
|
|
if (!pTick) {
|
|
pTick = ALLOCATE_ZEROMEMORY_ADAPTER(sizeof(TIMER_TICK));
|
|
if (!pTick) {
|
|
return DLC_STATUS_NO_MEMORY;
|
|
}
|
|
pTick->DeltaTime = DeltaTime;
|
|
pTick->Input = (USHORT)Input;
|
|
pTick->pNext = pAdapterContext->pTimerTicks;
|
|
pAdapterContext->pTimerTicks = pTick;
|
|
}
|
|
pTick->ReferenceCount++;
|
|
|
|
//
|
|
// We must delete the previous timer reference
|
|
// when we know if the memory allocation operation
|
|
// was successfull or not. Otherwise the setting of
|
|
// the link parameters might delete old timer tick,
|
|
// but it would not be able to allocate the new one.
|
|
// The link must be protected, when this routine is called.
|
|
//
|
|
|
|
if (pTimer->pTimerTick) {
|
|
StartNewTimer = TerminateTimer(pAdapterContext, pTimer);
|
|
}
|
|
pTimer->pTimerTick = pTick;
|
|
pTimer->hContext = hContextHandle;
|
|
|
|
if (StartNewTimer) {
|
|
StartTimer(pTimer);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
VOID
|
|
StartTimer(
|
|
IN OUT PLLC_TIMER pTimer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This starts the given timer within spin locks
|
|
|
|
Arguments:
|
|
|
|
pTimer - timer tick object of a link station
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLLC_TIMER pFront;
|
|
PTIMER_TICK pTimerTick = pTimer->pTimerTick;
|
|
|
|
ASSUME_IRQL(DISPATCH_LEVEL);
|
|
|
|
//
|
|
// We always reset the pNext pointer, when a item is
|
|
// removed from a link list => the timer element cannot be
|
|
// in the link list of a timer tick object if its next pointer is null
|
|
//
|
|
|
|
if (pTimer->pNext) {
|
|
|
|
//
|
|
// We don't need to change the timer's position, if the new timer
|
|
// would be the same as the old time.
|
|
//
|
|
|
|
if (pTimer->ExpirationTime != AbsoluteTime + pTimerTick->DeltaTime) {
|
|
|
|
//
|
|
// The timer has already been started, move it to the top of
|
|
// the link list.
|
|
//
|
|
|
|
if (pTimer != (pFront = pTimerTick->pFront)) {
|
|
pTimer->pPrev->pNext = pTimer->pNext;
|
|
pTimer->pNext->pPrev = pTimer->pPrev;
|
|
pTimer->pNext = pFront;
|
|
pTimer->pPrev = pFront->pPrev;
|
|
pFront->pPrev->pNext = pTimer;
|
|
pFront->pPrev = pTimer;
|
|
}
|
|
}
|
|
} else {
|
|
if (!(pFront = pTimerTick->pFront)) {
|
|
pTimerTick->pFront = pTimer->pNext = pTimer->pPrev = pTimer;
|
|
} else {
|
|
pTimer->pNext = pFront;
|
|
pTimer->pPrev = pFront->pPrev;
|
|
pFront->pPrev->pNext = pTimer;
|
|
pFront->pPrev = pTimer;
|
|
}
|
|
}
|
|
pTimer->ExpirationTime = AbsoluteTime + pTimerTick->DeltaTime;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
StopTimer(
|
|
IN PLLC_TIMER pTimer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This stops the given timer within spin locks
|
|
|
|
Arguments:
|
|
|
|
pTimer - timer tick object of a link station
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN
|
|
TRUE - timer was running
|
|
FALSE - timer was not running
|
|
|
|
--*/
|
|
|
|
{
|
|
ASSUME_IRQL(DISPATCH_LEVEL);
|
|
|
|
if (pTimer->pNext) {
|
|
|
|
PTIMER_TICK pTimerTick = pTimer->pTimerTick;
|
|
|
|
//
|
|
// if the timer points to itself then its the only thing on the list:
|
|
// zap the link in the timer tick structure (no more timers for this
|
|
// tick) and zap the next field in the timer structure to indicate
|
|
// the timer has been removed from the tick list. If the timer points
|
|
// to another timer, then remove this timer from the doubly-linked list
|
|
// of timers
|
|
//
|
|
|
|
if (pTimer != pTimer->pNext) {
|
|
if (pTimer == pTimerTick->pFront) {
|
|
pTimerTick->pFront = pTimer->pNext;
|
|
}
|
|
pTimer->pPrev->pNext = pTimer->pNext;
|
|
pTimer->pNext->pPrev = pTimer->pPrev;
|
|
pTimer->pNext = NULL;
|
|
} else {
|
|
pTimerTick->pFront = pTimer->pNext = NULL;
|
|
}
|
|
return TRUE;
|
|
} else {
|
|
|
|
//
|
|
// this timer was not on a timer tick list
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
}
|