//--------------------------------------------------------------------------------
//
//	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
//
//--------------------------------------------------------------------------------

#define TIMERSTREAM				FALSE
#define DOUBLEPARENT			TRUE

#include "AppleUSBAudioCommon.h"
#include "AppleUSBAudioDevice.h"
#include "AppleUSBAudioEngine.h"
#include "AppleUSBAudioLevelControl.h"
#include "AppleUSBAudioMuteControl.h"
#include "USBAudioObject.h"

#include <IOKit/IOLib.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/IORegistryEntry.h>
#include <IOKit/audio/IOAudioStream.h>
#include <IOKit/audio/IOAudioDefines.h>

#include <IOKit/usb/IOUSBInterface.h>
#include <IOKit/IOMessage.h>

#include <libkern/c++/OSCollectionIterator.h>

#define super IOAudioDevice

OSDefineMetaClassAndStructors (AppleUSBAudioDevice, super)

bool AppleUSBAudioDevice::init (OSDictionary * properties)  {
	bool		resultCode;

	debug2IOLog ("+AppleUSBAudioDevice[%p]::init ()\n", this);
	resultCode = FALSE;		// Assume failure

	// get the IOAudioDevice generic initialization
	FailIf (FALSE == super::init (properties), Exit);

	resultCode = TRUE;

Exit:
	debug2IOLog ("-AppleUSBAudioDevice[%p]::init ()\n", this);
	return resultCode;
}

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

#if DEBUG
	if (interfaceVendor && interfaceVendor->getRetainCount () == 0) Debugger ("interfaceVendor = 0");
	if (interfaceProduct && interfaceProduct->getRetainCount () == 0) Debugger ("interfaceProduct = 0");
	if (deviceReleaseNumber && deviceReleaseNumber->getRetainCount () == 0) Debugger ("deviceReleaseNumber = 0");
	if (configurationValue && configurationValue->getRetainCount () == 0) Debugger ("configurationValue = 0");
	if (interfaceNumber && interfaceNumber->getRetainCount () == 0) Debugger ("interfaceNumber = 0");
	if (usbAudio && usbAudio->getRetainCount () == 0) Debugger ("usbAudio = 0");
#endif

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

#if TIMERSTREAM
    if (deviceNotifier) {
        deviceNotifier->remove ();
        deviceNotifier = 0;
    }
#endif

    if (interfaceVendor) {
        interfaceVendor->release ();
        interfaceVendor = 0;
    }

    if (interfaceProduct) {
        interfaceProduct->release ();
        interfaceProduct = 0;
    }

    if (deviceReleaseNumber) {
        deviceReleaseNumber->release();
        deviceReleaseNumber = 0;
    }

    if (configurationValue) {
        configurationValue->release();
        configurationValue = 0;
    }

    if (interfaceNumber) {
        interfaceNumber->release();
        interfaceNumber = 0;
    }

    if (usbAudio) {
        usbAudio->release ();
        usbAudio = 0;
    }

    super::free ();
    debug2IOLog ("-AppleUSBAudioDevice[%p]::free ()\n", this);
}

#if DOUBLEPARENT
bool AppleUSBAudioDevice::requestTerminate (IOService * provider, IOOptionBits options) {
	debug4IOLog ("+AppleUSBAudioDevice[%p]::requestTerminate (%p, %x)\n", this, provider, options);
	debug4IOLog ("-AppleUSBAudioDevice[%p]::requestTerminate (%p, %x)\n", this, provider, options);
	return TRUE;		// it is OK to terminate us
}
#endif

