windows-nt/Source/XPSP1/NT/drivers/wdm/usb/driver/wceusbsh/int.c
2020-09-26 16:20:57 +08:00

693 lines
20 KiB
C

/* ++
Copyright (c) 1999-2000 Microsoft Corporation
Module Name :
int.c
Abstract:
Interrupt Pipe handler
Based on read.c
Author:
Jeff Midkiff (jeffmi) 08-20-99
--*/
#include "wceusbsh.h"
VOID
RestartInterruptWorkItem(
IN PWCE_WORK_ITEM PWorkItem
);
NTSTATUS
UsbInterruptComplete(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
);
//
// called with control lock held
//
#define START_ANOTHER_INTERRUPT( _PDevExt, _AcquireLock ) \
( (IRP_STATE_COMPLETE == _PDevExt->IntState) && \
CanAcceptIoRequests(_PDevExt->DeviceObject, _AcquireLock, TRUE) \
)
//
// This function allocates a single Irp & Urb to be continously
// submitted to USBD for INT pipe notifications.
// It is called from StartDevice.
// The Irp & Urb are finally freed in StopDevice.
//
NTSTATUS
AllocUsbInterrupt(
IN PDEVICE_EXTENSION PDevExt
)
{
NTSTATUS status = STATUS_SUCCESS;
PIRP pIrp;
PURB pUrb;
PAGED_CODE();
DbgDump(DBG_INT, (">AllocUsbInterrupt(%p)\n", PDevExt->DeviceObject));
ASSERT( PDevExt );
if ( !PDevExt->IntPipe.hPipe ) {
status = STATUS_UNSUCCESSFUL;
DbgDump(DBG_ERR, ("AllocUsbInterrupt: 0x%x\n", status ));
} else {
ASSERT( NULL == PDevExt->IntIrp );
pIrp = IoAllocateIrp( (CCHAR)(PDevExt->NextDevice->StackSize + 1), FALSE);
if (pIrp) {
//
// fixup irp so we can pass to ourself,
// and to USBD
//
FIXUP_RAW_IRP( pIrp, PDevExt->DeviceObject );
//
// alloc the int pipe's Urb
//
pUrb = ExAllocateFromNPagedLookasideList( &PDevExt->BulkTransferUrbPool );
if (pUrb) {
// save these to be freed when not needed
SetPVoidLocked( &PDevExt->IntIrp,
pIrp,
&PDevExt->ControlLock);
SetPVoidLocked( &PDevExt->IntUrb,
pUrb,
&PDevExt->ControlLock);
DbgDump(DBG_INT, ("IntIrp: %p\t IntUrb: %p\n", PDevExt->IntIrp, PDevExt->IntUrb));
InterlockedExchange(&PDevExt->IntState, IRP_STATE_COMPLETE);
KeInitializeEvent( &PDevExt->IntCancelEvent,
SynchronizationEvent,
FALSE);
} else {
//
// this is a fatal err since we can't post int requests to USBD
//
status = STATUS_INSUFFICIENT_RESOURCES;
DbgDump(DBG_ERR, ("AllocUsbInterrupt: 0x%x\n", status ));
TEST_TRAP();
}
} else {
//
// this is a fatal err since we can't post int requests to USBD
//
status = STATUS_INSUFFICIENT_RESOURCES;
DbgDump(DBG_ERR, ("AllocUsbInterrupt: 0x%x\n", status ));
TEST_TRAP();
}
}
DbgDump(DBG_INT, ("<AllocUsbInterrupt 0x%x\n", status ));
return status;
}
//
// This routine takes the device's current IntIrp and submits it to USBD.
// When the Irp is completed by USBD our completion routine fires.
//
// Return: successful return value is STATUS_SUCCESS, or
// STATUS_PENDING - which means the I/O is pending in the USB stack.
//
NTSTATUS
UsbInterruptRead(
IN PDEVICE_EXTENSION PDevExt
)
{
PIO_STACK_LOCATION pNextStack;
NTSTATUS status = STATUS_SUCCESS;
KIRQL irql;
DbgDump(DBG_INT, (">UsbInterruptRead(%p)\n", PDevExt->DeviceObject));
do {
//
// check our USB INT state
//
KeAcquireSpinLock(&PDevExt->ControlLock, &irql);
if ( !PDevExt->IntPipe.hPipe || !PDevExt->IntIrp ||
!PDevExt->IntUrb || !PDevExt->IntBuff ) {
status = STATUS_UNSUCCESSFUL;
DbgDump(DBG_ERR, ("UsbInterruptRead: 0x%x\n", status ));
KeReleaseSpinLock(&PDevExt->ControlLock, irql);
break;
}
if ( !CanAcceptIoRequests(PDevExt->DeviceObject, FALSE, TRUE) ) {
status = STATUS_DELETE_PENDING;
DbgDump(DBG_ERR, ("UsbInterruptRead: 0x%x\n", status ));
KeReleaseSpinLock(&PDevExt->ControlLock, irql);
break;
}
#if DBG
if (IRP_STATE_CANCELLED == PDevExt->IntState)
TEST_TRAP();
#endif
//
// we post our INT irp to USB if it has been completed (not cancelled),
// and the device is accepting requests
//
if ( START_ANOTHER_INTERRUPT( PDevExt, FALSE ) ) {
status = AcquireRemoveLock(&PDevExt->RemoveLock, PDevExt->IntIrp);
if ( !NT_SUCCESS(status) ) {
DbgDump(DBG_ERR, ("UsbInterruptRead: 0x%x\n", status ));
KeReleaseSpinLock(&PDevExt->ControlLock, irql);
break;
}
ASSERT( IRP_STATE_COMPLETE == PDevExt->IntState);
InterlockedExchange(&PDevExt->IntState, IRP_STATE_PENDING);
KeClearEvent( &PDevExt->PendingIntEvent );
KeClearEvent( &PDevExt->IntCancelEvent );
RecycleIrp( PDevExt->DeviceObject, PDevExt->IntIrp );
UsbBuildTransferUrb( PDevExt->IntUrb,
PDevExt->IntBuff,
PDevExt->IntPipe.MaxPacketSize,
PDevExt->IntPipe.hPipe,
TRUE );
//
// set Irp up for a submit Urb IOCTL
//
IoCopyCurrentIrpStackLocationToNext(PDevExt->IntIrp);
pNextStack = IoGetNextIrpStackLocation(PDevExt->IntIrp);
pNextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
pNextStack->Parameters.Others.Argument1 = PDevExt->IntUrb;
pNextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
//
// completion routine will take care of updating buffer
//
IoSetCompletionRoutine( PDevExt->IntIrp,
UsbInterruptComplete,
NULL, //PDevExt, // Context
TRUE, TRUE, TRUE);
InterlockedIncrement(&PDevExt->PendingIntCount);
KeReleaseSpinLock( &PDevExt->ControlLock, irql );
status = IoCallDriver(PDevExt->NextDevice, PDevExt->IntIrp );
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, ("UsbInterruptRead: 0x%x\n", status));
}
} else {
//
// we did not post an INT, but this is not an error condition
//
status = STATUS_SUCCESS;
DbgDump(DBG_INT, ("!UsbInterruptRead RE: 0x%x\n", PDevExt->IntState ));
KeReleaseSpinLock(&PDevExt->ControlLock, irql);
}
} while (0);
DbgDump(DBG_INT, ("<UsbInterruptRead 0x%x\n", status ));
return status;
}
/*
This completion routine fires when USBD completes our IntIrp
Note: we allocated the Irp, and recycle it.
Always return STATUS_MORE_PROCESSING_REQUIRED to retain the Irp.
This routine runs at DPC_LEVEL.
Interrupt Endpoint:
This endpoint will be used to indicate the availability of IN data,
as well as to reflect the state of the device serial control lines :
D15..D3 Reserved
D2 DSR state (1=Active, 0=Inactive)
D1 CTS state (1=Active, 0=Inactive)
D0 Data Available - (1=Host should read IN endpoint, 0=No data currently available)
*/
NTSTATUS
UsbInterruptComplete(
IN PDEVICE_OBJECT PDevObj,
IN PIRP Irp,
IN PVOID Context)
{
PDEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
ULONG count;
KIRQL irql;
PIRP pCurrentMaskIrp = NULL;
NTSTATUS irpStatus;
USBD_STATUS urbStatus;
USHORT usNewMSR;
USHORT usOldMSR;
USHORT usDeltaMSR;
NTSTATUS workStatus;
BOOLEAN bStartRead = FALSE;
UNREFERENCED_PARAMETER( Irp );
UNREFERENCED_PARAMETER( Context );
ASSERT( pDevExt->IntIrp == Irp );
ASSERT( pDevExt->IntBuff );
DbgDump(DBG_INT, (">UsbInterruptComplete(%p)\n", PDevObj));
KeAcquireSpinLock(&pDevExt->ControlLock, &irql);
//
// Our INT 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 Cancel routine set it,
// in which case USB can complete the irp normally or as cancelled
// depending on where it was in processing. If the state is cancelled
// then do NOT set to complete, else the Irp will
// go back down to USB and you are hosed.
//
ASSERT( (IRP_STATE_PENDING == pDevExt->IntState)
|| (IRP_STATE_CANCELLED== pDevExt->IntState) );
if (IRP_STATE_PENDING == pDevExt->IntState) {
InterlockedExchange(&pDevExt->IntState, IRP_STATE_COMPLETE);
}
//
// signal everyone if this is the last IRP
//
if ( 0 == InterlockedDecrement(&pDevExt->PendingIntCount) ) {
// DbgDump(DBG_INT, ("PendingIntCount: 0\n"));
// when we drop back to passive level they will get signalled
KeSetEvent(&pDevExt->PendingIntEvent, IO_SERIAL_INCREMENT, FALSE);
}
//
// get the completion info
//
count = pDevExt->IntUrb->UrbBulkOrInterruptTransfer.TransferBufferLength;
irpStatus = pDevExt->IntIrp->IoStatus.Status;
DbgDump(DBG_INT, ("Irp->IoStatus.Status 0x%x\n", irpStatus));
urbStatus = pDevExt->IntUrb->UrbHeader.Status;
DbgDump(DBG_INT, ("Urb->UrbHeader.Status 0x%x\n", urbStatus ));
switch (irpStatus) {
case STATUS_SUCCESS: {
ASSERT( USBD_STATUS_SUCCESS == urbStatus );
//
// clear pipe error count
//
InterlockedExchange( &pDevExt->IntDeviceErrors, 0);
#if DBG
if (DebugLevel & DBG_DUMP_INT) {
ULONG i;
DbgDump(DBG_INT, ("IntBuff[%d]: ", count ));
for (i=0; i < count; i++) {
KdPrint( ("%02x ", pDevExt->IntBuff[i] ) );
}
KdPrint(("\n"));
}
#endif
//
// Get Data Ready
//
// D0 - Data Available (1=Host should read IN endpoint, 0=No data currently available)
//
if ( pDevExt->IntBuff[0] & USB_COMM_DATA_READY_MASK ) {
DbgDump(DBG_INT, ("Data Ready\n"));
bStartRead = TRUE;
// Note: we may be prematurely setting this bit since we have not
// confirmed data reception, but need to get the user's read started.
// Perhaps only set if not using the ring-buffer, since buffered reads are not bound to app's reads.
pDevExt->SerialPort.HistoryMask |= SERIAL_EV_RXCHAR;
}
//
// Get Modem Status Register
//
// D1 CTS state (1=Active, 0=Inactive)
// D2 DSR state (1=Active, 0=Inactive)
//
usOldMSR = pDevExt->SerialPort.ModemStatus;
usNewMSR = pDevExt->IntBuff[0] & USB_COMM_MODEM_STATUS_MASK;
DbgDump(DBG_INT, ("USB_COMM State: 0x%x\n", usNewMSR));
if (usNewMSR & USB_COMM_CTS) {
pDevExt->SerialPort.ModemStatus |= SERIAL_MSR_CTS;
} else {
pDevExt->SerialPort.ModemStatus &= ~SERIAL_MSR_CTS;
}
if (usNewMSR & USB_COMM_DSR) {
pDevExt->SerialPort.ModemStatus |= SERIAL_MSR_DSR | SERIAL_MSR_DCD;
} else {
pDevExt->SerialPort.ModemStatus &= ~SERIAL_MSR_DSR & ~SERIAL_MSR_DCD;
}
// see what has changed in the status register
usDeltaMSR = usOldMSR ^ pDevExt->SerialPort.ModemStatus;
if (/*(pDevExt->SerialPort.RS232Lines & SERIAL_RTS_STATE) && */
(usDeltaMSR & SERIAL_MSR_CTS)) {
pDevExt->SerialPort.HistoryMask |= SERIAL_EV_CTS;
pDevExt->SerialPort.ModemStatus |= SERIAL_MSR_DCTS;
}
if (/*(pDevExt->SerialPort.RS232Lines & SERIAL_DTR_STATE) && */
(usDeltaMSR & SERIAL_MSR_DSR)) {
pDevExt->SerialPort.HistoryMask |= SERIAL_EV_DSR | SERIAL_EV_RLSD;
pDevExt->SerialPort.ModemStatus |= SERIAL_MSR_DDSR | SERIAL_MSR_DDCD;
}
DbgDump(DBG_INT, ("SerialPort.MSR: 0x%x\n", pDevExt->SerialPort.ModemStatus));
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
//
// signal serial events @ DISPATCH_LEVEL before starting our UsbRead,
// since we run at higher IRQL than apps.
//
ProcessSerialWaits( pDevExt );
if ( bStartRead ) {
//
// Get the data.
// We do set a timeout on 1st read, in case the INT was illegit.
// Note: we start this read @ DISPATCH_LEVEL.
//
UsbRead( pDevExt,
TRUE );
}
//
// Queue a passive work item to sync execution of the INT pipe and the IN pipe
// and then start the next INT packet.
//
workStatus = QueueWorkItem( PDevObj,
RestartInterruptWorkItem,
NULL,
0 );
}
break;
case STATUS_CANCELLED: {
DbgDump(DBG_INT|DBG_IRP, ("Int: STATUS_CANCELLED\n"));
// signal anyone who cancelled this or is waiting for it to stop
//
KeSetEvent(&pDevExt->IntCancelEvent, IO_SERIAL_INCREMENT, FALSE);
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
}
break;
case STATUS_DEVICE_DATA_ERROR: {
//
// generic device error set by USBD.
//
DbgDump(DBG_ERR, ("IntPipe STATUS_DEVICE_DATA_ERROR: 0x%x\n", urbStatus ));
//
// bump pipe error count
//
InterlockedIncrement( &pDevExt->IntDeviceErrors);
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
//
// is the endpoint is stalled?
//
if ( USBD_HALTED(pDevExt->IntUrb->UrbHeader.Status) ) {
//
// queue a reset request,
// which also starts another INT
//
workStatus = QueueWorkItem( PDevObj,
UsbResetOrAbortPipeWorkItem,
(PVOID)((LONG_PTR)urbStatus),
WORK_ITEM_RESET_INT_PIPE );
} else {
//
// queue a passive work item to start the next INT packet.
//
workStatus = QueueWorkItem( PDevObj,
RestartInterruptWorkItem,
NULL,
0 );
}
}
break;
default:
DbgDump(DBG_WRN|DBG_INT, ("Unhandled INT Pipe status: 0x%x 0x%x\n", irpStatus, urbStatus ));
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
break;
}
ReleaseRemoveLock(&pDevExt->RemoveLock, pDevExt->IntIrp);
DbgDump(DBG_INT, ("<UsbInterruptComplete\n"));
return STATUS_MORE_PROCESSING_REQUIRED;
}
//
// This routine requests USB to cancel our INT Irp.
// It must be called at passive level.
// Note: it is the responsibility of the caller to
// reset the IntState to IRP_STATE_COMPLETE and restart USB Ints
// when this routine completes. Else, no more Interrupts will get posted.
//
NTSTATUS
CancelUsbInterruptIrp(
IN PDEVICE_OBJECT PDevObj
)
{
PDEVICE_EXTENSION pDevExt = PDevObj->DeviceExtension;
NTSTATUS status = STATUS_SUCCESS;
NTSTATUS wait_status;
KIRQL irql;
DbgDump(DBG_INT|DBG_IRP, (">CancelUsbInterruptIrp\n"));
KeAcquireSpinLock(&pDevExt->ControlLock, &irql);
if ( pDevExt->IntPipe.hPipe && pDevExt->IntIrp ) {
switch (pDevExt->IntState) {
//case IRP_STATE_START:
case IRP_STATE_PENDING:
{
//
// the Irp is pending somewhere down the USB stack...
//
PVOID Objects[2] = { &pDevExt->PendingIntEvent,
&pDevExt->IntCancelEvent };
//
// signal we need to cancel the Irp
//
pDevExt->IntState = IRP_STATE_CANCELLED;
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
if ( !IoCancelIrp( pDevExt->IntIrp ) ) {
//
// This means USB has the IntIrp in a non-canceable state.
// We still need to wait for either the pending INT event, or the cancel event.
//
DbgDump(DBG_INT|DBG_IRP, ("Irp (%p) was not cancelled\n", pDevExt->IntIrp ));
// TEST_TRAP();
}
DbgDump(DBG_INT|DBG_IRP, ("Waiting for pending IntIrp (%p) to cancel...\n", pDevExt->IntIrp ));
PAGED_CODE();
wait_status = KeWaitForMultipleObjects(
2,
Objects,
WaitAny,
Executive,
KernelMode,
FALSE,
NULL,
NULL );
DbgDump(DBG_INT|DBG_IRP, ("...IntIrp (%p) signalled by: %d\n", pDevExt->IntIrp, wait_status ));
//
// At this point we have the Irp back from USB
//
}
break;
case IRP_STATE_COMPLETE:
case IRP_STATE_CANCELLED:
pDevExt->IntState = IRP_STATE_CANCELLED;
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
break;
default:
DbgDump(DBG_ERR, ("CancelUsbInterruptIrp - Invalid IntState: 0x%x\n", pDevExt->IntState ));
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
break;
}
if ( (IRP_STATE_CANCELLED != pDevExt->IntState) ||
(0 != pDevExt->PendingIntCount) ) {
DbgDump(DBG_ERR, ("CancelUsbInterruptIrp error: IntState: 0x%x \tPendingIntCount: 0x%x\n", pDevExt->IntState, pDevExt->PendingIntCount ));
TEST_TRAP();
}
} else {
status = STATUS_UNSUCCESSFUL;
DbgDump(DBG_ERR, ("No INT Irp\n" ));
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
// TEST_TRAP();
}
DbgDump(DBG_INT|DBG_IRP, ("<CancelUsbInterruptIrp\n"));
return status;
}
//
// Work item queued from interrupt completion
// to sync execution of the INT pipe and the IN pipe
// and then start another USB INT read if there is not one already in progress
//
VOID
RestartInterruptWorkItem(
IN PWCE_WORK_ITEM PWorkItem
)
{
PDEVICE_OBJECT pDevObj = PWorkItem->DeviceObject;
PDEVICE_EXTENSION pDevExt = pDevObj->DeviceExtension;
NTSTATUS status = STATUS_DELETE_PENDING;
NTSTATUS wait_status;
KIRQL irql;
DbgDump(DBG_INT|DBG_WORK_ITEMS, (">RestartInterruptWorkItem(%p)\n", pDevObj ));
KeAcquireSpinLock( &pDevExt->ControlLock, &irql );
//
// Is the READ Irp is pending somewhere in the USB stack?
//
if ( IRP_STATE_PENDING == pDevExt->UsbReadState ) {
//
// Then we need to sync with the Usb Read Completion routine.
//
#define WAIT_REASONS 2
PVOID Objects[WAIT_REASONS] = { &pDevExt->UsbReadCancelEvent,
&pDevExt->PendingDataInEvent };
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
DbgDump(DBG_INT, ("INT pipe waiting for pending UsbReadIrp (%p) to finish...\n", pDevExt->UsbReadIrp ));
PAGED_CODE();
wait_status = KeWaitForMultipleObjects( WAIT_REASONS,
Objects,
WaitAny,
Executive,
KernelMode,
FALSE,
NULL,
NULL );
DbgDump(DBG_INT, ("...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 UsbReadIrp back from USB
//
} else {
KeReleaseSpinLock(&pDevExt->ControlLock, irql);
}
// start another INT read
if ( START_ANOTHER_INTERRUPT(pDevExt, TRUE) ) {
status = UsbInterruptRead( pDevExt );
}
DequeueWorkItem( pDevObj, PWorkItem );
DbgDump(DBG_INT|DBG_WORK_ITEMS, ("<RestartInterruptWorkItem 0x%x\n", status ));
PAGED_CODE(); // we must exit at passive level
return;
}