/*
 * Copyright (c) 1998-2006 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * 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@
 */
 
//--------------------------------------------------------------------------------

//
//	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
#define LOCALIZABLE				FALSE

OSDefineMetaClassAndStructors (AppleUSBAudioDevice, super)

void AppleUSBAudioDevice::free () {
    debugIOLog ("+ AppleUSBAudioDevice[%p]::free ()", this);

	if (NULL != mUpdateTimer) 
	{
		mUpdateTimer->cancelTimeout ();
		mUpdateTimer->release ();
		mUpdateTimer = NULL;
	}

    if (mInterfaceLock) 
	{
        IORecursiveLockFree (mInterfaceLock);
        mInterfaceLock = NULL;
    }

    if (mUSBAudioConfig)
	{
        mUSBAudioConfig->release ();
        mUSBAudioConfig = NULL;
    }

	if (mRegisteredEngines) 
	{
		mRegisteredEngines->release ();
		mRegisteredEngines = NULL;
	}
	
	if (mMonoControlsArray)
	{
		mMonoControlsArray->release ();
		mMonoControlsArray = NULL;
	}

	if (mRetryEQDownloadThread)
	{
		thread_call_free (mRetryEQDownloadThread);
	}
    super::free ();
    debugIOLog ("- AppleUSBAudioDevice[%p]::free ()", this);
}

bool AppleUSBAudioDevice::ControlsStreamNumber (UInt8 streamNumber) {
	UInt8 *							streamNumbers;
	UInt8							numStreams;
	UInt8							index;
	bool							doesControl;

	doesControl = FALSE;

	if (mUSBAudioConfig) 
	{
		mUSBAudioConfig->GetControlledStreamNumbers (&streamNumbers, &numStreams);
		for (index = 0; index < numStreams; index++) 
		{
			debugIOLog ("? AppleUSBAudioDevice[%p]::ControlsStreamNumber () - Checking stream %d against controled stream %d", this, streamNumber, streamNumbers[index]);
			if (streamNumber == streamNumbers[index]) 
			{
				doesControl = TRUE;
				break;				// get out of for loop
			}
		}
	}

	return doesControl;
}

bool AppleUSBAudioDevice::start (IOService * provider) {
	bool								result;

    debugIOLog ("+ AppleUSBAudioDevice[%p]::start (%p)", this, provider);
	result = FALSE;

	mControlInterface = OSDynamicCast (IOUSBInterface, provider);
	FailIf (FALSE == mControlInterface->open (this), Exit);

	mInitHardwareThread = thread_call_allocate ((thread_call_func_t)AppleUSBAudioDevice::initHardwareThread, (thread_call_param_t)this);
	FailIf (NULL == mInitHardwareThread, Exit);

    debugIOLog ("- AppleUSBAudioDevice[%p]::start (%p)", this, provider);

	result = super::start (provider);				// Causes our initHardware routine to be called.

Exit:
	return result;
}

bool AppleUSBAudioDevice::initHardware (IOService * provider) {
	bool								result;

	result = FALSE;

	FailIf (NULL == mInitHardwareThread, Exit);
	thread_call_enter1 (mInitHardwareThread, (void *)provider);

	result = TRUE;

Exit:
	return result;
}

void AppleUSBAudioDevice::initHardwareThread (AppleUSBAudioDevice * aua, void * provider) {
	IOCommandGate *						cg;
	IOReturn							result;

	FailIf (NULL == aua, Exit);
//	FailIf (TRUE == aua->mTerminating, Exit);	

	cg = aua->getCommandGate ();
	if (cg) 
	{
		result = cg->runAction (aua->initHardwareThreadAction, provider);
	}

Exit:
	return;
}

IOReturn AppleUSBAudioDevice::initHardwareThreadAction (OSObject * owner, void * provider, void * arg2, void * arg3, void * arg4) {
	AppleUSBAudioDevice *				aua;
	IOReturn							result;

	result = kIOReturnError;

	aua = (AppleUSBAudioDevice *)owner;
	FailIf (NULL == aua, Exit);

	result = aua->protectedInitHardware ((IOService *)provider);

Exit:
	return result;
}

IOReturn AppleUSBAudioDevice::protectedInitHardware (IOService * provider) {
	char							string[kStringBufferSize];
	UInt8							stringIndex;
	IOReturn						err;
    Boolean							resultCode;
	UInt8 *							streamNumbers;
	UInt8							numStreams;

	debugIOLog ("+ AppleUSBAudioDevice[%p]::start (%p)", this, provider);

	resultCode = FALSE;
	FailIf (NULL == mControlInterface, Exit);

	debugIOLog ("? AppleUSBAudioDevice[%p]::protectedInitHardware () - %d configuration(s) on this device. This control interface number is %d", this, mControlInterface->GetDevice()->GetNumConfigurations (), mControlInterface->GetInterfaceNumber ());
	mUSBAudioConfig = USBAudioConfigObject::create (mControlInterface->GetDevice()->GetFullConfigurationDescriptor (0), mControlInterface->GetInterfaceNumber ());
	FailIf (NULL == mUSBAudioConfig, Exit);

	mControlGraph = BuildConnectionGraph (mControlInterface->GetInterfaceNumber ());
	FailIf (NULL == mControlGraph, Exit);

	// Check to make sure that the control interface we loaded against has audio streaming interfaces and not just MIDI.
	mUSBAudioConfig->GetControlledStreamNumbers (&streamNumbers, &numStreams);
	debugIOLog ("? AppleUSBAudioDevice[%p]::protectedInitHardware () - %d controlled stream(s). %d stream interface(s).", this, numStreams, mUSBAudioConfig->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 (mUSBAudioConfig->GetNumOutputTerminals (0, 0) == 1 && mUSBAudioConfig->GetIndexedOutputTerminalType (0, 0, 0) == OUTPUT_LOW_FREQUENCY_EFFECTS_SPEAKER, Exit);

	err = kIOReturnError;
	string[0] = 0;
	stringIndex = mControlInterface->GetInterfaceStringIndex ();
	if (0 != stringIndex) 
	{
		err = mControlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
		// rdar://3886272 - This could fail on some devices, so we should retry
		if (kIOReturnSuccess != err)
		{
			// Try a regular retry once
			debugIOLog ("! AppleUSBAudioDevice[%p]::protectedInitHardware () - couldn't get string descriptor. Retrying ...", this);
			err = mControlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
		}
		if (kIOReturnSuccess != err)
		{
			// Reset the device and try one last time
			debugIOLog ("! AppleUSBAudioDevice[%p]::protectedInitHardware () - Still couldn't get string descriptor. Resetting device ...", this);
			mControlInterface->GetDevice()->ResetDevice();      // Doesn't matter if this fails
			IOSleep (50);										// Give the device 50 ms to get ready
			debugIOLog ("! AppleUSBAudioDevice[%p]::protectedInitHardware - Last retry ...", this);
			err = mControlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
		}
	} 
	else 
	{
		stringIndex = mControlInterface->GetDevice()->GetProductStringIndex ();
		if (0 != stringIndex) 
		{
			err = mControlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
			// rdar://3886272 - This could fail on some devices, so we should retry
			if (kIOReturnSuccess != err)
			{
				// Try a regular retry once
				debugIOLog ("! AppleUSBAudioDevice[%p]::protectedInitHardware () - couldn't get string descriptor. Retrying ...", this);
				err = mControlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
			}
			if (kIOReturnSuccess != err)
			{
				// Reset the device and try one last time
				debugIOLog ("! AppleUSBAudioDevice[%p]::protectedInitHardware () - Still couldn't get string descriptor. Resetting device ...", this);
				mControlInterface->GetDevice()->ResetDevice();      // Doesn't matter if this fails
				IOSleep (50);										// Give the device 50 ms to get ready
				debugIOLog ("! AppleUSBAudioDevice[%p]::protectedInitHardware () - Last retry ...", this);
				err = mControlInterface->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 = mControlInterface->GetDevice()->GetManufacturerStringIndex ();
	if (0 != stringIndex) 
	{
		err = mControlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
	}

	if (0 == string[0] || kIOReturnSuccess != err) 
	{
		strcpy (string, "Unknown Manufacturer");
	}

	setManufacturerName (string);
	setDeviceTransportType (kIOAudioDeviceTransportTypeUSB);
	#if LOCALIZABLE
	setProperty (kIOAudioDeviceLocalizedBundleKey, "AppleUSBAudio.kext");
	#endif

	mInterfaceLock = IORecursiveLockAlloc ();
	FailIf (NULL == mInterfaceLock, Exit);

	resultCode = super::initHardware (provider);
	
	// Start the anchored time stamp timer if necessary
	
	// Initialize time stamp member variables
	mLastUSBFrame = 0ull;
	mLastWallTime_nanos = 0ull;
	mAnchorResetCount = kRefreshCount;
	mNewReferenceUSBFrame = 0ull;
	
	// Initialize mWallTimePerUSBCycle
	mWallTimePerUSBCycle = 1000000ull * kWallTimeExtraPrecision;
	
	// [rdar://] This member variable keeps track an engine that has had a catastrophic failure that requires an emergency format change.
	mFailingAudioEngine = NULL;

	// Register and start update timer
	mUpdateTimer = IOTimerEventSource::timerEventSource (this, TimerAction);
	FailIf (NULL == mUpdateTimer, Exit);
	workLoop->addEventSource (mUpdateTimer);
	debugIOLog ("? AppleUSBAudioDevice[%p]::protectedInitHardware () - starting rate timer");
	TimerAction ( this, mUpdateTimer);
	
	// Added for rdar://3993906 . This forces matchPropertyTable () to run again.
	IOService::registerService();

Exit:
	debugIOLog ("- AppleUSBAudioDevice[%p]::start (%p)", this, provider);

	return TRUE;
}

IOReturn AppleUSBAudioDevice::performPowerStateChange (IOAudioDevicePowerState oldPowerState, IOAudioDevicePowerState newPowerState, UInt32 *microSecsUntilComplete) {
	IOReturn						result;

	// debugIOLog ("+ AppleUSBAudioDevice[%p]::performPowerStateChange (%d, %d, %p)", this, oldPowerState, newPowerState, microSecsUntilComplete);

	result = super::performPowerStateChange (oldPowerState, newPowerState, microSecsUntilComplete);

	// We need to stop the time stamp rate timer now
	if	(		(mUpdateTimer)
			&&	(kIOAudioDeviceSleep == newPowerState))
	{
		// Stop the timer and reset the anchor.
		debugIOLog ("? AppleUSBAudioDevice[%p]::performPowerStateChange () - Going to sleep - stopping the rate timer.", this);
		mUpdateTimer->cancelTimeout ();
		// mUpdateTimer->disable();
		
		// The frame/time correlation isn't preserved across sleep/wake
		mNewReferenceUSBFrame = 0ull;		
		mLastUSBFrame = 0ull;
		( * (UInt64 *) &mNewReferenceWallTime) = 0ull;
		mLastWallTime_nanos = 0ull;
	}
	
	if (oldPowerState == kIOAudioDeviceSleep) 
	{
		// A new anchor should be taken at the first opportunity. The timer action will handle this with the following instruction.
		mAnchorResetCount = kRefreshCount;
		
		// [rdar://4380545] We need to reset the wall time per USB cycle because the frame number could become invalid entering sleep.
		mWallTimePerUSBCycle = 1000000ull * kWallTimeExtraPrecision;
		
		#if RESETAFTERSLEEP
		// [rdar://4234453] Reset the device after waking from sleep just to be safe.
		FailIf (NULL == mControlInterface, Exit);
		debugIOLog ("? AppleUSBAudioDevice[%p]::performPowerStateChange () - Resetting port after wake from sleep ...", this);
		mControlInterface->GetDevice()->ResetDevice();
		IOSleep (10);
		#endif
		
		// We need to restart the time stamp rate timer now
		debugIOLog ("? AppleUSBAudioDevice[%p]::performPowerStateChange () - Waking from sleep - restarting the rate timer.", this);
		TimerAction ( this, mUpdateTimer);
		
		debugIOLog ("? AppleUSBAudioDevice[%p]::performPowerStateChange () - Flushing controls to the device ...", this);
		flushAudioControls ();
	}

Exit:
	// debugIOLog ("- AppleUSBAudioDevice[%p]::performPowerStateChange (%d, %d, %p)", this, oldPowerState, newPowerState, microSecsUntilComplete);
	return result;
}

void AppleUSBAudioDevice::stop (IOService *provider) {

	debugIOLog ("+ AppleUSBAudioDevice[%p]::stop (%p) - audioEngines = %p - rc=%d", this, provider, audioEngines, getRetainCount());

	if (mUpdateTimer)
	{
		// Stop the rate calculation timer
		debugIOLog ("? AppleUSBAudioDevice[%p]::stop () - Cancelling time stamp rate timer ...", this);
		mUpdateTimer->cancelTimeout ();
		mUpdateTimer->disable();
	}
	
	super::stop (provider);  // call the IOAudioDevice generic stop routine

    if (mControlInterface) 
	{
        mControlInterface->close (this);
        mControlInterface = NULL;
    }
	
	if (mInitHardwareThread)
	{
		thread_call_cancel (mInitHardwareThread);
		thread_call_free (mInitHardwareThread);
		mInitHardwareThread = NULL;
	}

	debugIOLog("- AppleUSBAudioDevice[%p]::stop ()", 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;
	FailIf (NULL == mControlInterface, Exit);

	// Make sure they're speakers that can support boot beep
	vendorID = mControlInterface->GetDevice()->GetVendorID ();
	debugIOLog ("? AppleUSBAudioDevice[%p]::ShouldUpdatePRAM () - speaker's vendorID = 0x%x", this, vendorID);
	if (kIOUSBVendorIDAppleComputer == vendorID || kIOUSBVendorIDHarmonKardon == vendorID || kIOUSBVendorMicronas == vendorID) 
	{
		speakersGood = TRUE;
	}
	// debugIOLog ("speakersGood = %d", 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 = mControlInterface->GetDevice()->getParentEntry (usbPlane);
		FailIf (NULL == usbRegEntry, Exit);

		obj = usbRegEntry->getProperty (kUSBVendorID);
		number = OSDynamicCast (OSNumber, obj);
		FailIf (NULL == number, Exit);

		vendorID = number->unsigned32BitValue ();
		// debugIOLog ("hub's vendorID = 0x%x", 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 ("? AppleUSBAudioDevice[%p]::ShouldUpdatePRAM () - Directly connected to the root hub", this);
			} 
			else 
			{
				obj = usbRegEntry->getProperty (kUSBProductID);
				number = OSDynamicCast (OSNumber, obj);
				FailIf (NULL == number, Exit);

				productID = number->unsigned32BitValue ();
				// debugIOLog ("hub's productID = 0x%x", 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");
				}
			}
		}
	}
	// debugIOLog ("connectionGood = %d", 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:
	debugIOLog ("? AppleUSBAudioDevice[%p]::ShouldUpdatePRAM () - result = %d", this, 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;
}

IOReturn AppleUSBAudioDevice::message (UInt32 type, IOService * provider, void * arg) {
	debugIOLog ("+ AppleUSBAudioDevice[%p]::message (0x%x, %p) - rc=%d", this, type, provider, getRetainCount ());

	switch (type) 
	{
		case kIOMessageServiceIsTerminated:
		case kIOMessageServiceIsRequestingClose:
			if (mControlInterface != NULL && mControlInterface == provider) 
			{
				mControlInterface->close (this);
				mControlInterface = NULL;
			}
		default:
			;
	}

	debugIOLog ("- AppleUSBAudioDevice[%p]::message (0x%x, %p) - rc=%d", this, type, provider, getRetainCount ());
	return kIOReturnSuccess;
}

// added for rdar://4168019 . Returns the device speed (high, full, or low).
UInt8 AppleUSBAudioDevice::getDeviceSpeed () {
	IOUSBDevice *			usbDevice;
	UInt8					speed = 0;
	
	FailIf (NULL == mControlInterface, Exit);
	usbDevice = OSDynamicCast (IOUSBDevice, mControlInterface->GetDevice());
	speed = usbDevice->GetSpeed ();
	#if DEBUGLOGGING
	switch (speed)
	{
		case kUSBDeviceSpeedLow:
			debugIOLog ("? AppleUSBAudioDevice[%p]::getDeviceSpeed () = kUSBDeviceSpeedLow", this);
			break;
		case kUSBDeviceSpeedFull:
			debugIOLog ("? AppleUSBAudioDevice[%p]::getDeviceSpeed () = kUSBDeviceSpeedFull", this);
			break;
		case kUSBDeviceSpeedHigh:
			debugIOLog ("? AppleUSBAudioDevice[%p]::getDeviceSpeed () = kUSBDeviceSpeedHigh", this);
			break;
		default:
			debugIOLog ("? AppleUSBAudioDevice[%p]::getDeviceSpeed () = %d (UNKNOWN)", this, speed);
	}
	#endif
	
Exit:
	return speed;
}

// added for rdar://3959606 . Detects whether this is a full speed device plugged into a high speed hub.
bool AppleUSBAudioDevice::detectSplitTransactions () {
	IOUSBDevice *			usbDevice;
	const IORegistryPlane *	usbPlane = getPlane (kIOUSBPlane);
	IORegistryEntry *		currentEntry;
	UInt8					speed;
	bool					canStop = false;
	bool					splitTransactions = false;
	
	FailIf (NULL == mControlInterface, Exit);
	usbDevice = OSDynamicCast (IOUSBDevice, mControlInterface->GetDevice());
	FailIf (NULL == usbDevice, Exit);
	currentEntry = OSDynamicCast (IORegistryEntry, usbDevice);
	
	if (kUSBDeviceSpeedHigh == usbDevice->GetSpeed ())
	{
		debugIOLog ("? AppleUSBAudioDevice::detectSplitTransactions () - This is a high speed device, so there are no split transactions.");
		splitTransactions = false;
		canStop = true;
	}
	
	while	(		(!canStop)
				&&	(currentEntry)
				&&	(usbDevice))
	{
		// Searching for a high speed hub. Since it is not possible to run at high speed with a full speed
		// hub closer to the root hub in the chain, we can stop when we find the first high speed hub.
		
		speed = usbDevice->GetSpeed ();
		if (kUSBDeviceSpeedHigh == speed)
		{
			// Must be connected via USB 2.0 hub
			debugIOLog ("? AppleUSBAudioDevice::detectSplitTransactions () = true");
			splitTransactions = true;
			canStop = true;
		}
		else
		{
			// Get parent in USB plane
			currentEntry = OSDynamicCast (IORegistryEntry, currentEntry->getParentEntry (usbPlane));
			
			// If the current registry entry is not a device, this will make usbDevice NULL and exit the loop
			usbDevice = OSDynamicCast (IOUSBDevice, currentEntry);
		}
	} // end while
	
Exit:
	return splitTransactions;
}

Boolean AppleUSBAudioDevice::checkForUHCI () {
	Boolean					result = FALSE;
	IOUSBDevice *			usbDevice;
	const IORegistryPlane *	servicePlane = getPlane (kIOServicePlane);
	IOService *				currentEntry;
	IORegistryEntry *		parentEntry;
	char					serviceName[20];

	FailIf (NULL == mControlInterface, Exit);
	usbDevice = OSDynamicCast (IOUSBDevice, mControlInterface->GetDevice());
	FailIf (NULL == usbDevice, Exit);
	parentEntry = usbDevice->getParentEntry (servicePlane);
	FailIf (NULL == parentEntry, Exit);
	currentEntry = OSDynamicCast (IOService, parentEntry);
	FailIf (NULL == currentEntry, Exit);
	strcpy (serviceName, currentEntry->getName (servicePlane));

	while (    (currentEntry)
			&& strcmp (serviceName, "AppleUSBUHCI")
			&& strcmp (serviceName, "AppleUSBOHCI")
			&& strcmp (serviceName, "AppleUSBEHCI"))
	{
		// Searching for the root hub type. We only expect one of three types, so we can stop when we have found any of them.
		// The types are AppleUSBUHCI, AppleUSBOHCI, and AppleUSBEHCI.
		
		// Get parent in IOService plane
		parentEntry = currentEntry->getParentEntry (servicePlane);
		FailIf (NULL == parentEntry, Exit);
		currentEntry = OSDynamicCast (IOService, parentEntry);
		if (currentEntry)
		{
			strcpy (serviceName, currentEntry->getName (servicePlane));
		}
	} // end while
	FailIf (NULL == currentEntry, Exit);
	if (!strcmp (serviceName, "AppleUSBUHCI"))
	{
		// We are on a UHCI connection
		debugIOLog ("? AppleUSBAudioDevice::checkForUHCI () - UHCI connection detected!\n");
		result = TRUE;
	}
Exit:
	return result;
}

SInt32 AppleUSBAudioDevice::getEngineInfoIndex (AppleUSBAudioEngine * inAudioEngine) {
	OSDictionary *						engineInfo;
	AppleUSBAudioEngine *				usbAudioEngine;
	UInt16								engineIndex;
	SInt32								returnIndex;

	returnIndex = -1;
	if (mRegisteredEngines) 
	{
		for (engineIndex = 0; engineIndex < mRegisteredEngines->getCount (); engineIndex++) 
		{
			engineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (engineIndex));
			if (engineInfo) 
			{
				usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, engineInfo->getObject (kEngine));
				if (inAudioEngine == usbAudioEngine) 
				{
					returnIndex = engineIndex;
					break;		// Get out of for loop with correct index
				}
			}
		}
	}

	return returnIndex;
}

