/*++ Copyright (c) 1991 Microsoft Corporation Module Name: llcsmsb.c Abstract: The module implements the subroutines used by a IEEE 802.2 compatible state machine. To understand the procedure of this module, you should read Chapters 11 and 12 in IBM Token-Ring Architecture Reference. The procedures in this module can be called only when SendSpinLock is set. Contents: SaveStatusChangeEvent ResendPackets UpdateVa UpdateVaChkpt AdjustWw SendAck QueueCommandCompletion (DynamicWindowAlgorithm) Author: Antti Saarenheimo (o-anttis) 23-MAY-1991 Revision History: --*/ #include // // private prototypes // VOID DynamicWindowAlgorithm( IN OUT PDATA_LINK pLink // data link station strcuture ); // // functions // VOID SaveStatusChangeEvent( IN PDATA_LINK pLink, IN PUCHAR puchLlcHdr, IN BOOLEAN boolResponse ) /*++ Routine Description: Procedure saves Status Change event of the link to the event queue. to be indicated later to upper protocol. Arguments: pLink - LLC link station object puchLlcHdr - the received LLC header boolResponse - flag set if the received frame was response Return Value: None --*/ { UINT Event; PEVENT_PACKET pEvent; PVOID hClientHandle; PADAPTER_CONTEXT pAdapterContext = pLink->Gen.pAdapterContext; ASSUME_IRQL(DISPATCH_LEVEL); // // Set the ethernet header length (== type) the same as // in the current receive frame, that was either the first SABME or // any response to it, that opened the link connection. // if ((pLink->DlcStatus.StatusCode & (CONFIRM_CONNECT | LLC_INDICATE_CONNECT_REQUEST)) && pAdapterContext->RcvLanHeaderLength != pLink->cbLanHeaderLength && pLink->Gen.pLlcBinding->EthernetType == LLC_ETHERNET_TYPE_AUTO) { pLink->cbLanHeaderLength = (UCHAR)pLink->Gen.pAdapterContext->RcvLanHeaderLength; } // // Handle first the disconnect/connect complete // if (pLink->DlcStatus.StatusCode & (CONFIRM_CONNECT | CONFIRM_DISCONNECT | CONFIRM_CONNECT_FAILED)) { // // We cannot indicate any events to non-existing stations // if (pLink->Gen.hClientHandle != NULL) { if (pLink->DlcStatus.StatusCode & CONFIRM_DISCONNECT) { QueueCommandCompletion((PLLC_OBJECT)pLink, LLC_DISCONNECT_COMPLETION, STATUS_SUCCESS ); } if (pLink->DlcStatus.StatusCode & (CONFIRM_CONNECT | CONFIRM_CONNECT_FAILED)) { UINT Status; if (pLink->DlcStatus.StatusCode & CONFIRM_CONNECT) { // // Set the T1 timeout for the first checkpointing state. // This value will be changed when we have got the response // to the first poll, but the initial value is big to // be able to run DLC over a WAN connection // pLink->AverageResponseTime = 100; // 100 * 40 = 4 seconds pLink->Flags |= DLC_FIRST_POLL; InitializeLinkTimers(pLink); Status = STATUS_SUCCESS; } else { Status = DLC_STATUS_CONNECT_FAILED; } QueueCommandCompletion((PLLC_OBJECT)pLink, LLC_CONNECT_COMPLETION, Status ); } } pLink->DlcStatus.StatusCode &= ~(CONFIRM_CONNECT | CONFIRM_DISCONNECT | CONFIRM_CONNECT_FAILED); } if (pLink->DlcStatus.StatusCode != 0) { if (pLink->DlcStatus.StatusCode & INDICATE_FRMR_SENT) { #if LLC_DBG PrintLastInputs("FRMR SENT!\n", pLink); #endif pLink->DlcStatus.FrmrData.Command = puchLlcHdr[2]; pLink->DlcStatus.FrmrData.Ctrl = puchLlcHdr[3]; if ((pLink->DlcStatus.FrmrData.Command & LLC_S_U_TYPE_MASK) == LLC_U_TYPE) { pLink->DlcStatus.FrmrData.Ctrl = 0; } pLink->DlcStatus.FrmrData.Vs = pLink->Vs; pLink->DlcStatus.FrmrData.Vr = pLink->Vr | boolResponse; } else if (pLink->DlcStatus.StatusCode & INDICATE_FRMR_RECEIVED) { #if LLC_DBG PrintLastInputs("FRMR RECEIVED!\n", pLink); DbgBreakPoint(); #endif LlcMemCpy(&pLink->DlcStatus.FrmrData, &puchLlcHdr[3], sizeof(LLC_FRMR_INFORMATION) ); } // // A remote connect request may have created a link station // in link driver. The upper protocol must be able to separate // sap handle from the data link // if (pLink->Gen.hClientHandle == NULL) { // // Indicate the event on the sap, because the upper protocol // has not yet any link station create for this link, because // it has been created remotely. // hClientHandle = pLink->pSap->Gen.hClientHandle, Event = LLC_STATUS_CHANGE_ON_SAP; } else { // // Indicate the event on the link station // hClientHandle = pLink->Gen.hClientHandle, Event = LLC_STATUS_CHANGE; } // // The indications of the received SABMEs must be queued, // but all other events are indicated directy to // the upper protocol, because those indications must never // be lost because of an out of memory condition. // if (pLink->DlcStatus.StatusCode & INDICATE_CONNECT_REQUEST) { pEvent = ALLOCATE_PACKET_LLC_PKT(pAdapterContext->hPacketPool); if (pEvent != NULL) { LlcInsertTailList(&pAdapterContext->QueueEvents, pEvent); pEvent->pBinding = pLink->Gen.pLlcBinding; pEvent->hClientHandle = hClientHandle; pEvent->Event = Event; pEvent->pEventInformation = (PVOID)&pLink->DlcStatus; // // RLF 11/18/92 // // INDICATE_CONNECT_REQUEST is generated when we receive a // SABME for a station in the DISCONNECTED state. However, // we need to generate either INDICATE_CONNECT_REQUEST (0x0400) // or INDICATE_RESET (0x0800) depending on whether the SABME // created the link station or whether it was created by a // DLC.OPEN.STATION at this end. pLink->RemoteOpen is TRUE if // the link was created due to receipt of the SABME // This routine is only called by RunStateMachine and // INDICATE_CONNECT_REQUEST is never combined with any other // status codes // //pEvent->SecondaryInfo = pLink->DlcStatus.StatusCode; pEvent->SecondaryInfo = pLink->RemoteOpen ? INDICATE_CONNECT_REQUEST : INDICATE_RESET; } } else { // // We must do this with a locked SendSpinLock, because // otherwise somebody might delete the link, while // we are still using it. // THIS IS ACTAULLY QUITE DIRTY (callback with locked // spinlocks), BUT WE CANNOT USE THE PACKET POOLS WHEN // WE INDICATE AN EVENT, THAT MUST NOT BE LOST! // pLink->Gen.pLlcBinding->pfEventIndication( pLink->Gen.pLlcBinding->hClientContext, hClientHandle, Event, (PVOID)&pLink->DlcStatus, pLink->DlcStatus.StatusCode ); } // // We must cancel all queued transmit commands, if the link // is lost, disconnected or reset. // if (pLink->DlcStatus.StatusCode & (INDICATE_LINK_LOST | INDICATE_DM_DISC_RECEIVED | INDICATE_FRMR_RECEIVED | INDICATE_FRMR_SENT | INDICATE_RESET)) { CancelTransmitCommands((PLLC_OBJECT)pLink, DLC_STATUS_LINK_NOT_TRANSMITTING); } // // Reset the status code! // pLink->DlcStatus.StatusCode = 0; } } VOID ResendPackets( IN OUT PDATA_LINK pLink // data link strcuture ) /*++ Routine Description: Function initializes the send process to resend the rejected packets and resets the adaptive working window variables. The operations defined in IBM state machine are: Vs=Nr, Ww=1, Ia_Ct=0, but this also resorts the packet queue. Arguments: pLink - LLC link station object Return Value: None --*/ { PADAPTER_CONTEXT pAdapterContext = pLink->Gen.pAdapterContext; // // Complete all frames, that were acknowledged by the reject (if any) // if (pLink->Nr != pLink->Va) { DynamicWindowAlgorithm(pLink); } if ( (pLink->Vs != pLink->VsMax) && (((pLink->Vs < pLink->VsMax) && (pLink->Nr >= pLink->Vs) && (pLink->Nr <= pLink->VsMax) ) || (!((pLink->Vs > pLink->VsMax) && (pLink->Nr > pLink->VsMax) && (pLink->Nr < pLink->Vs)) ) ) ) { return; } // // Move all rejected packets from the queue sent packets back // to the send queue. We have already completed all acknowledged // packets => we can take the packet from the tail and put them // to the head of the send queue. // We can trust, that the reject window is correct, because // Nr has been checked before the state machine was called. // (note: the counters are a modulo 256, but we use bytes). // for (;pLink->Vs != pLink->Nr; pLink->Vs -= 2) { PLLC_PACKET pPacket; if (!IsListEmpty(&pLink->SentQueue) ){ pLink->Statistics.I_FrameTransmissionErrors++; if (pLink->Statistics.I_FrameTransmissionErrors == 0x80) { pLink->DlcStatus.StatusCode |= INDICATE_DLC_COUNTER_OVERFLOW; } pPacket = (PLLC_PACKET)LlcRemoveTailList(&pLink->SentQueue); LlcInsertHeadList(&pLink->SendQueue.ListHead, pPacket); } } // // The procedure starts the send process only if it has been // enabled by the state machine. Only StartSendProcessLocked // may start the process, if it has been locked by // StopSendProcess // StartSendProcess(pAdapterContext, pLink); // // Reset the current window (Vs=Nr, Ww=1, Ia_Ct=0) // pLink->Ww = 2; pLink->Ia_Ct = 0; } VOID UpdateVa( IN OUT PDATA_LINK pLink // data link station strcuture ) /*++ Routine Description: Function updates Va (last valid Nr received) and makes also some other actions needed in the normal receive operations. Arguments: pLink - LLC link station object Return Value: None --*/ { // // Reset the initilization state variable // pLink->Vi = 0; // // Update the receive state variable Va (the last valid received // frame), but update some Ww variables before that. // if (pLink->Nr != pLink->Va) { DynamicWindowAlgorithm(pLink); // // T1 reply timer must be running as far as there are // out (or sent) any unacknowledged frames. // Ti timer must be stopped whenever T1 is running and vice versa // if (pLink->Nr != pLink->Vs) { // // There still are some unacknowledged frames, // start or restart the reply timer. // StartTimer(&pLink->T1); // reply timer StopTimer(&pLink->Ti); } else { // // All sent frames have been acknowledged, // => We may stop the reply timer. // StopTimer(&pLink->T1); // reply timer StartTimer(&pLink->Ti); } // // Reset the I- frame retry counter whenever we do // any kind of progress // pLink->Is_Ct = pLink->N2; } } VOID UpdateVaChkpt( IN OUT PDATA_LINK pLink // data link station strcuture ) /*++ Routine Description: Function updates Va (last valid Nr received) and makes also some other actions needed in the check point receive operations. Arguments: pLink - LLC link station object Return Value: None --*/ { UCHAR OrginalWw = pLink->Ww; // // Reset the initilization state variable // pLink->Vi = 0; // // Update the receive state variable Va (the last valid received // frame), but update the acknowledged frames counter before that. // That counter is used by the Adaptive window algorithm. // if (pLink->Nr != pLink->Va) { // // Run adaptive transmit window (TW/T1) algorithm. // if (pLink->Ww == pLink->TW) { // // Update the counters of adaptive transmit window algorithm, // We need (LLC_MAX_T1_TO_I_RATIO/2) successful transmit using // the full window size, before we again try increase the // maximum transmit window size. // pLink->FullWindowTransmits += pLink->Ww; if ((UINT)pLink->FullWindowTransmits >= LLC_MAX_T1_TO_I_RATIO) { pLink->FullWindowTransmits = 2; if (pLink->TW < pLink->MaxOut) { pLink->TW += 2; } } } DynamicWindowAlgorithm(pLink); // // Reset the I- frame and Poll retry counters whenever // we do any kind of progress with the acknowledged I-frames. // pLink->P_Ct = pLink->N2; pLink->Is_Ct = pLink->N2; } // // Stop the reply timer, if we are not waiting // anything else from the other side. // if (pLink->Nr != pLink->Vs) { // // There still are some unacknowledged frames, // start or restart the reply timer. // StartTimer(&pLink->T1); // reply timer StopTimer(&pLink->Ti); } else { // // All sent frames have been acknowledged, // => We may stop the reply timer. // StopTimer(&pLink->T1); // reply timer StartTimer(&pLink->Ti); } // // Bugfix in 3-3-1992, Vp (!= pLInk->Vp) seems to be wrong here, // because in many cases it is not set when a checkpointing state is // entered. In the chekpointing state Vs=Vp, because the // send process is always stopped in our implementation, // when a checkpointing state is entered. // Why do we actually need the Vp? A: It's needed to prevent // forever looping between chekpointing and open states. // if (pLink->Nr != pLink->Vs) { // // We use a very simple adaptive transmit window (TW/T1) algorithm: // // TW is set the same as the last successful Working window // size (Ww), whenever T1 has been lost. We increase TW after // a constant number of full window transmits. // // The more complicated TW/T1 algorithms usually work worse // produce more code and decrease the performace, but this algorithm // is quite vulnerable to unreliable media (=> TW=1, a lot of T1 // timeouts). A better algorithm could also try to increase TW, // if the ratio of T1 timeouts to transferred I- frames increases // when the TW is decreased. I will leave this matter to my // successors (AS 19-MAR-1992). // // Another problem in this algorithm is the too fast increasing // of big working windows (Ww). In that case Ww is incremented by // more than 1 => the other side may lose I-frames before I-c1. // This is not very serious situation, we reset working window to 1 // and start it again. // // // Update the transmit window always after a T1 timeout. // if (pLink->P_Ct < pLink->N2) { // // Reset the maximum transmit window size whenever // we have lost the last I-C1 (poll). // In the first time the current windows size // becomes the maximum windows size (we never hit // the maximum tranmit window size, if the other // size have receive problems). This algorithm assumes, // that we have otherwise very reliable network. // if (OrginalWw > 2) { pLink->TW = (UCHAR)(OrginalWw - 2); } else if (pLink->TW > 2) { // // We may have already reset Ww because of REJ of a // I-c0 before the actual poll, that was lost also. // In that case we don't have any idea of the actual // window size, but we decrement TW in any case. // pLink->TW -= 2; } pLink->FullWindowTransmits = 2; } ResendPackets(pLink); } } VOID AdjustWw( IN OUT PDATA_LINK pLink // data link strcuture ) /*++ Routine Description: Procedure adjust the working window of a data link station. Arguments: pLink - LLC link station object Nr - NR of the received LLC LDPU Return Value: None --*/ { // // Update the receive state variable Va (the last valid received // frame), but update some Ww variables before that. // if (pLink->Nr != pLink->Va) { DynamicWindowAlgorithm(pLink); // // Reset the I- frame and Poll retry counters whenever // we do any kind of progress with the acknowledged I-frames. // pLink->P_Ct = pLink->N2; pLink->Is_Ct = pLink->N2; } } VOID SendAck( IN OUT PDATA_LINK pLink ) /*++ Routine Description: Procedure sends the ack, if the received unacknowledged frames counter expires and stops the acknowledge delay timer (T2). Otherwise it start (or restarts) the acknowledge delay timer. Arguments: pLink - LLC link station object Return Value: Returns the token of the next sent command frame. --*/ { pLink->Ir_Ct--; if (pLink->Ir_Ct == 0) { pLink->Ir_Ct = pLink->N3; // MaxIn StopTimer(&pLink->T2); // // Send RR-r0 to acknowledge the response // SendLlcFrame(pLink, (UCHAR)DLC_RR_TOKEN); } else { StartTimer(&pLink->T2); } } VOID QueueCommandCompletion( IN PLLC_OBJECT pLlcObject, IN UINT CompletionCode, IN UINT Status ) /*++ Routine Description: The function queues a command completion (if there was an allcoated packet in the completion queue). Arguments: pLlcObject - LLC object (link, sap or direct) CompletionCode - command completion code returned to upper protocol Status - returned status Return Value: None - --*/ { PLLC_PACKET *ppPacket; // // Search the command from the completion list. // (use the "address of address" scanning to take the // searched element from the middle of one way linked list) // ppPacket = &pLlcObject->Gen.pCompletionPackets; while (*ppPacket != NULL && (*ppPacket)->Data.Completion.CompletedCommand != CompletionCode) { ppPacket = &(*ppPacket)->pNext; } if (*ppPacket != NULL) { PLLC_PACKET pPacket = *ppPacket; *ppPacket = pPacket->pNext; pPacket->pBinding = pLlcObject->Gen.pLlcBinding; pPacket->Data.Completion.Status = Status; pPacket->Data.Completion.CompletedCommand = CompletionCode; pPacket->Data.Completion.hClientHandle = pLlcObject->Gen.hClientHandle; #if LLC_DBG pPacket->pNext = NULL; #endif LlcInsertTailList(&pLlcObject->Gen.pAdapterContext->QueueCommands, pPacket); } } VOID DynamicWindowAlgorithm( IN OUT PDATA_LINK pLink // data link station strcuture ) /*++ Routine Description: The function runs the dynamic window algorithm and updates the dynamic window size of used by the link's send process. This routine also completes the acknowledged transmissions. Arguments: pLink - LLC link station object Return Value: None --*/ { PADAPTER_CONTEXT pAdapterContext; // // Run Dynamic Window algorithm of IBM TR Architecture Ref: // // if (Working window less that the maximum window) // then // The Acknowledged frame count += The acknowledged frames // // if (The Acknowledged frame count > // packets to be aknowledged before next increment) // then // Increment the working window // endif // endif // if (pLink->Ww < pLink->TW) { // // The Acknowledged frame count += The acknowledged frames // (handle the wrap around of UCHAR counters) // if (pLink->Va > pLink->Nr) { pLink->Ia_Ct += (256 + pLink->Nr) - pLink->Va; } else { pLink->Ia_Ct += pLink->Nr - pLink->Va; } // // if (The Acknowledged frame count // > packets to be aknowledged before next increment) // then // Increment the working window // endif // if (pLink->Ia_Ct > pLink->Nw) { USHORT usWw; usWw = (USHORT)(pLink->Ww + (pLink->Ia_Ct / pLink->Nw) * 2); pLink->Ia_Ct = pLink->Ia_Ct % pLink->Nw; if (usWw > pLink->TW) { pLink->Ww = pLink->TW; } else { pLink->Ww = (UCHAR)usWw; } } } // // Complete all acknowledged I-frame packets // pAdapterContext = pLink->Gen.pAdapterContext; for (; pLink->Va != pLink->Nr; pLink->Va += 2) { PLLC_PACKET pPacket; MY_ASSERT(!IsListEmpty(&pLink->SentQueue)); if (IsListEmpty(&pLink->SentQueue)) { return; } pPacket = LlcRemoveHeadList(&pLink->SentQueue); pPacket->Data.Completion.Status = STATUS_SUCCESS; pPacket->Data.Completion.CompletedCommand = LLC_SEND_COMPLETION; pPacket->Data.Completion.hClientHandle = pPacket->Data.Xmit.pLlcObject->Gen.hClientHandle; // // We use extra status bits to indicate, when I- packet has been both // completed by NDIS and acknowledged by the other side of the link // connection. An I- packet can be queued to the completion queue by // the second quy (either state machine or SendCompletion handler) // only when the first guy has set completed its work. // An I packet could be acknowledged by the other side before // its completion is indicated by NDIS. Dlc Driver deallocates // the packet immediately, when Llc driver completes the acknowledged // packet => possible data corruption (if packet is reused before // NDIS has completed it). This is probably not possible in a // single processor NT- system, but very possible in multiprocessor // NT or systems without a single level DPC queue (like OS/2 and DOS). // pPacket->CompletionType &= ~LLC_I_PACKET_UNACKNOWLEDGED; if (pPacket->CompletionType == LLC_I_PACKET_COMPLETE) { LlcInsertTailList(&pAdapterContext->QueueCommands, pPacket); } // // Increment counter, when the I- frame has // succesfully received and acknowledged by the other side. // We must also send status changes indication, when // the USHORT counter hits the half way. // pLink->Statistics.I_FramesTransmitted++; if (pLink->Statistics.I_FramesTransmitted == 0x8000) { pLink->DlcStatus.StatusCode |= INDICATE_DLC_COUNTER_OVERFLOW; } pLink->pSap->Statistics.FramesTransmitted++; } }