/*++ Copyright (c) 2000 Microsoft Corporation Module Name: send.c Abstract: This module contains the code that is very specific to initialization and unload operations in the irenum driver Author: Brian Lieuallen, 7-13-2000 Environment: Kernel mode Revision History : --*/ #include "internal.h" VOID RemoveReferenceForBuffers( PSEND_TRACKER SendTracker ); VOID ProcessSend( PTDI_CONNECTION Connection ); VOID ProcessSendAtPassive( PTDI_CONNECTION Connection ); VOID SendBufferToTdi( PFILE_OBJECT FileObject, PIRCOMM_BUFFER Buffer ); NTSTATUS SendCompletion( PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context ); VOID TryToCompleteCurrentIrp( PSEND_TRACKER SendTracker ); VOID RemoveReferenceOnTracker( PSEND_TRACKER SendTracker ) { LONG Count; Count=InterlockedDecrement(&SendTracker->ReferenceCount); if (Count == 0) { REMOVE_REFERENCE_TO_CONNECTION(SendTracker->Connection); FREE_POOL(SendTracker); } return; } PIRP GetCurrentIrpAndAddReference( PSEND_TRACKER SendTracker ) { KIRQL OldIrql; PIRP Irp; KeAcquireSpinLock( &SendTracker->Connection->Send.ControlLock, &OldIrql ); Irp=SendTracker->CurrentWriteIrp; if (Irp != NULL) { // // irp is still around, add a ref coun to keep it around for a while. // SendTracker->IrpReferenceCount++; } KeReleaseSpinLock( &SendTracker->Connection->Send.ControlLock, OldIrql ); return Irp; } VOID ReleaseIrpReference( PSEND_TRACKER SendTracker ) { KIRQL OldIrql; PIRP Irp=NULL; CONNECTION_CALLBACK Callback=NULL; PVOID Context; KeAcquireSpinLock( &SendTracker->Connection->Send.ControlLock, &OldIrql ); SendTracker->IrpReferenceCount--; if (SendTracker->IrpReferenceCount==0) { // // done, with irp complete it now with the current status // Irp=SendTracker->CurrentWriteIrp; SendTracker->CurrentWriteIrp=NULL; Callback=SendTracker->CompletionRoutine; Context=SendTracker->CompletionContext; #if DBG SendTracker->CompletionRoutine=NULL; #endif SendTracker->Connection->Send.CurrentSendTracker=NULL; } KeReleaseSpinLock( &SendTracker->Connection->Send.ControlLock, OldIrql ); if (Irp != NULL) { // // The ref count has gone to zero, complete the irp // (Callback)( Context, Irp ); // // release the reference for the irp // RemoveReferenceOnTracker(SendTracker); } return; } VOID SetIrpAndRefcounts( PSEND_TRACKER SendTracker, PIRP Irp ) { // // set the tracker refcount to 2, one for the irp, and one for the rountine that called this // SendTracker->ReferenceCount=2; // // Set the irp count to one for the rountine that called this, it will release when it done // setting up the tracker block // SendTracker->IrpReferenceCount=1; // // save the irp // SendTracker->CurrentWriteIrp=Irp; return; } VOID SendTimerProc( PKDPC Dpc, PVOID Context, PVOID SystemParam1, PVOID SystemParam2 ) { PSEND_TRACKER SendTracker=Context; PIRP Irp; KIRQL OldIrql; D_ERROR(DbgPrint("IRCOMM: SendTimerProc\n");) ASSERT(SendTracker->TimerSet); #if DBG SendTracker->TimerExpired=TRUE; #endif SendTracker->TimerSet=FALSE; // // try to get a hold of the irp so we can set the status // Irp=GetCurrentIrpAndAddReference(SendTracker); Irp->IoStatus.Status=STATUS_TIMEOUT; // // release on reference for the one we just added // ReleaseIrpReference(SendTracker); TryToCompleteCurrentIrp( SendTracker ); // // release the second reference for the timer being set in the first place // ReleaseIrpReference(SendTracker); return; } VOID SendCancelRoutine( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PSEND_TRACKER SendTracker=Irp->Tail.Overlay.DriverContext[0]; KIRQL OldIrql; D_ERROR(DbgPrint("IRCOMM: SendCancelRoutine\n");) #if DBG SendTracker->IrpCanceled=TRUE; #endif IoReleaseCancelSpinLock(Irp->CancelIrql); Irp->IoStatus.Status=STATUS_CANCELLED; // // clean up the timer // TryToCompleteCurrentIrp(SendTracker); // // release the reference held for the cancel routine // ReleaseIrpReference(SendTracker); // // send tracker maybe freed at this point // return; } VOID TryToCompleteCurrentIrp( PSEND_TRACKER SendTracker ) { KIRQL OldIrql; PVOID OldCancelRoutine=NULL; PIRP Irp; BOOLEAN TimerCanceled=FALSE; Irp=GetCurrentIrpAndAddReference(SendTracker); KeAcquireSpinLock( &SendTracker->Connection->Send.ControlLock, &OldIrql ); if (SendTracker->TimerSet) { TimerCanceled=KeCancelTimer( &SendTracker->Timer ); if (TimerCanceled) { // // We ended up canceling the timer // SendTracker->TimerSet=FALSE; } else { // // The timer is already running, we will just let it complete // and do the clean up. // } } if (Irp != NULL) { // // the irp has not already been completed // OldCancelRoutine=IoSetCancelRoutine( Irp, NULL ); } KeReleaseSpinLock( &SendTracker->Connection->Send.ControlLock, OldIrql ); if (TimerCanceled) { // // we canceled the timer before it could run, remove the reference for it // ReleaseIrpReference(SendTracker); } if (Irp != NULL) { if (OldCancelRoutine != NULL) { // // since the cancel rountine had not run, release its reference to the irp // ReleaseIrpReference(SendTracker); } // // if this routine got the irp, release the reference to it // ReleaseIrpReference(SendTracker); } return; } VOID SendOnConnection( IRDA_HANDLE Handle, PIRP Irp, CONNECTION_CALLBACK Callback, PVOID Context, ULONG Timeout ) { PTDI_CONNECTION Connection=Handle; PIO_STACK_LOCATION IrpSp=IoGetCurrentIrpStackLocation(Irp); PSEND_TRACKER SendTracker; BOOLEAN AlreadyCanceled; KIRQL OldIrql; KIRQL OldCancelIrql; if (Connection->Send.CurrentSendTracker != NULL) { // // called when we already have an irp // (Callback)( Context, Irp ); return; } SendTracker=ALLOCATE_NONPAGED_POOL(sizeof(*SendTracker)); if (SendTracker == NULL) { Irp->IoStatus.Status=STATUS_INSUFFICIENT_RESOURCES; (Callback)( Context, Irp ); return; } RtlZeroMemory(SendTracker,sizeof(*SendTracker)); KeInitializeTimer( &SendTracker->Timer ); KeInitializeDpc( &SendTracker->TimerDpc, SendTimerProc, SendTracker ); // // set the irp and initialize the refcounts // SetIrpAndRefcounts(SendTracker,Irp); ADD_REFERENCE_TO_CONNECTION(Connection); // // initialize these values // SendTracker->Connection=Connection; SendTracker->BuffersOutstanding=0; SendTracker->CompletionContext = Context; SendTracker->CompletionRoutine = Callback; SendTracker->BytesRemainingInIrp = IrpSp->Parameters.Write.Length; if (Timeout > 0) { // // add a reference for the timer // GetCurrentIrpAndAddReference(SendTracker); } // // add a reference to the irp for the cancel rountine // GetCurrentIrpAndAddReference(SendTracker); KeAcquireSpinLock( &Connection->Send.ControlLock, &OldIrql ); Connection->Send.CurrentSendTracker=SendTracker; if (Timeout > 0) { // // need to set a timer // LARGE_INTEGER DueTime; DueTime.QuadPart= (LONGLONG)(Timeout+100) * -10000; SendTracker->TimerSet=TRUE; KeSetTimer( &SendTracker->Timer, DueTime, &SendTracker->TimerDpc ); } Irp->Tail.Overlay.DriverContext[0]=SendTracker; IoAcquireCancelSpinLock(&OldCancelIrql); AlreadyCanceled=Irp->Cancel; if (!AlreadyCanceled) { PIRCOMM_BUFFER Buffer; // // the irp has not been canceled already, set the cancel routine // IoSetCancelRoutine( Irp, SendCancelRoutine ); } else { // // it was canceled when we got it // Irp->IoStatus.Status=STATUS_CANCELLED; } IoReleaseCancelSpinLock(OldCancelIrql); KeReleaseSpinLock( &Connection->Send.ControlLock, OldIrql ); if (AlreadyCanceled) { // // The irp has already been canceled, just call the cancel rountine so the normal code runs // D_ERROR(DbgPrint("IRCOMM: SendOnConnection: irp already canceled\n");) IoAcquireCancelSpinLock(&Irp->CancelIrql); SendCancelRoutine( NULL, Irp ); // // the cancel rountine will release the cancel spinlock // } // // release the reference for this routine // ReleaseIrpReference(SendTracker); ProcessSendAtPassive(Connection); RemoveReferenceOnTracker(SendTracker); return; } VOID ProcessSendAtPassive( PTDI_CONNECTION Connection ) { if (KeGetCurrentIrql() < DISPATCH_LEVEL) { // // less than dispatch, just call directly // ProcessSend(Connection); } else { // // Called at dispatch, queue the work item // LONG Count=InterlockedIncrement(&Connection->Send.WorkItemCount); if (Count == 1) { ExQueueWorkItem(&Connection->Send.WorkItem,CriticalWorkQueue); } } return; } VOID SendWorkItemRountine( PVOID Context ) { PTDI_CONNECTION Connection=Context; // // the work item has run set the count to zero // InterlockedExchange(&Connection->Send.WorkItemCount,0); ProcessSend(Connection); } VOID ProcessSend( PTDI_CONNECTION Connection ) { PSEND_TRACKER SendTracker; PIRP Irp; PIO_STACK_LOCATION IrpSp; PLIST_ENTRY ListEntry; ULONG BytesUsedInBuffer; PIRCOMM_BUFFER Buffer; BOOLEAN ExitLoop; PFILE_OBJECT FileObject; CONNECTION_HANDLE ConnectionHandle; KIRQL OldIrql; KeAcquireSpinLock( &Connection->Send.ControlLock, &OldIrql ); if (Connection->Send.ProcessSendEntryCount == 0) { Connection->Send.ProcessSendEntryCount++; while ((Connection->Send.CurrentSendTracker != NULL) && (!Connection->Send.OutOfBuffers) && (Connection->LinkUp) && (Connection->Send.CurrentSendTracker->BytesRemainingInIrp > 0)) { SendTracker=Connection->Send.CurrentSendTracker; InterlockedIncrement(&SendTracker->ReferenceCount); KeReleaseSpinLock( &Connection->Send.ControlLock, OldIrql ); Irp=GetCurrentIrpAndAddReference(SendTracker); if (Irp != NULL) { // // got the current irp // IrpSp=IoGetCurrentIrpStackLocation(Irp); ConnectionHandle=GetCurrentConnection(Connection->LinkHandle); if (ConnectionHandle != NULL) { // // we have a good connection // FileObject=ConnectionGetFileObject(ConnectionHandle); Buffer=ConnectionGetBuffer(ConnectionHandle,BUFFER_TYPE_SEND); if (Buffer != NULL) { LONG BytesToCopy=min(SendTracker->BytesRemainingInIrp, (LONG)Buffer->BufferLength - 1); // // this buffer is going to be outstanding, set this before the bytes // remaining count goes to zero // InterlockedIncrement(&SendTracker->BuffersOutstanding); // // start with a zero length of control bytes // Buffer->Data[0]=0; // // actual data starts one byte in, after the length byte // // move the data // RtlCopyMemory( &Buffer->Data[1], (PUCHAR)Irp->AssociatedIrp.SystemBuffer+(IrpSp->Parameters.Write.Length - SendTracker->BytesRemainingInIrp), BytesToCopy ); // // the count has to include the control byte // Buffer->Mdl->ByteCount= 1 + BytesToCopy; #if DBG RtlFillMemory( &Buffer->Data[Buffer->Mdl->ByteCount], Buffer->BufferLength-Buffer->Mdl->ByteCount, 0xfb ); #endif InterlockedExchangeAdd(&SendTracker->BytesRemainingInIrp, -BytesToCopy); Buffer->Mdl->Next=NULL; Buffer->Context=SendTracker; InterlockedIncrement(&SendTracker->ReferenceCount); ASSERT(SendTracker->CurrentWriteIrp != NULL); SendBufferToTdi( FileObject, Buffer ); #if DBG Buffer=NULL; #endif } else { // // No more buffers availible // Connection->Send.OutOfBuffers=TRUE; } ConnectionReleaseFileObject(ConnectionHandle,FileObject); ReleaseConnection(ConnectionHandle); } else { // // The connection, must be down // D_ERROR(DbgPrint("IRCOMM: ProcessSend: Link down\n");) Connection->LinkUp=FALSE; } ReleaseIrpReference(SendTracker); } else { // // the irp has already been completed from this tracking block // D_ERROR(DbgPrint("IRCOMM: ProcessSend: no irp\n");) ASSERT(SendTracker->TimerExpired || SendTracker->IrpCanceled || SendTracker->SendAborted); } RemoveReferenceOnTracker(SendTracker); KeAcquireSpinLock( &Connection->Send.ControlLock, &OldIrql ); } // while Connection->Send.ProcessSendEntryCount--; } KeReleaseSpinLock( &Connection->Send.ControlLock, OldIrql ); return; } VOID SendBufferToTdi( PFILE_OBJECT FileObject, PIRCOMM_BUFFER Buffer ) { PDEVICE_OBJECT IrdaDeviceObject=IoGetRelatedDeviceObject(FileObject); ULONG SendLength; IoReuseIrp(Buffer->Irp,STATUS_SUCCESS); Buffer->Irp->Tail.Overlay.OriginalFileObject = FileObject; SendLength = MmGetMdlByteCount(Buffer->Mdl); TdiBuildSend( Buffer->Irp, IrdaDeviceObject, FileObject, SendCompletion, Buffer, Buffer->Mdl, 0, // send flags SendLength ); IoCallDriver(IrdaDeviceObject, Buffer->Irp); return; } NTSTATUS SendCompletion( PDEVICE_OBJECT DeviceObject, PIRP BufferIrp, PVOID Context ) { PIRCOMM_BUFFER Buffer=Context; PSEND_TRACKER SendTracker=Buffer->Context; PTDI_CONNECTION Connection=SendTracker->Connection; LONG BuffersOutstanding; // // save off the status for the sub transerfer // SendTracker->LastStatus=BufferIrp->IoStatus.Status; D_TRACE(DbgPrint("IRCOMM: SendComplete: Status=%08lx, len=%d\n",BufferIrp->IoStatus.Status,BufferIrp->IoStatus.Information);) #if DBG RtlFillMemory( &Buffer->Data[0], Buffer->BufferLength, 0xfe ); #endif // // return the buffer // Buffer->FreeBuffer(Buffer); Connection->Send.OutOfBuffers=FALSE; #if DBG Buffer=NULL; BufferIrp=NULL; #endif BuffersOutstanding=InterlockedDecrement(&SendTracker->BuffersOutstanding); if ((BuffersOutstanding == 0) && (SendTracker->BytesRemainingInIrp == 0)) { // // All of the data in the irp has been sent and all of the irps send to // the irda stack have completed // // Done with the irp in this tracker // PIRP Irp; PIO_STACK_LOCATION IrpSp; Irp=GetCurrentIrpAndAddReference(SendTracker); if (Irp != NULL) { IrpSp=IoGetCurrentIrpStackLocation(Irp); Irp->IoStatus.Information=IrpSp->Parameters.Write.Length; Irp->IoStatus.Status=SendTracker->LastStatus; ReleaseIrpReference(SendTracker); TryToCompleteCurrentIrp(SendTracker); } else { // // the irp has already been completed from this tracking block // D_ERROR(DbgPrint("IRCOMM: SendCompletion: no irp\n");) // // this should only happen if the timer expired or the irp was canceled // ASSERT(SendTracker->TimerExpired || SendTracker->IrpCanceled || SendTracker->SendAborted); } } RemoveReferenceOnTracker(SendTracker); ProcessSendAtPassive(Connection); return STATUS_MORE_PROCESSING_REQUIRED; } VOID AbortSend( IRDA_HANDLE Handle ) { PTDI_CONNECTION Connection=Handle; PSEND_TRACKER SendTracker=NULL; KIRQL OldIrql; KeAcquireSpinLock( &Connection->Send.ControlLock, &OldIrql ); SendTracker=Connection->Send.CurrentSendTracker; if (SendTracker != NULL) { InterlockedIncrement(&SendTracker->ReferenceCount); } KeReleaseSpinLock( &Connection->Send.ControlLock, OldIrql ); if (SendTracker != NULL) { #if DBG SendTracker->SendAborted=TRUE; #endif TryToCompleteCurrentIrp(SendTracker); RemoveReferenceOnTracker(SendTracker); } return; }