bool AppleUSBAudioDevice::start (IOService * provider) {
    OSObject *						obj;
	UInt8							stringIndex;
	char							string[kStringBufferSize];
	IOReturn						err;
    Boolean							resultCode;

	debug3IOLog ("+AppleUSBAudioDevice[%p]::start (%p)\n", this, provider);

	resultCode = FALSE;

	controlInterface = OSDynamicCast (IOUSBInterface, provider);
	FailIf (NULL == controlInterface, Exit);
	previousInterface = controlInterface;

	debug2IOLog ("There are %d configurations on this device\n", controlInterface->GetDevice()->GetNumConfigurations ());
	usbAudio = USBAudioConfigObject::create (controlInterface->GetDevice()->GetFullConfigurationDescriptor (0));
	FailIf (NULL == usbAudio, Exit);

	// If this is an iSub, we need to not go any further because we don't support it in this driver
	// This will cause the driver to not load on any device that has _only_ a low frequency effect output terminal
	FailIf (usbAudio->GetNumOutputTerminals (0, 0) == 1 && usbAudio->GetIndexedOutputTerminalType (0, 0, 0) == OUTPUT_LOW_FREQUENCY_EFFECTS_SPEAKER, Exit);
       // IOLog ("This isn't an iSub\n");

	err = kIOReturnError;
	string[0] = 0;
	stringIndex = controlInterface->GetDevice()->GetProductStringIndex ();
	if (0 != stringIndex) {
		err = controlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
	}

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

	setDeviceName (string);

	err = kIOReturnError;
	string[0] = 0;
	stringIndex = controlInterface->GetDevice()->GetManufacturerStringIndex ();
	if (0 != stringIndex) {
		err = controlInterface->GetDevice()->GetStringDescriptor (stringIndex, string, kStringBufferSize);
	}

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

	setManufacturerName (string);

	FailIf (FALSE == controlInterface->open (this), Exit);
	// get the IOAudioDevice generic start routine
	FailIf (FALSE == super::start (provider), Exit);

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

	if (obj = controlInterface->getProperty (kUSBVendorName)) {
		obj->retain ();
		interfaceVendor = obj;
	}

	if (obj = controlInterface->getProperty (kUSBProductName)) {
		obj->retain ();
		interfaceProduct = obj;
	}

	if (obj = controlInterface->getProperty (kUSBDeviceReleaseNumber)) {
		obj->retain ();
		deviceReleaseNumber = obj;
	}

	if (obj = controlInterface->getProperty (kUSBConfigurationValue)) {
		obj->retain();
		configurationValue = obj;
	}

	if (obj = controlInterface->getProperty (kUSBInterfaceNumber)) {
		obj->retain ();
		interfaceNumber = obj;
	}

	resultCode = TRUE;

Exit:
	debug3IOLog ("-AppleUSBAudioDevice[%p]::start (%p)\n", this, provider);
	return resultCode;
}

#if 0
UInt32 AppleUSBAudioDevice::getNumAttachedAudioDevices () {
    OSDictionary *				audioDeviceMatchingDict;
    UInt32 						numAudioDevices;

    debugIOLog ("+AppleUSBAudioDevice::getNumAttachedAudioDevices ()\n");
    numAudioDevices = 0;
    audioDeviceMatchingDict = serviceMatching ("IOAudioDevice");

    if (audioDeviceMatchingDict) {
        OSIterator *	deviceIterator;

        deviceIterator = getMatchingServices (audioDeviceMatchingDict);
        if (deviceIterator) {
            IOAudioDevice *device;
            while (device = (IOAudioDevice *)deviceIterator->getNextObject ()) {
                if (device != this) {
                    numAudioDevices++;
                }
            }
            deviceIterator->release ();
        } else {
            debugIOLog ("++AppleUSBAudioDevice::getNumAttachedAudioDevices () - no device iterator created\n");
        }
        audioDeviceMatchingDict->release ();
    } else {
        debugIOLog ("++AppleUSBAudioDevice::getNumAttachedAudioDevices () - no matching dict created\n");
    }

    debugIOLog ("-AppleUSBAudioDevice::getNumAttachedAudioDevices ()\n");
    return numAudioDevices;
}
#endif

void AppleUSBAudioDevice::streamStopped (IOAudioEngine *audioEngine) {
    // This must be called from within the workLoop
    // The AppleUSBAudioEngine does so from _setState()
    // which has the same requirement
    debugIOLog ("+AppleUSBAudioDevice::streamStopped ()\n");
    
    if (allStreamsStopped()) {
        terminate();
    }
    
    debugIOLog ("-AppleUSBAudioDevice::streamStopped ()\n");
}

#if TIMERSTREAM
bool AppleUSBAudioDevice::isUnattachedDeviceRegistered () {
    debugIOLog ("AppleUSBAudioDevice::isUnattachedDeviceRegistered ()\n");
    return (deviceNotifier != NULL);
}

