/*++ Copyright (c) 1989-1993 Microsoft Corporation Module Name: iframes.c Abstract: This module contains routines called to handle i-frames received from the NDIS driver. Most of these routines are called at receive indication time. Environment: Kernel mode, DISPATCH_LEVEL. Revision History: --*/ #include "st.h" #if 27 ULONG StNoisyReceives = 0; ULONG StRcvLoc = 0; ULONG StRcvs[10]; #endif NTSTATUS StProcessIIndicate( IN PTP_CONNECTION Connection, IN PST_HEADER StHeader, IN UINT StIndicatedLength, IN UINT StTotalLength, IN NDIS_HANDLE ReceiveContext, IN BOOLEAN Last ) /*++ Routine Description: This routine processes a received I frame at indication time. It will do all necessary verification processing of the frame and pass those frames that are valid on to the proper handling routines. Arguments: Connection - The connection that the data is destined for. StHeader - A pointer to the start of the ST header in the packet. StIndicatedLength - The length of the packet indicated, starting at StHeader. StTotalLength - The total length of the packet, starting at StHeader. ReceiveContext - A magic value for NDIS that indicates which packet we're talking about, used for calling TransferData Last - TRUE if this is the last packet in a send. Return Value: STATUS_SUCCESS if we've consumed the packet, but STATUS_MORE_PROCESSING_REQUIRED if we did so and also activated a receive; this tells the caller not to remove the connection refcount. --*/ { KIRQL oldirql; NTSTATUS status, tmpstatus; KIRQL cancelirql; PDEVICE_CONTEXT deviceContext; NDIS_STATUS ndisStatus; PNDIS_PACKET ndisPacket; PSINGLE_LIST_ENTRY linkage; PIRP irp; PIO_STACK_LOCATION irpSp; PNDIS_BUFFER ndisBuffer; ULONG destBytes; ULONG bufferChainLength; ULONG indicateBytesTransferred; ULONG ndisBytesTransferred; PUCHAR DataHeader; ULONG DataTotalLength; ULONG DataIndicatedLength; UINT BytesToTransfer; ULONG bytesIndicated; PRECEIVE_PACKET_TAG receiveTag; PTP_ADDRESS address; PTP_ADDRESS_FILE addressFile; PMDL SavedCurrentMdl; ULONG SavedCurrentByteOffset; LARGE_INTEGER time; ULONG DumpData[2]; BOOLEAN CancelSpinLockHeld = FALSE; #if 27 if (StNoisyReceives) { DbgPrint ("Indicate %d, Total %d\n", StIndicatedLength, StTotalLength); } if (StTotalLength > 1000) { StRcvs[StRcvLoc] = StTotalLength; StRcvLoc = (StRcvLoc + 1) % 10; } #endif // // copy this packet into our receive buffer. // deviceContext = Connection->Provider; addressFile = Connection->AddressFile; address = addressFile->Address; ACQUIRE_SPIN_LOCK (&Connection->SpinLock, &oldirql); // // If we have a previous receive that is pending // completion, then we need to ignore this frame. // This may be common on MP. // if (Connection->Flags2 & CONNECTION_FLAGS2_RC_PENDING) { Connection->IndicationInProgress = FALSE; RELEASE_SPIN_LOCK (&Connection->SpinLock, oldirql); return STATUS_SUCCESS; } DataHeader = (PUCHAR)StHeader + sizeof(ST_HEADER); DataTotalLength = StTotalLength - sizeof(ST_HEADER); DataIndicatedLength = StIndicatedLength - sizeof(ST_HEADER); // // Initialize this to zero, in case we do not indicate or // the client does not fill it in. // indicateBytesTransferred = 0; if (!(Connection->Flags & CONNECTION_FLAGS_ACTIVE_RECEIVE)) { // // check first to see if there is a receive available. If there is, // use it before doing an indication. // if (Connection->ReceiveQueue.Flink != &Connection->ReceiveQueue) { // // Found a receive, so make it the active one and // cycle around again. // Connection->Flags |= CONNECTION_FLAGS_ACTIVE_RECEIVE; Connection->MessageBytesReceived = 0; Connection->MessageBytesAcked = 0; Connection->CurrentReceiveRequest = CONTAINING_RECORD (Connection->ReceiveQueue.Flink, TP_REQUEST, Linkage); Connection->CurrentReceiveMdl = Connection->CurrentReceiveRequest->Buffer2; Connection->ReceiveLength = Connection->CurrentReceiveRequest->Buffer2Length; Connection->ReceiveByteOffset = 0; status = STATUS_SUCCESS; goto NormalReceive; } // // A receive is not active. Post a receive event. // if (!addressFile->RegisteredReceiveHandler) { // // There is no receive posted to the Connection, and // no event handler. Set the RECEIVE_WAKEUP bit, so that when a // receive does become available, it will restart the // current send. Also send a NoReceive to tell the other // guy he needs to resynch. // Connection->IndicationInProgress = FALSE; return STATUS_SUCCESS; } if ((Connection->Flags & CONNECTION_FLAGS_READY) == 0) { Connection->IndicationInProgress = FALSE; RELEASE_SPIN_LOCK (&Connection->SpinLock, oldirql); return STATUS_SUCCESS; } RELEASE_SPIN_LOCK (&Connection->SpinLock, oldirql); // // Indicate to the user. For BytesAvailable we // always use DataTotalLength; for BytesIndicated we use // MIN (DataIndicatedLength, DataTotalLength). // // To clarify BytesIndicated, on an Ethernet packet // which is padded DataTotalLength will be shorter; on an // Ethernet packet which is not padded and which is // completely indicated, the two will be equal; and // on a long Ethernet packet DataIndicatedLength // will be shorter. // bytesIndicated = DataIndicatedLength; if (DataTotalLength < bytesIndicated) { bytesIndicated = DataTotalLength; } status = (*addressFile->ReceiveHandler)( addressFile->ReceiveHandlerContext, Connection->Context, deviceContext->MacInfo.CopyLookahead ? TDI_RECEIVE_COPY_LOOKAHEAD : 0, // ReceiveFlags bytesIndicated, DataTotalLength, // BytesAvailable &indicateBytesTransferred, DataHeader, &irp); if (status == STATUS_SUCCESS) { // // The client has accepted some or all of the indicated data in // the event handler. Update MessageBytesReceived variable in // the Connection. // Connection->MessageBytesReceived += indicateBytesTransferred; Connection->IndicationInProgress = FALSE; return STATUS_SUCCESS; } else if (status == STATUS_DATA_NOT_ACCEPTED) { // // Either there is no event handler installed (the default // handler returns this code) or the event handler is not // able to process the received data at this time. If there // is a TdiReceive request outstanding on this Connection's // ReceiveQueue, then we may use it to receive this data. // If there is no request outstanding, then we must initiate // flow control at the transport level. // ACQUIRE_SPIN_LOCK (&Connection->SpinLock, &oldirql); if (Connection->ReceiveQueue.Flink == &Connection->ReceiveQueue) { // // There is no receive posted to the Connection, and // the event handler didn't want to accept the incoming // data. // Connection->IndicationInProgress = FALSE; return STATUS_SUCCESS; } else { // // Found a receive, so make it the active one. This will cause // an NdisTransferData below, so we don't dereference the // Connection here. // Connection->Flags |= CONNECTION_FLAGS_ACTIVE_RECEIVE; Connection->MessageBytesReceived = 0; Connection->MessageBytesAcked = 0; Connection->CurrentReceiveRequest = CONTAINING_RECORD (Connection->ReceiveQueue.Flink, TP_REQUEST, Linkage); Connection->CurrentReceiveMdl = Connection->CurrentReceiveRequest->Buffer2; Connection->ReceiveLength = Connection->CurrentReceiveRequest->Buffer2Length; Connection->ReceiveByteOffset = 0; } } else if (status == STATUS_MORE_PROCESSING_REQUIRED) { PTP_REQUEST SpecialIrpRequest; ULONG SpecialIrpLength; // // The client's event handler has returned an IRP in the // form of a TdiReceive that is to be associated with this // data. The request will be installed at the front of the // ReceiveQueue, and then made the active receive request. // This request will be used to accept the incoming data, which // will happen below. // // // Queueing a receive of any kind causes a Connection reference; // that's what we've just done here, so make the Connection stick // around. We create a request to keep a packets outstanding ref // count for the current IRP; we queue this on the connection's // receive queue so we can treat it like a normal receive. If // we can't get a request to describe this irp, we can't keep it // around hoping for better later; we simple fail it with // insufficient resources. Note this is only likely to happen if // we've completely run out of transport memory. // irp->IoStatus.Information = 0; // byte transfer count. irp->IoStatus.Status = STATUS_PENDING; irpSp = IoGetCurrentIrpStackLocation (irp); ASSERT (irpSp->FileObject->FsContext == Connection); SpecialIrpLength = ((PTDI_REQUEST_KERNEL_RECEIVE)&irpSp->Parameters)->ReceiveLength; // // The normal path, for longer receives. // time.HighPart = 0; time.LowPart = 0; status = StCreateRequest ( irp, Connection, REQUEST_FLAGS_CONNECTION | REQUEST_FLAGS_SEND_RCV, irp->MdlAddress, ((PTDI_REQUEST_KERNEL_RECEIVE )&irpSp->Parameters)->ReceiveLength, time, &SpecialIrpRequest); if (!NT_SUCCESS (status)) { ACQUIRE_SPIN_LOCK (&Connection->SpinLock, &oldirql); Connection->ReceiveByteOffset = 0; Connection->Flags |= CONNECTION_FLAGS_RECEIVE_WAKEUP; RELEASE_SPIN_LOCK (&Connection->SpinLock, oldirql); Connection->IndicationInProgress = FALSE; irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; IoCompleteRequest (irp, IO_NETWORK_INCREMENT); return STATUS_INSUFFICIENT_RESOURCES; } // // If the Connection is stopping, abort this request. // IoAcquireCancelSpinLock(&cancelirql); ACQUIRE_SPIN_LOCK (&Connection->SpinLock, &oldirql); if ((Connection->Flags & CONNECTION_FLAGS_STOPPING) != 0) { Connection->IndicationInProgress = FALSE; RELEASE_SPIN_LOCK (&Connection->SpinLock, oldirql); IoReleaseCancelSpinLock(cancelirql); StCompleteRequest ( SpecialIrpRequest, Connection->Status, 0); return STATUS_SUCCESS; // we have consumed the packet } // // Insert the request on the head of the connection's // receive queue, so it can be handled like a normal // receive. // InsertHeadList (&Connection->ReceiveQueue, &SpecialIrpRequest->Linkage); Connection->Flags |= CONNECTION_FLAGS_ACTIVE_RECEIVE; Connection->ReceiveLength = ((PTDI_REQUEST_KERNEL_RECEIVE )&irpSp->Parameters)->ReceiveLength; Connection->MessageBytesReceived = indicateBytesTransferred; Connection->MessageBytesAcked = 0; Connection->CurrentReceiveRequest = SpecialIrpRequest; Connection->CurrentReceiveMdl = irp->MdlAddress; Connection->ReceiveByteOffset = 0; Connection->CurrentReceiveRequest->Owner = ConnectionType; // // If this IRP has been cancelled, then call the // cancel routine. // if (irp->Cancel) { Connection->Flags |= CONNECTION_FLAGS_RECEIVE_WAKEUP; Connection->IndicationInProgress = FALSE; RELEASE_SPIN_LOCK (&Connection->SpinLock,oldirql); irp->CancelIrql = cancelirql; StCancelReceive((PDEVICE_OBJECT)deviceContext, irp); return STATUS_SUCCESS; } else { irp->CancelRoutine = StCancelReceive; status = STATUS_MORE_PROCESSING_REQUIRED; // // Make a note so we know to release the cancel // spinlock below. // CancelSpinLockHeld = TRUE; } } else { // // An unknown return code has been returned by the // client's event handler. This is a client programming // error. Because this can only occur when kernel-mode // clients have been coded incorrectly, we should beat // him with a stick. We have to do SOMETHING, or this // Connection will HANG. // Connection->IndicationInProgress = FALSE; return STATUS_SUCCESS; } } else { // // A receive is active, set the status to show // that so far. // status = STATUS_SUCCESS; } NormalReceive:; // // NOTE: The connection spinlock is held here. // // We should only get through here if a receive is active // and we have not released the lock since checking or // making one active. // ASSERT(Connection->Flags & CONNECTION_FLAGS_ACTIVE_RECEIVE); // // The status should be SUCCESS (we found an active receive) // or MORE_PROCESSING_REQUIRED (we made a new receive active). // ASSERT ((status == STATUS_SUCCESS) || (status == STATUS_MORE_PROCESSING_REQUIRED)); destBytes = Connection->ReceiveLength - Connection->MessageBytesReceived; StReferenceRequest ("Transfer Data", Connection->CurrentReceiveRequest); RELEASE_SPIN_LOCK (&Connection->SpinLock, oldirql); if (CancelSpinLockHeld) { IoReleaseCancelSpinLock (cancelirql); } // // get a packet for the coming transfer // linkage = ExInterlockedPopEntryList( &deviceContext->ReceivePacketPool, &deviceContext->Interlock); if (linkage != NULL) { ndisPacket = CONTAINING_RECORD( linkage, NDIS_PACKET, ProtocolReserved[0] ); } else { (VOID)InterlockedIncrement((PLONG)&deviceContext->ReceivePacketExhausted); StDereferenceRequest ("No receive packet", Connection->CurrentReceiveRequest); // We could not get a receive packet. // Connection->IndicationInProgress = FALSE; return status; } // // Initialize the receive packet. // receiveTag = (PRECEIVE_PACKET_TAG)(ndisPacket->ProtocolReserved); receiveTag->PacketType = TYPE_AT_INDICATE; receiveTag->Connection = Connection; receiveTag->TransferDataPended = TRUE; // // Determine how much data remains to be transferred. // BytesToTransfer = DataTotalLength - indicateBytesTransferred; ASSERT (BytesToTransfer >= 0); if (destBytes < BytesToTransfer) { // // If the data overflows the current receive, then make a // note that we should complete the receive at the end of // transfer data, but with EOR false. // receiveTag->EndOfMessage = FALSE; receiveTag->CompleteReceive = TRUE; BytesToTransfer = destBytes; } else if (destBytes == BytesToTransfer) { // // If the data just fills the current receive, then complete // the receive; EOR depends on whether this is a DOL or not. // receiveTag->EndOfMessage = Last; receiveTag->CompleteReceive = TRUE; } else { // // Complete the receive if this is a DOL. // receiveTag->EndOfMessage = Last; receiveTag->CompleteReceive = Last; } // // if we've got zero bytes left, avoid the TransferData below and // just deliver. // if (BytesToTransfer <= 0) { Connection->IndicationInProgress = FALSE; receiveTag->NdisStatus = NDIS_STATUS_SUCCESS; receiveTag->TransferDataPended = FALSE; StTransferDataComplete ( deviceContext, ndisPacket, NDIS_STATUS_SUCCESS, 0); return status; } // // describe the right part of the user buffer to NDIS. If we can't get // the mdl for the packet, drop it. Bump the request reference count // so that we know we need to hold open receives until the NDIS transfer // data requests complete. // SavedCurrentMdl = Connection->CurrentReceiveMdl; SavedCurrentByteOffset = Connection->ReceiveByteOffset; if ((Connection->ReceiveByteOffset == 0) && (receiveTag->CompleteReceive)) { // // If we are transferring into the beginning of // the current MDL, and we will be completing the // receive after the transfer, then we don't need to // copy it. // ndisBuffer = (PNDIS_BUFFER)Connection->CurrentReceiveMdl; bufferChainLength = BytesToTransfer; Connection->CurrentReceiveMdl = NULL; Connection->ReceiveByteOffset = 0; receiveTag->AllocatedNdisBuffer = FALSE; tmpstatus = STATUS_SUCCESS; } else { tmpstatus = BuildBufferChainFromMdlChain ( deviceContext->NdisBufferPoolHandle, Connection->CurrentReceiveMdl, Connection->ReceiveByteOffset, BytesToTransfer, &ndisBuffer, &Connection->CurrentReceiveMdl, &Connection->ReceiveByteOffset, &bufferChainLength); receiveTag->AllocatedNdisBuffer = TRUE; } if ((!NT_SUCCESS (tmpstatus)) || (bufferChainLength != BytesToTransfer)) { DumpData[0] = bufferChainLength; DumpData[1] = BytesToTransfer; StWriteGeneralErrorLog( deviceContext, EVENT_TRANSPORT_TRANSFER_DATA, 604, tmpstatus, NULL, 2, DumpData); StDereferenceRequest ("No MDL chain", Connection->CurrentReceiveRequest); // // Restore our old state. // Connection->CurrentReceiveMdl = SavedCurrentMdl; Connection->ReceiveByteOffset = SavedCurrentByteOffset; Connection->IndicationInProgress = FALSE; ExInterlockedPushEntryList( &deviceContext->ReceivePacketPool, (PSINGLE_LIST_ENTRY)&receiveTag->Linkage, &deviceContext->Interlock); return status; } NdisChainBufferAtFront (ndisPacket, ndisBuffer); // // set up async return status so we can tell when it has happened; // can never get return of NDIS_STATUS_PENDING in synch completion routine // for NdisTransferData, so we know it has completed when this status // changes // receiveTag->NdisStatus = NDIS_STATUS_PENDING; // // update the number of bytes received; OK to do this // unprotected since IndicationInProgress is still FALSE. // // Connection->MessageBytesReceived += BytesToTransfer; // // We have now updated all the connection counters (BUG, // assuming the TransferData will succeed) and this // packet's location in the request is secured, so we // can be reentered. // Connection->IndicationInProgress = FALSE; NdisTransferData ( &ndisStatus, deviceContext->NdisBindingHandle, ReceiveContext, sizeof (ST_HEADER) + indicateBytesTransferred, BytesToTransfer, ndisPacket, (PUINT)&ndisBytesTransferred); // // handle the various completion codes // switch (ndisStatus) { case NDIS_STATUS_SUCCESS: receiveTag->NdisStatus = NDIS_STATUS_SUCCESS; if (ndisBytesTransferred != BytesToTransfer) { // Did we get the entire packet? DumpData[0] = ndisBytesTransferred; DumpData[1] = BytesToTransfer; StWriteGeneralErrorLog( deviceContext, EVENT_TRANSPORT_TRANSFER_DATA, 604, ndisStatus, NULL, 2, DumpData); if (receiveTag->AllocatedNdisBuffer) { NdisUnchainBufferAtFront (ndisPacket, &ndisBuffer); while (ndisBuffer != NULL) { NdisFreeBuffer (ndisBuffer); NdisUnchainBufferAtFront (ndisPacket, &ndisBuffer); } } else { NdisReinitializePacket (ndisPacket); } ExInterlockedPushEntryList( &deviceContext->ReceivePacketPool, (PSINGLE_LIST_ENTRY)&receiveTag->Linkage, &deviceContext->Interlock); StDereferenceRequest ("Bad byte count", Connection->CurrentReceiveRequest); // // Restore our old state. // Connection->CurrentReceiveMdl = SavedCurrentMdl; Connection->ReceiveByteOffset = SavedCurrentByteOffset; Connection->MessageBytesReceived -= BytesToTransfer; return status; } // // deallocate the buffers and such that we've used if at indicate // receiveTag->TransferDataPended = FALSE; StTransferDataComplete ( deviceContext, ndisPacket, ndisStatus, BytesToTransfer); break; case NDIS_STATUS_PENDING: // waiting async complete from NdisTransferData // // Because TransferDataPended stays TRUE, this reference will // be removed in TransferDataComplete. It is OK to do this // now, even though TransferDataComplete may already have been // called, because we also hold the ProcessIIndicate reference // so there will be no "bounce". // StReferenceConnection ("TransferData pended", Connection); break; default: // // Something broke; certainly we'll never get NdisTransferData // asynch completion. // // BUGBUG: The driver should recover from this situation. // StWriteGeneralErrorLog( deviceContext, EVENT_TRANSPORT_TRANSFER_DATA, 604, ndisStatus, NULL, 0, NULL); if (receiveTag->AllocatedNdisBuffer) { NdisUnchainBufferAtFront (ndisPacket, &ndisBuffer); while (ndisBuffer != NULL) { NdisFreeBuffer (ndisBuffer); NdisUnchainBufferAtFront (ndisPacket, &ndisBuffer); } } else { NdisReinitializePacket (ndisPacket); } ExInterlockedPushEntryList( &deviceContext->ReceivePacketPool, (PSINGLE_LIST_ENTRY)&receiveTag->Linkage, &deviceContext->Interlock); StDereferenceRequest ("TransferData failed", Connection->CurrentReceiveRequest); // // Restore our old state. // Connection->CurrentReceiveMdl = SavedCurrentMdl; Connection->ReceiveByteOffset = SavedCurrentByteOffset; Connection->MessageBytesReceived -= BytesToTransfer; return status; } // switch ndisStatus return status; // which only means we've dealt with the packet } /* ProcessIIndicate */