/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1998-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 #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; AbsoluteTime timeStop; IOReturn err = kIOReturnSuccess; // Calculate the time between the filter interrupt and the workloop dispatching // /* clock_get_uptime (&timeStop); SUB_ABSOLUTETIME(&timeStop, &_filterTimeStamp); absolutetime_to_nanoseconds(timeStop, &timeElapsed); USBLog(5,"%s[%p]::PollInterrupts: microsecs since filter interrupt: %qd", getName(), this, timeElapsed / 1000); */ // 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) { USBError(1,"USB Controller on bus %d received and unrecoverable error interrupt. Attempting to recover (%d,%d,%d)", _busNumber,_onCardBus, _pcCardEjected, isInactive() ); _unrecoverableErrorInterrupt = 0; _errors.unrecoverableError++; if ( _onCardBus ) { USBError(1,"Ignoring unrecoverable error on PC Card" ); } else { if ( !(_errataBits & kErrataNECOHCIIsochWraparound ) ) { // 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 = HostToUSBLong(kOHCIHcCommandStatus_HCR); delay(10 * MICROSECOND); _pOHCIRegisters->hcControl = HostToUSBLong((kOHCIFunctionalState_Operational << kOHCIHcControl_HCFSPhase) | kOHCIHcControl_PLE); } else { // For NEC controllers, we unload all drivers // if ( _rootHubDevice ) { _rootHubDevice->terminate(kIOServiceRequired | kIOServiceSynchronous); _rootHubDevice->detachAll(gIOUSBPlane); _rootHubDevice->release(); _rootHubDevice = NULL; } SuspendUSBBus(); UIMFinalizeForPowerDown(); _ohciAvailable = false; // tell the interrupt filter routine that we are off IOSleep(100); UIMInitializeForPowerUp(); _ohciAvailable = true; // tell the interrupt filter routine that we are on _ohciBusState = kOHCIBusStateRunning; if ( _rootHubDevice == NULL ) { err = CreateRootHubDevice( _device, &_rootHubDevice ); if ( err != kIOReturnSuccess ) { USBError(1,"%s[%p] Could not create root hub device upon wakeup (%x)!",getName(), this, err); } else { _rootHubDevice->registerService(kIOServiceRequired | kIOServiceSynchronous); } } } } } // 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; } //================================================================================================ // // IsValidPhysicalAddress() // // This routine will search for the incoming physical address in our GTD and ITD Memory Blocks. This // is used to verify that the address is one that we "know" about before we actually try to read from // it to get the logical address (that is stored at the beginning of the memory blocks). // // Note that the comparison is making use of the fact that we allocate our memory blocks in kOHCIPageSize // chunks, so we only need to compare the page #'s to see if they are equal. We are assuming that the // incoming address is the address of an OHCI page (lower 12 bits are 0). // //================================================================================================ // bool AppleUSBOHCI::IsValidPhysicalAddress(IOPhysicalAddress pageAddr) { AppleUSBOHCIitdMemoryBlock *itdMemBlock = _itdMBHead; AppleUSBOHCIgtdMemoryBlock *gtdMemBlock = _gtdMBHead; while (gtdMemBlock) { if ( pageAddr == (gtdMemBlock->GetSharedPhysicalPtr(0) & kOHCIPageMask) ) return true; gtdMemBlock = gtdMemBlock->GetNextBlock(); } while (itdMemBlock) { if ( pageAddr == (itdMemBlock->GetSharedPhysicalPtr(0) & kOHCIPageMask) ) return true; itdMemBlock = itdMemBlock->GetNextBlock(); } return false; } bool AppleUSBOHCI::FilterInterrupt(int index) { register UInt32 activeInterrupts; register UInt32 enabledInterrupts; IOPhysicalAddress physicalAddress; AppleOHCIGeneralTransferDescriptorPtr pHCDoneTD = NULL; AppleOHCIGeneralTransferDescriptorPtr nextTD = NULL, prevTD = NULL; 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(); if (_errataBits & kErrataNECIncompleteWrite) { UInt32 newValue = 0, count = 0; newValue = USBToHostLong(_pOHCIRegisters->hcInterruptStatus); // this bit SHOULD now be cleared while ((count++ < 10) && (newValue & kOHCIHcInterrupt_FNO)) { // can't log in the FilterInterrupt routine // USBError(1, "OHCI driver: UIMInitializeForPowerUp - DRWE bit not sticking. Retrying."); _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_FNO); IOSync(); newValue = USBToHostLong(_pOHCIRegisters->hcInterruptStatus); } } } // 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(); if (_errataBits & kErrataNECIncompleteWrite) { UInt32 newValue = 0, count = 0; newValue = USBToHostLong(_pOHCIRegisters->hcInterruptStatus); // this bit SHOULD now be cleared while ((count++ < 10) && (newValue & kOHCIHcInterrupt_RHSC)) { // can't log in the FilterInterrupt routine // USBError(1, "OHCI driver: UIMInitializeForPowerUp - DRWE bit not sticking. Retrying."); _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_RHSC); IOSync(); newValue = USBToHostLong(_pOHCIRegisters->hcInterruptStatus); } } 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(); if (_errataBits & kErrataNECIncompleteWrite) { UInt32 newValue = 0, count = 0; newValue = USBToHostLong(_pOHCIRegisters->hcInterruptStatus); // this bit SHOULD now be cleared while ((count++ < 10) && (newValue & kOHCIHcInterrupt_RD)) { // can't log in the FilterInterrupt routine _pOHCIRegisters->hcInterruptStatus = HostToUSBLong(kOHCIHcInterrupt_RD); IOSync(); newValue = USBToHostLong(_pOHCIRegisters->hcInterruptStatus); } } 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; if ( physicalAddress == NULL ) pHCDoneTD = NULL; else { if ( _onCardBus ) { if (!IsValidPhysicalAddress( physicalAddress & kOHCIPageMask) ) { USBLog(1, "Bad phys addr #1 %p", physicalAddress); pHCDoneTD = NULL; } else { pHCDoneTD = AppleUSBOHCIgtdMemoryBlock::GetGTDFromPhysical(physicalAddress); } } else { // 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; if ( physicalAddress == NULL ) nextTD = NULL; else { if ( _onCardBus ) { if (!IsValidPhysicalAddress( physicalAddress & kOHCIPageMask) ) { nextTD = NULL; } else nextTD = AppleUSBOHCIgtdMemoryBlock::GetGTDFromPhysical(physicalAddress); } else 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; } } } else { if ( (pHCDoneTD->pType != kOHCIIsochronousInType) && (pHCDoneTD->pType != kOHCIIsochronousOutType) ) { // Time stamp this TD (non isoch TD) // pHCDoneTD->command->SetTimeStamp( timeStamp ); } } 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; }