Source to iokit/Families/IOAudio/IOAudioBus.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.
 *
 * Hardware independent (relatively) code for the AudioBus
 *
 * HISTORY
 *
 *
 */

#include <IOKit/audio/IOAudioBus.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOWorkLoop.h>
#include <IOKit/IOFilterInterruptEventSource.h>

#undef super
#define super"super IOAudioController

//************************************************************************
// Implementation of protocol clas.
//************************************************************************
OSDefineMetaClass(  IOAudioBus, IOAudioController )
OSDefineAbstractStructors(  IOAudioBus, IOAudioController )

#ifdef __ppc__

//************************************************************************
// Begin implementation of IOAudioBus class.
//************************************************************************

#define kNumberOfBuffers  4
#define kNumberOfSamples  8192
#define kNumberOfChannels 2	// left and right
#define kSampleSize 	  2	// 16 bit channels
#define kBlockSize 	  256

const int IOAudioBus::kAudioDMAdeviceInt	= 0;
const int IOAudioBus::kAudioDMAtxInt		= 1;
const int IOAudioBus::kAudioDMArxInt		= 2;

const int IOAudioBus::kAudioDMAOutputStream	= 0;
const int IOAudioBus::kAudioDMAInputStream	= 1;

// Constructs an empty audio bus:
bool
IOAudioBus::init(OSDictionary * properties)
{
    if (!super::init(properties))
            return false;

    ioAudioStreamsDMA = NULL;
    numDMAStreams = NULL;

    // Initialize my ivars.
    fBufMax = kNumberOfBuffers * kNumberOfSamples * kNumberOfChannels * kSampleSize;
    fBlockSize = kBlockSize;

    return true;
}

// This should free everything
void
IOAudioBus::free()
{
    FreeStreams();
    super::free();
}

void IOAudioBus::startWorkLoop()
{
    super::startWorkLoop();
    registerInterrupts();
}

void IOAudioBus::registerInterrupts()
{
    fTxInterruptSource = IOFilterInterruptEventSource::filterInterruptEventSource(this,
                                                                                  IOAudioBus::AudioBusInterruptHandler,
                                                                                  IOAudioBus::AudioBusInterruptFilter,
                                                                                  fDevice,
                                                                                  kAudioDMAtxInt);
    fWorkLoop->addEventSource(fTxInterruptSource);

    fRxInterruptSource = IOFilterInterruptEventSource::filterInterruptEventSource(this,
                                                                                  IOAudioBus::AudioBusInterruptHandler,
                                                                                  IOAudioBus::AudioBusInterruptFilter,
                                                                                  fDevice,
                                                                                  kAudioDMArxInt);
    fWorkLoop->addEventSource(fRxInterruptSource);

    fTxInterruptSource->enable();
    fRxInterruptSource->enable();
}

bool IOAudioBus::AudioBusInterruptFilter(OSObject *owner,
                               IOFilterInterruptEventSource *source)
{
    register IOAudioBus *bus = (IOAudioBus *)owner;
    bool result = true;

    if (bus) {
        result = bus->filterInterrupt(source->getIntIndex());
    }

    return result;
}

bool IOAudioBus::filterInterrupt(int index)
{
    IOAudioStreamStatus *status = getSharedStatus(getStreamForInterrupt(index));

    if (status) {
        clock_get_uptime(&status->fLastLoopTime);
        ++status->fCurrentLoopCount;
    }

    return false;
}

void IOAudioBus::AudioBusInterruptHandler(OSObject *owner,
                                IOInterruptEventSource * /*source*/,
                                int /*count*/)
{
    return;
}

AudioStreamIndex IOAudioBus::getStreamForInterrupt(int index)
{
    AudioStreamIndex result = kNoStream;

    switch (index) {
        case kAudioDMAtxInt:
            result = kAudioDMAOutputStream;
            break;
        case kAudioDMArxInt:
            result = kAudioDMAInputStream;
            break;
    }
    return result;
}

// Creates n empty streams (also a method to free them)
bool
IOAudioBus::AllocateStreams(int n)
{
    numDMAStreams = n;

    if (numDMAStreams > 0) {
        // Allocates as many StreamDMAInfo as needed:
        ioAudioStreamsDMA = (StreamDMAInfo*)IOMalloc(sizeof(StreamDMAInfo) * numDMAStreams);
        if (ioAudioStreamsDMA == NULL) {
            numDMAStreams = 0;
            return false;
        }
        for (n =0 ; n < numDMAStreams; n++) {
            ioAudioStreamsDMA[n].streamProperty = NULL;
            ioAudioStreamsDMA[n].fIOBaseDMA = NULL;
            ioAudioStreamsDMA[n].fSharedStatus = NULL;
        }
  
        return (true);
    }

    return (false);
}

