Source to iokit/Families/IOAudio/IOAudioController.cpp


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Copyright (c) 1998 Apple Computer, Inc.  All rights reserved. 
 *
 * IOAudioController.cpp
 *
 * HISTORY
 *
 */

#include <IOKit/assert.h>

#include <IOKit/IOLib.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/IOSyncer.h>
#include <IOKit/IOWorkLoop.h>
#include <IOKit/IOCommandQueue.h>
#include <IOKit/IOTimerEventSource.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/audio/IOAudioController.h>

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

const IORegistryPlane * IOAudioController::gIOAudioPlane = NULL;
const OSSymbol * IOAudioController::gValSym = NULL;
const OSSymbol * IOAudioController::gTypeSym = NULL;
const OSSymbol * IOAudioController::gInputsSym = NULL;
const OSSymbol * IOAudioController::gControlsSym = NULL;
const OSSymbol * IOAudioController::gJackSym = NULL;
const OSSymbol * IOAudioController::gSpeakerSym = NULL;
const OSSymbol * IOAudioController::gHeadphonesSym = NULL;
const OSSymbol * IOAudioController::gLineOutSym = NULL;
const OSSymbol * IOAudioController::gMuteSym = NULL;
const OSSymbol * IOAudioController::gIdSym = NULL;
const OSSymbol * IOAudioController::gMasterSym = NULL;

#define TIMER_INTS_PER_BUF	4
#define MAX_POLL_INTERVAL_SEC	60

//************************************************************************
// Implementation of protocol clas.
//************************************************************************
OSDefineMetaClass(  IOAudio, IOService )
OSDefineAbstractStructors(  IOAudio, IOService )

//************************************************************************
// Begin implementation of IOAudioController class.
//************************************************************************

#undef super
#define super IOAudio

OSDefineMetaClass(  IOAudioController, IOAudio )
OSDefineAbstractStructors(  IOAudioController, IOAudio )

bool IOAudioController::init(OSDictionary *properties)
{
    if (!super::init(properties)) {
        return false;
    }

    fMasterVolumeComponents = OSSet::withCapacity(1);
    fNumStreamsInUse = 0;

    nanoseconds_to_absolutetime((UInt64)(MAX_POLL_INTERVAL_SEC) * (UInt64)NSEC_PER_SEC, &fMaxPollInterval);

    return true;
}

bool IOAudioController::start( IOService *provider )
{
    if (!super::start(provider))
        return false;

    fDevice = provider;

    return true;
}

void IOAudioController::execAndSignal(OSObject * obj, void *field0, void *field1, void *field2, void *field3)
{
    IOAudioController *me = (IOAudioController *)obj;
    IOSyncer *syncer = (IOSyncer *) field0;
    IOAudioCmd cmd = (IOAudioCmd) (int) field1;

    me->execCommand(cmd, field2, field3);

    if(syncer)
        syncer->signal();
}

void IOAudioController::execCommand(IOAudioCmd cmd, void *field2, void *field3)
{
    switch (cmd) {
        case kConnect:
        getMap((AudioStreamIndex)field2, (IOAudioStreamMap *)field3);
        break;

        case kDetach:
        releaseMap((AudioStreamIndex)field2);
        break;

        case kSetFlow:
        setFlow((AudioStreamIndex)field2, (bool)field3);
        break;

        case kProbeStreams:
            int numStreams, i;
            numStreams = probeStreams();
            fStreams = OSArray::withCapacity(numStreams);
            if(fStreams) {
                for(i=0; i<numStreams; i++) {
                    IOAudioStream *stream;
                    stream = createAudioStream(i);
                    if(stream) {
                        fStreams->setObject(i, stream);
                        stream->attach(this);
                        stream->registerService();
                    }
                }
                CreateAudioTopology(fQueue);
            }
            // Start the timer event source, if necessary (eg polling Awacs inputs)
            AbsoluteTime pollInterval, wantTick;

            pollInterval = fMaxPollInterval;
            calculateTickInterval(&pollInterval);
            if(CMP_ABSOLUTETIME(&pollInterval, &fMaxPollInterval) < 0) {
                clock_get_uptime(&wantTick);
                ADD_ABSOLUTETIME(&wantTick, &pollInterval);
                fNextTick = wantTick;
                fTimer->wakeAtTime(fNextTick);
                fTimerInUse = true;
                fWorkLoop->addEventSource(fTimer);
            }

	break;

    case kSetVal:
        SetControl((UInt16)field2, (UInt16)field3);
        break;

    case kFlush:
        flushStream((AudioStreamIndex)field2, (IOAudioStreamPosition *)field3);
        break;

    case kAllocMixBuffer:
        if (*(void **)field3 == NULL) {
            *(void **)field3 = allocateMixBuffer((AudioStreamIndex)field2);
        }
        break;
        
    default:
        IOLog("Unexpected command %d in IOAudioController::execCommand!\n", cmd);

    }
}

