/* * Copyright (c) 1998-2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include "AppleUSBOHCIMemoryBlocks.h" #include "AppleUSBOHCI.h" #define nil (0) #define DEBUGGING_LEVEL 0 // 1 = low; 2 = high; 3 = extreme #define super IOUSBController #define self this void AppleUSBOHCI::PollInterrupts(IOUSBCompletionAction safeAction) { UInt64 timeElapsed; // Calculate the time in nanoseconds between the last 2 calls to the filter interrupt routine. Note that // we lose the data if there was more than 1 filter routine called before our action routine was called. // absolutetime_to_nanoseconds(_filterTimeStamp2, &_timeElapsed); // WritebackDoneHead Interrupt // if (_writeDoneHeadInterrupt & kOHCIHcInterrupt_WDH) { _writeDoneHeadInterrupt = 0; UIMProcessDoneQueue(safeAction); } // ResumeDetected Interrupt // if (_resumeDetectedInterrupt & kOHCIHcInterrupt_RD) { _resumeDetectedInterrupt = 0; //setPowerState(1, self); _remote_wakeup_occurred = true; //needed by ::callPlatformFunction() USBLog(3,"%s[%p] ResumeDetected Interrupt on bus %d", getName(), this, _busNumber ); if ( _idleSuspend ) setPowerState(kOHCISetPowerLevelRunning, self); } // Unrecoverable Error Interrupt // if (_unrecoverableErrorInterrupt & kOHCIHcInterrupt_UE) { _unrecoverableErrorInterrupt = 0; _errors.unrecoverableError++; // Let's do a SW reset to recover from this condition. // We could make sure all OCHI registers and in-memory // data structures are valid, too. _pOHCIRegisters->hcCommandStatus = USBToHostLong(kOHCIHcCommandStatus_HCR); delay(10 * MICROSECOND); // zzzz - note I'm leaving the Control/Bulk list processing off // for now. FIXME? ERIC _pOHCIRegisters->hcControl = USBToHostLong((kOHCIFunctionalState_Operational << kOHCIHcControl_HCFSPhase) | kOHCIHcControl_PLE); } // RootHubStatusChange Interrupt // if (_rootHubStatusChangeInterrupt & kOHCIHcInterrupt_RHSC) { _rootHubStatusChangeInterrupt = 0; _remote_wakeup_occurred = true; //needed by ::callPlatformFunction() USBLog(3,"%s[%p] RootHub Status Change Interrupt on bus %d", getName(), this, _busNumber ); UIMRootHubStatusChange( false ); LastRootHubPortStatusChanged ( true ); // We let the RootHub driver enable this interrupt once it gets another interrupt read // } } void AppleUSBOHCI::InterruptHandler(OSObject *owner, IOInterruptEventSource * /*source*/, int /*count*/) { register AppleUSBOHCI *controller = (AppleUSBOHCI *) owner; if (!controller || controller->isInactive() || (controller->_onCardBus && controller->_pcCardEjected) || !controller->_ohciAvailable) return; // Finish pending transactions first. // controller->finishPending(); controller->PollInterrupts(); controller->_filterInterruptCount = 0; } // This routine will get called at Primary interrupt time. When we are interrupted the host controller // has already written the HCDoneHead register to the HCCADoneHeadRegister. Furthermore, the host controller // will NOT update the HCCADoneHeadRegister again until we clear the WDH (WritebackDoneHead) bit of the // HCInterruptStatus register. At Filter Interrupt time (hardware interrupt) we will NOT clear that bit so that // the Action Interrupt ( secondary interrupt) can fire and it will then clear the bit. // // At primary interrupt time we are only concerned with updating the frStatus and frActCount fields of the frames // in low latency isoch TD's. We will traverse the done queue (pointed by the HCCADoneHeadRegister) and look for those // TD and update the frStatus and frActCount fields just like is done in the ProcessCompletedITD routine. // // The Done Queue has physical addresses. We need to traverse the queue using logical addresses, so we need to do // a lookup of the logical address from the physical address. // bool AppleUSBOHCI::PrimaryInterruptFilter(OSObject *owner, IOFilterInterruptEventSource *source) { register AppleUSBOHCI *controller = (AppleUSBOHCI *)owner; bool result = true; // If we our controller has gone away, or it's going away, or if we're on a PC Card and we have been ejected, // then don't process this interrupt. // if (!controller || controller->isInactive() || (controller->_onCardBus && controller->_pcCardEjected) || !controller->_ohciAvailable) return false; // Process this interrupt // result = controller->FilterInterrupt(0); return result; } bool AppleUSBOHCI::FilterInterrupt(int index) { register UInt32 activeInterrupts; register UInt32 enabledInterrupts; IOPhysicalAddress physicalAddress; AppleOHCIGeneralTransferDescriptorPtr pHCDoneTD; AppleOHCIGeneralTransferDescriptorPtr nextTD, prevTD; AbsoluteTime timeStamp; UInt32 numberOfTDs = 0; IOPhysicalAddress oldHead; IOPhysicalAddress cachedHead; UInt32 cachedProducer; Boolean needSecondary = false; // Check if the OHCI has written the DoneHead yet. First we get the list of // active enabled interrupts and we make sure that the master interrupts bit // is enabled and that we do have an interrupt to process. // enabledInterrupts = USBToHostLong(_pOHCIRegisters->hcInterruptEnable); activeInterrupts = enabledInterrupts & USBToHostLong(_pOHCIRegisters->hcInterruptStatus); if ((enabledInterrupts & kOHCIHcInterrupt_MIE) && (activeInterrupts != 0)) { // One of our 8 interrupts fired. Need to see which one it is // // Frame Number Overflow // if (activeInterrupts & kOHCIHcInterrupt_FNO) { // not really an error, but close enough // _errors.frameNumberOverflow++; if ( (USBToHostWord(*(UInt16*)(_pHCCA + kHCCAFrameNumberOffset)) & kOHCIFmNumberMask) < kOHCIBit15 ) _frameNumber += kOHCIFrameOverflowBit; // Clear the interrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_FNO); IOSync(); } // SchedulingOverrun Interrupt // if (activeInterrupts & kOHCIHcInterrupt_SO) { _errors.scheduleOverrun++; // Clear the interrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_SO); IOSync(); } // StartofFrame Interrupt // if (activeInterrupts & kOHCIHcInterrupt_SF) { // Clear the interrrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_SF); IOSync(); // and mask it off so it doesn't happen again. // will have to be turned on manually to happen again. // _pOHCIRegisters->hcInterruptDisable = HostToUSBLong(kOHCIHcInterrupt_SF); IOSync(); } // OwnershipChange Interrupt // if (activeInterrupts & kOHCIHcInterrupt_OC) { // well, we certainly weren't expecting this! _errors.ownershipChange++; // Clear the interrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_OC); IOSync(); } // RootHub Status Change Interrupt // if (activeInterrupts & kOHCIHcInterrupt_RHSC) { // Set the shadow field that will tell the secondary interrput that we had an RHSC // Interrupt event // _rootHubStatusChangeInterrupt = kOHCIHcInterrupt_RHSC; // disable the RHSC interrupt until we process it at secondary interrupt // time. some controllers do not respond to the clear bit _pOHCIRegisters->hcInterruptDisable = HostToUSBLong(kOHCIHcInterrupt_RHSC); IOSync(); // Clear the interrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_RHSC); IOSync(); needSecondary = true; } // Unrecoverable Error Interrupt // if (activeInterrupts & kOHCIHcInterrupt_UE) { _errors.unrecoverableError++; // Set the shadow field that will tell the secondary interrput that we had an RHSC // Interrupt event // _unrecoverableErrorInterrupt = kOHCIHcInterrupt_UE; // Clear the interrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_UE); IOSync(); needSecondary = true; } // Resume Detected Interrupt // if (activeInterrupts & kOHCIHcInterrupt_RD) { // Set the shadow field that will tell the secondary interrput that we had an RD // Interrupt event // _resumeDetectedInterrupt = kOHCIHcInterrupt_RD; // Clear the interrupt // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_RD); IOSync(); needSecondary = true; } // Check to see if the WriteDoneHead interrupt fired. If so, then we can look at the queue // if (activeInterrupts & kOHCIHcInterrupt_WDH) { // Now that we have the beginning of the queue, walk it looking for low latency isoch TD's // Use this time as the time stamp time for all the TD's that we processed. // clock_get_uptime(&timeStamp); // Debugging aid to keep track of how long we take in between calls to the filter routine // _filterInterruptCount++; _filterTimeStamp2 = timeStamp; SUB_ABSOLUTETIME(&_filterTimeStamp2, &_filterTimeStamp); _filterTimeStamp = timeStamp; // Get the pointer to the list (physical address) // physicalAddress = (UInt32) USBToHostLong(*(UInt32 *)(_pHCCA + kHCCADoneHeadOffset)); // Mask off interrupt bits // physicalAddress &= kOHCIHeadPMask; // Save the current value of the shadow queue head so that we can link our new head // to it later on. // oldHead = _savedDoneQueueHead; // And save the current head // cachedHead = physicalAddress; // Now get the logical address from the physical one // pHCDoneTD = AppleUSBOHCIgtdMemoryBlock::GetGTDFromPhysical(physicalAddress); // write to 0 to the HCCA DoneHead ptr so we won't look at it anymore. // *(UInt32 *)(_pHCCA + kHCCADoneHeadOffset) = 0L; // Since we have a copy of the queue to process, we can let the host update it again. We // do this by writing one to the bit in the register. // _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_WDH); IOSync(); _writeDoneHeadInterrupt = kOHCIHcInterrupt_WDH; prevTD = NULL; while (pHCDoneTD != NULL) { AppleOHCIIsochTransferDescriptorPtr pITD; IOUSBLowLatencyIsocFrame * pFrames; IOReturn errStatus; UInt32 control; UInt32 transferStatus; UInt32 frameCount; UInt32 i; // Increment our count of the number of TDs that this queue head is pointing to // numberOfTDs++; // Find the next one // physicalAddress = USBToHostLong(pHCDoneTD->pShared->nextTD) & kOHCIHeadPMask; nextTD = AppleUSBOHCIgtdMemoryBlock::GetGTDFromPhysical(physicalAddress); if ( (pHCDoneTD->pType == kOHCIIsochronousInLowLatencyType) || (pHCDoneTD->pType == kOHCIIsochronousOutLowLatencyType) ) { // We have a low latency isoch TD. Update debugging stamps // _lowLatencyIsochTDsProcessed++; // Since we know it's an ITD, cast it into one and get a pointer to the Low Latency ITD's frames // pITD = (AppleOHCIIsochTransferDescriptorPtr) pHCDoneTD; pFrames = (IOUSBLowLatencyIsocFrame *) pITD->pIsocFrame; // Get any errors from the TD // control = USBToHostLong(pHCDoneTD->pShared->ohciFlags); transferStatus = (control & kOHCIGTDControl_CC) >> kOHCIGTDControl_CCPhase; errStatus = TranslateStatusToUSBError(transferStatus); // Process the frames in the low latency isochTDs // frameCount = (USBToHostLong(pITD->pShared->flags) & kOHCIITDControl_FC) >> kOHCIITDControl_FCPhase; for (i = 0; i <= frameCount; i++) { // Debugging stamps // _framesUpdated++; if ( pFrames[pITD->frameNum + i].frStatus != (IOReturn) kUSBLowLatencyIsochTransferKey ) _framesError++; // Set the time stamp // pFrames[pITD->frameNum + i].frTimeStamp = timeStamp; // Get information on whether there was an error in the frame // UInt16 offset = USBToHostWord(pITD->pShared->offset[i]); if ( ((offset & kOHCIITDOffset_CC) >> kOHCIITDOffset_CCPhase) == kOHCIITDOffsetConditionNotAccessed) { // If the condition code is not accessed, set the frActCount to 0 and the status accordingly // pFrames[pITD->frameNum + i].frActCount = 0; pFrames[pITD->frameNum + i].frStatus = TranslateStatusToUSBError(kOHCIITDConditionNotAccessedReturn); } else { // Set the frStatus to the OHCI Condition code translated to the correct USB Error // pFrames[pITD->frameNum + i].frStatus = TranslateStatusToUSBError( (offset & kOHCIITDPSW_CC) >> kOHCIITDPSW_CCPhase); // Successful isoch transmit sets the size field to requested count, // successful receive sets size to actual packet size received // if((kIOReturnSuccess == pFrames[pITD->frameNum + i].frStatus) && (pITD->pType == kOHCIIsochronousOutLowLatencyType)) pFrames[pITD->frameNum + i].frActCount = pFrames[pITD->frameNum + i].frReqCount; else pFrames[pITD->frameNum + i].frActCount = offset & kOHCIITDPSW_Size; } } } prevTD = pHCDoneTD; // Look at the next TD pHCDoneTD = nextTD; /* New qHead */ } // We have now processed all the TD's in this queue. We need to update our producer count // cachedProducer = _producerCount; cachedProducer += numberOfTDs; // Now link in to the old queue head. Note that we have to write this in bus order as the // secondary interrupt routine will do the opposite when it reverses the list // if ( prevTD != NULL ) prevTD->pShared->nextTD = HostToUSBLong(oldHead); // Now, update the producer and head. We need to take a lock so that the consumer (the action routine) does not read them // while they are in transition. // IOSimpleLockLock( _wdhLock ); _savedDoneQueueHead = cachedHead; // updates the shadow head _producerCount = cachedProducer; // Validates _producerCount; IOSimpleLockUnlock( _wdhLock ); needSecondary = true; } } // We will return false from this filter routine, but will indicate that there the action routine should be called by calling _filterInterruptSource->signalInterrupt(). // This is needed because IOKit will disable interrupts for a level interrupt after the filter interrupt is run, until the action interrupt is called. We want to be // able to have our filter interrupt routine called before the action routine runs, if needed. That is what will enable low latency isoch transfers to work, as when the // system is under heavy load, the action routine can be delayed for tens of ms. // if (needSecondary) _filterInterruptSource->signalInterrupt(); return false; }