/*++ Copyright (c) 1999-2000 Microsoft Corporation Module Name : read.c Abstract: This driver implements a state machine which polls for USB read data. It pends a single private read irp to USBD as soon as it starts up (StartDevice) if there is no interrupt endpoint. We have only 1 USB read buffer of some configured size. When the USB read irp completes then we copy the data into any pending user read buffer, and resubmit the Usb UsbReadIrp to USBD, *IF OUR BUFFER* is emptied. This implements simple flow ctrl. There is an optional ring-buffer implementation, which will not bind USB reads to application reads. Timeouts are set from the app via serial ioctls. An alternative to this muck is to create a driver thread to do the polling for USB read data. This has it's own caveats & requires the thread to be scheduled, so take time to investigate beforehand. Author: Jeff Midkiff (jeffmi) 07-16-99 --*/ #if defined (USE_RING_BUFF) #define min(a,b) (((a) < (b)) ? (a) : (b)) #else #include #endif #include "wceusbsh.h" // // called with control held // #if defined (USE_RING_BUFF) #define START_ANOTHER_USBREAD( _PDevExt ) \ ( (IRP_STATE_COMPLETE == _PDevExt->UsbReadState) && \ CanAcceptIoRequests(_PDevExt->DeviceObject, FALSE, TRUE) \ ) #else #define START_ANOTHER_USBREAD( _PDevExt ) \ ( (IRP_STATE_COMPLETE == _PDevExt->UsbReadState) && \ (0 == _PDevExt->UsbReadBuffChars) && \ CanAcceptIoRequests(_PDevExt->DeviceObject, FALSE, TRUE) \ ) #endif NTSTATUS UsbReadCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ); __inline ULONG GetUserData( IN PDEVICE_EXTENSION PDevExt, IN PCHAR PDestBuff, IN ULONG RequestedLen, IN OUT PULONG PBytesCopied ); __inline VOID PutUserData( IN PDEVICE_EXTENSION PDevExt, IN ULONG Count ); __inline VOID CheckForQueuedUserReads( IN PDEVICE_EXTENSION PDevExt, IN KIRQL Irql ); VOID CancelQueuedIrp( IN PDEVICE_OBJECT PDevObj, IN PIRP PIrp ); VOID CancelUsbReadWorkItem( IN PWCE_WORK_ITEM PWorkItem ); VOID StartUsbReadWorkItem( IN PWCE_WORK_ITEM PWorkItem ); NTSTATUS StartUserRead( IN PDEVICE_EXTENSION PDevExt ); VOID UsbReadTimeout( IN PKDPC PDpc, IN PVOID DeferredContext, IN PVOID SystemContext1, IN PVOID SystemContext2 ); VOID CancelCurrentRead( IN PDEVICE_OBJECT PDevObj, IN PIRP PIrp ); NTSTATUS StartOrQueueIrp( IN PDEVICE_EXTENSION PDevExt, IN PIRP PIrp, IN PLIST_ENTRY PIrpQueue, IN PIRP *PPCurrentIrp, IN PSTART_ROUTINE StartRoutine ); VOID GetNextUserIrp( IN PIRP *PpCurrentOpIrp, IN PLIST_ENTRY PQueueToProcess, OUT PIRP *PpNextIrp, IN BOOLEAN CompleteCurrent, IN PDEVICE_EXTENSION PDevExt ); /////////////////////////////////////////////////////////////////// // // USB read section // // // // This function allocates a single Irp & Urb to be continously submitted // to USBD for buffered reads. // It is called from StartDevice. // The Irp & Urb are finally freed in StopDevice. // NTSTATUS AllocUsbRead( IN PDEVICE_EXTENSION PDevExt ) { NTSTATUS status = STATUS_SUCCESS; PIRP pIrp; DbgDump(DBG_READ, (">AllocUsbRead(%p)\n", PDevExt->DeviceObject)); ASSERT( PDevExt ); ASSERT( NULL == PDevExt->UsbReadIrp ); pIrp = IoAllocateIrp( (CCHAR)(PDevExt->NextDevice->StackSize + 1), FALSE); if ( pIrp ) { DbgDump(DBG_READ, ("UsbReadIrp: %p\n", pIrp )); // // fixup irp so we can pass to ourself, // and to USBD // FIXUP_RAW_IRP( pIrp, PDevExt->DeviceObject ); // // setup read state // KeInitializeEvent( &PDevExt->UsbReadCancelEvent, SynchronizationEvent, FALSE); PDevExt->UsbReadIrp = pIrp; ASSERT( PDevExt->UsbReadBuff ); PDevExt->UsbReadBuffChars = 0; PDevExt->UsbReadBuffIndex = 0; ASSERT( 0 == PDevExt->PendingReadCount ); InterlockedExchange(&PDevExt->UsbReadState, IRP_STATE_COMPLETE); } else { // // this is a fatal err since we can't post reads to USBD // TEST_TRAP(); status = STATUS_INSUFFICIENT_RESOURCES; DbgDump(DBG_ERR, ("AllocUsbRead: 0x%x\n", status )); } DbgDump(DBG_READ, ("DeviceObject; PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension; NTSTATUS status = STATUS_DELETE_PENDING; PERF_ENTRY( PERF_StartUsbReadWorkItem ); DbgDump(DBG_READ|DBG_WORK_ITEMS, (">StartUsbReadWorkItem(%p)\n", pDevObj )); if ( InterlockedCompareExchange(&pDevExt->AcceptingRequests, TRUE, TRUE) ) { status = UsbRead( pDevExt, FALSE ); } DequeueWorkItem( pDevObj, PWorkItem ); DbgDump(DBG_READ|DBG_WORK_ITEMS, ("UsbRead(%p, %d)\n", PDevExt->DeviceObject, UseTimeout)); do { // // check our USB read state // KeAcquireSpinLock(&PDevExt->ControlLock, &irql); if ( !PDevExt->UsbReadIrp ) { status = STATUS_UNSUCCESSFUL; DbgDump(DBG_ERR, ("UsbRead NO READ IRP\n")); KeReleaseSpinLock(&PDevExt->ControlLock, irql); PERF_EXIT( PERF_UsbRead ); break; } if ( !CanAcceptIoRequests(PDevExt->DeviceObject, FALSE, TRUE)) { status = STATUS_DELETE_PENDING; DbgDump(DBG_ERR, ("UsbRead: 0x%x\n", status )); KeReleaseSpinLock(&PDevExt->ControlLock, irql); PERF_EXIT( PERF_UsbRead ); break; } // // we post our read irp to USB if it has been completed (not cancelled), // and our read bufer is driained (if not using a ring-buffer) // and the device is accepting requests // if ( START_ANOTHER_USBREAD( PDevExt ) ) { status = AcquireRemoveLock(&PDevExt->RemoveLock, PDevExt->UsbReadIrp); if ( !NT_SUCCESS(status) ) { DbgDump(DBG_ERR, ("UsbRead: 0x%x\n", status )); KeReleaseSpinLock(&PDevExt->ControlLock, irql); PERF_EXIT( PERF_UsbRead ); break; } ASSERT( IRP_STATE_COMPLETE == PDevExt->UsbReadState); InterlockedExchange(&PDevExt->UsbReadState, IRP_STATE_PENDING); KeClearEvent( &PDevExt->PendingDataInEvent ); KeClearEvent( &PDevExt->UsbReadCancelEvent ); RecycleIrp( PDevExt->DeviceObject, PDevExt->UsbReadIrp ); // // bump ttl request count // PDevExt->TtlUSBReadRequests++; KeReleaseSpinLock(&PDevExt->ControlLock, irql); #if DBG if (UseTimeout) { DbgDump(DBG_INT, ("INT Read Timeout due in %d msec\n", (PDevExt->IntReadTimeOut.QuadPart/10000) )); KeQuerySystemTime(&PDevExt->LastIntReadTime); } #endif status = UsbReadWritePacket( PDevExt, PDevExt->UsbReadIrp, UsbReadCompletion, // Irp completion routine UseTimeout ? PDevExt->IntReadTimeOut : noTimeout, UseTimeout ? UsbReadTimeout : NULL, // Timeout routine TRUE ); // Read if ( (STATUS_SUCCESS != status) && (STATUS_PENDING != status) ) { // // We can end up here after our completion routine runs // for an error condition i.e., when we have an // invalid parameter, or when user pulls the plug, etc. // DbgDump(DBG_ERR, ("UsbReadWritePacket: 0x%x\n", status)); } } else { // // we did not post a Read, but this is not an error condition // status = STATUS_SUCCESS; DbgDump(DBG_READ, ("!UsbRead RE(2): (0x%x,0x%x)\n", PDevExt->UsbReadState, PDevExt->UsbReadBuffChars )); KeReleaseSpinLock(&PDevExt->ControlLock, irql); } } while (0); DbgDump(DBG_READ, ("DeviceExtension; PDEVICE_OBJECT pDevObj = pDevExt->DeviceObject; PURB pUrb; ULONG count; KIRQL irql; NTSTATUS irpStatus; NTSTATUS workStatus; USBD_STATUS urbStatus; PERF_ENTRY( PERF_UsbReadCompletion ); UNREFERENCED_PARAMETER( PDevObj ); DbgDump(DBG_READ, (">UsbReadCompletion (%p)\n", Irp)); KeAcquireSpinLock(&pDevExt->ControlLock, &irql); // // cancel the Packet Timer // if ( PPacket->Timeout.QuadPart != 0 ) { if (KeCancelTimer( &PPacket->TimerObj ) ) { // // the packet's timer was successfully removed from the system // DbgDump(DBG_READ|DBG_INT, ("Read PacketTimer: Canceled\n")); } else { // // the timer // a) already completed, in which case the Irp is being cancelled, or // b) it's spinning on the control lock, so tell it we took the Irp. // PPacket->Status = STATUS_ALERTED; DbgDump(DBG_READ|DBG_INT, ("Read PacketTimer: Alerted\n")); } } // // get everything we need out of the packet // and put it back on the list // // ensure the Irp is the same one as in our DevExt ASSERT( pDevExt->UsbReadIrp == Irp ); // ensure the Packet's Irp is the same one as in our DevExt ASSERT( PPacket->Irp == Irp ); pUrb = pDevExt->UsbReadUrb; // ensure the Packet's Urb is the same one as in our DevExt ASSERT( pUrb == &PPacket->Urb ); count = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength; RemoveEntryList( &PPacket->ListEntry ); // // Our read state should be either pending or cancelled at this point. // If it pending then USB is completing the Irp normally. // If it is cancelled then our CancelUsbReadIrp set it, // in which case USB can complete the irp normally or as cancelled // depending on where it was in processing. If the read state is cancelled // then do NOT set to complete, else the read Irp will // go back down to USB and you are hosed. // ASSERT( (IRP_STATE_PENDING == pDevExt->UsbReadState) || (IRP_STATE_CANCELLED== pDevExt->UsbReadState) ); if (IRP_STATE_PENDING == pDevExt->UsbReadState) { InterlockedExchange(&pDevExt->UsbReadState, IRP_STATE_COMPLETE); } // // Put the pacet back in packet pool // ExFreeToNPagedLookasideList( &pDevExt->PacketPool, // Lookaside, PPacket // Entry ); // // signal everyone if this is the last IRP // if ( 0 == InterlockedDecrement(&pDevExt->PendingReadCount) ) { DbgDump(DBG_READ, ("PendingReadCount(1) = 0\n")); // when we drop back to passive level they will get signalled KeSetEvent(&pDevExt->PendingDataInEvent, IO_SERIAL_INCREMENT, FALSE); } irpStatus = Irp->IoStatus.Status; DbgDump(DBG_READ, ("Irp->IoStatus.Status 0x%x\n", irpStatus)); urbStatus = pUrb->UrbHeader.Status; DbgDump(DBG_READ, ("pUrb->UrbHeader.Status 0x%x\n", urbStatus )); switch (irpStatus) { case STATUS_SUCCESS: { // // save the read transfer info // ASSERT( USBD_STATUS_SUCCESS == urbStatus ); DbgDump(DBG_READ_LENGTH, ("USB Read indication: %d\n", count)); // // store read data // PutUserData( pDevExt, count ); // // clear pipe error count // InterlockedExchange( &pDevExt->ReadDeviceErrors, 0); // // bump ttl byte counter // pDevExt->TtlUSBReadBytes += count; // // We have some USB read data in our local buffer, // let's see if we can satisfy any queued user read requests. // This f() releases the control lock. // CheckForQueuedUserReads(pDevExt, irql); // // kick off another USB read // UsbRead( pDevExt, (BOOLEAN)(pDevExt->IntPipe.hPipe ? TRUE : FALSE) ); } break; case STATUS_CANCELLED: { DbgDump(DBG_WRN|DBG_READ|DBG_IRP, ("Read: STATUS_CANCELLED\n")); KeReleaseSpinLock(&pDevExt->ControlLock, irql); // // If it was cancelled, it may have timed out. // We can tell by looking at the packet attached to it. // if ( STATUS_TIMEOUT == PPacket->Status ) { // // no read data available from USBD // DbgDump(DBG_WRN|DBG_READ|DBG_IRP, ("Read: STATUS_TIMEOUT\n")); ASSERT( USBD_STATUS_CANCELED == urbStatus); // // We need to kick off another USB read when we are out of reads, // or have an error condition. // if ( !pDevExt->IntPipe.hPipe ) { workStatus = QueueWorkItem( pDevObj, StartUsbReadWorkItem, NULL, 0 ); } else { workStatus = STATUS_UNSUCCESSFUL; } } // // signal anyone who cancelled this or is waiting for it to stop // KeSetEvent(&pDevExt->UsbReadCancelEvent, IO_SERIAL_INCREMENT, FALSE); } break; case STATUS_DEVICE_DATA_ERROR: { // // generic device error set by USBD. // DbgDump(DBG_ERR, ("ReadPipe STATUS_DEVICE_DATA_ERROR: 0x%x\n", urbStatus )); KeReleaseSpinLock(&pDevExt->ControlLock, irql); // // bump pipe error count // InterlockedIncrement( &pDevExt->ReadDeviceErrors); // // is the endpoint is stalled? // if ( USBD_HALTED(pUrb->UrbHeader.Status) ) { if ( USBD_STATUS_BUFFER_OVERRUN == pUrb->UrbHeader.Status) { pDevExt->TtlUSBReadBuffOverruns++; } // // queue a reset request, // which also starts another read // workStatus = QueueWorkItem( pDevObj, UsbResetOrAbortPipeWorkItem, (PVOID)((LONG_PTR)urbStatus), WORK_ITEM_RESET_READ_PIPE ); } else { // // kick start another USB read // workStatus = QueueWorkItem( PDevObj, StartUsbReadWorkItem, NULL, 0 ); } } break; case STATUS_INVALID_PARAMETER: { // // This means that our (TransferBufferSize > PipeInfo->MaxTransferSize) // we need to either break up requests or reject the Irp from the start. // DbgDump(DBG_WRN, ("STATUS_INVALID_PARAMETER\n")); ASSERT(USBD_STATUS_INVALID_PARAMETER == urbStatus); KeReleaseSpinLock(&pDevExt->ControlLock, irql); TEST_TRAP(); } break; default: { DbgDump(DBG_ERR, ("READ: Unhandled Irp status: 0x%x\n", irpStatus)); KeReleaseSpinLock(&pDevExt->ControlLock, irql); } break; } ReleaseRemoveLock(&pDevExt->RemoveLock, pDevExt->UsbReadIrp); DbgDump(DBG_READ, ("CheckForQueuedUserReads(%p)\n", PDevExt->DeviceObject)); // // is there a user read pending? // if ( (PDevExt->UserReadIrp != NULL) && (IRP_REFERENCE_COUNT(PDevExt->UserReadIrp) & IRP_REF_RX_BUFFER)) { // // copy our USB read data into user's irp buffer // #if DBG ULONG charsRead = #endif GetUserData( PDevExt, ((PUCHAR)(PDevExt->UserReadIrp->AssociatedIrp.SystemBuffer)) + (IoGetCurrentIrpStackLocation(PDevExt->UserReadIrp))->Parameters.Read.Length - PDevExt->NumberNeededForRead, PDevExt->NumberNeededForRead, (PULONG)&PDevExt->UserReadIrp->IoStatus.Information ); if ( !PDevExt->UserReadIrp ) { // // it's (no longer) possible to have completed the read Irp // in the above GetUserData cycle. // DbgDump(DBG_READ, ("UsbReadIrp already completed(2)\n")); TEST_TRAP(); } else if (PDevExt->NumberNeededForRead == 0) { // // Mark the user's read Irp as completed, // and try to get and service the next user read irp // ASSERT( PDevExt->UserReadIrp ); PDevExt->UserReadIrp->IoStatus.Status = STATUS_SUCCESS; // signals the interval timer this read is complete PDevExt->CountOnLastRead = SERIAL_COMPLETE_READ_COMPLETE; #if DBG if ( DebugLevel & DBG_READ_LENGTH) { ULONG count; if (PDevExt->UserReadIrp->IoStatus.Status == STATUS_SUCCESS) { count = (ULONG)PDevExt->UserReadIrp->IoStatus.Information; } else { count = 0; } KdPrint(("RD2: RL(%d) C(%d) I(%p)\n", IoGetCurrentIrpStackLocation(PDevExt->UserReadIrp)->Parameters.Read.Length, count, PDevExt->UserReadIrp)); } #endif TryToCompleteCurrentIrp( PDevExt, STATUS_SUCCESS, &PDevExt->UserReadIrp, &PDevExt->UserReadQueue, &PDevExt->ReadRequestIntervalTimer, &PDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_RX_BUFFER, TRUE, Irql ); } else { // // we could get here if we did not staisfy the user's read // but have drained our read buffer. This requires another // USB read post. // ASSERT( PDevExt->UserReadIrp ); ASSERT( PDevExt->NumberNeededForRead ); DbgDump(DBG_READ|DBG_READ_LENGTH, ("Pending Irp (%p) has %d bytes to satisfy\n", PDevExt->UserReadIrp, PDevExt->NumberNeededForRead)); KeReleaseSpinLock( &PDevExt->ControlLock, Irql); // TEST_TRAP(); } } else { // // Q: should we: // 1.) copy the data into a local ring-buffer and post another read to USBD, - or - // 2.) leave the data in the FIFO & let the device stall/NAK so that: // a) we dont lose any data if the user is not posting reads, // b) lets the other end know to stop sending data via NAKs // // ...Currently choose #2. If we were to add a ring-buffer then here is where you should do the // local copy. // // Note: we could get here before an app even opens this device // if the there is data coming in on the other side of the FIFO. // DbgDump(DBG_READ|DBG_READ_LENGTH, ("No pending user Reads\n")); KeReleaseSpinLock( &PDevExt->ControlLock, Irql); //TEST_TRAP(); } // // process serial Rx wait masks // ProcessSerialWaits(PDevExt); DbgDump(DBG_READ, ("GetUserData (%p)\n", PDevExt->DeviceObject )); count = min(PDevExt->UsbReadBuffChars, RequestedLen); if (count) { memcpy( PDestBuff, &PDevExt->UsbReadBuff[PDevExt->UsbReadBuffIndex], count); PDevExt->UsbReadBuffIndex += count; PDevExt->UsbReadBuffChars -= count; PDevExt->NumberNeededForRead -= count; *PBytesCopied += count; PDevExt->ReadByIsr += count; } #if DBG // temp hack to debug iPAQ 'CLIENT' indications if ((DebugLevel & DBG_DUMP_READS) && (count <= 6)) { ULONG i; KdPrint(("RD1(%d): ", count)); for (i = 0; i < count; i++) { KdPrint(("%02x ", PDestBuff[i] & 0xFF)); } KdPrint(("\n")); } #endif DbgDump(DBG_READ, ("PutUserData %d\n", Count )); PDevExt->UsbReadBuffChars = Count; PDevExt->UsbReadBuffIndex = 0; ASSERT_SERIAL_PORT(PDevExt->SerialPort); PDevExt->SerialPort.HistoryMask |= SERIAL_EV_RXCHAR; // We have no concept of 80% full. If we blindly set it // then serial apps may go into flow handlers. // | SERIAL_EV_RX80FULL; // // Scan for RXFLAG char if needed // if (PDevExt->SerialPort.WaitMask & SERIAL_EV_RXFLAG) { ULONG i; for (i = 0; i < Count; i++) { if ( *((PUCHAR)&PDevExt->UsbReadBuff[PDevExt->UsbReadBuffIndex] + i) == PDevExt->SerialPort.SpecialChars.EventChar) { PDevExt->SerialPort.HistoryMask |= SERIAL_EV_RXFLAG; DbgDump(DBG_READ|DBG_EVENTS, ("Found SpecialChar: %x\n", PDevExt->SerialPort.SpecialChars.EventChar )); break; } } } DbgDump(DBG_READ, ("GetUserData (%p)\n", PDevExt->DeviceObject )); count = min(PDevExt->RingBuff.CharsInBuff, RequestedLen); if (count) { for ( i = 0; i< count; i++) { // copy the ring buffer data into user's buffer PDestBuff[i] = *PDevExt->RingBuff.pHead; // bump head checking for wrap PDevExt->RingBuff.pHead = PDevExt->RingBuff.pBase + ((ULONG)(PDevExt->RingBuff.pHead + 1) % RINGBUFF_SIZE); } PDevExt->RingBuff.CharsInBuff -= count; PDevExt->NumberNeededForRead -= count; *PBytesCopied += count; PDevExt->ReadByIsr += count; } #if DBG if (DebugLevel & DBG_DUMP_READS) { ULONG i; KdPrint(("RD1(%d): ", count)); for (i = 0; i < count; i++) { KdPrint(("%02x ", PDestBuff[i] & 0xFF)); } KdPrint(("\n")); } #endif DbgDump(DBG_READ, ("RingBuff.pBase ); ASSERT( PDevExt->RingBuff.pHead ); ASSERT( PDevExt->RingBuff.pTail ); ASSERT( PDevExt->RingBuff.Size >= PDevExt->UsbReadBuffSize ); ASSERT_SERIAL_PORT(PDevExt->SerialPort); DbgDump(DBG_READ, (">PutUserData %d\n", Count )); pPrevTail = PDevExt->RingBuff.pTail; for ( i = 0; i < Count; i++) { // copy the USB data *PDevExt->RingBuff.pTail = PDevExt->UsbReadBuff[i]; // check EV_RXFLAG while we are here if ( (PDevExt->SerialPort.WaitMask & SERIAL_EV_RXFLAG) && (*PDevExt->RingBuff.pTail == PDevExt->SerialPort.SpecialChars.EventChar)) { PDevExt->SerialPort.HistoryMask |= SERIAL_EV_RXFLAG; DbgDump(DBG_READ|DBG_SERIAL, ("Found SpecialChar: %x\n", PDevExt->SerialPort.SpecialChars.EventChar )); } // bump tail checking for wrap PDevExt->RingBuff.pTail = PDevExt->RingBuff.pBase + ((ULONG)(PDevExt->RingBuff.pTail + 1) % PDevExt->RingBuff.Size); } // // bump count // if ( (PDevExt->RingBuff.CharsInBuff + Count) <= PDevExt->RingBuff.Size ) { PDevExt->RingBuff.CharsInBuff += Count; } else { // // Overrun condition. We could check for this first to save the above copy process, // but it's the unusual case. We could also optimize the above copy a bit, but still need // to check for EV_RXFLAG. // PDevExt->RingBuff.CharsInBuff = Count; PDevExt->RingBuff.pHead = pPrevTail; #if PERFORMANCE PDevExt->TtlRingBuffOverruns.QuadPart ++; #endif } PDevExt->SerialPort.HistoryMask |= SERIAL_EV_RXCHAR; // // Check for 80% full. // We currently signal this at 50% since we run at a raised IRQL and serial apps are slow. // if ( PDevExt->RingBuff.CharsInBuff > RINGBUFF_HIGHWATER_MARK ) { DbgDump(DBG_READ|DBG_READ_LENGTH|DBG_SERIAL|DBG_WRN, ("SERIAL_EV_RX80FULL\n")); PDevExt->SerialPort.HistoryMask |= SERIAL_EV_RX80FULL; } DbgDump(DBG_READ, ("DeviceExtension; NTSTATUS status = STATUS_SUCCESS; NTSTATUS wait_status; KIRQL irql; PERF_ENTRY( PERF_CancelUsbReadIrp ); DbgDump(DBG_READ|DBG_IRP, (">CancelUsbReadIrp\n")); KeAcquireSpinLock(&pDevExt->ControlLock, &irql); if ( pDevExt->UsbReadIrp ) { switch (pDevExt->UsbReadState) { //case IRP_STATE_START: case IRP_STATE_PENDING: { // // the Irp is pending somewhere down the USB stack... // PVOID Objects[2] = { &pDevExt->PendingDataInEvent, &pDevExt->UsbReadCancelEvent }; // // signal we need to cancel the Irp // pDevExt->UsbReadState = IRP_STATE_CANCELLED; KeReleaseSpinLock(&pDevExt->ControlLock, irql); if ( !IoCancelIrp( pDevExt->UsbReadIrp ) ) { // // This means USB has the UsbReadIrp in a non-canceable state. // We still need to wait for either the pending read event, or the cancel event. // DbgDump(DBG_READ|DBG_IRP, ("Irp (%p) was not cancelled\n", pDevExt->UsbReadIrp )); // TEST_TRAP(); } DbgDump(DBG_READ|DBG_IRP, ("Waiting for pending UsbReadIrp (%p) to cancel...\n", pDevExt->UsbReadIrp )); PAGED_CODE(); wait_status = KeWaitForMultipleObjects( 2, Objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL ); DbgDump(DBG_READ|DBG_IRP, ("...UsbReadIrp (%p) signalled by: %d\n", pDevExt->UsbReadIrp, wait_status )); // // At this point the read packet is back on our list // and we have the Irp back from USB // } break; case IRP_STATE_COMPLETE: pDevExt->UsbReadState = IRP_STATE_CANCELLED; KeReleaseSpinLock(&pDevExt->ControlLock, irql); break; default: DbgDump(DBG_ERR, ("CancelUsbReadIrp - Invalid UsbReadState: 0x%x\n", pDevExt->UsbReadState )); TEST_TRAP(); KeReleaseSpinLock(&pDevExt->ControlLock, irql); break; } if ( (IRP_STATE_CANCELLED != pDevExt->UsbReadState) || (0 != pDevExt->PendingReadCount) ) { DbgDump(DBG_ERR, ("CancelUsbReadIrp error: UsbReadState: 0x%x \tPendingReadCount: 0x%x\n", pDevExt->UsbReadState, pDevExt->PendingReadCount )); //TEST_TRAP(); } } else { status = STATUS_UNSUCCESSFUL; DbgDump(DBG_ERR, ("No Read Irp\n" )); KeReleaseSpinLock(&pDevExt->ControlLock, irql); // TEST_TRAP(); } DbgDump(DBG_READ|DBG_IRP, ("DeviceObject; PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension; NTSTATUS status = STATUS_DELETE_PENDING; KIRQL irql; PERF_ENTRY( PERF_CancelUsbReadWorkItem ); DbgDump(DBG_INT|DBG_READ|DBG_WORK_ITEMS, (">CancelUsbReadWorkItem(%p)\n", pDevObj )); KeAcquireSpinLock( &pDevExt->ControlLock, &irql); if (IRP_STATE_PENDING == pDevExt->UsbReadState) { KeReleaseSpinLock( &pDevExt->ControlLock, irql); status = CancelUsbReadIrp( pDevObj ); InterlockedExchange(&pDevExt->UsbReadState, IRP_STATE_COMPLETE); } else { KeReleaseSpinLock( &pDevExt->ControlLock, irql); } DequeueWorkItem( pDevObj, PWorkItem ); DbgDump(DBG_INT|DBG_READ|DBG_WORK_ITEMS, ("DeviceExtension; PDEVICE_OBJECT pDevObj = pDevExt->DeviceObject; NTSTATUS status; // = STATUS_TIMEOUT; KIRQL irql; #if DBG LARGE_INTEGER currentTime; #endif UNREFERENCED_PARAMETER( PDpc ); UNREFERENCED_PARAMETER( SystemContext1 ); UNREFERENCED_PARAMETER( SystemContext2 ); DbgDump(DBG_INT|DBG_READ, (">UsbReadTimeout\n")); if (pPacket && pDevExt && pDevObj) { // // sync with completion routine putting packet back on list // KeAcquireSpinLock( &pDevExt->ControlLock, &irql); if ( !pPacket || !pPacket->Irp || (STATUS_ALERTED == pPacket->Status) ) { status = STATUS_ALERTED; DbgDump(DBG_INT|DBG_READ, ("STATUS_ALERTED\n" )); KeReleaseSpinLock( &pDevExt->ControlLock, irql ); } else { // // queue a passive work item to cancel the USB read irp // KeReleaseSpinLock( &pDevExt->ControlLock, irql ); #if DBG KeQuerySystemTime(¤tTime); DbgDump(DBG_INT, ("INT Read Timeout occured in < %I64d msec\n", ((currentTime.QuadPart - pDevExt->LastIntReadTime.QuadPart)/(LONGLONG)10000) )); #endif status = QueueWorkItem( pDevObj, CancelUsbReadWorkItem, NULL, 0); } } else { status = STATUS_INVALID_PARAMETER; DbgDump(DBG_ERR, ("UsbReadTimeout: 0x%x\n", status )); TEST_TRAP(); } DbgDump(DBG_INT|DBG_READ, ("DeviceExtension; NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION pIrpSp; PERF_ENTRY( PERF_Read ); DbgDump(DBG_READ|DBG_TRACE, (">Read (%p, %p)\n", PDevObj, PIrp )); if ( !CanAcceptIoRequests( pDevExt->DeviceObject, TRUE, TRUE) ) { status = PIrp->IoStatus.Status = STATUS_DELETE_PENDING; IoCompleteRequest (PIrp, IO_SERIAL_INCREMENT ); DbgDump(DBG_ERR, ("Read: 0x%x\n", status )); PERF_EXIT( PERF_Read ); return status; } // // set return values to something known // PIrp->IoStatus.Information = 0; pIrpSp = IoGetCurrentIrpStackLocation(PIrp); if (pIrpSp->Parameters.Read.Length != 0) { status = AcquireRemoveLock(&pDevExt->RemoveLock, PIrp); if ( !NT_SUCCESS(status) ) { DbgDump(DBG_ERR, ("Read:(0x%x)\n", status)); PIrp->IoStatus.Status = status; IoCompleteRequest(PIrp, IO_NO_INCREMENT); return status; } DbgDump(DBG_READ_LENGTH, ("User Read (%p) length: %d\n", PIrp, pIrpSp->Parameters.Read.Length )); status = StartOrQueueIrp( pDevExt, PIrp, &pDevExt->UserReadQueue, &pDevExt->UserReadIrp, StartUserRead); } else { PIrp->IoStatus.Status = status = STATUS_SUCCESS; PIrp->IoStatus.Information = 0; IoCompleteRequest(PIrp, IO_SERIAL_INCREMENT); } DbgDump(DBG_READ|DBG_TRACE, ("StartOrQueueIrp (%p, %p)\n", PDevExt->DeviceObject, PIrp )); // // Make sure the device is accepting request // if ( !CanAcceptIoRequests( PDevExt->DeviceObject, TRUE, TRUE) ) { status = PIrp->IoStatus.Status = STATUS_DELETE_PENDING; ReleaseRemoveLock(&PDevExt->RemoveLock, PIrp); IoCompleteRequest (PIrp, IO_SERIAL_INCREMENT ); DbgDump(DBG_ERR, ("StartOrQueueIrp 0x%x\n", status )); PERF_EXIT( PERF_StartOrQueueIrp ); return status; } KeAcquireSpinLock( &PDevExt->ControlLock, &irql ); // // if nothing is pending then start this new irp // if (IsListEmpty(PQueue) && (NULL == *PPCurrentIrp)) { *PPCurrentIrp = PIrp; KeReleaseSpinLock( &PDevExt->ControlLock, irql ); status = Starter(PDevExt); DbgDump(DBG_READ, ("Cancel) { // // The IRP was apparently cancelled. Complete it. // KeReleaseSpinLock( &PDevExt->ControlLock, irql ); PIrp->IoStatus.Status = STATUS_CANCELLED; ReleaseRemoveLock(&PDevExt->RemoveLock, PIrp); IoCompleteRequest(PIrp, IO_SERIAL_INCREMENT); DbgDump(DBG_READ|DBG_TRACE, ("IoStatus.Status = STATUS_PENDING; IoMarkIrpPending(PIrp); InsertTailList(PQueue, &PIrp->Tail.Overlay.ListEntry); ASSERT ( !PIrp->CancelRoutine ); IoSetCancelRoutine(PIrp, CancelQueuedIrp); KeReleaseSpinLock( &PDevExt->ControlLock, irql ); DbgDump(DBG_READ, ("DeviceObject ) { DbgDump(DBG_ERR, ("StartUserRead: NO Extension\n")); status = STATUS_UNSUCCESSFUL; TEST_TRAP(); break; } DbgDump(DBG_READ, (">StartUserRead (%p, %p)\n", PDevExt->DeviceObject, PDevExt->UserReadIrp )); // // get user's read request parameters // bControlLockReleased = FALSE; KeAcquireSpinLock(&PDevExt->ControlLock, &irql); // ensure we have a user Irp to play with if ( !PDevExt->UserReadIrp ) { DbgDump(DBG_ERR, ("StartUserRead: NO UserReadIrp!!\n")); KeReleaseSpinLock( &PDevExt->ControlLock, irql); status = STATUS_UNSUCCESSFUL; TEST_TRAP(); break; } // ensure the timers were removed from an earilier read if ( KeCancelTimer(&PDevExt->ReadRequestTotalTimer) || KeCancelTimer(&PDevExt->ReadRequestIntervalTimer) ) { DbgDump(DBG_ERR, ("StartUserRead: Timer not cancelled !!\n")); TEST_TRAP(); } // // Always initialize the timer objects so that the // completion code can tell when it attempts to // cancel the timers whether the timers had ever // been Set. // KeInitializeTimer(&PDevExt->ReadRequestTotalTimer); KeInitializeTimer(&PDevExt->ReadRequestIntervalTimer); IRP_SET_REFERENCE(PDevExt->UserReadIrp, IRP_REF_RX_BUFFER); readLen = IoGetCurrentIrpStackLocation(PDevExt->UserReadIrp)->Parameters.Read.Length; PDevExt->NumberNeededForRead = readLen; PDevExt->ReadByIsr = 0; DbgDump(DBG_READ|DBG_TIME, ("NumberNeededForRead: %d\n", PDevExt->NumberNeededForRead )); DbgDump(DBG_READ|DBG_TIME, ("ReadByIsr: %d\n", PDevExt->ReadByIsr )); ASSERT_SERIAL_PORT( PDevExt->SerialPort ); timeoutsForIrp = PDevExt->SerialPort.Timeouts; PDevExt->CountOnLastRead = 0; // // determine which timeouts we need to calculate for the read // if (timeoutsForIrp.ReadIntervalTimeout && (timeoutsForIrp.ReadIntervalTimeout != MAXULONG)) { useIntervalTimer = TRUE; } if (timeoutsForIrp.ReadIntervalTimeout == MAXULONG) { // // We need to do special return quickly stuff here. // // // 1) If both constant and multiplier are // 0 then we return immediately with whatever // we've got, even if it was zero. // if (!timeoutsForIrp.ReadTotalTimeoutConstant && !timeoutsForIrp.ReadTotalTimeoutMultiplier) { returnWithWhatsPresent = TRUE; } // // 2) If constant and multiplier are not MAXULONG // then return immediately if any characters // are present, but if nothing is there, then // use the timeouts as specified. // else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG) && (timeoutsForIrp.ReadTotalTimeoutMultiplier != MAXULONG)) { useTotalTimer = TRUE; os2ssreturn = TRUE; multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier; constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; } // // 3) If multiplier is MAXULONG then do as in // "2" but return when the first character // arrives. // else if ((timeoutsForIrp.ReadTotalTimeoutConstant != MAXULONG) && (timeoutsForIrp.ReadTotalTimeoutMultiplier == MAXULONG)) { useTotalTimer = TRUE; os2ssreturn = TRUE; crunchDownToOne = TRUE; multiplierVal = 0; constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; } } else { // // If both the multiplier and the constant are // zero then don't do any total timeout processing. // if (timeoutsForIrp.ReadTotalTimeoutMultiplier || timeoutsForIrp.ReadTotalTimeoutConstant) { // // We have some timer values to calculate // useTotalTimer = TRUE; multiplierVal = timeoutsForIrp.ReadTotalTimeoutMultiplier; constantVal = timeoutsForIrp.ReadTotalTimeoutConstant; } } if (useTotalTimer) { ulNumberNeededForRead = PDevExt->NumberNeededForRead; } // bump total request count PDevExt->TtlReadRequests++; // // see if we have any read data already available // #if defined (USE_RING_BUFF) if (PDevExt->RingBuff.CharsInBuff) { #else if (PDevExt->UsbReadBuffChars) { #endif #if DBG ULONG charsRead = #endif GetUserData( PDevExt, ((PUCHAR)(PDevExt->UserReadIrp->AssociatedIrp.SystemBuffer)) + readLen - PDevExt->NumberNeededForRead, PDevExt->NumberNeededForRead, (PULONG)&PDevExt->UserReadIrp->IoStatus.Information ); } else { DbgDump(DBG_READ|DBG_READ_LENGTH, ("No immediate Read data\n")); } // // Try to kick start another USB read. // if ( START_ANOTHER_USBREAD(PDevExt) ) { KeReleaseSpinLock(&PDevExt->ControlLock, irql); UsbRead( PDevExt, (BOOLEAN)(PDevExt->IntPipe.hPipe ? TRUE : FALSE) ); bControlLockReleased = FALSE; KeAcquireSpinLock(&PDevExt->ControlLock, &irql); } if ( !PDevExt->UserReadIrp ) { // // it's possible that we completed the read Irp already // in the above cycle // DbgDump(DBG_READ, ("UsbReadIrp already completed(1)\n")); } else if (returnWithWhatsPresent || (PDevExt->NumberNeededForRead == 0) || (os2ssreturn && PDevExt->UsbReadIrp->IoStatus.Information)) { // // See if this read is complete // ASSERT( PDevExt->UserReadIrp ); #if DBG if ( DebugLevel & DBG_READ_LENGTH) { ULONG count; if (PDevExt->UserReadIrp->IoStatus.Status == STATUS_SUCCESS) { count = (ULONG)PDevExt->UserReadIrp->IoStatus.Information; } else { count = 0; } KdPrint(("RD3: RL(%d) C(%d) I(%p)\n", IoGetCurrentIrpStackLocation(PDevExt->UserReadIrp)->Parameters.Read.Length, count, PDevExt->UserReadIrp)); } #endif // // Update the amount of chars left in the ring buffer // PDevExt->UserReadIrp->IoStatus.Status = STATUS_SUCCESS; if (!bSetStatus) { status = STATUS_SUCCESS; bSetStatus = TRUE; } } else { // // The irp has the chance to timeout // IRP_INIT_REFERENCE(PDevExt->UserReadIrp); // // Check to see if it needs to be cancelled // if (PDevExt->UserReadIrp->Cancel) { PDevExt->UserReadIrp->IoStatus.Status = STATUS_CANCELLED; PDevExt->UserReadIrp->IoStatus.Information = 0; if (!bSetStatus) { bSetStatus = TRUE; status = STATUS_CANCELLED; } } else { // // If we are supposed to crunch the read down to // one character, then update the read length // in the irp and truncate the number needed for // read down to one. Note that if we are doing // this crunching, then the information must be // zero (or we would have completed above) and // the number needed for the read must still be /// equal to the read length. // if (crunchDownToOne) { PDevExt->NumberNeededForRead = 1; IoGetCurrentIrpStackLocation(PDevExt->UserReadIrp)->Parameters.Read.Length = 1; } IRP_SET_REFERENCE(PDevExt->UserReadIrp, IRP_REF_RX_BUFFER); IRP_SET_REFERENCE(PDevExt->UserReadIrp, IRP_REF_CANCEL); if (useTotalTimer) { CalculateTimeout( &totalTime, ulNumberNeededForRead, multiplierVal, constantVal ); IRP_SET_REFERENCE(PDevExt->UserReadIrp, IRP_REF_TOTAL_TIMER); DbgDump(DBG_READ|DBG_TIME, ("TotalReadTimeout for Irp %p due in %d msec\n", PDevExt->UserReadIrp, (totalTime.QuadPart/10000) )); KeSetTimer(&PDevExt->ReadRequestTotalTimer, totalTime, &PDevExt->TotalReadTimeoutDpc); } if (useIntervalTimer) { // relative time. Note we could lose the high order bit here PDevExt->IntervalTime.QuadPart = MILLISEC_TO_100NANOSEC( (signed)timeoutsForIrp.ReadIntervalTimeout ); IRP_SET_REFERENCE(PDevExt->UserReadIrp, IRP_REF_INTERVAL_TIMER); KeQuerySystemTime(&PDevExt->LastReadTime); DbgDump(DBG_READ|DBG_TIME, ("ReadIntervalTimeout for Irp %p due in %d msec\n", PDevExt->UserReadIrp, (PDevExt->IntervalTime.QuadPart/10000) )); KeSetTimer(&PDevExt->ReadRequestIntervalTimer, PDevExt->IntervalTime, &PDevExt->IntervalReadTimeoutDpc); } // // Mark IRP as cancellable // ASSERT( PDevExt->UserReadIrp ); IoSetCancelRoutine( PDevExt->UserReadIrp, CancelCurrentRead ); ASSERT( PDevExt->UserReadIrp ); IoMarkIrpPending( PDevExt->UserReadIrp ); bControlLockReleased = TRUE; KeReleaseSpinLock(&PDevExt->ControlLock, irql); if (!bSetStatus) { // // At this point the USB Read irp pending as the PDevExt->UsbReadIrp. // Either a read timer will fire to complete or cancel the Read, // or we hang indefinetly. // status = STATUS_PENDING; } } DbgDump(DBG_READ, ("ControlLock, irql); GetNextUserIrp( &PDevExt->UserReadIrp, &PDevExt->UserReadQueue, &newIrp, TRUE, PDevExt); } while (newIrp != NULL); DbgDump(DBG_READ, ("GetNextUserIrp (%p)\n", PDevExt->DeviceObject )); KeAcquireSpinLock(&PDevExt->ControlLock, &irql); pOldIrp = *PpCurrentOpIrp; // // Check to see if there is a new irp to start up // if ( !IsListEmpty(PQueueToProcess) ) { PLIST_ENTRY pHeadOfList; pHeadOfList = RemoveHeadList(PQueueToProcess); *PpCurrentOpIrp = CONTAINING_RECORD(pHeadOfList, IRP, Tail.Overlay.ListEntry); IoSetCancelRoutine(*PpCurrentOpIrp, NULL); } else { *PpCurrentOpIrp = NULL; } *PpNextIrp = *PpCurrentOpIrp; // // Complete the current one if so requested // if ( pOldIrp && CompleteCurrent ) { ASSERT(NULL == pOldIrp->CancelRoutine); DbgDump(DBG_IRP|DBG_READ|DBG_READ_LENGTH|DBG_TRACE, ("IoCompleteRequest(1, %p) Status: 0x%x Btyes: %d\n", pOldIrp, pOldIrp->IoStatus.Status, pOldIrp->IoStatus.Information )); // // bump ttl byte counter // PDevExt->TtlReadBytes += (ULONG)pOldIrp->IoStatus.Information; ReleaseRemoveLock(&PDevExt->RemoveLock, pOldIrp); KeReleaseSpinLock(&PDevExt->ControlLock, irql); IoCompleteRequest(pOldIrp, IO_NO_INCREMENT ); } else { KeReleaseSpinLock(&PDevExt->ControlLock, irql); } DbgDump(DBG_IRP|DBG_READ, ("Next Irp: %p\n", *PpNextIrp )); DbgDump(DBG_IRP|DBG_READ|DBG_TRACE, ("DeviceExtension; KIRQL irql; PERF_ENTRY( PERF_CancelCurrentRead ); DbgDump(DBG_READ|DBG_IRP, (">CancelCurrentRead (%p)\n", PDevObj )); // // we manage our own Irp queue, so release this ASAP // IoReleaseCancelSpinLock( PIrp->CancelIrql ); // // We set this to indicate to the interval timer // that the read has encountered a cancel. // // Recall that the interval timer dpc can be lurking in some // DPC queue. // KeAcquireSpinLock(&pDevExt->ControlLock, &irql); pDevExt->CountOnLastRead = SERIAL_COMPLETE_READ_CANCEL; if ( pDevExt->UserReadIrp ) { // grab the read irp IRP_CLEAR_REFERENCE(pDevExt->UserReadIrp, IRP_REF_RX_BUFFER); TryToCompleteCurrentIrp( pDevExt, STATUS_CANCELLED, &pDevExt->UserReadIrp, &pDevExt->UserReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_CANCEL, TRUE, irql ); } else { // // it is already gone // DbgDump( DBG_ERR, ("UserReadIrp already gone!\n" )); KeReleaseSpinLock(&pDevExt->ControlLock, irql); TEST_TRAP(); } DbgDump(DBG_READ|DBG_IRP, ("DeviceExtension; PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation(PIrp); KIRQL irql; PERF_ENTRY( PERF_CancelQueuedIrp ); DbgDump(DBG_READ|DBG_IRP|DBG_TRACE, (">CancelQueuedIrp (%p)\n", PDevObj )); // // we manage our own Irp queue, so release this ASAP // IoReleaseCancelSpinLock(PIrp->CancelIrql); // // The irp was cancelled -- remove it from the queue // KeAcquireSpinLock(&pDevExt->ControlLock, &irql); PIrp->IoStatus.Status = STATUS_CANCELLED; PIrp->IoStatus.Information = 0; RemoveEntryList(&PIrp->Tail.Overlay.ListEntry); ReleaseRemoveLock(&pDevExt->RemoveLock, PIrp); KeReleaseSpinLock(&pDevExt->ControlLock, irql); IoCompleteRequest(PIrp, IO_SERIAL_INCREMENT); DbgDump(DBG_READ|DBG_IRP|DBG_TRACE, ("ReadTimeout (%p)\n", pDevExt->DeviceObject )); KeAcquireSpinLock(&pDevExt->ControlLock, &oldIrql); if ( !CanAcceptIoRequests(pDevExt->DeviceObject, FALSE, TRUE) ) { TEST_TRAP(); IRP_CLEAR_REFERENCE( pDevExt->UserReadIrp, IRP_REF_TOTAL_TIMER); // manually set the cancel routine IoSetCancelRoutine( pDevExt->UserReadIrp, CancelCurrentRead ); KeReleaseSpinLock(&pDevExt->ControlLock, oldIrql); IoCancelIrp(pDevExt->UserReadIrp); PERF_EXIT( PERF_ReadTimeout ); return; } // // We set this to indicate to the interval timer // that the read has completed due to total timeout. // // Recall that the interval timer dpc can be lurking in some // DPC queue. // pDevExt->CountOnLastRead = SERIAL_COMPLETE_READ_TOTAL; // grab the read irp IRP_CLEAR_REFERENCE(pDevExt->UserReadIrp, IRP_REF_RX_BUFFER); DbgDump(DBG_TIME|DBG_READ_LENGTH, ("TotalReadTimeout for (%p)\n", pDevExt->UserReadIrp )); TryToCompleteCurrentIrp( pDevExt, STATUS_TIMEOUT, &pDevExt->UserReadIrp, &pDevExt->UserReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_TOTAL_TIMER, TRUE, oldIrql ); DbgDump(DBG_TIME, ("IntervalReadTimeout (%p)\n", pDevExt->DeviceObject )); KeAcquireSpinLock(&pDevExt->ControlLock, &irql); if ( !pDevExt->UserReadIrp || (IRP_REFERENCE_COUNT(pDevExt->UserReadIrp) & IRP_REF_INTERVAL_TIMER) == 0 ) { // // we already completed the read irp so just exit // DbgDump(DBG_TIME|DBG_IRP, ("Already completed User's Read Irp\n")); KeReleaseSpinLock(&pDevExt->ControlLock, irql); PERF_EXIT( PERF_IntervalReadTimeout ); return; } if ( !CanAcceptIoRequests(pDevExt->DeviceObject, FALSE, TRUE) ) { IRP_CLEAR_REFERENCE( pDevExt->UserReadIrp, IRP_REF_INTERVAL_TIMER); // manually set the cancel routine IoSetCancelRoutine( pDevExt->UserReadIrp, CancelCurrentRead ); KeReleaseSpinLock(&pDevExt->ControlLock, irql); IoCancelIrp(pDevExt->UserReadIrp); PERF_EXIT( PERF_IntervalReadTimeout ); return; } if (pDevExt->CountOnLastRead == SERIAL_COMPLETE_READ_TOTAL) { // // This value is only set by the total // timer to indicate that it has fired. // If so, then we should simply try to complete. // DbgDump(DBG_TIME, ("SERIAL_COMPLETE_READ_TOTAL\n")); // grab the read irp IRP_CLEAR_REFERENCE(pDevExt->UserReadIrp, IRP_REF_RX_BUFFER); pDevExt->CountOnLastRead = 0; TryToCompleteCurrentIrp( pDevExt, STATUS_TIMEOUT, &pDevExt->UserReadIrp, &pDevExt->UserReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_INTERVAL_TIMER, TRUE, irql ); } else if (pDevExt->CountOnLastRead == SERIAL_COMPLETE_READ_COMPLETE) { // // This value is only set by the regular completion routine. // If so, then we should simply try to complete. // DbgDump(DBG_TIME|DBG_READ_LENGTH, ("SERIAL_COMPLETE_READ_COMPLETE\n")); // grab the read irp IRP_CLEAR_REFERENCE(pDevExt->UserReadIrp, IRP_REF_RX_BUFFER); pDevExt->CountOnLastRead = 0; TryToCompleteCurrentIrp( pDevExt, STATUS_SUCCESS, &pDevExt->UserReadIrp, &pDevExt->UserReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_INTERVAL_TIMER, TRUE, irql ); } else if (pDevExt->CountOnLastRead == SERIAL_COMPLETE_READ_CANCEL) { // // This value is only set by the cancel // read routine. // // If so, then we should simply try to complete. // DbgDump(DBG_TIME, ("SERIAL_COMPLETE_READ_CANCEL\n")); // grab the read irp IRP_CLEAR_REFERENCE(pDevExt->UserReadIrp, IRP_REF_RX_BUFFER); pDevExt->CountOnLastRead = 0; TryToCompleteCurrentIrp( pDevExt, STATUS_CANCELLED, &pDevExt->UserReadIrp, &pDevExt->UserReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_INTERVAL_TIMER, TRUE, irql ); } else if (pDevExt->CountOnLastRead || pDevExt->ReadByIsr) { // // Something has happened since we last came here. We // check to see if the ISR has read in any more characters. // If it did then we should update the isr's read count // and resubmit the timer. // if (pDevExt->ReadByIsr) { DbgDump(DBG_TIME, ("ReadByIsr %d\n", pDevExt->ReadByIsr)); pDevExt->CountOnLastRead = pDevExt->ReadByIsr; pDevExt->ReadByIsr = 0; // // Save off the "last" time something was read. // As we come back to this routine we will compare // the current time to the "last" time. If the // difference is ever larger then the interval // requested by the user, then time out the request. // KeQuerySystemTime(&pDevExt->LastReadTime); DbgDump(DBG_TIME, ("ReadIntervalTimeout for Irp %p due in %d msec\n", pDevExt->UsbReadIrp, pDevExt->IntervalTime.QuadPart/10000 )); KeSetTimer(&pDevExt->ReadRequestIntervalTimer, pDevExt->IntervalTime, &pDevExt->IntervalReadTimeoutDpc); KeReleaseSpinLock(&pDevExt->ControlLock, irql); } else { // // Take the difference between the current time // and the last time we had characters and // see if it is greater then the interval time. // if it is, then time out the request. Otherwise // restart the timer. // // // No characters read in the interval time. Kill // this read. // LARGE_INTEGER currentTime; KeQuerySystemTime(¤tTime); if ((currentTime.QuadPart - pDevExt->LastReadTime.QuadPart) >= -(pDevExt->IntervalTime.QuadPart) ) { // absolute time DbgDump(DBG_TIME, ("TIMEOUT - CountOnLastRead=%d ReadByIsr=%d\n", pDevExt->CountOnLastRead, pDevExt->ReadByIsr)); #if DBG if (pDevExt->ReadByIsr > pDevExt->NumberNeededForRead ) { // did we we forgot to clear ReadByIsr TEST_TRAP(); } #endif // grab the read irp IRP_CLEAR_REFERENCE(pDevExt->UserReadIrp, IRP_REF_RX_BUFFER); pDevExt->CountOnLastRead = pDevExt->ReadByIsr = 0; // return any chars read up to this point TryToCompleteCurrentIrp( pDevExt, STATUS_TIMEOUT, &pDevExt->UserReadIrp, &pDevExt->UserReadQueue, &pDevExt->ReadRequestIntervalTimer, &pDevExt->ReadRequestTotalTimer, StartUserRead, GetNextUserIrp, IRP_REF_INTERVAL_TIMER, TRUE, irql ); } else { DbgDump(DBG_TIME, ("ReadIntervalTimeout for Irp %p due in %d msec\n", pDevExt->UsbReadIrp, pDevExt->IntervalTime.QuadPart/10000 )); KeSetTimer(&pDevExt->ReadRequestIntervalTimer, pDevExt->IntervalTime, &pDevExt->IntervalReadTimeoutDpc); KeReleaseSpinLock(&pDevExt->ControlLock, irql); } } } else { // // Timer doesn't really start until the first character. // So we should simply resubmit ourselves. // DbgDump(DBG_TIME, ("ReadIntervalTimeout for Irp %p due in %d msec\n", pDevExt->UsbReadIrp, pDevExt->IntervalTime.QuadPart/10000 )); KeSetTimer(&pDevExt->ReadRequestIntervalTimer, pDevExt->IntervalTime, &pDevExt->IntervalReadTimeoutDpc); KeReleaseSpinLock(&pDevExt->ControlLock, irql); } DbgDump(DBG_TIME, ("