/*
 *  IOFWDCL.cpp
 *  IOFireWireFamily
 *
 *  Created by Niels on Fri Feb 21 2003.
 *  Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 *	$ Log:IOFWDCL.cpp,v $
 */

#import "IOFWDCL.h"
#import "FWDebugging.h"
#import "IOFWUserIsochPort.h"

#import "IOFireWireLibNuDCL.h"

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

#define FIRELOG_MSG(x)	{ do {} while (0); }

using namespace IOFireWireLib ;

OSDefineMetaClassAndAbstractStructors( IOFWDCL, OSObject )

bool
IOFWDCL::initWithRanges ( 
	OSSet * 				updateSet, 
	unsigned 				rangesCount, 
	IOVirtualRange 			ranges [] )
{
	if ( ! OSObject::init() )
		return false ;
		
	if ( updateSet )
	{
		updateSet->setObject( this ) ;
	}
	
	if ( kIOReturnSuccess != setRanges( rangesCount, ranges ) )
	{
		return false ;
	}
		
	return true ;
}

void
IOFWDCL::setBranch( IOFWDCL* branch )
{ 
	fLoLevel->lastBranch = fBranch ;
	fBranch = branch ;
}

IOFWDCL *
IOFWDCL::getBranch() const
{
	return fBranch ;
}

void
IOFWDCL::setTimeStampPtr ( UInt32* timeStampPtr )
{
	fTimeStampPtr = timeStampPtr ;
}

UInt32 *
IOFWDCL::getTimeStampPtr () const
{
	return (UInt32*)fTimeStampPtr ;
}

void
IOFWDCL::setCallback( Callback callback )
{
	fCallback = callback ; 
}

IOFWDCL::Callback
IOFWDCL::getCallback() const
{
	return fCallback ; 
}

void
IOFWDCL::setStatusPtr( UInt32* statusPtr )
{
	fUserStatusPtr = statusPtr ; 
}

UInt32*
IOFWDCL::getStatusPtr() const
{
	return (UInt32*) fUserStatusPtr ; 
}

void
IOFWDCL::setRefcon( void * refcon )
{
	fRefcon = refcon ; 
}

void *
IOFWDCL::getRefcon() const
{
	return fRefcon ; 
}

const OSSet *
IOFWDCL::getUpdateList() const
{
	return fUpdateList ;
}

IOReturn
IOFWDCL::addRange ( IOVirtualRange & range )
{	
	IOVirtualRange * newRanges = new IOVirtualRange[ fRangeCount + 1 ] ;
	if ( !newRanges )
	{
		return kIOReturnNoMemory ;
	}
	
	bcopy( fRanges, newRanges, sizeof( IOVirtualRange ) * fRangeCount ) ;
	delete[] fRanges ;
	
	fRanges = newRanges ;
	fRanges[ fRangeCount ] = range ;	
	++fRangeCount ;

	return kIOReturnSuccess ;
}

IOReturn
IOFWDCL::setRanges ( UInt32 numRanges, IOVirtualRange ranges[] )
{
	delete[] fRanges ;

	fRanges = ( numRanges && ranges ) ? new IOVirtualRange[ numRanges ] : NULL ;
	
	if ( !fRanges )
	{
		return kIOReturnNoMemory ;
	}
	
	bcopy( ranges, fRanges, numRanges * sizeof( IOVirtualRange ) ) ;
	fRangeCount = numRanges ;
	
	return kIOReturnSuccess ;
}

UInt32
IOFWDCL::getRanges( UInt32 maxRanges, IOVirtualRange ranges[] ) const
{
	unsigned count = min( maxRanges, fRangeCount ) ;
	for( unsigned index=0; index < count; ++index )
	{
		ranges[ index ] = fRanges[ index ] ;
	}
	
	return count ;
}

UInt32
IOFWDCL::countRanges()
{
	return fRangeCount ; 
}

IOReturn
IOFWDCL::getSpan( IOVirtualRange& result ) const
{
	if ( fRangeCount == 0 )
	{
		result.address = 0 ;
		result.length = 0 ;
		return kIOReturnSuccess ;
	}

	IOVirtualAddress lowAddress = fRanges[0].address ;
	IOVirtualAddress highAddress = lowAddress + fRanges[0].length ;

	for( unsigned index=1; index < fRangeCount; ++index )
	{
		lowAddress = min( fRanges[index].address, lowAddress ) ;
		highAddress = min( lowAddress + fRanges[ index ].length, highAddress ) ;
	}
	
	result.address = lowAddress ;
	result.length = highAddress - lowAddress ;
	
	return kIOReturnSuccess ;
}