bool
IOAudioBus::FreeStreams()
{
    int n;

    if (ioAudioStreamsDMA != NULL) {
        // First stops all the streams:
        for (n =0 ; n < numDMAStreams; n++)
            if (ioAudioStreamsDMA[n].fIOBaseDMA != NULL) {
                stopStream(n);

                // If we ever created a stream property fpr the stream, delete it:
                IOFree(ioAudioStreamsDMA[n].streamProperty, 128);
                ioAudioStreamsDMA[n].streamProperty = NULL;
            }

        IOFree(ioAudioStreamsDMA, sizeof(StreamDMAInfo) * numDMAStreams);
        ioAudioStreamsDMA = NULL;

        return true;
    }

    return false;
}

// For each stream defines its properties:
bool
IOAudioBus::DefineStream(AudioStreamIndex i, int direction, UInt32 rate, IODBDMAChannelRegisters *base)
{
    if ((ioAudioStreamsDMA != NULL) && (i < numDMAStreams) && (base != NULL)) {
        ioAudioStreamsDMA[i].fIOBaseDMA = base;

        // The stream direction is "hardwired" so that ecah channel
        // can go in one direction. 
        if (direction == kInput) {
            ioAudioStreamsDMA[i].fDmaCmd = kdbdmaInputMore;
            ioAudioStreamsDMA[i].fNeedsErase = false;
            ioAudioStreamsDMA[i].fIsInput = true;
        }
        else {
            ioAudioStreamsDMA[i].fDmaCmd = kdbdmaOutputMore;
            ioAudioStreamsDMA[i].fNeedsErase = true;
            ioAudioStreamsDMA[i].fIsInput = false;
        }
        ioAudioStreamsDMA[i].fSampleRate = rate;

#ifdef DEBUGMODE
        IOLog("IOAudioBus::DefineStream(%d, %s, %ld, 0x%08lx)\n",i, (direction == kInput ? "kInput" : "kOutput"),rate, (UInt32)base);
#endif
    }
    
    return false;
}

// Returns the first stream in the given direction after the given
// index. (this is useful if we have to handle more than one stream
// for input and output). (afterIndex is inclusive)

AudioStreamIndex
IOAudioBus::firstStreamAfter(int inDirection, AudioStreamIndex afterIndex)
{
    if ((ioAudioStreamsDMA != NULL) && (afterIndex < numDMAStreams)) {
        for (;afterIndex < numDMAStreams; afterIndex++) {
            if ((inDirection == kInput) && (ioAudioStreamsDMA[afterIndex].fIsInput))
                return (afterIndex);
            else if ((inDirection == kOutput) && (!ioAudioStreamsDMA[afterIndex].fIsInput))
                return (afterIndex);
        }
    }

    // We did not find a stream with the wanted properties
    return (kInvalidStreamIndex);
}

OSDictionary*
IOAudioBus::getStreamProperties(AudioStreamIndex i)
{
    OSDictionary *dict = NULL;
    OSString *errorString = NULL;

    if ((ioAudioStreamsDMA != NULL) && (i < numDMAStreams))
        if (ioAudioStreamsDMA[i].fIOBaseDMA != NULL) {

            ioAudioStreamsDMA[i].streamProperty = (char*)IOMalloc(128);
            if (ioAudioStreamsDMA[i].streamProperty != NULL) {
                // Depending from the direction of the stream this builds the property
                // string:
                if (ioAudioStreamsDMA[i].fIsInput) {
                    sprintf(ioAudioStreamsDMA[i].streamProperty, "{'In'=%d:8;'Out'=%d:8;'Channels'=%d:8;'Rate'=%ld:32;}",
                            (UInt8)1, (UInt8)0, (UInt8)2,
                            ioAudioStreamsDMA[i].fSampleRate);
                }
                else{
                    sprintf(ioAudioStreamsDMA[i].streamProperty, "{'In'=%d:8;'Out'=%d:8;'Channels'=%d:8;'Rate'=%ld:32;}",
                            (UInt8)0, (UInt8)1, (UInt8)2,
                            ioAudioStreamsDMA[i].fSampleRate);
                }

                dict = OSDynamicCast(OSDictionary, OSUnserialize(ioAudioStreamsDMA[i].streamProperty, &errorString));
            }
        }
            else
                IOLog("IOAudioBus::getStreamProperties: bad index %d\n", i);

    if (dict == NULL) {
        if (errorString != NULL) {
            IOLog("IOAudioBus::getStreamProperties %s (\"%s\")\n", errorString->getCStringNoCopy(), ioAudioStreamsDMA[i].streamProperty);
            errorString->release();
        }
    }

    return dict;
}