void IOAudioController::clockTick(OSObject * obj, IOTimerEventSource *timer)
{
    ((IOAudioController *)obj)->DoClockTick(timer);
}

void IOAudioController::DoClockTick(IOTimerEventSource *timer)
{
    AudioStreamIndex stream;
    AudioStreamIndex end;

    if(fStreams)
        end = fStreams->getCount();
    else
        end = 0;

    for(stream = 0; stream < end; stream++) {
        IOAudioStreamStatus *status = getSharedStatus(stream);

        if(status && status->fRunning) {
            AbsoluteTime now;

            clock_get_uptime(&now);

            // Do buffer clearing before potentially removing the buffer!
            if(status->fErases) {
                UInt32 currentBlock;

                // Clear buffer behind DMA back to last erase point
                char *buf = (char *)getSampleBuffer(stream);
                char *mixBuf = (char *)getMixBuffer(stream);

                currentBlock = status->fCurrentBlock;

                if(currentBlock < status->fEraseHeadBlock) {
                    if (buf) {
                        bzero(buf, currentBlock * status->fBlockSize);
                        bzero(buf + (status->fEraseHeadBlock * status->fBlockSize),
                              status->fBufSize - (status->fEraseHeadBlock * status->fBlockSize));
                    }
                    if (mixBuf) {
                        bzero(mixBuf, currentBlock * status->fBlockSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE);
                        bzero(mixBuf + (status->fEraseHeadBlock * status->fBlockSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE),
                              (status->fBufSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE) - (status->fEraseHeadBlock * status->fBlockSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE));
                    }
                }
                else {
                    if (buf) {
                        bzero(buf + (status->fEraseHeadBlock * status->fBlockSize),
                              (currentBlock - status->fEraseHeadBlock) * status->fBlockSize);
                    }
                    if (mixBuf) {
                        bzero(mixBuf + (status->fEraseHeadBlock * status->fBlockSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE),
                              (currentBlock - status->fEraseHeadBlock) * status->fBlockSize  / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE);
                    }
                }

                status->fEraseHeadBlock = currentBlock;
            }
            if(status->fConnections == 0) {
                UInt32 currentBlock, currentLoopCount;

                currentLoopCount = status->fCurrentLoopCount;
                currentBlock = status->fCurrentBlock;
                
                // See if its time to stop (we have completed flushing output buffers)
                if ((currentLoopCount > status->fFlushEnd.fLoopCount)
                 || ((currentLoopCount == status->fFlushEnd.fLoopCount)
                  && (currentBlock > status->fFlushEnd.fBlock))) {
                    AbsoluteTime pollInterval;
                    status->fRunning = 0;
                    stopStream(stream);
                    fNumStreamsInUse--;
                    if (fNumStreamsInUse == 0) {
                        pollInterval = fMaxPollInterval;
                        calculateTickInterval(&pollInterval);
                        if(CMP_ABSOLUTETIME(&pollInterval, &fMaxPollInterval) < 0) {
                            fWorkLoop->removeEventSource(fTimer);
                            fTimerInUse = false;
                        }
                    }
                }
            }
        }
    }

    if(fTimerInUse) {
        fTimer->setTimeout(fTickPeriod);
    }
}