IOReturn AppleUSBAudioDevice::doControlStuff (IOAudioEngine *audioEngine, UInt8 interfaceNum, UInt8 altSettingNum) {
	AppleUSBAudioEngine *				usbAudioEngine;
	IOAudioSelectorControl *			inputSelector;
	OSArray *							arrayOfPathsFromOutputTerminal;
	OSArray *							aPath;
	OSArray *							playThroughPaths;
	OSNumber *							theUnitIDNum;
	OSNumber *							number;
	OSDictionary *						engineInfo;
	IOReturn							result;
	UInt32								numUnitsInPath, unitIndexInPath /*, pathIndex */;
	UInt32								outputTerminalIndex;
	UInt32								numOutputTerminalArrays;
	UInt32								numPathsFromOutputTerminal;
	UInt32								numOutputTerminals;
	UInt32								pathsToOutputTerminalN;
	UInt32								selection;
	SInt32								engineIndex;
	SInt32								oldEngineIndex;
	UInt8								selectorUnitID;
	UInt8								featureUnitID;
	UInt8								controlInterfaceNum;
	UInt8								unitID;
	UInt8								outputTerminalID;
	Boolean								done;

	debugIOLog ("+ AppleUSBAudioDevice::doControlStuff(0x%x, %d, %d)", audioEngine, interfaceNum, altSettingNum);
	result = kIOReturnError;
	inputSelector = NULL;
	done = FALSE;

    usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, audioEngine);
    FailIf (NULL == usbAudioEngine, Exit);
	FailIf (NULL == mControlInterface, Exit);
	debugIOLog ("? AppleUSBAudioDevice::doControlStuff () - This usbAudioEngine = %p", usbAudioEngine);

	if (NULL == mRegisteredEngines) 
	{
		mRegisteredEngines = OSArray::withCapacity (1);
		FailIf (NULL == mRegisteredEngines, Exit);
	}

	engineInfo = OSDictionary::withCapacity (1);
	FailIf (NULL == engineInfo, Exit);
	engineInfo->setObject (kEngine, usbAudioEngine);
	number = OSNumber::withNumber (interfaceNum, 8);
	engineInfo->setObject (kInterface, number);
	number->release ();
	number = OSNumber::withNumber (altSettingNum, 8);
	engineInfo->setObject (kAltSetting, number);
	number->release ();
	
	// [rdar://4102789] Be sure to preserve the integrity of mRegisteredEngines. It is vital to perform emergency format changes.
	oldEngineIndex = getEngineInfoIndex (usbAudioEngine);
	if (-1 != oldEngineIndex)
	{
		// This engine already has information stored. We should replace it.
		mRegisteredEngines->replaceObject (oldEngineIndex, engineInfo);
	}
	else
	{
		mRegisteredEngines->setObject (engineInfo);
	}
	engineInfo->release ();

	featureUnitID = 0;
	selectorUnitID = 0;
	outputTerminalID = 0;
	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();

	numOutputTerminals = mUSBAudioConfig->GetNumOutputTerminals (controlInterfaceNum, 0);

	if (usbAudioEngine->getDirection () == kIOAudioStreamDirectionOutput) {
		for (outputTerminalIndex = 0; outputTerminalIndex < numOutputTerminals && FALSE == done; outputTerminalIndex++) 
		{
			if (mUSBAudioConfig->GetIndexedOutputTerminalType (controlInterfaceNum, 0, outputTerminalIndex) != 0x101) 
			{
				outputTerminalID = mUSBAudioConfig->GetIndexedOutputTerminalID (controlInterfaceNum, 0, outputTerminalIndex);

				numOutputTerminalArrays = mControlGraph->getCount ();
				for (pathsToOutputTerminalN = 0; pathsToOutputTerminalN < numOutputTerminalArrays; pathsToOutputTerminalN++) 
				{
					arrayOfPathsFromOutputTerminal = OSDynamicCast (OSArray, mControlGraph->getObject (pathsToOutputTerminalN));
					FailIf (NULL == arrayOfPathsFromOutputTerminal, Exit);
					aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (0));
					FailIf (NULL == aPath, Exit);
					theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (0));
					FailIf (NULL == theUnitIDNum, Exit);
					unitID = theUnitIDNum->unsigned8BitValue ();
		
					if (unitID == outputTerminalID) 
					{
						featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageOutput, interfaceNum, altSettingNum, kVolumeControl);
						if (featureUnitID) 
						{
							// Create the output gain controls
							debugIOLog("? AppleUSBAudioDevice::doControlStuff () - Creating output gain controls");
							addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsageOutput);
							featureUnitID = 0;
						}
						featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageOutput, interfaceNum, altSettingNum, kMuteControl);
						if (featureUnitID) 
						{
							addMuteControl (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsageOutput);
							featureUnitID = 0;
							done = TRUE;
						}
					}
				} // for pathsToOutputTerminalN
			} // if (unitID == outputTerminalID
		} // for outputTerminalIndex
	} 
	else 
	{		// direction == kIOAudioStreamDirectionInput
		for (outputTerminalIndex = 0; outputTerminalIndex < numOutputTerminals; outputTerminalIndex++) 
		{
			if (mUSBAudioConfig->GetIndexedOutputTerminalType (controlInterfaceNum, 0, outputTerminalIndex) == 0x101) 
			{
				outputTerminalID = mUSBAudioConfig->GetIndexedOutputTerminalID (controlInterfaceNum, 0, outputTerminalIndex);
				break;		// Found the (hopefully only) streaming output terminal we're looking for
			}
		}

		numOutputTerminalArrays = mControlGraph->getCount ();
		for (pathsToOutputTerminalN = 0; pathsToOutputTerminalN < numOutputTerminalArrays; pathsToOutputTerminalN++) 
		{
			arrayOfPathsFromOutputTerminal = OSDynamicCast (OSArray, mControlGraph->getObject (pathsToOutputTerminalN));
			FailIf (NULL == arrayOfPathsFromOutputTerminal, Exit);
			aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (0));
			FailIf (NULL == aPath, Exit);
			theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (0));
			FailIf (NULL == theUnitIDNum, Exit);
			unitID = theUnitIDNum->unsigned8BitValue ();

			if (unitID == outputTerminalID) 
			{
				// Check for a playthrough path that would require a playthrough control
				theUnitIDNum = OSDynamicCast (OSNumber, aPath->getLastObject ());
				FailIf (NULL == theUnitIDNum, Exit);
				unitID = theUnitIDNum->unsigned8BitValue ();
				playThroughPaths = getPlaythroughPaths ();
				if (playThroughPaths) 
				{
					doPlaythroughSetup (usbAudioEngine, playThroughPaths, interfaceNum, altSettingNum);
					playThroughPaths->release ();
				}

				numPathsFromOutputTerminal = arrayOfPathsFromOutputTerminal->getCount ();
				if (numPathsFromOutputTerminal > 1 && mUSBAudioConfig->GetNumSelectorUnits (controlInterfaceNum, 0)) 
				{
					// Found the array of paths that lead to our streaming output terminal
					numUnitsInPath = aPath->getCount ();
					for (unitIndexInPath = 1; unitIndexInPath < numUnitsInPath; unitIndexInPath++) 
					{
						theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (unitIndexInPath));
						FailIf (NULL == theUnitIDNum, Exit);
						unitID = theUnitIDNum->unsigned8BitValue ();
						if (SELECTOR_UNIT == mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID)) 
						{
							if (kIOReturnSuccess == setSelectorSetting (unitID, 1)) 
							{
								selectorUnitID = unitID;
								engineIndex = getEngineInfoIndex (usbAudioEngine);
								if (-1 != engineIndex) 
								{
									selection = (0xFF000000 & (pathsToOutputTerminalN << 24)) | (0x00FF0000 & (0 << 16)) | (0x0000FF00 & (selectorUnitID << 8)) | 1;
									inputSelector = IOAudioSelectorControl::createInputSelector (selection, kIOAudioControlChannelIDAll, 0, engineIndex);
									FailIf (NULL == inputSelector, Exit);
									inputSelector->setValueChangeHandler (controlChangedHandler, this);
									usbAudioEngine->addDefaultAudioControl (inputSelector);
									featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kVolumeControl);
									if (featureUnitID) 
									{
										// Create the input gain controls
										debugIOLog("? AppleUSBAudioDevice::doControlStuff () - Creating input gain controls");
										addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsageInput);
										featureUnitID = 0;
									}
									featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kMuteControl);
									if (featureUnitID) 
									{
										addMuteControl (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsageInput);
										featureUnitID = 0;
									}
								} // if (-1 != engineIndex)
							} // if (kIOReturnSuccess == setSelectorSetting (unitID, 1))
							break;		// Get out of unitIndexInPath for loop
						} // if (SELECTOR_UNIT == mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID))
					} // for unitIndexInPath

					if (NULL != inputSelector) 
					{
						addSelectorSourcesToSelectorControl (inputSelector, arrayOfPathsFromOutputTerminal, pathsToOutputTerminalN, unitIndexInPath);
						inputSelector->release ();
					} 
					else 
					{
						// There are no programmable selectors, so just find the one feature unit, if it exists.
						featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kVolumeControl);
						if (featureUnitID) 
						{
							// Create the playthrough volume controls
							addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsageInput);
							featureUnitID = 0;
						}
					}
				} // if (numPathsFromOutputTerminal > 1 && mUSBAudioConfig->GetNumSelectorUnits (controlInterfaceNum, 0))
				else 
				{
					// There are no selectors, so just find the one feature unit, if it exists.
					featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kVolumeControl);
					if (featureUnitID) 
					{
						// Create the playthrough volume controls
						addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsageInput);
						featureUnitID = 0;
					}
				}
				break;		// Get out of pathsToOutputTerminalN for loop
			}
		}
	}

	result = kIOReturnSuccess;

Exit:
	debugIOLog ("- AppleUSBAudioDevice::doControlStuff(0x%x, %d, %d)", audioEngine, interfaceNum, altSettingNum);
	return result;
}

