Source to iokit/Families/IOFireWire/IOFWIsochChannel.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) 1999 Apple Computer, Inc.  All rights reserved.
 *
 * HISTORY
 *
 */

#include <IOKit/assert.h>
#include <IOKit/firewire/IOFireWireController.h>
#include <IOKit/firewire/IOFWIsochChannel.h>
#include <IOKit/firewire/IOFWIsochPort.h>
#include <libkern/c++/OSSet.h>
#include <libkern/c++/OSCollectionIterator.h>
#include <IOKit/firewire/IOFWCommand.h>
#include <IOKit/firewire/IOFireWireDevice.h>

OSDefineMetaClassAndStructors(IOFWIsochChannel, OSObject)

void IOFWIsochChannel::threadFunc(thread_call_param_t arg, thread_call_param_t)
{
    ((IOFWIsochChannel *)arg)->reallocBandwidth();
}

bool IOFWIsochChannel::init(IOFireWireController *control, bool doIRM,
	UInt32 bandwidth, IOFWSpeed prefSpeed,
	FWIsochChannelForceStopNotificationProcPtr stopProc, void *stopRefCon)
{
    if(!OSObject::init())
	return false;
    fControl = control;
    fDoIRM = doIRM;
    fMBitSec = bandwidth;
    fPrefSpeed = prefSpeed;
    fStopProc = stopProc;
    fStopRefCon = stopRefCon;
    fTalker = NULL;
    fListeners = OSSet::withCapacity(1);
    fChannel = 64;	// Illegal channel
    return fListeners != NULL;
}

void IOFWIsochChannel::free()
{
    if(fListeners)
        fListeners->release();
    OSObject::free();
}

IOReturn IOFWIsochChannel::setTalker(IOFWIsochPort *talker)
{
    fTalker = talker;
    return kIOReturnSuccess;
}

IOReturn IOFWIsochChannel::addListener(IOFWIsochPort *listener)
{
    if(fListeners->setObject(listener))
        return kIOReturnSuccess;
    else
        return kIOReturnNoMemory;
}

IOReturn IOFWIsochChannel::updateBandwidth(bool claim)
{
    FWAddress addr(kCSRRegisterSpaceBaseAddressHi, kCSRBandwidthAvailable);
    IOFireWireDevice *irm;	// The Isochronous Resource Manager, if there is one
    IOFWReadQuadCommand *readCmd = NULL;
    IOFWCompareAndSwapCommand *lockCmd = NULL;
    UInt32 newVal;
    UInt32 oldVal;
    IOReturn result;
    bool tryAgain;

    do {
        irm = fControl->getIRMDevice();
        if(fBandwidth != 0) {
            if(irm != NULL) {
                readCmd = irm->createReadQuadCommand(addr, &oldVal, 1);
                if(!readCmd) {
                    result = kIOReturnNoMemory;
                    break;
                }
                readCmd->submit();
                result = readCmd->fStatus;
                readCmd->release();
                readCmd = NULL;
                if(kIOReturnSuccess != result) {
                    kprintf("read result 0x%x\n", result);
                    break;
                }
            }
            do {
		if(claim)
                    newVal = oldVal - fBandwidth;
		else
                    newVal = oldVal + fBandwidth;
		if(irm) {
                    lockCmd = fControl->getIRMDevice()->createCompareAndSwapCommand(addr, &oldVal, &newVal, 1);
                    if(!lockCmd) {
                        result = kIOReturnNoMemory;
                        break;
                    }
                    lockCmd->submit();
                    if(kIOReturnSuccess != lockCmd->fStatus)
                        kprintf("bandwidth update result 0x%x\n", lockCmd->fStatus);
                    tryAgain = !lockCmd->locked(&oldVal);
                    lockCmd->release();
                    lockCmd = NULL;
                    if(tryAgain) {
                        kprintf("bandwidth update failed, newval 0x%x\n", oldVal);
                    }
		}
		else
                    tryAgain = false;
            } while (tryAgain);
            if(!claim)
                fBandwidth = 0;
        }
        if(fChannel != 64 && (irm != NULL)) {
            UInt32 mask;
            if(fChannel <= 31) {
                addr.addressLo = kCSRChannelsAvailable31_0;
                mask = 1 << (31-fChannel);
            }
            else {
                addr.addressLo = kCSRChannelsAvailable63_32;
                mask = 1 << (63-fChannel);
            }
            readCmd = irm->createReadQuadCommand(addr, &oldVal, 1);
            if(!readCmd) {
                result = kIOReturnNoMemory;
                break;
            }
            readCmd->submit();
            result = readCmd->fStatus;
            if(kIOReturnSuccess != result) {
                kprintf("read result 0x%x\n", result);
                break;
            }
            do {
                if(claim)
                    newVal = oldVal & ~mask;
		else
                    newVal = oldVal | mask;
                lockCmd = irm->createCompareAndSwapCommand(addr, &oldVal, &newVal, 1);
                if(!lockCmd) {
                    result = kIOReturnNoMemory;
                    break;
                }
                lockCmd->submit();
                if(kIOReturnSuccess != lockCmd->fStatus)
                    kprintf("channel update result 0x%x\n", lockCmd->fStatus);
                tryAgain = !lockCmd->locked(&oldVal);
                lockCmd->release();
                lockCmd = NULL;
                if(tryAgain) {
                    kprintf("channel update failed, newval 0x%x\n", oldVal);
                }
            } while (tryAgain);
            if(!claim)
                fChannel = 64;
        }
    } while (false);
    if(readCmd)
        readCmd->release();
    if(lockCmd)
        lockCmd->release();
    return result;
}