//------------------------------------------------------------------------
/*
 * startWorkLoop()
 *
 * Drivers call this after initializing their hardware
 * We ask the device here how many initial audio channels it has, create appropriate nubs,
 * and then start the work loop running
 */
void IOAudioController::startWorkLoop()
{
    if(NULL == gIOAudioPlane) {
         gIOAudioPlane = IORegistryEntry::makePlane( kIOAudioPlane );
    }

    if(NULL == gValSym) {
        gValSym = OSSymbol::withCString("Val");
        gInputsSym = OSSymbol::withCString("Inputs");
        gControlsSym = OSSymbol::withCString("Controls");
        gJackSym = OSSymbol::withCString("Jack");
        gSpeakerSym = OSSymbol::withCString("Speaker");
        gHeadphonesSym = OSSymbol::withCString("Headphones");
        gLineOutSym = OSSymbol::withCString("LineOut");
        gTypeSym = OSSymbol::withCString("Type");
	gMuteSym = OSSymbol::withCString("Mute");
	gIdSym = OSSymbol::withCString("Id");
        gMasterSym = OSSymbol::withCString("Master");
    }

    fTimer = IOTimerEventSource::timerEventSource(this, &clockTick);
    fQueue = IOCommandQueue::commandQueue(this, &execAndSignal);

    fWorkLoop = createWorkLoop();
    fWorkLoop->addEventSource(fQueue);

    fQueue->enqueueCommand(true, 0, (void *)kProbeStreams, 0);

    registerService();
}

IOWorkLoop *IOAudioController::getWorkLoop() const
{
    return fWorkLoop;
}

IOWorkLoop * IOAudioController::createWorkLoop()
{
    IOWorkLoop *loop;

    loop = new IOWorkLoop();
    if(loop && !loop->init()) {
	loop->release();
	loop = NULL;
    }
    return loop;
}

IOAudioStream *IOAudioController::createAudioStream(AudioStreamIndex index)
{
    OSDictionary *props = getStreamProperties(index);
    IOAudioStreamImpl *stream;
    SInt32 isOut;
    IOReturn err;

    if(!props)
	return NULL;

    stream = new IOAudioStreamImpl;
    if(stream)
    	stream->initWithPropsIndexQueue(props, index, fQueue);

    props->release();	// Finished with this now.

    /*
     * Output streams are the start of a sound chain, so put
     * them at the root of the IOAudio plane.
     */
    err = stream->isOutput(&isOut);
    if(!err && isOut)
        stream->attachToParent(getRegistryRoot(), gIOAudioPlane);

    return stream;
}

IOAudioComponentImpl *
IOAudioController::buildComponentAndAttach(IORegistryEntry *parent,
			IORegistryEntry *child, const char *serialProps,
							IOCommandQueue *queue)
{
    OSDictionary *dict;
    OSString *errorString;
    IOAudioComponentImpl * aComp;
    OSNumber *masterVal;

    aComp = new IOAudioComponentImpl;
    if(!aComp)
        return NULL;

    dict = OSDynamicCast(OSDictionary, OSUnserialize(serialProps, &errorString));
    if (!dict && errorString) {
	IOLog("%s\n", errorString->getCStringNoCopy());
	errorString->release();
    }
    if(!aComp->initWithStuff(this, dict, queue)) {
        aComp->release();
        return NULL;
    }

    if(!parent)
        parent = getRegistryRoot();

    aComp->attachToParent(parent, gIOAudioPlane);
    
    if(child)	
        child->attachToParent(aComp, gIOAudioPlane);

    masterVal = (OSNumber *)dict->getObject(gMasterSym);
    if ((masterVal != NULL) && (masterVal->unsigned32BitValue() != 0)) {
        fMasterVolumeComponents->setObject(aComp);
    }
        
    return aComp;
}