IOReturn AppleUSBAudioDevice::doPlaythroughSetup (AppleUSBAudioEngine * usbAudioEngine, OSArray * playThroughPaths, UInt8 interfaceNum, UInt8 altSettingNum) {
	OSArray *							aPath;
	OSNumber *							theUnitIDNum;
	OSString *							nameString;
	IOAudioSelectorControl *			playThroughSelector;
	OSDictionary *						engineInfo;
	UInt32								numPlayThroughPaths;
	UInt32								pathIndex;
	SInt32								engineInfoIndex;
	UInt8								channelNum;
	UInt8								featureUnitID;
	UInt8								inputTerminalID;
	UInt8								controlInterfaceNum;
	IOReturn							result;

	result = kIOReturnError;
	FailIf (NULL == mControlInterface, Exit);

	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();

	engineInfoIndex = getEngineInfoIndex (usbAudioEngine);
	FailIf (-1 == engineInfoIndex, Exit);

	engineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (engineInfoIndex));
	FailIf (NULL == engineInfo, Exit);
	engineInfo->setObject (kPassThruPathsArray, playThroughPaths);

	numPlayThroughPaths = playThroughPaths->getCount ();
	if (numPlayThroughPaths > 1) 
	{
		// Create a virtual selector to manipulate the mutes on the feature units to toggle through playthrough sources.
		playThroughSelector = IOAudioSelectorControl::create (0, kIOAudioControlChannelIDAll, 0, engineInfoIndex, kIOAudioSelectorControlSubTypeInput, kIOAudioControlUsagePassThru);
		FailIf (NULL == playThroughSelector, Exit);
		playThroughSelector->setValueChangeHandler (controlChangedHandler, this);
		usbAudioEngine->addDefaultAudioControl (playThroughSelector);

		for (pathIndex = 0; pathIndex < numPlayThroughPaths; pathIndex++) 
		{
			aPath = OSDynamicCast (OSArray, playThroughPaths->getObject (pathIndex));
			FailIf (NULL == aPath, Exit);
			featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kVolumeControl);
			if (featureUnitID) 
			{
				theUnitIDNum = OSDynamicCast (OSNumber, aPath->getLastObject ());
				FailIf (NULL == theUnitIDNum, Exit);
				inputTerminalID = theUnitIDNum->unsigned8BitValue ();
				nameString = OSString::withCString (TerminalTypeString (mUSBAudioConfig->GetInputTerminalType (controlInterfaceNum, 0, inputTerminalID)));
				FailIf (NULL == nameString, Exit);
				playThroughSelector->addAvailableSelection (pathIndex, nameString);
				nameString->release ();
				for (channelNum = 0; channelNum < mUSBAudioConfig->GetNumControls (controlInterfaceNum, 0, featureUnitID); channelNum++) 
				{
					setCurVolume (featureUnitID, channelNum, 0);
					setCurMute (featureUnitID, channelNum, 0);
				}
			}
		}
		aPath = OSDynamicCast (OSArray, playThroughPaths->getObject (0));
		FailIf (NULL == aPath, Exit);
		featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kVolumeControl);
		if (featureUnitID) 
		{
			// Create the playthrough volume controls
			addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsagePassThru);
		}
		featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsageInput, interfaceNum, altSettingNum, kMuteControl);
		if (featureUnitID) 
		{
			addMuteControl (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsagePassThru);
		}
		result = kIOReturnSuccess;
	} // if (numPlayThroughPaths > 1)
	else 
	{
		// Only one playthrough path, so just publish its volume and mute controls.
		aPath = OSDynamicCast (OSArray, playThroughPaths->getObject (0));
		FailIf (NULL == aPath, Exit);
		featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsagePassThru, interfaceNum, altSettingNum, kVolumeControl);
		if (featureUnitID) 
		{
			// Create the playthrough volume controls
			addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsagePassThru);
		}
		featureUnitID = getBestFeatureUnitInPath (aPath, kIOAudioControlUsagePassThru, interfaceNum, altSettingNum, kMuteControl);
		if (featureUnitID) 
		{
			addMuteControl (usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, kIOAudioControlUsagePassThru);
		}
		result = kIOReturnSuccess;
	}

Exit:
	return result;
}

IOReturn AppleUSBAudioDevice::addSelectorSourcesToSelectorControl (IOAudioSelectorControl * theSelectorControl, OSArray * arrayOfPathsFromOutputTerminal, UInt32 pathsToOutputTerminalN, UInt8 selectorIndex) {
	OSArray *							aPath;
	OSNumber *							theUnitIDNum;
	OSString *							nameString;
	UInt32								selectorSourceIndex;
	UInt32								pathIndex;
	UInt32								selection;
	UInt8								numSelectorSources;
	UInt8								selectorID;
	UInt8								controlInterfaceNum;

	FailIf (NULL == mControlInterface, Exit);
	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();

	aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (0));
	FailIf (NULL == aPath, Exit);
	theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (selectorIndex));
	FailIf (NULL == theUnitIDNum, Exit);
	selectorID = theUnitIDNum->unsigned8BitValue ();

	pathIndex = 0;
	numSelectorSources = mUSBAudioConfig->GetNumSources (controlInterfaceNum, 0, selectorID);
	for (selectorSourceIndex = 0; selectorSourceIndex < numSelectorSources; selectorSourceIndex++) 
	{
		nameString = getNameForPath (arrayOfPathsFromOutputTerminal, &pathIndex, selectorIndex + 1);
		if (NULL != nameString) 
		{
			selection = (0xFF000000 & (pathsToOutputTerminalN << 24)) | (0x00FF0000 & ((pathIndex - 1) << 16)) | (0x0000FF00 & (selectorID << 8)) | (0x000000FF & (selectorSourceIndex + 1));
			theSelectorControl->addAvailableSelection (selection, nameString);
			nameString->release ();
		}
	}

Exit:
	return kIOReturnSuccess;
}

// Starting point is the array index of the element after the selector unit.
OSString * AppleUSBAudioDevice::getNameForPath (OSArray * arrayOfPathsFromOutputTerminal, UInt32 * pathIndex, UInt8 startingPoint) {
	OSString *							theString;
	OSString *							tempString;
	OSArray *							aPath;
	OSNumber *							theUnitIDNum;
	UInt32								numElementsInPath;
	UInt32								elementIndex;
	UInt8								unitID;
	UInt8								subType;
	UInt8								controlInterfaceNum;
	Boolean								done;

	done = FALSE;
	theString = NULL;
	FailIf (NULL == mControlInterface, Exit);

	aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (*pathIndex));
	FailIf (NULL == aPath, Exit);

	numElementsInPath = aPath->getCount ();
	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();
	for (elementIndex = startingPoint; elementIndex < numElementsInPath && FALSE == done; elementIndex++) 
	{
		theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (elementIndex));
		FailIf (NULL == theUnitIDNum, Exit);
		unitID = theUnitIDNum->unsigned8BitValue ();
		subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID);
		switch (subType) 
		{
			case INPUT_TERMINAL:
				tempString = OSString::withCString (TerminalTypeString (mUSBAudioConfig->GetInputTerminalType (controlInterfaceNum, 0, unitID)));
				FailIf (NULL == tempString, Exit);
				if (!tempString->isEqualTo ("USB streaming")) 
				{
					theString = OSString::withString (tempString);
				}
				tempString->release ();
				(*pathIndex)++;
				break;
			case MIXER_UNIT:
				theString = getNameForMixerPath (arrayOfPathsFromOutputTerminal, pathIndex, elementIndex);
				done = TRUE;
				break;
		}
	}

Exit:
	return theString;
}

// Starting point is the array index of the mixer unit.
OSString * AppleUSBAudioDevice::getNameForMixerPath (OSArray * arrayOfPathsFromOutputTerminal, UInt32 * pathIndex, UInt8 startingPoint) {
	char								string[255];
	OSString *							theString;
	OSString *							tempString;
	OSArray *							aPath;
	OSNumber *							theUnitIDNum;
	UInt32								numElementsInPath;
	UInt32								mixerSourceIndex;
	UInt32								elementIndex;
	UInt8								numMixerSources;
	UInt8								unitID;
	UInt8								subType;
	UInt8								controlInterfaceNum;

	string[0] = 0;
	FailIf (NULL == mControlInterface, Exit);

	aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (*pathIndex));
	FailIf (NULL == aPath, Exit);
	theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (startingPoint));
	FailIf (NULL == theUnitIDNum, Exit);
	unitID = theUnitIDNum->unsigned8BitValue ();

	numElementsInPath = aPath->getCount ();
	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();
	numMixerSources = mUSBAudioConfig->GetNumSources (controlInterfaceNum, 0, unitID);
	for (mixerSourceIndex = *pathIndex; mixerSourceIndex < *pathIndex + numMixerSources; /* mixerSourceIndex incremented elsewhere */) 
	{
		for (elementIndex = startingPoint + 1; elementIndex < numElementsInPath; elementIndex++) 
		{
			theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (elementIndex));
			FailIf (NULL == theUnitIDNum, Exit);
			unitID = theUnitIDNum->unsigned8BitValue ();
			subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID);
			switch (subType) 
			{
				case INPUT_TERMINAL:
					tempString = getNameForPath (arrayOfPathsFromOutputTerminal, &mixerSourceIndex, elementIndex);
					if (NULL != tempString) 
					{
						strcat (string, tempString->getCStringNoCopy ());
						strcat (string, " & ");
						tempString->release ();
					}
					break;
				case MIXER_UNIT:
					tempString = getNameForMixerPath (arrayOfPathsFromOutputTerminal, &mixerSourceIndex, elementIndex);
					if (NULL != tempString) 
					{
						strcat (string, tempString->getCStringNoCopy ());
						tempString->release ();
					}
					break;
			}
		}
	}
	*pathIndex = mixerSourceIndex;

	if (strlen (string) > 3) 
	{
		string[strlen (string) - 3] = 0;
	}

Exit:
	theString = OSString::withCString (string);
	return theString;
}

void AppleUSBAudioDevice::addVolumeControls (AppleUSBAudioEngine * usbAudioEngine, UInt8 featureUnitID, UInt8 interfaceNum, UInt8 altSettingNum, UInt32 usage) {
	OSArray *							inputGainControlsArray;
	OSArray *							passThruVolControlsArray;
	OSArray *							outputVolControlsArray;
	OSDictionary *						engineInfo;
	IOAudioLevelControl *				theLevelControl;
	IOFixed								deviceMinDB;
	IOFixed								deviceMaxDB;
	SInt32								engineInfoIndex;
	SInt16								deviceCur;
	SInt16								deviceMin;
	SInt16								deviceMax;
	UInt16								volRes;
	SInt16								offset;
	UInt8								channelNum;
	UInt8								controlInterfaceNum;

	debugIOLog ("+ AppleUSBAudioDevice::addVolumeControls (0x%x, %d, %d, %d, %u)", usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, usage);
	engineInfoIndex = getEngineInfoIndex (usbAudioEngine);
	FailIf (-1 == engineInfoIndex, Exit);
	FailIf (NULL == mControlInterface, Exit);

	engineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (engineInfoIndex));
	FailIf (NULL == engineInfo, Exit);
	inputGainControlsArray = NULL;
	passThruVolControlsArray = NULL;
	outputVolControlsArray = NULL;
	
	// remove mono controls array if adding volume controls for output
	if (    (kIOAudioControlUsageOutput == usage)
	     && (NULL != mMonoControlsArray))
	{
		mMonoControlsArray->release ();
		mMonoControlsArray = NULL;
	}

	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();
	for (channelNum = 0; channelNum <= mUSBAudioConfig->GetNumControls (controlInterfaceNum, 0, featureUnitID); channelNum++) 
	{
		debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Checking channel %d for volume controls", this, channelNum);
		if (mUSBAudioConfig->ChannelHasVolumeControl (controlInterfaceNum, 0, featureUnitID, channelNum)) 
		{
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Creating volume controls for channel %d", this, channelNum);
			FailIf (kIOReturnSuccess != getCurVolume (featureUnitID, channelNum, &deviceCur), Error);
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - deviceCur = %x", this, deviceCur);
			FailIf (kIOReturnSuccess != getMinVolume (featureUnitID, channelNum, &deviceMin), Error);
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - deviceMin = %x", this, deviceMin);
			FailIf (kIOReturnSuccess != getMaxVolume (featureUnitID, channelNum, &deviceMax), Error);
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - deviceMax = %x", this, deviceMax);
			getVolumeResolution (featureUnitID, channelNum, &volRes);
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - volRes = %x", this, volRes);
			// [rdar://4511427] Need to check volRes for class compliance (Audio Spec 5.2.2.4.3.2).
			FailIf (0 == volRes, Error);
			
			// [rdar://4228556] Unless the current volume is negative infinity, we should try to flush the volume out to the device.
			if ( (SInt16) kNegativeInfinity != deviceCur )
			{
				debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Attempting to set volume to current volume ...", this);
				FailIf (kIOReturnSuccess != setCurVolume (featureUnitID, channelNum, HostToUSBWord(deviceCur)), Error);
				debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Volume was set successfully.", this);
			}

			if ( (SInt16) kNegativeInfinity == deviceMin ) 
			{
				deviceMin = (SInt16)0x8001;
			}
			deviceMinDB = ConvertUSBVolumeTodB (deviceMin);
			deviceMaxDB = ConvertUSBVolumeTodB (deviceMax);

			offset = -deviceMin;

			deviceCur = (deviceCur + offset) / volRes;
			if (deviceMin < 0 && deviceMax > 0) 
			{
				deviceMax += volRes;
			}
			deviceMax = ((deviceMin + offset) + (deviceMax + offset)) / volRes;
			deviceMin = -1;

			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Creating control...", this);
			theLevelControl = IOAudioLevelControl::createVolumeControl (deviceCur, deviceMin, deviceMax, deviceMinDB, deviceMaxDB, channelNum, 0, featureUnitID, usage);
			FailIf (NULL == theLevelControl, Exit);
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Created control %p", this, theLevelControl);
			theLevelControl->setValueChangeHandler (controlChangedHandler, this);
			usbAudioEngine->addDefaultAudioControl (theLevelControl);
			switch (usage) 
			{
				case kIOAudioControlUsageInput:
					if (NULL == inputGainControlsArray) 
					{
						inputGainControlsArray = OSArray::withObjects ((const OSObject **)&theLevelControl, 1);
					} 
					else 
					{
						inputGainControlsArray->setObject (theLevelControl);
					}
					break;
				case kIOAudioControlUsagePassThru:
					if (NULL == passThruVolControlsArray) 
					{
						passThruVolControlsArray = OSArray::withObjects ((const OSObject **)&theLevelControl, 1);
					} 
					else 
					{
						passThruVolControlsArray->setObject (theLevelControl);
					}
					break;
				case kIOAudioControlUsageOutput:
					if (NULL == outputVolControlsArray) 
					{
						outputVolControlsArray = OSArray::withObjects ((const OSObject **)&theLevelControl, 1);
					} 
					else 
					{
						outputVolControlsArray->setObject (theLevelControl);
					}
					
					// add channel number to mono output controls array if necessary
					if (mDeviceIsInMonoMode)
					{
						OSNumber *number = OSNumber::withNumber (channelNum, 8);
						if (NULL == mMonoControlsArray)
						{
							mMonoControlsArray = OSArray::withObjects ((const OSObject **) &number, 1);
						}
						else
						{
							mMonoControlsArray->setObject (number);
						}
						debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Added channel %d to mono controls array", this, channelNum);
						number->release ();
					}
					
					break;
				default:
					debugIOLog ("! AppleUSBAudioDevice[%p]::addVolumeControls () - Control %p has an unknown usage!", this, theLevelControl);
			}
			theLevelControl->release ();
		} 
		else 
		{
			debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Channel %d has no volume controls; skipping ...", this, channelNum);
		}
		goto NoError;
Error: 
		debugIOLog ("! AppleUSBAudioDevice[%p]::addVolumeControls () - Error creating controls for channel %d!", this, channelNum);
NoError:
		debugIOLog ("? AppleUSBAudioDevice[%p]::addVolumeControls () - Done with channel %d", this, channelNum);
	}

	if (NULL != inputGainControlsArray) 
	{
		engineInfo->setObject (kInputGainControls, inputGainControlsArray);
		inputGainControlsArray->release ();
	}
	if (NULL != passThruVolControlsArray) 
	{
		engineInfo->setObject (kPassThruVolControls, passThruVolControlsArray);
		passThruVolControlsArray->release ();
	}
	if (NULL != outputVolControlsArray) 
	{
		engineInfo->setObject (kOutputVolControls, outputVolControlsArray);
		outputVolControlsArray->release ();
	}

