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

#include <IOKit/IOLib.h>
#include <IOKit/IOLocks.h>
#include <IOKit/assert.h>

#include <IOKit/graphics/IOAccelerator.h>
#include <IOKit/graphics/IOGraphicsTypesPrivate.h>
#include <IOKit/IOUserClient.h>


OSDefineMetaClassAndStructors(IOAccelerator, IOService)

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static IOLock * 	gLock;
static queue_head_t	gGlobalList;
static UInt32		gTotalCount;
static SInt32		gTweak;

struct IOAccelIDRecord
{
    IOAccelID		id;
    SInt32		retain;
    queue_chain_t	task_link;
    queue_chain_t	glob_link;
};

enum { kTweakBits = 0x1f };	// sizeof(IOAccelIDRecord) == 24

class IOAccelerationUserClient : public IOUserClient
{
    /*
     * Declare the metaclass information that is used for runtime
     * typechecking of IOKit objects.
     */

    OSDeclareDefaultStructors( IOAccelerationUserClient );

private:
    task_t		fTask;
    queue_head_t	fTaskList;

    static void IOAccelerationUserClient::initialize();

public:
    /* IOService overrides */
    virtual bool start( IOService * provider );
    virtual void stop( IOService * provider );

    /* IOUserClient overrides */
    virtual bool initWithTask( task_t owningTask, void * securityID,
                                                UInt32 type,  OSDictionary * properties );
    virtual IOReturn clientClose( void );

    virtual IOExternalMethod * getTargetAndMethodForIndex(
                                            IOService ** targetP, UInt32 index );


    IOReturn extCreate(IOOptionBits options,
			IOAccelID requestedID, IOAccelID * idOut);
    IOReturn extDestroy(IOOptionBits options, IOAccelID id);

};

#define super IOUserClient
OSDefineMetaClassAndStructorsWithInit(IOAccelerationUserClient, IOUserClient, 
					IOAccelerationUserClient::initialize());

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

void IOAccelerationUserClient::initialize()
{
    if (!gLock)
    {
	gLock = IOLockAlloc();
	queue_init(&gGlobalList);
    }
}

bool IOAccelerationUserClient::initWithTask( task_t owningTask, void * securityID,
                                             UInt32 type,  OSDictionary * properties )
{
   
    fTask = owningTask;
    queue_init(&fTaskList);

    return( super::initWithTask( owningTask, securityID, type, properties ));
}

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

    return (true);
}

IOReturn IOAccelerationUserClient::clientClose( void )
{
    if( !isInactive())
        terminate();

    return( kIOReturnSuccess );
}

void IOAccelerationUserClient::stop( IOService * provider )
{
    IOAccelIDRecord * record;

    IOLockLock(gLock);

    while (!queue_empty( &fTaskList ))
    {
        queue_remove_first( &fTaskList,
                            record,
                            IOAccelIDRecord *,
                            task_link );

	if (--record->retain)
	    record->task_link.next = 0;
	else
	{
	    queue_remove(&gGlobalList,
			    record,
			    IOAccelIDRecord *,
			    glob_link);
	    gTotalCount--;
	    IODelete(record, IOAccelIDRecord, 1);
	}
    }
    IOLockUnlock(gLock);

    super::stop( provider );
}