void IOAudioController::AttachComponents(IORegistryEntry *parent,
			IORegistryEntry *child)
{
    child->attachToParent(parent, gIOAudioPlane);
}


void IOAudioController::free()
{
    if (fMasterVolumeComponents)
        fMasterVolumeComponents->release();
    if(fWorkLoop)
        fWorkLoop->release();
    if(fQueue)
        fQueue->release();
    if(fTimer)
        fTimer->release();
    if(fStreams)
	fStreams->release();
    super::free();
}

void IOAudioController::calculateTickInterval(AbsoluteTime *tickInterval)
{
    AudioStreamIndex stream;
    AudioStreamIndex end;

    if(fStreams)
        end = fStreams->getCount();
    else
        end = 0;

    for(stream = 0; stream < end; stream++) {
        IOAudioStreamStatus *status = getSharedStatus(stream);
        if(status) {
            AbsoluteTime pollInterval;

            nanoseconds_to_absolutetime(((UInt64)NSEC_PER_SEC * (UInt64)status->fBufSize / (UInt64)status->fDataRate / (UInt64)TIMER_INTS_PER_BUF), &pollInterval);

            if (CMP_ABSOLUTETIME(&pollInterval, tickInterval) < 0) {
                *tickInterval = pollInterval;
            }
        }
    }

    // Save the minimum period
    fTickPeriod = *tickInterval;
}

/*
 * Map stream data for caller.
 */
void IOAudioController::getMap(AudioStreamIndex index, IOAudioStreamMap *map)
{
    IOAudioStreamStatus * status = getSharedStatus(index);

    if(!status) {
        fNumStreamsInUse++;
        status = startStream(index);
        if(status) {
            AbsoluteTime pollInterval, wantTick;

            //map->fMixBuffer = (void *)IOMallocAligned(round_page(status->fBufSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE), PAGE_SIZE);

            clock_get_uptime(&wantTick);
            if(status->fErases) {
                status->fEraseHeadBlock = 0;
            }

            status->fFlushEnd.fBlock = 0;
            status->fFlushEnd.fLoopCount = 0;
            status->fMixBufferInUse = false;

            pollInterval = fMaxPollInterval;
            calculateTickInterval(&pollInterval);

            ADD_ABSOLUTETIME(&wantTick, &pollInterval);

            if(!fTimerInUse || (CMP_ABSOLUTETIME(&wantTick, &fNextTick) < 0)) {
                fNextTick = wantTick;
                fTimer->wakeAtTime(fNextTick);
            }
            if(!fTimerInUse) {
                fTimerInUse = true;
                fWorkLoop->addEventSource(fTimer);
            }
            setFlow(index, true);
        }
        else {
            map->fStatus = NULL;
            map->fSampleBuffer = NULL;
            //map->fMixBuffer = NULL;
            fNumStreamsInUse--;
            return;
        }
    }
    status->fConnections = 1;
    map->fStatus = status;
    map->fSampleBuffer = getSampleBuffer(index);
}

void IOAudioController::releaseMap(AudioStreamIndex index)
{
    IOAudioStreamStatus * status = getSharedStatus(index);

    if (status) {
        status->fConnections = 0;
    }
}

void IOAudioController::setFlow(AudioStreamIndex index, bool flowing)
{
    IOAudioStreamStatus * status = getSharedStatus(index);
    if(status) {
        if(flowing) {
            if(status->fRunning++ == 0) {
                fNumStreamsInUse++;
                resumeStream(index);
            }
        }
        else {
            if(--status->fRunning == 0) {
                pauseStream(index);
                fNumStreamsInUse--;
            }
        }
    }
}

void IOAudioController::flushStream(AudioStreamIndex index, IOAudioStreamPosition *endingPosition)
{
    IOAudioStreamStatus *status = getSharedStatus(index);

    if (status) {
        if ((endingPosition->fLoopCount > status->fFlushEnd.fLoopCount) || ((endingPosition->fLoopCount == status->fFlushEnd.fLoopCount) && (endingPosition->fBlock > status->fFlushEnd.fBlock))) {
            status->fFlushEnd = *endingPosition;
        }
    }
}

