#include "AppleiSubEngine.h" #include "AppleUSBAudioCommon.h" #include "AppleUSBAudioLevelControl.h" #include "AppleUSBAudioMuteControl.h" #include "USBAudioObject.h" #include #include #include #define kiSubFeatureUnitID 2 #define kiSubMuteControlChannelNum 0 #define kiSubVolumeControlMasterChannelNum 1 // Has to be true or else the iSub looses the beginning of sounds if it has auto powered down #define NUM_POWER_STATES 2 #define super IOService // aml 02.14.02 mandatory definitions of static constants const UInt32 AppleiSubEngine::kDefaultOutputSampleRate; const UInt32 AppleiSubEngine::kDefaultNumChannels; const UInt32 AppleiSubEngine::kDefaultBytesPerSample; const iSubAltInterfaceType AppleiSubEngine::kDefaultiSubAltInterface; OSDefineMetaClassAndStructors (AppleiSubEngine, super) #pragma mark -IOKit Routines- void AppleiSubEngine::closeiSub (IOService * forClient) { IOReturn result; debug3IOLog ("+AppleiSubEngine[%p]::closeiSub (%p)\n", this, forClient); if (forClient == audioEngine) { if ((NULL != muteControl || NULL != iSubVolumeControl) && NULL != audioEngine) { debugIOLog ("Removing iSub audio controls\n"); if (NULL != iSubVolumeControl) { result = audioEngine->removeDefaultAudioControl (iSubVolumeControl); iSubVolumeControl = NULL; #if DEBUGLOG if (kIOReturnSuccess != result) IOLog ("Error 0x%x removing left iSub control\n", result); #endif } if (NULL != muteControl) { result = audioEngine->removeDefaultAudioControl (muteControl); muteControl = NULL; #if DEBUGLOG if (kIOReturnSuccess != result) IOLog ("Error 0x%x removing mute iSub control\n", result); #endif } release (); // It's safe to release us now, there are no more controls that might be able to call into us. // Don't try to use our audio engine host anymore audioEngine = NULL; #if DEBUGLOG } else { if (NULL == muteControl) {IOLog ("NULL == muteControl\n");} if (NULL == iSubVolumeControl) {IOLog ("NULL == iSubVolumeControl\n");} if (NULL == audioEngine) {IOLog ("NULL == audioEngine\n");} #endif } } else { debugIOLog ("the wrong client tried to close us\n"); } debug3IOLog ("-AppleiSubEngine[%p]::closeiSub (%p)\n", this, forClient); } void AppleiSubEngine::free (void) { UInt32 frameIndex; debug2IOLog ("+AppleiSubEngine[%p]::free ()\n", this); if (NULL != thePipe) { thePipe->release (); thePipe = NULL; } if (NULL != sampleBufferDescriptor) { sampleBufferDescriptor->release (); sampleBufferDescriptor = NULL; } if (NULL != sampleBuffer) { IOFree (sampleBuffer, bufferSize); sampleBuffer = NULL; } for (frameIndex = 0; frameIndex < NUM_ISUB_FRAME_LISTS; frameIndex++) { if (NULL != soundBuffer[frameIndex]) { soundBuffer[frameIndex]->release (); soundBuffer[frameIndex] = NULL; } } super::free (); debug2IOLog ("-AppleiSubEngine[%p]::free ()\n", this); return; } // This has to be called on the audio engine's work loop so that we don't interrupt the IOAudioFamily bool AppleiSubEngine::openiSub (IOService * forClient) { bool resultCode; resultCode = FALSE; // Only one client at a time! FailIf (NULL != audioEngine, Exit); audioEngine = OSDynamicCast (IOAudioEngine, forClient); FailIf (NULL == audioEngine, Exit); // audioEngine->joinPMtree (this); // if (pm_vars != NULL) { // registerPowerDriver (this, iSubPowerStates, NUM_POWER_STATES); // changePowerStateTo (1); // } audioEngine->pauseAudioEngine (); audioEngine->beginConfigurationChange (); retain (); // As long as there are controls out there, don't let ourselves be released. // Only need a master volume control now that the iSub is running mono. iSubVolumeControl = AppleUSBAudioLevelControl::create (kiSubFeatureUnitID, controlInterface->GetInterfaceNumber (), VOLUME_CONTROL, kiSubVolumeControlMasterChannelNum, FALSE, (USBDeviceRequest)&deviceRequest, this, kIOAudioLevelControlSubTypeLFEVolume, kIOAudioControlUsageOutput); FailIf (NULL == iSubVolumeControl, Exit); audioEngine->addDefaultAudioControl (iSubVolumeControl); muteControl = AppleUSBAudioMuteControl::create (kiSubFeatureUnitID, controlInterface->GetInterfaceNumber (), 0, (USBDeviceRequest)&deviceRequest, this, kIOAudioControlUsageOutput, kIOAudioToggleControlSubTypeLFEMute); FailIf (NULL == muteControl, Exit); audioEngine->addDefaultAudioControl (muteControl); audioEngine->completeConfigurationChange (); audioEngine->resumeAudioEngine (); resultCode = TRUE; Exit: debug4IOLog ("-AppleiSubEngine[%p]::handleOpen (%p, 0x%lx, %p) = %d\n", this, forClient, resultCode); return resultCode; } bool AppleiSubEngine::init (OSDictionary * properties) { Boolean resultCode; debug3IOLog ("+AppleiSubEngine[%p]::init (%p)\n", this, properties); resultCode = FALSE; FailIf (FALSE == super::init (properties), Exit); resultCode = TRUE; // aml 2.28.02 initialize member vars mFormat.altInterface = kDefaultiSubAltInterface; mFormat.numChannels = kDefaultNumChannels; mFormat.bytesPerSample = kDefaultBytesPerSample; mFormat.outputSampleRate = kDefaultOutputSampleRate; Exit: debug4IOLog ("-AppleiSubEngine[%p]::init (%p) = %d\n", this, properties, resultCode); return resultCode; } bool AppleiSubEngine::start (IOService * provider) { Boolean resultBool; IOUSBFindInterfaceRequest findRequest; IOUSBFindEndpointRequest audioIsochEndpoint; IOReturn resultIOReturn; UInt32 i; UInt32 frameListNum; UInt16 numQueued; static IOPMPowerState iSubPowerStates[2] = { {kIOPMPowerStateVersion1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {kIOPMPowerStateVersion1, kIOPMDeviceUsable, kIOPMPowerOn, kIOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0} }; debug3IOLog ("+AppleiSubEngine[%p]::start (%p)\n", this, provider); resultBool = FALSE; FailIf (FALSE == super::start (provider), Exit); streamInterface = OSDynamicCast (IOUSBInterface, provider); FailIf (NULL == streamInterface, Exit); ourInterfaceNumber = streamInterface->GetInterfaceNumber (); debug2IOLog ("AppleiSubEngine->ourInterfaceNumber = %d\n", ourInterfaceNumber); // aml 1.18.02 replaced constant with member alternateInterfaceID = mFormat.altInterface; PMinit (); streamInterface->joinPMtree (this); if (pm_vars != NULL) { registerPowerDriver (this, iSubPowerStates, NUM_POWER_STATES); changePowerStateTo (1); } // aml 1.18.02 replaced sample rate with member // aml 2.13.02 replaced kInputSampleRate with mOutputSampleRate // aml 2.13.02 added num channels and bit depth constants, instead of '4' frameListSize = CalculateNumSamplesPerBuffer (mFormat.outputSampleRate, NUM_ISUB_FRAMES_PER_LIST) * mFormat.numChannels * mFormat.bytesPerSample; debug4IOLog ("format = %ldHz, %ld channels, %ld bits\n", mFormat.outputSampleRate, mFormat.numChannels, mFormat.bytesPerSample * 8); #if AML_DEBUGLOG IOLog ("AppleiSubEngine::handleOpen: frameListSize = %d\n", frameListSize); #endif debug2IOLog ("frameListSize = %d\n", frameListSize); // aml 2.13.02 replaced kInputSampleRate with mOutputSampleRate // aml 2.13.02 added num channels and bit depth members, instead of '4' bufferSize = CalculateNumSamplesPerBuffer (mFormat.outputSampleRate, NUM_ISUB_FRAMES_PER_LIST, NUM_ISUB_FRAME_LISTS) * mFormat.numChannels * mFormat.bytesPerSample; debug2IOLog ("bufferSize = %d\n", bufferSize); sampleBuffer = IOMallocAligned (round_page (bufferSize), PAGE_SIZE); FailIf (NULL == sampleBuffer, Exit); // aml 2.13.02 changed from clearing by longs to words for (i = 0; i < bufferSize / sizeof(UInt16); i++) { ((UInt16*)sampleBuffer)[i] = 0; } sampleBufferDescriptor = IOMemoryDescriptor::withAddress (sampleBuffer, bufferSize, kIODirectionNone); FailIf (NULL == sampleBufferDescriptor, Exit); FailIf (kIOReturnSuccess != PrepareFrameLists (frameListSize), Exit); // FailIf (TRUE == streamOpened, Exit); // IOLog ("after TRUE == streamOpened\n"); shouldCloseStream = FALSE; FailIf (FALSE == streamInterface->open (this), Exit); streamOpened = TRUE; resultIOReturn = streamInterface->SetAlternateInterface (this, kRootAlternateSetting); FailIf (kIOReturnSuccess != resultIOReturn, Exit); // aml 1.18.02 replaced 4 (16 bit stereo alternate interface) with member resultIOReturn = streamInterface->SetAlternateInterface (this, mFormat.altInterface); FailIf (kIOReturnSuccess != resultIOReturn, Exit); // Acquire a PIPE for the isochronous stream. audioIsochEndpoint.type = kUSBIsoc; audioIsochEndpoint.direction = kUSBOut; thePipe = streamInterface->FindNextPipe (NULL, &audioIsochEndpoint); FailIf (NULL == thePipe, Exit); thePipe->retain (); // The iSub is a little funky and reseting it improves (but doesn't totally eliminate) funky behavior. // streamInterface->GetDevice()->ResetDevice (); // Start the iSub playing silence now loopCount = 0; numUSBFrameListsNotOutstanding = 0; numQueued = 0; currentFrameList = 0; currentByteOffset = 0; theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset; for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) { resultIOReturn = WriteFrameList (frameListNum); FailIf (kIOReturnSuccess != resultIOReturn, Exit); numQueued++; } debug2IOLog ("writeFrameList resultIOReturn = 0x%lx\n", resultIOReturn); // Find control interface on iSub device findRequest.bInterfaceClass = 1; findRequest.bInterfaceSubClass = 1; findRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; findRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare; controlInterface = streamInterface->GetDevice()->FindNextInterface (NULL, &findRequest); FailIf (NULL == controlInterface, Exit); registerService (); resultBool = TRUE; Exit: debug4IOLog ("-AppleiSubEngine[%p]::start (%p) = %d\n", this, provider, resultBool); return resultBool; } bool AppleiSubEngine::willTerminate (IOService * provider, IOOptionBits options) { bool resultCode; debug5IOLog ("+AppleiSubEngine[%p]::willTerminate (%p, 0x%x) rc =\n", this, provider, options, getRetainCount ()); if (FALSE == iSubUSBRunning) { // close our stream interface and go away if ((streamInterface != NULL) && (streamInterface == provider)) { debugIOLog ("willTerminate closing iSub down\n"); if (NULL != thePipe) { thePipe->release (); thePipe = NULL; } streamInterface->close (this); streamOpened = FALSE; } } else { shouldCloseStream = TRUE; } resultCode = super::willTerminate (provider, options); debug6IOLog ("-AppleiSubEngine[%p]::willTerminate (%p, 0x%x) = %d, rc=%d\n", this, provider, options, resultCode, getRetainCount ()); return resultCode; } IOReturn AppleiSubEngine::setPowerState (unsigned long powerStateOrdinal, IOService *device) { IOReturn result; debug4IOLog("AppleiSubEngine[%p]::setPowerState (%lu, %p)\n", this, powerStateOrdinal, device); if (device == this) { switch (powerStateOrdinal) { case 0: // Power off state debugIOLog ("sleeping...\n"); sleeping = TRUE; StopiSub (); result = IOPMNoErr; break; case 1: // Power on state debugIOLog ("waking...\n"); sleeping = FALSE; // Don't do the reset on this thread because it takes too long. /* if (NULL != streamInterface) { streamInterface->GetDevice()->ResetDevice (); // The ResetDevice causes the iSub to loose its volume and mute states, so put them back. if (NULL != muteControl) { muteControl->flushValue (); } if (NULL != iSubVolumeControl) { iSubVolumeControl->flushValue (); } } */ result = IOPMNoErr; break; default: result = IOPMNoSuchState; } } return result; } #pragma mark -iSub Routines- UInt32 AppleiSubEngine::CalculateNumSamplesPerBuffer (UInt32 sampleRate, UInt32 theNumFramesPerList, UInt32 theNumFrameLists) { UInt32 numSamplesPerFrameList; UInt32 totalFrames; UInt32 numAlternateFrames; UInt32 numAverageFrames; UInt16 averageSamplesPerFrame; UInt16 additionalSampleFrameFreq; CalculateSamplesPerFrame (sampleRate, &averageSamplesPerFrame, &additionalSampleFrameFreq); if (0 == additionalSampleFrameFreq) { numSamplesPerFrameList = averageSamplesPerFrame * theNumFramesPerList * theNumFrameLists; } else { totalFrames = theNumFramesPerList * theNumFrameLists; numAlternateFrames = totalFrames / additionalSampleFrameFreq; numAverageFrames = totalFrames - numAlternateFrames; numSamplesPerFrameList = (numAverageFrames * averageSamplesPerFrame) + (numAlternateFrames * (averageSamplesPerFrame + 1)); } return numSamplesPerFrameList; } void AppleiSubEngine::CalculateSamplesPerFrame (UInt32 sampleRate, UInt16 * averageSamplesPerFrame, UInt16 * additionalSampleFrameFreq) { UInt32 divisor; *averageSamplesPerFrame = sampleRate / 1000; divisor = (sampleRate % 1000); if (divisor) *additionalSampleFrameFreq = 1000 / divisor; else *additionalSampleFrameFreq = 0; } IOReturn AppleiSubEngine::deviceRequest (IOUSBDevRequest * request, AppleiSubEngine * self, IOUSBCompletion * completion) { IOReturn result; debug4IOLog ("+AppleiSubEngine[%p]::deviceRequest (%p, %p)\n", self, request, completion); result = kIOReturnSuccess; if (self->streamInterface && request) { result = self->streamInterface->DeviceRequest (request, completion); } debug4IOLog ("-AppleiSubEngine[%p]::deviceRequest (%p, %p)\n", self, request, completion); return result; } UInt32 AppleiSubEngine::GetCurrentByteCount (void) { return currentByteOffset; } UInt32 AppleiSubEngine::GetCurrentLoopCount (void) { return loopCount; } IOMemoryDescriptor * AppleiSubEngine::GetSampleBuffer (void) { return sampleBufferDescriptor; } IOReturn AppleiSubEngine::PrepareFrameLists (UInt32 frameListSize) { IOReturn result; UInt32 frameIndex; result = kIOReturnError; for (frameIndex = 0; frameIndex < NUM_ISUB_FRAME_LISTS; frameIndex++) { usbCompletion[frameIndex].target = (void *)this; usbCompletion[frameIndex].parameter = (void *)((UInt8 *)sampleBuffer + (frameIndex * frameListSize)); // pointer into the buffer that is the start of this frame list usbCompletion[frameIndex].action = WriteHandler; soundBuffer[frameIndex] = IOMemoryDescriptor::withAddress ((UInt8 *)usbCompletion[frameIndex].parameter, frameListSize, kIODirectionNone); FailIf (NULL == soundBuffer[frameIndex], Exit); } result = kIOReturnSuccess; Exit: return result; } IOReturn AppleiSubEngine::StartiSub (void) { UInt32 frameListNum; UInt16 numQueued = 0; // aml this was unititialized and the for loop below never ran sometimes! IOReturn resultCode; debug2IOLog ("+AppleiSubEngine[%p]::StartiSub ()\n", this); FailWithAction (TRUE == sleeping, resultCode = kIOReturnOffline, Exit); resultCode = kIOReturnError; iSubRunning = TRUE; #if ABORT_PIPE_ON_START FailIf (NULL == thePipe, Exit); thePipe->Abort (); // let's kill all outstanding IO and start right back at the beginning iSubUSBRunning = FALSE; // FailIf (NULL == streamInterface, Exit); // theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset; // for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) { // resultCode = WriteFrameList (frameListNum); // FailIf (kIOReturnSuccess != resultCode, Exit); // numQueued++; // } #else loopCount = 0xFFFFFFFF; // so that it will go to 0 in the write completion routine when the frames are aborted currentFrameList = NUM_ISUB_FRAME_LISTS - 1; // theFirstFrame = 0; // force the completion routine to calculate the correct starting frame // FailIf (NULL == thePipe, Exit); // thePipe->Abort (); // let's kill all outstanding IO and start right back at the beginning // iSubUSBRunning = FALSE; // currentByteOffset = 0; #endif // ABORT_PIPE_ON_START resultCode = kIOReturnSuccess; if (FALSE == iSubUSBRunning) { loopCount = 0; numUSBFrameListsNotOutstanding = 0; numQueued = 0; currentFrameList = 0; currentByteOffset = 0; FailIf (NULL == streamInterface, Exit); theFirstFrame = streamInterface->GetDevice()->GetBus()->GetFrameNumber () + kMinimumiSubFrameOffset; for (frameListNum = currentFrameList; numQueued < NUM_ISUB_FRAME_LISTS_TO_QUEUE; frameListNum++) { resultCode = WriteFrameList (frameListNum); FailIf (kIOReturnSuccess != resultCode, Exit); numQueued++; } } Exit: debug3IOLog ("-AppleiSubEngine[%p]::StartiSub (), result = %d\n", this, resultCode); return resultCode; } IOReturn AppleiSubEngine::StopiSub (void) { debug2IOLog ("+AppleiSubEngine[%p]::StopiSub ()\n", this); iSubRunning = FALSE; debug2IOLog ("-AppleiSubEngine[%p]::StopiSub ()\n", this); return kIOReturnSuccess; } IOReturn AppleiSubEngine::WriteFrameList (UInt32 frameListNum) { UInt32 frameIndex; UInt32 firstFrame; UInt16 averageFrameSamples; UInt16 averageFrameSize; UInt16 alternateFrameSize; UInt16 additionalSampleFrameFreq; IOReturn result; result = kIOReturnError; firstFrame = (frameListNum % NUM_ISUB_FRAME_LISTS_TO_QUEUE) * NUM_ISUB_FRAMES_PER_LIST; // aml 2.13.02 replaced kInputSampleRate with kOutputSampleRate CalculateSamplesPerFrame (mFormat.outputSampleRate, &averageFrameSamples, &additionalSampleFrameFreq); // aml 2.13.02 added num channels and bit depth constants, instead of '4' averageFrameSize = averageFrameSamples * mFormat.numChannels * mFormat.bytesPerSample; alternateFrameSize = (averageFrameSamples + 1) * mFormat.numChannels * mFormat.bytesPerSample; if (additionalSampleFrameFreq) { for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) { theFrames[firstFrame + frameIndex].frStatus = -1; if ((frameIndex % additionalSampleFrameFreq) == (UInt16)(additionalSampleFrameFreq - 1)) { theFrames[firstFrame + frameIndex].frReqCount = alternateFrameSize; } else { theFrames[firstFrame + frameIndex].frReqCount = averageFrameSize; } theFrames[firstFrame + frameIndex].frActCount = 0; } } else { for (frameIndex = 0; frameIndex < NUM_ISUB_FRAMES_PER_LIST; frameIndex++) { theFrames[firstFrame + frameIndex].frStatus = -1; theFrames[firstFrame + frameIndex].frReqCount = averageFrameSize; theFrames[firstFrame + frameIndex].frActCount = 0; } } FailIf (NULL == thePipe, Exit); // retain (); // Don't want the driver being terminated until our completion routine runs. result = thePipe->Write (soundBuffer[frameListNum], theFirstFrame, NUM_ISUB_FRAMES_PER_LIST, &theFrames[firstFrame], &usbCompletion[frameListNum]); if (result != kIOReturnSuccess) { if (streamInterface) { debug6IOLog ("++AppleiSubEngine[%p]::WriteFrameList (%d) - error writing to pipe at frame %lu - current = %lu: 0x%x\n", this, frameListNum, (UInt32)theFirstFrame, (UInt32)streamInterface->GetDevice()->GetBus()->GetFrameNumber(), result); } iSubUSBRunning = FALSE; } else { theFirstFrame += NUM_ISUB_FRAMES_PER_LIST; iSubUSBRunning = TRUE; } Exit: return result; } void AppleiSubEngine::WriteHandler (void * object, void * buffer, IOReturn result, IOUSBIsocFrame * pFrames) { AppleiSubEngine * self; UInt64 currentUSBFrame; UInt32 frameListToWrite; UInt32 i; self = (AppleiSubEngine *)object; FailIf (TRUE == self->sleeping, Exit); if (result != kIOReturnSuccess) { #if DEBUGLOG IOLog ("++AppleiSubEngine::WriteHandler () - error 0x%x\n", result); #endif FailIf (NULL == self->streamInterface, Exit); currentUSBFrame = self->streamInterface->GetDevice()->GetBus()->GetFrameNumber (); switch (result) { #if ABORT_PIPE_ON_START case kIOReturnAborted: #if AML_DEBUGLOG IOLog ("AppleiSubEngine::WriteHandler() aborted.\n"); #endif goto Exit; // do nothing break; case kIOReturnOverrun: #else case kIOReturnOverrun: case kIOReturnAborted: #endif default: // skip ahead and see if that helps if (self->theFirstFrame <= currentUSBFrame) { self->theFirstFrame = currentUSBFrame + kMinimumiSubFrameOffset; #if DEBUGLOG IOLog ("+++AppleiSubEngine::skipping ahead to frame %ld\n", self->theFirstFrame); #endif } break; } } // aml 4.25.02 moved below error checking! // Zero the data in the buffer so that this buffer just contains silence // aml 2.13.02 changed from clearing by longs to words for (i = 0; i < self->frameListSize / sizeof(UInt16); i++) { ((UInt16*)buffer)[i] = 0; } if ((NUM_ISUB_FRAME_LISTS - 1) == self->currentFrameList) { if (TRUE == self->iSubRunning) { self->loopCount++; self->currentByteOffset = 0; } self->currentFrameList = 0; } else { self->currentFrameList++; if (TRUE == self->iSubRunning) { self->currentByteOffset = self->currentFrameList * self->frameListSize; } } if (FALSE == self->shouldCloseStream) { frameListToWrite = self->currentFrameList + NUM_ISUB_FRAME_LISTS_TO_QUEUE - 1; if (frameListToWrite >= NUM_ISUB_FRAME_LISTS) { frameListToWrite -= NUM_ISUB_FRAME_LISTS; } self->WriteFrameList (frameListToWrite); } Exit: if (TRUE == self->shouldCloseStream) { #if DEBUGLOG IOLog ("++AppleiSubEngine[%p]::WriteHandler () - stopping: %d\n", self, self->numUSBFrameListsNotOutstanding); #endif self->numUSBFrameListsNotOutstanding++; if (self->numUSBFrameListsNotOutstanding == NUM_ISUB_FRAME_LISTS_TO_QUEUE) { #if DEBUGLOG IOLog ("iSub last write completed, closing streamInterface\n"); #endif if (NULL != self->thePipe) { self->thePipe->release (); self->thePipe = NULL; } self->streamInterface->close (self); self->streamOpened = FALSE; self->iSubUSBRunning = FALSE; } } // self->release (); return; }