void AppleUSBAudioDevice::registerUnattachedDevice () {
    debugIOLog ("+AppleUSBAudioDevice::registerUnattachedDevice ()\n");
    if (!deviceNotifier) {
        OSDictionary *	matchingDict;

        matchingDict = serviceMatching ("IOUSBInterface");
        matchingDict->setObject (kUSBVendorName, interfaceVendor);
        matchingDict->setObject (kUSBProductName, interfaceProduct);
        matchingDict->setObject (kUSBDeviceReleaseNumber, deviceReleaseNumber);
        matchingDict->setObject (kUSBConfigurationValue, configurationValue);
        matchingDict->setObject (kUSBInterfaceNumber, interfaceNumber);
        matchingDict->setObject (gIOMatchCategoryKey, (OSSymbol *)gIODefaultMatchCategoryKey);
        
        deviceNotifier = addNotification(gIOPublishNotification,
                                         matchingDict,
                                         (IOServiceNotificationHandler) &interfacePublished,
                                         this,
                                         NULL,
                                         1000);
        // matchingDict->release();
    } else {
        debugIOLog ("++AppleUSBAudioDevice::registerUnattachedDevice () - error - already have a device notifier - inconsistent state.\n");
    }

    debugIOLog ("-AppleUSBAudioDevice::registerUnattachedDevice ()\n");
}

void AppleUSBAudioDevice::unregisterUnattachedDevice () {
    debugIOLog("+AppleUSBAudioDevice::unregisterUnattachedDevice ()\n");

    if (deviceNotifier) {
        deviceNotifier->remove ();
        deviceNotifier = 0;
    } else {
        debugIOLog("++AppleUSBAudioDevice::unregisterUnattachedDevice () - error - don't have a device notifier - inconsistent state.\n");
    }

    debugIOLog("-AppleUSBAudioDevice::unregisterUnattachedDevice ()\n");
}

bool AppleUSBAudioDevice::interfacePublished (AppleUSBAudioDevice *audioDevice, void *ref, IOService *newService) {
    IOUSBInterface *			interface;
    bool						result;
    IOCommandGate		*cg;

    result = TRUE;

    debugIOLog ("+AppleUSBAudioDevice::interfacePublished ()\n");

    if (interface = OSDynamicCast (IOUSBInterface, newService)) {
        cg = audioDevice->getCommandGate();
        if (cg) {
            cg->runAction(reinitWithInterfaceAction, interface, (void *)&result);
        }
    }
    
    debugIOLog ("-AppleUSBAudioDevice::interfacePublished ()\n");
    return result;
}

IOReturn AppleUSBAudioDevice::reinitWithInterfaceAction(OSObject *owner, void *arg1, void *arg2, void *arg3, void *arg4)
{
    IOReturn result = kIOReturnBadArgument;
    
    if (owner && arg1 && arg2) {
        AppleUSBAudioDevice *audioDevice = OSDynamicCast(AppleUSBAudioDevice, owner);
        IOUSBInterface *interface = OSDynamicCast(IOUSBInterface, (OSObject *)arg1);
        
        if (audioDevice) {
            *(bool *)arg2 = audioDevice->reinitWithInterface(interface);
            result = kIOReturnSuccess;
        }
    }
    
    return result;
}