void *IOAudioController::allocateMixBuffer(AudioStreamIndex index)
{
    IOAudioStreamStatus * status = getSharedStatus(index);
    void *mixBuffer = NULL;

    if(status) {
        mixBuffer = (void *)IOMallocAligned(round_page(status->fBufSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE), PAGE_SIZE);
        bzero (mixBuffer, round_page(status->fBufSize / status->fSampleSize * MIX_BUFFER_SAMPLE_SIZE));
        status->fMixBufferInUse = true;
    }

    return mixBuffer;
}
void *IOAudioController::getMixBuffer(AudioStreamIndex index)
{
    void *mixBuffer = NULL;
    if (fStreams) {
        IOAudioStreamImpl *stream = (IOAudioStreamImpl *)fStreams->getObject(index);
        if (stream) {
            if (stream->getMixBuffer(&mixBuffer) != kIOReturnSuccess) {
                mixBuffer = NULL;
            }
        }
    }

    return mixBuffer;
}

void IOAudioController::setMasterComponentsProperties(OSDictionary *properties)
{
    if (properties) {
        OSCollectionIterator *iter;

        iter = OSCollectionIterator::withCollection(fMasterVolumeComponents);
        if (iter) {
            IOAudioComponent *component;

            while ((component = OSDynamicCast(IOAudioComponent, iter->getNextObject())) != NULL) {
                component->setProperties(properties);
            }
        }
    }
}
void IOAudioController::setMasterVolumeLeft(UInt16 newMasterVolumeLeft)
{
    OSDictionary *controlDict;
    OSString *errorString;
    char controlDictString[51]; // strlen(formatString) + 5 (max chars in 16-bit int) + 1 (terminator)

    sprintf (controlDictString, "{'Controls'={'VolumeLeft'={'Val'=%d:16;};};};", newMasterVolumeLeft);
    controlDict = OSDynamicCast(OSDictionary, OSUnserialize(controlDictString, &errorString));

    if (controlDict) {
        setMasterComponentsProperties(controlDict);
        controlDict->release();
    } else if (errorString) {
        IOLog("IOAudioController: %s\n", errorString->getCStringNoCopy());
        errorString->release();
    }
}

void IOAudioController::setMasterVolumeRight(UInt16 newMasterVolumeRight)
{
    OSDictionary *controlDict;
    OSString *errorString;
    char controlDictString[52]; // strlen(formatString) + 5 (max chars in 16-bit int) + 1 (terminator)

    // FIXME - We really should scale to min/max in this control...
    sprintf (controlDictString, "{'Controls'={'VolumeRight'={'Val'=%d:16;};};};", newMasterVolumeRight);
    controlDict = OSDynamicCast(OSDictionary, OSUnserialize(controlDictString, &errorString));

    if (controlDict) {
        setMasterComponentsProperties(controlDict);
        controlDict->release();
    } else if (errorString) {
        IOLog("IOAudioController: %s\n", errorString->getCStringNoCopy());
        errorString->release();
    }
}

void IOAudioController::setMasterMute(bool newMasterMute)
{
    OSDictionary *controlDict;
    OSString *errorString;
    char controlDictString[43]; // strlen(formatString) + 1 (single digit) + 1 (terminator)

    sprintf (controlDictString, "{'Controls'={'MuteAll'={'Val'=%d:8;};};};", newMasterMute ? 1 : 0);
    controlDict = OSDynamicCast(OSDictionary, OSUnserialize(controlDictString, &errorString));

    if (controlDict) {
        setMasterComponentsProperties(controlDict);
        controlDict->release();
    } else if (errorString) {
        IOLog("IOAudioController: %s\n", errorString->getCStringNoCopy());
        errorString->release();
    }
}