/*++ Copyright (c) 1989 Microsoft Corporation Module Name: send.c Abstract: This module contains the code for passing on send IRPs to TDI providers. Author: David Treadwell (davidtr) 13-Mar-1992 Revision History: --*/ #include "afdp.h" VOID AfdCancelSend ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ); NTSTATUS AfdRestartSend ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ); NTSTATUS AfdRestartSendConnDatagram ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ); NTSTATUS AfdRestartSendDatagram ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ); typedef struct _AFD_SEND_CONN_DATAGRAM_CONTEXT { PAFD_ENDPOINT Endpoint; TDI_CONNECTION_INFORMATION ConnectionInformation; } AFD_SEND_CONN_DATAGRAM_CONTEXT, *PAFD_SEND_CONN_DATAGRAM_CONTEXT; #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGEAFD, AfdSend ) #pragma alloc_text( PAGEAFD, AfdSendDatagram ) #pragma alloc_text( PAGEAFD, AfdCancelSend ) #pragma alloc_text( PAGEAFD, AfdRestartSend ) #pragma alloc_text( PAGEAFD, AfdRestartBufferSend ) #pragma alloc_text( PAGEAFD, AfdRestartSendConnDatagram ) #pragma alloc_text( PAGEAFD, AfdRestartSendDatagram ) #pragma alloc_text( PAGEAFD, AfdSendPossibleEventHandler ) #endif // // Macros to make the send restart code more maintainable. // #define AfdRestartSendInfo DeviceIoControl #define AfdMdlChain Type3InputBuffer #define AfdSendFlags InputBufferLength #define AfdOriginalLength OutputBufferLength #define AfdCurrentLength IoControlCode NTSTATUS AfdSend ( IN PIRP Irp, IN PIO_STACK_LOCATION IrpSp ) { NTSTATUS status; PAFD_ENDPOINT endpoint; ULONG sendLength; PAFD_CONNECTION connection; BOOLEAN doSendBufferring; PAFD_BUFFER afdBuffer; ULONG sendFlags; ULONG afdFlags; BOOLEAN pendedIrp = FALSE; PAFD_SEND_INFO sendInfo; // // Make sure that the endpoint is in the correct state. // endpoint = IrpSp->FileObject->FsContext; ASSERT( IS_AFD_ENDPOINT_TYPE( endpoint ) ); if ( endpoint->State != AfdEndpointStateConnected ) { status = STATUS_INVALID_CONNECTION; goto complete; } // // If send has been shut down on this endpoint, fail. We need to be // careful about what error code we return here: if the connection // has been aborted, be sure to return the apprpriate error code. // if ( (endpoint->DisconnectMode & AFD_PARTIAL_DISCONNECT_SEND) != 0 ) { if ( (endpoint->DisconnectMode & AFD_ABORTIVE_DISCONNECT) != 0 ) { status = STATUS_LOCAL_DISCONNECT; } else { status = STATUS_PIPE_DISCONNECTED; } goto complete; } // // Set up the IRP on the assumption that it will complete successfully. // Irp->IoStatus.Status = STATUS_SUCCESS; // // If this is an IOCTL_AFD_SEND, then grab the parameters from the // supplied AFD_SEND_INFO structure, build an MDL chain describing // the WSABUF array, and attach the MDL chain to the IRP. // // If this is an IRP_MJ_WRITE IRP, just grab the length from the IRP // and set the flags to zero. // if( IrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL ) { // // Sanity check. // ASSERT( IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_AFD_SEND ); if( IrpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*sendInfo) ) { try { // // Probe the input structure. // sendInfo = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer; if( Irp->RequestorMode != KernelMode ) { ProbeForRead( sendInfo, sizeof(*sendInfo), sizeof(ULONG) ); } // // Snag the send flags. // sendFlags = sendInfo->TdiFlags; afdFlags = sendInfo->AfdFlags; // // Validate the WSABUF parameters. // if( sendInfo->BufferArray != NULL && sendInfo->BufferCount > 0 ) { // // Create the MDL chain describing the WSABUF array. // status = AfdAllocateMdlChain( Irp, sendInfo->BufferArray, sendInfo->BufferCount, IoReadAccess, &sendLength ); } else { // // Invalid BufferArray or BufferCount fields. // status = STATUS_INVALID_PARAMETER; } } except( EXCEPTION_EXECUTE_HANDLER ) { // // Exception accessing input structure. // status = GetExceptionCode(); } } else { // // Invalid input buffer length. // status = STATUS_INVALID_PARAMETER; } if( !NT_SUCCESS(status) ) { goto complete; } } else { ASSERT( IrpSp->MajorFunction == IRP_MJ_WRITE ); sendFlags = 0; afdFlags = AFD_OVERLAPPED; sendLength = IrpSp->Parameters.Write.Length; } // // AfdSend() will either complete fully or will fail. // Irp->IoStatus.Information = sendLength; // // Setup for possible restart if the transport completes // the send partially. // IrpSp->Parameters.AfdRestartSendInfo.AfdMdlChain = Irp->MdlAddress; IrpSp->Parameters.AfdRestartSendInfo.AfdSendFlags = sendFlags; IrpSp->Parameters.AfdRestartSendInfo.AfdOriginalLength = sendLength; IrpSp->Parameters.AfdRestartSendInfo.AfdCurrentLength = sendLength; // // Buffer sends if the TDI provider does not buffer. // if ( endpoint->TdiBufferring ) { doSendBufferring = FALSE; // // If this is a nonblocking endpoint, set the TDI nonblocking // send flag so that the request will fail if the send cannot be // performed immediately. // if ( endpoint->NonBlocking ) { sendFlags |= TDI_SEND_NON_BLOCKING; } } else { doSendBufferring = TRUE; } // // If this is a datagram endpoint, format up a send datagram request // and pass it on to the TDI provider. // if ( IS_DGRAM_ENDPOINT(endpoint) ) { PAFD_SEND_CONN_DATAGRAM_CONTEXT context; // // It is illegal to send expedited data on a datagram socket. // if ( (sendFlags & TDI_SEND_EXPEDITED) != 0 ) { status = STATUS_NOT_SUPPORTED; goto complete; } // // Allocate space to hold the connection information structure // we'll use on input. // context = AFD_ALLOCATE_POOL( NonPagedPool, sizeof(*context), AFD_TDI_POOL_TAG ); if ( context == NULL ) { status = STATUS_INSUFFICIENT_RESOURCES; goto complete; } context->Endpoint = endpoint; context->ConnectionInformation.UserDataLength = 0; context->ConnectionInformation.UserData = NULL; context->ConnectionInformation.OptionsLength = 0; context->ConnectionInformation.Options = NULL; context->ConnectionInformation.RemoteAddressLength = endpoint->Common.Datagram.RemoteAddressLength; context->ConnectionInformation.RemoteAddress = endpoint->Common.Datagram.RemoteAddress; // // Build a send datagram request. // TdiBuildSendDatagram( Irp, endpoint->AddressDeviceObject, endpoint->AddressFileObject, AfdRestartSendConnDatagram, context, Irp->MdlAddress, sendLength, &context->ConnectionInformation ); // // Call the transport to actually perform the send operation. // return AfdIoCallDriver( endpoint, endpoint->AddressDeviceObject, Irp ); } // // Get a pointer to the relevent AFD connection structure. // connection = AFD_CONNECTION_FROM_ENDPOINT( endpoint ); ASSERT( connection != NULL ); ASSERT( connection->Type == AfdBlockTypeConnection ); ASSERT( !connection->CleanupBegun ); // // If the connection has been aborted, do not pend the IRP. // if ( connection->AbortIndicated ) { status = STATUS_CONNECTION_RESET; goto complete; } // // If we need to buffer the send, do so. // if ( doSendBufferring && connection->MaxBufferredSendCount != 0 ) { KIRQL cancelIrql; KIRQL oldIrql; ULONG bytesCopied; ASSERT( !endpoint->TdiBufferring ); ASSERT( !connection->TdiBufferring ); // // First make sure that we don't have too many bytes of send // data already outstanding and that someone else isn't already // in the process of completing pended send IRPs. We can't // issue the send here if someone else is completing pended // sends because we have to preserve ordering of the sends. // // Note that we'll give the send data to the TDI provider even // if we have exceeded our send buffer limits, but that we don't // complete the user's IRP until some send buffer space has // freed up. This effects flow control by blocking the user's // thread while ensuring that the TDI provider always has lots // of data available to be sent. // IoAcquireCancelSpinLock( &cancelIrql ); AfdAcquireSpinLock( &endpoint->SpinLock, &oldIrql ); if ( connection->VcBufferredSendBytes >= connection->MaxBufferredSendBytes || connection->VcBufferredSendCount >= connection->MaxBufferredSendCount ) { // // There is already as much send data bufferred on the // connection as is allowed. If this is a nonblocking // endpoint and this is not an overlapped operation, fail the // request. // if ( endpoint->NonBlocking && !( afdFlags & AFD_OVERLAPPED ) ) { // // Enable the send event. // endpoint->EventsActive &= ~AFD_POLL_SEND; IF_DEBUG(EVENT_SELECT) { KdPrint(( "AfdSend: Endp %08lX, Active %08lX\n", endpoint, endpoint->EventsActive )); } AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); IoReleaseCancelSpinLock( cancelIrql ); status = STATUS_DEVICE_NOT_READY; goto complete; } // // We're going to have to pend the request here in AFD. // Place the IRP on the connection's list of pended send // IRPs and mark the IRP as pended. // InsertTailList( &connection->VcSendIrpListHead, &Irp->Tail.Overlay.ListEntry ); // // Set up the cancellation routine in the IRP. If the IRP // has already been cancelled, just call the cancellation // routine here. // if ( Irp->Cancel ) { AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); Irp->CancelIrql = cancelIrql; AfdCancelSend( IrpSp->DeviceObject, Irp ); return STATUS_CANCELLED; } IoSetCancelRoutine( Irp, AfdCancelSend ); // // Remember that we pended the IRP so that we do not // complete it later. // pendedIrp = TRUE; } // // We don't need the IO cancel spin lock any more, so release it // while being careful to swap the IRQLs. We have to hold on to // the endpoint spin lock until after we have copied the data // out of the IRP in order to prevent the IRP from being // completed in our send completion routine. // IoReleaseCancelSpinLock( oldIrql ); oldIrql = cancelIrql; // // Next get an AFD buffer structure that contains an IRP and a // buffer to hold the data. // afdBuffer = AfdGetBuffer( sendLength, 0 ); if ( afdBuffer == NULL && sendLength > AfdBufferLengthForOnePage ) { IF_DEBUG(SEND) { KdPrint(( "AfdSend: cannot allocate %lu, trying chain\n", sendLength )); } afdBuffer = AfdGetBufferChain( sendLength ); } if ( afdBuffer == NULL ) { if ( pendedIrp ) { RemoveEntryList( &Irp->Tail.Overlay.ListEntry ); } AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); IoAcquireCancelSpinLock( &cancelIrql ); IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( cancelIrql ); status = STATUS_INSUFFICIENT_RESOURCES; goto complete; } // // If we're pending the user's IRP, then mark it so. // if( pendedIrp ) { IoMarkIrpPending( Irp ); } // // Update count of send bytes pending on the connection. // connection->VcBufferredSendBytes += sendLength; connection->VcBufferredSendCount += 1; // // We have to rebuild the MDL in the AFD buffer structure to // represent exactly the number of bytes we're going to be // sending. // if( afdBuffer->NextBuffer == NULL ) { afdBuffer->Mdl->ByteCount = sendLength; SET_CHAIN_LENGTH( afdBuffer, sendLength ); } // // Remember the endpoint in the AFD buffer structure. We need // this in order to access the endpoint in the restart routine. // afdBuffer->Context = endpoint; // // Copy the user's data into the AFD buffer. If the MDL in the // IRP is NULL, then don't bother doing the copy--this is a // send of length 0. // if ( Irp->MdlAddress != NULL ) { TdiCopyMdlToBuffer( Irp->MdlAddress, 0, afdBuffer->Buffer, 0, sendLength, &bytesCopied ); ASSERT( bytesCopied == sendLength ); // // Now that we've capture the send data, we can free the // MDL chain associated with the incoming IRP. // // !!! Is this really a wise thing to do? // AfdDestroyMdlChain( Irp ); } else { ASSERT( IrpSp->Parameters.AfdRestartSendInfo.AfdOriginalLength == 0 ); } // // Release the endpoint lock AFTER we are 100% done with the IRP // (if pended). This prevents the user's pended IRP from being // completed in our send completion routine while we're still // looking at it. // AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); // // Use the IRP in the AFD buffer structure to give to the TDI // provider. Build the TDI send request. // TdiBuildSend( afdBuffer->Irp, connection->DeviceObject, connection->FileObject, AfdRestartBufferSend, afdBuffer, afdBuffer->Mdl, sendFlags, sendLength ); // // Add a reference to the connection object since the send // request will complete asynchronously. // REFERENCE_CONNECTION2( connection, (PVOID)(0xafdafd02), afdBuffer->Irp ); // // Call the transport to actually perform the send. // status = IoCallDriver( connection->DeviceObject, afdBuffer->Irp ); // // Complete the user's IRP as appropriate if we didn't already // pend it. Note that we change the status code from what was // returned by the TDI provider into STATUS_SUCCESS. This is // because we don't want to complete the IRP with STATUS_PENDING // etc. // if ( NT_SUCCESS(status) && !pendedIrp ) { ASSERT( Irp->IoStatus.Information == sendLength ); IoCompleteRequest( Irp, AfdPriorityBoost ); return STATUS_SUCCESS; } // // If we pended the user's IRP, return appropriate status. Note // that in this case we ignore an IoCallDriver() failure. // if ( pendedIrp ) { return STATUS_PENDING; } // // The send request to the TDI provider failed immediately. // Propagate the failure to the user. // goto complete; } else { // // Build the TDI send request. // connection = AFD_CONNECTION_FROM_ENDPOINT( endpoint ); ASSERT( connection != NULL ); TdiBuildSend( Irp, connection->DeviceObject, connection->FileObject, AfdRestartSend, endpoint, Irp->MdlAddress, sendFlags, sendLength ); // // Add a reference to the connection object since the send // request will complete asynchronously. // REFERENCE_CONNECTION2( connection, (PVOID)(0xafdafd03), Irp ); // // Call the transport to actually perform the send. // return AfdIoCallDriver( endpoint, connection->DeviceObject, Irp ); } complete: Irp->IoStatus.Information = 0; Irp->IoStatus.Status = status; IoCompleteRequest( Irp, AfdPriorityBoost ); return status; } // AfdSend NTSTATUS AfdRestartSend ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PIO_STACK_LOCATION irpSp; PAFD_ENDPOINT endpoint = Context; PAFD_CONNECTION connection; PMDL mdlChain; PMDL nextMdl; NTSTATUS status; PIRP disconnectIrp; KIRQL oldIrql; ASSERT( endpoint != NULL ); ASSERT( endpoint->Type == AfdBlockTypeVcConnecting ); connection = endpoint->Common.VcConnecting.Connection; ASSERT( connection != NULL ); ASSERT( connection->Type == AfdBlockTypeConnection ); IF_DEBUG(SEND) { KdPrint(( "AfdRestartSend: send completed for IRP %lx, endpoint %lx, " "status = %X\n", Irp, Context, Irp->IoStatus.Status )); } // // If the request failed indicating that the send would have blocked, // and the client issues a nonblocking send, remember that nonblocking // sends won't work until we get a send possible indication. This // is required for write polls to work correctly. // // If the status code is STATUS_REQUEST_NOT_ACCEPTED, then the // transport does not want us to update our internal variable that // remembers that nonblocking sends are possible. The transport // will tell us when sends are or are not possible. // // !!! should we also say that nonblocking sends are not possible if // a send is completed with fewer bytes than were requested? if ( Irp->IoStatus.Status == STATUS_DEVICE_NOT_READY ) { // // Reenable the send event. // AfdAcquireSpinLock( &endpoint->SpinLock, &oldIrql ); endpoint->EventsActive &= ~AFD_POLL_SEND; IF_DEBUG(EVENT_SELECT) { KdPrint(( "AfdRestartSend: Endp %08lX, Active %08lX\n", endpoint, endpoint->EventsActive )); } AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); connection->VcNonBlockingSendPossible = FALSE; } // // If this is a send IRP on a nonblocking endpoint and fewer bytes // were actually sent than were requested to be sent, reissue // another send for the remaining buffer space. // irpSp = IoGetCurrentIrpStackLocation( Irp ); if ( !endpoint->NonBlocking && NT_SUCCESS(Irp->IoStatus.Status) && Irp->IoStatus.Information < irpSp->Parameters.AfdRestartSendInfo.AfdCurrentLength ) { ASSERT( Irp->MdlAddress != NULL ); // // Advance the MDL chain by the number of bytes actually sent. // mdlChain = AfdAdvanceMdlChain( Irp->MdlAddress, Irp->IoStatus.Information ); // // If the first MDL referenced by the IRP has the MDL_PARTIAL // flag set, then it's one of ours from a previous partial // send and must be freed. // if ( Irp->MdlAddress->MdlFlags & MDL_PARTIAL ) { nextMdl = Irp->MdlAddress->Next; IoFreeMdl( Irp->MdlAddress ); Irp->MdlAddress = nextMdl; } if ( mdlChain != NULL ) { Irp->MdlAddress = mdlChain; // // Update our restart info. // irpSp->Parameters.AfdRestartSendInfo.AfdCurrentLength -= Irp->IoStatus.Information; // // Reissue the send. // TdiBuildSend( Irp, connection->FileObject->DeviceObject, connection->FileObject, AfdRestartSend, endpoint, Irp->MdlAddress, irpSp->Parameters.AfdRestartSendInfo.AfdSendFlags, irpSp->Parameters.AfdRestartSendInfo.AfdCurrentLength ); status = AfdIoCallDriver( endpoint, connection->FileObject->DeviceObject, Irp ); IF_DEBUG(SEND) { if ( !NT_SUCCESS(status) ) { KdPrint(( "AfdRestartSend: AfdIoCallDriver returned %lx\n", status )); } } return STATUS_MORE_PROCESSING_REQUIRED; } else { // // Bad news, could not allocate a new partial MDL. // Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; disconnectIrp = connection->VcDisconnectIrp; connection->VcDisconnectIrp = NULL; AfdBeginAbort( connection ); // // If there was a disconnect IRP, rather than just freeing it // give it to the transport. This will cause the correct cleanup // stuff (dereference objects, free IRP and disconnect context) // to occur. Note that we do this AFTER starting to abort the // connection so that we do not confuse the other side. // if ( disconnectIrp != NULL ) { IoCallDriver( connection->FileObject->DeviceObject, disconnectIrp ); } AfdDeleteConnectedReference( connection, FALSE ); // // Remove the reference added just before calling the transport. // DEREFERENCE_CONNECTION2( connection, (PVOID)(0xafd11105), Irp ); return STATUS_MORE_PROCESSING_REQUIRED; } } // // If the first MDL referenced by the IRP has the MDL_PARTIAL // flag set, then it's one of ours from a previous partial // send and must be freed. // if( Irp->MdlAddress != NULL && Irp->MdlAddress->MdlFlags & MDL_PARTIAL ) { IoFreeMdl( Irp->MdlAddress ); } // // Restore the IRP to its former glory before completing it. // Irp->MdlAddress = irpSp->Parameters.AfdRestartSendInfo.AfdMdlChain; Irp->IoStatus.Information = irpSp->Parameters.AfdRestartSendInfo.AfdOriginalLength; AfdCompleteOutstandingIrp( endpoint, Irp ); // // If pending has be returned for this irp then mark the current // stack as pending. // if ( Irp->PendingReturned ) { IoMarkIrpPending(Irp); } // // Remove the reference added just before calling the transport. // DEREFERENCE_CONNECTION2( connection, (PVOID)(0xafd11100), Irp ); return STATUS_SUCCESS; } // AfdRestartSend NTSTATUS AfdRestartBufferSend ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PAFD_BUFFER afdBuffer = Context; PAFD_ENDPOINT endpoint; PAFD_CONNECTION connection; KIRQL cancelIrql; KIRQL oldIrql; PLIST_ENTRY listEntry; PIRP irp; ULONG sendCount; PIRP disconnectIrp; LIST_ENTRY irpsToComplete; endpoint = afdBuffer->Context; ASSERT( endpoint != NULL ); ASSERT( endpoint->Type == AfdBlockTypeVcConnecting ); ASSERT( !endpoint->TdiBufferring ); connection = endpoint->Common.VcConnecting.Connection; ASSERT( connection != NULL ); ASSERT( connection->Type == AfdBlockTypeConnection ); ASSERT( connection->ReferenceCount > 0 ); IF_DEBUG(SEND) { KdPrint(( "AfdRestartBufferSend: send completed for IRP %lx, endpoint %lx, " "status = %X\n", Irp, Context, Irp->IoStatus.Status )); } UPDATE_CONN( connection, Irp->IoStatus.Status ); // // Make a special test here to see whether this send was from the // TransmitFile fast path with MDL file I/O. If there is a file // object pointer in the AFD buffer structure, then we need to // return the MDL chain to the cache manager and derefence the file // object. // if ( afdBuffer->FileObject != NULL ) { ASSERT( afdBuffer->NextBuffer == NULL ); ASSERT( afdBuffer->Mdl->Next != NULL ); AfdMdlReadComplete( afdBuffer->FileObject, afdBuffer->Mdl->Next, afdBuffer->FileOffset, afdBuffer->ReadLength ); afdBuffer->Mdl->Next = NULL; ObDereferenceObject( afdBuffer->FileObject ); afdBuffer->FileObject = NULL; } // // Update the count of send bytes outstanding on the connection. // Note that we must do this BEFORE we check to see whether there // are any pended sends--otherwise, there is a timing window where // a new send could come in, get pended, and we would not kick // the sends here. // AfdAcquireSpinLock( &endpoint->SpinLock, &oldIrql ); ASSERT( connection->VcBufferredSendBytes >= Irp->IoStatus.Information ); ASSERT( (connection->VcBufferredSendCount & 0x8000) == 0 ); ASSERT( connection->VcBufferredSendCount != 0 ); connection->VcBufferredSendBytes -= Irp->IoStatus.Information; connection->VcBufferredSendCount -= 1; // // If the send failed, abort the connection. // if ( !NT_SUCCESS(Irp->IoStatus.Status) ) { disconnectIrp = connection->VcDisconnectIrp; if ( disconnectIrp != NULL ) { connection->VcDisconnectIrp = NULL; } AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); if( afdBuffer->NextBuffer == NULL ) { afdBuffer->Mdl->ByteCount = afdBuffer->BufferLength; RESET_CHAIN_LENGTH( afdBuffer ); AfdReturnBuffer( afdBuffer ); } else { AfdReturnBufferChain( afdBuffer ); } AfdBeginAbort( connection ); // // If there was a disconnect IRP, rather than just freeing it // give it to the transport. This will cause the correct cleanup // stuff (dereferenvce objects, free IRP and disconnect context) // to occur. Note that we do this AFTER starting to abort the // connection so that we do not confuse the other side. // if ( disconnectIrp != NULL ) { IoCallDriver( connection->DeviceObject, disconnectIrp ); } AfdDeleteConnectedReference( connection, FALSE ); // // Remove the reference added just before calling the transport. // DEREFERENCE_CONNECTION2( connection, (PVOID)(0xafd11101), Irp ); return STATUS_MORE_PROCESSING_REQUIRED; } // // Make sure that the TDI provider sent everything we requested that // he send. // ASSERT( Irp->IoStatus.Information == afdBuffer->TotalChainLength ); // // Return the AFD buffer to our buffer pool. // if( afdBuffer->NextBuffer == NULL ) { afdBuffer->Mdl->ByteCount = afdBuffer->BufferLength; RESET_CHAIN_LENGTH( afdBuffer ); AfdReturnBuffer( afdBuffer ); } else { AfdReturnBufferChain( afdBuffer ); } // // If there are no pended sends on the connection, we're done. Tell // the IO system to stop processing IO completion for this IRP. // if ( IsListEmpty( &connection->VcSendIrpListHead ) ) { // // If there is no "special condition" on the endpoint, return // immediately. We use the special condition indication so that // we need only a single test in the typical case. // if ( !connection->SpecialCondition ) { ASSERT( connection->TdiBufferring || connection->VcDisconnectIrp == NULL ); ASSERT( connection->ConnectedReferenceAdded ); // // There are no sends outstanding on the connection, so indicate // that the endpoint is writable. // AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); AfdIndicatePollEvent( endpoint, AFD_POLL_SEND_BIT, STATUS_SUCCESS ); // // Remove the reference added just before calling the transport. // DEREFERENCE_CONNECTION2( connection, (PVOID)(0xafd11102), Irp ); // // Tell the IO system to stop doing completion processing on // this IRP. // return STATUS_MORE_PROCESSING_REQUIRED; } // // Before we release the lock on the endpoint, remember // the count of sends outstanding in the TDI provider. We must // grab this while holding the endpoint lock. // sendCount = connection->VcBufferredSendCount; AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); // // While holding the AFD spin lock, grab the disconnect IRP // if any. We'll only start the disconnect IRP if there is no // more pended send data (sendCount == 0). // AfdAcquireSpinLock( &AfdSpinLock, &oldIrql ); disconnectIrp = connection->VcDisconnectIrp; if ( disconnectIrp != NULL && sendCount == 0 ) { connection->VcDisconnectIrp = NULL; } else { disconnectIrp = NULL; } AfdReleaseSpinLock( &AfdSpinLock, oldIrql ); // // There are no sends outstanding on the connection, so indicate // that the endpoint is writable. // AfdIndicatePollEvent( endpoint, AFD_POLL_SEND_BIT, STATUS_SUCCESS ); // // If there is a disconnect IRP, give it to the TDI provider. // if ( disconnectIrp != NULL ) { IoCallDriver( connection->DeviceObject, disconnectIrp ); } // // If the connected reference delete is pending, attempt to // remove it. // AfdDeleteConnectedReference( connection, FALSE ); // // Remove the reference added just before calling the transport. // DEREFERENCE_CONNECTION2( connection, (PVOID)(0xafd11103), Irp ); // // Tell the IO system to stop doing completion processing on // this IRP. // return STATUS_MORE_PROCESSING_REQUIRED; } // // We have to release the endpoint's spin lock in order to acquire // the cancel spin lock due to lock ordering restrictions. // This helps performance in the normal case since we won't have // to acquire the cancel spin lock. Since we recheck whether // the list is empty after reacquiring the locks, everything // will work out. // AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); IoAcquireCancelSpinLock( &cancelIrql ); AfdAcquireSpinLock( &endpoint->SpinLock, &oldIrql ); // // Now loop completing as many pended sends as possible. Note that // in order to avoid a nasty race condition (between this thread and // a thread performing sends on this connection) we must build a local // list of IRPs to complete while holding the cancel and endpoint // spinlocks. After that list is built then we can release the locks // and scan the list to actually complete the IRPs. // // We complete sends when we fall below the send bufferring limits, OR // when there is only a single send pended. We want to be agressive // in completing the send if there is only one because we want to // give applications every oppurtunity to get data down to us--we // definitely do not want to incur excessive blocking in the // application. // InitializeListHead( &irpsToComplete ); while ( (connection->VcBufferredSendBytes <= connection->MaxBufferredSendBytes || connection->VcSendIrpListHead.Flink == connection->VcSendIrpListHead.Blink) && connection->VcBufferredSendCount <= connection->MaxBufferredSendCount && !IsListEmpty( &connection->VcSendIrpListHead ) ) { // // Take the first pended user send IRP off the connection's // list of pended send IRPs. // listEntry = RemoveHeadList( &connection->VcSendIrpListHead ); irp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry ); // // Reset the cancel routine in the user IRP since we're about // to complete it. // IoSetCancelRoutine( irp, NULL ); // // Append the IRP to the local list. // InsertTailList( &irpsToComplete, &irp->Tail.Overlay.ListEntry ); } // // While we're still holding the locks, capture the send count // from the connection. // sendCount = connection->VcBufferredSendCount; // // Now we can release the locks and scan the local list of IRPs // we need to complete, and actually complete them. // AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); IoReleaseCancelSpinLock( cancelIrql ); while( !IsListEmpty( &irpsToComplete ) ) { // // Remove the first item from the IRP list. // listEntry = RemoveHeadList( &irpsToComplete ); irp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry ); // // Complete the user's IRP with a successful status code. The IRP // should already be set up with the correct status and bytes // written count. // #if DBG if ( irp->IoStatus.Status == STATUS_SUCCESS ) { PIO_STACK_LOCATION irpSp; irpSp = IoGetCurrentIrpStackLocation( irp ); ASSERT( irp->IoStatus.Information == irpSp->Parameters.AfdRestartSendInfo.AfdOriginalLength ); } #endif IoCompleteRequest( irp, AfdPriorityBoost ); } // // If the list of pended send IRPs is now empty, we'll indicate // that the endpoint it writable. // if ( sendCount == 0 ) { AfdIndicatePollEvent( endpoint, AFD_POLL_SEND_BIT, STATUS_SUCCESS ); } // // Remove the reference added just before calling the transport. // DEREFERENCE_CONNECTION2( connection, (PVOID)(0xafd11104), Irp ); // // Tell the IO system to stop processing IO completion for this IRP. // return STATUS_MORE_PROCESSING_REQUIRED; } // AfdRestartBufferSend NTSTATUS AfdRestartSendConnDatagram ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PAFD_SEND_CONN_DATAGRAM_CONTEXT context = Context; IF_DEBUG(SEND) { KdPrint(( "AfdRestartSendConnDatagram: send conn completed for " "IRP %lx, endpoint %lx, status = %X\n", Irp, context->Endpoint, Irp->IoStatus.Status )); } // // Free the context structure we allocated earlier. // AfdCompleteOutstandingIrp( context->Endpoint, Irp ); AFD_FREE_POOL( context, AFD_TDI_POOL_TAG ); // // If pending has be returned for this irp then mark the current // stack as pending. // if ( Irp->PendingReturned ) { IoMarkIrpPending(Irp); } return STATUS_SUCCESS; } // AfdRestartSendConnDatagram NTSTATUS AfdSendDatagram ( IN PIRP Irp, IN PIO_STACK_LOCATION IrpSp ) { NTSTATUS status; PAFD_ENDPOINT endpoint; PAFD_SEND_DATAGRAM_INFO sendInfo; ULONG destinationAddressLength; PAFD_BUFFER afdBuffer; ULONG sendLength; // // Make sure that the endpoint is in the correct state. // endpoint = IrpSp->FileObject->FsContext; ASSERT( endpoint->Type == AfdBlockTypeDatagram ); if ( endpoint->State != AfdEndpointStateBound ) { status = STATUS_INVALID_PARAMETER; goto complete; } if( IrpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*sendInfo) ) { try { // // Probe the input structure. // sendInfo = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer; if( Irp->RequestorMode != KernelMode ) { ProbeForRead( sendInfo, sizeof(*sendInfo), sizeof(ULONG) ); } // // Grab the length of the destination address. // destinationAddressLength = sendInfo->TdiConnInfo.RemoteAddressLength; // // Validate the WSABUF parameters. // if( sendInfo->BufferArray != NULL && sendInfo->BufferCount > 0 ) { // // Create the MDL chain describing the WSABUF array. // status = AfdAllocateMdlChain( Irp, sendInfo->BufferArray, sendInfo->BufferCount, IoReadAccess, &sendLength ); } else { // // Invalid BufferArray or BufferCount fields. // status = STATUS_INVALID_PARAMETER; } } except( EXCEPTION_EXECUTE_HANDLER ) { // // Exception accessing input structure. // status = GetExceptionCode(); } } else { // // Invalid input buffer length. // status = STATUS_INVALID_PARAMETER; } if( !NT_SUCCESS(status) ) { goto complete; } // // If send has been shut down on this endpoint, fail. // if ( (endpoint->DisconnectMode & AFD_PARTIAL_DISCONNECT_SEND) ) { status = STATUS_PIPE_DISCONNECTED; goto complete; } // // Get an AFD buffer to use for the request. We need this to // hold the destination address for the datagram. // afdBuffer = AfdGetBuffer( 0, destinationAddressLength ); if ( afdBuffer == NULL ) { status = STATUS_INSUFFICIENT_RESOURCES; goto complete; } // // Copy the destination address to the AFD buffer. // try { RtlCopyMemory( afdBuffer->SourceAddress, sendInfo->TdiConnInfo.RemoteAddress, destinationAddressLength ); afdBuffer->TdiInputInfo.RemoteAddressLength = destinationAddressLength; afdBuffer->TdiInputInfo.RemoteAddress = afdBuffer->SourceAddress; } except( EXCEPTION_EXECUTE_HANDLER ) { AfdReturnBufferChain( afdBuffer ); status = STATUS_ACCESS_VIOLATION; goto complete; } // // Build the request to send the datagram. // afdBuffer->Context = endpoint; TdiBuildSendDatagram( Irp, endpoint->AddressDeviceObject, endpoint->AddressFileObject, AfdRestartSendDatagram, afdBuffer, Irp->MdlAddress, sendLength, &afdBuffer->TdiInputInfo ); IF_DEBUG(SEND) { PTDI_REQUEST_SEND_DATAGRAM tdiRequest = &sendInfo->TdiRequest; KdPrint(( "AfdSendDatagram: tdiRequest at %lx, SendDataInfo at %lx, len = %lx\n", tdiRequest, tdiRequest->SendDatagramInformation, IrpSp->Parameters.DeviceIoControl.InputBufferLength )); KdPrint(( "AfdSendDatagram: remote address at %lx, len = %lx\n", tdiRequest->SendDatagramInformation->RemoteAddress, tdiRequest->SendDatagramInformation->RemoteAddressLength )); KdPrint(( "AfdSendDatagram: output buffer length = %lx\n", IrpSp->Parameters.DeviceIoControl.OutputBufferLength )); } // // Call the transport to actually perform the send datagram. // return AfdIoCallDriver( endpoint, endpoint->AddressDeviceObject, Irp ); complete: Irp->IoStatus.Information = 0; Irp->IoStatus.Status = status; IoCompleteRequest( Irp, AfdPriorityBoost ); return status; } // AfdSendDatagram NTSTATUS AfdRestartSendDatagram ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PAFD_BUFFER afdBuffer; PAFD_ENDPOINT endpoint; afdBuffer = Context; endpoint = afdBuffer->Context; ASSERT( endpoint->Type == AfdBlockTypeDatagram ); AfdCompleteOutstandingIrp( endpoint, Irp ); IF_DEBUG(SEND) { KdPrint(( "AfdRestartSendDatagram: send datagram completed for " "IRP %lx, endpoint %lx, status = %X\n", Irp, Context, Irp->IoStatus.Status )); } // // If pending has be returned for this irp then mark the current // stack as pending. // if ( Irp->PendingReturned ) { IoMarkIrpPending(Irp); } afdBuffer->TdiInputInfo.RemoteAddressLength = 0; afdBuffer->TdiInputInfo.RemoteAddress = NULL; AfdReturnBufferChain( afdBuffer ); return STATUS_SUCCESS; } // AfdRestartSendDatagram NTSTATUS AfdSendPossibleEventHandler ( IN PVOID TdiEventContext, IN PVOID ConnectionContext, IN ULONG BytesAvailable ) { PAFD_CONNECTION connection; PAFD_ENDPOINT endpoint; UNREFERENCED_PARAMETER( TdiEventContext ); UNREFERENCED_PARAMETER( BytesAvailable ); connection = (PAFD_CONNECTION)ConnectionContext; ASSERT( connection != NULL ); endpoint = connection->Endpoint; ASSERT( endpoint != NULL ); ASSERT( connection->Type == AfdBlockTypeConnection ); ASSERT( IS_AFD_ENDPOINT_TYPE( endpoint ) ); ASSERT( endpoint->TdiBufferring ); ASSERT( connection->TdiBufferring ); IF_DEBUG(SEND) { KdPrint(( "AfdSendPossibleEventHandler: send possible on endpoint %lx " " conn %lx bytes=%ld\n", endpoint, connection, BytesAvailable )); } // // Remember that it is now possible to do a send on this connection. // if ( BytesAvailable != 0 ) { connection->VcNonBlockingSendPossible = TRUE; // // Complete any outstanding poll IRPs waiting for a send poll. // AfdIndicatePollEvent( endpoint, AFD_POLL_SEND_BIT, STATUS_SUCCESS ); } else { connection->VcNonBlockingSendPossible = FALSE; } return STATUS_SUCCESS; } // AfdSendPossibleEventHandler VOID AfdCancelSend ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) /*++ Routine Description: Cancels a send IRP that is pended in AFD. Arguments: DeviceObject - not used. Irp - the IRP to cancel. Return Value: None. --*/ { PIO_STACK_LOCATION irpSp; PAFD_ENDPOINT endpoint; PAFD_CONNECTION connection; KIRQL oldIrql; // // Get the endpoint pointer from our IRP stack location and the // connection pointer from the endpoint. // irpSp = IoGetCurrentIrpStackLocation( Irp ); endpoint = irpSp->FileObject->FsContext; ASSERT( endpoint->Type == AfdBlockTypeVcConnecting ); connection = endpoint->Common.VcConnecting.Connection; ASSERT( connection->Type == AfdBlockTypeConnection ); // // Remove the IRP from the connection's IRP list, synchronizing with // the endpoint lock which protects the lists. Note that the IRP // *must* be on one of the connection's lists if we are getting // called here--anybody that removes the IRP from the list must do // so while holding the cancel spin lock and reset the cancel // routine to NULL before releasing the cancel spin lock. // AfdAcquireSpinLock( &endpoint->SpinLock, &oldIrql ); RemoveEntryList( &Irp->Tail.Overlay.ListEntry ); AfdReleaseSpinLock( &endpoint->SpinLock, oldIrql ); // // Reset the cancel routine in the IRP. // IoSetCancelRoutine( Irp, NULL ); // // Release the cancel spin lock and complete the IRP with a // cancellation status code. // IoReleaseCancelSpinLock( Irp->CancelIrql ); Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest( Irp, AfdPriorityBoost ); return; } // AfdCancelSend