/*++ ***************************************************************************** * * * This software contains proprietary and confidential information of * * * * Digi International Inc. * * * * By accepting transfer of this copy, Recipient agrees to retain this * * software in confidence, to prevent disclosure to others, and to make * * no use of this software other than that for which it was delivered. * * This is an unpublished copyrighted work of Digi International Inc. * * Except as permitted by federal law, 17 USC 117, copying is strictly * * prohibited. * * * ***************************************************************************** Module Name: misc.c Abstract: Revision History: * $Log: /Components/Windows/NT/Async/FEP5/MISC.C $ * * 4 3/05/96 6:27p Stana * Bugfix: When DigiCancelIrpQueue cancels its first read irp, if another * read irp is immediately issued by the app, it also gets cancelled. * This can actually go on forever, but usually stops within a few hours. * Now I have a spinlock (NewIrpLock) to prevent this. * * 1 3/04/96 12:18p Stana * Misc. functions required to help with NT issues such as Multi-processor * support, timing problems, and other such things. * * Revision 1.15.1.5 1995/11/28 12:48:26 dirkh * Adopt common header file. * * Revision 1.15.1.4 1995/10/04 18:25:10 dirkh * DevExt->XcPreview must be reset when pXoffCounter is cleared. (Not sure why...) * * Revision 1.15.1.3 1995/09/19 12:49:52 dirkh * Add IOCTL_SERIAL_XOFF_COUNTER support: * { * DigiStartIrpRequest (if WRITE or XOFF_COUNTER is queued behind a *transmitted* XOFF_COUNTER, complete the transmitted XOFF_COUNTER). * DigiTryToCompleteIrp (if IRP is XOFF_COUNTER, clear DevExt->pXoffCounter). * } * Simplify interface to DigiCancelIrpQueue. * * Revision 1.15.1.2 1995/09/05 16:59:38 dirkh * DigiTryToCompleteIrp: Fix recovery from failed assertion. * * Revision 1.15.1.1 1995/09/05 14:28:18 dirkh * General: Minimize IoCancel spin lock window. * General: Eliminate special handling for "fast RAS" flush IRP. (It's now fully realized for IoCompleteRequest.) * DigiStartIrpRequest queues IMMEDIATE_CHAR IRPs at the head of the queue, others at the tail. * DigiCancelQueuedIrp holds lock to fix DevExt->TotalCharsQueued. * DigiRundownIrpRefs kills the cancel routine only if that's the only reference left. * * Revision 1.15 1995/04/19 13:09:27 rik * Added an undeclared local variable. * * Revision 1.14 1995/04/18 18:15:05 rik * Fixed potential timing hole with canceling an Irp in the generic cancel * routine. * * Revision 1.13 1995/04/12 14:42:04 rik * Take into account self-inserted flush irp's being cancelled. * * Revision 1.12 1994/09/13 07:40:58 rik * Added debug tracking output for cancel irps. * * Revision 1.11 1993/06/14 14:42:22 rik * Tightened up some spinlock windows, and fixed a problem with how * I was calling the startroutine in the DigiTryToCompleteIrp routine. * * Revision 1.10 1993/06/06 14:17:03 rik * Tightened up windows in the code which were causing problems. Primarily, * changes were in the functions DigiTryToCompleteIrp, and DigiCancelIrpQueue. * I use Cancel spinlocks more rigoursly to help eliminate windows which were * seen on multi-processor machines. The problem could also happen on * uni-processor machines, depending on which IRQL level the requests were * done at. * * Revision 1.9 1993/05/18 05:08:00 rik * Fixed spinlock problems where the device extension wasn't being protected * by its spinlock. As a result, on multi-processor machines, the device * extension was being changed when it was being accessed by the other * processor causing faults. * * Revision 1.8 1993/05/09 09:22:11 rik * Added debugging output for completing IRP. * * Revision 1.7 1993/03/08 07:23:04 rik * Changed how I handle read/write/wait IRPs now. Instead of always marking * the IRP, I have changed it such that I only mark an IRP pending if the * value from the start routine is STATUS_PENDING or if there is all ready * and outstanding IRP(s) present on the appropriate queue. * * Revision 1.6 1993/02/25 19:09:58 rik * Added debugging for tracing IRPs better. * * Revision 1.5 1993/02/04 12:23:40 rik * ?? * * Revision 1.4 1993/01/28 10:36:44 rik * Updated function to always return STATUS_PENDING since I always IRP requests * status pending. This is a new requirement for NT build 354. * * Revision 1.3 1993/01/22 12:36:10 rik * *** empty log message *** * * Revision 1.2 1992/12/10 16:12:08 rik * Reorganized function names to better reflect how they are used through out * the driver. * * Revision 1.1 1992/11/12 12:50:59 rik * Initial revision * --*/ #include "header.h" #ifndef _MISC_DOT_C # define _MISC_DOT_C static char RCSInfo_MiscDotC[] = "$Header: /Components/Windows/NT/Async/FEP5/MISC.C 4 3/05/96 6:27p Stana $"; #endif /****************************************************************************/ /* Local Prototypes */ /****************************************************************************/ void __inline DigiRundownIrpRefs( IN PIRP Irp, IN PKTIMER IntervalTimer OPTIONAL, IN PKTIMER TotalTimer OPTIONAL ); VOID DigiCancelQueuedIrp( PDEVICE_OBJECT DeviceObject, PIRP Irp ); NTSTATUS DigiStartIrpRequest( IN PDIGI_CONTROLLER_EXTENSION ControllerExt, IN PDIGI_DEVICE_EXTENSION DeviceExt, IN PLIST_ENTRY Queue, IN PIRP Irp, IN PDIGI_START_ROUTINE StartRoutine ) /*++ Routine Description: Arguments: ControllerExt - Pointer to the controller object extension associated with this device. DeviceExt - Pointer to the device object extension for this device. Queue - The queue of Irp requests. StartRoutine - The routine to call if the queue is empty. ( i.e. if this is the first request possibly ). Return Value: --*/ { PIO_STACK_LOCATION IrpSp; KIRQL OldIrql; NTSTATUS Status; BOOLEAN EmptyList; DigiDump( DIGIFLOW, ("Entering DigiStartIrpRequest\n") ); KeAcquireSpinLock( &DeviceExt->NewIrpLock, &OldIrql ); KeReleaseSpinLock( &DeviceExt->NewIrpLock, OldIrql ); IrpSp = IoGetCurrentIrpStackLocation( Irp ); KeAcquireSpinLock( &DeviceExt->ControlAccess, &OldIrql); // If we enqueue a WRITE or XOFF_COUNTER behind a transmitted XOFF_COUNTER, // then the transmitted XOFF_COUNTER must be completed immediately. if( Queue == &DeviceExt->WriteQueue && !IsListEmpty( Queue ) && Queue->Flink->Flink == Queue // only one IRP on the queue && ( IrpSp->MajorFunction == IRP_MJ_WRITE || ( IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_XOFF_COUNTER && IrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL ) ) ) { PIRP HeadIrp = CONTAINING_RECORD( Queue->Flink, IRP, Tail.Overlay.ListEntry ); if( HeadIrp->IoStatus.Information == 1 ) // XOFF_COUNTER has been transmitted { PIO_STACK_LOCATION HeadIrpSp = IoGetCurrentIrpStackLocation( HeadIrp ); if( HeadIrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_XOFF_COUNTER && HeadIrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL ) { DigiDump( (DIGIWRITE|DIGIIRP|DIGIDIAG1), ("DigiStartIrpRequest is absorbing transmitted XOFF_COUNTER.\n") ); // Absorb the XOFF_COUNTER. DigiTryToCompleteIrp( DeviceExt, &OldIrql, STATUS_SERIAL_MORE_WRITES, Queue, NULL, &DeviceExt->WriteRequestTotalTimer, StartRoutine ); // It's possible that some other IRP sneaked in ahead of us... KeAcquireSpinLock( &DeviceExt->ControlAccess, &OldIrql); } } } // WRITE or XOFF_COUNTER is being added to non-empty WriteQueue // IMMEDIATE_CHAR is queued at the head, all others at the tail of Queue. if( IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_IMMEDIATE_CHAR && IrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL ) { ASSERT( Queue == &DeviceExt->WriteQueue ); DeviceExt->TotalCharsQueued++; EmptyList = TRUE; // Force StartRoutine to be called. // DH Change cancel routine on former head IRP, and avoid re-sending or starving IRP. InsertHeadList( Queue, &Irp->Tail.Overlay.ListEntry ); } else { if( IrpSp->MajorFunction == IRP_MJ_WRITE ) { ASSERT( Queue == &DeviceExt->WriteQueue ); DeviceExt->TotalCharsQueued += IrpSp->Parameters.Write.Length; } else if( IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_XOFF_COUNTER && IrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL ) { ASSERT( Queue == &DeviceExt->WriteQueue ); DeviceExt->TotalCharsQueued++; } EmptyList = IsListEmpty( Queue ); InsertTailList( Queue, &Irp->Tail.Overlay.ListEntry ); } // Mark IRP as "never pending" to advise priority boost on IRP completion. Irp->IoStatus.Status = STATUS_SUCCESS; if( EmptyList ) { DigiDump( DIGIFLOW, (" Calling Starter Routine\n") ); Status = StartRoutine( ControllerExt, DeviceExt, &OldIrql ); if( Status == STATUS_PENDING ) { ASSERT( Irp->CancelRoutine != NULL ); // StartRoutine should have set this. Irp->IoStatus.Status = Status; // STATUS_PENDING DigiIoMarkIrpPending( Irp ); } } else // The IRP will be started later. { KIRQL OldCancelIrql; DigiDump( DIGIFLOW, (" Queuing the Irp\n") ); Irp->IoStatus.Status = Status = STATUS_PENDING; DigiIoMarkIrpPending( Irp ); IoAcquireCancelSpinLock( &OldCancelIrql ); IoSetCancelRoutine( Irp, DigiCancelQueuedIrp ); IoReleaseCancelSpinLock( OldCancelIrql ); } DigiDump( DIGIFLOW, ("Exiting DigiStartIrpRequest\n") ); KeReleaseSpinLock( &DeviceExt->ControlAccess, OldIrql ); return( Status ); } // end DigiStartIrpRequest VOID DigiCancelQueuedIrp( PDEVICE_OBJECT DeviceObject, PIRP Irp ) /*++ Routine Description: This routine is used to cancel Irps on the queue which are NOT the head of the queue. I assume the head entry is the current Irp. Arguments: DeviceExt - Pointer to the device object for this device. Irp - Pointer to the IRP to be cancelled. Return Value: None. --*/ { PIO_STACK_LOCATION IrpSp; PDIGI_DEVICE_EXTENSION DeviceExt; KIRQL OldIrql; IoReleaseCancelSpinLock( Irp->CancelIrql ); DigiDump( (DIGIFLOW|DIGICANCELIRP), ("Canceling Queued Irp 0x%x\n", Irp) ); IrpSp = IoGetCurrentIrpStackLocation(Irp); DeviceExt = DeviceObject->DeviceExtension; KeAcquireSpinLock( &DeviceExt->ControlAccess, &OldIrql ); RemoveEntryList( &Irp->Tail.Overlay.ListEntry ); if( IrpSp->MajorFunction == IRP_MJ_WRITE ) { DeviceExt->TotalCharsQueued -= IrpSp->Parameters.Write.Length; } else if( IrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL && ( IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_IMMEDIATE_CHAR || IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_SERIAL_XOFF_COUNTER ) ) { DeviceExt->TotalCharsQueued--; } KeReleaseSpinLock( &DeviceExt->ControlAccess, OldIrql ); Irp->IoStatus.Status = STATUS_CANCELLED; Irp->IoStatus.Information = 0; DigiIoCompleteRequest( Irp, IO_NO_INCREMENT ); DigiDump( DIGIFLOW, ("Exiting DigiCancelQueuedIrp\n") ); } // end DigiCancelQueuedIrp VOID DigiTryToCompleteIrp( PDIGI_DEVICE_EXTENSION DeviceExt, PKIRQL pOldIrql, NTSTATUS StatusToUse, PLIST_ENTRY Queue, PKTIMER IntervalTimer, PKTIMER TotalTimer, PDIGI_START_ROUTINE StartRoutine ) /*++ Routine Description: Arguments: DeviceExt - Pointer to the device object for this device. Irp - Pointer to the IRP to be cancelled. Return Value: None. --*/ { PIRP Irp; DigiDump( DIGIFLOW, ("Entering DigiTryToCompleteIrp\n") ); if( IsListEmpty( Queue ) ) { ASSERT( !IsListEmpty( Queue ) ); KeReleaseSpinLock( &DeviceExt->ControlAccess, *pOldIrql ); return; } Irp = CONTAINING_RECORD( Queue->Flink, IRP, Tail.Overlay.ListEntry ); // // We can decrement the reference to "remove" the fact // that the caller no longer will be accessing this irp. // DigiDump( DIGIREFERENCE, (" Dec Ref for entering\n") ); DIGI_DEC_REFERENCE( Irp ); // // Try to run down all other references to this irp. // DigiRundownIrpRefs( Irp, IntervalTimer, TotalTimer ); // // See if the ref count is zero after trying to kill everybody else. // if( !DIGI_REFERENCE_COUNT( Irp ) ) { BOOLEAN discoveredIrp; PIO_STACK_LOCATION IrpSp; #if DBG LONG Extra; char const *IrpType, ReadIrp[] = "read", WriteIrp[] = "write", FlushIrp[] = "flush", IoctlIrp[] = "ioctl", UnknownIrp[] = "unknown"; #endif // // The ref count was zero so we should complete this // request. // DigiDump( DIGIREFERENCE, (" Completing Irp!\n") ); RemoveHeadList( Queue ); // Race to start IRPs: user mode (Serial*) vs. DPC routines // // When DigiStartIrpRequest adds an IRP to an empty queue, it starts the IRP. // If we uncover/discover an IRP, DigiStartIrpRequest(s) will not see // an empty queue while the lock is down, so it(they) will not start the IRP. // Thus, if the queue is not empty now, we must start the IRP. // // We don't race ourselves because only one DigiTryToCompleteIrp wins (completes) // and there never are any references to "buried" IRPs that would trigger // completions and starts. if( StartRoutine ) discoveredIrp = !IsListEmpty( Queue ); else discoveredIrp = FALSE; if( Queue == &DeviceExt->WriteQueue && DeviceExt->pXoffCounter ) { ASSERT( DeviceExt->pXoffCounter == Irp->AssociatedIrp.SystemBuffer ); DeviceExt->pXoffCounter = NULL; #if 1 // DBG DH necessary, but haven't figured out why DeviceExt->XcPreview = 0; // Looks a little nicer... #endif } KeReleaseSpinLock( &DeviceExt->ControlAccess, *pOldIrql ); Irp->IoStatus.Status = StatusToUse; if( StatusToUse == STATUS_CANCELLED ) Irp->IoStatus.Information = 0; #if DBG IrpSp = IoGetCurrentIrpStackLocation( Irp ); switch ( IrpSp->MajorFunction ) { case IRP_MJ_READ: IrpType = ReadIrp; Extra = IrpSp->Parameters.Read.Length; if (Irp->IoStatus.Information>IrpSp->Parameters.Read.Length) { DbgPrint("Returning too much data! Asked for (%d) gave (%d).\n", IrpSp->Parameters.Read.Length, Irp->IoStatus.Information); DbgBreakPoint(); } break; case IRP_MJ_WRITE: IrpType = WriteIrp; Extra = IrpSp->Parameters.Write.Length; break; case IRP_MJ_FLUSH_BUFFERS: IrpType = FlushIrp; Extra = -1; break; case IRP_MJ_DEVICE_CONTROL: IrpType = IoctlIrp; Extra = IrpSp->Parameters.DeviceIoControl.IoControlCode; break; default: IrpType = UnknownIrp; Extra = IrpSp->MajorFunction; break; } DigiDump( (DIGIFLOW|DIGIREAD|DIGIWRITE|DIGIIRP|DIGIWAIT|DIGIREFERENCE), ("Completing %s(%d) IRP 0x%x, Status = 0x%.8x, Information = %u\n", IrpType, Extra, Irp, Irp->IoStatus.Status, Irp->IoStatus.Information ) ); #endif if (StatusToUse==STATUS_SUCCESS) { IrpSp = IoGetCurrentIrpStackLocation( Irp ); switch ( IrpSp->MajorFunction ) { case IRP_MJ_READ: ExInterlockedAddUlong(&DeviceExt->ParentControllerExt->PerfData.BytesRead, Irp->IoStatus.Information, &DeviceExt->ParentControllerExt->PerfLock); ExInterlockedAddUlong(&DeviceExt->PerfData.BytesRead, Irp->IoStatus.Information, &DeviceExt->PerfLock); break; case IRP_MJ_WRITE: ExInterlockedAddUlong(&DeviceExt->ParentControllerExt->PerfData.BytesWritten, Irp->IoStatus.Information, &DeviceExt->ParentControllerExt->PerfLock); ExInterlockedAddUlong(&DeviceExt->PerfData.BytesWritten, Irp->IoStatus.Information, &DeviceExt->PerfLock); break; default: break; } } DigiIoCompleteRequest( Irp, (char) ((StatusToUse == STATUS_SUCCESS) ? IO_SERIAL_INCREMENT : IO_NO_INCREMENT) ); if( discoveredIrp ) { // // We uncovered it, so we must start it. // DH rare race: two IRPs, first completes, second cancels, a third is queued onto an empty queue -- will be started twice. // KeAcquireSpinLock( &DeviceExt->ControlAccess, pOldIrql ); // IRP may have timed out or have been cancelled while we dropped the lock. if( !IsListEmpty( Queue ) ) StartRoutine( DeviceExt->ParentControllerExt, DeviceExt, pOldIrql ); KeReleaseSpinLock( &DeviceExt->ControlAccess, *pOldIrql ); } } else { KeReleaseSpinLock( &DeviceExt->ControlAccess, *pOldIrql ); } // // The expected behavior is for DigiTryToCompleteIrp to return // with the passed in ControlAccess spinlock released. // DigiDump( DIGIFLOW, ("Exiting DigiTryToCompleteIrp\n") ); } // end DigiTryToCompleteIrp void __inline DigiRundownIrpRefs( IN PIRP Irp, IN PKTIMER IntervalTimer OPTIONAL, IN PKTIMER TotalTimer OPTIONAL ) /*++ Routine Description: This routine runs through the various items that *could* have a reference to the current read/write. It try's to kill the reason. If it does succeed in killing the reason it will decrement the reference count on the irp. NOTE: This routine assumes that it is called with the ControlAccess spin lock held. Arguments: Irp - Pointer to current irp for this particular operation. IntervalTimer - Pointer to the interval timer for the operation. NOTE: This could be null. TotalTimer - Pointer to the total timer for the operation. NOTE: This could be null. Return Value: None. --*/ { if( IntervalTimer ) { // // Try to cancel the operations interval timer. If the operation // returns true then the timer did have a reference to the // irp. Since we've canceled this timer that reference is // no longer valid and we can decrement the reference count. // // If the cancel returns false then this means either of two things: // // a) The timer has already fired. // // b) There never was an interval timer. // // In the case of "b" there is no need to decrement the reference // count since the "timer" never had a reference to it. // // In the case of "a", then the timer itself will be coming // along and decrement it's reference. Note that the caller // of this routine might actually be the this timer, but it // has already decremented the reference. // if( KeCancelTimer( IntervalTimer ) ) { DigiDump( DIGIREFERENCE, (" Dec Ref for interval timer\n") ); DIGI_DEC_REFERENCE( Irp ); } } if( TotalTimer ) { // // Try to cancel the operations total timer. If the operation // returns true then the timer did have a reference to the // irp. Since we've canceled this timer that reference is // no longer valid and we can decrement the reference count. // // If the cancel returns false then this means either of two things: // // a) The timer has already fired. // // b) There never was an total timer. // // In the case of "b" there is no need to decrement the reference // count since the "timer" never had a reference to it. // // In the case of "a", then the timer itself will be coming // along and decrement it's reference. Note that the caller // of this routine might actually be the this timer, but it // has already decremented the reference. // if( KeCancelTimer( TotalTimer ) ) { DigiDump( DIGIREFERENCE, (" Dec Ref for total timer\n") ); DIGI_DEC_REFERENCE( Irp ); } } // // Don't kill the cancel routine until there's nothing else left. // if( DIGI_REFERENCE_COUNT( Irp ) == 1 ) { KIRQL CancelIrql; IoAcquireCancelSpinLock( &CancelIrql ); if( Irp->CancelRoutine ) { DigiDump( DIGIREFERENCE, (" Dec Ref for cancel\n") ); DIGI_DEC_REFERENCE( Irp ); IoSetCancelRoutine( Irp, NULL ); IoReleaseCancelSpinLock( CancelIrql ); } else { IoReleaseCancelSpinLock( CancelIrql ); } } } // end DigiRundownIrpRefs void DigiCancelIrpQueue( IN PDEVICE_OBJECT DeviceObject, IN PLIST_ENTRY Queue ) /*++ Routine Description: Arguments: DeviceExt - Pointer to the device object extension for this device. Queue - The queue of Irp requests. Return Value: --*/ { KIRQL cancelIrql; KIRQL OldIrql; PDIGI_DEVICE_EXTENSION DeviceExt = DeviceObject->DeviceExtension; DigiDump( DIGIFLOW, ("DigiBoard: Entering DigiCancelIrpQueue\n") ); KeAcquireSpinLock( &DeviceExt->NewIrpLock, &OldIrql ); KeAcquireSpinLockAtDpcLevel( &DeviceExt->ControlAccess ); // // We acquire the cancel spin lock. This will prevent the // irps from moving around. // IoAcquireCancelSpinLock( &cancelIrql ); while( !IsListEmpty( Queue ) ) { PIRP currentLastIrp; PDRIVER_CANCEL cancelRoutine; currentLastIrp = CONTAINING_RECORD( Queue->Blink, IRP, Tail.Overlay.ListEntry ); cancelRoutine = currentLastIrp->CancelRoutine; currentLastIrp->Cancel = TRUE; currentLastIrp->CancelRoutine = NULL; IoReleaseCancelSpinLock( cancelIrql ); KeReleaseSpinLockFromDpcLevel( &DeviceExt->ControlAccess ); if( cancelRoutine ) { IoAcquireCancelSpinLock( &cancelIrql ); currentLastIrp->CancelIrql = cancelIrql; // // This routine will release the cancel spin lock. // cancelRoutine( DeviceObject, currentLastIrp ); } else { // // Assume whoever nulled out the cancel routine // is also going to complete the IRP. // #if DBG DbgPrint( "DigiCancelIrpQueue(dev %s, ", DeviceExt->DeviceDbgString ); switch( (UCHAR *)Queue - (UCHAR *)DeviceExt ) { case FIELD_OFFSET( DIGI_DEVICE_EXTENSION, ReadQueue ): DbgPrint( "ReadQueue): " ); break; case FIELD_OFFSET( DIGI_DEVICE_EXTENSION, WriteQueue ): DbgPrint( "WriteQueue): " ); break; case FIELD_OFFSET( DIGI_DEVICE_EXTENSION, WaitQueue ): DbgPrint( "WaitQueue): " ); break; default: DbgPrint( "unknown queue at offset %d): ", (UCHAR *)Queue - (UCHAR *)DeviceExt ); break; } DbgPrint( "no cancel routine for irp 0x%x!\n", currentLastIrp ); #endif // DBG KeReleaseSpinLock( &DeviceExt->NewIrpLock, OldIrql ); return; } KeAcquireSpinLockAtDpcLevel( &DeviceExt->ControlAccess ); IoAcquireCancelSpinLock( &cancelIrql ); } IoReleaseCancelSpinLock( cancelIrql ); KeReleaseSpinLockFromDpcLevel( &DeviceExt->ControlAccess ); KeReleaseSpinLock( &DeviceExt->NewIrpLock, OldIrql ); } // end DigiCancelIrpQueue