/*++ 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; jFrameListVirtualAddress) + 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; iFrameListVirtualAddress) + 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; slotMaxRequests; 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; slotMaxRequests; 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; iTriggerTDList->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")); }