bool AppleUSBAudioDevice::reinitWithInterface (IOUSBInterface *interface) {
    OSCollectionIterator *			audioEngineIterator;
    AppleUSBAudioEngine *			audioEngine;
    OSCollectionIterator *			portIterator;
    IOAudioPort *					audioPort;
    OSCollectionIterator *			controlIterator;
    IOAudioControl *				audioControl;
    AppleUSBAudioLevelControl *		levelControl;
    AppleUSBAudioMuteControl *		muteControl;
    bool							result;

    debug3IOLog("+AppleUSBAudioDevice[%p]::reinitWithInterface(%p)\n", this, interface);

    result = FALSE;
    FailIf (interface == previousInterface, Exit);
    FailIf (kIOReturnSuccess != interface->open (this), Exit);

    debug3IOLog("+AppleUSBAudioDevice[%p]::reinitWithInterface(%p) - 1\n", this, interface);
    
    controlInterface = interface;
    previousInterface = interface;
    this->attach (controlInterface);

    unregisterUnattachedDevice ();

    if (audioEngines) {
        // We need to do this stuff on the IOWorkLoop
        
        // need to tell streams not to notify us when stopped - there's a potential race here
        // because we've already committed to the new interface, but haven't told the streams
        // yet - we could still get a notification which could cause us to stop
        // probably need another lock to make sure that doesn't happen

        audioEngineIterator = OSCollectionIterator::withCollection (audioEngines);
        if (audioEngineIterator) {
            while (audioEngine = (AppleUSBAudioEngine *)audioEngineIterator->getNextObject ()) {
                if (OSDynamicCast (AppleUSBAudioEngine, audioEngine)) {
                    audioEngine->notifyDeviceOfStop = FALSE;
                }
            }

            audioEngineIterator->release ();
        }
    }

    debug3IOLog("+AppleUSBAudioDevice[%p]::reinitWithInterface(%p) - 4\n", this, interface);
    
    // Force control updates so that the device is set to the correct volume, etc...
    if (audioPorts) {
        // No portsLock anymore - need to be on IOWorkLoop

        portIterator = OSCollectionIterator::withCollection (audioPorts);
        if (portIterator) {
            while (audioPort = (IOAudioPort *)portIterator->getNextObject ()) {
                if (OSDynamicCast (IOAudioPort, audioPort)) {
                    if (audioPort->audioControls) {
                        controlIterator = OSCollectionIterator::withCollection (audioPort->audioControls);

                        if (controlIterator) {
                            while (audioControl = (IOAudioControl *)controlIterator->getNextObject ()) {
                                if (levelControl = OSDynamicCast (AppleUSBAudioLevelControl, audioControl)) {
                                    levelControl->updateUSBValue ();
                                } else if (muteControl = OSDynamicCast (AppleUSBAudioMuteControl, audioControl)) {
                                    muteControl->updateUSBValue ();
                                }
                            }
                            controlIterator->release ();
                        }
                    }
                }
            }
            portIterator->release ();
        }
    }

    debug3IOLog ("++AppleUSBAudioDevice[%p]::reinitWithInterface (%p) - successfully attached to new interface\n", this, interface);

    result = TRUE;

Exit:
    debug3IOLog ("-AppleUSBAudioDevice[%p]::reinitWithInterface (%p)\n", this, interface);
    return result;
}
#endif

bool AppleUSBAudioDevice::allStreamsStopped () {
    OSCollectionIterator *			audioEngineIterator;
    IOAudioEngine *					audioEngine;
    bool							result;

    debug2IOLog ("+AppleUSBAudioDevice[%p]::allStreamsStopped ()\n", this);
    result = TRUE;

    FailIf (NULL == audioEngines, Exit);

    // Need to do this on the IOWorkLoop
    
    audioEngineIterator = OSCollectionIterator::withCollection (audioEngines);

    if (audioEngineIterator) {
        while (audioEngine = (IOAudioEngine *)audioEngineIterator->getNextObject ()) {
            if (OSDynamicCast (AppleUSBAudioEngine, audioEngine)) {	// We only care that the AppleUSBAudioEngines are stopped
                if (audioEngine->getState () != kIOAudioEngineStopped) {
                    result = FALSE;
                    break;
                }
            }
        }
        audioEngineIterator->release ();
    }

Exit:
    debug2IOLog ("-AppleUSBAudioDevice[%p]::allStreamsStopped ()\n", this);
    return result;
}

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

	debug4IOLog ("+AppleUSBAudioDevice::performPowerStateChange (%d, %d, %p)\n", oldPowerState, newPowerState, microSecsUntilComplete);

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

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

	return result;
}

#if TIMERSTREAM
IOReturn AppleUSBAudioDevice::performUnattachedDeviceRegistrationAction (OSObject *owner, void *arg1, void *arg2, void *arg3, void *arg4) {
	IOReturn						result;
	AppleUSBAudioDevice *			audioDevice;

	result = kIOReturnBadArgument;
	if (owner && arg1) {
		audioDevice = OSDynamicCast (AppleUSBAudioDevice, owner);
		if (audioDevice) {
			*(bool *)arg1 = audioDevice->performUnattachedDeviceRegistration ();
			result = kIOReturnSuccess;
		}
	}

	return result;
}