Exit:
	debugIOLog ("- AppleUSBAudioDevice::addVolumeControls (0x%x, %d, %d, %d, %u)", usbAudioEngine, featureUnitID, interfaceNum, altSettingNum, usage);
	return;
}

void AppleUSBAudioDevice::addMuteControl (AppleUSBAudioEngine * usbAudioEngine, UInt8 featureUnitID, UInt8 interfaceNum, UInt8 altSettingNum, UInt32 usage) {
	OSArray *							inputMuteControlsArray;
	OSDictionary *						engineInfo;
	IOAudioToggleControl *				theMuteControl;
	SInt32								engineInfoIndex;
	SInt16								deviceCur;
	UInt8								channelNum;
	UInt8								controlInterfaceNum;
	IOReturn							resultCode;

	engineInfoIndex = getEngineInfoIndex (usbAudioEngine);
	FailIf (-1 == engineInfoIndex, Exit);
	FailIf (NULL == mControlInterface, Exit);

	engineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (engineInfoIndex));
	FailIf (NULL == engineInfo, Exit);
	inputMuteControlsArray = NULL;

	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();
	for (channelNum = 0; channelNum <= mUSBAudioConfig->GetNumControls (controlInterfaceNum, 0, featureUnitID); channelNum++) 
	{
		if (mUSBAudioConfig->ChannelHasMuteControl (controlInterfaceNum, 0, featureUnitID, channelNum)) 
		{
			resultCode = getCurMute (featureUnitID, channelNum, &deviceCur);

			theMuteControl = IOAudioToggleControl::createMuteControl (deviceCur, channelNum, 0, featureUnitID, usage);
			setCurMute (featureUnitID, channelNum, HostToUSBWord(deviceCur));
			FailIf (NULL == theMuteControl, Exit);
			theMuteControl->setValueChangeHandler (controlChangedHandler, this);
			usbAudioEngine->addDefaultAudioControl (theMuteControl);
			switch (usage) 
			{
				case kIOAudioControlUsageInput:
					if (NULL == inputMuteControlsArray) 
					{
						inputMuteControlsArray = OSArray::withObjects ((const OSObject **)&theMuteControl, 1);
					} 
					else 
					{
						inputMuteControlsArray->setObject (theMuteControl);
					}
					break;
				case kIOAudioControlUsagePassThru:
					break;
				case kIOAudioControlUsageOutput:
					break;
			}
			theMuteControl->release ();
		}
	}

	if (NULL != inputMuteControlsArray) 
	{
		engineInfo->setObject (kInputMuteControls, inputMuteControlsArray);
		inputMuteControlsArray->release ();
	}

Exit:
	return;
}

// This is how the thing is defined in the USB Audio spec (section 5.2.2.4.3.2 for the curious).
// The volume setting of a device is described in 1/256 dB increments using a number that goes from
// a max of 0x7fff (127.9961 dB) down to 0x8001 (-127.9961 dB) using standard signed math, but 0x8000
// is actually negative infinity (not -128 dB), so I have to special case it.
IOFixed AppleUSBAudioDevice::ConvertUSBVolumeTodB (SInt16 volume) {
	IOFixed							dBVolumeFixed;

	if (volume == (SInt16)0x8000) 
	{
		dBVolumeFixed = ((SInt16)0x8000 * 256) << 8;	// really is negative infinity
	} 
	else 
	{
		dBVolumeFixed = volume * 256;
	}

	// debugIOLog ("volume = %d, dBVolumeFixed = 0x%X", volume, dBVolumeFixed);

	return dBVolumeFixed;
}

IOReturn AppleUSBAudioDevice::getFeatureUnitSetting (UInt8 controlSelector, UInt8 unitID, UInt8 channelNumber, UInt8 requestType, SInt16 * target) {
    IOReturn							result;
	IOUSBDevRequestDesc					devReq;
	UInt16								theSetting;
	IOBufferMemoryDescriptor *			theSettingDesc = NULL;
	UInt8								length;

	result = kIOReturnError;
	// Initialize theSetting so that 
	theSetting = 0;
	FailIf (NULL == target, Exit);
	FailIf (NULL == mControlInterface, Exit);

	switch (controlSelector) 
	{
		case MUTE_CONTROL:
			length = 1;
			break;
		case VOLUME_CONTROL:
			length = 2;
			break;
		default:
			length = 0;
	}
	theSettingDesc = IOBufferMemoryDescriptor::withOptions (kIODirectionIn, length);
	FailIf (NULL == theSettingDesc, Exit);

    devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBClass, kUSBInterface);
    devReq.bRequest = requestType;
    devReq.wValue = (controlSelector << 8) | channelNumber;
    devReq.wIndex = (0xFF00 & (unitID << 8)) | (0x00FF & mControlInterface->GetInterfaceNumber ());
    devReq.wLength = length;
    devReq.pData = theSettingDesc;

	result = deviceRequest (&devReq);
	FailIf (kIOReturnSuccess != result, Exit);
	memcpy (&theSetting, theSettingDesc->getBytesNoCopy (), length);
	
Exit:
	if (NULL != theSettingDesc) 
	{
		theSettingDesc->release ();
	}
	if (NULL != target) 
	{
		*target = USBToHostWord (theSetting);
	}
	return result;
}

IOReturn AppleUSBAudioDevice::setFeatureUnitSetting (UInt8 controlSelector, UInt8 unitID, UInt8 channelNumber, UInt8 requestType, UInt16 newValue, UInt16 newValueLen) {
    IOUSBDevRequestDesc					devReq;
	IOBufferMemoryDescriptor *			theSettingDesc = NULL;
	IOReturn							result;

	result = kIOReturnError;
	FailIf (NULL == mControlInterface, Exit);

	theSettingDesc = OSTypeAlloc (IOBufferMemoryDescriptor);
	FailIf (NULL == theSettingDesc, Exit);
	theSettingDesc->initWithBytes (&newValue, newValueLen, kIODirectionOut);

    devReq.bmRequestType = USBmakebmRequestType (kUSBOut, kUSBClass, kUSBInterface);
    devReq.bRequest = requestType;
    devReq.wValue = (controlSelector << 8) | channelNumber;
    devReq.wIndex = (0xFF00 & (unitID << 8)) | (0x00FF & mControlInterface->GetInterfaceNumber ());
    devReq.wLength = newValueLen;
    devReq.pData = theSettingDesc;

	FailIf ((TRUE == isInactive()), DeviceInactive);  	// In case we've been unplugged during sleep
	result = deviceRequest (&devReq);

Exit:
	if (NULL != theSettingDesc) 
	{
		theSettingDesc->release ();
	}
	return result;
	
DeviceInactive:
	debugIOLog("? AppleUSBAudioLevelControl::SetCurVolume ERROR attempt to send a device request to an inactive device");
	goto Exit;
}

IOReturn AppleUSBAudioDevice::getCurMute (UInt8 unitID, UInt8 channelNumber, SInt16 * target) {
	return getFeatureUnitSetting (MUTE_CONTROL, unitID, channelNumber, GET_CUR, target);
}

IOReturn AppleUSBAudioDevice::getCurVolume (UInt8 unitID, UInt8 channelNumber, SInt16 * target) {
	return getFeatureUnitSetting (VOLUME_CONTROL, unitID, channelNumber, GET_CUR, target);
}

IOReturn AppleUSBAudioDevice::getMaxVolume (UInt8 unitID, UInt8 channelNumber, SInt16 * target) {
	return getFeatureUnitSetting (VOLUME_CONTROL, unitID, channelNumber, GET_MAX, target);
}

IOReturn AppleUSBAudioDevice::getMinVolume (UInt8 unitID, UInt8 channelNumber, SInt16 * target) {
	return getFeatureUnitSetting (VOLUME_CONTROL, unitID, channelNumber, GET_MIN, target);
}

IOReturn AppleUSBAudioDevice::getVolumeResolution (UInt8 unitID, UInt8 channelNumber, UInt16 * target) {
	return getFeatureUnitSetting (VOLUME_CONTROL, unitID, channelNumber, GET_RES, (SInt16 *) target);
}

IOReturn AppleUSBAudioDevice::setCurVolume (UInt8 unitID, UInt8 channelNumber, SInt16 volume) {
	return setFeatureUnitSetting (VOLUME_CONTROL, unitID, channelNumber, SET_CUR, volume, 2);
}

IOReturn AppleUSBAudioDevice::setCurMute (UInt8 unitID, UInt8 channelNumber, SInt16 mute) {
	return setFeatureUnitSetting (MUTE_CONTROL, unitID, channelNumber, SET_CUR, mute, 1);
}

IOReturn AppleUSBAudioDevice::controlChangedHandler (OSObject * target, IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
    IOReturn							result;
	AppleUSBAudioDevice *				self;

	result = kIOReturnError;

	self = OSDynamicCast (AppleUSBAudioDevice, target);
	FailIf (NULL == self, Exit);
	result = self->protectedControlChangedHandler (audioControl, oldValue, newValue);

Exit:
	return result;
}

IOReturn AppleUSBAudioDevice::protectedControlChangedHandler (IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
    IOReturn							result;

	result = kIOReturnError;
    switch (audioControl->getType ()) 
	{
		case kIOAudioControlTypeLevel:
			result = doVolumeControlChange (audioControl, oldValue, newValue);
			break;
		case kIOAudioControlTypeToggle:
			result = doToggleControlChange (audioControl, oldValue, newValue);
			break;
		case kIOAudioControlTypeSelector:
			result = doSelectorControlChange (audioControl, oldValue, newValue);
			break;
	}

	return result;
}

IOReturn AppleUSBAudioDevice::doSelectorControlChange (IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
    IOReturn							result;

	result = kIOReturnError;
	switch (audioControl->getUsage ()) 
	{
		case kIOAudioControlUsageInput:
			result = doInputSelectorChange (audioControl, oldValue, newValue);
			break;
		case kIOAudioControlUsageOutput:
			break;
		case kIOAudioControlUsagePassThru:
			result = doPassThruSelectorChange (audioControl, oldValue, newValue);
			break;
	}

	return result;
}

IOReturn AppleUSBAudioDevice::doVolumeControlChange (IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
	IOReturn							result;
	SInt16								newVolume;
	SInt16								deviceMin;
	SInt16								offset;
	UInt16								volRes;
	UInt8								unitID;
	UInt8								channelNum;

	debugIOLog ("+ AppleUSBAudioDevice[%p]::doVolumeControlChange( %p, 0x%x, 0x%x )", this, audioControl, oldValue, newValue);
	unitID = audioControl->getControlID ();
	channelNum = audioControl->getChannelID ();
	result = kIOReturnError;

	if (    (kIOAudioControlUsageInput == audioControl->getUsage())
	     || (FALSE == mDeviceIsInMonoMode))
	{
		getMinVolume (unitID, channelNum, &deviceMin);
		offset = -deviceMin;

		if (newValue < 0) 
		{
			newVolume = 0x8000;
		} 
		else 
		{
			getVolumeResolution (unitID, channelNum, &volRes);
			if (newValue > 0) 
			{
				newVolume = ((newValue - 1) * volRes) - offset;
			} 
			else 
			{
				newVolume = (newValue * volRes) - offset;
			}
		}

		debugIOLog ("? AppleUSBAudioDevice[%p]::doVolumeControlChange () - Setting volume to 0x%x", this, newVolume);
		result = setCurVolume (unitID, channelNum, HostToUSBWord (newVolume));
	
	}
	else
	{	// mono output case
		UInt8 i;
		FailIf (NULL == mMonoControlsArray, Exit);
		debugIOLog ("? AppleUSBAudioDevice[%p]::doVolumeControlChange () - Performing mono volume control change", this);
		for (i = 0; i < mMonoControlsArray->getCount (); i++)
		{
			channelNum = ((OSNumber *) mMonoControlsArray->getObject(i))->unsigned8BitValue ();
			getMinVolume (unitID, channelNum, &deviceMin);
			offset = -deviceMin;
		
			if (newValue < 0) 
			{
				newVolume = 0x8000;
			} 
			else 
			{
				getVolumeResolution (unitID, channelNum, &volRes);
				if (newValue > 0) 
				{
					newVolume = ((newValue - 1) * volRes) - offset;
				} 
				else 
				{
					newVolume = (newValue * volRes) - offset;
				}
			}
		
			result = setCurVolume (unitID, channelNum, HostToUSBWord (newVolume));
			debugIOLog ("? AppleUSBAudioDevice[%p]::doVolumeControlChange () - Set volume for channel %d to 0x%x = %d", this, channelNum, newVolume, result);
		}
	}
	
Exit:
	debugIOLog ("- AppleUSBAudioDevice[%p]::doVolumeControlChange( %p, 0x%x, 0x%x ) = 0x%x", this, audioControl, oldValue, newValue, result);
	return result;
}

IOReturn AppleUSBAudioDevice::doToggleControlChange (IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
	IOReturn							result;
	UInt8								unitID;
	UInt8								channelNum;

	unitID = audioControl->getControlID ();
	channelNum = audioControl->getChannelID ();

	result = setCurMute (unitID, channelNum, HostToUSBWord (newValue));

	return result;
}

IOReturn AppleUSBAudioDevice::doPassThruSelectorChange (IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
	AppleUSBAudioEngine *				usbAudioEngine;
	OSArray *							playThroughPaths;
	OSArray *							passThruVolControlsArray;
	OSArray *							thePath;
	OSNumber *							number;
	OSDictionary *						engineInfo;
	UInt32								i;
	UInt32								numPassThruControls;
	UInt8								interfaceNum;
	UInt8								altSetting;
	UInt8								featureUnitID;
	UInt8								pathIndex;

	pathIndex = (newValue & 0x000000FF);

	engineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (audioControl->getControlID ()));
	FailIf (NULL == engineInfo, Exit);
	usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, engineInfo->getObject (kEngine));
	FailIf (NULL == usbAudioEngine, Exit);
	number = OSDynamicCast (OSNumber, engineInfo->getObject (kInterface));
	FailIf (NULL == number, Exit);
	interfaceNum = number->unsigned8BitValue ();
	number = OSDynamicCast (OSNumber, engineInfo->getObject (kAltSetting));
	FailIf (NULL == number, Exit);
	altSetting = number->unsigned8BitValue ();
	passThruVolControlsArray = OSDynamicCast (OSArray, engineInfo->getObject (kPassThruVolControls));
	FailIf (NULL == passThruVolControlsArray, Exit);
	numPassThruControls = passThruVolControlsArray->getCount ();

	usbAudioEngine->pauseAudioEngine ();
	usbAudioEngine->beginConfigurationChange ();
	for (i = 0; i < numPassThruControls; i++) 
	{
		usbAudioEngine->removeDefaultAudioControl ((IOAudioLevelControl *)passThruVolControlsArray->getObject (i));
	}
	passThruVolControlsArray->flushCollection ();

	playThroughPaths = OSDynamicCast (OSArray, engineInfo->getObject (kPassThruPathsArray));
	FailIf (NULL == playThroughPaths, Exit);
	thePath = OSDynamicCast (OSArray, playThroughPaths->getObject (pathIndex));
	FailIf (NULL == thePath, Exit);
	featureUnitID = getBestFeatureUnitInPath (thePath, kIOAudioControlUsagePassThru, interfaceNum, altSetting, kVolumeControl);
	addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSetting, kIOAudioControlUsagePassThru);
	usbAudioEngine->completeConfigurationChange ();
	usbAudioEngine->resumeAudioEngine ();