IOExternalMethod * IOAccelerationUserClient::getTargetAndMethodForIndex(
    IOService ** targetP, UInt32 index )
{
    static const IOExternalMethod methodTemplate[] =
    {
	/* 0 */  { NULL, (IOMethod) &IOAccelerationUserClient::extCreate,
		    kIOUCScalarIScalarO, 2, 1 },
	/* 1 */  { NULL, (IOMethod) &IOAccelerationUserClient::extDestroy,
		    kIOUCScalarIScalarO, 2, 0 },
    };

    if (index > (sizeof(methodTemplate) / sizeof(methodTemplate[0])))
        return (NULL);

    *targetP = this;

    return ((IOExternalMethod *)(methodTemplate + index));
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static 
IOReturn _CreateID(queue_head_t * taskList, IOOptionBits options,
		    IOAccelID requestedID, IOAccelID * idOut)
{
    IOReturn	      err;
    Boolean	      found;
    IOAccelIDRecord * record;
    IOAccelIDRecord * dup;

    record = IONew(IOAccelIDRecord, 1);
    record->retain = 1;

    IOLockLock(gLock);

    gTotalCount++;

    do
    {
	if (kIOAccelSpecificID & options)
	{
	    if ((requestedID > 4095) || (requestedID < -4096))
	    {
		err = kIOReturnExclusiveAccess;
		break;
	    }
    
	    found = false;
	    queue_iterate(&gGlobalList,
			    dup,
			    IOAccelIDRecord *,
			    glob_link)
	    {
		found = (dup->id == requestedID);
		if (found)
		    break;
	    }
    
	    if (found)
	    {
		err = kIOReturnExclusiveAccess;
		break;
	    }
    
	    record->id = requestedID;
	}
	else
	{
    	    record->id = ((IOAccelID) record) ^ (kTweakBits & gTweak++);
	}

	if (taskList)
	{
	    queue_enter(taskList, record,
			    IOAccelIDRecord *, task_link);
	}
	else
	    record->task_link.next = 0;

	queue_enter(&gGlobalList, record,
			IOAccelIDRecord *, glob_link);

	*idOut = record->id;
	err = kIOReturnSuccess;
    }
    while (false);

    if (kIOReturnSuccess != err)
	gTotalCount--;

    IOLockUnlock(gLock);

    if (kIOReturnSuccess != err)
    {
	IODelete(record, IOAccelIDRecord, 1);
    }
    return (err);
}

IOReturn IOAccelerationUserClient::extCreate(IOOptionBits options,
						IOAccelID requestedID, IOAccelID * idOut)
{
    return (_CreateID(&fTaskList, options, requestedID, idOut));
}

IOReturn IOAccelerationUserClient::extDestroy(IOOptionBits options, IOAccelID id)
{
    IOAccelIDRecord * record;
    bool found = false;
    IOLockLock(gLock);

    queue_iterate(&fTaskList,
                    record,
                    IOAccelIDRecord *,
                    task_link)
    {
        found = (record->id == id);
	if (found)
	{
	    queue_remove(&fTaskList,
			    record,
			    IOAccelIDRecord *,
			    task_link);
	    if (--record->retain)
		record->task_link.next = 0;
	    else
	    {
		queue_remove(&gGlobalList,
				record,
				IOAccelIDRecord *,
				glob_link);
		gTotalCount--;
		IODelete(record, IOAccelIDRecord, 1);
	    }
	    break;
	}
    }

    IOLockUnlock(gLock);

    return (found ? kIOReturnSuccess : kIOReturnBadMessageID);
}

IOReturn
IOAccelerator::createAccelID(IOOptionBits options, IOAccelID * identifier)
{
    return (_CreateID(0, options, *identifier, identifier));
}

IOReturn
IOAccelerator::retainAccelID(IOOptionBits options, IOAccelID id)
{
    IOAccelIDRecord * record;
    bool found = false;
    IOLockLock(gLock);

    queue_iterate(&gGlobalList,
                    record,
                    IOAccelIDRecord *,
                    glob_link)
    {
        found = (record->id == id);
	if (found)
	{
	    record->retain++;
	    break;
	}
    }

    IOLockUnlock(gLock);

    return (found ? kIOReturnSuccess : kIOReturnBadMessageID);
}

IOReturn
IOAccelerator::releaseAccelID(IOOptionBits options, IOAccelID id)
{
    IOAccelIDRecord * record;
    bool found = false;
    IOLockLock(gLock);

    queue_iterate(&gGlobalList,
                    record,
                    IOAccelIDRecord *,
                    glob_link)
    {
        found = (record->id == id);
	if (found)
	{
	    if (!--record->retain)
	    {
		if (record->task_link.next)
		    IOPanic("IOAccelerator::releaseID task_link");

		queue_remove(&gGlobalList,
				record,
				IOAccelIDRecord *,
				glob_link);
		gTotalCount--;
		IODelete(record, IOAccelIDRecord, 1);
	    }
	    break;
	}
    }

    IOLockUnlock(gLock);

    return (found ? kIOReturnSuccess : kIOReturnBadMessageID);
}

