1052 lines
32 KiB
C
1052 lines
32 KiB
C
/* ++
|
||
|
||
Copyright (c) 1999-2000 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
USBIO.C
|
||
|
||
Abstract:
|
||
|
||
USB I/O functions
|
||
|
||
Environment:
|
||
|
||
kernel mode only
|
||
|
||
|
||
Revision History:
|
||
|
||
07-14-99 : created
|
||
|
||
Authors:
|
||
|
||
Jeff Midkiff (jeffmi)
|
||
|
||
-- */
|
||
|
||
#include <wdm.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <usbdi.h>
|
||
#include <usbdlib.h>
|
||
#include <ntddser.h>
|
||
|
||
#include "wceusbsh.h"
|
||
|
||
|
||
NTSTATUS
|
||
UsbSubmitSyncUrbCompletion(
|
||
IN PDEVICE_OBJECT PDevObj,
|
||
IN PIRP PIrp,
|
||
IN PKEVENT PSyncEvent
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
PDevObj - Pointer to Device Object
|
||
PIrp - Pointer to IRP that is being completed
|
||
PSyncEvent - Pointer to event that we should set
|
||
|
||
Return Value:
|
||
|
||
STATUS_MORE_PROCESSING_REQUIRED
|
||
|
||
--*/
|
||
{
|
||
UNREFERENCED_PARAMETER( PDevObj );
|
||
UNREFERENCED_PARAMETER( PIrp );
|
||
|
||
DbgDump(DBG_USB, (">UsbSubmitSyncUrbCompletion (%p)\n", PIrp) );
|
||
|
||
ASSERT( PSyncEvent );
|
||
KeSetEvent( PSyncEvent, IO_NO_INCREMENT, FALSE );
|
||
|
||
DbgDump(DBG_USB, ("<UsbSubmitSyncUrbCompletion 0x%x\n", PIrp->IoStatus.Status ) );
|
||
|
||
// our driver owns and releases the irp
|
||
return STATUS_MORE_PROCESSING_REQUIRED;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
UsbSubmitSyncUrb(
|
||
IN PDEVICE_OBJECT PDevObj,
|
||
IN PURB PUrb,
|
||
IN BOOLEAN Configuration,
|
||
IN LONG TimeOut
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine issues a synchronous URB request to the USBD.
|
||
|
||
Arguments:
|
||
|
||
PDevObj - Ptr to our FDO
|
||
|
||
PUrb - URB to pass
|
||
|
||
Configuration - special case to allow USB config transactions onto the bus.
|
||
We need to do this because a) if the device was removed then we can stall the controller
|
||
which results in a reset kicking anything off the bus and re-enumerating the bus.
|
||
b) to trap any cases of suprise removal from numerous paths
|
||
|
||
TimeOut - timeout in milliseconds
|
||
|
||
Note: runs at PASSIVE_LEVEL.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - propogates status from USBD
|
||
|
||
--*/
|
||
{
|
||
PDEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
|
||
IO_STATUS_BLOCK ioStatus = {0, 0};
|
||
PIO_STACK_LOCATION pNextIrpSp;
|
||
KEVENT event;
|
||
NTSTATUS status, wait_status;
|
||
PIRP pIrp;
|
||
|
||
PAGED_CODE();
|
||
|
||
DbgDump(DBG_USB|DBG_TRACE, (">UsbSubmitSyncUrb\n") );
|
||
|
||
if ( !PUrb || !pDevExt->NextDevice ) {
|
||
status = STATUS_INVALID_PARAMETER;
|
||
DbgDump(DBG_ERR, ("UsbSubmitSyncUrb.1: 0x%x\n", status));
|
||
TEST_TRAP();
|
||
return status;
|
||
}
|
||
|
||
if ( !Configuration && !CanAcceptIoRequests(PDevObj, TRUE, TRUE) ) {
|
||
status = STATUS_DELETE_PENDING;
|
||
DbgDump(DBG_ERR, ("UsbSubmitSyncUrb.2: 0x%x\n", status));
|
||
return status;
|
||
}
|
||
|
||
// we need to grab the lock here to keep it's IoCount correct
|
||
status = AcquireRemoveLock(&pDevExt->RemoveLock, PUrb);
|
||
if ( !NT_SUCCESS(status) ) {
|
||
DbgDump(DBG_ERR, ("UsbSubmitSyncUrb.3: 0x%x\n", status));
|
||
return status;
|
||
}
|
||
|
||
DbgDump(DBG_USB, (">UsbSubmitSyncUrb (%p, %p)\n", PDevObj, PUrb) );
|
||
|
||
pIrp = IoAllocateIrp( (CCHAR)(pDevExt->NextDevice->StackSize + 1), FALSE);
|
||
|
||
if ( pIrp ) {
|
||
|
||
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
||
|
||
RecycleIrp( PDevObj, pIrp);
|
||
|
||
IoSetCompletionRoutine(pIrp,
|
||
UsbSubmitSyncUrbCompletion,
|
||
&event, // Context
|
||
TRUE, TRUE, TRUE );
|
||
|
||
pNextIrpSp = IoGetNextIrpStackLocation(pIrp);
|
||
ASSERT(pNextIrpSp);
|
||
pNextIrpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
|
||
pNextIrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
|
||
pNextIrpSp->Parameters.DeviceIoControl.OutputBufferLength = 0;
|
||
pNextIrpSp->Parameters.DeviceIoControl.InputBufferLength = 0;
|
||
|
||
pNextIrpSp->Parameters.Others.Argument1 = PUrb;
|
||
|
||
status = IoCallDriver( pDevExt->NextDevice, pIrp );
|
||
|
||
if (STATUS_PENDING == status ) {
|
||
//
|
||
// Set a default timeout in case the hardware is flakey, so USB will not hang us.
|
||
// We may want these timeouts user configurable via registry.
|
||
//
|
||
LARGE_INTEGER timeOut;
|
||
|
||
ASSERT(TimeOut >= 0);
|
||
timeOut.QuadPart = MILLISEC_TO_100NANOSEC( (TimeOut == 0 ? DEFAULT_PENDING_TIMEOUT : TimeOut) );
|
||
|
||
wait_status = KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, &timeOut );
|
||
|
||
if (STATUS_TIMEOUT == wait_status) {
|
||
//
|
||
// The wait timed out, try to cancel the Irp.
|
||
// N.B: if you freed the Irp in the completion routine
|
||
// then you have a race condition between the completion routine freeing the Irp
|
||
// and the timer firing where we need to set the cancel bit.
|
||
//
|
||
DbgDump(DBG_USB|DBG_WRN, ("UsbSubmitSyncUrb: STATUS_TIMEOUT\n"));
|
||
|
||
if ( !IoCancelIrp(pIrp) ) {
|
||
//
|
||
// This means USB has the Irp in a non-canceable state.
|
||
//
|
||
DbgDump(DBG_ERR, ("!IoCancelIrp(%p)\n", pIrp));
|
||
TEST_TRAP();
|
||
}
|
||
|
||
//
|
||
// Wait for our completion routine, to see if the Irp completed normally or actually cancelled.
|
||
// An alternative could be alloc an event & status block, stored in the Irp
|
||
// strung along a list, which would also get freed in the completion routine...
|
||
// which creates other problems not worth the effort for an exit condition.
|
||
//
|
||
wait_status = KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL );
|
||
}
|
||
}
|
||
|
||
//
|
||
// The completion routine signalled the event and completed,
|
||
// and our the timer has expired. Now we can safely free the Irp.
|
||
//
|
||
status = pIrp->IoStatus.Status;
|
||
|
||
#if DBG
|
||
if (STATUS_SUCCESS != status) {
|
||
DbgDump(DBG_ERR, ("UsbSubmitSyncUrb IrpStatus: 0x%x UrbStatus: 0x%x\n", status, PUrb->UrbHeader.Status) );
|
||
}
|
||
#endif
|
||
|
||
IoFreeIrp( pIrp );
|
||
|
||
} else {
|
||
DbgDump(DBG_ERR, ("IoAllocateIrp failed!\n") );
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
TEST_TRAP();
|
||
}
|
||
|
||
ReleaseRemoveLock(&pDevExt->RemoveLock, PUrb);
|
||
|
||
DbgDump(DBG_USB|DBG_TRACE, ("<UsbSubmitSyncUrb (0x%x)\n", status) );
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
UsbClassVendorCommand(
|
||
IN PDEVICE_OBJECT PDevObj,
|
||
IN UCHAR Request,
|
||
IN USHORT Value,
|
||
IN USHORT Index,
|
||
IN PVOID Buffer,
|
||
IN OUT PULONG BufferLen,
|
||
IN BOOLEAN Read,
|
||
IN ULONG Class
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Issue class or vendor specific command
|
||
|
||
Arguments:
|
||
|
||
PDevObj - pointer to a your object
|
||
Request - request field of class/vendor specific command
|
||
Value - value field of class/vendor specific command
|
||
Index - index field of class/vendor specific command
|
||
Buffer - pointer to data buffer
|
||
BufferLen - data buffer length
|
||
Read - data direction flag
|
||
Class - True if Class Command, else vendor command
|
||
|
||
Return Value:
|
||
NTSTATUS
|
||
|
||
--*/
|
||
{
|
||
PDEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
|
||
NTSTATUS status;
|
||
PURB pUrb;
|
||
ULONG ulSize;
|
||
ULONG ulLength;
|
||
|
||
PAGED_CODE();
|
||
|
||
DbgDump(DBG_USB, (">UsbClassVendorCommand\n" ));
|
||
|
||
ulLength = BufferLen ? *BufferLen : 0;
|
||
|
||
ulSize = sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST);
|
||
|
||
pUrb = ExAllocateFromNPagedLookasideList( &pDevExt->VendorRequestUrbPool );
|
||
|
||
if (pUrb) {
|
||
|
||
UsbBuildVendorRequest( pUrb,
|
||
Class == WCEUSB_CLASS_COMMAND ? URB_FUNCTION_CLASS_INTERFACE : URB_FUNCTION_VENDOR_DEVICE,
|
||
(USHORT)ulSize,
|
||
Read ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT,
|
||
0,
|
||
Request,
|
||
Value,
|
||
Index,
|
||
Buffer,
|
||
NULL,
|
||
ulLength,
|
||
NULL);
|
||
|
||
status = UsbSubmitSyncUrb(PDevObj, pUrb, FALSE, DEFAULT_CTRL_TIMEOUT);
|
||
|
||
if (BufferLen)
|
||
*BufferLen = pUrb->UrbControlVendorClassRequest.TransferBufferLength;
|
||
|
||
ExFreeToNPagedLookasideList( &pDevExt->VendorRequestUrbPool, pUrb );
|
||
|
||
} else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
DbgDump(DBG_ERR, ("ExAllocatePool error: 0x%x\n", status));
|
||
TEST_TRAP();
|
||
}
|
||
|
||
DbgDump(DBG_USB, ("<UsbClassVendorCommand (0x%x)\n", status));
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Usb Read / Write Utils
|
||
//
|
||
|
||
NTSTATUS
|
||
UsbReadWritePacket(
|
||
IN PDEVICE_EXTENSION PDevExt,
|
||
IN PIRP PIrp,
|
||
IN PIO_COMPLETION_ROUTINE CompletionRoutine,
|
||
IN LARGE_INTEGER TimeOut,
|
||
IN PKDEFERRED_ROUTINE TimeoutRoutine,
|
||
IN BOOLEAN Read
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function allocates and passes a Bulk Transfer URB Request
|
||
down to USBD to perform a Read/Write. Note that the Packet
|
||
MUST freed (put back on the packet list) in the
|
||
CompletionRoutine.
|
||
|
||
Arguments:
|
||
|
||
PDevExt - Pointer to device extension
|
||
|
||
PIrp - Read/Write IRP
|
||
|
||
CompletionRoutine - completion routine to set in the Irp
|
||
|
||
TimeOut - Timeout value for packet. If no timeout is
|
||
specified the we use a default timeout.
|
||
|
||
Read - TRUE for Read, else Write
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS
|
||
|
||
Notes:
|
||
|
||
This is not currently documented in the DDK, so here 's what
|
||
happens:
|
||
|
||
We pass the Irp to USBD. USBD parameter checks the Irp.
|
||
If any parameters are invalid then USBD returns an NT status code
|
||
and Urb status code, then the Irp goes to our completion routine.
|
||
If there are no errors then USBD passes Irp to HCD. HCD queues the
|
||
Irp to it's StartIo & and returns STATUS_PENDING. When HCD finishes
|
||
the (DMA) transfer it completes the Irp, setting the Irp & Urb status
|
||
fields. USBD's completion routine gets the Irp, translates any HCD
|
||
error codes, completes it, which percolates it back up to our
|
||
completion routine.
|
||
|
||
Note: HCD uses DMA & therefore MDLs. Since this client driver
|
||
currently uses METHOD_BUFFERED, then USBD allocates an MDL for
|
||
HCD. So you have the I/O manager double bufffering the data
|
||
and USBD mapping MDLs. What the hell, we have to buffer user reads
|
||
too... uggh. Note that if you change to method direct then
|
||
the read path gets nastier.
|
||
|
||
Note: when the user submits a write buffer > MaxTransferSize
|
||
then we reject the buffer.
|
||
|
||
--*/
|
||
{
|
||
PIO_STACK_LOCATION pIrpSp;
|
||
PUSB_PACKET pPacket;
|
||
NTSTATUS status;
|
||
KIRQL irql; //, cancelIrql;
|
||
PURB pUrb;
|
||
PVOID pvBuffer;
|
||
ULONG ulLength;
|
||
USBD_PIPE_HANDLE hPipe;
|
||
|
||
PERF_ENTRY( PERF_UsbReadWritePacket );
|
||
|
||
DbgDump(DBG_USB, (">UsbReadWritePacket (%p, %p, %d, %d)\n", PDevExt->DeviceObject, PIrp, TimeOut.QuadPart/10000, Read));
|
||
|
||
|
||
if ( !PDevExt || !PIrp || !CompletionRoutine ) {
|
||
|
||
status = PIrp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
||
DbgDump(DBG_ERR, ("<UsbReadWritePacket 0x%x\n", status));
|
||
KeAcquireSpinLock( &PDevExt->ControlLock, &irql );
|
||
|
||
TryToCompleteCurrentIrp(
|
||
PDevExt,
|
||
status,
|
||
&PIrp,
|
||
NULL, // Queue
|
||
NULL, // IntervalTimer
|
||
NULL, // PTotalTimer
|
||
NULL, // Starter
|
||
NULL, // PGetNextIrp
|
||
IRP_REF_RX_BUFFER, // RefType
|
||
(BOOLEAN)(!Read),
|
||
irql ); // Complete
|
||
|
||
PERF_EXIT( PERF_UsbReadWritePacket );
|
||
TEST_TRAP();
|
||
return status;
|
||
}
|
||
|
||
IRP_SET_REFERENCE(PIrp, IRP_REF_RX_BUFFER);
|
||
|
||
pIrpSp = IoGetCurrentIrpStackLocation(PIrp);
|
||
ASSERT( pIrpSp );
|
||
|
||
//
|
||
// Allocate & Build a USB Bulk Transfer Request (Packet)
|
||
//
|
||
pPacket = ExAllocateFromNPagedLookasideList( &PDevExt->PacketPool );
|
||
|
||
if ( !pPacket ) {
|
||
|
||
status = PIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
|
||
DbgDump(DBG_ERR, ("<UsbReadWritePacket 0x%x\n", status));
|
||
KeAcquireSpinLock( &PDevExt->ControlLock, &irql );
|
||
|
||
TryToCompleteCurrentIrp(
|
||
PDevExt,
|
||
status,
|
||
&PIrp,
|
||
NULL, // Queue
|
||
NULL, // IntervalTimer
|
||
NULL, // PTotalTimer
|
||
NULL, // Starter
|
||
NULL, // PGetNextIrp
|
||
IRP_REF_RX_BUFFER, // RefType
|
||
(BOOLEAN)(!Read),
|
||
irql );
|
||
|
||
PERF_EXIT( PERF_UsbReadWritePacket );
|
||
TEST_TRAP();
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// init the Packet
|
||
//
|
||
RtlZeroMemory( pPacket, sizeof(USB_PACKET) );
|
||
|
||
pPacket->DeviceExtension = PDevExt;
|
||
|
||
pPacket->Irp = PIrp;
|
||
|
||
pUrb = &pPacket->Urb;
|
||
ASSERT( pUrb );
|
||
|
||
KeAcquireSpinLock( &PDevExt->ControlLock, &irql );
|
||
|
||
|
||
if (Read) {
|
||
//
|
||
// store the Urb for buffered reads
|
||
//
|
||
PDevExt->UsbReadUrb = pUrb;
|
||
}
|
||
|
||
//
|
||
// Build the URB.
|
||
// Note: HCD breaks up our buffer into Transport Descriptors (TD)
|
||
// of PipeInfo->MaxPacketSize.
|
||
// Q: does USBD/HCD look at the PipeInfo->MaxTransferSize to see
|
||
// if he can Rx/Tx?
|
||
// A: Yes. HCD will return urbStatus = USBD_STATUS_INVALID_PARAMETER
|
||
// and status = STATUS_INVALID_PARAMETER of too large.
|
||
//
|
||
ASSERT( Read ? (PDevExt->UsbReadBuffSize <= PDevExt->MaximumTransferSize ) :
|
||
(pIrpSp->Parameters.Write.Length <= PDevExt->MaximumTransferSize ) );
|
||
|
||
//
|
||
// Note: Reads are done into our local USB read buffer,
|
||
// and then copied into the user's buffer on completion.
|
||
// Writes are done directly from user's buffer.
|
||
// We allow NULL writes to indicate end of a USB transaction.
|
||
//
|
||
pvBuffer = Read ? PDevExt->UsbReadBuff :
|
||
PIrp->AssociatedIrp.SystemBuffer;
|
||
|
||
ulLength = Read ? PDevExt->UsbReadBuffSize :
|
||
pIrpSp->Parameters.Write.Length;
|
||
|
||
hPipe = Read ? PDevExt->ReadPipe.hPipe :
|
||
PDevExt->WritePipe.hPipe;
|
||
|
||
ASSERT( hPipe );
|
||
|
||
UsbBuildTransferUrb(
|
||
pUrb, // Urb
|
||
pvBuffer, // Buffer
|
||
ulLength, // Length
|
||
hPipe, // PipeHandle
|
||
Read // ReadRequest
|
||
);
|
||
|
||
//
|
||
// put the packet on a pending list
|
||
//
|
||
InsertTailList( Read ? &PDevExt->PendingReadPackets : // ListHead,
|
||
&PDevExt->PendingWritePackets,
|
||
&pPacket->ListEntry ); // ListEntry
|
||
|
||
//
|
||
// Increment the pending packet counter
|
||
//
|
||
InterlockedIncrement( Read ? &PDevExt->PendingReadCount:
|
||
&PDevExt->PendingWriteCount );
|
||
|
||
//
|
||
// Setup Irp for submit Urb IOCTL
|
||
//
|
||
IoCopyCurrentIrpStackLocationToNext(PIrp);
|
||
|
||
pIrpSp = IoGetNextIrpStackLocation(PIrp);
|
||
|
||
pIrpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
|
||
|
||
pIrpSp->Parameters.Others.Argument1 = pUrb;
|
||
|
||
pIrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
|
||
|
||
IoSetCompletionRoutine( PIrp,
|
||
CompletionRoutine,
|
||
pPacket, // Context
|
||
TRUE, TRUE, TRUE);
|
||
//
|
||
// Initialize and Arm the Packet's Timer.
|
||
// If the Timer fires then the packet's Timeout routine runs.
|
||
//
|
||
KeInitializeTimer( &pPacket->TimerObj );
|
||
|
||
if ( 0 != TimeOut.QuadPart ) {
|
||
|
||
pPacket->Timeout = TimeOut;
|
||
|
||
ASSERT( TimeoutRoutine );
|
||
|
||
pPacket->TimerDPCRoutine = TimeoutRoutine;
|
||
|
||
KeInitializeDpc( &pPacket->TimerDPCObj, // DPC Object
|
||
pPacket->TimerDPCRoutine, // DPC Routine
|
||
pPacket ); // Context
|
||
|
||
DbgDump(DBG_USB, ("Timer for Irp %p due in %d msec\n", pPacket->Irp, pPacket->Timeout.QuadPart/10000 ));
|
||
|
||
KeSetTimer( &pPacket->TimerObj, // TimerObj
|
||
pPacket->Timeout, // DueTime
|
||
&pPacket->TimerDPCObj // DPC Obj
|
||
);
|
||
|
||
}
|
||
|
||
//
|
||
// pass the Irp to USBD
|
||
//
|
||
DbgDump(DBG_IRP, ("UsbReadWritePacket IoCallDriver with %p\n", PIrp));
|
||
|
||
KeReleaseSpinLock( &PDevExt->ControlLock, irql );
|
||
|
||
status = IoCallDriver( PDevExt->NextDevice, PIrp );
|
||
|
||
if ( (STATUS_SUCCESS != status) && (STATUS_PENDING != status) ) {
|
||
//
|
||
// We end up here after our completion routine runs
|
||
// for an error condition i.e., when we have and
|
||
// invalid parameter, or when user pulls the plug, etc.
|
||
//
|
||
DbgDump(DBG_ERR, ("UsbReadWritePacket error: 0x%x\n", status));
|
||
}
|
||
|
||
DbgDump(DBG_USB , ("<UsbReadWritePacket 0x%x\n", status));
|
||
|
||
PERF_EXIT( PERF_UsbReadWritePacket );
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// This routine sets up a PUrb for a _URB_BULK_OR_INTERRUPT_TRANSFER.
|
||
// It assumes it's called holding a SpinLock.
|
||
//
|
||
VOID
|
||
UsbBuildTransferUrb(
|
||
PURB PUrb,
|
||
PUCHAR PBuffer,
|
||
ULONG Length,
|
||
IN USBD_PIPE_HANDLE PipeHandle,
|
||
IN BOOLEAN Read
|
||
)
|
||
{
|
||
ULONG size;
|
||
|
||
ASSERT( PUrb );
|
||
ASSERT( PipeHandle );
|
||
|
||
size = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER);
|
||
|
||
RtlZeroMemory(PUrb, size);
|
||
|
||
PUrb->UrbBulkOrInterruptTransfer.Hdr.Length = (USHORT)size;
|
||
|
||
PUrb->UrbBulkOrInterruptTransfer.Hdr.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER;
|
||
|
||
PUrb->UrbBulkOrInterruptTransfer.Hdr.Status = USBD_STATUS_SUCCESS;
|
||
|
||
PUrb->UrbBulkOrInterruptTransfer.PipeHandle = PipeHandle;
|
||
|
||
//
|
||
// we are using a tranfsfer buffer instead of an MDL
|
||
//
|
||
PUrb->UrbBulkOrInterruptTransfer.TransferBuffer = PBuffer;
|
||
|
||
PUrb->UrbBulkOrInterruptTransfer.TransferBufferLength = Length;
|
||
|
||
PUrb->UrbBulkOrInterruptTransfer.TransferBufferMDL = NULL;
|
||
|
||
//
|
||
// Set transfer flags
|
||
//
|
||
PUrb->UrbBulkOrInterruptTransfer.TransferFlags |= Read ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT;
|
||
|
||
//
|
||
// Short transfer is not treated as an error.
|
||
// If USBD_TRANSFER_DIRECTION_IN is set,
|
||
// directs the HCD not to return an error if a packet is received from the device
|
||
// shorter than the maximum packet size for the endpoint.
|
||
// Otherwise, a short request is returns an error condition.
|
||
//
|
||
PUrb->UrbBulkOrInterruptTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK;
|
||
|
||
//
|
||
// no linkage for now
|
||
//
|
||
PUrb->UrbBulkOrInterruptTransfer.UrbLink = NULL;
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// Usb Reset Utils
|
||
//
|
||
VOID
|
||
UsbResetOrAbortPipeWorkItem(
|
||
IN PWCE_WORK_ITEM PWorkItem
|
||
)
|
||
{
|
||
PDEVICE_OBJECT pDevObj = PWorkItem->DeviceObject;
|
||
PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension;
|
||
|
||
UCHAR retries = 0;
|
||
ULONG ulUniqueErrorValue = 0;
|
||
NTSTATUS status = STATUS_DELETE_PENDING;
|
||
|
||
DbgDump(DBG_WORK_ITEMS, (">UsbResetOrAbortPipeWorkItem (0x%x)\n", pDevObj));
|
||
|
||
//
|
||
// The work item was queued at IRQL > PASSIVE some time ago from an I/O completion routine.
|
||
// If we are unsuccessful after max retries then stop taking I/O requests & assume the device is broken
|
||
//
|
||
if ( CanAcceptIoRequests(pDevObj, TRUE, TRUE) )
|
||
{
|
||
switch (PWorkItem->Flags)
|
||
{
|
||
case WORK_ITEM_RESET_READ_PIPE:
|
||
{
|
||
DbgDump(DBG_WORK_ITEMS, ("WORK_ITEM_RESET_READ_PIPE\n"));
|
||
|
||
if ( pDevExt->ReadDeviceErrors < g_ulMaxPipeErrors)
|
||
{
|
||
//
|
||
// reset read Pipe, which could fail.
|
||
// E.g. flakey h/w, suprise remove, timeout, ...
|
||
//
|
||
status = UsbResetOrAbortPipe( pDevObj, &pDevExt->ReadPipe, RESET );
|
||
|
||
switch (status)
|
||
{
|
||
case STATUS_SUCCESS:
|
||
{
|
||
//
|
||
// kick start another read
|
||
//
|
||
status = UsbRead( pDevExt,
|
||
(BOOLEAN)(pDevExt->IntPipe.hPipe ? TRUE : FALSE) );
|
||
|
||
if ( (STATUS_SUCCESS == status) || (STATUS_PENDING == status) ) {
|
||
//
|
||
// the device recovered OK
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
|
||
} else {
|
||
DbgDump(DBG_ERR, ("UsbRead error: 0x%x\n", status));
|
||
}
|
||
|
||
} break;
|
||
|
||
case STATUS_UNSUCCESSFUL:
|
||
// a previous reset/abort request failed, so this request was rejected
|
||
break;
|
||
|
||
case STATUS_DELETE_PENDING:
|
||
// the device is going away
|
||
break;
|
||
|
||
case STATUS_PENDING:
|
||
// there is a reset/abort request already pending
|
||
break;
|
||
|
||
default:
|
||
{
|
||
//
|
||
// if we can not reset the endpoint the device is hosed or removed
|
||
//
|
||
DbgDump(DBG_ERR, ("UsbResetOrAbortPipeWorkItem.1 error: 0x%x\n", status ));
|
||
retries = 1;
|
||
ulUniqueErrorValue = ERR_NO_READ_PIPE_RESET;
|
||
} break;
|
||
|
||
} // status
|
||
|
||
} else {
|
||
status = (NTSTATUS)PtrToLong(PWorkItem->Context); // Urb status is stored here
|
||
retries = (UCHAR)pDevExt->ReadDeviceErrors;
|
||
ulUniqueErrorValue = ERR_MAX_READ_PIPE_DEVICE_ERRORS;
|
||
}
|
||
|
||
if ( USBD_STATUS_BUFFER_OVERRUN == (USBD_STATUS)PtrToLong(PWorkItem->Context)) {
|
||
LogError( NULL,
|
||
pDevObj,
|
||
0, 0,
|
||
(UCHAR)pDevExt->ReadDeviceErrors,
|
||
ERR_USB_READ_BUFF_OVERRUN,
|
||
(USBD_STATUS)PtrToLong(PWorkItem->Context),
|
||
SERIAL_USB_READ_BUFF_OVERRUN,
|
||
pDevExt->DeviceName.Length + sizeof(WCHAR),
|
||
pDevExt->DeviceName.Buffer,
|
||
0, NULL );
|
||
}
|
||
} // WORK_ITEM_RESET_READ_PIPE
|
||
break;
|
||
|
||
|
||
case WORK_ITEM_RESET_WRITE_PIPE:
|
||
{
|
||
DbgDump(DBG_WORK_ITEMS, ("WORK_ITEM_RESET_WRITE_PIPE\n"));
|
||
|
||
if (pDevExt->WriteDeviceErrors < g_ulMaxPipeErrors)
|
||
{
|
||
//
|
||
// reset write Pipe, which could fail.
|
||
// E.g. flakey h/w, suprise remove, timeout, ...
|
||
//
|
||
status = UsbResetOrAbortPipe( pDevObj, &pDevExt->WritePipe, RESET );
|
||
|
||
switch (status)
|
||
{
|
||
case STATUS_SUCCESS:
|
||
// the device recovered OK
|
||
break;
|
||
|
||
case STATUS_UNSUCCESSFUL:
|
||
// a previous reset/abort request failed, so this request was rejected
|
||
break;
|
||
|
||
case STATUS_DELETE_PENDING:
|
||
// the device is going away
|
||
break;
|
||
|
||
case STATUS_PENDING:
|
||
// there is a reset/abort request already pending
|
||
break;
|
||
|
||
default: {
|
||
//
|
||
// if we can not reset the endpoint the device is hosed or removed
|
||
//
|
||
DbgDump(DBG_ERR, ("UsbResetOrAbortPipeWorkItem.2 error: 0x%x\n", status ));
|
||
retries = 1;
|
||
ulUniqueErrorValue = ERR_NO_WRITE_PIPE_RESET;
|
||
} break;
|
||
|
||
} // status
|
||
|
||
} else {
|
||
status = (NTSTATUS)PtrToLong(PWorkItem->Context);
|
||
retries = (UCHAR)pDevExt->WriteDeviceErrors;
|
||
ulUniqueErrorValue = ERR_MAX_WRITE_PIPE_DEVICE_ERRORS;
|
||
}
|
||
|
||
} // WORK_ITEM_RESET_WRITE_PIPE
|
||
break;
|
||
|
||
|
||
case WORK_ITEM_RESET_INT_PIPE:
|
||
{
|
||
DbgDump(DBG_WORK_ITEMS, ("WORK_ITEM_RESET_INT_PIPE\n"));
|
||
|
||
if ( pDevExt->IntDeviceErrors < g_ulMaxPipeErrors)
|
||
{
|
||
//
|
||
// reset INT Pipe, which could fail.
|
||
// E.g. flakey h/w, suprise remove, timeout, ...
|
||
//
|
||
status = UsbResetOrAbortPipe( pDevObj, &pDevExt->IntPipe, RESET );
|
||
|
||
switch (status)
|
||
{
|
||
case STATUS_SUCCESS:
|
||
{
|
||
//
|
||
// kick start another INT read
|
||
//
|
||
status = UsbInterruptRead( pDevExt );
|
||
|
||
if ((STATUS_SUCCESS == status) || (STATUS_PENDING == status) ) {
|
||
//
|
||
// the device recovered OK
|
||
//
|
||
status = STATUS_SUCCESS;
|
||
|
||
} else {
|
||
DbgDump(DBG_ERR, ("UsbInterruptRead error: 0x%x\n", status));
|
||
}
|
||
|
||
} break;
|
||
|
||
case STATUS_UNSUCCESSFUL:
|
||
// a previous reset/abort request failed, so this request was rejected
|
||
break;
|
||
|
||
case STATUS_DELETE_PENDING:
|
||
// the device is going away
|
||
break;
|
||
|
||
case STATUS_PENDING:
|
||
// there is a reset/abort request already pending
|
||
break;
|
||
|
||
default:
|
||
{
|
||
//
|
||
// if we can not reset the endpoint the device is either hosed or removed
|
||
//
|
||
DbgDump(DBG_ERR, ("UsbResetOrAbortPipeWorkItem.3 error: 0x%x\n", status ));
|
||
retries = 1;
|
||
ulUniqueErrorValue = ERR_NO_INT_PIPE_RESET;
|
||
} break;
|
||
|
||
} // switch
|
||
|
||
} else {
|
||
status = (NTSTATUS)PtrToLong(PWorkItem->Context);
|
||
retries = (UCHAR)pDevExt->IntDeviceErrors;
|
||
ulUniqueErrorValue = ERR_MAX_INT_PIPE_DEVICE_ERRORS;
|
||
}
|
||
|
||
} // WORK_ITEM_RESET_INT_PIPE
|
||
break;
|
||
|
||
case WORK_ITEM_ABORT_READ_PIPE:
|
||
case WORK_ITEM_ABORT_WRITE_PIPE:
|
||
case WORK_ITEM_ABORT_INT_PIPE:
|
||
default:
|
||
// status = STATUS_NOT_IMPLEMENTED; - let it fall through and see what happens
|
||
DbgDump(DBG_ERR, ("ResetWorkItemFlags: 0x%x 0x%x\n", PWorkItem->Flags, status ));
|
||
ASSERT(0);
|
||
break;
|
||
|
||
} // PWorkItem->Flags
|
||
|
||
} else {
|
||
status = STATUS_DELETE_PENDING;
|
||
}
|
||
|
||
//
|
||
// is the device is hosed?
|
||
//
|
||
if ( (STATUS_SUCCESS != status) && (STATUS_DELETE_PENDING != status) && (0 != retries)) {
|
||
|
||
// only log known errors, not suprise remove.
|
||
if (1 == retries ) {
|
||
|
||
// mark as PNP_DEVICE_REMOVED
|
||
InterlockedExchange(&pDevExt->DeviceRemoved, TRUE);
|
||
|
||
DbgDump(DBG_WRN, ("DEVICE REMOVED\n"));
|
||
|
||
} else {
|
||
|
||
// mark as PNP_DEVICE_FAILED
|
||
InterlockedExchange(&pDevExt->AcceptingRequests, FALSE);
|
||
|
||
DbgDump(DBG_ERR, ("*** UNRECOVERABLE DEVICE ERROR: (0x%x, %d) No longer Accepting Requests ***\n", status, retries ));
|
||
|
||
LogError( NULL,
|
||
pDevObj,
|
||
0, 0,
|
||
retries,
|
||
ulUniqueErrorValue,
|
||
status,
|
||
SERIAL_HARDWARE_FAILURE,
|
||
pDevExt->DeviceName.Length + sizeof(WCHAR),
|
||
pDevExt->DeviceName.Buffer,
|
||
0,
|
||
NULL );
|
||
}
|
||
|
||
IoInvalidateDeviceState( pDevExt->PDO );
|
||
|
||
}
|
||
|
||
DequeueWorkItem( pDevObj, PWorkItem );
|
||
|
||
DbgDump(DBG_WORK_ITEMS, ("<UsbResetOrAbortPipeWorkItem 0x%x\n", status));
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// NT's USB stack likes only 1 reset pending at any time.
|
||
// Also, if any reset fails then do NOT send anymore, else you'll get the controller
|
||
// or HUB in a funky state, where it will try to reset the port... which kicks off all
|
||
// the other devices on the hub.
|
||
//
|
||
NTSTATUS
|
||
UsbResetOrAbortPipe(
|
||
IN PDEVICE_OBJECT PDevObj,
|
||
IN PUSB_PIPE PPipe,
|
||
IN BOOLEAN Reset
|
||
)
|
||
{
|
||
PDEVICE_EXTENSION pDevExt;
|
||
NTSTATUS status;
|
||
KIRQL irql;
|
||
PURB pUrb;
|
||
|
||
ASSERT( PDevObj );
|
||
|
||
DbgDump(DBG_USB, (">UsbResetOrAbortPipe (%p)\n", PDevObj) );
|
||
|
||
PAGED_CODE();
|
||
|
||
if (!PDevObj || !PPipe || !PPipe->hPipe ) {
|
||
DbgDump(DBG_ERR, ("UsbResetOrAbortPipe: STATUS_INVALID_PARAMETER\n") );
|
||
TEST_TRAP();
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
pDevExt = PDevObj->DeviceExtension;
|
||
|
||
KeAcquireSpinLock(&pDevExt->ControlLock, &irql);
|
||
|
||
if ( PPipe->ResetOrAbortFailed ) {
|
||
status = STATUS_UNSUCCESSFUL;
|
||
DbgDump(DBG_ERR, ("UsbResetOrAbortPipe.1: 0x%x\n", status) );
|
||
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
|
||
return status;
|
||
}
|
||
|
||
if (!CanAcceptIoRequests(PDevObj, FALSE, TRUE) ||
|
||
!NT_SUCCESS(AcquireRemoveLock(&pDevExt->RemoveLock, UlongToPtr(Reset ? URB_FUNCTION_RESET_PIPE : URB_FUNCTION_ABORT_PIPE))))
|
||
{
|
||
status = STATUS_DELETE_PENDING;
|
||
DbgDump(DBG_ERR, ("UsbResetOrAbortPipe.2: 0x%x\n", status) );
|
||
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
|
||
return status;
|
||
}
|
||
|
||
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
|
||
|
||
//
|
||
// USBVERIFIER ASSERT: Reset sent on a pipe with a reset already pending
|
||
// The USB stack likes only 1 pending Reset or Abort request per pipe a time.
|
||
//
|
||
if ( 1 == InterlockedIncrement(&PPipe->ResetOrAbortPending) ) {
|
||
|
||
pUrb = ExAllocateFromNPagedLookasideList( &pDevExt->PipeRequestUrbPool );
|
||
|
||
if ( pUrb != NULL ) {
|
||
//
|
||
// pass the Reset -or- Abort request to USBD
|
||
//
|
||
pUrb->UrbHeader.Length = (USHORT)sizeof(struct _URB_PIPE_REQUEST);
|
||
|
||
pUrb->UrbHeader.Function = Reset ? URB_FUNCTION_RESET_PIPE : URB_FUNCTION_ABORT_PIPE;
|
||
|
||
pUrb->UrbPipeRequest.PipeHandle = PPipe->hPipe;
|
||
|
||
status = UsbSubmitSyncUrb(PDevObj, pUrb, FALSE, DEFAULT_BULK_TIMEOUT);
|
||
|
||
if (status != STATUS_SUCCESS) {
|
||
DbgDump(DBG_ERR , ("*** UsbResetOrAbortPipe ERROR: 0x%x ***\n", status));
|
||
InterlockedIncrement(&PPipe->ResetOrAbortFailed);
|
||
}
|
||
|
||
ExFreeToNPagedLookasideList(&pDevExt->PipeRequestUrbPool, pUrb);
|
||
|
||
} else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
DbgDump(DBG_ERR , ("ExAllocateFromNPagedLookasideList failed (0x%x)!\n", status));
|
||
}
|
||
|
||
InterlockedDecrement(&PPipe->ResetOrAbortPending);
|
||
|
||
ASSERT(PPipe->ResetOrAbortPending == 0);
|
||
|
||
} else {
|
||
//
|
||
// If there is a reset/abort request pending then we are done.
|
||
// Return STATUS_PENDING here so the work item won't start another transfer,
|
||
// but will dequeue the item. The real item will follow-up with the correct status.
|
||
//
|
||
DbgDump(DBG_WRN, ("UsbResetOrAbortPipe: STATUS_PENDING\n"));
|
||
TEST_TRAP();
|
||
status = STATUS_PENDING;
|
||
}
|
||
|
||
ReleaseRemoveLock(&pDevExt->RemoveLock, UlongToPtr(Reset ? URB_FUNCTION_RESET_PIPE : URB_FUNCTION_ABORT_PIPE));
|
||
|
||
DbgDump(DBG_USB, ("<UsbResetOrAbortPipe(0x%x)\n", status) );
|
||
|
||
return status;
|
||
}
|
||
|
||
// EOF
|