windows-nt/Source/XPSP1/NT/drivers/wdm/usb/hcd/uhcd/int.c

815 lines
22 KiB
C
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1995,1996 Microsoft Corporation
:ts=4
Module Name:
int.c
Abstract:
This module contains the interrupt routine, DPC routines and routines
that synchronize with the interrupt routine.
Environment:
kernel mode only
Notes:
Revision History:
11-01-95 : created
--*/
#include "wdm.h"
#include "stdarg.h"
#include "stdio.h"
#include "usbdi.h"
#include "hcdi.h"
#include "uhcd.h"
#ifdef DEBUG_LOG
ULONG TrapOn = 0;
#endif
BOOLEAN
UHCD_InterruptService(
IN PKINTERRUPT Interrupt,
IN PVOID Context
)
/*++
Routine Description:
This is the interrupt service routine for the UHCD.
Arguments:
Interrupt - A pointer to the interrupt object for this interrupt.
Context - A pointer to the device object.
Return Value:
Returns TRUE if the interrupt was expected (and therefore processed);
otherwise, FALSE is returned.
--*/
{
PDEVICE_EXTENSION deviceExtension;
PDEVICE_OBJECT deviceObject;
USHORT status;
ULONG frameNumber;
BOOLEAN usbInt = FALSE;
UNREFERENCED_PARAMETER(Interrupt);
// UHCD_KdPrint((2, "'enter UHCD_InterruptService\n"));
deviceObject = (PDEVICE_OBJECT) Context;
deviceExtension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension;
// Ignore ints if we are not in D0.
if (deviceExtension->CurrentDevicePowerState != PowerDeviceD0) {
goto UHCD_InterruptService_Done;
}
status = READ_PORT_USHORT(STATUS_REG(deviceExtension));
if (status & (UHCD_STATUS_USBINT |
UHCD_STATUS_USBERR |
UHCD_STATUS_RESUME |
UHCD_STATUS_HCERR |
UHCD_STATUS_PCIERR
// BUGBUG we will ignore the halt bit by itself since
// the controller will never allow us to clear the status
/*| UHCD_STATUS_HCHALT*/)) {
usbInt = TRUE;
//clear the condition
WRITE_PORT_USHORT(STATUS_REG(deviceExtension), 0xff);
} else {
goto UHCD_InterruptService_Done;
}
if ((status & (UHCD_STATUS_HCHALT | UHCD_STATUS_USBINT)) ==
(UHCD_STATUS_HCHALT | UHCD_STATUS_USBINT)) {
ULONG frame;
USHORT cmd;
frameNumber = (ULONG) READ_PORT_USHORT(FRAME_LIST_CURRENT_INDEX_REG(deviceExtension)) & 0x3ff;
frame = UHCD_GetCurrentFrame(deviceObject);
//LOGENTRY(LOG_MISC, 'Hlt!', status, deviceExtension->FrameListVirtualAddress, frameNumber);
UHCD_KdPrint((2, "'UHCD Host Controller Halted %x, frame = 0x%x - 0x%x\n", status,
frame, frameNumber));
//
// nasty error in the host controller, we will want to debug.
//
UHCD_KdPrint((0, "'HC HALTED! attempting to recover\n"));
// attempt to recover
WRITE_PORT_USHORT(STATUS_REG(deviceExtension), 0xff);
cmd = READ_PORT_USHORT(COMMAND_REG(deviceExtension));
cmd |= UHCD_CMD_RUN;
WRITE_PORT_USHORT(COMMAND_REG(deviceExtension), cmd);
usbInt = TRUE;
return usbInt;
}
//
// Process the interrupt
//
if (status & UHCD_STATUS_RESUME) {
//
// system wakeup interrupt
//
#ifdef MAX_DEBUG
TEST_TRAP();
#endif
} else if (status & UHCD_STATUS_USBINT) {
//
// Interrupt because a TD completed
//
// UHCD_KdPrint((2, "'UHCD_InterruptService status = %x\n", status));
#ifdef DEBUG_LOG
// if (TrapOn > 0) {
// USHORT portStatus;
// // check for port disable
// portStatus = READ_PORT_USHORT(PORT1_REG(deviceExtension));
// if (portStatus & 0x0008) {
frameNumber = (ULONG) READ_PORT_USHORT(FRAME_LIST_CURRENT_INDEX_REG(deviceExtension)) & 0x3ff;
////
//// UHCD_KdPrint((2, "'Port Disabled frame = 0x%x\n", frameNumber));
////
// TRAP();
// }
// portStatus = READ_PORT_USHORT(PORT2_REG(deviceExtension));
// if (portStatus & 0x0008) {
// frameNumber = (ULONG) READ_PORT_USHORT(FRAME_LIST_CURRENT_INDEX_REG(deviceExtension)) & 0x3ff;
////
//// UHCD_KdPrint((2, "'Port Disabled frame = 0x%x\n", frameNumber));
////
// TRAP();
// }
// }
#endif
// This code maintains the 32-bit frame counter
frameNumber = (ULONG) READ_PORT_USHORT(FRAME_LIST_CURRENT_INDEX_REG(deviceExtension));
// did the sign bit change ?
if ((deviceExtension->LastFrame ^ frameNumber) & 0x0400) {
// Yes
deviceExtension->FrameHighPart += 0x0800 -
((frameNumber ^ deviceExtension->FrameHighPart) & 0x0400);
}
// remember the last frame number
deviceExtension->LastFrame = frameNumber;
//***
//
// start at the last frame processed
//
{
ULONG i, j;
ULONG currentFrame, highPart;
highPart = deviceExtension->FrameHighPart;
// get 11-bit frame number, high 17-bits are 0
//frameNumber = (ULONG) READ_PORT_USHORT(FRAME_LIST_CURRENT_INDEX_REG(deviceExtension));
currentFrame = ((frameNumber & 0x0bff) | highPart) +
((frameNumber ^ highPart) & 0x0400);
// UHCD_KdPrint((2, "'currentFrame = %x\n", currentFrame));
if (currentFrame-deviceExtension->LastFrameProcessed > 1024) {
deviceExtension->Stats.ScheduleOverrunCount++;
// we have a schedule overrun,
// this means it has been more that 1000 ms since our last
// interrupt, because of this the iso entries in the schedule
// are invalid -- we need to remove all of them and start over
// UHCD_KdPrint((2, "'schedule overrun currentFrame = %d, lastframe = %d \n",
// currentFrame, deviceExtension->LastFrameProcessed));
// TRAP();
// first remove all iso TDs from the list
for (j=0; j<FRAME_LIST_SIZE; j++) {
// put back the physical address that was there before we started
// adding isoch descriptors.
*( ((PULONG) (deviceExtension->FrameListVirtualAddress) + j) ) =
*( ((PULONG) (deviceExtension->FrameListCopyVirtualAddress) + j) );
#if DBG
*( deviceExtension->IsoList + j ) = 0;
#endif
}
deviceExtension->LastFrameProcessed = currentFrame;
}
{
#ifdef FAST_ISO
PUHCD_ENDPOINT endpoint;
endpoint = UHCD_GetLastFastIsoEndpoint(deviceObject);
#endif /* FAST_ISO */
for (i=deviceExtension->LastFrameProcessed+1; i<currentFrame; i++) {
// remove isoch TDs for frame i;
j = i % FRAME_LIST_SIZE;
// put back the physical address that was there before we started
// adding isoch descriptors.
*( ((PULONG) (deviceExtension->FrameListVirtualAddress) + j) ) =
*( ((PULONG) (deviceExtension->FrameListCopyVirtualAddress) + j) );
#if DBG
*( deviceExtension->IsoList + j ) = 0;
#endif
#ifdef FAST_ISO
if (endpoint) {
UHCD_CleanupFastIsoTD(deviceObject,
endpoint,
j,
TRUE);
}
#endif /* FAST_ISO */
}
}
deviceExtension->LastFrameProcessed = currentFrame-1;
}
//***
//
// Queue the DPC to complete any transfers
//
#ifdef PROFILE
{
LARGE_INTEGER time;
time = KeQueryPerformanceCounter(NULL);
UHCD_KdPrint((2, "'time.HighPart = %x time.LowPart %x\n", time.HighPart,
time.LowPart));
// LOGENTRY(LOG_MISC, 'Tim1", 0, sysTime.LowPart, sysTime.HighPart);
KeInsertQueueDpc(&deviceExtension->IsrDpc,
time.HighPart,
time.LowPart);
}
#else
KeInsertQueueDpc(&deviceExtension->IsrDpc,
NULL,
NULL);
#endif
// UHCD_KdPrint((2, "'exit UHCD_InterruptService\n"));
// USB interrupt
} else {
//
// USB Interrupt not recognized in ISR
//
UHCD_KdBreak((2, "'USB interrupt not recognized by ISR, status = %x\n", status));
// BUGBUG check why we are not handling it
}
UHCD_InterruptService_Done:
//#ifdef MAX_DEBUG
// if (!usbInt) {
// UHCD_KdPrint((2, "'Non USB interrupt, status = %x\n", status));
// }
//#endif
return usbInt;
}
VOID
UHCD_IsrDpc(
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 - supplies the DeviceObject.
SystemArgument1 - not used.
SystemArgument2 - not used.
Return Value:
None.
--*/
{
PDEVICE_EXTENSION deviceExtension;
PDEVICE_OBJECT deviceObject;
PLIST_ENTRY listEntry;
PUHCD_ENDPOINT endpoint;
LONG slot;
BOOLEAN process = TRUE;
KIRQL irql;
// STARTPROC("IDpc");
#ifdef PROFILE
{
//
// See how long it took for our DPC to get called
//
LARGE_INTEGER time, timeNow;
LONG delta;
time.HighPart = SystemArgument1;
time.LowPart = SystemArgument2;
timeNow = KeQueryPerformanceCounter(NULL);
delta = timeNow.QuadPart - time.QuadPart;
UHCD_KdPrint((2, "'time.HighPart = %x time.LowPart %x\n", time.HighPart,
time.LowPart));
UHCD_KdPrint((2, "'timeNow.HighPart = %x timeNow.LowPart %x\n", timeNow.HighPart,
timeNow.LowPart));
UHCD_KdPrint((2, "'delta %x %x ms\n", delta, delta/((0x1234de*50)/1000)));
if (delta > ((0x1234de*50)/1000)) {
UHCD_KdTrap(("DPC delayed > 50 ms\n"));
}
//LOGENTRY(LOG_MISC, 'Tim2", timeNow - time, time, timeNow);
}
#endif
// UHCD_KdPrint((2, "'enter UHCD_IsrDpc\n"));
deviceObject = (PDEVICE_OBJECT) DeferredContext;
deviceExtension = (PDEVICE_EXTENSION) deviceObject->DeviceExtension;
//
// Walk through the Endpoint list checking for
// any endpoints that have transfers that need completing
//
LOCK_ENDPOINT_LIST(deviceExtension, irql);
if (deviceExtension->EndpointListBusy) {
process = FALSE;
} else {
deviceExtension->EndpointListBusy = TRUE;
}
UNLOCK_ENDPOINT_LIST(deviceExtension, irql);
if (process) {
//
// we now have exclusive access to the endpoint list
//
listEntry = &deviceExtension->EndpointList;
if (!IsListEmpty(listEntry)) {
listEntry = deviceExtension->EndpointList.Flink;
}
LOGENTRY(LOG_MISC, 'EPl+', listEntry,
&deviceExtension->EndpointList, 0);
while (listEntry != &deviceExtension->EndpointList) {
ULONG cnt = 0;
endpoint = CONTAINING_RECORD(listEntry,
UHCD_ENDPOINT,
ListEntry);
ASSERT_ENDPOINT(endpoint);
// LOGENTRY(LOG_MISC, 'prEP', endpoint,
// &deviceExtension->EndpointList, listEntry);
listEntry = endpoint->ListEntry.Flink;
//
// Scan active transfer slots and process any transfers
// that have been programmed into the hardware.
//
for (slot=0; slot<endpoint->MaxRequests; slot++) {
//
// If we have a transfer in the slot call the completion
// handler.
//
if (endpoint->ActiveTransfers[slot]) {
cnt++;
LOGENTRY(LOG_MISC, 'epWk', endpoint, slot, endpoint->ActiveTransfers[slot]);
// only call the completer if no double buffer
// endpoints
if (!(endpoint->EndpointFlags & EPFLAG_DBL_BUFFER)) {
LOGENTRY(LOG_MISC, 'cPTR', endpoint,
slot, endpoint->ActiveTransfers[slot]);
UHCD_CompleteTransferDPC(deviceObject, endpoint, slot);
}
}
}
// if we had no transfers see if we can idle the endpoint
if (cnt) {
UHCD_EndpointWakeup(deviceObject, endpoint);
} else {
UHCD_EndpointIdle(deviceObject, endpoint);
}
//
// For DBL_BUFFER transfers, we don't do the active abort
// until we call the worker code. The worker code will also
// clear the flag for us. So, if it is a DBL_BUFFER, don't
// clear the flag yet.
//
if (!(endpoint->EndpointFlags & EPFLAG_DBL_BUFFER)) {
//
// safe to clear the ABORT_ACTIVE_TRANSFERS flag
// this will allow a reset_endpoint to succeed.
// LOGENTRY(LOG_MISC, 'clrA', endpoint, 0, 0);
//
CLR_EPFLAG(endpoint, EPFLAG_ABORT_ACTIVE_TRANSFERS);
}
// does this endpoint need attention, we mail have bailed
// because endpointworker was busy, if so process it now
if (endpoint->EndpointFlags & EPFLAG_HAVE_WORK) {
UHCD_EndpointWorker(deviceObject, endpoint);
}
}
LOGENTRY(LOG_MISC, 'EPl-', listEntry,
&deviceExtension->EndpointList, 0);
// now walk the list looking for idle bulk
{
ULONG idleBulkEndpoints = 0;
ULONG bulkEndpoints = 0;
listEntry = &deviceExtension->EndpointList;
if (!IsListEmpty(listEntry)) {
listEntry = deviceExtension->EndpointList.Flink;
}
LOGENTRY(LOG_MISC, 'EPl+', listEntry,
&deviceExtension->EndpointList, 0);
while (listEntry != &deviceExtension->EndpointList) {
BOOLEAN idle = TRUE;
endpoint = CONTAINING_RECORD(listEntry,
UHCD_ENDPOINT,
ListEntry);
ASSERT_ENDPOINT(endpoint);
LOGENTRY(LOG_MISC, 'prEP', endpoint,
&deviceExtension->EndpointList, listEntry);
listEntry = endpoint->ListEntry.Flink;
// see if this ep is closed, if so remove it and
// it on the closed list
if (endpoint->EndpointFlags & EPFLAG_EP_CLOSED) {
LOGENTRY(LOG_MISC, 'epCL', endpoint,
&deviceExtension->EndpointList, listEntry);
RemoveEntryList(&endpoint->ListEntry);
continue;
}
//
// Scan active transfer slots and process any transfers
// thet have been programmed into the hardware.
//
for (slot=0; slot<endpoint->MaxRequests; slot++) {
//
// If we have a transfer in the slot we are not idle
//
if (endpoint->ActiveTransfers[slot]) {
idle = FALSE;
}
}
if (endpoint->Type == USB_ENDPOINT_TYPE_BULK) {
bulkEndpoints++;
if (idle) {
idleBulkEndpoints++;
}
}
}
if (bulkEndpoints && bulkEndpoints == idleBulkEndpoints) {
// no activity on the bulk endpoints
// disable BW reclimation.
UHCD_BW_Reclimation(deviceObject, FALSE);
}
}
//
// See if we can free any endpoint structures
//
// walk through the closed endpoint list, if we find an endpoint that has
// been freed in a previous frame then go ahead and release its
// resources.
while (!IsListEmpty(&deviceExtension->ClosedEndpointList)) {
ULONG cnt;
listEntry = RemoveHeadList( &deviceExtension->ClosedEndpointList);
endpoint = CONTAINING_RECORD(listEntry,
UHCD_ENDPOINT,
ListEntry);
ASSERT_ENDPOINT(endpoint);
if (UHCD_GetCurrentFrame(deviceObject) <= endpoint->FrameToClose) {
//
// put it back and get out
//
InsertHeadList(&deviceExtension->ClosedEndpointList, &endpoint->ListEntry);
break;
}
// free the endpoint resources
UHCD_KdPrint((2, "'UHCD_IsrDpc free endpoint %x\n", endpoint));
if (endpoint->EndpointFlags & EPFLAG_DBL_BUFFER) {
UHCD_UnInitializeNoDMAEndpoint(deviceObject,
endpoint);
}
UHCD_ASSERT(!(endpoint->EndpointFlags & EPFLAG_FAST_ISO));
UHCD_FreeBandwidth(deviceObject, endpoint, endpoint->Offset);
for (cnt=0; cnt< endpoint->MaxRequests; cnt++) {
UHCD_FreeHardwareDescriptors(deviceObject,
endpoint->HardwareDescriptorList[cnt]);
}
#if DBG
//UHCD_BufferPoolCheck(deviceObject);
#endif
RETHEAP(endpoint);
}
// now check the lookaside list and add any endpoints on it
while (!IsListEmpty(&deviceExtension->EndpointLookAsideList)) {
listEntry = RemoveHeadList(&deviceExtension->EndpointLookAsideList);
endpoint = CONTAINING_RECORD(listEntry,
UHCD_ENDPOINT,
ListEntry);
ASSERT_ENDPOINT(endpoint);
InsertHeadList(&deviceExtension->EndpointList, &endpoint->ListEntry);
}
LOCK_ENDPOINT_LIST(deviceExtension, irql);
deviceExtension->EndpointListBusy = FALSE;
UNLOCK_ENDPOINT_LIST(deviceExtension, irql);
}
#ifdef FAST_ISO
// walk the fastiso list and complete any transfers
// if the last frame has pssed
{
listEntry = &deviceExtension->FastIsoTransferList;
if (!IsListEmpty(listEntry)) {
listEntry = deviceExtension->FastIsoTransferList.Flink;
}
LOGENTRY(LOG_MISC, 'FIl+', listEntry,
&deviceExtension->FastIsoTransferList, 0);
while (listEntry != &deviceExtension->FastIsoTransferList) {
PIRP irp;
PHCD_URB urb;
ULONG cf, lastFrame;
urb = (PHCD_URB) CONTAINING_RECORD(
listEntry,
struct _URB_HCD_COMMON_TRANSFER,
hca.HcdListEntry);
listEntry = HCD_AREA(urb).HcdListEntry.Flink;
lastFrame = urb->UrbIsochronousTransfer.StartFrame+1;
// urb->UrbIsochronousTransfer.NumberOfPackets;
cf = UHCD_GetCurrentFrame(deviceObject);
LOGENTRY(LOG_MISC, 'FIck', urb, cf, lastFrame);
if (cf >= lastFrame) {
LOGENTRY(LOG_MISC, 'FIcp', urb, cf, lastFrame);
RemoveEntryList(&HCD_AREA(urb).HcdListEntry);
irp = HCD_AREA(urb).HcdIrp;
UHCD_CompleteIrp(deviceObject,
irp,
STATUS_SUCCESS,
0,
urb);
continue;
}
}
}
#endif /* FAST_ISO */
// ENDPROC("IDpc");
// UHCD_KdPrint((2, "'exit UHCD_IsrDpc\n"));
}
VOID
UHCD_RequestInterrupt(
IN PDEVICE_OBJECT DeviceObject,
IN LONG FrameNumber
)
/*++
Routine Description:
This routine triggers a hradware interrupt on a specific
USB frame number.
Arguments:
DeviceObject - Supplies the device object.
Frame - frame to generate intrerrupt on, a negative value
indicates relative to the current frame.
Return Value:
None.
--*/
{
ULONG requestFrameNumber;
PDEVICE_EXTENSION deviceExtension;
PHW_TRANSFER_DESCRIPTOR transferDescriptor;
ULONG i;
UHCD_KdPrint((2, "'enter UHCD_RequestInterrupt\n"));
deviceExtension = DeviceObject->DeviceExtension;
//
// Allocate a TD we can use for this request
//
// NOTE:
// First two TDs are dedicated to detecting frame rollover.
//
for (i=UHCD_FIRST_TRIGGER_TD; i<MAX_TDS_PER_ENDPOINT; i++) {
transferDescriptor =
&deviceExtension->TriggerTDList->TDs[i];
if (deviceExtension->LastFrameProcessed >
transferDescriptor->Frame) {
break;
}
transferDescriptor = NULL;
}
if (transferDescriptor == NULL) {
//
// no TDS available for interrupt request, this means enough
// interrupts are happening that we'll be OK -- worst case
// is we'll get an interrupt on frame rollover.
//
UHCD_KdBreak((2, "'no tds for interrupt request\n"));
goto UHCD_RequestInterrupt_Done;
}
if (FrameNumber < 0) {
requestFrameNumber = UHCD_GetCurrentFrame(DeviceObject) -
FrameNumber;
} else {
requestFrameNumber = FrameNumber;
}
//
// all we do here is put a dummy isoch TD in the schedule at
// the requested frame number
//
LOGENTRY(LOG_MISC, 'REQi', requestFrameNumber, FrameNumber,
transferDescriptor);
//
// Make sure we can schedule it
//
if (requestFrameNumber >
deviceExtension->LastFrameProcessed+1+FRAME_LIST_SIZE-1) {
// note: if the request is to far in advance.
LOGENTRY(LOG_MISC, 'REQf', FrameNumber, requestFrameNumber,
(deviceExtension->LastFrameProcessed+1+FRAME_LIST_SIZE-1));
// if the request is too far in advance we'll just let it be handled
// by the rollover interrupts.
} else {
transferDescriptor->Active = 0;
transferDescriptor->MaxLength = UHCD_SYSTEM_TO_USB_BUFFER_LENGTH(0);
transferDescriptor->InterruptOnComplete = 1;
transferDescriptor->Frame = requestFrameNumber;
#ifdef VIA_HC
transferDescriptor->PID = USB_IN_PID;
#endif /* VIA_HC */
UHCD_InsertIsochDescriptor(DeviceObject,
transferDescriptor,
requestFrameNumber);
}
UHCD_RequestInterrupt_Done:
UHCD_KdPrint((2, "'exit UHCD_RequestInterrupt\n"));
}