/*++ Copyright (c) 1989, 1990, 1991 Microsoft Corporation Module Name: rcveng.c Abstract: This module contains code that implements the receive engine for the Jetbeui transport provider. This code is responsible for the following basic activities: 1. Transitioning a TdiReceive request from an inactive state on the connection's ReceiveQueue to the active state on that connection (ActivateReceive). 2. Advancing the status of the active receive request by copying 0 or more bytes of data from an incoming DATA_FIRST_MIDDLE or DATA_ONLY_LAST NBF frame. 3. Completing receive requests. Author: David Beaver (dbeaver) 1-July-1991 Environment: Kernel mode Revision History: --*/ #include "precomp.h" #pragma hdrstop VOID ActivateReceive( PTP_CONNECTION Connection ) /*++ Routine Description: This routine activates the next TdiReceive request on the specified connection object if there is no active request on that connection already. This allows the request to accept data on the connection. NOTE: THIS FUNCTION MUST BE CALLED AT DPC LEVEL. Arguments: Connection - Pointer to a TP_CONNECTION object. Return Value: none. --*/ { PIRP Irp; ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL); IF_NBFDBG (NBF_DEBUG_RCVENG) { NbfPrint0 (" ActivateReceive: Entered.\n"); } // // The ACTIVE_RECEIVE bitflag will be set on the connection if // the receive-fields in the CONNECTION object are valid. If // this flag is cleared, then we try to make the next TdiReceive // request in the ReceiveQueue the active request. // ACQUIRE_DPC_SPIN_LOCK (Connection->LinkSpinLock); if (!(Connection->Flags & CONNECTION_FLAGS_ACTIVE_RECEIVE)) { if (!IsListEmpty (&Connection->ReceiveQueue)) { // // Found a receive, so make it the active one. // Connection->Flags |= CONNECTION_FLAGS_ACTIVE_RECEIVE; Irp = CONTAINING_RECORD( Connection->ReceiveQueue.Flink, IRP, Tail.Overlay.ListEntry); Connection->MessageBytesReceived = 0; Connection->MessageBytesAcked = 0; Connection->MessageInitAccepted = 0; Connection->CurrentReceiveIrp = Irp; Connection->CurrentReceiveSynchronous = Connection->Provider->MacInfo.SingleReceive; Connection->CurrentReceiveMdl = Irp->MdlAddress; Connection->ReceiveLength = IRP_RECEIVE_LENGTH(IoGetCurrentIrpStackLocation(Irp)); Connection->ReceiveByteOffset = 0; } } RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock); IF_NBFDBG (NBF_DEBUG_RCVENG) { NbfPrint0 (" ActivateReceive: Exiting.\n"); } } /* ActivateReceive */ VOID AwakenReceive( PTP_CONNECTION Connection ) /*++ Routine Description: This routine is called to reactivate a sleeping connection with the RECEIVE_WAKEUP bitflag set because data arrived for which no receive was available. The caller has made a receive available at the connection, so here we activate the next receive, and send the appropriate protocol to restart the message at the first byte offset past the one received by the last receive. NOTE: THIS FUNCTION MUST BE CALLED AT DPC LEVEL. IT IS CALLED WITH CONNECTION->LINKSPINLOCK HELD. Arguments: Connection - Pointer to a TP_CONNECTION object. Return Value: none. --*/ { IF_NBFDBG (NBF_DEBUG_RCVENG) { NbfPrint0 (" AwakenReceive: Entered.\n"); } // // If the RECEIVE_WAKEUP bitflag is set, then awaken the connection. // if (Connection->Flags & CONNECTION_FLAGS_RECEIVE_WAKEUP) { if (Connection->ReceiveQueue.Flink != &Connection->ReceiveQueue) { Connection->Flags &= ~CONNECTION_FLAGS_RECEIVE_WAKEUP; // // Found a receive, so turn off the wakeup flag, activate // the next receive, and send the protocol. // // // Quick fix: So there is no window where a receive // is active but the bit is not on (otherwise we could // accept whatever data happens to show up in the // interim). // Connection->Flags |= CONNECTION_FLAGS_W_RESYNCH; NbfReferenceConnection ("temp AwakenReceive", Connection, CREF_BY_ID); // release lookup hold. RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock); ActivateReceive (Connection); // // What if this fails? The successful queueing // of a RCV_O should cause ActivateReceive to be called. // // NOTE: Send this after ActivateReceive, since that // is where the MessageBytesAcked/Received variables // are initialized. // NbfSendReceiveOutstanding (Connection); IF_NBFDBG (NBF_DEBUG_RCVENG) { NbfPrint0 (" AwakenReceive: Returned from NbfSendReceive.\n"); } NbfDereferenceConnection("temp AwakenReceive", Connection, CREF_BY_ID); return; } } RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock); } /* AwakenReceive */ VOID CompleteReceive( PTP_CONNECTION Connection, BOOLEAN EndOfMessage, IN ULONG BytesTransferred ) /*++ Routine Description: This routine is called by ProcessIncomingData when the current receive must be completed. Depending on whether the current frame being processed is a DATA_FIRST_MIDDLE or DATA_ONLY_LAST, and also whether all of the data was processed, the EndOfMessage flag will be set accordingly by the caller to indicate that a message boundary was received. NOTE: THIS FUNCTION MUST BE CALLED AT DPC LEVEL. Arguments: Connection - Pointer to a TP_CONNECTION object. EndOfMessage - BOOLEAN set to true if TDI_END_OF_RECORD should be reported. BytesTransferred - Number of bytes copied in this receive. Return Value: none. --*/ { PLIST_ENTRY p; PIRP Irp; ULONG BytesReceived; PIO_STACK_LOCATION IrpSp; IF_NBFDBG (NBF_DEBUG_RCVENG) { NbfPrint0 (" CompleteReceive: Entered.\n"); } if (Connection->SpecialReceiveIrp) { PIRP Irp = Connection->SpecialReceiveIrp; Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = BytesTransferred; ACQUIRE_DPC_SPIN_LOCK (Connection->LinkSpinLock); Connection->Flags |= CONNECTION_FLAGS_RC_PENDING; Connection->Flags &= ~CONNECTION_FLAGS_ACTIVE_RECEIVE; Connection->SpecialReceiveIrp = FALSE; ++Connection->ReceivedTsdus; ExInterlockedInsertHeadList( &Connection->Provider->IrpCompletionQueue, &Irp->Tail.Overlay.ListEntry, Connection->ProviderInterlock); // // NOTE: NbfAcknowledgeDataOnlyLast releases // the connection spinlock. // NbfAcknowledgeDataOnlyLast( Connection, Connection->MessageBytesReceived ); } else { KIRQL cancelIrql; if (EndOfMessage) { // // The messages has been completely received, ack it. // // We set DEFERRED_ACK and DEFERRED_NOT_Q here, which // will cause an ack to be piggybacked if any data is // sent during the call to CompleteReceive. If this // does not happen, then we will call AcknowledgeDataOnlyLast // which will will send a DATA ACK or queue a request for // a piggyback ack. We do this *after* calling CompleteReceive // so we know that we will complete the receive back to // the client before we ack the data, to prevent the // next receive from being sent before this one is // completed. // IoAcquireCancelSpinLock(&cancelIrql); ACQUIRE_DPC_SPIN_LOCK (Connection->LinkSpinLock); Connection->DeferredFlags |= (CONNECTION_FLAGS_DEFERRED_ACK | CONNECTION_FLAGS_DEFERRED_NOT_Q); Connection->Flags |= CONNECTION_FLAGS_RC_PENDING; } else { // // Send a receive outstanding (even though we don't // know that we have a receive) to get him to // reframe his send. Pre-2.0 clients require a // no receive before the receive outstanding. // // what if this fails (due to no send packets)? // if ((Connection->Flags & CONNECTION_FLAGS_VERSION2) == 0) { NbfSendNoReceive (Connection); } NbfSendReceiveOutstanding (Connection); // // If there is a receive posted, make it current and // send a receive outstanding. // // need general function for this, which sends NO_RECEIVE if appropriate. // ActivateReceive (Connection); IoAcquireCancelSpinLock(&cancelIrql); ACQUIRE_DPC_SPIN_LOCK (Connection->LinkSpinLock); } // // If we indicated to the client, adjust this down by the // amount of data taken, when it hits zero we can reindicate. // if (Connection->ReceiveBytesUnaccepted) { if (Connection->MessageBytesReceived >= Connection->ReceiveBytesUnaccepted) { Connection->ReceiveBytesUnaccepted = 0; } else { Connection->ReceiveBytesUnaccepted -= Connection->MessageBytesReceived; } } // // NOTE: The connection lock is held here. // if (IsListEmpty (&Connection->ReceiveQueue)) { ASSERT ((Connection->Flags2 & CONNECTION_FLAGS2_STOPPING) != 0); // // Release the cancel spinlock out of order. Since we were // already at DPC level when it was acquired, there is no // need to swap irqls. // ASSERT(cancelIrql == DISPATCH_LEVEL); IoReleaseCancelSpinLock(cancelIrql); } else { Connection->Flags &= ~CONNECTION_FLAGS_ACTIVE_RECEIVE; BytesReceived = Connection->MessageBytesReceived; // // Complete the TdiReceive request at the head of the // connection's ReceiveQueue. // IF_NBFDBG (NBF_DEBUG_RCVENG) { NbfPrint0 (" CompleteReceive: Normal IRP is present.\n"); } p = RemoveHeadList (&Connection->ReceiveQueue); Irp = CONTAINING_RECORD (p, IRP, Tail.Overlay.ListEntry); IoSetCancelRoutine(Irp, NULL); // // Release the cancel spinlock out of order. Since we were // already at DPC level when it was acquired, there is no // need to swap irqls. // ASSERT(cancelIrql == DISPATCH_LEVEL); IoReleaseCancelSpinLock(cancelIrql); // // If this request should generate no back traffic, then // disable piggyback acks for it. // IrpSp = IoGetCurrentIrpStackLocation(Irp); if (IRP_RECEIVE_FLAGS(IrpSp) & TDI_RECEIVE_NO_RESPONSE_EXP) { Connection->CurrentReceiveAckQueueable = FALSE; } #if DBG NbfCompletedReceives[NbfCompletedReceivesNext].Irp = Irp; NbfCompletedReceives[NbfCompletedReceivesNext].Request = NULL; NbfCompletedReceives[NbfCompletedReceivesNext].Status = EndOfMessage ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW; { ULONG i,j,k; PUCHAR va; PMDL mdl; mdl = Irp->MdlAddress; if (BytesReceived > TRACK_TDI_CAPTURE) { NbfCompletedReceives[NbfCompletedReceivesNext].Contents[0] = 0xFF; } else { NbfCompletedReceives[NbfCompletedReceivesNext].Contents[0] = (UCHAR)BytesReceived; } i = 1; while (iNext; } } NbfCompletedReceivesNext = (NbfCompletedReceivesNext++) % TRACK_TDI_LIMIT; #endif ++Connection->ReceivedTsdus; // // This can be called with locks held. // NbfCompleteReceiveIrp( Irp, EndOfMessage ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW, BytesReceived); } // // If NOT_Q is still set, that means that the deferred ack was // not satisfied by anything resulting from the call to // CompleteReceive, so we need to ack or queue an ack here. // if ((Connection->DeferredFlags & CONNECTION_FLAGS_DEFERRED_NOT_Q) != 0) { Connection->DeferredFlags &= ~(CONNECTION_FLAGS_DEFERRED_ACK | CONNECTION_FLAGS_DEFERRED_NOT_Q); // // NOTE: NbfAcknowledgeDataOnlyLast releases // the connection spinlock. // NbfAcknowledgeDataOnlyLast( Connection, Connection->MessageBytesReceived ); } else { RELEASE_DPC_SPIN_LOCK (Connection->LinkSpinLock); } } } /* CompleteReceive */ VOID NbfCancelReceive( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is called by the I/O system to cancel a receive. The receive is found on the connection's receive queue; if it is the current request it is cancelled and the connection goes into "cancelled receive" mode, otherwise it is cancelled silently. In "cancelled receive" mode the connection makes it appear to the remote the data is being received, but in fact it is not indicated to the transport or buffered on our end NOTE: This routine is called with the CancelSpinLock held and is responsible for releasing it. Arguments: DeviceObject - Pointer to the device object for this driver. Irp - Pointer to the request packet representing the I/O request. Return Value: none. --*/ { KIRQL oldirql; PIO_STACK_LOCATION IrpSp; PIRP ReceiveIrp; PTP_CONNECTION Connection; PLIST_ENTRY p; ULONG BytesReceived; BOOLEAN Found; UNREFERENCED_PARAMETER (DeviceObject); // // Get a pointer to the current stack location in the IRP. This is where // the function codes and parameters are stored. // IrpSp = IoGetCurrentIrpStackLocation (Irp); ASSERT ((IrpSp->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL) && (IrpSp->MinorFunction == TDI_RECEIVE)); Connection = IrpSp->FileObject->FsContext; // // Since this IRP is still in the cancellable state, we know // that the connection is still around (although it may be in // the process of being torn down). // // // See if this is the IRP for the current receive request. // ACQUIRE_SPIN_LOCK (Connection->LinkSpinLock, &oldirql); BytesReceived = Connection->MessageBytesReceived; p = Connection->ReceiveQueue.Flink; // // If there is a receive active and it is not a special // IRP, then see if this is it. // if (((Connection->Flags & CONNECTION_FLAGS_ACTIVE_RECEIVE) != 0) && (!Connection->SpecialReceiveIrp)) { ReceiveIrp = CONTAINING_RECORD (p, IRP, Tail.Overlay.ListEntry); if (ReceiveIrp == Irp) { // // yes, it is the active receive. Turn on the RCV_CANCELLED // bit instructing the connection to drop the rest of the // data received (until the DOL comes in). // Connection->Flags |= CONNECTION_FLAGS_RCV_CANCELLED; Connection->Flags &= ~CONNECTION_FLAGS_ACTIVE_RECEIVE; (VOID)RemoveHeadList (&Connection->ReceiveQueue); #if DBG NbfCompletedReceives[NbfCompletedReceivesNext].Irp = ReceiveIrp; NbfCompletedReceives[NbfCompletedReceivesNext].Request = NULL; NbfCompletedReceives[NbfCompletedReceivesNext].Status = STATUS_CANCELLED; { ULONG i,j,k; PUCHAR va; PMDL mdl; mdl = ReceiveIrp->MdlAddress; if (BytesReceived > TRACK_TDI_CAPTURE) { NbfCompletedReceives[NbfCompletedReceivesNext].Contents[0] = 0xFF; } else { NbfCompletedReceives[NbfCompletedReceivesNext].Contents[0] = (UCHAR)BytesReceived; } i = 1; while (iNext; } } NbfCompletedReceivesNext = (NbfCompletedReceivesNext++) % TRACK_TDI_LIMIT; #endif RELEASE_SPIN_LOCK (Connection->LinkSpinLock, oldirql); IoReleaseCancelSpinLock (Irp->CancelIrql); #if DBG DbgPrint("NBF: Canceled in-progress receive %lx on %lx\n", Irp, Connection); #endif // // The following dereference will complete the I/O, provided removes // the last reference on the request object. The I/O will complete // with the status and information stored in the Irp. Therefore, // we set those values here before the dereference. // NbfCompleteReceiveIrp (ReceiveIrp, STATUS_CANCELLED, 0); return; } } // // If we fall through to here, the IRP was not the active receive. // Scan through the list, looking for this IRP. // Found = FALSE; while (p != &Connection->ReceiveQueue) { ReceiveIrp = CONTAINING_RECORD (p, IRP, Tail.Overlay.ListEntry); if (ReceiveIrp == Irp) { // // Found it, remove it from the list here. // RemoveEntryList (p); Found = TRUE; #if DBG NbfCompletedReceives[NbfCompletedReceivesNext].Irp = ReceiveIrp; NbfCompletedReceives[NbfCompletedReceivesNext].Request = NULL; NbfCompletedReceives[NbfCompletedReceivesNext].Status = STATUS_CANCELLED; { ULONG i,j,k; PUCHAR va; PMDL mdl; mdl = ReceiveIrp->MdlAddress; if (BytesReceived > TRACK_TDI_CAPTURE) { NbfCompletedReceives[NbfCompletedReceivesNext].Contents[0] = 0xFF; } else { NbfCompletedReceives[NbfCompletedReceivesNext].Contents[0] = (UCHAR)BytesReceived; } i = 1; while (iNext; } } NbfCompletedReceivesNext = (NbfCompletedReceivesNext++) % TRACK_TDI_LIMIT; #endif RELEASE_SPIN_LOCK (Connection->LinkSpinLock, oldirql); IoReleaseCancelSpinLock (Irp->CancelIrql); #if DBG DbgPrint("NBF: Canceled receive %lx on %lx\n", ReceiveIrp, Connection); #endif // // The following dereference will complete the I/O, provided removes // the last reference on the request object. The I/O will complete // with the status and information stored in the Irp. Therefore, // we set those values here before the dereference. // NbfCompleteReceiveIrp (ReceiveIrp, STATUS_CANCELLED, 0); break; } p = p->Flink; } if (!Found) { // // We didn't find it! // #if DBG DbgPrint("NBF: Tried to cancel receive %lx on %lx, not found\n", Irp, Connection); #endif RELEASE_SPIN_LOCK (Connection->LinkSpinLock, oldirql); IoReleaseCancelSpinLock (Irp->CancelIrql); } } VOID NbfCancelReceiveDatagram( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: This routine is called by the I/O system to cancel a receive datagram. The receive is looked for on the address file's receive datagram queue; if it is found it is cancelled. NOTE: This routine is called with the CancelSpinLock held and is responsible for releasing it. Arguments: DeviceObject - Pointer to the device object for this driver. Irp - Pointer to the request packet representing the I/O request. Return Value: none. --*/ { KIRQL oldirql; PIO_STACK_LOCATION IrpSp; PTP_ADDRESS_FILE AddressFile; PTP_ADDRESS Address; PLIST_ENTRY p; BOOLEAN Found; UNREFERENCED_PARAMETER (DeviceObject); // // Get a pointer to the current stack location in the IRP. This is where // the function codes and parameters are stored. // IrpSp = IoGetCurrentIrpStackLocation (Irp); ASSERT ((IrpSp->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL) && (IrpSp->MinorFunction == TDI_RECEIVE_DATAGRAM)); AddressFile = IrpSp->FileObject->FsContext; Address = AddressFile->Address; // // Since this IRP is still in the cancellable state, we know // that the address file is still around (although it may be in // the process of being torn down). See if the IRP is on the list. // Found = FALSE; ACQUIRE_SPIN_LOCK (&Address->SpinLock, &oldirql); for (p = AddressFile->ReceiveDatagramQueue.Flink; p != &AddressFile->ReceiveDatagramQueue; p = p->Flink) { if (CONTAINING_RECORD(p, IRP, Tail.Overlay.ListEntry) == Irp) { RemoveEntryList (p); Found = TRUE; break; } } RELEASE_SPIN_LOCK (&Address->SpinLock, oldirql); IoReleaseCancelSpinLock (Irp->CancelIrql); if (Found) { #if DBG DbgPrint("NBF: Canceled receive datagram %lx on %lx\n", Irp, AddressFile); #endif Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest (Irp, IO_NETWORK_INCREMENT); NbfDereferenceAddress ("Receive DG cancelled", Address, AREF_REQUEST); } else { #if DBG DbgPrint("NBF: Tried to cancel receive datagram %lx on %lx, not found\n", Irp, AddressFile); #endif } } /* NbfCancelReceiveDatagram */