//-------------------------------------------------------------------------------- // // File: AppleUSBAudioDevice.cpp // // Contains: Support for the USB Audio Class Control Interface. // This includes support for exporting device controls // to the Audio HAL such as Volume, Bass, Treble and // Mute. // // Future support will include parsing of the device // topology and exporting of all appropriate device // control functions through the Audio HAL. // // Technology: OS X // //-------------------------------------------------------------------------------- #include "AppleUSBAudioDevice.h" #define super IOAudioDevice OSDefineMetaClassAndStructors (AppleUSBAudioDevice, super) bool AppleUSBAudioDevice::init (OSDictionary * properties) { bool resultCode; debug2IOLog ("+AppleUSBAudioDevice[%p]::init ()\n", this); resultCode = FALSE; // Assume failure // get the IOAudioDevice generic initialization FailIf (FALSE == super::init (properties), Exit); resultCode = TRUE; Exit: debug2IOLog ("-AppleUSBAudioDevice[%p]::init ()\n", this); return resultCode; } void AppleUSBAudioDevice::free () { debug2IOLog ("+AppleUSBAudioDevice[%p]::free ()\n", this); #if DEBUG if (interfaceVendor && interfaceVendor->getRetainCount () == 0) Debugger ("interfaceVendor = 0"); if (interfaceProduct && interfaceProduct->getRetainCount () == 0) Debugger ("interfaceProduct = 0"); if (deviceReleaseNumber && deviceReleaseNumber->getRetainCount () == 0) Debugger ("deviceReleaseNumber = 0"); if (configurationValue && configurationValue->getRetainCount () == 0) Debugger ("configurationValue = 0"); if (interfaceNumber && interfaceNumber->getRetainCount () == 0) Debugger ("interfaceNumber = 0"); if (usbAudio && usbAudio->getRetainCount () == 0) Debugger ("usbAudio = 0"); #endif if (interfaceLock) { IORecursiveLockFree (interfaceLock); interfaceLock = NULL; } if (interfaceVendor) { interfaceVendor->release (); interfaceVendor = 0; } if (interfaceProduct) { interfaceProduct->release (); interfaceProduct = 0; } if (deviceReleaseNumber) { deviceReleaseNumber->release (); deviceReleaseNumber = 0; } if (configurationValue) { configurationValue->release (); configurationValue = 0; } if (interfaceNumber) { interfaceNumber->release (); interfaceNumber = 0; } if (usbAudio) { usbAudio->release (); usbAudio = NULL; } super::free (); debug2IOLog ("-AppleUSBAudioDevice[%p]::free ()\n", this); } bool AppleUSBAudioDevice::requestTerminate (IOService * provider, IOOptionBits options) { debug4IOLog ("+AppleUSBAudioDevice[%p]::requestTerminate (%p, %x)\n", this, provider, options); debug4IOLog ("-AppleUSBAudioDevice[%p]::requestTerminate (%p, %x)\n", this, provider, options); return TRUE; // it is OK to terminate us } bool AppleUSBAudioDevice::ControlsStreamNumber (UInt8 streamNumber) { UInt8 * streamNumbers; UInt8 numStreams; UInt8 index; bool doesControl; doesControl = FALSE; if (usbAudio) { usbAudio->GetControlledStreamNumbers (&streamNumbers, &numStreams); for (index = 0; index < numStreams; index++) { debug3IOLog ("Checking stream %d against controled stream %d\n", streamNumber, streamNumbers[index]); if (streamNumber == streamNumbers[index]) { doesControl = TRUE; break; // get out of for loop } } } return doesControl; } bool AppleUSBAudioDevice::start (IOService * provider) { OSObject * obj; UInt8 stringIndex; char string[kStringBufferSize]; IOReturn err; Boolean resultCode; UInt8 * streamNumbers; UInt8 numStreams; debug3IOLog ("+AppleUSBAudioDevice[%p]::start (%p)\n", this, provider); resultCode = FALSE; controlInterface = OSDynamicCast (IOUSBInterface, provider); FailIf (FALSE == controlInterface->open (this), Exit); debug2IOLog ("There are %d configurations on this device\n", controlInterface->GetDevice()->GetNumConfigurations ()); debug2IOLog ("Our control interface number is %d\n", controlInterface->GetInterfaceNumber ()); usbAudio = USBAudioConfigObject::create (controlInterface->GetDevice()->GetFullConfigurationDescriptor (0), controlInterface->GetInterfaceNumber ()); FailIf (NULL == usbAudio, Exit); // Check to make sure that the control interface we loaded against has audio streaming interfaces and not just MIDI. usbAudio->GetControlledStreamNumbers (&streamNumbers, &numStreams); debug2IOLog ("Num streams controlled = %d\n", numStreams); debug2IOLog ("GetNumStreamInterfaces = %d\n", usbAudio->GetNumStreamInterfaces ()); FailIf (0 == numStreams, Exit); // If this is an iSub, we need to not go any further because we don't support it in this driver // This will cause the driver to not load on any device that has _only_ a low frequency effect output terminal FailIf (usbAudio->GetNumOutputTerminals (0, 0) == 1 && usbAudio->GetIndexedOutputTerminalType (0, 0, 0) == OUTPUT_LOW_FREQUENCY_EFFECTS_SPEAKER, Exit); err = kIOReturnError; string[0] = 0; stringIndex = controlInterface->GetInterfaceStringIndex (); if (0 != stringIndex) { err = controlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize); } else { stringIndex = controlInterface->GetDevice()->GetProductStringIndex (); if (0 != stringIndex) { err = controlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize); } } if (0 == string[0] || kIOReturnSuccess != err) { strcpy (string, "Unknown USB Audio Device"); } setDeviceName (string); err = kIOReturnError; string[0] = 0; stringIndex = controlInterface->GetDevice()->GetManufacturerStringIndex (); if (0 != stringIndex) { err = controlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize); } if (0 == string[0] || kIOReturnSuccess != err) { strcpy (string, "Unknown Manufacturer"); } setManufacturerName (string); setDeviceTransportType (kIOAudioDeviceTransportTypeUSB); interfaceLock = IORecursiveLockAlloc (); FailIf (NULL == interfaceLock, Exit); if (obj = controlInterface->getProperty (kUSBVendorName)) { obj->retain (); interfaceVendor = obj; } if (obj = controlInterface->getProperty (kUSBProductName)) { obj->retain (); interfaceProduct = obj; } if (obj = controlInterface->getProperty (kUSBDeviceReleaseNumber)) { obj->retain (); deviceReleaseNumber = obj; } if (obj = controlInterface->getProperty (kUSBConfigurationValue)) { obj->retain(); configurationValue = obj; } if (obj = controlInterface->getProperty (kUSBInterfaceNumber)) { obj->retain (); interfaceNumber = obj; } FailIf (FALSE == super::start (provider), Exit); resultCode = TRUE; Exit: debug3IOLog ("-AppleUSBAudioDevice[%p]::start (%p)\n", this, provider); return resultCode; } IOReturn AppleUSBAudioDevice::performPowerStateChange (IOAudioDevicePowerState oldPowerState, IOAudioDevicePowerState newPowerState, UInt32 *microSecsUntilComplete) { IOReturn result; debug4IOLog ("+AppleUSBAudioDevice::performPowerStateChange (%d, %d, %p)\n", oldPowerState, newPowerState, microSecsUntilComplete); result = super::performPowerStateChange (oldPowerState, newPowerState, microSecsUntilComplete); if (oldPowerState == kIOAudioDeviceSleep) { debugIOLog ("Waking from sleep - flushing controls to the device.\n"); flushAudioControls (); } return result; } void AppleUSBAudioDevice::stop (IOService *provider) { bool shouldStop; debug5IOLog ("+AppleUSBAudioDevice[%p]::stop (%p) - audioEngines = %p - rc=%d\n", this, provider, audioEngines, getRetainCount()); shouldStop = TRUE; if (shouldStop) { performStop (provider); } debug2IOLog("-AppleUSBAudioDevice[%p]::stop ()\n", this); } // Return FALSE if you don't want PRAM updated on a volume change, TRUE if you want it updated. // Only update PRAM if we're on a Cube and the speakers are Cube, SoundSticks, or Mirconas (somethings). Boolean AppleUSBAudioDevice::ShouldUpdatePRAM (void) { const IORegistryPlane * usbPlane; IORegistryEntry * usbRegEntry; OSObject * obj; OSNumber * number; UInt16 productID; UInt16 vendorID; Boolean speakersGood; Boolean connectionGood; Boolean result; // Assume failure result = FALSE; speakersGood = FALSE; connectionGood = FALSE; // Make sure they're speakers that can support boot beep vendorID = controlInterface->GetDevice()->GetVendorID (); debug2IOLog ("+ ShouldUpdatePRAM\nspeaker's vendorID = 0x%x\n", vendorID); if (kIOUSBVendorIDAppleComputer == vendorID || kIOUSBVendorIDHaronKardon == vendorID || kIOUSBVendorMicronas == vendorID) { speakersGood = TRUE; } debug2IOLog ("speakersGood = %d\n", speakersGood); // They have to be plugged into a root hub or a hub in monitor that can support boot beep if (TRUE == speakersGood) { usbPlane = getPlane (kIOUSBPlane); FailIf (NULL == usbPlane, Exit); usbRegEntry = controlInterface->GetDevice()->getParentEntry (usbPlane); FailIf (NULL == usbRegEntry, Exit); obj = usbRegEntry->getProperty (kUSBVendorID); number = OSDynamicCast (OSNumber, obj); FailIf (NULL == number, Exit); vendorID = number->unsigned32BitValue (); debug2IOLog ("hub's vendorID = 0x%x\n", vendorID); if (kIOUSBVendorIDAppleComputer == vendorID) { obj = usbRegEntry->getProperty (kUSBDevicePropertyLocationID); number = OSDynamicCast (OSNumber, obj); FailIf (NULL == number, Exit); if (OSDynamicCast (IOUSBRootHubDevice, usbRegEntry)) { // It's connected to the root hub connectionGood = TRUE; debugIOLog ("Directly connected to the root hub\n"); } else { obj = usbRegEntry->getProperty (kUSBProductID); number = OSDynamicCast (OSNumber, obj); FailIf (NULL == number, Exit); productID = number->unsigned32BitValue (); debug2IOLog ("hub's productID = 0x%x\n", productID); if (kStudioDisplay15CRT == productID || kStudioDisplay17CRT == productID || kCinemaDisplay == productID || kStudioDisplay17FP == productID) { // It's connected to a good monitor connectionGood = TRUE; debugIOLog ("Connected to a capable monitor\n"); } } } } debug2IOLog ("connectionGood = %d\n", connectionGood); // And there CANNOT be a "sound" node in the device tree so that OF will boot beep through them if (TRUE == connectionGood && FALSE == FindSoundNode ()) { result = TRUE; } Exit: debug2IOLog ("- ShouldUpdatePRAM result = %d\n", result); return result; } Boolean AppleUSBAudioDevice::FindSoundNode (void) { const IORegistryPlane * dtPlane; IORegistryEntry * regEntry; IORegistryIterator * iterator; Boolean found; Boolean done; const char * name; found = FALSE; dtPlane = IORegistryEntry::getPlane (kIODeviceTreePlane); FailIf (NULL == dtPlane, Exit); iterator = IORegistryIterator::iterateOver (dtPlane, kIORegistryIterateRecursively); FailIf (NULL == iterator, Exit); done = FALSE; regEntry = iterator->getNextObject (); while (NULL != regEntry && FALSE == done) { name = regEntry->getName (); if (0 == strcmp (name, "mac-io")) { // This is where we want to start the search iterator->release (); // release the current iterator and make a new one rooted at "mac-io" iterator = IORegistryIterator::iterateOver (regEntry, dtPlane); done = TRUE; } regEntry = iterator->getNextObject (); } // Now the real search begins... regEntry = iterator->getNextObject (); while (NULL != regEntry && FALSE == found) { name = regEntry->getName (); if (0 == strcmp (name, "sound")) { found = TRUE; } regEntry = iterator->getNextObject (); } iterator->release (); Exit: return found; } void AppleUSBAudioDevice::performStop (IOService *provider) { debug3IOLog("+AppleUSBAudioDevice[%p]::performStop (%p)\n", this, provider); super::stop (provider); // call the IOAudioDevice generic stop routine if (controlInterface) { controlInterface->close (this); controlInterface = NULL; } debug3IOLog ("-AppleUSBAudioDevice[%p]::performStop (%p)\n", this, provider); } bool AppleUSBAudioDevice::terminate (IOOptionBits options) { bool shouldTerminate; bool result; shouldTerminate = TRUE; result = TRUE; debug3IOLog ("+AppleUSBAudioDevice[%p]::terminate () - rc=%d\n", this, getRetainCount ()); if (shouldTerminate) { result = super::terminate (options); } debug3IOLog ("-AppleUSBAudioDevice[%p]::terminate () - rc=%d\n", this, getRetainCount ()); return result; } bool AppleUSBAudioDevice::finalize (IOOptionBits options) { bool result; debug4IOLog ("+AppleUSBAudioDevice[%p]::finalize (%p) - rc=%d\n", this, options, getRetainCount ()); result = super::finalize (options); debug4IOLog ("-AppleUSBAudioDevice[%p]::finalize (%p) - rc=%d\n", this, options, getRetainCount ()); return result; } IOReturn AppleUSBAudioDevice::message (UInt32 type, IOService * provider, void * arg) { debug5IOLog ("+AppleUSBAudioDevice[%p]::message (0x%x, %p) - rc=%d\n", this, type, provider, getRetainCount ()); switch (type) { case kIOMessageServiceIsTerminated: case kIOMessageServiceIsRequestingClose: if (controlInterface != NULL && controlInterface == provider) { controlInterface->close (this); controlInterface = NULL; } default: ; } debug5IOLog ("-AppleUSBAudioDevice[%p]::message (0x%x, %p) - rc=%d\n", this, type, provider, getRetainCount ()); return kIOReturnSuccess; } IOReturn AppleUSBAudioDevice::createControlsForInterface (IOAudioEngine *audioEngine, UInt8 interfaceNum, UInt8 altInterfaceNum) { AppleUSBAudioEngine * usbAudioEngine; UInt8 channelNum; AppleUSBAudioLevelControl * speakerControl; AppleUSBAudioLevelControl * micControl; AppleUSBAudioLevelControl * pramControl; AppleUSBAudioMuteControl * muteControl; IOReturn result; UInt16 terminalType; UInt8 numTerminals; UInt8 featureUnitID; UInt8 terminalIndex; UInt8 controlInterfaceNum; Boolean shouldUpdatePRAM; debug4IOLog ("+AppleUSBAudioDevice[%p]::createControlsForInterface %d %d\n", this, interfaceNum, altInterfaceNum); result = kIOReturnError; terminatingDriver = FALSE; FailIf (NULL == controlInterface, Exit); usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, audioEngine); FailIf (NULL == usbAudioEngine, Exit); controlInterfaceNum = controlInterface->GetInterfaceNumber (); if (usbAudioEngine->getDirection () == kIOAudioStreamDirectionOutput) { // It's an output device (e.g., a speaker). featureUnitID = 0; // look for a streaming input terminal that's connected to a non-streaming output terminal // for the moment we'll just look for a feature unit connected to a non-streaming output terminal numTerminals = usbAudio->GetNumOutputTerminals (controlInterfaceNum, 0); debug2IOLog ("num output terminals = %d\n", numTerminals); for (terminalIndex = 0; terminalIndex < numTerminals; terminalIndex++) { debug2IOLog ("terminalIndex = 0x%X\n", terminalIndex); terminalType = usbAudio->GetIndexedOutputTerminalType (controlInterfaceNum, 0, terminalIndex); debug2IOLog ("terminalType = 0x%X\n", terminalType); if (terminalType != 0x0101) { // Only look for output terminals that output audio (things we can play to) featureUnitID = usbAudio->GetFeatureUnitIDConnectedToOutputTerminal (controlInterfaceNum, 0, usbAudio->GetIndexedOutputTerminalID (controlInterfaceNum, 0, terminalIndex)); debug2IOLog ("featureUnitID = %d\n", featureUnitID); break; // get out of for loop } } FailWithAction (0 == featureUnitID, result = kIOReturnSuccess, Exit); // There isn't a feature unit connected to this input terminal // The interface was opened in AppleUSBAudioEngine::initHardware controlInterface->SetAlternateInterface (this, kRootAlternateSetting); shouldUpdatePRAM = ShouldUpdatePRAM (); if (shouldUpdatePRAM) { pramControl = AppleUSBAudioLevelControl::create (featureUnitID, controlInterfaceNum, 0xff, 1, shouldUpdatePRAM, (USBDeviceRequest)&deviceRequest, this, kIOAudioLevelControlSubTypeVolume, kIOAudioControlUsageOutput); if (NULL != pramControl) { audioEngine->addDefaultAudioControl (pramControl); pramControl->release (); } } for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) { if (usbAudio->ChannelHasVolumeControl (controlInterfaceNum, 0, featureUnitID, channelNum)) { speakerControl = AppleUSBAudioLevelControl::create (featureUnitID, controlInterfaceNum, VOLUME_CONTROL, channelNum, shouldUpdatePRAM, (USBDeviceRequest)&deviceRequest, this, kIOAudioLevelControlSubTypeVolume, kIOAudioControlUsageOutput); if (NULL != speakerControl) { audioEngine->addDefaultAudioControl (speakerControl); speakerControl->release (); } else { debug2IOLog ("++AppleUSBAudioDevice::createControlsForInterface () - error creating volume control for channelNum %d\n", channelNum); } } } muteControl = NULL; for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) { if (usbAudio->ChannelHasMuteControl (controlInterfaceNum, 0, featureUnitID, channelNum)) { muteControl = AppleUSBAudioMuteControl::create (featureUnitID, controlInterfaceNum, channelNum, (USBDeviceRequest)&deviceRequest, this, kIOAudioControlUsageOutput); if (NULL != muteControl) { audioEngine->addDefaultAudioControl (muteControl); muteControl->release (); } else { debug2IOLog ("++AppleUSBAudioDevice::createControlsForInterface () - error creating mute control for channelNum %d\n", channelNum); } } } } else { // It's an input device (e.g., a microphone). featureUnitID = 0; // look for a streaming input terminal that's connected to a non-streaming output terminal // for the moment we'll just look for a feature unit connected to a non-streaming output terminal numTerminals = usbAudio->GetNumOutputTerminals (controlInterfaceNum, 0); debug2IOLog ("num output terminals = %d\n", numTerminals); for (terminalIndex = 0; terminalIndex < numTerminals; terminalIndex++) { debug2IOLog ("terminalIndex = 0x%X\n", terminalIndex); terminalType = usbAudio->GetIndexedOutputTerminalType (controlInterfaceNum, 0, terminalIndex); debug2IOLog ("terminalType = 0x%X\n", terminalType); if (terminalType == 0x0101) { // Only look for output terminals that output digital audio data (things we can record from) featureUnitID = usbAudio->GetFeatureUnitIDConnectedToOutputTerminal (controlInterfaceNum, 0, usbAudio->GetIndexedOutputTerminalID (controlInterfaceNum, 0, terminalIndex)); debug2IOLog ("featureUnitID = %d\n", featureUnitID); break; // get out of for loop } } FailWithAction (0 == featureUnitID, result = kIOReturnSuccess, Exit); // There isn't a feature unit connected to this output terminal // The interface was opened in AppleUSBAudioEngine::initHardware controlInterface->SetAlternateInterface (this, kRootAlternateSetting); debug2IOLog ("There are %d controls\n", usbAudio->GetNumChannels (interfaceNum, altInterfaceNum)); for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) { if (usbAudio->ChannelHasVolumeControl (controlInterfaceNum, 0, featureUnitID, channelNum)) { micControl = AppleUSBAudioLevelControl::create (featureUnitID, controlInterfaceNum, VOLUME_CONTROL, channelNum, FALSE, (USBDeviceRequest)&deviceRequest, this, kIOAudioLevelControlSubTypeVolume, kIOAudioControlUsageInput); if (NULL != micControl) { audioEngine->addDefaultAudioControl (micControl); micControl->release (); } else { debug2IOLog ("++AppleUSBAudioDevice::createControlsForInterface () - error creating volume control for channelNum %d\n", channelNum); } } else { debug2IOLog ("channel %d doesn't have a volume control\n", channelNum); } } muteControl = NULL; for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) { if (usbAudio->ChannelHasMuteControl (controlInterfaceNum, 0, featureUnitID, channelNum)) { muteControl = AppleUSBAudioMuteControl::create (featureUnitID, controlInterfaceNum, channelNum, (USBDeviceRequest)&deviceRequest, this, kIOAudioControlUsageInput); if (NULL != muteControl) { audioEngine->addDefaultAudioControl (muteControl); muteControl->release (); } else { debug2IOLog ("++AppleUSBAudioDevice::createControlsForInterface () - error creating mute control for channelNum %d\n", channelNum); } } } } Exit: return result; } IOReturn AppleUSBAudioDevice::activateAudioEngine (IOAudioEngine *audioEngine, bool shouldStartAudioEngine, UInt8 interfaceNum, UInt8 altInterfaceNum) { IOReturn result; debug5IOLog ("+AppleUSBAudioDevice[%p]::activateAudioEngine (%p, %d) - rc=%d\n", this, audioEngine, shouldStartAudioEngine, getRetainCount()); result = super::activateAudioEngine (audioEngine, shouldStartAudioEngine); debug4IOLog ("-AppleUSBAudioDevice[%p]::activateAudioEngine (%p) - rc=%d\n", this, audioEngine, getRetainCount ()); return result; } IOReturn AppleUSBAudioDevice::deviceRequest (IOUSBDevRequest *request, AppleUSBAudioDevice * self, IOUSBCompletion *completion) { IOReturn result; debug4IOLog ("+AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)\n", self, request, completion); result = kIOReturnSuccess; if (self->controlInterface && FALSE == self->terminatingDriver) { FailIf (NULL == self->interfaceLock, Exit); IORecursiveLockLock (self->interfaceLock); result = self->controlInterface->DeviceRequest (request, completion); IORecursiveLockUnlock (self->interfaceLock); } debug4IOLog ("-AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)\n", self, request, completion); Exit: return result; } bool AppleUSBAudioDevice::willTerminate (IOService * provider, IOOptionBits options) { debug3IOLog ("+AppleUSBAudioDevice[%p]::willTerminate (%p)\n", this, provider); if (controlInterface == provider) { terminatingDriver = TRUE; } debug2IOLog ("-AppleUSBAudioDevice[%p]::willTerminate\n", this); return super::willTerminate (provider, options); } #ifdef DEBUG void AppleUSBAudioDevice::retain() const { // debug3IOLog("AppleUSBAudioDevice(%p)::retain() - rc=%d\n", this, getRetainCount()); super::retain(); } void AppleUSBAudioDevice::release() const { // debug3IOLog("AppleUSBAudioDevice(%p)::release() - rc=%d\n", this, getRetainCount()); super::release(); } #endif