//--------------------------------------------------------------------------------
//
//	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 (mInterfaceLock) {
        IORecursiveLockFree (mInterfaceLock);
        mInterfaceLock = NULL;
    }

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

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

    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 ("Checking stream %d against controled stream %d", 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 ("There are %d configurations on this device", mControlInterface->GetDevice()->GetNumConfigurations ());
	debugIOLog ("Our control interface number is %d", 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 ("Num streams controlled = %d", numStreams);
	debugIOLog ("GetNumStreamInterfaces = %d", 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);
	} else {
		stringIndex = mControlInterface->GetDevice()->GetProductStringIndex ();
		if (0 != stringIndex) {
			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);

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);

	if (oldPowerState == kIOAudioDeviceSleep) {
		debugIOLog ("Waking from sleep - flushing controls to the device.");
		flushAudioControls ();
	}

	return result;
}

void AppleUSBAudioDevice::stop (IOService *provider) {

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

	super::stop (provider);  // call the IOAudioDevice generic stop routine

    if (mControlInterface) {
        mControlInterface->close (this);
        mControlInterface = 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 ("+ ShouldUpdatePRAM\nspeaker's vendorID = 0x%x", vendorID);
	if (kIOUSBVendorIDAppleComputer == vendorID || kIOUSBVendorIDHaronKardon == 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 ("Directly connected to the root hub");
			} 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 ("- ShouldUpdatePRAM result = %d", 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;
}

SInt32 AppleUSBAudioDevice::getEngineInfoIndex (AppleUSBAudioEngine * inAudioEngine) {
	OSDictionary *						engineInfo;
	AppleUSBAudioEngine *				usbAudioEngine;
	SInt32								theEngineIndex;

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

	return theEngineIndex;
}

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;
	UInt8								selectorUnitID;
	UInt8								featureUnitID;
	UInt8								controlInterfaceNum;
	UInt8								unitID;
	UInt8								outputTerminalID;
	Boolean								done;
	
	result = kIOReturnError;
	inputSelector = NULL;
	done = FALSE;

    usbAudioEngine = OSDynamicCast (AppleUSBAudioEngine, audioEngine);
    FailIf (NULL == usbAudioEngine, Exit);
	FailIf (NULL == mControlInterface, Exit);
	debugIOLog ("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 ();
	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 input 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;
						}
					}
				}
			}
		}
	} 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
										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;
									}
								}
							}
							break;		// Get out of unitIndexInPath for loop
						}
					}

					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;
						}
					}
				} 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:
	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;
	} 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;
	SInt16								volRes;
	SInt16								offset;
	UInt8								channelNum;
	UInt8								controlInterfaceNum;

	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;

	controlInterfaceNum = mControlInterface->GetInterfaceNumber ();
	for (channelNum = 0; channelNum <= mUSBAudioConfig->GetNumControls (controlInterfaceNum, 0, featureUnitID); channelNum++) {
		debugIOLog ("checking channel %d for vol controls", channelNum);
		if (mUSBAudioConfig->ChannelHasVolumeControl (controlInterfaceNum, 0, featureUnitID, channelNum)) {
			debugIOLog ("creating vol controls for channel %d", channelNum);
			deviceCur = getCurVolume (featureUnitID, channelNum);
			deviceMin = getMinVolume (featureUnitID, channelNum);
			debugIOLog ("deviceMin = %x", deviceMin);
			deviceMax = getMaxVolume (featureUnitID, channelNum);
			debugIOLog ("deviceMax = %x", deviceMax);
			volRes = getVolumeResolution (featureUnitID, channelNum);
			debugIOLog ("volRes = %x", volRes);

			if ((SInt16)0x8000 == 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;

			theLevelControl = IOAudioLevelControl::createVolumeControl (deviceCur, deviceMin, deviceMax, deviceMinDB, deviceMaxDB, channelNum, 0, featureUnitID, usage);
			FailIf (NULL == theLevelControl, Exit);
			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);
					}
					break;
			}
			theLevelControl->release ();
		}
	}

	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:
	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;

	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)) {
			deviceCur = getCurMute (featureUnitID, channelNum);

			theMuteControl = IOAudioToggleControl::createMuteControl (deviceCur, channelNum, 0, featureUnitID, usage);
			setCurMute (featureUnitID, channelNum, 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;
}

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

	theSetting = 0;
	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 ();
	}
	return USBToHostWord (theSetting);
}

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, kIODirectionIn);

    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 and inactive device");
	goto Exit;
}

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

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

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

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

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

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;
	UInt8								unitID;
	UInt8								channelNum;

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

	deviceMin = getMinVolume (unitID, channelNum);
	offset = -deviceMin;

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

	debugIOLog ("setting volume to 0x%x", newVolume);
	result = setCurVolume (unitID, channelNum, HostToUSBWord (newVolume));

	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;
							}
						}
					}
				}
			}
			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;
							}
						}
					}
				}
			}
			break;
	}

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;
}