bool AppleUSBAudioDevice::performUnattachedDeviceRegistration () {
	OSCollectionIterator 		*audioEngineIterator;
	AppleUSBAudioEngine			*audioEngine;
	bool 						isRegistered;

	isRegistered = FALSE;

	// We should stick around if we have a running audio engine
	if (audioEngines) {
		debugIOLog("++AppleUSBAudioDevice::performUnattachedDeviceRegistration () - we're the only device - checking to see if any audioEngines are running.\n");

		// We're stopping - do we need to lock this stuff? on IOWorkLoop?
		audioEngineIterator = OSCollectionIterator::withCollection (audioEngines);
		if (audioEngineIterator) {
			while (audioEngine = (AppleUSBAudioEngine *)audioEngineIterator->getNextObject ()) {
				if (OSDynamicCast (AppleUSBAudioEngine, audioEngine)) {	// We only care about runninng AppleUSBAudioEngine objects

					// Nothing more to lock here - definitely need to be on the IOWorkLoop
					if (audioEngine->getState () == kIOAudioEngineRunning) {
						debug2IOLog ("+++AppleUSBAudioDevice::performUnattachedDeviceRegistration () - found a running audio engine: %p - sticking around until a re-attach or all audio engines stop.\n", audioEngine);
						isRegistered = TRUE;

						audioEngine->notifyDeviceOfStop = TRUE;

						if (!isUnattachedDeviceRegistered ()) {
							// register for new USB device notification
							registerUnattachedDevice ();
							if (!hasBeenUnattached) {
								retain ();
								hasBeenUnattached = TRUE;
							}
						}
						break;
					}
				}
			}
			audioEngineIterator->release ();
		}
	}

	return isRegistered;
}
#endif

void AppleUSBAudioDevice::stop (IOService *provider) {
	bool			shouldStop;

#if TIMERSTREAM
	debug6IOLog ("+AppleUSBAudioDevice[%p]::stop (%p) - audioEngines = %p - deviceNotifier = %p - rc=%d\n", this, provider, audioEngines, deviceNotifier, getRetainCount());
#else
	debug5IOLog ("+AppleUSBAudioDevice[%p]::stop (%p) - audioEngines = %p - rc=%d\n", this, provider, audioEngines, getRetainCount());
#endif

	shouldStop = TRUE;

#if TIMERSTREAM
	if (!isInactive()) {	// We haven't been terminated, so we need to check to see if we are still running
		IOCommandGate *cg;

		cg = getCommandGate();

		if (cg) {
			bool isRegistered;

			if (cg->runAction(performUnattachedDeviceRegistrationAction, (void *)&isRegistered) == kIOReturnSuccess) {
				shouldStop = !isRegistered;
			}
		}
	}
#endif

	if (shouldStop) {
		performStop (provider);
	}

	debug2IOLog("-AppleUSBAudioDevice[%p]::stop ()\n", this);
}

