/*++ Copyright (c) 1989-1993 Microsoft Corporation Module Name: request.c Abstract: This module contains code which implements the TP_REQUEST object. Routines are provided to create, destroy, reference, and dereference, transport request objects. Environment: Kernel mode Revision History: --*/ #include "st.h" VOID StTdiRequestTimeoutHandler( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++ Routine Description: This routine is executed as a DPC at DISPATCH_LEVEL when a request such as TdiSend, TdiReceive, TdiSendDatagram, TdiReceiveDatagram, etc., encounters a timeout. This routine cleans up the activity and cancels it. Arguments: Dpc - Pointer to a system DPC object. DeferredContext - Pointer to the TP_REQUEST block representing the request that has timed out. SystemArgument1 - Not used. SystemArgument2 - Not used. Return Value: none. --*/ { KIRQL oldirql; PTP_REQUEST Request; PTP_CONNECTION Connection; PIO_STACK_LOCATION IrpSp; UNREFERENCED_PARAMETER(Dpc); UNREFERENCED_PARAMETER(SystemArgument1); UNREFERENCED_PARAMETER(SystemArgument2); Request = (PTP_REQUEST)DeferredContext; ACQUIRE_SPIN_LOCK (&Request->SpinLock, &oldirql); Request->Flags &= ~REQUEST_FLAGS_TIMER; if ((Request->Flags & REQUEST_FLAGS_STOPPING) == 0) { // // find reason for timeout // IrpSp = IoGetCurrentIrpStackLocation (Request->IoRequestPacket); if (IrpSp->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL) { switch (IrpSp->MinorFunction) { // // none of these should time out. // case TDI_SEND: case TDI_ACCEPT: case TDI_SET_INFORMATION: case TDI_SET_EVENT_HANDLER: case TDI_SEND_DATAGRAM: case TDI_RECEIVE_DATAGRAM: case TDI_RECEIVE: ASSERT (FALSE); RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); StCompleteRequest (Request, STATUS_IO_TIMEOUT, 0); break; case TDI_LISTEN: case TDI_CONNECT: Connection = (PTP_CONNECTION)(Request->Context); RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); // // Since these requests are part of the connection // itself, we just stop the connection and the // request will get torn down then. If we get the // situation where the request times out before // it is queued to the connection, then the code // that is about to queue it will check the STOPPING // flag and complete it then. // StStopConnection (Connection, STATUS_IO_TIMEOUT); break; case TDI_DISCONNECT: // // We don't create requests for TDI_DISCONNECT any more. // ASSERT(FALSE); break; default: RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); break; } // end of switch } else { RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); } StDereferenceRequest ("Timeout", Request); // for the timeout } else { RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); StDereferenceRequest ("Timeout: stopping", Request); // for the timeout } return; } /* RequestTimeoutHandler */ VOID StAllocateRequest( IN PDEVICE_CONTEXT DeviceContext, OUT PTP_REQUEST *TransportRequest ) /*++ Routine Description: This routine allocates a request packet from nonpaged pool and initializes it to a known state. NOTE: This routine is called with the device context spinlock held, or at such a time as synchronization is unnecessary. Arguments: DeviceContext - Pointer to the device context (which is really just the device object with its extension) to be associated with the address. TransportRequest - Pointer to a place where this routine will return a pointer to a transport request structure. It returns NULL if no storage can be allocated. Return Value: None. --*/ { PTP_REQUEST Request; if ((DeviceContext->MemoryLimit != 0) && ((DeviceContext->MemoryUsage + sizeof(TP_REQUEST)) > DeviceContext->MemoryLimit)) { PANIC("ST: Could not allocate request: limit\n"); StWriteResourceErrorLog (DeviceContext, sizeof(TP_REQUEST), 104); *TransportRequest = NULL; return; } Request = (PTP_REQUEST)ExAllocatePool (NonPagedPool, sizeof (TP_REQUEST)); if (Request == NULL) { PANIC("ST: Could not allocate request: no pool\n"); StWriteResourceErrorLog (DeviceContext, sizeof(TP_REQUEST), 204); *TransportRequest = NULL; return; } RtlZeroMemory (Request, sizeof(TP_REQUEST)); DeviceContext->MemoryUsage += sizeof(TP_REQUEST); ++DeviceContext->RequestAllocated; Request->Type = ST_REQUEST_SIGNATURE; Request->Size = sizeof (TP_REQUEST); Request->Provider = DeviceContext; Request->ProviderInterlock = &DeviceContext->Interlock; KeInitializeSpinLock (&Request->SpinLock); KeInitializeDpc (&Request->Dpc, StTdiRequestTimeoutHandler, (PVOID)Request); KeInitializeTimer (&Request->Timer); // set to not-signaled state. *TransportRequest = Request; } /* StAllocateRequest */ VOID StDeallocateRequest( IN PDEVICE_CONTEXT DeviceContext, IN PTP_REQUEST TransportRequest ) /*++ Routine Description: This routine frees a request packet. NOTE: This routine is called with the device context spinlock held, or at such a time as synchronization is unnecessary. Arguments: DeviceContext - Pointer to the device context (which is really just the device object with its extension) to be associated with the address. TransportRequest - Pointer to a transport request structure. Return Value: None. --*/ { ExFreePool (TransportRequest); --DeviceContext->RequestAllocated; DeviceContext->MemoryUsage -= sizeof(TP_REQUEST); } /* StDeallocateRequest */ NTSTATUS StCreateRequest( IN PIRP Irp, IN PVOID Context, IN ULONG Flags, IN PMDL Buffer2, IN ULONG Buffer2Length, IN LARGE_INTEGER Timeout, OUT PTP_REQUEST * TpRequest ) /*++ Routine Description: This routine creates a transport request and associates it with the specified IRP, context, and queue. All major requests, including TdiSend, TdiSendDatagram, TdiReceive, and TdiReceiveDatagram requests, are composed in this manner. Arguments: Irp - Pointer to an IRP which was received by the transport for this request. Context - Pointer to anything to associate this request with. This value is not interpreted except at request cancelation time. Flags - A set of bitflags indicating the disposition of this request. Timeout - Timeout value (if non-zero) to start a timer for this request. If zero, then no timer is activated for the request. TpRequest - If the function returns STATUS_SUCCESS, this will return pointer to the TP_REQUEST structure allocated. Return Value: NTSTATUS - status of operation. --*/ { KIRQL oldirql; PDEVICE_CONTEXT DeviceContext; PTP_REQUEST Request; PLIST_ENTRY p; PIO_STACK_LOCATION irpSp; irpSp = IoGetCurrentIrpStackLocation(Irp); DeviceContext = (PDEVICE_CONTEXT)irpSp->FileObject->DeviceObject; ACQUIRE_SPIN_LOCK (&DeviceContext->SpinLock, &oldirql); p = RemoveHeadList (&DeviceContext->RequestPool); if (p == &DeviceContext->RequestPool) { if ((DeviceContext->RequestMaxAllocated == 0) || (DeviceContext->RequestAllocated < DeviceContext->RequestMaxAllocated)) { StAllocateRequest (DeviceContext, &Request); } else { StWriteResourceErrorLog (DeviceContext, sizeof(TP_REQUEST), 404); Request = NULL; } if (Request == NULL) { ++DeviceContext->RequestExhausted; RELEASE_SPIN_LOCK (&DeviceContext->SpinLock, oldirql); PANIC ("StCreateConnection: Could not allocate request object!\n"); return STATUS_INSUFFICIENT_RESOURCES; } } else { Request = CONTAINING_RECORD (p, TP_REQUEST, Linkage); } ++DeviceContext->RequestInUse; if (DeviceContext->RequestInUse > DeviceContext->RequestMaxInUse) { ++DeviceContext->RequestMaxInUse; } DeviceContext->RequestTotal += DeviceContext->RequestInUse; ++DeviceContext->RequestSamples; RELEASE_SPIN_LOCK (&DeviceContext->SpinLock, oldirql); // // fill out the request. // // Request->Provider = DeviceContext; Request->IoRequestPacket = Irp; Request->Buffer2 = Buffer2; Request->Buffer2Length = Buffer2Length; Request->Flags = Flags; Request->Context = Context; Request->ReferenceCount = 1; // initialize reference count. if ((Timeout.LowPart == 0) && (Timeout.HighPart == 0)) { // no timeout } else { Request->Flags |= REQUEST_FLAGS_TIMER; // there is a timeout on this request. KeInitializeTimer (&Request->Timer); // set to not-signaled state. StReferenceRequest ("Create: timer", Request); // one for the timer KeSetTimer (&Request->Timer, Timeout, &Request->Dpc); } *TpRequest = Request; return STATUS_SUCCESS; } /* StCreateRequest */ VOID StDestroyRequest( IN PTP_REQUEST Request ) /*++ Routine Description: This routine returns a request block to the free pool. Arguments: Request - Pointer to a TP_REQUEST block to return to the free pool. Return Value: NTSTATUS - status of operation. --*/ { KIRQL oldirql; PIO_STACK_LOCATION irpSp; PDEVICE_CONTEXT DeviceContext; // // Return the request to the caller with whatever status is in the IRP. // // // Now dereference the owner of this request so that we are safe when // we finally tear down the {connection, address}. The problem we're // facing here is that we can't allow the user to assume semantics; // the end of life for a connection must truly be the real end of life. // for that to occur, we reference the owning object when the request is // created and we dereference it just before we return it to the pool. // switch (Request->Owner) { case ConnectionType: if (!(Request->Flags & REQUEST_FLAGS_DELAY)) { StDereferenceConnection ("Removing Connection",((PTP_CONNECTION)Request->Context)); } break; case AddressType: StDereferenceAddress ("Removing Address", ((PTP_ADDRESS)Request->Context)); break; case DeviceContextType: StDereferenceDeviceContext ("Removing Address", ((PDEVICE_CONTEXT)Request->Context)); break; } irpSp = IoGetCurrentIrpStackLocation (Request->IoRequestPacket); DeviceContext = Request->Provider; if (Request->Flags & REQUEST_FLAGS_DELAY) { ACQUIRE_SPIN_LOCK (&DeviceContext->SpinLock, &oldirql); InsertTailList( &DeviceContext->IrpCompletionQueue, &Request->IoRequestPacket->Tail.Overlay.ListEntry); } else { IoCompleteRequest (Request->IoRequestPacket, IO_NETWORK_INCREMENT); ACQUIRE_SPIN_LOCK (&DeviceContext->SpinLock, &oldirql); } // // Put the request back on the free list. NOTE: we have the // lock held here. // DeviceContext->RequestTotal += DeviceContext->RequestInUse; ++DeviceContext->RequestSamples; --DeviceContext->RequestInUse; if ((DeviceContext->RequestAllocated - DeviceContext->RequestInUse) > DeviceContext->RequestInitAllocated) { StDeallocateRequest (DeviceContext, Request); } else { InsertTailList (&DeviceContext->RequestPool, &Request->Linkage); } RELEASE_SPIN_LOCK (&DeviceContext->SpinLock, oldirql); } /* StDestroyRequest */ VOID StRefRequest( IN PTP_REQUEST Request ) /*++ Routine Description: This routine increments the reference count on a transport request. Arguments: Request - Pointer to a TP_REQUEST block. Return Value: none. --*/ { ASSERT (Request->ReferenceCount > 0); InterlockedIncrement (&Request->ReferenceCount); } /* StRefRequest */ VOID StDerefRequest( IN PTP_REQUEST Request ) /*++ Routine Description: This routine dereferences a transport request by decrementing the reference count contained in the structure. If, after being decremented, the reference count is zero, then this routine calls StDestroyRequest to remove it from the system. Arguments: Request - Pointer to a transport request object. Return Value: none. --*/ { LONG result; result = InterlockedDecrement (&Request->ReferenceCount); ASSERT (result >= 0); // // If we have deleted all references to this request, then we can // destroy the object. It is okay to have already released the spin // lock at this point because there is no possible way that another // stream of execution has access to the request any longer. // if (result == 0) { StDestroyRequest (Request); } } /* StDerefRequest */ VOID StCompleteRequest( IN PTP_REQUEST Request, IN NTSTATUS Status, IN ULONG Information ) /*++ Routine Description: This routine completes a transport request object, completing the I/O, stopping the timeout, and freeing up the request object itself. Arguments: Request - Pointer to a transport request object. Status - Actual return status to be assigned to the request. This value may be overridden if the timed-out bitflag is set in the request. Information - the information field for the I/O Status Block. Return Value: none. --*/ { KIRQL oldirql; PIRP Irp; NTSTATUS FinalStatus = Status; BOOLEAN TimerWasSet; ASSERT (Status != STATUS_PENDING); if (Request->Flags & REQUEST_FLAGS_SEND_RCV) { // // Sends and receives we check for since we know // they don't have timers and should only complete // once. // Request->Flags |= REQUEST_FLAGS_STOPPING; Irp = Request->IoRequestPacket; Irp->IoStatus.Status = FinalStatus; Irp->IoStatus.Information = Information; StDereferenceRequest ("Complete", Request); // remove creation reference. return; } ACQUIRE_SPIN_LOCK (&Request->SpinLock, &oldirql); if ((Request->Flags & REQUEST_FLAGS_STOPPING) == 0) { Request->Flags |= REQUEST_FLAGS_STOPPING; // // Cancel the pending timeout on this request. Not all requests // have their timer set. If this request has the TIMER bit set, // then the timer needs to be cancelled. If it cannot be cancelled, // then the timer routine will be run, so we just return and let // the timer routine worry about cleaning up this request. // if ((Request->Flags & REQUEST_FLAGS_TIMER) != 0) { Request->Flags &= ~REQUEST_FLAGS_TIMER; RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); TimerWasSet = KeCancelTimer (&Request->Timer); if (TimerWasSet) { StDereferenceRequest ("Complete: stop timer", Request); } } else { RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); } Irp = Request->IoRequestPacket; // // Install the return code in the IRP so that when we call StDestroyRequest, // it will get completed with the proper return status. // Irp->IoStatus.Status = FinalStatus; Irp->IoStatus.Information = Information; // // The entire transport is done with this request. // StDereferenceRequest ("Complete", Request); // remove creation reference. } else { RELEASE_SPIN_LOCK (&Request->SpinLock, oldirql); } } /* StCompleteRequest */ VOID StRefSendIrp( IN PIO_STACK_LOCATION IrpSp ) /*++ Routine Description: This routine increments the reference count on a send IRP. Arguments: IrpSp - Pointer to the IRP's stack location. Return Value: none. --*/ { ASSERT (IRP_REFCOUNT(IrpSp) > 0); InterlockedIncrement (&IRP_REFCOUNT(IrpSp)); } /* StRefSendIrp */ VOID StDerefSendIrp( IN PIO_STACK_LOCATION IrpSp ) /*++ Routine Description: This routine dereferences a transport send IRP by decrementing the reference count contained in the structure. If, after being decremented, the reference count is zero, then this routine calls IoCompleteRequest to actually complete the IRP. NOTE: This assume that IRP_CONNECTION(IrpSp) has been changed to point to the IRP instead of the connection. Arguments: Request - Pointer to a transport send IRP's stack location. Return Value: none. --*/ { LONG result; result = InterlockedDecrement (&IRP_REFCOUNT(IrpSp)); ASSERT (result >= 0); // // If we have deleted all references to this request, then we can // destroy the object. It is okay to have already released the spin // lock at this point because there is no possible way that another // stream of execution has access to the request any longer. // if (result == 0) { PIRP Irp = (PIRP)IRP_CONNECTION(IrpSp); IRP_REFCOUNT(IrpSp) = 0; IRP_CONNECTION (IrpSp) = NULL; IoCompleteRequest (Irp, IO_NETWORK_INCREMENT); } } /* StDerefSendIrp */ VOID StCompleteSendIrp( IN PIRP Irp, IN NTSTATUS Status, IN ULONG Information ) /*++ Routine Description: This routine completes a transport send IRP. Arguments: Irp - Pointer to a send IRP. Status - Actual return status to be assigned to the request. This value may be overridden if the timed-out bitflag is set in the request. Information - the information field for the I/O Status Block. Return Value: none. --*/ { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PTP_CONNECTION Connection; ASSERT (Status != STATUS_PENDING); Connection = IRP_CONNECTION(IrpSp); // // Sends and receives we check for since we know // they don't have timers and should only complete // once. // Irp->IoStatus.Status = Status; Irp->IoStatus.Information = Information; IRP_CONNECTION(IrpSp) = Irp; StDereferenceSendIrp ("Complete", IrpSp); // remove creation reference. StDereferenceConnection ("Removing Connection", Connection); } /* StCompleteSendIrp */