IOReturn AppleUSBAudioDevice::createControlsForInterface (IOAudioEngine *audioEngine, UInt8 interfaceNum, UInt8 altSettingNum) {
    AppleUSBAudioEngine *				usbAudioEngine;
    IOReturn							result;
    debugIOLog ("+AppleUSBAudioDevice[%p]::createControlsForInterface %d %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;
#if 0
	UInt32							h, i, j;
#endif
	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);
	}

#if 0
	for (h = 0; h < allOutputTerminalPaths->getCount (); h++) {
		IOLog ("h = %ld\n", h);
		pathsFromOutputTerminalN = OSDynamicCast (OSArray, allOutputTerminalPaths->getObject (h));
		FailIf (NULL == pathsFromOutputTerminalN, Exit);
		for (i = 0; i < pathsFromOutputTerminalN->getCount (); i++) {
			OSArray *		theArray;
			OSNumber *		theUnitIDNum;
			UInt8			unitID;
	
			theArray = OSDynamicCast (OSArray, pathsFromOutputTerminalN->getObject (i));
			if (NULL != theArray) {
				for (j = 0; j < theArray->getCount (); j++) {
					theUnitIDNum = OSDynamicCast (OSNumber, theArray->getObject (j));
					if (NULL != theUnitIDNum) {
						unitID = theUnitIDNum->unsigned8BitValue ();
						IOLog ("%x->", unitID);
					}
				}
				IOLog ("\n\n");
			}
		}
	}
#endif

Exit:
	return allOutputTerminalPaths;
}

#if 0
void AppleUSBAudioDevice::PruneInputPaths (OSArray * allOutputTerminalPaths) {
	OSArray *						allOutputTerminalPaths;
	OSArray *						pathsFromOutputTerminalN;
	OSArray *						thisPath;
	UInt32							h, i;
	UInt8							outputTerminalID;
	UInt8							inputTerminalID;

	for (h = 0; h < allOutputTerminalPaths->getCount (); h++) {
		pathsFromOutputTerminalN = OSDynamicCast (OSArray, allOutputTerminalPaths->getObject (h));
		FailIf (NULL == pathsFromOutputTerminalN, Exit);
		for (i = 0; i < pathsFromOutputTerminalN->getCount (); i++) {
			OSArray *		theArray;
			OSNumber *		theUnitIDNum;
			UInt8			unitID;
	
			theArray = OSDynamicCast (OSArray, pathsFromOutputTerminalN->getObject (i));
			if (NULL != theArray) {
				theUnitIDNum = OSDynamicCast (OSNumber, theArray->getObject (0));
				FailIf (NULL == theUnitIDNum, Exit);
				outputTerminalID = theUnitIDNum->unsigned8BitValue ();
				theUnitIDNum = OSDynamicCast (OSNumber, theArray->getLastObject ());
				FailIf (NULL == theUnitIDNum, Exit);
				inputTerminalID = theUnitIDNum->unsigned8BitValue ();

				if (mUSBAudioConfig->GetOutputTerminalType (controlInterfaceNum, 0, outputTerminalID) == mUSBAudioConfig->GetInputTerminalType (controlInterfaceNum, 0, inputTerminalID)) {
					pathsFromOutputTerminalN->removeObject (i);
					i--;
				}
			}
		}
	}

Exit:
	return
}
#endif

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;
		}
	}

Exit:
	return curPath;
}