// Return FALSE if you don't want PRAM updated on a volume change, TRUE if you want it updated.
// Only update PRAM if we're on a Cube and the speakers are Cube, SoundSticks, or Mirconas (somethings).
Boolean AppleUSBAudioDevice::ShouldUpdatePRAM (void) {
	const IORegistryPlane *			usbPlane;
	IORegistryEntry *				usbRegEntry;
	OSObject *						obj;
	OSNumber *						number;
	UInt32							locationID;
	UInt16							productID;
	UInt16							vendorID;
	Boolean							speakersGood;
	Boolean							connectionGood;
	Boolean							result;

	// Assume failure
	result = FALSE;
	speakersGood = FALSE;
	connectionGood = FALSE;

	// Make sure they're speakers that can support boot beep
	vendorID = controlInterface->GetDevice()->GetVendorID ();
	debug2IOLog ("+ ShouldUpdatePRAM\nspeaker's vendorID = 0x%x\n", vendorID);
	if (kIOUSBVendorIDAppleComputer == vendorID || kIOUSBVendorIDHaronKardon == vendorID || kIOUSBVendorMicronas == vendorID) {
		speakersGood = TRUE;
	}
	debug2IOLog ("speakersGood = %d\n", speakersGood);

	// They have to be plugged into a root hub or a hub in monitor that can support boot beep
	if (TRUE == speakersGood) {
		usbPlane = getPlane (kIOUSBPlane);
		FailIf (NULL == usbPlane, Exit);

		usbRegEntry = controlInterface->GetDevice()->getParentEntry (usbPlane);
		FailIf (NULL == usbRegEntry, Exit);

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

		vendorID = number->unsigned32BitValue ();
		debug2IOLog ("hub's vendorID = 0x%x\n", vendorID);

		if (kIOUSBVendorIDAppleComputer == vendorID) {
			obj = usbRegEntry->getProperty (kUSBDevicePropertyLocationID);
			number = OSDynamicCast (OSNumber, obj);
			FailIf (NULL == number, Exit);

			locationID = number->unsigned32BitValue ();
			debug2IOLog ("speaker's locationID = 0x%lx\n", locationID);

			if (0x18000000 == locationID || 0x19000000 == locationID) {
				// It's connected to the root hub
				connectionGood = TRUE;
			} else {
				obj = usbRegEntry->getProperty (kUSBProductID);
				number = OSDynamicCast (OSNumber, obj);
				FailIf (NULL == number, Exit);

				productID = number->unsigned32BitValue ();
				debug2IOLog ("hub's productID = 0x%x\n", productID);

				if (kStudioDisplay15CRT == productID || kStudioDisplay17CRT == productID || kCinemaDisplay == productID || kStudioDisplay17FP == productID) {
					// It's connected to a good monitor
					connectionGood = TRUE;
				}
			}
		}
	}
	debug2IOLog ("connectionGood = %d\n", speakersGood);

	// And there CANNOT be a "sound" node in the device tree so that OF will boot beep through them
	if (TRUE == connectionGood && FALSE == FindSoundNode ()) {
		result = TRUE;
	}

Exit:
	debug2IOLog ("- ShouldUpdatePRAM result = %d", 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, kIORegistryIterateRecursively);
			done = TRUE;
		}
		regEntry = iterator->getNextObject ();
	}

	// Now the real search begins...
	regEntry = iterator->getNextObject ();
	while (NULL != regEntry && FALSE == found) {
		name = regEntry->getName ();
		if (0 == strcmp (name, "sound")) {
			found = TRUE;
		}
		regEntry = iterator->getNextObject ();
	}

	iterator->release ();
Exit:
	return found;
}

void AppleUSBAudioDevice::performStop (IOService *provider) {

    debug3IOLog("+AppleUSBAudioDevice[%p]::performStop (%p)\n", this, provider);

#if TIMERSTREAM
    // remove from new USB device notification
    if (isUnattachedDeviceRegistered ()) {
        unregisterUnattachedDevice ();
    }
#endif

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

    if (controlInterface) {
        controlInterface->close (this);
        controlInterface = NULL;
    }

#if TIMERSTREAM
    if (hasBeenUnattached) {
        // we need to schedule this for later
        release ();
    }
#endif

    debug3IOLog ("-AppleUSBAudioDevice[%p]::performStop (%p)\n", this, provider);
}

bool AppleUSBAudioDevice::terminate (IOOptionBits options = 0) {
#if TIMERSTREAM
	IOCommandGate *					cg;
	bool							isRegistered;
#endif
	bool							shouldTerminate;
	bool							result;

	shouldTerminate = TRUE;
	result = TRUE;
	debug3IOLog ("+AppleUSBAudioDevice[%p]::terminate () - rc=%d\n", this, getRetainCount ());

#if TIMERSTREAM
	cg = getCommandGate();

	if (cg) {
		if (cg->runAction (performUnattachedDeviceRegistrationAction, (void *)&isRegistered) == kIOReturnSuccess) {
			shouldTerminate = !isRegistered;
		}
	}
#endif

	if (shouldTerminate) {
		result = super::terminate (options);
	}

	debug3IOLog ("-AppleUSBAudioDevice[%p]::terminate () - rc=%d\n", this, getRetainCount ());

	return result;
}

bool AppleUSBAudioDevice::finalize (IOOptionBits options) {
	bool result;
	debug4IOLog ("+AppleUSBAudioDevice[%p]::finalize (%p) - rc=%d\n", this, options, getRetainCount ());

	result = super::finalize (options);

#if TIMERSTREAM
	if (isUnattachedDeviceRegistered ()) {
		stop (0);
	}
#endif

	debug4IOLog ("-AppleUSBAudioDevice[%p]::finalize (%p) - rc=%d\n", this, options, getRetainCount ());

	return result;
}

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

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

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

