|
|
/*++
Copyright (c) 1989, 1990, 1991 Microsoft Corporation
Module Name:
timer.c
Abstract:
This module contains code that implements the lightweight timer system for the NBF protocol provider. This is not a general-purpose timer system; rather, it is specific to servicing LLC (802.2) links with three timers each.
Services are provided in macro form (see NBFPROCS.H) to start and stop timers. This module contains the code that gets control when the timer in the device context expires as a result of calling kernel services. 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.
Author:
David Beaver (dbeaver) 1-July-1991
Environment:
Kernel mode
Revision History:
--*/
#include "precomp.h"
#pragma hdrstop
ULONG StartTimer = 0; ULONG StartTimerSet = 0; ULONG StartTimerT1 = 0; ULONG StartTimerT2 = 0; ULONG StartTimerDelayedAck = 0; ULONG StartTimerLinkDeferredAdd = 0; ULONG StartTimerLinkDeferredDelete = 0;
#if DBG
extern ULONG NbfDebugPiggybackAcks; ULONG NbfDebugShortTimer = 0; #endif
#if DBG
//
// These are temp, to track how the timers are working
//
ULONG TimerInsertsAtEnd = 0; ULONG TimerInsertsEmpty = 0; ULONG TimerInsertsInMiddle = 0; #endif
//
// These are constants calculated by InitializeTimerSystem
// to be the indicated amound divided by the tick increment.
//
ULONG NbfTickIncrement = 0; ULONG NbfTwentyMillisecondsTicks = 0; ULONG NbfShortTimerDeltaTicks = 0; ULONG NbfMaximumIntervalTicks = 0; // usually 60 seconds in ticks
LARGE_INTEGER DueTimeDelta = { (ULONG)(-SHORT_TIMER_DELTA), -1 };
VOID ExpireT2Timer( PTP_LINK Link );
VOID StopStalledConnections( IN PDEVICE_CONTEXT DeviceContext );
ULONG GetTimerInterval( IN PTP_LINK Link )
/*++
Routine Description:
GetTimerInterval returns the difference in time between the current time and Link->CurrentTimerStart (in ticks). We limit the interval to 60 seconds. A value of 0 may be returned which should be interpreted as 1/2.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
Return Value:
The interval.
--*/
{
LARGE_INTEGER CurrentTick; LARGE_INTEGER Interval;
//
// Determine the current tick; the start tick has been saved
// in Link->CurrentTimerStart.
//
KeQueryTickCount (&CurrentTick);
//
// Determine the difference between now and then.
//
Interval.QuadPart = CurrentTick.QuadPart - (Link->CurrentTimerStart).QuadPart;
//
// If the gap is too big, return 1 minute.
//
if (Interval.HighPart != 0 || (Interval.LowPart > NbfMaximumIntervalTicks)) { return NbfMaximumIntervalTicks; }
return Interval.LowPart;
} /* GetTimerInterval */
VOID BackoffCurrentT1Timeout( IN PTP_LINK Link )
/*++
Routine Description:
This routine is called if T1 expires and we are about to retransmit a poll frame. It backs off CurrentT1Timeout, up to a limit of 10 seconds.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
Return Value:
None.
--*/
{
//
// We must have previously sent a poll frame if we are
// calling this.
//
// do we need spinlock guarding for MP ?
//
if (!Link->CurrentPollOutstanding) { return; }
++Link->CurrentPollRetransmits;
//
// T1 backs off 1.5 times each time.
//
Link->CurrentT1Timeout += (Link->CurrentT1Timeout >> 1);
//
// Limit T1 to 10 seconds.
//
if (Link->CurrentT1Timeout > ((10 * SECONDS) / SHORT_TIMER_DELTA)) { Link->CurrentT1Timeout = (10 * SECONDS) / SHORT_TIMER_DELTA; }
} /* BackoffCurrentT1Timeout */
VOID UpdateBaseT1Timeout( IN PTP_LINK Link )
/*++
Routine Description:
This routine is called when a response to a poll frame is received. StartT1 will have been called when the frame is sent. The routine updates the link's T1 timeout as well as delay and throughput.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
Return Value:
None.
--*/
{ ULONG Delay; ULONG ShiftedTicksDelay;
//
// We must have previously sent a poll frame if we are
// calling this.
//
if (!Link->CurrentPollOutstanding) { return; }
Delay = GetTimerInterval (Link);
if (Link->CurrentPollRetransmits == 0) {
//
// Convert the delay into NBF ticks, shifted by
// DLC_TIMER_ACCURACY and also multiplied by 4.
// We want to divide by SHORT_TIMER_DELTA, then
// shift left by DLC_TIMER_ACCURACY+2. We divide
// by NbfShortTimerDeltaTicks because the Delay
// is returned in ticks.
//
// We treat a delay of 0 as 1/2, so we use 1
// shifted left by (DLC_TIMER_ACCURACY+1).
//
if (Delay == 0) {
ShiftedTicksDelay = (1 << (DLC_TIMER_ACCURACY + 1)) / NbfShortTimerDeltaTicks;
} else {
ShiftedTicksDelay = (Delay << (DLC_TIMER_ACCURACY + 2)) / NbfShortTimerDeltaTicks;
}
//
// Use the timing information to update BaseT1Timeout,
// if the last frame sent was large enough to matter
// (we use half of the max frame size here). This is
// so we don't shrink the timeout too much after sending
// a short frame. However, we update even for small frames
// if the last time we sent a poll we had to retransmit
// it, since that means T1 is much too small and we should
// increase it as much as we can. We also update for any
// size frame if the new delay is bigger than the current
// value, so we can ramp up quickly if needed.
//
if (ShiftedTicksDelay > Link->BaseT1Timeout) {
//
// If our new delay is more, than we weight it evenly
// with the previous value.
//
Link->BaseT1Timeout = (Link->BaseT1Timeout + ShiftedTicksDelay) / 2;
} else if (Link->CurrentT1Backoff) {
//
// If we got a retransmit last time, then weight
// the new timer more heavily than usual.
//
Link->BaseT1Timeout = ((Link->BaseT1Timeout * 3) + ShiftedTicksDelay) / 4;
} else if (Link->CurrentPollSize >= Link->BaseT1RecalcThreshhold) {
//
// Normally, the new timeout is 7/8 the previous value and
// 1/8 the newly observed delay.
//
Link->BaseT1Timeout = ((Link->BaseT1Timeout * 7) + ShiftedTicksDelay) / 8;
}
//
// Restrict the real timeout to a minimum based on
// the link speed (always >= 400 ms).
//
if (Link->BaseT1Timeout < Link->MinimumBaseT1Timeout) {
Link->BaseT1Timeout = Link->MinimumBaseT1Timeout;
}
//
// Update link delay and throughput also. Remember
// that a delay of 0 should be interpreted as 1/2.
//
UpdateDelayAndThroughput( Link, (Delay == 0) ? (NbfTickIncrement / 2) : (Delay * NbfTickIncrement));
//
// We had no retransmits last time, so go back to current base.
//
Link->CurrentT1Timeout = Link->BaseT1Timeout >> DLC_TIMER_ACCURACY;
Link->CurrentT1Backoff = FALSE;
} else {
Link->CurrentT1Backoff = TRUE;
if (!(Link->ThroughputAccurate)) {
//
// If we are just starting up, we have to update the
// throughput even on a retransmit, so we get *some*
// value there.
//
UpdateDelayAndThroughput( Link, (Delay == 0) ? (NbfTickIncrement / 2) : (Delay * NbfTickIncrement));
}
}
} /* UpdateBaseT1Timeout */
VOID CancelT1Timeout( IN PTP_LINK Link )
/*++
Routine Description:
This routine is called when we have not received any responses to a poll frame and are giving up rather than retransmitting.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
Return Value:
None.
--*/
{
//
// We must have previously sent a poll frame if we are
// calling this.
//
// do we need spinlock guarding for MP ?
//
if (!Link->CurrentPollOutstanding) { return; }
//
// We are stopping a polling condition, so reset T1.
//
Link->CurrentT1Timeout = Link->BaseT1Timeout >> DLC_TIMER_ACCURACY;
Link->CurrentT1Backoff = FALSE;
//
// Again, this isn't safe on MP (or UP, maybe).
//
Link->CurrentPollOutstanding = FALSE;
} /* CancelT1Timeout */
VOID UpdateDelayAndThroughput( IN PTP_LINK Link, IN ULONG TimerInterval )
/*++
Routine Description:
This routine is called when a response packet used to time link delay has been received. It is assumed that StartT1 or FakeStartT1 was called when the initial packet was sent.
NOTE: For now, we also calculate throughput based on this.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
TimerInterval - The link delay measured.
Return Value:
None.
--*/
{
ULONG PacketSize;
if (Link->Delay == 0xffffffff) {
//
// If delay is unknown, use this.
//
Link->Delay = TimerInterval;
} else if (Link->CurrentPollSize <= 64) {
//
// Otherwise, for small frames calculate the new
// delay by averaging with the old one.
//
Link->Delay = (Link->Delay + TimerInterval) / 2;
}
//
// Calculate the packet size times the number of time units
// in 10 milliseconds, which will allow us to calculate
// throughput in bytes/10ms (we later multiply by 100
// to obtain the real throughput in bytes/s).
//
// Given the size of MILLISECONDS, this allows packets of up
// to ~20K, so for bigger packets we just assume that (since
// throughput won't be an issue there).
//
if (Link->CurrentPollSize > 20000) { PacketSize = 20000 * (10 * MILLISECONDS); } else { PacketSize = Link->CurrentPollSize * (10*MILLISECONDS); }
//
// If throughput is not accurate, then we will use this
// packet only to calculate it. To avoid being confused
// by very small packets, assume a minimum size of 64.
//
if ((!Link->ThroughputAccurate) && (PacketSize < (64*(10*MILLISECONDS)))) { PacketSize = 64 * (10*MILLISECONDS); }
//
// PacketSize is going to be divided by TimerInterval;
// to prevent a zero throughput, we boost it up if needed.
//
if (PacketSize < TimerInterval) { PacketSize = TimerInterval; }
if (Link->CurrentPollSize >= 512) {
//
// Calculate throughput here by removing the established delay
// from the time.
//
if ((Link->Delay + (2*MILLISECONDS)) < TimerInterval) {
//
// If the current delay is less than the new timer
// interval (plus 2 ms), then subtract it off for a
// more accurate throughput calculation.
//
TimerInterval -= Link->Delay;
}
//
// We assume by this point (sending a > 512-byte frame) we
// already have something established as Link->Throughput.
//
if (!(Link->ThroughputAccurate)) {
Link->Throughput.QuadPart = UInt32x32To64((PacketSize / TimerInterval), 100);
Link->ThroughputAccurate = TRUE;
#if 0
NbfPrint2 ("INT: %ld.%1.1d us\n", TimerInterval / 10, TimerInterval % 10); NbfPrint4 ("D: %ld.%1.1d us T: %ld (%d)/s\n", Link->Delay / 10, Link->Delay % 10, Link->Throughput.LowPart, Link->CurrentPollSize); #endif
} else {
LARGE_INTEGER TwiceThroughput;
//
// New throughput is the average of the old throughput, and
// the current packet size divided by the delay just observed.
// First we calculate the sum, then we shift right by one.
//
TwiceThroughput.QuadPart = Link->Throughput.QuadPart + UInt32x32To64((PacketSize / TimerInterval), 100);
Link->Throughput.QuadPart = TwiceThroughput.QuadPart >> 1; }
} else if (!(Link->ThroughputAccurate)) {
//
// We don't have accurate throughput, so just get an estimate
// by ignoring the delay on this small frame.
//
Link->Throughput.QuadPart = UInt32x32To64((PacketSize / TimerInterval), 100);
}
} /* UpdateDelayAndThroughput */
VOID FakeStartT1( IN PTP_LINK Link, IN ULONG PacketSize )
/*++
Routine Description:
This routine is called before sending a packet that will be used to time link delay, but where StartT1 will not be started. It is assumed that FakeUpdateBaseT1Timeout will be called when the response is received. This is used for timing frames that have a known immediate response, but are not poll frames.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
PacketSize - The size of the packet that was just sent.
Return Value:
None.
--*/
{
Link->CurrentPollSize = PacketSize; KeQueryTickCount(&Link->CurrentTimerStart);
} /* FakeStartT1 */
VOID FakeUpdateBaseT1Timeout( IN PTP_LINK Link )
/*++
Routine Description:
This routine is called when a response to a frame is received, and we called FakeStartT1 when the initial frame was sent. This is used for timing frames that have a known immediate response, but are not poll frames.
NOTE: This routine should be called with the link spinlock held.
Arguments:
Link - Pointer to a transport link object.
Return Value:
None.
--*/
{ ULONG Delay;
Delay = GetTimerInterval (Link);
//
// Convert the delay into NBF ticks, shifted by
// DLC_TIMER_ACCURACY and also multiplied by 4.
// We want to divide by SHORT_TIMER_DELTA, then
// shift left by DLC_TIMER_ACCURACY+2. We divide
// by NbfShortTimerDeltaTicks because the Delay
// is returned in ticks. We treat a Delay of 0
// as 1/2 and calculate ((1/2) << x) as (1 << (x-1)).
//
// This timeout is treated as the correct value.
//
if (Delay == 0) {
Link->BaseT1Timeout = (1 << (DLC_TIMER_ACCURACY + 1)) / NbfShortTimerDeltaTicks;
} else {
Link->BaseT1Timeout = (Delay << (DLC_TIMER_ACCURACY + 2)) / NbfShortTimerDeltaTicks;
}
//
// Restrict the real timeout to a minimum based on
// the link speed (always >= 400 ms).
//
if (Link->BaseT1Timeout < Link->MinimumBaseT1Timeout) { Link->BaseT1Timeout = Link->MinimumBaseT1Timeout; }
Link->CurrentT1Timeout = Link->BaseT1Timeout >> DLC_TIMER_ACCURACY;
//
// Update link delay and throughput also.
//
UpdateDelayAndThroughput( Link, (Delay == 0) ? (NbfTickIncrement / 2) : (Delay * NbfTickIncrement));
} /* FakeUpdateBaseT1Timeout */
VOID StartT1( IN PTP_LINK Link, IN ULONG PacketSize )
/*++
Routine Description:
This routine starts the T1 timer for the given link. If the link was already on the list, it is moved to the tail. If not, it is inserted at tail.
NOTE: THIS ROUTINE MUST BE CALLED AT DPC LEVEL.
Arguments:
Link - pointer to the link of interest.
PollPacketSize - If a poll packet was just sent it is its size; otherwise this will be 0 (when non-poll I-frames are sent).
Return Value:
None.
--*/
{ PDEVICE_CONTEXT DeviceContext = Link->Provider;
if (PacketSize > 0) {
//
// If we are sending an initial poll frame, then do timing stuff.
//
Link->CurrentPollRetransmits = 0; Link->CurrentPollSize = PacketSize; Link->CurrentPollOutstanding = TRUE; KeQueryTickCount(&Link->CurrentTimerStart);
} else {
Link->CurrentPollOutstanding = FALSE;
}
//
// Insert us in the queue if we aren't in it.
//
Link->T1 = DeviceContext->ShortAbsoluteTime+Link->CurrentT1Timeout;
if (!Link->OnShortList) {
ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
if (!Link->OnShortList) { Link->OnShortList = TRUE; InsertTailList (&DeviceContext->ShortList, &Link->ShortList); }
if (!DeviceContext->a.i.ShortListActive) {
StartTimerT1++; NbfStartShortTimer (DeviceContext); DeviceContext->a.i.ShortListActive = TRUE;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); }
}
VOID StartT2( IN PTP_LINK Link )
/*++
Routine Description:
This routine adds the given link to the T2 queue and starts the timer. If the link is already on the queue, it is moved to the queue end.
NOTE: THIS ROUTINE MUST BE CALLED AT DPC LEVEL.
Arguments:
Link - pointer to the link of interest.
Return Value:
None.
--*/
{ PDEVICE_CONTEXT DeviceContext = Link->Provider;
if (DeviceContext->MacInfo.MediumAsync) {
//
// On an async line, expire it as soon as possible.
//
Link->T2 = DeviceContext->ShortAbsoluteTime;
} else {
Link->T2 = DeviceContext->ShortAbsoluteTime+Link->T2Timeout;
}
if (!Link->OnShortList) {
ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
if (!Link->OnShortList) { Link->OnShortList = TRUE; InsertTailList (&DeviceContext->ShortList, &Link->ShortList); }
if (!DeviceContext->a.i.ShortListActive) {
StartTimerT2++; NbfStartShortTimer (DeviceContext); DeviceContext->a.i.ShortListActive = TRUE;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); }
}
VOID StartTi( IN PTP_LINK Link )
/*++
Routine Description:
This routine adds the given link to the Ti queue and starts the timer. As above, if the link is already on the queue it is moved to the queue end.
NOTE: THIS ROUTINE MUST BE CALLED AT DPC LEVEL.
Arguments:
Link - pointer to the link of interest.
Return Value:
None.
--*/
{ PDEVICE_CONTEXT DeviceContext = Link->Provider;
//
// On an easily disconnected link, with only server connections
// on this link, we set a long Ti timeout, and when it
// expires with no activity we start checkpointing, otherwise
// we assume things are OK.
//
if (DeviceContext->EasilyDisconnected && Link->NumberOfConnectors == 0) { Link->Ti = DeviceContext->LongAbsoluteTime + (2 * Link->TiTimeout); Link->TiStartPacketsReceived = Link->PacketsReceived; } else { Link->Ti = DeviceContext->LongAbsoluteTime+Link->TiTimeout; }
if (!Link->OnLongList) {
ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
if (!Link->OnLongList) { Link->OnLongList = TRUE; InsertTailList (&DeviceContext->LongList, &Link->LongList); }
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); }
}
#if DBG
VOID StopT1( IN PTP_LINK Link )
/*++
Routine Description:
This routine
Arguments:
Link - pointer to the link of interest.
Return Value:
None.
--*/
{ //
// Again, this isn't safe on MP (or UP, maybe).
//
Link->CurrentPollOutstanding = FALSE; Link->T1 = 0;
}
VOID StopT2( IN PTP_LINK Link )
/*++
Routine Description:
This routine
Arguments:
Link - pointer to the link of interest.
Return Value:
None.
--*/
{ Link->ConsecutiveIFrames = 0; Link->T2 = 0;
}
VOID StopTi( IN PTP_LINK Link )
/*++
Routine Description:
This routine
Arguments:
Link - pointer to the link of interest.
Return Value:
None.
--*/
{ Link->Ti = 0; } #endif
VOID ExpireT1Timer( PTP_LINK Link )
/*++
Routine Description:
This routine is called when a link's T1 timer expires. T1 is the retransmission timer, and is used to remember that a response is expected to any of the following: (1) a checkpoint, (2) a transmitted I-frame, (3) a SABME, or (4) a DISC. Cases 3 and 4 are actually special forms of a checkpoint, since they are sent by this protocol implementation with the poll bit set, effectively making them a checkpoint sequence.
Arguments:
Link - Pointer to the TP_LINK object whose T1 timer has expired.
Return Value:
none.
--*/
{ PDLC_I_FRAME DlcHeader;
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: Entered.\n"); }
ACQUIRE_DPC_SPIN_LOCK (&Link->SpinLock);
switch (Link->State) {
case LINK_STATE_ADM:
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: State=ADM, timeout not expected.\n"); } break;
case LINK_STATE_READY:
//
// We've sent an I-frame and haven't received an acknowlegement
// yet, or we are checkpointing, and must retry the checkpoint.
// Another possibility is that we're rejecting, and he hasn't
// sent anything yet.
//
switch (Link->SendState) {
case SEND_STATE_DOWN:
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: Link READY but SendState=DOWN.\n"); } break;
case SEND_STATE_READY:
//
// We sent an I-frame and didn't get an acknowlegement.
// Initiate a checkpoint sequence.
//
IF_NBFDBG (NBF_DEBUG_TIMER) { {PTP_PACKET packet; PLIST_ENTRY p; NbfPrint0 ("ExpireT1Timer: Link State=READY, SendState=READY .\n"); NbfDumpLinkInfo (Link); p=Link->WackQ.Flink; NbfPrint0 ("ExpireT1Timer: Link WackQ entries:\n"); while (p != &Link->WackQ) { packet = CONTAINING_RECORD (p, TP_PACKET, Linkage); DlcHeader = (PDLC_I_FRAME)&(packet->Header[Link->HeaderLength]); NbfPrint2 (" %08lx %03d\n", p, (DlcHeader->SendSeq >> 1)); p = p->Flink; }} }
Link->SendRetries = (UCHAR)Link->LlcRetries; Link->SendState = SEND_STATE_CHECKPOINTING; // Don't BackoffT1Timeout yet.
NbfSendRr (Link, TRUE, TRUE);// send RR-c/p, StartT1, release lock
break;
case SEND_STATE_REJECTING:
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: Link State=READY, SendState=REJECTING.\n"); NbfPrint0 ("so what do we do here? consult the manual...\n"); } Link->SendState = SEND_STATE_CHECKPOINTING; // Link->SendRetries = Link->LlcRetries;
// break; // DGB: doing nothing is obviously wrong, we've
// // gotten a T1 expiration during resend. Try
// // an RR to say hey.
case SEND_STATE_CHECKPOINTING:
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: Link State=READY, SendState=CHECKPOINTING.\n"); NbfDumpLinkInfo (Link); } if (--Link->SendRetries == 0) {
//
// We have not gotten any response to RR-p packets,
// initiate orderly link teardown.
//
CancelT1Timeout (Link); // we are stopping a polling state
Link->State = LINK_STATE_W_DISC_RSP; // we are awaiting a DISC/f.
Link->SendState = SEND_STATE_DOWN; Link->ReceiveState = RECEIVE_STATE_DOWN; Link->SendRetries = (UCHAR)Link->LlcRetries;
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock);
NbfStopLink (Link);
StartT1 (Link, Link->HeaderLength + sizeof(DLC_S_FRAME)); // retransmit timer.
NbfSendDisc (Link, TRUE); // send DISC-c/p.
#if DBG
if (NbfDisconnectDebug) { NbfPrint0( "ExpireT1Timer sending DISC (checkpoint failed)\n" ); } #endif
} else {
BackoffCurrentT1Timeout (Link); NbfSendRr (Link, TRUE, TRUE); // send RR-c/p, StartT1, release lock.
} break;
default:
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint1 ("ExpireT1Timer: Link State=READY, SendState=%ld (UNKNOWN).\n", Link->SendState); } } break;
case LINK_STATE_CONNECTING:
//
// We sent a SABME-c/p and have not yet received UA-r/f. This
// means we must decrement the retry count and if it is not yet
// zero, we issue another SABME command, because he has probably
// dropped our first one.
//
if (--Link->SendRetries == 0) {
CancelT1Timeout (Link); // we are stopping a polling state
Link->State = LINK_STATE_ADM; NbfSendDm (Link, FALSE); // send DM/0, release lock
#if DBG
if (NbfDisconnectDebug) { NbfPrint0( "ExpireT1Timer calling NbfStopLink (no response to SABME)\n" ); } #endif
NbfStopLink (Link);
// moving to ADM, remove reference
NbfDereferenceLinkSpecial("Expire T1 in CONNECTING mode", Link, LREF_NOT_ADM);
return; // skip extra spinlock release.
} else { BackoffCurrentT1Timeout (Link); NbfSendSabme (Link, TRUE); // send SABME/p, StartT1, release lock
} break;
case LINK_STATE_W_POLL:
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: State=W_POLL, timeout not expected.\n"); } break;
case LINK_STATE_W_FINAL:
//
// We sent our initial RR-c/p and have not received his RR-r/f.
// We have to restart the checkpoint, unless our retries have
// run out, in which case we just abort the link.
//
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT1Timer: Link State=W_FINAL.\n"); NbfDumpLinkInfo (Link); }
if (--Link->SendRetries == 0) {
CancelT1Timeout (Link); // we are stopping a polling state
Link->State = LINK_STATE_ADM; NbfSendDm (Link, FALSE); // send DM/0, release lock
#if DBG
if (NbfDisconnectDebug) { NbfPrint0( "ExpireT1Timer calling NbfStopLink (no final received)\n" ); } #endif
NbfStopLink (Link);
// moving to ADM, remove reference
NbfDereferenceLinkSpecial("Expire T1 in W_FINAL mode", Link, LREF_NOT_ADM);
return; // skip extra spinlock release.
} else {
BackoffCurrentT1Timeout (Link); NbfSendRr (Link, TRUE, TRUE); // send RR-c/p, StartT1, release lock
} break;
case LINK_STATE_W_DISC_RSP:
//
// We sent a DISC-c/p to disconnect this link and are awaiting
// his response, either a UA-r/f or DM-r/f. We have to issue
// the DISC again, unless we've tried a few times, in which case
// we just shut the link down.
//
IF_NBFDBG (NBF_DEBUG_TEARDOWN) { NbfPrint0 ("ExpireT1Timer: Link State=W_DISC_RESP.\n"); NbfDumpLinkInfo (Link); }
if (--Link->SendRetries == 0) {
CancelT1Timeout (Link); // we are stopping a polling state
Link->State = LINK_STATE_ADM; NbfSendDm (Link, FALSE); // send DM/0, release lock
#if DBG
if (NbfDisconnectDebug) { NbfPrint0( "ExpireT1Timer calling NbfStopLink (no response to DISC)\n" ); } #endif
NbfStopLink (Link);
// moving to ADM, remove reference
NbfDereferenceLinkSpecial("Expire T1 in W_DISC_RSP mode", Link, LREF_NOT_ADM);
return; // skip extra spinlock release.
} else {
// we don't bother calling BackoffCurrentT1Timeout for DISCs.
++Link->CurrentPollRetransmits; StartT1 (Link, Link->HeaderLength + sizeof(DLC_S_FRAME)); // startup timer again.
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); NbfSendDisc (Link, TRUE); // send DISC/p.
} break;
default:
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint1 ("ExpireT1Timer: State=%ld (UNKNOWN), timeout not expected.\n", Link->State); } }
} /* ExpireT1Timer */
VOID ExpireT2Timer( PTP_LINK Link )
/*++
Routine Description:
This routine is called when a link's T2 timer expires. T2 is the delayed acknowlegement timer in the LLC connection-oriented procedures. The T2 timer is started when a valid I-frame is received but not immediately acknowleged. Then, if reverse I-frame traffic is sent, the timer is stopped, since the reverse traffic will acknowlege the received I-frames. If no reverse I-frame traffic becomes available to send, then this timer fires, causing a RR-r/0 to be sent so as to acknowlege the received but as yet unacked I-frames.
Arguments:
Link - Pointer to the TP_LINK object whose T2 timer has expired.
Return Value:
none.
--*/
{ IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireT2Timer: Entered.\n"); }
ACQUIRE_DPC_SPIN_LOCK (&Link->SpinLock);
NbfSendRr (Link, FALSE, FALSE); // send RR-r/f, release lock.
} /* ExpireT2Timer */
VOID ExpireTiTimer( PTP_LINK Link )
/*++
Routine Description:
This routine is called when a link's Ti timer expires. Ti is the inactivity timer, and serves as a keep-alive on a link basis, to periodically perform some protocol exchange with the remote connection partner that will implicitly reveal whether the link is still active or not. This implementation simply uses a checkpoint sequence, but some other protocols may choose to add protocol, including sending a NetBIOS SESSION_ALIVE frame. If a checkpoint sequence is already in progress, then we do nothing.
This timer expiration routine is self-perpetuating; that is, it starts itself after finishing its tasks every time.
Arguments:
Link - Pointer to the TP_LINK object whose Ti timer has expired.
Return Value:
none.
--*/
{ IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireTiTimer: Entered.\n"); }
ACQUIRE_DPC_SPIN_LOCK (&Link->SpinLock);
if ((Link->State != LINK_STATE_ADM) && (Link->State != LINK_STATE_W_DISC_RSP) && (Link->SendState != SEND_STATE_CHECKPOINTING)) {
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpireTiTimer: Entered.\n"); NbfDumpLinkInfo (Link); }
if (Link->Provider->EasilyDisconnected && Link->NumberOfConnectors == 0) {
//
// On an easily disconnected network with only server connections,
// if there has been no activity in this timeout period then
// we trash the connection.
//
if (Link->PacketsReceived == Link->TiStartPacketsReceived) {
Link->State = LINK_STATE_ADM; NbfSendDm (Link, FALSE); // send DM/0, release lock
#if DBG
if (NbfDisconnectDebug) { NbfPrint0( "ExpireT1Timer calling NbfStopLink (no final received)\n" ); } #endif
NbfStopLink (Link);
// moving to ADM, remove reference
NbfDereferenceLinkSpecial("Expire T1 in W_FINAL mode", Link, LREF_NOT_ADM);
} else {
//
// There was traffic, restart the timer.
//
StartTi (Link); RELEASE_DPC_SPIN_LOCK (&Link->SpinLock);
}
} else {
#if 0
if ((Link->SendState == SEND_STATE_READY) && (Link->T1 == 0) && (!IsListEmpty (&Link->WackQ))) {
//
// If we think the link is idle but there are packets
// on the WackQ, the link is messed up, disconnect it.
//
NbfPrint1 ("NBF: Link %d hung at Ti expiration, recovering\n", Link); RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); NbfStopLink (Link);
} else { #endif
Link->SendState = SEND_STATE_CHECKPOINTING; Link->PacketsSent = 0; Link->PacketsResent = 0; Link->PacketsReceived = 0; NbfSendRr (Link, TRUE, TRUE); // send RR-c/p, StartT1, release lock.
#if 0
} #endif
}
} else {
Link->PacketsSent = 0; Link->PacketsResent = 0; Link->PacketsReceived = 0;
RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); #if DBG
if (Link->SendState == SEND_STATE_REJECTING) { NbfPrint0 ("ExpireTiTimer: link state == rejecting, shouldn't be\n"); } #endif
}
#if 0
//
// Startup the inactivity timer again.
//
if (Link->State != LINK_STATE_ADM) { StartTi (Link); } #endif
} /* ExpireTiTimer */
#if 0
VOID ExpirePurgeTimer( PDEVICE_CONTEXT DeviceContext )
/*++
Routine Description:
This routine is called when the device context's periodic adaptive window algorithm timer expires. The timer perpetuates itself on a regular basis.
Arguments:
DeviceContext - Pointer to the device context whose purge timer has expired.
Return Value:
none.
--*/
{ PTP_LINK Link; PLIST_ENTRY p;
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("ExpirePurgeTimer: Entered.\n"); }
//
// Scan through the link database on this device context and clear
// their worst window size limit. This will allow stuck links to
// grow their window again even though they encountered temporary
// congestion at the remote link station's adapter.
//
while (!IsListEmpty (&DeviceContext->PurgeList)) { p = RemoveHeadList (&DeviceContext->PurgeList); Link = CONTAINING_RECORD (p, TP_LINK, PurgeList); Link->WorstWindowSize = Link->MaxWindowSize; // maximum window possible.
}
//
// Restart purge timer.
//
DeviceContext->AdaptivePurge = DeviceContext->ShortAbsoluteTime + TIMER_PURGE_TICKS;
} /* ExpirePurgeTimer */ #endif
VOID ScanShortTimersDpc( 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:
DeferredContext - Pointer to our DEVICE_CONTEXT object.
Return Value:
none.
--*/
{ PLIST_ENTRY p, nextp; PDEVICE_CONTEXT DeviceContext; PTP_LINK Link; PTP_CONNECTION Connection; BOOLEAN RestartTimer = FALSE; LARGE_INTEGER CurrentTick; LARGE_INTEGER TickDifference; ULONG TickDelta;
Dpc, SystemArgument1, SystemArgument2; // prevent compiler warnings
ENTER_NBF;
DeviceContext = DeferredContext;
IF_NBFDBG (NBF_DEBUG_TIMERDPC) { NbfPrint0 ("ScanShortTimersDpc: Entered.\n"); }
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// This prevents anybody from starting the timer while we
// are in this routine (the main reason for this is that it
// makes it easier to determine whether we should restart
// it at the end of this routine).
//
DeviceContext->ProcessingShortTimer = TRUE;
//
// Advance the up-counter used to mark time in SHORT_TIMER_DELTA units. If we
// advance it all the way to 0xf0000000, then reset it to 0x10000000.
// We also run all the lists, decreasing all counters by 0xe0000000.
//
KeQueryTickCount (&CurrentTick);
TickDifference.QuadPart = CurrentTick.QuadPart - (DeviceContext->ShortTimerStart).QuadPart;
TickDelta = TickDifference.LowPart / NbfShortTimerDeltaTicks; if (TickDelta == 0) { TickDelta = 1; }
DeviceContext->ShortAbsoluteTime += TickDelta;
if (DeviceContext->ShortAbsoluteTime >= 0xf0000000) {
ULONG Timeout;
DeviceContext->ShortAbsoluteTime -= 0xe0000000;
p = DeviceContext->ShortList.Flink; while (p != &DeviceContext->ShortList) {
Link = CONTAINING_RECORD (p, TP_LINK, ShortList);
Timeout = Link->T1; if (Timeout) { Link->T1 = Timeout - 0xe0000000; }
Timeout = Link->T2; if (Timeout) { Link->T2 = Timeout - 0xe0000000; }
p = p->Flink; }
}
//
// now, as the timers are started, links are added to the end of the
// respective queue for that timer. since we know the additions are
// done in an orderly fashion and are sequential, we must only traverse
// a particular timer list to the first entry that is greater than our
// timer. That entry and all further entries will not need service.
// When a timer is cancelled, we remove the link from the list. With all
// of this fooling around, we wind up only visiting those links that are
// actually in danger of timing out, minimizing time in this routine.
//
// T1 timers first; this is the link-level response expected timer, and is
// the shortest one.
// T2 timers. This is the iframe response expected timer, and is typically
// about 300 ms.
p = DeviceContext->ShortList.Flink; while (p != &DeviceContext->ShortList) {
Link = CONTAINING_RECORD (p, TP_LINK, ShortList);
ASSERT (Link->OnShortList);
//
// To avoid problems with the refcount being 0, don't
// do this if we are in ADM.
//
if (Link->State != LINK_STATE_ADM) {
if (Link->T1 && (DeviceContext->ShortAbsoluteTime > Link->T1)) {
Link->T1 = 0; RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
ExpireT1Timer (Link); // no spinlocks held
INCREMENT_COUNTER (DeviceContext, ResponseTimerExpirations);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
}
if (Link->T2 && (DeviceContext->ShortAbsoluteTime > Link->T2)) {
Link->T2 = 0; RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
ExpireT2Timer (Link); // no spinlocks held
INCREMENT_COUNTER (DeviceContext, AckTimerExpirations);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
}
}
if (!Link->OnShortList) {
//
// The link has been taken out of the list while
// we were processing it. In this (rare) case we
// stop processing the whole list, we'll get it
// next time.
//
#if DBG
DbgPrint ("NBF: Stop processing ShortList, %lx removed\n", Link); #endif
break;
}
nextp = p->Flink;
if ((Link->T1 == 0) && (Link->T2 == 0)) { Link->OnShortList = FALSE; RemoveEntryList(p);
//
// Do another check; that way if someone slipped in between
// the check of Link->Tx and the OnShortList = FALSE and
// therefore exited without inserting, we'll catch that here.
//
if ((Link->T1 != 0) || (Link->T2 != 0)) { InsertTailList(&DeviceContext->ShortList, &Link->ShortList); Link->OnShortList = TRUE; }
}
p = nextp;
}
//
// If the list is empty note that, otherwise ShortListActive
// remains TRUE.
//
if (IsListEmpty (&DeviceContext->ShortList)) { DeviceContext->a.i.ShortListActive = FALSE; }
//
// NOTE: DeviceContext->TimerSpinLock is held here.
//
//
// Connection Data Ack timers. This queue is used to indicate
// that a piggyback ack is pending for this connection. We walk
// the queue, for each element we check if the connection has
// been on the queue for NbfDeferredPasses times through
// here. If so, we take it off and send an ack. Note that
// we have to be very careful how we walk the queue, since
// it may be changing while this is running.
//
// NOTE: There is no expiration time for connections on this
// queue; it "expires" every time ScanShortTimersDpc runs.
//
for (p = DeviceContext->DataAckQueue.Flink; p != &DeviceContext->DataAckQueue; p = p->Flink) {
Connection = CONTAINING_RECORD (p, TP_CONNECTION, DataAckLinkage);
//
// Skip this connection if it is not queued or it is
// too recent to matter. We may skip incorrectly if
// the connection is just being queued, but that is
// OK, we will get it next time.
//
if (((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_ACK) == 0) && ((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_NOT_Q) == 0)) { continue; }
TickDifference.QuadPart = CurrentTick.QuadPart - (Connection->ConnectStartTime).QuadPart;
if ((TickDifference.HighPart == 0) && (TickDifference.LowPart <= NbfTwentyMillisecondsTicks)) { continue; }
NbfReferenceConnection ("ScanShortTimersDpc", Connection, CREF_DATA_ACK_QUEUE);
DeviceContext->DataAckQueueChanged = FALSE;
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// Check the correct connection flag, to ensure that a
// send has not just taken him off the queue.
//
ACQUIRE_DPC_SPIN_LOCK (Connection->LinkSpinLock);
if (((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_ACK) != 0) && ((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_NOT_Q) == 0)) {
//
// Yes, we were waiting to piggyback an ack, but no send
// has come along. Turn off the flags and send an ack.
//
// We have to ensure we nest the spin lock acquisition
// correctly.
//
Connection->DeferredFlags &= ~CONNECTION_FLAGS_DEFERRED_ACK;
RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock);
INCREMENT_COUNTER (DeviceContext, PiggybackAckTimeouts);
#if DBG
if (NbfDebugPiggybackAcks) { NbfPrint0("T"); } #endif
NbfSendDataAck (Connection);
} else {
RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock);
}
NbfDereferenceConnection ("ScanShortTimersDpc", Connection, CREF_DATA_ACK_QUEUE);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// If the list has changed, then we need to stop processing
// since p->Flink is not valid.
//
if (DeviceContext->DataAckQueueChanged) { break; }
}
if (IsListEmpty (&DeviceContext->DataAckQueue)) { DeviceContext->a.i.DataAckQueueActive = FALSE; }
#if 0
//
// NOTE: This is currently disabled, it may be reenabled
// at some point - adamba 9/1/92
//
// If the adaptive purge timer has expired, then run the purge
// algorithm on all affected links.
//
if (DeviceContext->ShortAbsoluteTime > DeviceContext->AdaptivePurge) { DeviceContext->AdaptivePurge = DeviceContext->ShortAbsoluteTime + TIMER_PURGE_TICKS; ExpirePurgeTimer (DeviceContext); } #endif
//
// deferred processing. We will handle all link structure additions and
// deletions here; we must be the exclusive user of the link tree to do
// this. We verify that we are by examining the semaphore that tells us
// how many readers of the tree are curretly processing it. If there are
// any readers, we simply increment our "deferred processing locked out"
// counter and do something else. If we defer too many times, we simply
// bugcheck, as something is wrong somewhere in the system.
//
if (!IsListEmpty (&DeviceContext->LinkDeferred)) { RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// now do additions or deletions if we can.
//
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->LinkSpinLock); ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
while (!IsListEmpty (&DeviceContext->LinkDeferred)) { p = RemoveHeadList (&DeviceContext->LinkDeferred); DeviceContext->DeferredNotSatisfied = 0;
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// now do an addition or deletion if we can.
//
Link = CONTAINING_RECORD (p, TP_LINK, DeferredList);
IF_NBFDBG (NBF_DEBUG_TEARDOWN) { NbfPrint4 ("ScanShortTimersDPC: link off deferred queue %lx %lx %lx Flags: %lx \n", Link, DeviceContext->LinkDeferred.Flink, DeviceContext->LinkDeferred.Blink, Link->DeferredFlags); } Link->DeferredList.Flink = Link->DeferredList.Blink = &Link->DeferredList;
if ((Link->DeferredFlags & LINK_FLAGS_DEFERRED_MASK) == 0) { // Tried to do an operation we don't understand; whine.
IF_NBFDBG (NBF_DEBUG_TEARDOWN) { NbfPrint2 ("ScanTimerDPC: Attempting deferred operation on nothing! \nScanTimerDPC: Link: %lx, DeviceContext->DeferredQueue: %lx\n", Link, &DeviceContext->LinkDeferred); DbgBreakPoint (); } InitializeListHead (&DeviceContext->LinkDeferred); // We could have a hosed deferred operations queue here;
// take some time to figure out if it is ok.
}
if ((Link->DeferredFlags & LINK_FLAGS_DEFERRED_ADD) != 0) {
Link->DeferredFlags &= ~LINK_FLAGS_DEFERRED_ADD;
if ((Link->DeferredFlags & LINK_FLAGS_DEFERRED_DELETE) != 0) {
//
// It is being added and deleted; just destroy it.
//
Link->DeferredFlags &= ~LINK_FLAGS_DEFERRED_DELETE; NbfDestroyLink (Link);
IF_NBFDBG (NBF_DEBUG_TEARDOWN) { NbfPrint1 ("ScanTimerDPC: deferred processing: Add AND Delete link: %lx\n",Link); }
} else {
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); NbfAddLinkToTree (DeviceContext, Link); RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); IF_NBFDBG (NBF_DEBUG_TEARDOWN) { NbfPrint1 ("ScanTimerDPC: deferred processing: Added link to tree: %lx\n",Link); }
}
} else if ((Link->DeferredFlags & LINK_FLAGS_DEFERRED_DELETE) != 0) { Link->DeferredFlags &= ~LINK_FLAGS_DEFERRED_DELETE; NbfRemoveLinkFromTree (DeviceContext, Link); NbfDestroyLink (Link);
IF_NBFDBG (NBF_DEBUG_TEARDOWN) { NbfPrint1 ("ScanTimerDPC: deferred processing: returning link %lx to LinkPool.\n", Link); }
}
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); }
InitializeListHead (&DeviceContext->LinkDeferred);
DeviceContext->a.i.LinkDeferredActive = FALSE;
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); RELEASE_DPC_SPIN_LOCK (&DeviceContext->LinkSpinLock);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
}
//
// Update the real counters from the temp ones.
//
ADD_TO_LARGE_INTEGER( &DeviceContext->Statistics.DataFrameBytesSent, DeviceContext->TempIFrameBytesSent); DeviceContext->Statistics.DataFramesSent += DeviceContext->TempIFramesSent;
DeviceContext->TempIFrameBytesSent = 0; DeviceContext->TempIFramesSent = 0;
ADD_TO_LARGE_INTEGER( &DeviceContext->Statistics.DataFrameBytesReceived, DeviceContext->TempIFrameBytesReceived); DeviceContext->Statistics.DataFramesReceived += DeviceContext->TempIFramesReceived;
DeviceContext->TempIFrameBytesReceived = 0; DeviceContext->TempIFramesReceived = 0;
//
// Determine if we have to restart the timer.
//
DeviceContext->ProcessingShortTimer = FALSE;
if (DeviceContext->a.AnyActive && (DeviceContext->State != DEVICECONTEXT_STATE_STOPPING)) {
RestartTimer = TRUE;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
if (RestartTimer) {
//
// 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.
//
KeQueryTickCount(&DeviceContext->ShortTimerStart); START_TIMER(DeviceContext, SHORT_TIMER, &DeviceContext->ShortSystemTimer, DueTimeDelta, &DeviceContext->ShortTimerSystemDpc); } else {
#if DBG
if (NbfDebugShortTimer) { DbgPrint("x"); } #endif
NbfDereferenceDeviceContext ("Don't restart short timer", DeviceContext, DCREF_SCAN_TIMER);
}
LEAVE_TIMER(DeviceContext, SHORT_TIMER);
LEAVE_NBF; return;
} /* ScanShortTimersDpc */
VOID ScanLongTimersDpc( 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 long timers have expired, and if they have, to execute their expiration routines.
Arguments:
DeferredContext - Pointer to our DEVICE_CONTEXT object.
Return Value:
none.
--*/
{ LARGE_INTEGER DueTime; PLIST_ENTRY p, nextp; PDEVICE_CONTEXT DeviceContext; PTP_LINK Link; PTP_CONNECTION Connection;
Dpc, SystemArgument1, SystemArgument2; // prevent compiler warnings
ENTER_NBF;
DeviceContext = DeferredContext;
IF_NBFDBG (NBF_DEBUG_TIMERDPC) { NbfPrint0 ("ScanLongTimersDpc: Entered.\n"); } //
// Advance the up-counter used to mark time in LONG_TIMER_DELTA units.If we
// advance it all the way to 0xf0000000, then reset it to 0x10000000.
// We also run all the lists, decreasing all counters by 0xe0000000.
//
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
if (++DeviceContext->LongAbsoluteTime == 0xf0000000) {
ULONG Timeout;
DeviceContext->LongAbsoluteTime = 0x10000000;
p = DeviceContext->LongList.Flink; while (p != &DeviceContext->LongList) {
Link = CONTAINING_RECORD (p, TP_LINK, LongList);
Timeout = Link->Ti; if (Timeout) { Link->Ti = Timeout - 0xe0000000; }
p = p->Flink; }
}
//
// now, as the timers are started, links are added to the end of the
// respective queue for that timer. since we know the additions are
// done in an orderly fashion and are sequential, we must only traverse
// a particular timer list to the first entry that is greater than our
// timer. That entry and all further entries will not need service.
// When a timer is cancelled, we remove the link from the list. With all
// of this fooling around, we wind up only visiting those links that are
// actually in danger of timing out, minimizing time in this routine.
//
//
// Ti timers. This is the inactivity timer for the link, used when no
// activity has occurred on the link in some time. We only check this
// every four expirations of the timer since the granularity is usually
// in the 30 second range.
// NOTE: DeviceContext->TimerSpinLock is held here.
//
if ((DeviceContext->LongAbsoluteTime % 4) == 0) {
p = DeviceContext->LongList.Flink; while (p != &DeviceContext->LongList) {
Link = CONTAINING_RECORD (p, TP_LINK, LongList);
ASSERT (Link->OnLongList);
//
// To avoid problems with the refcount being 0, don't
// do this if we are in ADM.
//
#if DBG
if (Link->SendState == SEND_STATE_REJECTING) { NbfPrint0 ("Timer: link state == rejecting, shouldn't be\n"); } #endif
if (Link->State != LINK_STATE_ADM) {
if (Link->Ti && (DeviceContext->LongAbsoluteTime > Link->Ti)) {
Link->Ti = 0; RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
ExpireTiTimer (Link); // no spinlocks held
++DeviceContext->TiExpirations;
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
}
}
if (!Link->OnLongList) {
//
// The link has been taken out of the list while
// we were processing it. In this (rare) case we
// stop processing the whole list, we'll get it
// next time.
//
#if DBG
DbgPrint ("NBF: Stop processing LongList, %lx removed\n", Link); #endif
break;
}
nextp = p->Flink;
if (Link->Ti == 0) {
Link->OnLongList = FALSE; RemoveEntryList(p);
if (Link->Ti != 0) { InsertTailList(&DeviceContext->LongList, &Link->LongList); Link->OnLongList = TRUE; }
}
p = nextp;
}
}
//
// Now scan the data ack queue, looking for connections with
// no acks queued that we can get rid of.
//
// Note: The timer spinlock is held here.
//
p = DeviceContext->DataAckQueue.Flink;
while (p != &DeviceContext->DataAckQueue && !DeviceContext->DataAckQueueChanged) {
Connection = CONTAINING_RECORD (DeviceContext->DataAckQueue.Flink, TP_CONNECTION, DataAckLinkage);
if ((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_ACK) != 0) { p = p->Flink; continue; }
NbfReferenceConnection ("ScanShortTimersDpc", Connection, CREF_DATA_ACK_QUEUE);
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
ACQUIRE_DPC_SPIN_LOCK (Connection->LinkSpinLock); ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// Have to check again, because the connection might
// just have been stopped.
//
if (Connection->OnDataAckQueue) { Connection->OnDataAckQueue = FALSE;
RemoveEntryList (&Connection->DataAckLinkage);
if ((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_ACK) != 0) { InsertTailList (&DeviceContext->DataAckQueue, &Connection->DataAckLinkage); Connection->OnDataAckQueue = TRUE; }
DeviceContext->DataAckQueueChanged = TRUE;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock); RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock);
NbfDereferenceConnection ("ScanShortTimersDpc", Connection, CREF_DATA_ACK_QUEUE);
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// Since we have changed the list, we can't tell if p->Flink
// is valid, so break. The effect is that we gradually peel
// connections off the queue.
//
break;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->TimerSpinLock);
//
// See if we got any multicast traffic last time.
//
if (DeviceContext->MulticastPacketCount == 0) {
++DeviceContext->LongTimeoutsWithoutMulticast;
if (DeviceContext->EasilyDisconnected && (DeviceContext->LongTimeoutsWithoutMulticast > 5)) {
PLIST_ENTRY p; PTP_ADDRESS address;
//
// We have had five timeouts in a row with no
// traffic, mark all the addresses as needing
// reregistration next time a connect is
// done on them.
//
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->SpinLock);
for (p = DeviceContext->AddressDatabase.Flink; p != &DeviceContext->AddressDatabase; p = p->Flink) {
address = CONTAINING_RECORD (p, TP_ADDRESS, Linkage); address->Flags |= ADDRESS_FLAGS_NEED_REREGISTER;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->SpinLock);
DeviceContext->LongTimeoutsWithoutMulticast = 0;
}
} else {
DeviceContext->LongTimeoutsWithoutMulticast = 0;
}
DeviceContext->MulticastPacketCount = 0;
//
// Every thirty seconds, check for stalled connections
//
++DeviceContext->StalledConnectionCount;
if (DeviceContext->StalledConnectionCount == (USHORT)((30 * SECONDS) / LONG_TIMER_DELTA)) {
DeviceContext->StalledConnectionCount = 0; StopStalledConnections (DeviceContext);
}
//
// Scan for any uncompleted receive IRPs, this may happen if
// the cable is pulled and we don't get any more ReceiveComplete
// indications.
NbfReceiveComplete((NDIS_HANDLE)DeviceContext);
//
// 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 (DeviceContext->State != DEVICECONTEXT_STATE_STOPPING) { DueTime.HighPart = -1; DueTime.LowPart = (ULONG)-(LONG_TIMER_DELTA); // delta time to next click.
START_TIMER(DeviceContext, LONG_TIMER, &DeviceContext->LongSystemTimer, DueTime, &DeviceContext->LongTimerSystemDpc); } else { NbfDereferenceDeviceContext ("Don't restart long timer", DeviceContext, DCREF_SCAN_TIMER); }
LEAVE_TIMER(DeviceContext, LONG_TIMER); LEAVE_NBF; return;
} /* ScanLongTimersDpc */
VOID StopStalledConnections( IN PDEVICE_CONTEXT DeviceContext )
/*++
Routine Description:
This routine is called from ScanLongTimersDpc every 30 seconds. It checks for connections that have not made any progress in their sends in the last two minutes, and stops them.
Arguments:
DeviceContext - The device context to check.
Return Value:
none.
--*/
{
PTP_ADDRESS Address, PrevAddress; PTP_CONNECTION Connection, StalledConnection; PLIST_ENTRY p, q;
//
// If we have crossed a thirty-second interval, then
// check each address for connections that have not
// made any progress in two minutes.
//
PrevAddress = NULL;
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->SpinLock);
for (p = DeviceContext->AddressDatabase.Flink; p != &DeviceContext->AddressDatabase; p = p->Flink) {
Address = CONTAINING_RECORD ( p, TP_ADDRESS, Linkage);
if ((Address->Flags & ADDRESS_FLAGS_STOPPING) != 0) { continue; }
//
// By referencing the address, we ensure that it will stay
// in the AddressDatabase, this its Flink will stay valid.
//
NbfReferenceAddress("checking for dead connections", Address, AREF_TIMER_SCAN);
RELEASE_DPC_SPIN_LOCK (&DeviceContext->SpinLock);
if (PrevAddress) { NbfDereferenceAddress ("done checking", PrevAddress, AREF_TIMER_SCAN); }
//
// Scan this addresses connection database for connections
// that have not made progress in the last two minutes; we
// kill the first one we find.
//
StalledConnection = NULL;
ACQUIRE_DPC_SPIN_LOCK (&Address->SpinLock);
for (q = Address->ConnectionDatabase.Flink; q != &Address->ConnectionDatabase; q = q->Flink) {
Connection = CONTAINING_RECORD (q, TP_CONNECTION, AddressList);
ACQUIRE_DPC_C_SPIN_LOCK (&Connection->SpinLock);
if (!IsListEmpty (&Connection->SendQueue)) {
//
// If there is a connection on the queue...
//
if (Connection->StallBytesSent == Connection->sp.MessageBytesSent) {
//
// ...and it has not made any progress...
//
if (Connection->StallCount >= 4) {
//
// .. four times in a row, the connection is dead.
//
if (!StalledConnection) { StalledConnection = Connection; NbfReferenceConnection ("stalled", Connection, CREF_STALLED); } #if DBG
DbgPrint ("NBF: Found connection %lx [%d for %d] stalled on %lx\n", Connection, Connection->StallBytesSent, Connection->StallCount, Address); #endif
} else {
//
// If it is stuck, increment the count.
//
++Connection->StallCount;
}
} else {
Connection->StallBytesSent = Connection->sp.MessageBytesSent;
}
}
RELEASE_DPC_C_SPIN_LOCK (&Connection->SpinLock);
}
RELEASE_DPC_SPIN_LOCK (&Address->SpinLock);
if (StalledConnection) {
PTP_LINK Link = StalledConnection->Link;
#if DBG
DbgPrint("NBF: Stopping stalled connection %lx, link %lx\n", StalledConnection, Link); #endif
FailSend (StalledConnection, STATUS_IO_TIMEOUT, TRUE); // fail the send
ACQUIRE_DPC_SPIN_LOCK (&Link->SpinLock); if (Link->State == LINK_STATE_READY) { CancelT1Timeout (Link); Link->State = LINK_STATE_W_DISC_RSP; Link->SendState = SEND_STATE_DOWN; Link->ReceiveState = RECEIVE_STATE_DOWN; Link->SendRetries = (UCHAR)Link->LlcRetries; RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); NbfStopLink (Link); StartT1 (Link, Link->HeaderLength + sizeof(DLC_S_FRAME)); // retransmit timer.
NbfSendDisc (Link, TRUE); // send DISC-c/p.
} else { RELEASE_DPC_SPIN_LOCK (&Link->SpinLock); NbfStopLink (Link); }
NbfDereferenceConnection ("stalled", StalledConnection, CREF_STALLED);
}
ACQUIRE_DPC_SPIN_LOCK (&DeviceContext->SpinLock);
PrevAddress = Address;
}
RELEASE_DPC_SPIN_LOCK (&DeviceContext->SpinLock);
if (PrevAddress) { NbfDereferenceAddress ("done checking", PrevAddress, AREF_TIMER_SCAN); }
} /* StopStalledConnections */
VOID NbfStartShortTimer( IN PDEVICE_CONTEXT DeviceContext )
/*++
Routine Description:
This routine starts the short timer, if it is not already running.
Arguments:
DeviceContext - Pointer to our device context.
Return Value:
none.
--*/
{
//
// Start the timer unless it the DPC is already running (in
// which case it will restart the timer itself if needed),
// or some list is active (meaning the timer is already
// queued up).
//
// We use a trick to check all four active lists at the
// same time, but this depends on some alignment and
// size assumptions.
//
ASSERT (sizeof(ULONG) >= 3 * sizeof(BOOLEAN)); ASSERT ((PVOID)&DeviceContext->a.AnyActive == (PVOID)&DeviceContext->a.i.ShortListActive);
StartTimer++;
if ((!DeviceContext->ProcessingShortTimer) && (!(DeviceContext->a.AnyActive))) {
#if DBG
if (NbfDebugShortTimer) { DbgPrint("X"); } #endif
NbfReferenceDeviceContext ("Start short timer", DeviceContext, DCREF_SCAN_TIMER);
KeQueryTickCount(&DeviceContext->ShortTimerStart); StartTimerSet++; START_TIMER(DeviceContext, SHORT_TIMER, &DeviceContext->ShortSystemTimer, DueTimeDelta, &DeviceContext->ShortTimerSystemDpc); }
} /* NbfStartShortTimer */
VOID NbfInitializeTimerSystem( IN PDEVICE_CONTEXT DeviceContext )
/*++
Routine Description:
This routine initializes the lightweight timer system for the transport provider.
Arguments:
DeviceContext - Pointer to our device context.
Return Value:
none.
--*/
{ LARGE_INTEGER DueTime;
IF_NBFDBG (NBF_DEBUG_TIMER) { NbfPrint0 ("NbfInitializeTimerSystem: Entered.\n"); }
ASSERT(TIMERS_INITIALIZED(DeviceContext)); //
// Set these up.
//
NbfTickIncrement = KeQueryTimeIncrement();
if (NbfTickIncrement > (20 * MILLISECONDS)) { NbfTwentyMillisecondsTicks = 1; } else { NbfTwentyMillisecondsTicks = (20 * MILLISECONDS) / NbfTickIncrement; }
if (NbfTickIncrement > (SHORT_TIMER_DELTA)) { NbfShortTimerDeltaTicks = 1; } else { NbfShortTimerDeltaTicks = (SHORT_TIMER_DELTA) / NbfTickIncrement; }
//
// MaximumIntervalTicks represents 60 seconds, unless the value
// when shifted out by the accuracy required is too big.
//
if ((((ULONG)0xffffffff) >> (DLC_TIMER_ACCURACY+2)) > ((60 * SECONDS) / NbfTickIncrement)) { NbfMaximumIntervalTicks = (60 * SECONDS) / NbfTickIncrement; } else { NbfMaximumIntervalTicks = ((ULONG)0xffffffff) >> (DLC_TIMER_ACCURACY + 2); }
//
// The AbsoluteTime cycles between 0x10000000 and 0xf0000000.
//
DeviceContext->ShortAbsoluteTime = 0x10000000; // initialize our timer click up-counter.
DeviceContext->LongAbsoluteTime = 0x10000000; // initialize our timer click up-counter.
DeviceContext->AdaptivePurge = TIMER_PURGE_TICKS;
DeviceContext->MulticastPacketCount = 0; DeviceContext->LongTimeoutsWithoutMulticast = 0;
KeInitializeDpc( &DeviceContext->ShortTimerSystemDpc, ScanShortTimersDpc, DeviceContext);
KeInitializeDpc( &DeviceContext->LongTimerSystemDpc, ScanLongTimersDpc, DeviceContext);
KeInitializeTimer (&DeviceContext->ShortSystemTimer);
KeInitializeTimer (&DeviceContext->LongSystemTimer);
DueTime.HighPart = -1; DueTime.LowPart = (ULONG)-(LONG_TIMER_DELTA);
ENABLE_TIMERS(DeviceContext);
//
// One reference for the long timer.
//
NbfReferenceDeviceContext ("Long timer active", DeviceContext, DCREF_SCAN_TIMER);
START_TIMER(DeviceContext, LONG_TIMER, &DeviceContext->LongSystemTimer, DueTime, &DeviceContext->LongTimerSystemDpc);
} /* NbfInitializeTimerSystem */
VOID NbfStopTimerSystem( IN PDEVICE_CONTEXT DeviceContext )
/*++
Routine Description:
This routine stops the lightweight timer system for the transport provider.
Arguments:
DeviceContext - Pointer to our device context.
Return Value:
none.
--*/
{
//
// If timers are currently executing timer code, then this
// function blocks until they are done executing. Also
// no new timers will be allowed to be queued after this.
//
{ if (KeCancelTimer(&DeviceContext->LongSystemTimer)) { LEAVE_TIMER(DeviceContext, LONG_TIMER); NbfDereferenceDeviceContext ("Long timer cancelled", DeviceContext, DCREF_SCAN_TIMER); }
if (KeCancelTimer(&DeviceContext->ShortSystemTimer)) { LEAVE_TIMER(DeviceContext, SHORT_TIMER); NbfDereferenceDeviceContext ("Short timer cancelled", DeviceContext, DCREF_SCAN_TIMER); } }
DISABLE_TIMERS(DeviceContext); }
|