IOByteCount
IOFWDCL::getSize() const
{
	IOByteCount size = 0 ;
	for( unsigned index=0; index < fRangeCount; ++index )
		size += fRanges[ index ].length ;
	
	return size ;
}

IOReturn
IOFWDCL::setUpdateList( OSSet* updateList )
{
	if ( updateList )
	{
		updateList->retain() ;
	}
	
	if ( fUpdateList )
	{
		fUpdateList->release() ;
		fUpdateList = NULL ;
	}

	if ( fUpdateIterator )
	{
		fUpdateIterator->release() ;
		fUpdateIterator = NULL ;
	}
	
	if ( updateList )
	{
		fUpdateList = updateList ;
		fUpdateIterator = OSCollectionIterator::withCollection( fUpdateList ) ;
	}
	
	return kIOReturnSuccess ;
}

void
IOFWDCL::setFlags( UInt32 flags )
{
	fFlags = flags ; 
}

UInt32
IOFWDCL::getFlags() const
{
	return fFlags ; 
}

void
IOFWDCL::debug()
{
	if ( fBranch )
	{
		DebugLog( "    branch --> %p\n", fBranch ) ;
	}
	
	if ( fCallback )
	{
		DebugLog( "    callback %p\n", fCallback ) ;
	}
	
	if ( fTimeStampPtr )
	{
		DebugLog( "    time stamp @ %p\n", fTimeStampPtr ) ;
	}
	
	if ( fRangeCount )
	{
		DebugLog( "    ranges\n" ) ;
		for( unsigned index=0; index < fRangeCount; ++index )
		{
			DebugLog( "        %d: %p ( +%d )\n", index, (void *)fRanges[index].address, (unsigned int)fRanges[index].length ) ;
		}
	}
	
	if ( fUpdateList )
	{
		DebugLog( "    update" ) ;
		OSIterator * iterator = OSCollectionIterator::withCollection( fUpdateList ) ;

		if ( !iterator )
		{
			DebugLog("couldn't get iterator!\n") ;
		}
		else
		{
			iterator->release() ;
		}
	}

	if ( fUserStatusPtr )
	{
		DebugLog( "    status @ %p\n", fUserStatusPtr ) ;
	}
	
	if ( fRefcon )
	{
		DebugLog( "    refcon %p\n", fRefcon ) ;
	}
}

void
IOFWDCL::free ()
{
	if ( fUpdateList )
	{
		fUpdateList->release() ;
	}
	if ( fUpdateIterator )
	{
		fUpdateIterator->release() ;
	}
	
	if ( fCallback == IOFWUserLocalIsochPort::s_nuDCLCallout )
	{
		delete [] (uint64_t*)fRefcon ;
	}
	
	delete[] fRanges ;
	
	delete fLoLevel ;
	
	OSObject::free() ;	
}

void
IOFWDCL::finalize ( IODCLProgram & )
{
	if ( fUpdateList )
	{
		fUpdateList->release() ;
		fUpdateList = NULL ;
	}
	
	if ( fUpdateIterator )
	{
		fUpdateIterator->release() ;
		fUpdateIterator = NULL ;
	}
}