#if 0
OSArray * AppleUSBAudioDevice::FindAllInputSources (OSArray * outputTerminalPaths, UInt8 outputTerminalID, Boolean forOutput) {
	OSArray *						inputSources;
	OSArray *						thePath;
	OSArray *						thePath2;
	OSNumber *						theUnitIDNum;
	UInt32							i, j;

	inputSources = OSArray::withCapacity (1);
	if (NULL != inputSources) {
		// Find all paths from output terminal to input terminals
		for (i = 0; i < outputTerminalPaths->getCount (); i++) {
			thePath = OSDynamicCast (OSArray, outputTerminalPaths->getObject (i));
			if (NULL != thePath) {
				theUnitIDNum = OSDynamicCast (OSNumber, thePath->getObject (0));
				if ((NULL != theUnitIDNum) && (theUnitIDNum->unsigned8BitValue () == outputTerminalID)) {
					inputSources->setObject (thePath);
				}
			}
		}
		// Prune out paths that have duplicate input terminals by taking only the shortest path to that input terminal
		for (i = 0; i < inputSources->getCount (); i++) {
			thePath = OSDynamicCast (OSArray, inputSources->getObject (i));
			if (NULL != thePath) {
				theUnitIDNum = OSDynamicCast (OSNumber, thePath->getLastObject ());
				for (j = i + 1; j < inputSources->getCount (); j++) {
					thePath2 = OSDynamicCast (OSArray, inputSources->getObject (j));
					if ((NULL != thePath2) && (NULL != theUnitIDNum) && (theUnitIDNum->unsigned8BitValue () == OSDynamicCast (OSNumber, thePath2->getLastObject ())->unsigned8BitValue ())) {
						if (thePath->getCount () > thePath2->getCount ()) {
							inputSources->removeObject (i);
						} else {
							inputSources->removeObject (j);
						}
						j--;		// Back up because removeObject coalesces the array.
					}
				}
			}
		}
	}

#if 0
	IOLog ("Input sources from OT %x are:\n", outputTerminalID);
	for (i = 0; i < inputSources->getCount (); i++) {
		OSArray *		theArray;
		OSNumber *		theUnitIDNum;
		UInt8			unitID;

		theArray = OSDynamicCast (OSArray, inputSources->getObject (i));
		if (NULL != theArray) {
			for (j = 0; j < theArray->getCount (); j++) {
				theUnitIDNum = OSDynamicCast (OSNumber, theArray->getObject (j));
				if (NULL != theUnitIDNum) {
					unitID = theUnitIDNum->unsigned8BitValue ();
					IOLog ("%x->", unitID);
				}
			}
			IOLog ("\n\n");
		}
	}
#endif

	return inputSources;
}

OSArray * AppleUSBAudioDevice::FindSelectorUnitsInPath (UInt8 controlInterfaceNum, UInt8 startingUnitID) {
	OSArray *						selectors;
	OSNumber *						theSourceID;
	UInt8 *							sourceIDs;
	UInt32							i;
	UInt8							numSources;
	UInt8							thisUnitID;
	UInt8							sourceID;
	UInt8							subType;

	selectors = NULL;
	thisUnitID = startingUnitID;
	subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, thisUnitID);
	sourceID = 0;
	while (INPUT_TERMINAL != subType) {
		switch (subType) {
			case SELECTOR_UNIT:
				numSources = mUSBAudioConfig->GetNumSources (controlInterfaceNum, 0, thisUnitID);
				sourceIDs = mUSBAudioConfig->GetSourceIDs (controlInterfaceNum, 0, thisUnitID);
				theSourceID = OSNumber::withNumber (sourceID, 8);
				if (NULL != theSourceID) {
					if (NULL == selectors) {
						selectors = OSArray::withObjects ((const OSObject **)&theSourceID, 1);
					} else {
						selectors->setObject (theSourceID);
					}
					theSourceID->release ();
					theSourceID = NULL;
				}	// fall through...
			case MIXER_UNIT:
			case EXTENSION_UNIT:
			case PROCESSING_UNIT:
				numSources = mUSBAudioConfig->GetNumSources (controlInterfaceNum, 0, thisUnitID);
				sourceIDs = mUSBAudioConfig->GetSourceIDs (controlInterfaceNum, 0, thisUnitID);
				for (i = 0; i < numSources; i++) {
					selectors->merge (FindSelectorUnitsInPath (controlInterfaceNum, sourceIDs[i]));
				}
				break;
			case OUTPUT_TERMINAL:
			case FEATURE_UNIT:
			default:
				// Should get here at least once before getting a selector unit case statement.
				sourceID = mUSBAudioConfig->GetSourceID (controlInterfaceNum, 0, thisUnitID);
				break;
		}
		thisUnitID = sourceID;
		subType = mUSBAudioConfig->GetSubType (controlInterfaceNum, 0, thisUnitID);
	}

	return selectors;
}

#endif

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;

	debugIOLog ("+AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)", this, request, completion);
	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);

	debugIOLog ("-AppleUSBAudioDevice[%p]::deviceRequest (%p, %p) = %lx", this, request, completion, result);

Exit:
	return result;
}

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

	debugIOLog ("+AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)", this, request, completion);
	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);

	debugIOLog ("-AppleUSBAudioDevice[%p]::deviceRequest (%p, %p) = %lx", this, request, completion, result);

Exit:
	return result;
}

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

	debugIOLog ("+AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)", self, request, completion);
	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);

	debugIOLog ("-AppleUSBAudioDevice[%p]::deviceRequest (%p, %p) = %lx", self, request, completion, result);

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