Exit:
	return kIOReturnSuccess;
}

IOReturn AppleUSBAudioDevice::doInputSelectorChange (IOAudioControl * audioControl, SInt32 oldValue, SInt32 newValue) {
	AppleUSBAudioEngine *				usbAudioEngine;
	OSArray *							inputGainControlsArray;
	OSArray *							arrayOfPathsFromOutputTerminal;
	OSArray *							thePath;
	OSNumber *							number;
	OSDictionary *						engineInfo;
    IOReturn							result;
	UInt32								i;
	UInt32								numGainControls;
	UInt8								interfaceNum;
	UInt8								altSetting;
	UInt8								featureUnitID;
	UInt8								selectorUnitID;
	UInt8								selectorPosition;
	UInt8								pathsToOutputTerminal;
	UInt8								pathIndex;

	pathsToOutputTerminal = (newValue & 0xFF000000) >> 24;
	pathIndex = (newValue & 0x00FF0000) >> 16;
	selectorUnitID = (newValue & 0x0000FF00) >> 8;
	selectorPosition = newValue & 0x000000FF;
	result = setSelectorSetting (selectorUnitID, selectorPosition);
	FailIf (kIOReturnSuccess != result, Exit);

	engineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (audioControl->getControlID ()));
	FailIf (NULL == engineInfo, Exit);
	usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, engineInfo->getObject (kEngine));
	FailIf (NULL == usbAudioEngine, Exit);
	number = OSDynamicCast (OSNumber, engineInfo->getObject (kInterface));
	FailIf (NULL == number, Exit);
	interfaceNum = number->unsigned8BitValue ();
	number = OSDynamicCast (OSNumber, engineInfo->getObject (kAltSetting));
	FailIf (NULL == number, Exit);
	altSetting = number->unsigned8BitValue ();
	inputGainControlsArray = OSDynamicCast (OSArray, engineInfo->getObject (kInputGainControls));
	FailIf (NULL == inputGainControlsArray, Exit);
	numGainControls = inputGainControlsArray->getCount ();

	usbAudioEngine->pauseAudioEngine ();
	usbAudioEngine->beginConfigurationChange ();
	for (i = 0; i < numGainControls; i++) 
	{
		usbAudioEngine->removeDefaultAudioControl ((IOAudioLevelControl *)inputGainControlsArray->getObject (i));
	}
	inputGainControlsArray->flushCollection ();

	arrayOfPathsFromOutputTerminal = OSDynamicCast (OSArray, mControlGraph->getObject (pathsToOutputTerminal));
	FailIf (NULL == arrayOfPathsFromOutputTerminal, Exit);
	thePath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (pathIndex));
	FailIf (NULL == thePath, Exit);
	featureUnitID = getBestFeatureUnitInPath (thePath, kIOAudioControlUsageInput, interfaceNum, altSetting, kVolumeControl);
	addVolumeControls (usbAudioEngine, featureUnitID, interfaceNum, altSetting, kIOAudioControlUsageInput);
	usbAudioEngine->completeConfigurationChange ();
	usbAudioEngine->resumeAudioEngine ();

Exit:
	return result;
}

// This should detect a playthrough path; which is a non-streaming input terminal connected to a non-streaming output terminal.
OSArray * AppleUSBAudioDevice::getPlaythroughPaths () {
	OSArray *							arrayOfPathsFromOutputTerminal;
	OSArray *							playThroughPaths;
	OSArray *							aPath;
	OSNumber *							theUnitIDNum;
	UInt32								numOutputTerminalArrays;
	UInt32								numPathsFromOutputTerminal;
	UInt32								pathsToOutputTerminalN;
	UInt32								pathNumber;
	UInt16								terminalType;
	UInt8								controlInterfaceNum;
	UInt8								unitID;

	playThroughPaths = NULL;
	FailIf (NULL == mControlInterface, Exit);
	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();

	numOutputTerminalArrays = mControlGraph->getCount ();
	for (pathsToOutputTerminalN = 0; pathsToOutputTerminalN < numOutputTerminalArrays; pathsToOutputTerminalN++) 
	{
		arrayOfPathsFromOutputTerminal = OSDynamicCast (OSArray, mControlGraph->getObject (pathsToOutputTerminalN));
		FailIf (NULL == arrayOfPathsFromOutputTerminal, Exit);
		aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (0));
		FailIf (NULL == aPath, Exit);
		theUnitIDNum = OSDynamicCast (OSNumber, aPath->getObject (0));
		FailIf (NULL == theUnitIDNum, Exit);
		unitID = theUnitIDNum->unsigned8BitValue ();
		if (mUSBAudioConfig->GetOutputTerminalType (controlInterfaceNum, 0, unitID) == 0x101) continue;		// only looking for non-streaming outputs

		numPathsFromOutputTerminal = arrayOfPathsFromOutputTerminal->getCount ();
		for (pathNumber = 0; pathNumber < numPathsFromOutputTerminal; pathNumber++) 
		{
			aPath = OSDynamicCast (OSArray, arrayOfPathsFromOutputTerminal->getObject (pathNumber));
			FailIf (NULL == aPath, Exit);
			theUnitIDNum = OSDynamicCast (OSNumber, aPath->getLastObject ());
			FailIf (NULL == theUnitIDNum, Exit);
			unitID = theUnitIDNum->unsigned8BitValue ();
			terminalType = mUSBAudioConfig->GetInputTerminalType (controlInterfaceNum, 0, unitID);
			if (terminalType != 0x101) 
			{
				if (NULL == playThroughPaths) 
				{
					playThroughPaths = OSArray::withObjects ((const OSObject **)&aPath, 1);
				} 
				else 
				{
					playThroughPaths->setObject (aPath);
				}
			}
		}
	}

Exit:
	return playThroughPaths;
}

// This finds the feature unit closest to the input terminal.
UInt8 AppleUSBAudioDevice::getBestFeatureUnitInPath (OSArray * thePath, UInt32 direction, UInt8 interfaceNum, UInt8 altSettingNum, UInt32 controlTypeWanted) {
	OSNumber *							theUnitIDNum;
	UInt32								numUnitsInPath;
	UInt8								featureUnitID;
	UInt8								unitIndex;
	UInt8								controlInterfaceNum;
	UInt8								unitID;
	UInt8								subType;
	UInt8								channelNum;
	Boolean								found;

	featureUnitID = 0;
	FailIf (NULL == mControlInterface, Exit);
	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();
	numUnitsInPath = thePath->getCount ();
	found = FALSE;

	switch (direction) 
	{
		case kIOAudioControlUsageInput:
		case kIOAudioControlUsagePassThru:
		// Find the feature unit closest to the input terminal.
			for (unitIndex = numUnitsInPath - 2; unitIndex > 0; unitIndex--) 
			{
				theUnitIDNum = OSDynamicCast (OSNumber, thePath->getObject (unitIndex));
				if (NULL != theUnitIDNum) 
				{
					unitID = theUnitIDNum->unsigned8BitValue ();
					subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID);
					if (FEATURE_UNIT == subType) 
					{
						for (channelNum = 0; channelNum <= mUSBAudioConfig->GetNumChannels (interfaceNum, altSettingNum); channelNum++) 
						{
							switch (controlTypeWanted) 
							{
								case kVolumeControl:
									if (mUSBAudioConfig->ChannelHasVolumeControl (controlInterfaceNum, 0, unitID, channelNum)) 
									{
										featureUnitID = unitID;
										found = TRUE;
									}
									break;
								case kMuteControl:
									if (mUSBAudioConfig->ChannelHasMuteControl (controlInterfaceNum, 0, unitID, channelNum)) 
									{
										featureUnitID = unitID;
										found = TRUE;
									}
									break;
							} // switch
						} // for channelNum
					} // if (FEATURE_UNIT == subType)
				} // if (NULL != theUnitIDNum)
			} // for unitIndex
			break;
		case kIOAudioControlUsageOutput:
		default:
			// Find the feature unit closest to the output terminal.
			for (unitIndex = 1; unitIndex < numUnitsInPath && !found; unitIndex++) 
			{
				theUnitIDNum = OSDynamicCast (OSNumber, thePath->getObject (unitIndex));
				if (NULL != theUnitIDNum) 
				{
					unitID = theUnitIDNum->unsigned8BitValue ();
					subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID);
					if (FEATURE_UNIT == subType) 
					{
						for (channelNum = 0; channelNum <= mUSBAudioConfig->GetNumChannels (interfaceNum, altSettingNum); channelNum++) 
						{
							switch (controlTypeWanted) 
							{
								case kVolumeControl:
									if (mUSBAudioConfig->ChannelHasVolumeControl (controlInterfaceNum, 0, unitID, channelNum)) 
									{
										featureUnitID = unitID;
										found = TRUE;
									}
									break;
								case kMuteControl:
									if (mUSBAudioConfig->ChannelHasMuteControl (controlInterfaceNum, 0, unitID, channelNum)) 
									{
										featureUnitID = unitID;
										found = TRUE;
									}
									break;
							} // switch
						} // for channelNum
					} // if (FEATURE_UNIT == subType)
				} // if (NULL != theUnitIDNum)
			} // for unitIndex
			break;
	} // switch (direction)

Exit:
	return featureUnitID;
}

UInt8 AppleUSBAudioDevice::getSelectorSetting (UInt8 selectorID) {
    IOUSBDevRequestDesc					devReq;
	IOReturn							result;
	UInt8								setting;
	IOBufferMemoryDescriptor *			settingDesc = NULL;

	setting = 0;
	FailIf (NULL == mControlInterface, Exit);

	settingDesc = IOBufferMemoryDescriptor::withOptions (kIODirectionIn, 1);
	FailIf (NULL == settingDesc, Exit);

    devReq.bmRequestType = USBmakebmRequestType (kUSBIn, kUSBClass, kUSBInterface);
    devReq.bRequest = GET_CUR;
    devReq.wValue = 0;
    devReq.wIndex = (0xFF00 & (selectorID << 8)) | (0x00FF & mControlInterface->GetInterfaceNumber ());
    devReq.wLength = 1;
    devReq.pData = settingDesc;

	result = deviceRequest (&devReq);
	FailIf (kIOReturnSuccess != result, Exit);
	memcpy (&setting, settingDesc->getBytesNoCopy (), 1);

Exit:
	if (NULL != settingDesc) 
	{
		settingDesc->release ();
	}
	return setting;
}

IOReturn AppleUSBAudioDevice::setSelectorSetting (UInt8 selectorID, UInt8 setting) {
    IOUSBDevRequestDesc					devReq;
	IOReturn							result;
	IOBufferMemoryDescriptor *			settingDesc = NULL;

	result = kIOReturnError;
	FailIf (NULL == mControlInterface, Exit);

	settingDesc = OSTypeAlloc (IOBufferMemoryDescriptor);
	FailIf (NULL == settingDesc, Exit);
	settingDesc->initWithBytes (&setting, 1, kIODirectionIn);

    devReq.bmRequestType = USBmakebmRequestType (kUSBOut, kUSBClass, kUSBInterface);
    devReq.bRequest = SET_CUR;
    devReq.wValue = 0;
    devReq.wIndex = (0xFF00 & (selectorID << 8)) | (0x00FF & mControlInterface->GetInterfaceNumber ());
    devReq.wLength = 1;
    devReq.pData = settingDesc;

	result = deviceRequest (&devReq);

Exit:
	if (NULL != settingDesc) 
	{
		settingDesc->release ();
	}
	return result;
}

void AppleUSBAudioDevice::setMonoState (Boolean state)
{
	mDeviceIsInMonoMode = state;
}

IOReturn AppleUSBAudioDevice::createControlsForInterface (IOAudioEngine *audioEngine, UInt8 interfaceNum, UInt8 altSettingNum) {
    AppleUSBAudioEngine *				usbAudioEngine;
    IOReturn							result;
    debugIOLog ("? AppleUSBAudioDevice[%p]::createControlsForInterface () - Interface %d alternate setting %d", this, interfaceNum, altSettingNum);

    result = kIOReturnError;
	mTerminatingDriver = FALSE;

    usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, audioEngine);
    FailIf (NULL == usbAudioEngine, Exit);

	doControlStuff (audioEngine, interfaceNum, altSettingNum);

Exit:
	return result;
}

OSArray * AppleUSBAudioDevice::BuildConnectionGraph (UInt8 controlInterfaceNum) {
	OSArray *						allOutputTerminalPaths;
	OSArray *						pathsFromOutputTerminalN;
	OSArray *						thisPath;
	UInt8							terminalIndex;
	UInt8							numTerminals;

	thisPath = NULL;
	allOutputTerminalPaths = OSArray::withCapacity (1);
	FailIf (NULL == allOutputTerminalPaths, Exit);
	pathsFromOutputTerminalN = OSArray::withCapacity (1);
	FailIf (NULL == pathsFromOutputTerminalN, Exit);
	numTerminals = mUSBAudioConfig->GetNumOutputTerminals (controlInterfaceNum, 0);

	for (terminalIndex = 0; terminalIndex < numTerminals; terminalIndex++) 
	{
		BuildPath (controlInterfaceNum, mUSBAudioConfig->GetIndexedOutputTerminalID (controlInterfaceNum, 0, terminalIndex), pathsFromOutputTerminalN, thisPath);
		allOutputTerminalPaths->setObject (pathsFromOutputTerminalN);
		pathsFromOutputTerminalN->release ();
		pathsFromOutputTerminalN = OSArray::withCapacity (1);
		FailIf (NULL == pathsFromOutputTerminalN, Exit);
	}

	pathsFromOutputTerminalN->release();
	
Exit:
	return allOutputTerminalPaths;
}