IOReturn AppleUSBAudioDevice::activateAudioEngine (IOAudioEngine *audioEngine, bool shouldStartAudioEngine, UInt8 interfaceNum, UInt8 altInterfaceNum) {
    AppleUSBAudioEngine *				usbAudioEngine;
    UInt8								channelNum;
    AppleUSBAudioLevelControl *			speakerControl;
    AppleUSBAudioLevelControl *			micControl;
    AppleUSBAudioMuteControl *			muteControl;
    IOReturn							result;
	UInt16								terminalType;
	UInt8								numTerminals;
	UInt8								featureUnitID;
	UInt8								terminalIndex;
	UInt8								controlInterfaceNum;
	Boolean								shouldUpdatePRAM;

    debug5IOLog ("+AppleUSBAudioDevice[%p]::activateAudioEngine (%p, %d) - rc=%d\n", this, audioEngine, shouldStartAudioEngine, getRetainCount());

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

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

	controlInterfaceNum = controlInterface->GetInterfaceNumber ();

    if (usbAudioEngine->getDirection () == kIOAudioStreamDirectionOutput) {
        // It's an output device (e.g., a speaker).
		featureUnitID = 0;
		// look for a streaming input terminal that's connected to a non-streaming output terminal
		// for the moment we'll just look for a feature unit connected to a non-streaming output terminal
		numTerminals = usbAudio->GetNumOutputTerminals (controlInterfaceNum, 0);
		debug2IOLog ("num output terminals = %d\n", numTerminals);
		for (terminalIndex = 0; terminalIndex < numTerminals; terminalIndex++) {
			debug2IOLog ("terminalIndex = 0x%X\n", terminalIndex);
			terminalType = usbAudio->GetIndexedOutputTerminalType (controlInterfaceNum, 0, terminalIndex);
			debug2IOLog ("terminalType = 0x%X\n", terminalType);
			if (terminalType != 0x0101) {	// Only look for output terminals that output audio (things we can play to)
				featureUnitID = usbAudio->GetFeatureUnitIDConnectedToOutputTerminal (controlInterfaceNum, 0, usbAudio->GetIndexedOutputTerminalID (controlInterfaceNum, 0, terminalIndex));
				debug2IOLog ("featureUnitID = %d\n", featureUnitID);
				break;	// get out of for loop
			}
		}
		FailWithAction (0 == featureUnitID, result = kIOReturnSuccess, Finish);	// There isn't a feature unit connected to this input terminal
		// The interface was opened in AppleUSBAudioEngine::initHardware
		controlInterface->SetAlternateInterface (this, kRootAlternateSetting);
		shouldUpdatePRAM = ShouldUpdatePRAM ();
		for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) {
			if (usbAudio->ChannelHasVolumeControl (controlInterfaceNum, 0, featureUnitID, channelNum)) {
				speakerControl = AppleUSBAudioLevelControl::create (featureUnitID,
																	controlInterfaceNum,
																	VOLUME_CONTROL,
																	channelNum,
																	shouldUpdatePRAM,
																	(USBDeviceRequest)&deviceRequest,
																	this,
																	kIOAudioLevelControlSubTypeVolume,
																	kIOAudioControlUsageOutput);

				if (NULL != speakerControl) {
					audioEngine->addDefaultAudioControl (speakerControl);
					speakerControl->release ();
				} else {
					debug2IOLog ("++AppleUSBAudioDevice::activateAudioEngine () - error creating volume control for channelNum %d\n", channelNum);
				}
			}
		}
		muteControl = NULL;
		for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) {
			if (usbAudio->ChannelHasMuteControl (controlInterfaceNum, 0, featureUnitID, channelNum)) {
				muteControl = AppleUSBAudioMuteControl::create (featureUnitID,
																controlInterfaceNum,
																channelNum,
																(USBDeviceRequest)&deviceRequest,
																this,
																kIOAudioControlUsageOutput);
				if (NULL != muteControl) {
					audioEngine->addDefaultAudioControl (muteControl);
					muteControl->release ();
				} else {
					debug2IOLog ("++AppleUSBAudioDevice::activateAudioEngine () - error creating mute control for channelNum %d\n", channelNum);
				}
			}
		}
    } else {
        // It's an input device (e.g., a microphone).
		featureUnitID = 0;
		// look for a streaming input terminal that's connected to a non-streaming output terminal
		// for the moment we'll just look for a feature unit connected to a non-streaming output terminal
		numTerminals = usbAudio->GetNumOutputTerminals (controlInterfaceNum, 0);
		debug2IOLog ("num output terminals = %d\n", numTerminals);
		for (terminalIndex = 0; terminalIndex < numTerminals; terminalIndex++) {
			debug2IOLog ("terminalIndex = 0x%X\n", terminalIndex);
			terminalType = usbAudio->GetIndexedOutputTerminalType (controlInterfaceNum, 0, terminalIndex);
			debug2IOLog ("terminalType = 0x%X\n", terminalType);
			if (terminalType == 0x0101) {	// Only look for output terminals that output digital audio data (things we can record from)
				featureUnitID = usbAudio->GetFeatureUnitIDConnectedToOutputTerminal (controlInterfaceNum, 0, usbAudio->GetIndexedOutputTerminalID (controlInterfaceNum, 0, terminalIndex));
				debug2IOLog ("featureUnitID = %d\n", featureUnitID);
				break;	// get out of for loop
			}
		}
		FailWithAction (0 == featureUnitID, result = kIOReturnSuccess, Finish);	// There isn't a feature unit connected to this output terminal
		// The interface was opened in AppleUSBAudioEngine::initHardware
		controlInterface->SetAlternateInterface (this, kRootAlternateSetting);
		for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) {
			if (usbAudio->ChannelHasVolumeControl (controlInterfaceNum, 0, featureUnitID, channelNum)) {
				micControl = AppleUSBAudioLevelControl::create (featureUnitID,
																controlInterfaceNum,
																VOLUME_CONTROL,
																channelNum,
																FALSE,
																(USBDeviceRequest)&deviceRequest,
																this,
																kIOAudioLevelControlSubTypeVolume,
																kIOAudioControlUsageInput);

				if (NULL != micControl) {
					audioEngine->addDefaultAudioControl (micControl);
					micControl->release ();
				} else {
					debug2IOLog ("++AppleUSBAudioDevice::activateAudioEngine () - error creating volume control for channelNum %d\n", channelNum);
				}
			}
		}
		muteControl = NULL;
		for (channelNum = 0; channelNum <= usbAudio->GetNumChannels (interfaceNum, altInterfaceNum); channelNum++) {
			if (usbAudio->ChannelHasMuteControl (controlInterfaceNum, 0, featureUnitID, channelNum)) {
				muteControl = AppleUSBAudioMuteControl::create (featureUnitID,
																controlInterfaceNum,
																channelNum,
																(USBDeviceRequest)&deviceRequest,
																this,
																kIOAudioControlUsageInput);
				if (NULL != muteControl) {
					audioEngine->addDefaultAudioControl (muteControl);
					muteControl->release ();
				} else {
					debug2IOLog ("++AppleUSBAudioDevice::activateAudioEngine () - error creating mute control for channelNum %d\n", channelNum);
				}
			}
		}
    }

Finish:
    FailIf (kIOReturnSuccess != super::activateAudioEngine (audioEngine, shouldStartAudioEngine), Exit);
	result = TRUE;

Exit:
    debug4IOLog ("-AppleUSBAudioDevice[%p]::activateAudioEngine (%p) - rc=%d\n", this, audioEngine, getRetainCount ());
    return result;
}

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

	debug4IOLog ("+AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)\n", self, request, completion);
	result = kIOReturnSuccess;
	if (self->controlInterface) {
		FailIf (NULL == self->interfaceLock, Exit);

        IORecursiveLockLock (self->interfaceLock);
        result = self->controlInterface->DeviceRequest (request, completion);
        IORecursiveLockUnlock (self->interfaceLock);
    }

	debug4IOLog ("-AppleUSBAudioDevice[%p]::deviceRequest (%p, %p)\n", self, request, completion);

Exit:
	return result;
}

#ifdef DEBUG

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

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

#endif