/*
 * Map stream data for caller.
 */

IOAudioStream *
IOAudioBus::createAudioStream(AudioStreamIndex index)
{
    assert(index < numDMAStreams);
    IOAudioStream * stream = super::createAudioStream(index);
    return stream;
}

int IOAudioBus::probeStreams()
{
    return numDMAStreams;
}

IOAudioStreamStatus * IOAudioBus::startStream(AudioStreamIndex index)
{
    assert(index < numDMAStreams);

    int numBlocks, bufSize, i, cmdSize;
    u_int32_t cmdPhys, bufPhys, seqPhys, offset;
    IOAudioStreamStatus *status;
    char *bufs;
    bool doInterrupt = false;

    // Calculate size and allocate dbdma command area, sample buffer and shared status.
    numBlocks = fBufMax/fBlockSize;
    bufSize = fBlockSize * numBlocks;
    cmdSize = (numBlocks * 2 + 1) * sizeof(IODBDMADescriptor);
    IODBDMADescriptor *cmds = (IODBDMADescriptor *)IOMallocAligned(cmdSize, 4);
    bufs = (char *)IOMallocAligned(round_page(bufSize), PAGE_SIZE);

    // This makes sure we get an entire page for the status buffer
    // to prevent the problem of other memory being allocated
    // in the same page that we're sharing read-only with user space.
    // Since we don't need an entire page for each status struct,
    // we could keep track of how much of the page that we've used and
    // assign chunks of it for each stream...

    status = (IOAudioStreamStatus *)IOMallocAligned(round_page(sizeof(IOAudioStreamStatus)), PAGE_SIZE);

    // Everything after this check should always succeed.
    if(!cmds || !bufs || !status)
        return NULL;

    bzero(bufs, bufSize);

    // get physical addresses of everything for the DBDMA controller.
    seqPhys = pmap_extract(kernel_pmap, (vm_address_t) &(status->fCurrentBlock));
    cmdPhys = pmap_extract(kernel_pmap, (vm_address_t) cmds);
    bufPhys = pmap_extract(kernel_pmap, (vm_address_t) bufs);
    offset = 0;
    
    // The address of the stop command:
    u_int32_t cmdStopPys = pmap_extract(kernel_pmap, (vm_address_t) (&cmds[numBlocks * 2]));

    for(i=0; i<numBlocks; i++) {
        u_int32_t cmdDest;

        if(offset >= PAGE_SIZE) {
            bufPhys = pmap_extract(kernel_pmap, (vm_address_t) (bufs + i*fBlockSize));
            offset = 0;
        }

        // Need a DBDMA branch if the next command is on a different page or
        // if we have to loop back to the first DBDMA command.
        if(i == numBlocks-1) {
            cmdDest = cmdPhys;
            doInterrupt = true;
        } else if( ((2*(i+1)*sizeof(IODBDMADescriptor)) % PAGE_SIZE) == 0)
            cmdDest = pmap_extract(kernel_pmap, (vm_address_t) (cmds+2*(i+1)));
        else
            cmdDest = 0;

        IOMakeDBDMADescriptorDep( &cmds[2*i],
                                        kdbdmaStoreQuad,
                                        kdbdmaKeyStream0,
                                        kdbdmaIntNever,
                                        kdbdmaBranchNever,
                                        kdbdmaWaitNever,
                                        sizeof(u_int32_t),
                                        seqPhys,
                                        OSReadLittleInt32(&i, 0)  );
        if(cmdDest) {
            IOMakeDBDMADescriptorDep( &cmds[2*i+1],
                                      ioAudioStreamsDMA[index].fDmaCmd,
                                      kdbdmaKeyStream0,
                                      doInterrupt ? kdbdmaIntAlways : kdbdmaIntNever,
                                      kdbdmaBranchAlways,
                                      kdbdmaWaitNever,
                                      fBlockSize,
                                      bufPhys+offset,
                                      cmdDest);
        }
        else {
           IOMakeDBDMADescriptorDep(  &cmds[2*i+1],
                                    ioAudioStreamsDMA[index].fDmaCmd,
                                    kdbdmaKeyStream0,
                                    kdbdmaIntNever,
                                    kdbdmaBranchIfTrue,
                                    kdbdmaWaitNever,
                                    fBlockSize,
                                    bufPhys+offset,
                                    cmdStopPys);

        }
        offset += fBlockSize;
    }
    
    // Add a STOP:
    IOMakeDBDMADescriptor(  &cmds[2*i],
                        kdbdmaStop,
                        kdbdmaKeyStream0,
                        kdbdmaIntNever,
                        kdbdmaBranchNever,
                        kdbdmaWaitNever,
                        0,
                        NULL);
    
    ioAudioStreamsDMA[index].fSharedStatus = status;
    ioAudioStreamsDMA[index].fCmds = cmds;
    ioAudioStreamsDMA[index].fCmdSize = cmdSize;
    ioAudioStreamsDMA[index].fSampleBuffer = (int16_t *)bufs;
    status->fVersion = 1;
    status->fErases = ioAudioStreamsDMA[index].fNeedsErase;
    status->fRunning = 0;
    status->fConnections = 0;

    status->fBufSize = bufSize;
    status->fBlockSize = fBlockSize;
    status->fNumBlocks = numBlocks;
    status->fSampleSize = 2;
    status->fChannels = 2;
    status->fDataRate = ioAudioStreamsDMA[index].fSampleRate * status->fSampleSize * status->fChannels;
    status->fCurrentBlock = 0;
    status->fEraseHeadBlock = 0;
    status->fCurrentLoopCount = 0;
    status->fMixBufferInUse = false;

#ifdef DEBUGMODE
    IOLog("DMA commands at 0x%x, %d blocks, seq at 0x%x\n", cmds, numBlocks, seqVirt);
    IOLog("Block size %d, total size %d\n", fBlockSize, bufSize);
#endif

    flush_dcache((vm_offset_t) cmds, cmdSize, false );

    clock_get_uptime(&status->fLastLoopTime);

    IOSetDBDMAChannelControl( ioAudioStreamsDMA[index].fIOBaseDMA, IOClearDBDMAChannelControlBits(kdbdmaS0));
    IOSetDBDMABranchSelect( ioAudioStreamsDMA[index].fIOBaseDMA, IOSetDBDMAChannelControlBits(kdbdmaS0));
    IODBDMAStart( ioAudioStreamsDMA[index].fIOBaseDMA, cmdPhys );

    return status;
}

