diff options
Diffstat (limited to '')
-rw-r--r-- | private/ntos/tdi/nbf/timer.c | 2762 |
1 files changed, 2762 insertions, 0 deletions
diff --git a/private/ntos/tdi/nbf/timer.c b/private/ntos/tdi/nbf/timer.c new file mode 100644 index 000000000..0664f0aa6 --- /dev/null +++ b/private/ntos/tdi/nbf/timer.c @@ -0,0 +1,2762 @@ +/*++ + +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. + // + // BUGBUG: 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. + // + // BUGBUG: 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; + + IF_NBFDBG (NBF_DEBUG_TIMERDPC) { + NbfPrint0 ("ScanShortTimersDpc: Entered.\n"); + } + + DeviceContext = DeferredContext; + + 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; + // BUGBUG: 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 { + + NbfAddLinkToTree (DeviceContext, Link); + 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); + KeSetTimer ( + &DeviceContext->ShortSystemTimer, + DueTimeDelta, + &DeviceContext->ShortTimerSystemDpc); + + } else { + +#if DBG + if (NbfDebugShortTimer) { + DbgPrint("x"); + } +#endif + NbfDereferenceDeviceContext ("Don't restart short timer", DeviceContext, DCREF_SCAN_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; + + IF_NBFDBG (NBF_DEBUG_TIMERDPC) { + NbfPrint0 ("ScanLongTimersDpc: Entered.\n"); + } + + DeviceContext = DeferredContext; + + // + // 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. + KeSetTimer ( + &DeviceContext->LongSystemTimer, + DueTime, + &DeviceContext->LongTimerSystemDpc); + } else { + NbfDereferenceDeviceContext ("Don't restart long timer", DeviceContext, DCREF_SCAN_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++; + KeSetTimer ( + &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"); + } + + // + // 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); + + // + // One reference for the long timer. + // + + NbfReferenceDeviceContext ("Long timer active", DeviceContext, DCREF_SCAN_TIMER); + + KeSetTimer ( + &DeviceContext->LongSystemTimer, + DueTime, + &DeviceContext->LongTimerSystemDpc); + + DeviceContext->TimersInitialized = TRUE; + +} /* 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. + +--*/ + +{ + + // + // For now we ignore what happens if the timer is currently + // running when we do this. + // + + if (DeviceContext->TimersInitialized) { + + if (KeCancelTimer( + &DeviceContext->LongSystemTimer)) { + NbfDereferenceDeviceContext ("Long timer cancelled", DeviceContext, DCREF_SCAN_TIMER); + } + + if (KeCancelTimer( + &DeviceContext->ShortSystemTimer)) { + NbfDereferenceDeviceContext ("Short timer cancelled", DeviceContext, DCREF_SCAN_TIMER); + } + + } + +} |