OSArray * AppleUSBAudioDevice::BuildPath (UInt8 controlInterfaceNum, UInt8 startingUnitID, OSArray * allPaths, OSArray * startingPath) {
	OSArray *						curPath;
	OSArray *						tempPath;
	OSNumber *						thisUnitIDNum;
	UInt8							unitID;	
	UInt8 *							sourceIDs;
	UInt32							i;
	UInt8							thisUnitID;
	UInt8							numSources;
	UInt8							sourceID;
	UInt8							subType;

	thisUnitID = startingUnitID;
	curPath = NULL;
	thisUnitIDNum = OSNumber::withNumber (thisUnitID, 8);
	FailIf (NULL == thisUnitIDNum, Exit);
	if (NULL != startingPath) 
	{
		curPath = OSArray::withArray (startingPath);
	}
	if (NULL == curPath) 
	{
		curPath = OSArray::withObjects ((const OSObject **)&thisUnitIDNum, 1);
	} 
	else 
	{
		curPath->setObject (thisUnitIDNum);
	}
	thisUnitIDNum->release ();
	thisUnitIDNum = NULL;

	subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, thisUnitID);

	while (INPUT_TERMINAL != subType && subType != 0) 
	{
		switch (subType) 
		{
			case MIXER_UNIT:
			case SELECTOR_UNIT:
			case EXTENSION_UNIT:
			case PROCESSING_UNIT:
				numSources = mUSBAudioConfig->GetNumSources (controlInterfaceNum, 0, thisUnitID);
				sourceIDs = mUSBAudioConfig->GetSourceIDs (controlInterfaceNum, 0, thisUnitID);
				tempPath = OSArray::withArray (curPath);
				for (i = 0; i < numSources; i++) 
				{
					if (NULL == curPath) 
					{
						curPath = OSArray::withCapacity (1);
					}
					FailIf (NULL == curPath, Exit);
					curPath = BuildPath (controlInterfaceNum, sourceIDs[i], allPaths, tempPath);
					if (curPath && curPath->getCount ()) 
					{
						thisUnitIDNum = OSDynamicCast (OSNumber, curPath->getLastObject ());
						FailIf (NULL == thisUnitIDNum, Exit);
						unitID = thisUnitIDNum->unsigned8BitValue ();
						if (unitID && mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, unitID) == INPUT_TERMINAL) 
						{
							allPaths->setObject (curPath);
						}
					}
					if (curPath) 
					{
						curPath->release ();
						curPath = NULL;
					}
				}
				tempPath->release ();
				subType = 0;
				break;
			case OUTPUT_TERMINAL:
			case FEATURE_UNIT:
			default:
				sourceID = mUSBAudioConfig->GetSourceID (controlInterfaceNum, 0, thisUnitID);
				thisUnitID = sourceID;
				thisUnitIDNum = OSNumber::withNumber (thisUnitID, 8);
				if (NULL != thisUnitIDNum) 
				{
					curPath->setObject (thisUnitIDNum);
					thisUnitIDNum->release ();
					thisUnitIDNum = NULL;
				}
				subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, thisUnitID);
				if (subType == INPUT_TERMINAL && mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, startingUnitID) == OUTPUT_TERMINAL) 
				{
					allPaths->setObject (curPath);
				}
				break;
		} // switch (subType)
	} // while (INPUT_TERMINAL != subType && subType != 0)

Exit:
	return curPath;
}

char * AppleUSBAudioDevice::TerminalTypeString (UInt16 terminalType) 
{
	char *					terminalTypeString;

	switch (terminalType) {
		case 0x101:											terminalTypeString = "USB streaming";									break;
	#if LOCALIZABLE
		case INPUT_UNDEFINED:								terminalTypeString = "StringInputUndefined";							break;
		case INPUT_MICROPHONE:								terminalTypeString = "StringMicrophone";								break;
		case INPUT_DESKTOP_MICROPHONE:						terminalTypeString = "StringDesktopMicrophone";							break;
		case INPUT_PERSONAL_MICROPHONE:						terminalTypeString = "StringPersonalMicrophone";						break;
		case INPUT_OMNIDIRECTIONAL_MICROPHONE:				terminalTypeString = "StringOmnidirectionalMicrophone";					break;
		case INPUT_MICROPHONE_ARRAY:						terminalTypeString = "StringMicrophoneArray";							break;
		case INPUT_PROCESSING_MICROPHONE_ARRAY:				terminalTypeString = "StringProcessingMicrophoneArray";					break;
		case INPUT_MODEM_AUDIO:								terminalTypeString = "StringModemAudio";								break;
		case OUTPUT_UNDEFINED:								terminalTypeString = "StringOutputUndefined";							break;
		case OUTPUT_SPEAKER:								terminalTypeString = "StringSpeaker";									break;
		case OUTPUT_HEADPHONES:								terminalTypeString = "StringHeadphones";								break;
		case OUTPUT_HEAD_MOUNTED_DISPLAY_AUDIO:				terminalTypeString = "StringHeadMountedDisplayAudio";					break;
		case OUTPUT_DESKTOP_SPEAKER:						terminalTypeString = "StringDesktopSpeaker";							break;
		case OUTPUT_ROOM_SPEAKER:							terminalTypeString = "StringRoomSpeaker";								break;
		case OUTPUT_COMMUNICATION_SPEAKER:					terminalTypeString = "StringCommunicationSpeaker";						break;
		case OUTPUT_LOW_FREQUENCY_EFFECTS_SPEAKER:			terminalTypeString = "StringLowFrequencyEffectsSpeaker";				break;
		case BIDIRECTIONAL_UNDEFINED:						terminalTypeString = "StringBidirectionalUndefined";					break;
		case BIDIRECTIONAL_HANDSET:							terminalTypeString = "StringBidirectionalHandset";						break;
		case BIDIRECTIONAL_HEADSET:							terminalTypeString = "StringBidirectionalHeadset";						break;
		case BIDIRECTIONAL_SPEAKERPHONE_NO_ECHO_REDX:		terminalTypeString = "StringBidirectionalSpeakerphoneNoEchoRedx";		break;
		case BIDIRECTIONAL_ECHO_SUPPRESSING_SPEAKERPHONE:	terminalTypeString = "StringBidirectionalEchoSuppressingSpeakerphone";	break;
		case BIDIRECTIONAL_ECHO_CANCELING_SPEAKERPHONE:		terminalTypeString = "StringBidirectionalEchoCancelingSpeakerphone";	break;
		case TELEPHONY_UNDEFINED:							terminalTypeString = "StringTelephoneUndefined";						break;
		case TELEPHONY_PHONE_LINE:							terminalTypeString = "StringTelephoneLine";								break;
		case TELEPHONY_TELEPHONE:							terminalTypeString = "StringTelephone";									break;
		case TELEPHONY_DOWN_LINE_PHONE:						terminalTypeString = "StringDownLinePhone";								break;
		case EXTERNAL_UNDEFINED:							terminalTypeString = "StringExternalUndefined";							break;
		case EXTERNAL_ANALOG_CONNECTOR:						terminalTypeString = "StringExternalAnalogConnector";					break;
		case EXTERNAL_DIGITAL_AUDIO_INTERFACE:				terminalTypeString = "StringExternalDigitalAudioInterface";				break;
		case EXTERNAL_LINE_CONNECTOR:						terminalTypeString = "StringExternalLineConnector";						break;
		case EXTERNAL_LEGACY_AUDIO_CONNECTOR:				terminalTypeString = "StringExternalLegacyAudioConnector";				break;
		case EXTERNAL_SPDIF_INTERFACE:						terminalTypeString = "StringExternalSPDIFInterface";					break;
		case EXTERNAL_1394_DA_STREAM:						terminalTypeString = "StringExternal1394DAStream";						break;
		case EXTERNAL_1394_DV_STREAM_SOUNDTRACK:			terminalTypeString = "StringExternal1394DVStreamSoundtrack";			break;
		case EMBEDDED_UNDEFINED:							terminalTypeString = "StringEmbeddedUndefined";							break;
		case EMBEDDED_LEVEL_CALIBRATION_NOISE_SOURCE:		terminalTypeString = "StringEmbeddedLevelCalibrationNoiseSource";		break;
		case EMBEDDED_EQUALIZATION_NOISE:					terminalTypeString = "StringEmbeddedEqualizationNoise";					break;
		case EMBEDDED_CD_PLAYER:							terminalTypeString = "StringEmbeddedCDPlayer";							break;
		case EMBEDDED_DAT:									terminalTypeString = "StringEmbeddedDAT";								break;
		case EMBEDDED_DCC:									terminalTypeString = "StringEmbeddedDCC";								break;
		case EMBEDDED_MINIDISK:								terminalTypeString = "StringEmbeddedMiniDisc";							break;
		case EMBEDDED_ANALOG_TAPE:							terminalTypeString = "StringEmbeddedAnalogTape";						break;
		case EMBEDDED_PHONOGRAPH:							terminalTypeString = "StringEmbeddedPhonograph";						break;
		case EMBEDDED_VCR_AUDIO:							terminalTypeString = "StringEmbeddedVCRAudio";							break;
		case EMBEDDED_VIDEO_DISC_AUDIO:						terminalTypeString = "StringEmbeddedVideoDiscAudio";					break;
		case EMBEDDED_DVD_AUDIO:							terminalTypeString = "StringEmbeddedDVDAudio";							break;
		case EMBEDDED_TV_TUNER_AUDIO:						terminalTypeString = "StringEmbeddedTVTunerAudio";						break;
		case EMBEDDED_SATELLITE_RECEIVER_AUDIO:				terminalTypeString = "StringEmbeddedSatelliteReceiverAudio";			break;
		case EMBEDDED_CABLE_TUNER_AUDIO:					terminalTypeString = "StringEmbeddedCableTunerAudio";					break;
		case EMBEDDED_DSS_AUDIO:							terminalTypeString = "StringEmbeddedDSSAudio";							break;
		case EMBEDDED_RADIO_RECEIVER:						terminalTypeString = "StringEmbeddedRadioReceiver";						break;
		case EMBEDDED_RADIO_TRANSMITTER:					terminalTypeString = "StringEmbeddedRadioTransmitter";					break;
		case EMBEDDED_MULTITRACK_RECORDER:					terminalTypeString = "StringEmbeddedMultitrackRecorder";				break;
		case EMBEDDED_SYNTHESIZER:							terminalTypeString = "StringEmbeddedSynthesizer";						break;
		default:											terminalTypeString = "StringUnknown";									break;
	#else
		case INPUT_UNDEFINED:								terminalTypeString = "InputUndefined";									break;
		case INPUT_MICROPHONE:								terminalTypeString = "Microphone";										break;
		case INPUT_DESKTOP_MICROPHONE:						terminalTypeString = "Desktop Microphone";								break;
		case INPUT_PERSONAL_MICROPHONE:						terminalTypeString = "Personal Microphone";								break;
		case INPUT_OMNIDIRECTIONAL_MICROPHONE:				terminalTypeString = "Omnidirectional Microphone";						break;
		case INPUT_MICROPHONE_ARRAY:						terminalTypeString = "Microphone Array";								break;
		case INPUT_PROCESSING_MICROPHONE_ARRAY:				terminalTypeString = "Processing Microphone Array";						break;
		case INPUT_MODEM_AUDIO:								terminalTypeString = "Modem Audio";										break;
		case OUTPUT_UNDEFINED:								terminalTypeString = "Output Undefined";								break;
		case OUTPUT_SPEAKER:								terminalTypeString = "Speaker";											break;
		case OUTPUT_HEADPHONES:								terminalTypeString = "Headphones";										break;
		case OUTPUT_HEAD_MOUNTED_DISPLAY_AUDIO:				terminalTypeString = "Head Mounted Display Audio";						break;
		case OUTPUT_DESKTOP_SPEAKER:						terminalTypeString = "Desktop Speaker";									break;
		case OUTPUT_ROOM_SPEAKER:							terminalTypeString = "Room Speaker";									break;
		case OUTPUT_COMMUNICATION_SPEAKER:					terminalTypeString = "Communication Speaker";							break;
		case OUTPUT_LOW_FREQUENCY_EFFECTS_SPEAKER:			terminalTypeString = "Low Frequency Effects Speaker";					break;
		case BIDIRECTIONAL_UNDEFINED:						terminalTypeString = "Bidirectional Undefined";							break;
		case BIDIRECTIONAL_HANDSET:							terminalTypeString = "Bidirectional Handset";							break;
		case BIDIRECTIONAL_HEADSET:							terminalTypeString = "Bidirectional Headset";							break;
		case BIDIRECTIONAL_SPEAKERPHONE_NO_ECHO_REDX:		terminalTypeString = "Bidirectional Speakerphone No Echo Redx";			break;
		case BIDIRECTIONAL_ECHO_SUPPRESSING_SPEAKERPHONE:	terminalTypeString = "Bidirectional Echo Suppressing Speakerphone";		break;
		case BIDIRECTIONAL_ECHO_CANCELING_SPEAKERPHONE:		terminalTypeString = "Bidirectional Echo Canceling Speakerphone";		break;
		case TELEPHONY_UNDEFINED:							terminalTypeString = "Telephone Undefined";								break;
		case TELEPHONY_PHONE_LINE:							terminalTypeString = "Telephone Line";									break;
		case TELEPHONY_TELEPHONE:							terminalTypeString = "Telephone";										break;
		case TELEPHONY_DOWN_LINE_PHONE:						terminalTypeString = "Down Line Phone";									break;
		case EXTERNAL_UNDEFINED:							terminalTypeString = "External Undefined";								break;
		case EXTERNAL_ANALOG_CONNECTOR:						terminalTypeString = "External Analog Connector";						break;
		case EXTERNAL_DIGITAL_AUDIO_INTERFACE:				terminalTypeString = "External Digital Audio Interface";				break;
		case EXTERNAL_LINE_CONNECTOR:						terminalTypeString = "External Line Connector";							break;
		case EXTERNAL_LEGACY_AUDIO_CONNECTOR:				terminalTypeString = "External Legacy Audio Connector";					break;
		case EXTERNAL_SPDIF_INTERFACE:						terminalTypeString = "External SPDIF Interface";						break;
		case EXTERNAL_1394_DA_STREAM:						terminalTypeString = "External 1394 DA Stream";							break;
		case EXTERNAL_1394_DV_STREAM_SOUNDTRACK:			terminalTypeString = "External 1394 DV Stream Soundtrack";				break;
		case EMBEDDED_UNDEFINED:							terminalTypeString = "Embedded Undefined";								break;
		case EMBEDDED_LEVEL_CALIBRATION_NOISE_SOURCE:		terminalTypeString = "Embedded Level Calibration Noise Source";			break;
		case EMBEDDED_EQUALIZATION_NOISE:					terminalTypeString = "Embedded Equalization Noise";						break;
		case EMBEDDED_CD_PLAYER:							terminalTypeString = "Embedded CD Player";								break;
		case EMBEDDED_DAT:									terminalTypeString = "Embedded DAT";									break;
		case EMBEDDED_DCC:									terminalTypeString = "Embedded DCC";									break;
		case EMBEDDED_MINIDISK:								terminalTypeString = "Embedded Mini Disc";								break;
		case EMBEDDED_ANALOG_TAPE:							terminalTypeString = "Embedded Analog Tape";							break;
		case EMBEDDED_PHONOGRAPH:							terminalTypeString = "Embedded Phonograph";								break;
		case EMBEDDED_VCR_AUDIO:							terminalTypeString = "Embedded VCR Audio";								break;
		case EMBEDDED_VIDEO_DISC_AUDIO:						terminalTypeString = "Embedded Video Disc Audio";						break;
		case EMBEDDED_DVD_AUDIO:							terminalTypeString = "Embedded DVD Audio";								break;
		case EMBEDDED_TV_TUNER_AUDIO:						terminalTypeString = "Embedded TV Tuner Audio";							break;
		case EMBEDDED_SATELLITE_RECEIVER_AUDIO:				terminalTypeString = "Embedded Satellite Receiver Audio";				break;
		case EMBEDDED_CABLE_TUNER_AUDIO:					terminalTypeString = "Embedded Cable Tuner Audio";						break;
		case EMBEDDED_DSS_AUDIO:							terminalTypeString = "Embedded DSS Audio";								break;
		case EMBEDDED_RADIO_RECEIVER:						terminalTypeString = "Embedded Radio Receiver";							break;
		case EMBEDDED_RADIO_TRANSMITTER:					terminalTypeString = "Embedded Radio Transmitter";						break;
		case EMBEDDED_MULTITRACK_RECORDER:					terminalTypeString = "Embedded Multitrack Recorder";					break;
		case EMBEDDED_SYNTHESIZER:							terminalTypeString = "Embedded Synthesizer";							break;
		default:											terminalTypeString = "Unknown";											break;
	#endif
	}

	return terminalTypeString;
}