void IOAudioBus::stopStream(AudioStreamIndex index)
{
    IOFilterInterruptEventSource *interruptEventSource;
    UInt8 attemptsToStop = 100;

    assert(!ioAudioStreamsDMA[index].fSharedStatus->fRunning);

    if (index == kAudioDMAOutputStream) {
        interruptEventSource = fTxInterruptSource;
    } else {	// index == kAudioDMAInputStream
        interruptEventSource = fRxInterruptSource;
    }
    interruptEventSource->disable();

    IOSetDBDMAChannelControl( ioAudioStreamsDMA[index].fIOBaseDMA, IOSetDBDMAChannelControlBits(kdbdmaS0));
    while ((IOGetDBDMAChannelStatus(ioAudioStreamsDMA[index].fIOBaseDMA) & kdbdmaActive ) && (attemptsToStop--)) {
        eieio();
        IOSleep(10);
    }

    IODBDMAStop( ioAudioStreamsDMA[index].fIOBaseDMA );
    IODBDMAReset( ioAudioStreamsDMA[index].fIOBaseDMA );

    IOFreeAligned(ioAudioStreamsDMA[index].fCmds, ioAudioStreamsDMA[index].fCmdSize);
    IOFreeAligned(ioAudioStreamsDMA[index].fSampleBuffer, round_page(ioAudioStreamsDMA[index].fSharedStatus->fBufSize));
    IOFreeAligned(ioAudioStreamsDMA[index].fSharedStatus, round_page(sizeof(IOAudioStreamStatus)));

    ioAudioStreamsDMA[index].fCmds = NULL;
    ioAudioStreamsDMA[index].fSampleBuffer = NULL;
    ioAudioStreamsDMA[index].fSharedStatus = NULL;

    interruptEventSource->enable();
}

void IOAudioBus::pauseStream(AudioStreamIndex index)
{
    assert(index < numDMAStreams);
    IODBDMAPause( ioAudioStreamsDMA[index].fIOBaseDMA );
}

void IOAudioBus::resumeStream(AudioStreamIndex index)
{
    assert(index < numDMAStreams);

    IODBDMAContinue( ioAudioStreamsDMA[index].fIOBaseDMA );
}

IOAudioStreamStatus * IOAudioBus::getSharedStatus(AudioStreamIndex index)
{
    assert(index < numDMAStreams);
    return ioAudioStreamsDMA[index].fSharedStatus;
}

void * IOAudioBus::getSampleBuffer(AudioStreamIndex index)
{
    assert(index < numDMAStreams);
    return ioAudioStreamsDMA[index].fSampleBuffer;
}

#endif // __ppc__