IOReturn
IOFWDCL::importUserDCL (
	UInt8 *					data,
	IOByteCount &			dataSize,
	IOMemoryMap *			bufferMap,
	const OSArray *			dcls )
{
	IOVirtualAddress		kernBaseAddress = bufferMap->getVirtualAddress() ;
	NuDCLExportData * 		sharedData = ( NuDCLExportData * )data ;
	dataSize = sizeof( NuDCLExportData ) ;
	data += dataSize ;
	
	// (IOFireWireFamily) IOFWDCL<0xffffff804c8e8f40>::importUserDCL
	//	DebugLog("IOFWDCL<%p>::importUserDCL\n", this ) ;

	IOReturn error = kIOReturnSuccess ;

	{
		IOVirtualRange kernRanges[ sharedData->rangeCount ] ;
		for( unsigned index=0; index < sharedData->rangeCount; ++index )
		{
			kernRanges[ index ].address = kernBaseAddress + sharedData->ranges[ index ].address ;
			kernRanges[ index ].length = sharedData->ranges[ index ].length ;
		}

		error = setRanges( sharedData->rangeCount, kernRanges ) ;
	}

	if ( sharedData->updateCount )
	{
		// In the shared data struct for this DCL, the updateList field has
		// been filled in with the updateList length, if any, replacing
		// the user space CFMutableSetRef.
		// The update list data follows the dcl shared data struct
		// in the export data block.

		uint64_t * userUpdateList = ( uint64_t * )data ;
		dataSize += sharedData->updateCount * sizeof( uint64_t ) ;

		OSSet * updateSet = OSSet::withCapacity( (unsigned)sharedData->updateCount ) ;

		if ( __builtin_expect( !updateSet, false ) )
		{
			error = kIOReturnNoMemory ;
		}
		else
		{
			for( unsigned index=0; index < (unsigned)sharedData->updateCount; ++index )
			{
				updateSet->setObject( dcls->getObject( (unsigned int)(userUpdateList[ index ] - 1 ) ) ) ;
			}

			setUpdateList( updateSet ) ;
			updateSet->release() ;
		}
	}

	if ( !error )
	{
		setFlags( IOFWDCL::kUser | sharedData->flags ) ;
	}

	if ( !error )
	{
		if ( sharedData->callback )
		{
			setCallback( sharedData->callback ? IOFWUserLocalIsochPort::s_nuDCLCallout : NULL ) ;

			uint64_t * asyncRef = (uint64_t *) getRefcon() ;

			if ( !asyncRef )
			{
				asyncRef = new uint64_t[ kOSAsyncRef64Count ] ;
			}

			if ( !asyncRef )
			{
				error = kIOReturnNoMemory ;
			}
			else
			{
				asyncRef[ kIOAsyncCalloutFuncIndex ] = (uint64_t)sharedData->callback ;
				asyncRef[ kIOAsyncCalloutRefconIndex ] = (uint64_t)sharedData->refcon ;

				setRefcon( asyncRef ) ;
			}
		}
		else
		{
			if ( getCallback() == IOFWUserLocalIsochPort::s_nuDCLCallout )
			{
				delete [] (uint64_t*)getRefcon() ;
			}

			setCallback( NULL ) ;
			setRefcon( 0 ) ;
		}
	}

	if ( !error )
	{
		setTimeStampPtr( sharedData->timeStampOffset ? (UInt32*)( kernBaseAddress + sharedData->timeStampOffset - 1 ) : NULL ) ;
		setStatusPtr( sharedData->statusOffset ? (UInt32*)( kernBaseAddress + sharedData->statusOffset - 1 ) : NULL ) ;
	}

	if ( !error )
	{
		if ( sharedData->branchIndex )
		{
			unsigned branchIndex = (unsigned int)(sharedData->branchIndex - 1 ) ;
			if ( branchIndex >= dcls->getCount() )
			{
				DebugLog("branch index out of range\n") ;
				error = kIOReturnBadArgument ;
			}
			else
			{
				setBranch( (IOFWDCL*)dcls->getObject( branchIndex ) ) ;
			}
		}
		else
		{
			setBranch( NULL ) ;
		}
	}

	return error ;
}

OSMetaClassDefineReservedUsed ( IOFWDCL, 0 ) ;
OSMetaClassDefineReservedUnused ( IOFWDCL, 1 ) ;
OSMetaClassDefineReservedUnused ( IOFWDCL, 2 ) ;
OSMetaClassDefineReservedUnused ( IOFWDCL, 3 ) ;
OSMetaClassDefineReservedUnused ( IOFWDCL, 4 ) ;		// used to be relink()

#pragma mark -

OSDefineMetaClassAndAbstractStructors( IOFWReceiveDCL, IOFWDCL )

bool
IOFWReceiveDCL::initWithParams(
	OSSet * 			updateSet, 
	UInt8 				headerBytes, 
	unsigned 			rangesCount, 
	IOVirtualRange 		ranges[] )
{
	// can only get 0, 1, or 2 header quads...
	if (!( headerBytes == 0 || headerBytes == 4 || headerBytes == 8 ) )
	{	
		DebugLog("receive DCL header bytes must be 0, 4, or 8\n") ;
		return false ;
	}
	
	fHeaderBytes = headerBytes ;

	return IOFWDCL::initWithRanges( updateSet, rangesCount, ranges ) ;		
}


IOReturn
IOFWReceiveDCL::setWaitControl( bool wait )
{
	fWait = wait ; 
	return kIOReturnSuccess ; 
}

void
IOFWReceiveDCL::debug()
{
	DebugLog("%p: RECEIVE\n", this ) ;
	DebugLog("    wait: %s, headerBytes: %d\n", fWait ? "YES" : "NO", fHeaderBytes ) ;
	
	IOFWDCL::debug() ;
}

