|
|
/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
DEVICE.C
Abstract:
This module contains the code that implements various support functions related to device configuration.
Environment:
kernel mode only
Notes:
Revision History:
10-29-95 : created
--*/
#include "wdm.h"
#include "stdarg.h"
#include "stdio.h"
#include "usbdi.h" //public data structures
#include "hcdi.h"
#include "usbd.h" //private data strutures
#ifdef USBD_DRIVER // USBPORT supercedes most of USBD, so we will remove
// the obsolete code by compiling it only if
// USBD_DRIVER is set.
#define DEADMAN_TIMER
#define DEADMAN_TIMEOUT 5000 //timeout in ms
//use a 5 second timeout
typedef struct _USBD_TIMEOUT_CONTEXT { PIRP Irp; KTIMER TimeoutTimer; KDPC TimeoutDpc; KSPIN_LOCK TimeoutSpin; KEVENT Event; BOOLEAN Complete; } USBD_TIMEOUT_CONTEXT, *PUSBD_TIMEOUT_CONTEXT;
#ifdef PAGE_CODE
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, USBD_SubmitSynchronousURB)
#pragma alloc_text(PAGE, USBD_SendCommand)
#pragma alloc_text(PAGE, USBD_OpenEndpoint)
#pragma alloc_text(PAGE, USBD_CloseEndpoint)
#pragma alloc_text(PAGE, USBD_FreeUsbAddress)
#pragma alloc_text(PAGE, USBD_AllocateUsbAddress)
#pragma alloc_text(PAGE, USBD_GetEndpointState)
#endif
#endif
#ifdef DEADMAN_TIMER
VOID USBD_SyncUrbTimeoutDPC( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) /*++
Routine Description:
This routine runs at DISPATCH_LEVEL IRQL.
Arguments:
Dpc - Pointer to the DPC object.
DeferredContext -
SystemArgument1 - not used. SystemArgument2 - not used.
Return Value:
None.
--*/ { PUSBD_TIMEOUT_CONTEXT usbdTimeoutContext = DeferredContext; BOOLEAN complete; #if DBG
BOOLEAN status; #endif
KIRQL irql;
KeAcquireSpinLock(&usbdTimeoutContext->TimeoutSpin, &irql); complete = usbdTimeoutContext->Complete; KeReleaseSpinLock(&usbdTimeoutContext->TimeoutSpin, irql);
if (!complete) { #if DBG
status = #endif
IoCancelIrp(usbdTimeoutContext->Irp);
#if DBG
USBD_ASSERT(status == TRUE); #endif
}
//OK to free it
KeSetEvent(&usbdTimeoutContext->Event, 1, FALSE); }
NTSTATUS USBD_SyncIrp_Complete( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++
Routine Description:
This routine is called when the port driver completes an IRP.
Arguments:
DeviceObject - Pointer to the device object for the class device.
Irp - Irp completed.
Context - Driver defined context.
Return Value:
The function value is the final status from the operation.
--*/ { PUSBD_TIMEOUT_CONTEXT usbdTimeoutContext = Context; KIRQL irql; BOOLEAN cancelled; NTSTATUS ntStatus; KeAcquireSpinLock(&usbdTimeoutContext->TimeoutSpin, &irql); usbdTimeoutContext->Complete = TRUE; cancelled = KeCancelTimer(&usbdTimeoutContext->TimeoutTimer); KeReleaseSpinLock(&usbdTimeoutContext->TimeoutSpin, irql);
// see if the timer was in the queue, if it was then it is safe to free
// it
if (cancelled) { // safe to free it
KeSetEvent(&usbdTimeoutContext->Event, 1, FALSE); }
ntStatus = Irp->IoStatus.Status; return ntStatus; }
#endif /* DEADMAN_TIMER */
NTSTATUS USBD_SubmitSynchronousURB( IN PURB Urb, IN PDEVICE_OBJECT DeviceObject, IN PUSBD_DEVICE_DATA DeviceData ) /*++
Routine Description:
Submit a Urb to HCD synchronously
Arguments:
Urb - Urb to submit
DeviceObject USBD device object
Return Value:
STATUS_SUCCESS if successful, STATUS_UNSUCCESSFUL otherwise
--*/ { NTSTATUS ntStatus = STATUS_SUCCESS, status; PIRP irp; KEVENT event; IO_STATUS_BLOCK ioStatus; PIO_STACK_LOCATION nextStack; #ifdef DEADMAN_TIMER
BOOLEAN haveTimer = FALSE; PUSBD_TIMEOUT_CONTEXT usbdTimeoutContext; #endif /* DEADMAN_TIMER */
PAGED_CODE();
USBD_KdPrint(3, ("'enter USBD_SubmitSynchronousURB\n")); ASSERT_DEVICE(DeviceData);
irp = IoBuildDeviceIoControlRequest( IOCTL_INTERNAL_USB_SUBMIT_URB, HCD_DEVICE_OBJECT(DeviceObject), NULL, 0, NULL, 0, TRUE, /* INTERNAL */ &event, &ioStatus);
if (NULL == irp) { USBD_KdBreak(("USBD_SubmitSynchronousURB build Irp failed\n")); return STATUS_INSUFFICIENT_RESOURCES; }
//
// Call the hc driver to perform the operation. If the returned status
// is PENDING, wait for the request to complete.
//
KeInitializeEvent(&event, NotificationEvent, FALSE);
nextStack = IoGetNextIrpStackLocation(irp); ASSERT(nextStack != NULL); nextStack->Parameters.Others.Argument1 = Urb;
#ifdef DEADMAN_TIMER
usbdTimeoutContext = GETHEAP(NonPagedPool, sizeof(*usbdTimeoutContext)); if (usbdTimeoutContext) { LARGE_INTEGER dueTime;
usbdTimeoutContext->Irp = irp; usbdTimeoutContext->Complete = FALSE;
KeInitializeEvent(&usbdTimeoutContext->Event, NotificationEvent, FALSE); KeInitializeSpinLock(&usbdTimeoutContext->TimeoutSpin); KeInitializeTimer(&usbdTimeoutContext->TimeoutTimer); KeInitializeDpc(&usbdTimeoutContext->TimeoutDpc, USBD_SyncUrbTimeoutDPC, usbdTimeoutContext);
dueTime.QuadPart = -10000 * DEADMAN_TIMEOUT;
KeSetTimer(&usbdTimeoutContext->TimeoutTimer, dueTime, &usbdTimeoutContext->TimeoutDpc);
haveTimer = TRUE;
IoSetCompletionRoutine(irp, USBD_SyncIrp_Complete, // always pass FDO to completion routine
usbdTimeoutContext, TRUE, TRUE, TRUE); } #endif
//
// initialize flags field
// for internal request
//
Urb->UrbHeader.UsbdFlags = 0;
//
// Init the Irp field for transfers
//
switch(Urb->UrbHeader.Function) { case URB_FUNCTION_CONTROL_TRANSFER: case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER: HC_URB(Urb)->HcdUrbCommonTransfer.hca.HcdIrp = irp;
if (HC_URB(Urb)->HcdUrbCommonTransfer.TransferBufferMDL == NULL && HC_URB(Urb)->HcdUrbCommonTransfer.TransferBufferLength != 0) {
if ((HC_URB(Urb)->HcdUrbCommonTransfer.TransferBufferMDL = IoAllocateMdl(HC_URB(Urb)->HcdUrbCommonTransfer.TransferBuffer, HC_URB(Urb)->HcdUrbCommonTransfer.TransferBufferLength, FALSE, FALSE, NULL)) == NULL) ntStatus = STATUS_INSUFFICIENT_RESOURCES; else { Urb->UrbHeader.UsbdFlags |= USBD_REQUEST_MDL_ALLOCATED; MmBuildMdlForNonPagedPool(HC_URB(Urb)->HcdUrbCommonTransfer.TransferBufferMDL); }
} break; }
USBD_KdPrint(3, ("'USBD_SubmitSynchronousURB: calling HCD with URB\n"));
if (NT_SUCCESS(ntStatus)) { // set the renter bit on the URB function code
Urb->UrbHeader.Function |= HCD_NO_USBD_CALL;
ntStatus = IoCallDriver(HCD_DEVICE_OBJECT(DeviceObject), irp); }
USBD_KdPrint(3, ("'ntStatus from IoCallDriver = 0x%x\n", ntStatus));
status = STATUS_SUCCESS; if (ntStatus == STATUS_PENDING) { status = KeWaitForSingleObject( &event, Suspended, KernelMode, FALSE, NULL); ntStatus = ioStatus.Status; } else { ioStatus.Status = ntStatus; }
#ifdef DEADMAN_TIMER
// the completion routine should have canceled the timer
// so we should never find it in the queue
//
// remove our timeoutDPC from the queue
//
if (haveTimer) { USBD_ASSERT(KeCancelTimer(&usbdTimeoutContext->TimeoutTimer) == FALSE); KeWaitForSingleObject(&usbdTimeoutContext->Event, Suspended, KernelMode, FALSE, NULL); RETHEAP(usbdTimeoutContext); } #endif
// NOTE:
// mapping is handled by completion routine
// called by HCD
USBD_KdPrint(3, ("'urb status = 0x%x ntStatus = 0x%x\n", Urb->UrbHeader.Status, ntStatus));
return ntStatus; }
NTSTATUS USBD_SendCommand( IN PUSBD_DEVICE_DATA DeviceData, IN PDEVICE_OBJECT DeviceObject, IN USHORT RequestCode, IN USHORT WValue, IN USHORT WIndex, IN USHORT WLength, IN PVOID Buffer, IN ULONG BufferLength, OUT PULONG BytesReturned, OUT USBD_STATUS *UsbStatus ) /*++
Routine Description:
Send a standard USB command on the default pipe.
Arguments:
DeviceData - ptr to USBD device structure the command will be sent to
DeviceObject -
RequestCode -
WValue - wValue for setup packet
WIndex - wIndex for setup packet
WLength - wLength for setup packet
Buffer - Input/Output Buffer for command BufferLength - Length of Input/Output buffer.
BytesReturned - pointer to ulong to copy number of bytes returned (optional)
UsbStatus - USBD status code returned in the URB.
Return Value:
STATUS_SUCCESS if successful, STATUS_UNSUCCESSFUL otherwise
--*/ { NTSTATUS ntStatus; PHCD_URB urb = NULL; PUSBD_PIPE defaultPipe; PUSB_STANDARD_SETUP_PACKET setupPacket; PUSBD_EXTENSION deviceExtension;
PAGED_CODE(); USBD_KdPrint(3, ("'enter USBD_SendCommand\n")); ASSERT_DEVICE(DeviceData);
if (!DeviceData || DeviceData->Sig != SIG_DEVICE) { USBD_Warning(NULL, "Bad DeviceData passed to USBD_SendCommand, fail!\n", FALSE);
return STATUS_INVALID_PARAMETER; }
defaultPipe = &(DeviceData->DefaultPipe);
deviceExtension = GET_DEVICE_EXTENSION(DeviceObject);
if (deviceExtension->DeviceHackFlags & USBD_DEVHACK_SLOW_ENUMERATION) {
//
// if noncomplience switch is on in the
// registry we'll pause here to give the
// device a chance to respond.
//
LARGE_INTEGER deltaTime; deltaTime.QuadPart = 100 * -10000; (VOID) KeDelayExecutionThread(KernelMode, FALSE, &deltaTime); }
urb = GETHEAP(NonPagedPool, sizeof(struct _URB_CONTROL_TRANSFER));
if (!urb) { ntStatus = STATUS_INSUFFICIENT_RESOURCES; } else {
urb->UrbHeader.Length = sizeof(struct _URB_CONTROL_TRANSFER);
urb->UrbHeader.Function = URB_FUNCTION_CONTROL_TRANSFER;
setupPacket = (PUSB_STANDARD_SETUP_PACKET) urb->HcdUrbCommonTransfer.Extension.u.SetupPacket; setupPacket->RequestCode = RequestCode; setupPacket->wValue = WValue; setupPacket->wIndex = WIndex; setupPacket->wLength = WLength;
if (!USBD_ValidatePipe(defaultPipe) || !defaultPipe->HcdEndpoint) {
USBD_Warning(DeviceData, "Bad DefaultPipe or Endpoint in USBD_SendCommand, fail!\n", FALSE);
ntStatus = STATUS_INVALID_PARAMETER; goto USBD_SendCommand_done; }
urb->HcdUrbCommonTransfer.hca.HcdEndpoint = defaultPipe->HcdEndpoint; urb->HcdUrbCommonTransfer.TransferFlags = USBD_SHORT_TRANSFER_OK;
// USBD is responsible for setting the transfer direction
//
// TRANSFER direction is implied in the command
if (RequestCode & USB_DEVICE_TO_HOST) USBD_SET_TRANSFER_DIRECTION_IN(urb->HcdUrbCommonTransfer.TransferFlags); else USBD_SET_TRANSFER_DIRECTION_OUT(urb->HcdUrbCommonTransfer.TransferFlags);
urb->HcdUrbCommonTransfer.TransferBufferLength = BufferLength; urb->HcdUrbCommonTransfer.TransferBuffer = Buffer; urb->HcdUrbCommonTransfer.TransferBufferMDL = NULL; urb->HcdUrbCommonTransfer.UrbLink = NULL;
USBD_KdPrint(3, ("'SendCommand cmd = 0x%x buffer = 0x%x length = 0x%x direction = 0x%x\n", setupPacket->RequestCode, urb->HcdUrbCommonTransfer.TransferBuffer, urb->HcdUrbCommonTransfer.TransferBufferLength, urb->HcdUrbCommonTransfer.TransferFlags ));
ntStatus = USBD_SubmitSynchronousURB((PURB)urb, DeviceObject, DeviceData);
if (BytesReturned) { *BytesReturned = urb->HcdUrbCommonTransfer.TransferBufferLength; }
if (UsbStatus) { *UsbStatus = urb->HcdUrbCommonTransfer.Status; }
USBD_SendCommand_done:
// free the transfer URB
RETHEAP(urb);
}
USBD_KdPrint(3, ("'exit USBD_SendCommand 0x%x\n", ntStatus));
return ntStatus; }
NTSTATUS USBD_OpenEndpoint( IN PUSBD_DEVICE_DATA DeviceData, IN PDEVICE_OBJECT DeviceObject, IN OUT PUSBD_PIPE PipeHandle, OUT USBD_STATUS *UsbStatus, BOOLEAN IsDefaultPipe ) /*++
Routine Description:
open an endpoint on a USB device.
Arguments:
DeviceData - data describes the device this endpoint is on.
DeviceObject - USBD device object.
PipeHandle - USBD PipeHandle to associate with the endpoint. on input MaxTransferSize initialize to the largest transfer that will be sent on this endpoint,
Return Value:
NT status code.
--*/ { NTSTATUS ntStatus; PHCD_URB urb; PUSBD_EXTENSION deviceExtension; extern UCHAR ForceDoubleBuffer; extern UCHAR ForceFastIso;
PAGED_CODE(); USBD_KdPrint(3, ("'enter USBD_OpenEndpoint\n"));
deviceExtension = GET_DEVICE_EXTENSION(DeviceObject);
ASSERT_DEVICE(DeviceData); USBD_ASSERT(PIPE_CLOSED(PipeHandle) == TRUE);
urb = GETHEAP(NonPagedPool, sizeof(struct _URB_HCD_OPEN_ENDPOINT));
if (!urb) { ntStatus = STATUS_INSUFFICIENT_RESOURCES; } else {
urb->UrbHeader.Length = sizeof(struct _URB_HCD_OPEN_ENDPOINT); urb->UrbHeader.Function = URB_FUNCTION_HCD_OPEN_ENDPOINT;
urb->HcdUrbOpenEndpoint.EndpointDescriptor = &PipeHandle->EndpointDescriptor; urb->HcdUrbOpenEndpoint.DeviceAddress = DeviceData->DeviceAddress; urb->HcdUrbOpenEndpoint.HcdEndpointFlags = 0;
if (DeviceData->LowSpeed == TRUE) { urb->HcdUrbOpenEndpoint.HcdEndpointFlags |= USBD_EP_FLAG_LOWSPEED; }
// default pipe and iso pipes never halt
if (IsDefaultPipe || (PipeHandle->EndpointDescriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_ISOCHRONOUS) { urb->HcdUrbOpenEndpoint.HcdEndpointFlags |= USBD_EP_FLAG_NEVERHALT; }
if (ForceDoubleBuffer && ((PipeHandle->EndpointDescriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_BULK)) { PipeHandle->UsbdPipeFlags |= USBD_PF_DOUBLE_BUFFER; USBD_KdPrint(1, (">>Forcing Double Buffer -- Bulk <<\n")); }
if (ForceFastIso && ((PipeHandle->EndpointDescriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_ISOCHRONOUS)) { PipeHandle->UsbdPipeFlags |= USBD_PF_ENABLE_RT_THREAD_ACCESS; USBD_KdPrint(1, (">>Forcing Fast Iso <<\n")); }
urb->HcdUrbOpenEndpoint.MaxTransferSize = PipeHandle->MaxTransferSize;
// check client option flags
if (PipeHandle->UsbdPipeFlags & USBD_PF_DOUBLE_BUFFER) { USBD_KdPrint(1, (">>Setting Double Buffer Flag<<\n")); urb->HcdUrbOpenEndpoint.HcdEndpointFlags |= USBD_EP_FLAG_DOUBLE_BUFFER; }
if (PipeHandle->UsbdPipeFlags & USBD_PF_ENABLE_RT_THREAD_ACCESS) { USBD_KdPrint(1, (">>Setting Fast ISO Flag<<\n")); urb->HcdUrbOpenEndpoint.HcdEndpointFlags |= USBD_EP_FLAG_FAST_ISO; }
if (PipeHandle->UsbdPipeFlags & USBD_PF_MAP_ADD_TRANSFERS) { USBD_KdPrint(1, (">>Setting Map Add Flag<<\n")); urb->HcdUrbOpenEndpoint.HcdEndpointFlags |= USBD_EP_FLAG_MAP_ADD_IO; } //
// Serialize Open Endpoint requests
//
ntStatus = USBD_SubmitSynchronousURB((PURB) urb, DeviceObject, DeviceData);
if (NT_SUCCESS(ntStatus)) { PipeHandle->HcdEndpoint = urb->HcdUrbOpenEndpoint.HcdEndpoint; PipeHandle->ScheduleOffset = urb->HcdUrbOpenEndpoint.ScheduleOffset; PipeHandle->Sig = SIG_PIPE; }
if (UsbStatus) { *UsbStatus = urb->UrbHeader.Status; }
RETHEAP(urb); }
USBD_KdPrint(3, ("'exit USBD_OpenEndpoint 0x%x\n", ntStatus));
return ntStatus; }
NTSTATUS USBD_CloseEndpoint( IN PUSBD_DEVICE_DATA DeviceData, IN PDEVICE_OBJECT DeviceObject, IN PUSBD_PIPE PipeHandle, IN OUT USBD_STATUS *UsbStatus ) /*++
Routine Description:
Close an Endpoint
Arguments:
DeviceData - ptr to USBD device data structure.
DeviceObject - USBD device object.
PipeHandle - USBD pipe handle associated with the endpoint.
Return Value:
STATUS_SUCCESS if successful, STATUS_UNSUCCESSFUL otherwise
--*/ { NTSTATUS ntStatus; PHCD_URB urb; PUSBD_EXTENSION deviceExtension;
PAGED_CODE(); USBD_KdPrint(3, ("'enter USBD_CloseEndpoint\n")); ASSERT_DEVICE(DeviceData);
deviceExtension = GET_DEVICE_EXTENSION(DeviceObject);
urb = GETHEAP(NonPagedPool, sizeof(struct _URB_HCD_CLOSE_ENDPOINT));
if (!urb) { ntStatus = STATUS_INSUFFICIENT_RESOURCES; } else {
urb->UrbHeader.Length = sizeof(struct _URB_HCD_CLOSE_ENDPOINT); urb->UrbHeader.Function = URB_FUNCTION_HCD_CLOSE_ENDPOINT;
urb->HcdUrbCloseEndpoint.HcdEndpoint = PipeHandle->HcdEndpoint;
//
// Serialize Close Endpoint requests
//
ntStatus = USBD_SubmitSynchronousURB((PURB) urb, DeviceObject, DeviceData);
if (UsbStatus) { *UsbStatus = urb->UrbHeader.Status; }
RETHEAP(urb); }
USBD_KdPrint(3, ("'exit USBD_CloseEndpoint 0x%x\n", ntStatus));
return ntStatus; }
VOID USBD_FreeUsbAddress( IN PDEVICE_OBJECT DeviceObject, IN USHORT DeviceAddress ) /*++
Routine Description:
Arguments:
Return Value:
Valid USB address (1..127) to use for this device, returns 0 if no device address available.
--*/ { PUSBD_EXTENSION deviceExtension; USHORT address = 0, i, j; ULONG bit;
PAGED_CODE();
// we should never see a free to device address 0
USBD_ASSERT(DeviceAddress != 0); deviceExtension = GET_DEVICE_EXTENSION(DeviceObject);
for (j=0; j<4; j++) { bit = 1; for (i=0; i<32; i++) { address = (USHORT)(j*32+i); if (address == DeviceAddress) { deviceExtension->AddressList[j] &= ~bit; goto USBD_FreeUsbAddress_Done; } bit = bit<<1; } }
USBD_FreeUsbAddress_Done:
USBD_KdPrint(3, ("'USBD free Address %d\n", address));
}
USHORT USBD_AllocateUsbAddress( IN PDEVICE_OBJECT DeviceObject ) /*++
Routine Description:
Arguments:
Return Value:
Valid USB address (1..127) to use for this device, returns 0 if no device address available.
--*/ { PUSBD_EXTENSION deviceExtension; USHORT address = 0, i, j; ULONG bit;
PAGED_CODE(); deviceExtension = GET_DEVICE_EXTENSION(DeviceObject);
for (j=0; j<4; j++) { bit = 1; for (i=0; i<32; i++) {
if (!(deviceExtension->AddressList[j] & bit)) { deviceExtension->AddressList[j] |= bit; address = (USHORT)(j*32+i); goto USBD_AllocateUsbAddress_Done; } bit = bit<<1; } }
USBD_AllocateUsbAddress_Done:
USBD_KdPrint(3, ("'USBD assigning Address %d\n", address));
return address; }
NTSTATUS USBD_GetEndpointState( IN PUSBD_DEVICE_DATA DeviceData, IN PDEVICE_OBJECT DeviceObject, IN PUSBD_PIPE PipeHandle, OUT USBD_STATUS *UsbStatus, OUT PULONG EndpointState ) /*++
Routine Description:
open an endpoint on a USB device.
Arguments:
DeviceData - data describes the device this endpoint is on.
DeviceObject - USBD device object.
PipeHandle - USBD PipeHandle to associate with the endpoint.
Return Value:
NT status code.
--*/ { NTSTATUS ntStatus; PHCD_URB urb; PUSBD_EXTENSION deviceExtension;
PAGED_CODE(); USBD_KdPrint(3, ("'enter USBD_GetEndpointState\n")); ASSERT_DEVICE(DeviceData);
deviceExtension = GET_DEVICE_EXTENSION(DeviceObject);
USBD_ASSERT(PIPE_CLOSED(PipeHandle) == FALSE);
urb = GETHEAP(NonPagedPool, sizeof(struct _URB_HCD_OPEN_ENDPOINT));
if (!urb) { ntStatus = STATUS_INSUFFICIENT_RESOURCES; } else {
urb->UrbHeader.Length = sizeof(struct _URB_HCD_OPEN_ENDPOINT); urb->UrbHeader.Function = URB_FUNCTION_HCD_GET_ENDPOINT_STATE;
urb->HcdUrbEndpointState.HcdEndpoint = PipeHandle->HcdEndpoint; urb->HcdUrbEndpointState.HcdEndpointState = 0;
// Serialize Open Endpoint requests
//
ntStatus = USBD_SubmitSynchronousURB((PURB) urb, DeviceObject, DeviceData);
if (UsbStatus) { *UsbStatus = urb->UrbHeader.Status; } *EndpointState = urb->HcdUrbEndpointState.HcdEndpointState; RETHEAP(urb); }
USBD_KdPrint(3, ("'exit USBD_GetEndpointState 0x%x\n", ntStatus));
return ntStatus; }
#endif // USBD_DRIVER
|