IOReturn AppleUSBAudioDevice::deviceRequest (IOUSBDevRequestDesc * request, IOUSBCompletion * completion) {
	IOReturn						result;
	UInt32							timeout;
	Boolean							done;

	result = kIOReturnSuccess;
	FailIf (NULL == mInterfaceLock, Exit);
	IORecursiveLockLock (mInterfaceLock);

	if (FALSE == mTerminatingDriver) 
	{
		done = FALSE;
		timeout = 5;
		while (!done && timeout && mControlInterface) 
		{
			result = mControlInterface->DeviceRequest (request, completion);
			if (result != kIOReturnSuccess) 
			{
				timeout--;
				IOSleep (1);
			} 
			else 
			{
				done = TRUE;
			}
		}
    }
	IORecursiveLockUnlock (mInterfaceLock);
	#if LOGDEVICEREQUESTS
	debugIOLog ("? AppleUSBAudioDevice[%p]::deviceRequest (%p, %p) = %lx", this, request, completion, result);
	#endif
Exit:
	return result;
}

IOReturn AppleUSBAudioDevice::deviceRequest (IOUSBDevRequest * request, IOUSBCompletion * completion) {
	IOReturn						result;
	UInt32							timeout;
	Boolean							done;

	result = kIOReturnSuccess;
	FailIf (NULL == mInterfaceLock, Exit);
	IORecursiveLockLock (mInterfaceLock);

	if (FALSE == mTerminatingDriver) 
	{
		done = FALSE;
		timeout = 5;
		while (!done && timeout && mControlInterface) 
		{
			result = mControlInterface->DeviceRequest (request, completion);
			if (result != kIOReturnSuccess) 
			{
				timeout--;
				IOSleep (1);
			} 
			else 
			{
				done = TRUE;
			}
		}
    }
	IORecursiveLockUnlock (mInterfaceLock);
	#if LOGDEVICEREQUESTS
	debugIOLog ("? AppleUSBAudioDevice[%p]::deviceRequest (%p, %p) = %lx", this, request, completion, result);
	#endif
Exit:
	return result;
}

IOReturn AppleUSBAudioDevice::deviceRequest (IOUSBDevRequest *request, AppleUSBAudioDevice * self, IOUSBCompletion *completion) {
	IOReturn						result;
	UInt32							timeout;
	Boolean							done;

	result = kIOReturnSuccess;
	FailIf (NULL == self->mInterfaceLock, Exit);
	IORecursiveLockLock (self->mInterfaceLock);

	if (FALSE == self->mTerminatingDriver) 
	{
		done = FALSE;
		timeout = 5;
		while (!done && timeout && self->mControlInterface) 
		{
			result = self->mControlInterface->DeviceRequest (request, completion);
			if (result != kIOReturnSuccess) 
			{
				timeout--;
				IOSleep (1);
			} 
			else 
			{
				done = TRUE;
			}
		}
	}
	IORecursiveLockUnlock (self->mInterfaceLock);
	#if LOGDEVICEREQUESTS
	debugIOLog ("? AppleUSBAudioDevice[%p]::deviceRequest (%p, %p) = %lx", self, request, completion, result);
	#endif
Exit:
	return result;
}

bool AppleUSBAudioDevice::willTerminate (IOService * provider, IOOptionBits options) {
	debugIOLog ("+ AppleUSBAudioDevice[%p]::willTerminate (%p)", this, provider);

	if (mControlInterface == provider) 
	{
		mTerminatingDriver = TRUE;
	}

	debugIOLog ("- AppleUSBAudioDevice[%p]::willTerminate ()", this);

	return super::willTerminate (provider, options);
}

void AppleUSBAudioDevice::setConfigurationApp (const char *bundleID) {
	setConfigurationApplicationBundle (bundleID);
}

#ifdef DEBUG

void AppleUSBAudioDevice::retain() const
{
//    debugIOLog("AppleUSBAudioDevice(%p)::retain() - rc=%d", this, getRetainCount());
    super::retain();
}

void AppleUSBAudioDevice::release() const
{
//    debugIOLog("AppleUSBAudioDevice(%p)::release() - rc=%d", this, getRetainCount());
	super::release();
}

#endif

// Implemented for rdar://3993906 . Allows matching based on a custom dictionary.
bool AppleUSBAudioDevice::matchPropertyTable(OSDictionary * table, SInt32 *score)
{
	bool		returnValue = false;
	OSObject *	deviceName;
	
	// debugIOLog ("+AppleUSBAudioDevice[%p]::matchPropertyTable (%p, %p)", this, table, score);
	deviceName = table->getObject(kIOAudioDeviceNameKey);
	if (deviceName)
	{
		// This custom dictionary wants the device to have a name.
		if (getProperty (kIOAudioDeviceNameKey))
		{
			// No need to match name; it exists, and that's all that matters
			returnValue = true;
		}
		else
		{
			// Device doesn't have a name yet, so don't match
			returnValue = false;
		}
	}
	else
	{
		// This is our standard matchPropertyTable implementation
		returnValue = super::matchPropertyTable (table, score);
	}
	
	if (    (deviceName)
		 && (returnValue))
	{
		debugIOLog ("? AppleUSBAudioDevice[%p]::matchPropertyTable (%p, %p) = %d (custom dictionary match)", 
					this, table, score, returnValue);
	}
	
	return returnValue;
}

#pragma mark Anchored Time Stamps Methods

#if DEBUGANCHORS
void AppleUSBAudioDevice::accumulateAnchor (UInt64 anchorFrame, AbsoluteTime timeStamp)
{
	UInt32		i;
	
	if (mAnchorFrames[(kAnchorsToAccumulate - 1)] == 0ull)
	{
		// Find the last empty space and store this anchor frame and time stamp
		for ( i =  0; i < kAnchorsToAccumulate ; i++)
		{
			if (0 == mAnchorFrames[i])
			{
				mAnchorFrames[i] = anchorFrame;
				mAnchorTimes[i] = timeStamp;
				break;
			}
		}
	}
	
	if (mAnchorFrames[(kAnchorsToAccumulate - 1)] != 0ull)
	{
		UInt64	time_nanos;
		// Maximum number of anchors has already been accumulated.
		debugIOLog ("? AppleUSBAudioDevice::accumulateAnchor () - Frame # %d accumulated.", kAnchorsToAccumulate);
		for (i = 0; i < kAnchorsToAccumulate ; i++)
		{
			absolutetime_to_nanoseconds (mAnchorTimes[i], &time_nanos);
			debugIOLog ("  - %llu \t %llu", mAnchorFrames[i], time_nanos);
		}
		
		// Reset data
		for (i = 0; i < kAnchorsToAccumulate ; i++)
		{
			mAnchorFrames[i] = 0ull;
		}
	}
}
#endif

IOReturn AppleUSBAudioDevice::getAnchorFrameAndTimeStamp (UInt64 *frame, AbsoluteTime *time) {
	AbsoluteTime	finishTime;
	AbsoluteTime	offset;
	AbsoluteTime	curTime;
	AbsoluteTime	thisTime;
	UInt64			thisFrame;
	IOReturn		result = kIOReturnError;
	
	FailIf (NULL == mControlInterface, Exit);
	if (NULL == frame)
	{
		frame = &mNewReferenceUSBFrame;
	}
	if (NULL == time)
	{
		time = &mNewReferenceWallTime;
	}
	nanoseconds_to_absolutetime (1100000, &offset);
	clock_get_uptime (&finishTime);
	ADD_ABSOLUTETIME (&finishTime, &offset);	// finishTime is when we timeout
	
	thisFrame = mControlInterface->GetDevice()->GetBus()->GetFrameNumber ();
	// spin until the frame changes
	do
	{
		clock_get_uptime (&curTime);
	} while (		(mControlInterface)
				&&	(thisFrame == mControlInterface->GetDevice()->GetBus()->GetFrameNumber ()) 
				&&	(CMP_ABSOLUTETIME (&finishTime, &curTime) > 0));

	clock_get_uptime (&thisTime);
	FailIf (CMP_ABSOLUTETIME (&finishTime, &curTime) < 0, Exit);		// if we timed out
	*frame = ++thisFrame;
	*time = thisTime;

	result = kIOReturnSuccess;
Exit:
	return result;
}

IOReturn AppleUSBAudioDevice::getFrameAndTimeStamp (UInt64 *frame, AbsoluteTime *time)
{
	IOReturn	result = kIOReturnError;
	do
	{
		FailIf (NULL == mControlInterface, Exit);
		*frame = mControlInterface->GetDevice()->GetBus()->GetFrameNumber ();
		clock_get_uptime (time);
	} while	(		(mControlInterface)
				&&	(*frame != mControlInterface->GetDevice()->GetBus()->GetFrameNumber ()));
	
	result = kIOReturnSuccess;
Exit:
	return result;
}

UInt64 AppleUSBAudioDevice::getWallTimeInNanos (void)
{
	AbsoluteTime	time;
	UInt64			timeInNanos;
	
	clock_get_uptime (&time);
	absolutetime_to_nanoseconds (time, &timeInNanos);
	
	return timeInNanos;
}

// First order recursive filter for eliminating jitter from time measurements
UInt64 AppleUSBAudioDevice::jitterFilter (UInt64 invCoefficient, UInt64 prev, UInt64 curr) {
	UInt64 filteredValue;
	
	if (0llu != invCoefficient)
	{ 
		// Execute a low pass filter on the new rate
		filteredValue = curr + (invCoefficient - 1) * prev;
		filteredValue += invCoefficient / 2;
		filteredValue /= invCoefficient;
	// debugIOLog ("filtered value () = %llu", filteredValue);
	}
	else
	{
		filteredValue = curr;
	}
	return filteredValue;
}

void AppleUSBAudioDevice::updateWallTimePerUSBCycle () {
	UInt64			currentUSBFrame;
	UInt64			newWallTimePerUSBCycle;
	AbsoluteTime	time;
	UInt64			time_nanos;
	
	#if DEBUGTIMER
		debugIOLog ("+ AppleUSBAudioDevice::updateWallTimePerUSBCycle ()");
	#endif
	FailIf (NULL == mControlInterface, Exit);
	
	// Get wall time for the current USB frame
	FailIf (kIOReturnSuccess != getFrameAndTimeStamp (&currentUSBFrame, &time), Exit);
	
	if (0ull == mNewReferenceUSBFrame)
	{
		// Get first reference frame and time
		if (kIOReturnSuccess != getAnchorFrameAndTimeStamp (&mNewReferenceUSBFrame, &mNewReferenceWallTime))
		{
			// Just take these values for now since we couldn't get our anchor
			debugIOLog ("! AppleUSBAudioDevice[%p]::updateWallTimePerUSBCycle () - Couldn't get a solid first anchor!", this);
			mNewReferenceUSBFrame = currentUSBFrame;
			mNewReferenceWallTime = time;
		}
		debugIOLog ("? AppleUSBAudioDevice[%p]::updateWallTimePerUSBCycle () - NOTICE: reference frame = %llu", this, mNewReferenceUSBFrame);
	}
	else
	{
		// Convert current time to nanoseconds
		absolutetime_to_nanoseconds (time, &time_nanos);
	}
	
	if (0ull == mLastUSBFrame)
	{
		// Initialize last reference frame and time
		debugIOLog ("? AppleUSBAudioDevice[%p]::updateWallTimePerUSBCycle () - NOTICE: initializing last USB frame and last wall time", this);
		mLastUSBFrame = mNewReferenceUSBFrame;
		absolutetime_to_nanoseconds (mNewReferenceWallTime, &mLastWallTime_nanos);
	}
	else
	{
		// Compute new slope
		FailIf (currentUSBFrame == mLastUSBFrame, Exit);
		newWallTimePerUSBCycle = (time_nanos - mLastWallTime_nanos) * kWallTimeExtraPrecision / (currentUSBFrame - mLastUSBFrame);
		// debugIOLog ("mWallTimePerUSBCycle = %llu, newWallTimePerUSBCycle = %llu", mWallTimePerUSBCycle, newWallTimePerUSBCycle);
		
		if (0ull == mWallTimePerUSBCycle)
		{
			// This is our first estimate. Just assign it
			mWallTimePerUSBCycle = newWallTimePerUSBCycle;
			debugIOLog ("+NOTICE: Initializing mWallTimePerUSBCycle = %llu", mWallTimePerUSBCycle);
		}
		else
		{
			// Need to filter and update mWallTimePerUSBCycle
			UInt64 a = 1024;	// (1 / filterCoefficient)
			
			mWallTimePerUSBCycle = jitterFilter (a, mWallTimePerUSBCycle, newWallTimePerUSBCycle);
			#if LOGWALLTIMEPERUSBCYCLE
			debugIOLog ("? AppleUSBAudioDevice[%p]::updateWallTimePerUSBCycle - mWallTimePerUSBCycle * kExtraPrecision = %llu", this, mWallTimePerUSBCycle);
			#endif
		}
		
		// Update last values
		mLastUSBFrame = currentUSBFrame;
		mLastWallTime_nanos = time_nanos;
	}
	
Exit:
	#if DEBUGTIMER
		debugIOLog ("- AppleUSBAudioDevice::updateWallTimePerUSBCycle ()");
	#endif
	return;
}

void AppleUSBAudioDevice::TimerAction (OSObject * owner, IOTimerEventSource * sender) {
	AppleUSBAudioDevice *	self;
	
	#if DEBUGTIMER
		debugIOLog ("+ AppleUSBAudioDevice::TimerAction (%p, %p)", owner, sender);
	#endif
	FailIf (NULL == owner, Exit);
	self = (AppleUSBAudioDevice *) owner;
	FailIf (NULL == self, Exit);
	self->doTimerAction ( sender );
Exit:
	#if DEBUGTIMER
		debugIOLog ("- AppleUSBAudioDevice::TimerAction ()");
	#endif
	return;
}

void AppleUSBAudioDevice::doTimerAction (IOTimerEventSource * timer) {
	// This method updates our running wall time per USB cycle every kRefreshInterval ms.
	// After kRefreshCount updates, we re-anchor our reference frame and time to avoid accumulating error.
	
	// This timer thread may also be used to perform routine watchdog-type events.
	
	#if DEBUGTIMER
		debugIOLog ("+ AppleUSBAudioDevice::doTimerAction (%p)", timer);
	#endif
	FailIf (NULL == timer, Exit);
	
	updateWallTimePerUSBCycle ();
	mAnchorResetCount++;
	
	// Perform any watchdog-type events here.
	if (mFailingAudioEngine)
	{
		debugIOLog ("! AppleUSBAudioDevice[%p]::doTimerAction () - Detected failing audio engine (%p)! Performing emergency format change.", this, mFailingAudioEngine);
		// Attempt to synchronize the input and output sample rates.
		formatChangeController (mFailingAudioEngine, NULL, NULL, NULL);
		mFailingAudioEngine = NULL;
		setSingleSampleRateDevice (true);
	}
	
	if ( mAnchorResetCount >= kRefreshCount)
	{
		// re-anchor our reference frame and time
		FailIf (NULL == mControlInterface, Exit);
		if (kIOReturnSuccess == getAnchorFrameAndTimeStamp (&mNewReferenceUSBFrame, &mNewReferenceWallTime))
		{
			#if DEBUGTIMESTAMPS
			UInt64	newReferenceWallTime_nanos;
			absolutetime_to_nanoseconds (mNewReferenceWallTime, &newReferenceWallTime_nanos);
			debugIOLog ("? AppleUSBAudioDevice::doTimerAction () - New anchor! mNewReferenceUSBFrame = %llu, mNewReferenceWallTime = %llu", mNewReferenceUSBFrame, newReferenceWallTime_nanos);
			#endif
			#if DEBUGANCHORS
			accumulateAnchor (mNewReferenceUSBFrame, mNewReferenceWallTime);
			#endif
		}
		else
		{
			#if DEBUGTIMESTAMPS
			debugIOLog ("! AppleUSBAudioDevice::doTimerAction () - ERROR: Couldn't get new anchor! Keeping old anchor ...\n");
			#endif
		}
		
		// reset the counter
		mAnchorResetCount = 0;
	}
	
	// Schedule the next anchor frame and time update
	if (timer)
	{
		timer->setTimeoutMS ( kRefreshInterval );
	}
Exit:
	#if DEBUGTIMER
		debugIOLog ("- AppleUSBAudioDevice::doTimerAction ()");
	#endif
	return;
}