IOReturn IOFWIsochChannel::allocateChannel()
{
    UInt64 portChans;
    UInt64 allowedChans, savedChans;
    IOFireWireDevice *irm;	// The Isochronous Resource Manager, if there is one
    IOFWReadQuadCommand *readCmd = NULL;
    IOFWCompareAndSwapCommand *lockCmd = NULL;
    UInt32 newVal;
    FWAddress addr(kCSRRegisterSpaceBaseAddressHi, kCSRBandwidthAvailable);
    OSIterator *listenIterator;
    IOFWIsochPort *listen;
    IOFWSpeed portSpeed;
    UInt32 old[3];
    UInt32 bandwidth;
    UInt32 channel;
    bool tryAgain;	// For locks.
    IOReturn result = kIOReturnSuccess;

    // Get best speed, minimum of requested speed and paths from talker to each listener
    fSpeed = fPrefSpeed;

    do {
        // reduce speed to minimum of so far and what all ports can do,
        // and find valid channels
        allowedChans = (UInt64)-1;
        if(fTalker) {
            fTalker->getSupported(portSpeed, portChans);
            if(portSpeed < fSpeed)
                fSpeed = portSpeed;
            allowedChans &= portChans;
        }
        listenIterator = OSCollectionIterator::withCollection(fListeners);
        if(listenIterator) {
            while( (listen = (IOFWIsochPort *) listenIterator->getNextObject())) {
                listen->getSupported(portSpeed, portChans);
                if(portSpeed < fSpeed)
                    fSpeed = portSpeed;
                allowedChans &= portChans;
            }
        }

        // reserve bandwidth, allocate a channel
        if(fDoIRM && (irm = fControl->getIRMDevice())) {
            savedChans = allowedChans; // In case we have to try a few times
            // Careful of 32 bit overflow!
            bandwidth = fMBitSec / (1000 * (1 << fSpeed));
            bandwidth *= 6144;
            bandwidth /= 100000;
    //kprintf("Bandwidth is %d\n", bandwidth);
            readCmd = irm->createReadQuadCommand(addr, old, 3);
            if(!readCmd) {
                result = kIOReturnNoMemory;
                break;
            }
            readCmd->submit();
            result = readCmd->fStatus;
            if(kIOReturnSuccess != result) {
    		kprintf("read result 0x%x\n", readCmd->fStatus);
		break;
            }
    //kprintf("Allocated Bandwidth is %d, channels are 0x%x 0x%x\n", old[0], old[1], old[2]);
            allowedChans &= old[1] | ((UInt64)old[2] << 32);

            // Claim bandwidth
            tryAgain = false;
            do {
                if(old[0] < bandwidth) {
                    result = kIOReturnNoSpace;
                    break;
                }
                newVal = old[0] - bandwidth;
                lockCmd = irm->createCompareAndSwapCommand(addr, &old[0], &newVal, 1);
                if(!lockCmd) {
                    result = kIOReturnNoMemory;
                    break;
                }
                lockCmd->submit();
                if(kIOReturnSuccess != lockCmd->fStatus)
                    kprintf("bandwith update result 0x%x\n", lockCmd->fStatus);
                tryAgain = !lockCmd->locked(&old[0]);
                lockCmd->release();
                lockCmd = NULL;
                if(tryAgain) {
                    kprintf("Bandwith lock failed, newval 0x%x\n", old[0]);
		}
            } while (tryAgain);
            if(kIOReturnSuccess != result)
		break;
            fBandwidth = bandwidth;
        }

        tryAgain = false;
        do {
            for(channel=0; channel<64; channel++) {
                if(allowedChans & (1<<(63-channel))) {
                     break;
                }
            }
            kprintf("using channel %d\n", channel);
            if(channel == 64) {
                result = kIOReturnNoResources;
                break;
            }

            // Allocate a channel
            if(fDoIRM && irm) {
                UInt32 *oldPtr;
                // Claim channel
                if(channel < 32) {
                    addr.addressLo = kCSRChannelsAvailable31_0;
                    oldPtr = &old[1];
                    newVal = *oldPtr & ~(1<<(31-channel));
		}
		else {
                    addr.addressLo = kCSRChannelsAvailable63_32;
                    oldPtr = &old[2];
                    newVal = *oldPtr & ~(1<<(63-channel));
		}
                lockCmd = irm->createCompareAndSwapCommand(addr, oldPtr, &newVal, 1);
                if(!lockCmd) {
                    result = kIOReturnNoMemory;
                    break;
                }
                lockCmd->submit();
                if(kIOReturnSuccess != lockCmd->fStatus)
                    kprintf("channel update result 0x%x\n", lockCmd->fStatus);
                tryAgain = !lockCmd->locked(oldPtr);
                lockCmd->release();
                lockCmd = NULL;
                if(tryAgain) {
                    kprintf("channel update failed, newval 0x%x\n", old[0]);
                }
            }
            else
		tryAgain = false;
        } while (tryAgain);
        if(kIOReturnSuccess != result)
            break;
        fChannel = channel;
        if(fDoIRM)
            fControl->addAllocatedChannel(this);

        // allocate hardware resources for each port
        if(listenIterator) {
            listenIterator->reset();
            while( (listen = (IOFWIsochPort *) listenIterator->getNextObject())) {
                listen->allocatePort(fSpeed, fChannel);
            }
            listenIterator->release();
        }
        if(fTalker)
            fTalker->allocatePort(fSpeed, fChannel);
    } while (false);

    if(readCmd)
	readCmd->release();
    if(lockCmd)
        lockCmd->release();

    return result;
}