IOReturn
IOFWReceiveDCL::importUserDCL (
	UInt8 *					data,
	IOByteCount &			dataSize,
	IOMemoryMap *			bufferMap,
	const OSArray *			dcls )
{
	// (IOFireWireFamily) IOFWReceiveDCL<0xffffff804c4c8a40>::importUserDCL
	//	DebugLog("IOFWReceiveDCL<%p>::importUserDCL\n", this ) ;
	
	IOReturn error = IOFWDCL::importUserDCL( data, dataSize, bufferMap, dcls ) ;

	if ( !error )
	{
		ReceiveNuDCLExportData * rcvData = (ReceiveNuDCLExportData*)( data + dataSize ) ;
		dataSize += sizeof( ReceiveNuDCLExportData ) ;
	
		error = setWaitControl( rcvData->wait ) ;
	}
	
	return error ;
}

#pragma mark -

OSDefineMetaClassAndAbstractStructors( IOFWSendDCL, IOFWDCL )

void
IOFWSendDCL::free()
{
	if ( fSkipCallback == IOFWUserLocalIsochPort::s_nuDCLCallout )
	{
		delete[] (uint64_t*)fSkipRefcon ;
	}

	IOFWDCL::free() ;
}

bool
IOFWSendDCL::initWithParams (
	OSSet * 				updateSet, 
	unsigned 				rangesCount,
	IOVirtualRange 			ranges[],
	UInt8					sync,
	UInt8					tag )
{
	if ( !IOFWDCL::initWithRanges( updateSet, rangesCount, ranges ) )
		return false ;
	
	fSync = sync ;
	fTag = tag ;
	
	return true ;
}

IOReturn
IOFWSendDCL::addRange ( IOVirtualRange & range )
{
	if ( fRangeCount >= 5 )
		return kIOReturnError ;

	return IOFWDCL::addRange( range ) ;
}

void
IOFWSendDCL::setUserHeaderPtr( UInt32* userHeaderPtr, UInt32 * maskPtr )
{ 
	fUserHeaderPtr = userHeaderPtr ; 
	fUserHeaderMaskPtr = maskPtr ; 
}

UInt32 *
IOFWSendDCL::getUserHeaderPtr()
{
	return fUserHeaderPtr ; 
}

UInt32 *
IOFWSendDCL::getUserHeaderMask()
{
	return fUserHeaderMaskPtr ; 
}

void
IOFWSendDCL::setSkipBranch( IOFWDCL * skipBranchDCL )
{
	fSkipBranchDCL = skipBranchDCL ; 
}

IOFWDCL *
IOFWSendDCL::getSkipBranch() const
{
	return fSkipBranchDCL ; 
}

void
IOFWSendDCL::setSkipCallback( Callback callback )
{
	fSkipCallback = callback ; 
}

void
IOFWSendDCL::setSkipRefcon( void * refcon )
{
	fSkipRefcon = refcon ; 
}

IOFWDCL::Callback
IOFWSendDCL::getSkipCallback() const
{
	return fSkipCallback ;
}

void *
IOFWSendDCL::getSkipRefcon() const
{
	return fSkipRefcon ; 
}

void
IOFWSendDCL::setSync( UInt8 sync )
{
	fSync = sync ; 
}

UInt8
IOFWSendDCL::getSync() const
{
	return fSync ; 
}

void
IOFWSendDCL::setTag( UInt8 tag )
{
	fTag = tag ; 
}

UInt8
IOFWSendDCL::getTag() const
{
	return fTag ; 
}

IOReturn
IOFWSendDCL::setRanges ( UInt32 numRanges, IOVirtualRange ranges[] )
{
	if ( numRanges > 5 )
	{
		DebugLog( "can't build send DCL with more than 5 ranges\n" ) ;
		return kIOReturnBadArgument ;
	}
	
	return IOFWDCL::setRanges( numRanges, ranges ) ;
}

void
IOFWSendDCL::debug()
{
	DebugLog("%p: SEND\n", this ) ;
	if ( fUserHeaderPtr )
	{
		DebugLog("    user hdr: %p, user hdr mask --> %p\n", fUserHeaderPtr, fUserHeaderMaskPtr ) ;
	}

	if ( fSkipBranchDCL )
	{
		DebugLog("    skip --> %p\n", fSkipBranchDCL ) ;
	}
	
	if ( fSkipCallback )
	{
		if ( fSkipCallback == IOFWUserLocalIsochPort::s_nuDCLCallout )
		{
			DebugLog("        skip callback: (USER) " ) ;
			
			if ( fSkipRefcon )
			{
				DebugLog("callback:0x%llx refcon:0x%llx\n", ((uint64_t*)fSkipRefcon)[ kIOAsyncCalloutFuncIndex ], ((uint64_t*)fSkipRefcon)[ kIOAsyncCalloutRefconIndex ] ) ;
			}
			else
			{
				DebugLog("NULL\n") ;
			}
		}
		else
		{
			DebugLog("        skip callback: %p\n", fSkipCallback ) ;
			DebugLog("        skip refcon: %p\n", fSkipRefcon ) ;
		}
	}
	
	IOFWDCL::debug() ;
}