#pragma mark Format Change Methods


UInt32 AppleUSBAudioDevice::formatChangeController (IOAudioEngine *audioEngine, IOAudioStream *audioStream, const IOAudioStreamFormat *newFormat, const IOAudioSampleRate *newSampleRate)
{
	AppleUSBAudioEngine *			thisAudioEngine = NULL;
	AppleUSBAudioEngine *			otherAudioEngine = NULL;
	IOAudioStream *					thisStream = NULL;
	IOAudioStream *					otherStream = NULL;
	const IOAudioStreamFormat *		thisFormat;
	const IOAudioStreamFormat *		otherFormat;
	const IOAudioSampleRate *		thisSampleRate;
	const IOAudioSampleRate *		otherSampleRate;
	UInt32							result = kAUAFormatChangeError;
	IOReturn						formatChangeReturnCode = kIOReturnError;
	bool							mustMatchFormats;
	bool							enginesPaused = false;
	
	debugIOLog ("+ AppleUSBAudioDevice[%p]::formatChangeController (%p, %p, %p, %p)", this, audioEngine, audioStream, newFormat, newSampleRate);
	
	thisAudioEngine = (AppleUSBAudioEngine *) audioEngine;
	
	mustMatchFormats = (		(		(NULL == audioStream)
									&&	(mRegisteredEngines) 
									&&	(2 == mRegisteredEngines->getCount ()))
							||	(true == mSingleSampleRateDevice));
	
	if (mustMatchFormats)
	{
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - Attempting to match this format with the format for the other stream interface.", this);
		result = kAUAFormatChangeForceFailure;
		
		if (NULL == thisAudioEngine)
		{
			// Get both engines.
			FailIf (kIOReturnSuccess != getBothEngines (&thisAudioEngine, &otherAudioEngine), Exit);
		}
		else
		{
			// Just get the other engine.
			otherAudioEngine = otherEngine (thisAudioEngine);
		}
		FailIf (NULL == thisAudioEngine, Exit);
		FailIf (NULL == otherAudioEngine, Exit);
		
		thisAudioEngine->pauseAudioEngine ();
		otherAudioEngine->pauseAudioEngine ();
		enginesPaused = true;
		
		// Get formats and other sample rate.
		thisStream = thisAudioEngine->mMainStream;
		otherStream = otherAudioEngine->mMainStream;
		FailIf (NULL == thisStream, Exit);
		FailIf (NULL == otherStream, Exit);
		thisFormat = thisStream->getFormat ();
		otherFormat = otherStream->getFormat ();
		FailIf (NULL == thisFormat, Exit);
		FailIf (NULL == otherFormat, Exit);
		thisSampleRate = thisAudioEngine->getSampleRate ();
		otherSampleRate = otherAudioEngine->getSampleRate ();
		FailIf (NULL == thisSampleRate, Exit);
		FailIf (NULL == otherSampleRate, Exit);
		
		// Log what we have so far.
		debugIOLog ("\n");
		debugIOLog ("-------------------- BEFORE --------------------");
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - engine %p (interface %d, alternateSetting %d) info:", this, thisAudioEngine, thisAudioEngine->mInterfaceNumber, thisAudioEngine->mAlternateSettingID);
		debugIOLog ("    thisFormat = %p", thisFormat);
		debugIOLog ("        fNumChannels = %d", thisFormat->fNumChannels);
		debugIOLog ("        fBitDepth = %d", thisFormat->fBitDepth);
		debugIOLog ("        fDriverTag = 0x%x", thisFormat->fDriverTag);
		debugIOLog ("    thisSampleRate->whole = %lu", thisSampleRate->whole);
		debugIOLog ("\n");
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - engine %p (interface %d, alternateSetting %d) info:", this, otherAudioEngine, otherAudioEngine->mInterfaceNumber, otherAudioEngine->mAlternateSettingID);
		debugIOLog ("    otherFormat = %p", otherFormat);
		debugIOLog ("        fNumChannels = %d", otherFormat->fNumChannels);
		debugIOLog ("        fBitDepth = %d", otherFormat->fBitDepth);
		debugIOLog ("        fDriverTag = 0x%x", otherFormat->fDriverTag);
		debugIOLog ("    otherSampleRate->whole = %lu", otherSampleRate->whole);
		debugIOLog ("\n");
		debugIOLog (" AppleUSBAudioDevice[%p]::formatChangeController () - newFormat = %p", this, newFormat);
		if (newFormat)
		{
			debugIOLog ("        fNumChannels = %d", newFormat->fNumChannels);
			debugIOLog ("        fBitDepth = %d", newFormat->fBitDepth);
			debugIOLog ("        fDriverTag = 0x%x", newFormat->fDriverTag);
		}
		debugIOLog ("------------------------------------------------");
		debugIOLog ("\n");
		
		if (false == mSingleSampleRateDevice)
		{
			// This is an emergency format change. We need to determine which engine(s) need(!s) to get a format change. Our order of preferability is:
			//		1. This engine should change if the sample rate can be matched at the current bit depth and channel count. If not,
			//		2. The other engine should change if this engine's reported sample rate can be matched at the other engine's current bit depth and channel count. If not,
			//		3. Both engines should return to their default settings, which had better be compatible.
			
			if (255 != mUSBAudioConfig->FindAltInterfaceWithSettings (thisAudioEngine->mInterfaceNumber, thisFormat->fNumChannels, thisFormat->fBitDepth, otherSampleRate->whole))
			{
				// We'll change this engine only.
				formatChangeReturnCode = thisAudioEngine->controlledFormatChange (NULL, NULL, otherSampleRate);
				if (kIOReturnSuccess == formatChangeReturnCode)
				{
					debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - This engine (%p) sample rate changed successfully to %lu.", this, thisAudioEngine, otherSampleRate->whole);
					result = kAUAFormatChangeForced;
					thisAudioEngine->hardwareSampleRateChanged (otherSampleRate);
				}
			}
			else if (255 != mUSBAudioConfig->FindAltInterfaceWithSettings (otherAudioEngine->mInterfaceNumber, otherFormat->fNumChannels, otherFormat->fBitDepth, thisSampleRate->whole))
			{
				// We'll change the other engine only.
				formatChangeReturnCode = otherAudioEngine->controlledFormatChange (NULL, NULL, thisSampleRate);
				if (kIOReturnSuccess == formatChangeReturnCode)
				{
					debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - Other engine (%p) sample rate changed succsesfully to %lu.", this, otherAudioEngine, thisSampleRate->whole);
					result = kAUAFormatChangeForced;
					otherAudioEngine->hardwareSampleRateChanged (thisSampleRate);
				}
			}
			else
			{
				// We'll restore both engines to their default settings.
				debugIOLog ("! AppleUSBAudioDevice[%p]::formatChangeController () - Restoring both engines to their default settings.", this);
				formatChangeReturnCode = thisAudioEngine->controlledFormatChange (thisStream, &(thisAudioEngine->mDefaultAudioStreamFormat), &(thisAudioEngine->mDefaultAudioSampleRate));
				if (kIOReturnSuccess == formatChangeReturnCode)
				{
					debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - This engine (%p) restored to default settings succsesfully.", this, thisAudioEngine);
					result = kAUAFormatChangeForced;
					thisAudioEngine->hardwareSampleRateChanged (&(thisAudioEngine->mDefaultAudioSampleRate));
				}
				formatChangeReturnCode = otherAudioEngine->controlledFormatChange (otherStream, &(otherAudioEngine->mDefaultAudioStreamFormat), &(otherAudioEngine->mDefaultAudioSampleRate));
				if (kIOReturnSuccess == formatChangeReturnCode)
				{
					debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - Other engine (%p) restored to default settings succsesfully.", this, otherAudioEngine);
					otherAudioEngine->hardwareSampleRateChanged (&(otherAudioEngine->mDefaultAudioSampleRate));
				}
				else
				{
					result = kAUAFormatChangeForceFailure;
				}
			}
		} // Emergency format change case
		else
		{
			// This device is already known to be a single-sample rate device. If the sample rate is changing, change it for both engines or change it for neither.
			if (		(newSampleRate)
					&&	(newSampleRate->whole != otherSampleRate->whole))
			{
				// See if we can the change the other sample rate at the current format.
				if (255 != mUSBAudioConfig->FindAltInterfaceWithSettings (otherAudioEngine->mInterfaceNumber, otherFormat->fNumChannels, otherFormat->fBitDepth, newSampleRate->whole))
				{
					// Issue format changes to both engines.
					formatChangeReturnCode = thisAudioEngine->controlledFormatChange (thisStream, newFormat, newSampleRate);
					if (kIOReturnSuccess == formatChangeReturnCode)
					{
						result = kAUAFormatChangeForced;
						thisAudioEngine->hardwareSampleRateChanged (newSampleRate);
						formatChangeReturnCode = otherAudioEngine->controlledFormatChange (otherStream, otherFormat, newSampleRate);
						if (kIOReturnSuccess != formatChangeReturnCode)
						{
							result = kAUAFormatChangeForceFailure;
						}
						else
						{
							otherAudioEngine->hardwareSampleRateChanged (newSampleRate);
						}
					}
				}
				else
				{
					// Fail this request.
					debugIOLog ("! AppleUSBAudioDevice[%p]::formatChangeController () - Other audio engine (%p) does not support sample rate %lu at %d bit %d channel(s). Failing.", 
								this, otherAudioEngine, newSampleRate->whole, otherFormat->fBitDepth, otherFormat->fNumChannels);
					result = kAUAFormatChangeForceFailure;
				}
			}
			else
			{
				// The sample rate isn't changing, so process this request normally.
				result = kAUAFormatChangeNormal;
				formatChangeReturnCode = (thisAudioEngine->controlledFormatChange (audioStream, newFormat, newSampleRate));
			}
		}
		
		FailIf (NULL == thisAudioEngine, Exit);
		FailIf (NULL == otherAudioEngine, Exit);
		thisStream = thisAudioEngine->mMainStream;
		otherStream = otherAudioEngine->mMainStream;
		FailIf (NULL == thisStream, Exit);
		FailIf (NULL == otherStream, Exit);
		thisFormat = thisStream->getFormat ();
		otherFormat = otherStream->getFormat ();
		FailIf (NULL == thisFormat, Exit);
		FailIf (NULL == otherFormat, Exit);
		
		// Log the new values.
		debugIOLog ("\n");
		debugIOLog ("-------------------- AFTER --------------------");
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - engine %p (interface %d, alternateSetting %d) info:", this, thisAudioEngine, thisAudioEngine->mInterfaceNumber, thisAudioEngine->mAlternateSettingID);
		debugIOLog ("    thisFormat = %p", thisFormat);
		debugIOLog ("        fNumChannels = %d", thisFormat->fNumChannels);
		debugIOLog ("        fBitDepth = %d", thisFormat->fBitDepth);
		debugIOLog ("        fDriverTag = 0x%x", thisFormat->fDriverTag);
		debugIOLog ("    thisSampleRate->whole = %lu", thisSampleRate->whole);
		debugIOLog ("\n");
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - engine %p (interface %d, alternateSetting %d) info:", this, otherAudioEngine, otherAudioEngine->mInterfaceNumber, otherAudioEngine->mAlternateSettingID);
		debugIOLog ("    otherFormat = %p", otherFormat);
		debugIOLog ("        fNumChannels = %d", otherFormat->fNumChannels);
		debugIOLog ("        fBitDepth = %d", otherFormat->fBitDepth);
		debugIOLog ("        fDriverTag = 0x%x", otherFormat->fDriverTag);
		debugIOLog ("    otherSampleRate->whole = %lu", otherSampleRate->whole);
		debugIOLog ("-----------------------------------------------");
		debugIOLog ("\n");
	}
	else
	{
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - Attempting normal format change request.", this);
		result = kAUAFormatChangeNormal;
		// Issue this format change request.
		formatChangeReturnCode = thisAudioEngine->controlledFormatChange (audioStream, newFormat, newSampleRate);
	}
	
	if (kIOReturnSuccess != formatChangeReturnCode)
	{
		result = kAUAFormatChangeError;
		debugIOLog ("! AppleUSBAudioDevice[%p]::formatChangeController () - This format change failed with error 0x%x.", this, result);
	}
	else
	{
		debugIOLog ("? AppleUSBAudioDevice[%p]::formatChangeController () - This format change was successful.", this);
		if	(		(kAUAFormatChangeNormal != result)
				&&	(kAUAFormatChangeForced != result))
		{
			debugIOLog ("! AppleUSBAudioDevice[%p]::formatChangeController () - Forced format change failed.", this);
			result = kAUAFormatChangeForceFailure;
		}
	}
	
Exit:
	if (enginesPaused)
	{
		thisAudioEngine->resumeAudioEngine ();
		otherAudioEngine->resumeAudioEngine ();
	}
	debugIOLog ("- AppleUSBAudioDevice[%p]::formatChangeController (%p, %p, %p, %p) = %d", this, audioEngine, audioStream, newFormat, newSampleRate, result);
	return result;
}

AppleUSBAudioEngine * AppleUSBAudioDevice::otherEngine (AppleUSBAudioEngine * thisEngine)
{
	SInt32	engineIndex;
	SInt32	otherEngineIndex;
	OSDictionary * otherAudioEngineInfo = NULL;
	AppleUSBAudioEngine * otherAudioEngine = NULL;
	
	FailIf (NULL == thisEngine, Exit);
	FailIf (NULL == mRegisteredEngines, Exit);
	engineIndex = getEngineInfoIndex (thisEngine);
		
	// Must stop here if we didn't find this engine.
	FailIf (-1 == engineIndex, Exit);
	
	otherEngineIndex = ((1 == engineIndex) ? 0 : 1);
	// The two engine indeces should be 0 and 1, so we'll NOT this engine to get the other one.
	otherAudioEngineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (otherEngineIndex));
	FailIf (NULL == otherAudioEngineInfo, Exit);
		
	otherAudioEngine = OSDynamicCast (AppleUSBAudioEngine, otherAudioEngineInfo->getObject (kEngine));
	FailIf (NULL == otherAudioEngine, Exit);
Exit:
	return otherAudioEngine;
}

IOReturn AppleUSBAudioDevice::getBothEngines (AppleUSBAudioEngine ** firstEngine, AppleUSBAudioEngine ** secondEngine)
{
	SInt32			engineIndex;
	OSDictionary *	firstAudioEngineInfo = NULL;
	IOReturn		result = kIOReturnError;
	
	FailIf (NULL == mRegisteredEngines, Exit);
	engineIndex = 0;
	
	firstAudioEngineInfo = OSDynamicCast (OSDictionary, mRegisteredEngines->getObject (engineIndex));
	// Must stop here if we didn't find this engine's dictionary.
	FailIf (NULL == firstAudioEngineInfo, Exit);
	*firstEngine = OSDynamicCast (AppleUSBAudioEngine, firstAudioEngineInfo->getObject (kEngine));
	FailIf (NULL == *firstEngine, Exit);
	
	// Now that we have the firstEngine, get the second.
	*secondEngine = otherEngine (*firstEngine);
	FailIf (NULL == secondEngine, Exit);
	result = kIOReturnSuccess;
Exit:
	return result;

}