IOReturn IOFWIsochChannel::releaseChannel()
{
    OSIterator *listenIterator;
    IOFWIsochPort *listen;
    IOReturn result;

    if(fTalker) {
        fTalker->releasePort();
    }
    listenIterator = OSCollectionIterator::withCollection(fListeners);
    if(listenIterator) {
        while( (listen = (IOFWIsochPort *) listenIterator->getNextObject())) {
            listen->releasePort();
        }
        listenIterator->release();
    }

    // release bandwidth and channel
    if(fDoIRM) {
        /*
         * Tell the controller that we don't need to know about
         * bus resets bedore doing anything else, since a bus reset
         * sets us into the state we want (no allocated bandwidth).
         */
        fControl->removeAllocatedChannel(this);
	updateBandwidth(false);
    }
    return kIOReturnSuccess;
}

void IOFWIsochChannel::reallocBandwidth()
{
    kprintf("reallocBandwidth\n");
    updateBandwidth(true);
}

void IOFWIsochChannel::handleBusReset()
{
    kprintf("handleBusReset\n");
    thread_call_func(threadFunc, this, true);
}

IOReturn IOFWIsochChannel::start()
{
    OSIterator *listenIterator;
    IOFWIsochPort *listen;

    // Start all listeners, then start the talker
    listenIterator = OSCollectionIterator::withCollection(fListeners);
    if(listenIterator) {
        listenIterator->reset();
        while( (listen = (IOFWIsochPort *) listenIterator->getNextObject())) {
            listen->start();
        }
        listenIterator->release();
    }
    if(fTalker)
	fTalker->start();

    return kIOReturnSuccess;
}

IOReturn IOFWIsochChannel::stop()
{
    OSIterator *listenIterator;
    IOFWIsochPort *listen;

    // Stop all listeners, then stop the talker
    listenIterator = OSCollectionIterator::withCollection(fListeners);
    if(listenIterator) {
         while( (listen = (IOFWIsochPort *) listenIterator->getNextObject())) {
            listen->stop();
        }
        listenIterator->release();
    }
    if(fTalker)
	fTalker->stop();

    return kIOReturnSuccess;
}