IOReturn
IOFWSendDCL::importUserDCL (
	UInt8 *					data,
	IOByteCount &			dataSize,
	IOMemoryMap *			bufferMap,
	const OSArray *			dcls )
{
	DebugLog("// *** TEST: IOFWSendDCL<%p>::importUserDCL\n", this ) ;
	
	IOReturn error = IOFWDCL::importUserDCL( data, dataSize, bufferMap, dcls ) ;
	if ( error )
	{
		return error ;
	}

	SendNuDCLExportData * sendData = (SendNuDCLExportData*) ( data + dataSize ) ;
	dataSize += sizeof( SendNuDCLExportData ) ;

	{
		setSync( sendData->syncBits ) ;
		setTag( sendData->tagBits ) ;
	}

	{
		UInt32 * userHeaderPtr = NULL ;
		UInt32 * userHeaderMaskPtr = NULL ;

		if ( sendData->userHeaderOffset && sendData->userHeaderMaskOffset )
		{
			IOVirtualAddress		kernBaseAddress = bufferMap->getVirtualAddress() ;

			userHeaderPtr = (UInt32*)( kernBaseAddress + sendData->userHeaderOffset - 1 ) ;
			userHeaderMaskPtr = (UInt32*)( kernBaseAddress + sendData->userHeaderMaskOffset - 1 ) ;
		}

		setUserHeaderPtr( userHeaderPtr, userHeaderMaskPtr ) ;
	}

	if ( sendData->skipBranchIndex )
	{
		unsigned branchIndex = (unsigned int)(sendData->skipBranchIndex - 1) ;
		if ( branchIndex >= dcls->getCount() )
		{
			DebugLog("skip branch index out of range\n") ;
			error = kIOReturnBadArgument ;
		}
		else
		{
			setSkipBranch( (IOFWDCL*)dcls->getObject( branchIndex ) ) ;
		}
	}
	else
	{
		setSkipBranch( NULL ) ;
	}

	{
		if ( sendData->skipCallback )
		{
			setSkipCallback( sendData->skipCallback ? IOFWUserLocalIsochPort::s_nuDCLCallout : NULL ) ;

			uint64_t * asyncRef = (uint64_t *) getSkipRefcon() ;

			if ( !asyncRef )
			{
				asyncRef = new uint64_t[ kOSAsyncRef64Count ] ;
			}

			if ( !asyncRef )
			{
				error = kIOReturnNoMemory ;
			}
			else
			{
				asyncRef[ kIOAsyncCalloutFuncIndex ] = (uint64_t)sendData->skipCallback ;
				asyncRef[ kIOAsyncCalloutRefconIndex ] = (uint64_t)sendData->skipRefcon ;

				setSkipRefcon( asyncRef ) ;
			}
		}
		else
		{
			if ( getSkipCallback() == IOFWUserLocalIsochPort::s_nuDCLCallout )
			{
				delete [] (uint64_t*)getSkipRefcon() ;
			}

			setSkipCallback( NULL ) ;
			setSkipRefcon( 0 ) ;
		}
	}


	return kIOReturnSuccess ;
}

#pragma mark -

OSDefineMetaClassAndAbstractStructors( IOFWSkipCycleDCL, IOFWDCL )

bool
IOFWSkipCycleDCL::init ()
{
	DebugLog("// *** TEST: IOFWSkipCycleDCL<%p>::init\n", this );
	
	bool result = IOFWDCL::initWithRanges( NULL, 0, NULL ) ;
	
	return result ;
}

IOReturn
IOFWSkipCycleDCL::addRange ( IOVirtualRange& range )
{
	return kIOReturnUnsupported ;
}

IOReturn
IOFWSkipCycleDCL::setRanges ( UInt32 numRanges, IOVirtualRange ranges[] )		
{
	if ( numRanges == 0 )
	{
		// init always calls setRanges.. for this DCL we will be called with 0 ranges, which
		// is okay, so return success ;
		
		return kIOReturnSuccess ;
	}
	return kIOReturnUnsupported ; 
}

IOReturn
IOFWSkipCycleDCL::getSpan ( IOVirtualRange& result )
{
	return kIOReturnUnsupported ;
}

void
IOFWSkipCycleDCL::debug()
{
	DebugLog("%p: SKIP CYCLE\n", this ) ;
	IOFWDCL::debug() ;
}
