windows-nt/Source/XPSP1/NT/net/dlc/driver/llctimr.c

757 lines
19 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
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;
}
}