You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2480 lines
70 KiB
2480 lines
70 KiB
/*++
|
|
|
|
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 <stdlib.h>
|
|
#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, ("<AllocUsbRead 0x%x\n", status ));
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//
|
|
// Work item queued from read completion or read timeout.
|
|
// Starts another USB read if there is not one already in progress
|
|
//
|
|
VOID
|
|
StartUsbReadWorkItem(
|
|
IN PWCE_WORK_ITEM PWorkItem
|
|
)
|
|
{
|
|
PDEVICE_OBJECT pDevObj = PWorkItem->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, ("<StartUsbReadWorkItem 0x%x\n", status ));
|
|
|
|
PAGED_CODE(); // we must exit at passive level
|
|
|
|
PERF_EXIT( PERF_StartUsbReadWorkItem );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// This routine takes the device's current USB UsbReadIrp and submits it to USBD.
|
|
// When the Irp is completed by USBD our completion routine fires.
|
|
//
|
|
// An optional Timeout value sets a timer on the USB Read Irp,
|
|
// so USBD won't queue the read Irp indefinetly.
|
|
// If there is a device error then USB returns the Irp.
|
|
//
|
|
// Return: successful return value is STATUS_SUCCESS, or
|
|
// STATUS_PENDING - which means the I/O is pending in the USB stack.
|
|
//
|
|
NTSTATUS
|
|
UsbRead(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN BOOLEAN UseTimeout
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
KIRQL irql;
|
|
LARGE_INTEGER noTimeout = {0,0};
|
|
|
|
PERF_ENTRY( PERF_UsbRead );
|
|
|
|
DbgDump(DBG_READ, (">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, ("<UsbRead 0x%x\n", status ));
|
|
|
|
PERF_EXIT( PERF_UsbRead );
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//
|
|
// This completion routine fires when our USB read completes our UsbReadIrp
|
|
// Note: we allocated the Irp, and recycle it.
|
|
// Always return STATUS_MORE_PROCESSING_REQUIRED to retain the Irp.
|
|
// This routine runs at DPC_LEVEL.
|
|
//
|
|
NTSTATUS
|
|
UsbReadCompletion(
|
|
IN PDEVICE_OBJECT PDevObj,
|
|
IN PIRP Irp,
|
|
IN PUSB_PACKET PPacket // Context
|
|
)
|
|
{
|
|
PDEVICE_EXTENSION pDevExt = PPacket->DeviceExtension;
|
|
PDEVICE_OBJECT pDevObj = pDevExt->DeviceObject;
|
|
PURB pUrb;
|
|
ULONG count;
|
|
KIRQL irql;
|
|
NTSTATUS irpStatus;
|
|
NTSTATUS workStatus;
|
|
USBD_STATUS urbStatus;
|
|
NTSTATUS packetStatus;
|
|
|
|
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;
|
|
|
|
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 ));
|
|
|
|
packetStatus = PPacket->Status;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
// These things are free now, don't touch them again.
|
|
//
|
|
PPacket = NULL;
|
|
Irp = NULL;
|
|
pUrb = NULL;
|
|
|
|
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 == packetStatus ) {
|
|
//
|
|
// 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(urbStatus) ) {
|
|
|
|
if ( USBD_STATUS_BUFFER_OVERRUN == urbStatus) {
|
|
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, ("<UsbReadCompletion\n"));
|
|
|
|
PERF_EXIT( PERF_UsbReadCompletion );
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
|
|
//
|
|
// This routine is called from the UsbReadCompletion routine
|
|
// when our USB UsbReadIrp has completed successfully.
|
|
// See if we have any queued user read Irps that we can satisfy.
|
|
// It is called with the control lock held (as an optimization)
|
|
// and must release the lock upon return.
|
|
//
|
|
__inline
|
|
VOID
|
|
CheckForQueuedUserReads(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN KIRQL Irql
|
|
)
|
|
{
|
|
PERF_ENTRY( PERF_CheckForQueuedUserReads );
|
|
|
|
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, ("<CheckForQueuedUserReads\n"));
|
|
|
|
PERF_EXIT( PERF_CheckForQueuedUserReads );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
#if !defined (USE_RING_BUFF)
|
|
//
|
|
// We have no ring-buffer.
|
|
// Simply copy data from our USB read buffer into user's buffer.
|
|
// Called with the control lock held.
|
|
//
|
|
// Returns the number of bytes copied.
|
|
//
|
|
|
|
__inline
|
|
ULONG
|
|
GetUserData(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN PCHAR PDestBuff,
|
|
IN ULONG RequestedLen,
|
|
IN OUT PULONG PBytesCopied
|
|
)
|
|
{
|
|
ULONG count;
|
|
|
|
PERF_ENTRY( PERF_GetUserData );
|
|
|
|
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, ("<GetUserData 0x%x\n", count ));
|
|
|
|
PERF_EXIT( PERF_GetUserData );
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/*
|
|
We have no ring buffer.
|
|
Simply update the USB read buffer's count & index
|
|
and process serial chars.
|
|
|
|
Called with the ControlLock held.
|
|
*/
|
|
__inline
|
|
VOID
|
|
PutUserData(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN ULONG Count
|
|
)
|
|
{
|
|
PERF_ENTRY( PERF_PutUserData );
|
|
|
|
ASSERT( PDevExt );
|
|
|
|
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, ("<PutUserData\n"));
|
|
|
|
PERF_EXIT( PERF_PutUserData );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
/*
|
|
Ring-buffer version
|
|
Copy the ring-buffer data into the user's buffer while checking for wrap around.
|
|
Also must check if we exhaust the read buffer.
|
|
|
|
Called with the ControlLock held.
|
|
*/
|
|
__inline
|
|
ULONG
|
|
GetUserData(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN PCHAR PDestBuff,
|
|
IN ULONG RequestedLen,
|
|
IN OUT PULONG PBytesCopied
|
|
)
|
|
{
|
|
ULONG i, count;
|
|
|
|
PERF_ENTRY( PERF_GetUserData );
|
|
|
|
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, ("<GetUserData 0x%x\n", count ));
|
|
|
|
PERF_EXIT( PERF_GetUserData );
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
/*
|
|
Ring-buffer version
|
|
Copy the USB Read buffer into the ring-buffer while checking for wrap around.
|
|
|
|
The ring buffer is assummed to be at least the same size as the USB read buffer.
|
|
This is a simple ring where writes occur at the tail, which can eventually overwrite the start of
|
|
the user read buffer location at the head, if user is not consuming the data. If we overwrite the
|
|
head then we must reset the head to be where we started the current write.
|
|
|
|
Note: the USB read buffer is a simple char array, with it's current index = 0.
|
|
Note: SerialPort ownership assummed.
|
|
|
|
Called with the ControlLock held.
|
|
|
|
Q: Should write an error log if an app has an open handle and we overrun the buffer?
|
|
|
|
*/
|
|
__inline
|
|
VOID
|
|
PutUserData(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN ULONG Count
|
|
)
|
|
{
|
|
ULONG i;
|
|
PUCHAR pPrevTail;
|
|
|
|
PERF_ENTRY( PERF_PutUserData );
|
|
|
|
ASSERT( PDevExt );
|
|
ASSERT( PDevExt->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, ("<PutUserData\n"));
|
|
|
|
PERF_EXIT( PERF_PutUserData );
|
|
|
|
return;
|
|
}
|
|
|
|
#endif // USE_RING_BUFF
|
|
|
|
|
|
//
|
|
// This routine requests USB to cancel our USB Read Irp.
|
|
//
|
|
// Note: it is the responsibility of the caller to
|
|
// reset the read state to IRP_STATE_COMPLETE and restart USB reads
|
|
// when this routine completes. Else, no more reads will get posted.
|
|
// Note: when the USB Read Irp is cancelled the pending USB read packet
|
|
// is cancelled via the USB Read completion routine.
|
|
// Note: must be called at PASSIVE_LEVEL.
|
|
//
|
|
NTSTATUS
|
|
CancelUsbReadIrp(
|
|
IN PDEVICE_OBJECT PDevObj
|
|
)
|
|
{
|
|
PDEVICE_EXTENSION pDevExt = PDevObj->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, ("<CancelUsbReadIrp\n"));
|
|
|
|
PERF_EXIT( PERF_CancelUsbReadIrp );
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
//
|
|
// Work item queued from USB read timeout
|
|
// to Cancel the USB read irp pending in the USB stack
|
|
//
|
|
VOID
|
|
CancelUsbReadWorkItem(
|
|
IN PWCE_WORK_ITEM PWorkItem
|
|
)
|
|
{
|
|
PDEVICE_OBJECT pDevObj = PWorkItem->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, ("<CancelUsbReadWorkItem 0x%x\n", status ));
|
|
|
|
PAGED_CODE(); // we must exit at passive level
|
|
|
|
PERF_EXIT( PERF_CancelUsbReadWorkItem );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// USB read timeout set on a read packet in UsbRead.
|
|
// Runs at DISPATCH_LEVEL.
|
|
//
|
|
VOID
|
|
UsbReadTimeout(
|
|
IN PKDPC PDpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemContext1,
|
|
IN PVOID SystemContext2
|
|
)
|
|
{
|
|
PUSB_PACKET pPacket = (PUSB_PACKET)DeferredContext;
|
|
PDEVICE_EXTENSION pDevExt = pPacket->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, ("<UsbReadTimeout (0x%x)\n", status ));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// User read section
|
|
//
|
|
|
|
NTSTATUS
|
|
Read(
|
|
IN PDEVICE_OBJECT PDevObj,
|
|
IN PIRP PIrp)
|
|
{
|
|
PDEVICE_EXTENSION pDevExt = PDevObj->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, ("<Read 0x%x\n", status));
|
|
|
|
PERF_EXIT( PERF_Read );
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
StartOrQueueIrp(
|
|
IN PDEVICE_EXTENSION PDevExt,
|
|
IN PIRP PIrp,
|
|
IN PLIST_ENTRY PQueue,
|
|
IN PIRP *PPCurrentIrp,
|
|
IN PSTART_ROUTINE Starter)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to either start processing an I/O request or to
|
|
queue it on the appropriate queue if a request is already pending or
|
|
requests may not be started.
|
|
|
|
Arguments:
|
|
|
|
PDevExt - A pointer to the DeviceExtension.
|
|
PIrp - A pointer to the IRP that is being started or queued.
|
|
PQueue - A pointer to the queue to place the IRP on if necessary.
|
|
PPCurrentIrp - A pointer to the pointer to the currently active I/O IRP.
|
|
Starter - Function to call if we decide to start this IRP.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
KIRQL irql;
|
|
NTSTATUS status;
|
|
|
|
PERF_ENTRY( PERF_StartOrQueueIrp );
|
|
|
|
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, ("<StartOrQueueIrp 0x%x\n", status ));
|
|
|
|
PERF_EXIT( PERF_StartOrQueueIrp );
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// We're queueing the irp, so we need a cancel routine -- make sure
|
|
// the irp hasn't already been cancelled.
|
|
//
|
|
if (PIrp->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, ("<StartOrQueueIrp 0x%x\n", STATUS_CANCELLED ));
|
|
|
|
PERF_EXIT( PERF_StartOrQueueIrp );
|
|
|
|
return STATUS_CANCELLED;
|
|
}
|
|
|
|
//
|
|
// Mark as pending, attach our cancel routine, put on our wait list
|
|
//
|
|
PIrp->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, ("<StartOrQueueIrp 0x%x\n", STATUS_PENDING ));
|
|
|
|
PERF_EXIT( PERF_StartOrQueueIrp );
|
|
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
StartUserRead(
|
|
IN PDEVICE_EXTENSION PDevExt
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine processes the active user read request by initializing any timers,
|
|
doing the initial submission to the read state machine, etc.
|
|
|
|
Arguments:
|
|
|
|
PDevExt - Pointer to the device extension for the device to start a read on
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
BOOLEAN bSetStatus = FALSE;
|
|
KIRQL irql;
|
|
PIRP newIrp = NULL;
|
|
ULONG readLen;
|
|
ULONG multiplierVal = 0;
|
|
ULONG constantVal = 0;
|
|
ULONG ulNumberNeededForRead = 0;
|
|
BOOLEAN useTotalTimer = FALSE;
|
|
BOOLEAN returnWithWhatsPresent = FALSE;
|
|
BOOLEAN os2ssreturn = FALSE;
|
|
BOOLEAN crunchDownToOne = FALSE;
|
|
BOOLEAN useIntervalTimer = FALSE;
|
|
SERIAL_TIMEOUTS timeoutsForIrp;
|
|
LARGE_INTEGER totalTime = {0,0};
|
|
BOOLEAN bControlLockReleased = FALSE;
|
|
|
|
PERF_ENTRY( PERF_StartUserRead );
|
|
|
|
do {
|
|
|
|
if ( !PDevExt || !PDevExt->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, ("<StartUserRead (1) 0x%x\n", status ));
|
|
|
|
PERF_EXIT( PERF_StartUserRead );
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// kick start the next queued user Read Irp
|
|
//
|
|
bControlLockReleased = TRUE;
|
|
KeReleaseSpinLock(&PDevExt->ControlLock, irql);
|
|
|
|
GetNextUserIrp( &PDevExt->UserReadIrp,
|
|
&PDevExt->UserReadQueue,
|
|
&newIrp,
|
|
TRUE,
|
|
PDevExt);
|
|
|
|
} while (newIrp != NULL);
|
|
|
|
DbgDump(DBG_READ, ("<StartUserRead (2) 0x%x\n", status ));
|
|
|
|
PERF_EXIT( PERF_StartUserRead );
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
GetNextUserIrp(
|
|
IN PIRP *PpCurrentOpIrp,
|
|
IN PLIST_ENTRY PQueueToProcess,
|
|
OUT PIRP *PpNextIrp,
|
|
IN BOOLEAN CompleteCurrent,
|
|
IN PDEVICE_EXTENSION PDevExt
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function gets the next IRP off a queue, marks it as current,
|
|
and possibly completes the current IRP.
|
|
|
|
Arguments:
|
|
|
|
PpCurrentOpIrp - A pointer to the pointer to the current IRP.
|
|
PQueueToProcess - A pointer to the queue to get the next IRP from.
|
|
PpNextIrp - A pointer to the pointer to the next IRP to process.
|
|
CompleteCurrent - TRUE if we should complete the IRP that is current at
|
|
the time we are called.
|
|
PDevExt - A pointer to the device extension.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
KIRQL irql;
|
|
PIRP pOldIrp;
|
|
|
|
PERF_ENTRY( PERF_GetNextUserIrp );
|
|
|
|
if ( !PpCurrentOpIrp || !PQueueToProcess || !PpNextIrp || !PDevExt ) {
|
|
DbgDump(DBG_ERR, ("GetNextUserIrp: missing parameter\n" ));
|
|
PERF_EXIT( PERF_GetNextUserIrp );
|
|
TEST_TRAP();
|
|
return;
|
|
}
|
|
|
|
DbgDump(DBG_IRP|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, ("<GetNextUserIrp\n" ));
|
|
|
|
PERF_EXIT( PERF_GetNextUserIrp );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
CancelCurrentRead(
|
|
IN PDEVICE_OBJECT PDevObj,
|
|
IN PIRP PIrp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to cancel the User's current read Irp.
|
|
|
|
Arguments:
|
|
|
|
PDevObj - Pointer to the device object for this device
|
|
|
|
PIrp - Pointer to the IRP to be canceled.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
|
|
PDEVICE_EXTENSION pDevExt = PDevObj->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, ("<CancelCurrentRead\n" ));
|
|
|
|
PERF_EXIT( PERF_CancelCurrentRead );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
CancelQueuedIrp(
|
|
IN PDEVICE_OBJECT PDevObj,
|
|
IN PIRP PIrp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used as a cancel routine for queued irps.
|
|
Basically for us this means the user's Read IRPs.
|
|
|
|
Arguments:
|
|
|
|
PDevObj - A pointer to the serial device object.
|
|
|
|
PIrp - A pointer to the IRP that is being cancelled
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
PDEVICE_EXTENSION pDevExt = PDevObj->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, ("<CancelQueuedIrp\n" ));
|
|
|
|
PERF_EXIT( PERF_CancelQueuedIrp );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
ReadTimeout(
|
|
IN PKDPC PDpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemContext1,
|
|
IN PVOID SystemContext2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to complete a read because its total
|
|
timer has expired.
|
|
|
|
Arguments:
|
|
|
|
PDpc - Not Used.
|
|
|
|
DeferredContext - Really points to the device extension.
|
|
|
|
SystemContext1 - Not Used.
|
|
|
|
SystemContext2 - Not Used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PDEVICE_EXTENSION pDevExt = DeferredContext;
|
|
KIRQL oldIrql;
|
|
|
|
PERF_ENTRY( PERF_ReadTimeout );
|
|
|
|
UNREFERENCED_PARAMETER(PDpc);
|
|
UNREFERENCED_PARAMETER(SystemContext1);
|
|
UNREFERENCED_PARAMETER(SystemContext2);
|
|
|
|
DbgDump(DBG_TIME, (">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, ("<ReadTimeout\n"));
|
|
|
|
PERF_EXIT( PERF_ReadTimeout );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
IntervalReadTimeout(
|
|
IN PKDPC PDpc,
|
|
IN PVOID DeferredContext,
|
|
IN PVOID SystemContext1,
|
|
IN PVOID SystemContext2
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used timeout the request if the time between
|
|
characters exceed the interval time. A global is kept in
|
|
the device extension that records the count of characters read
|
|
the last the last time this routine was invoked (This dpc
|
|
will resubmit the timer if the count has changed). If the
|
|
count has not changed then this routine will attempt to complete
|
|
the irp. Note the special case of the last count being zero.
|
|
The timer isn't really in effect until the first character is
|
|
read.
|
|
|
|
Arguments:
|
|
|
|
PDpc - Not Used.
|
|
|
|
DeferredContext - Really points to the device extension.
|
|
|
|
SystemContext1 - Not Used.
|
|
|
|
SystemContext2 - Not Used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PDEVICE_EXTENSION pDevExt = DeferredContext;
|
|
KIRQL irql;
|
|
|
|
PERF_ENTRY( PERF_IntervalReadTimeout );
|
|
|
|
UNREFERENCED_PARAMETER(PDpc);
|
|
UNREFERENCED_PARAMETER(SystemContext1);
|
|
UNREFERENCED_PARAMETER(SystemContext2);
|
|
|
|
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, ("<IntervalReadTimeout\n"));
|
|
|
|
PERF_EXIT( PERF_IntervalReadTimeout );
|
|
|
|
return;
|
|
}
|
|
|
|
// EOF
|