// ****************************************************************************
//
//		CDaffyDuck.CPP
//
//		Implementation file for the CDaffyDuck class.
//		Set editor tabs to 3 for your viewing pleasure.
//
//		Copyright Echo Digital Audio Corporation (c) 1998 - 2002
//		All rights reserved
//		www.echoaudio.com
//		
//		Permission is hereby granted, free of charge, to any person obtaining a
//		copy of this software and associated documentation files (the
//		"Software"), to deal with the Software without restriction, including
//		without limitation the rights to use, copy, modify, merge, publish,
//		distribute, sublicense, and/or sell copies of the Software, and to
//		permit persons to whom the Software is furnished to do so, subject to
//		the following conditions:
//		
//		- Redistributions of source code must retain the above copyright
//		notice, this list of conditions and the following disclaimers.
//		
//		- Redistributions in binary form must reproduce the above copyright
//		notice, this list of conditions and the following disclaimers in the
//		documentation and/or other materials provided with the distribution.
//		
//		- Neither the name of Echo Digital Audio, nor the names of its
//		contributors may be used to endorse or promote products derived from
//		this Software without specific prior written permission.
//
//		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//		EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//		MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//		IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
//		ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
//		TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//		SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
// 
//---------------------------------------------------------------------------
//
// The head pointer tracks the next free slot in the circular buffers
// The tail pointer tracks the oldest mapping.
//
//****************************************************************************

#include "CEchoGals.h"



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

	Construction/destruction

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

//===========================================================================
//
// Overload new & delete so memory for this object is allocated
//	from non-paged memory.
//
//===========================================================================

PVOID CDaffyDuck::operator new( size_t Size )
{
	PVOID 		pMemory;
	ECHOSTATUS 	Status;
	
	Status = OsAllocateNonPaged(Size,&pMemory);
	
	if ( (ECHOSTATUS_OK != Status) || (NULL == pMemory ))
	{
		ECHO_DEBUGPRINTF(("CDaffyDuck::operator new - memory allocation failed\n"));

		pMemory = NULL;
	}
	else
	{
		memset( pMemory, 0, Size );
	}

	return pMemory;

}	// PVOID CDaffyDuck::operator new( size_t Size )


VOID  CDaffyDuck::operator delete( PVOID pVoid )
{
	if ( ECHOSTATUS_OK != OsFreeNonPaged( pVoid ) )
	{
		ECHO_DEBUGPRINTF(("CDaffyDuck::operator delete memory free failed\n"));
	}
	
}	// VOID  CDaffyDuck::operator delete( PVOID pVoid )


//===========================================================================
//
// Constructor
//
//===========================================================================

CDaffyDuck::CDaffyDuck
(
	PCOsSupport 	pOsSupport,
	CDspCommObject	*pDspCommObject,
	WORD				wPipeIndex
)
{
	//
	// Allocate the page for the duck entries
	// 
	if ( ECHOSTATUS_OK != pOsSupport->OsPageAllocate( 1,
																	  (PVOID *) &m_DuckEntries,
																	  &m_dwDuckEntriesPhys ) )
	{
		ECHO_DEBUGPRINTF( ("CDaffyDuck: memory allocation failed\n") );
		return;
	}
	
	//
	//	Stash stuff
	// 
	m_pOsSupport = pOsSupport;
	m_pDspCommObject = pDspCommObject;
	m_wPipeIndex = wPipeIndex;
	
	//
	// Put the physical address of the duck at the end of 
	// the m_DuckEntries array so the DSP will wrap around
	// to the start of the duck.  
	//
	m_DuckEntries[MAX_ENTRIES].PhysAddr = SWAP( m_dwDuckEntriesPhys );
	
}	// CDaffyDuck::CDaffyDuck()


//===========================================================================
//
// Destructor
//
//===========================================================================

CDaffyDuck::~CDaffyDuck()
{

	m_pOsSupport->OsPageFree(1,m_DuckEntries,m_dwDuckEntriesPhys);

}	// CDaffyDuck::~CDaffyDuck()




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

	Setup and initialization

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

//===========================================================================
//
// InitCheck - returns ECHOSTATUS_OK if the duck created OK
//
//===========================================================================

ECHOSTATUS CDaffyDuck::InitCheck()
{
	if (NULL == m_DuckEntries)
		return ECHOSTATUS_NO_MEM;
		
	return ECHOSTATUS_OK;
	
}	// InitCheck


//===========================================================================
//
// Reset - resets the mapping and duck entry circular buffers
//
//===========================================================================

void CDaffyDuck::Reset()
{
	//
	//	Zero everything
	// 
	OsZeroMemory(m_DuckEntries,4096);
	OsZeroMemory(m_Mappings,sizeof(m_Mappings));
	
	m_dwHead = 0;
	m_dwTail = 0;
	m_dwCount = 0;
	m_ullLastEndPos = 0;

	//
	// Put the physical address of the duck at the end of 
	// the m_DuckEntries array so the DSP will wrap around
	// to the start of the duck.  
	//
	m_DuckEntries[MAX_ENTRIES].PhysAddr = SWAP( m_dwDuckEntriesPhys );
	
}	// Reset


//===========================================================================
//
// ResetStartPos - Takes the current list and re-calculates the
// DMA end position for each mapping, starting at DMA position zero.
//
//===========================================================================

void CDaffyDuck::ResetStartPos()
{
	DWORD dwRemaining,dwIndex,dwNumEntries;

	m_ullLastEndPos = 0L;
	
	//
	// See if the mapping at the buffer tail has been consumed
	//
	dwRemaining = m_dwCount;
	dwIndex = m_dwTail;
	while (0 != dwRemaining)
	{
		m_Mappings[dwIndex].ullEndPos = 
			m_ullLastEndPos + SWAP( m_DuckEntries[dwIndex].dwSize );
			
		m_ullLastEndPos = m_Mappings[dwIndex].ullEndPos;
	
		dwNumEntries = m_Mappings[m_dwTail].dwNumEntries;
		
		ASSERT(dwRemaining >= dwNumEntries);

		dwIndex += dwNumEntries;
		if (dwIndex >= MAX_ENTRIES)
			dwIndex -= MAX_ENTRIES;
			
		dwRemaining -= dwNumEntries;
	}
	
}	// ResetStartPos




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

	Mapping management

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

//===========================================================================
//
// AddMapping
//
// Take a mapping and add it to the circular buffer.
//
// Note that the m_DuckEntries array is read by the DSP; entries must
// therefore be stored in little-endian format. 
//
// The buffer pointed to by dwPhysAddr and dwBytes must be
// physically contiguous.
//
//===========================================================================

ECHOSTATUS CDaffyDuck::AddMapping
(
	DWORD			dwPhysAddr,
	DWORD			dwBytes,
	DWORD			dwTag,
	DWORD			dwInterrupt,
	DWORD			&dwNumFreeEntries
)
{
#ifdef INTEGRITY_CHECK
	CheckIntegrity();
#endif	

	//
	// The caller should only be trying to do this if 
	// there are at least two free entries
	//
	ASSERT((MAX_ENTRIES - m_dwCount) >= 2);
	
	//
	//	At least two slots are available in the circular 
	// buffer, so it's OK to add either a regular mapping or 
	// a mapping with a double zero
	//
	m_DuckEntries[m_dwHead].PhysAddr = SWAP( dwPhysAddr );
	m_DuckEntries[m_dwHead].dwSize 	= SWAP( dwBytes );
	
	m_Mappings[m_dwHead].dwTag			= dwTag;
	m_Mappings[m_dwHead].ullEndPos	= m_ullLastEndPos + dwBytes;
	
	m_ullLastEndPos = m_Mappings[m_dwHead].ullEndPos;
	
	/*
	ECHO_DEBUGPRINTF(("CDaffyDuck::AddMapping  m_dwCount %ld  pipe index %d  end pos %I64x\n",
							m_dwCount,m_wPipeIndex,m_ullLastEndPos));	
	*/
		
	//
	// If the caller wants an interrupt after this mapping, then
	// dwInterrupt will be non-zero
	//
	if (dwInterrupt)
	{
		DWORD dwNext;

		//
		// Interrupt wanted - need to add an extra slot with the double zero
		//
		m_Mappings[m_dwHead].dwNumEntries = 2;

		//
		// Put in the double zero so the DSP will
		// generate an interrupt
		//
		dwNext = m_dwHead + 1;
		if (dwNext >= MAX_ENTRIES)
			dwNext -= MAX_ENTRIES;	
	
		m_DuckEntries[dwNext].PhysAddr 	= 0;	// no need to swap zero!
		m_DuckEntries[dwNext].dwSize 		= 0;
		
		m_dwCount += 2;				
	}
	else
	{
		m_Mappings[m_dwHead].dwNumEntries = 1;

		m_dwCount++;
	}
	
	//
	// Move the head to point to the next empty slot
	//		
	m_dwHead += m_Mappings[m_dwHead].dwNumEntries;
	if (m_dwHead >= MAX_ENTRIES)
		m_dwHead -= MAX_ENTRIES;	// circular buffer wrap

	//
	// Return value to the caller
	//	
	dwNumFreeEntries = MAX_ENTRIES - m_dwCount;
	
	//
	// Tell the DSP about the new buffer if the
	// interrupt flag is set 
	//
	if (dwInterrupt != 0)
	{
		m_pDspCommObject->AddBuffer( m_wPipeIndex );
	}


//#ifdef _DEBUG
#if 0
	DWORD hi,lo;
	
	hi = (DWORD) (m_ullLastEndPos >> 32);
	lo = (DWORD) (m_ullLastEndPos & 0xffffffffL);
	
	ECHO_DEBUGPRINTF(("Added tag %ld, end pos 0x%08lx%08lx (int %ld)\n",dwTag,hi,lo,dwInterrupt));

#ifdef INTEGRITY_CHECK
	CheckIntegrity();
#endif

	ECHO_DEBUGPRINTF(("Daffy duck count is %ld\n",m_dwCount));
	
#endif
	
	return ECHOSTATUS_OK;
	
}	// AddMapping


//===========================================================================
//
// AddDoubleZero
//
// Adds a double zero to the circular buffer to cause the DSP to generate an
// IRQ.  If the buffer is full -or- the most recent entry already has a 
// double zero, nothing happens.
// 
//===========================================================================

void CDaffyDuck::AddDoubleZero()
{
	//
	// Skip if the circular buffer is full or empty
	//
	if ((MAX_ENTRIES == m_dwCount) || (0 == m_dwCount))
		return;
	
	//ECHO_DEBUGPRINTF(("CDaffyDuck::AddDoubleZero  m_dwCount %ld\n",m_dwCount));	

	//
	// Search back before the head and find the most recent entry
	//
	DWORD	dwEntry;
#ifdef _DEBUG
	DWORD dwCount = 0;
#endif


	do
	{
#ifdef _DEBUG
		dwCount++;
		if (dwCount > 2)
		{
			ECHO_DEBUGPRINTF(("CDaffyDuck::AddDoubleZero - shouldn't search back more "
									"than two entries!\n"));
			ECHO_DEBUGBREAK();
		}
#endif

		dwEntry = m_dwHead - 1;
		if (dwEntry >= 0x80000000)
			dwEntry += MAX_ENTRIES;		// circular buffer wrap

		if (m_Mappings[dwEntry].dwNumEntries != 0)
			break;


				
	} while (dwEntry != m_dwTail);

	//
	// Quit if this entry already has a double zero
	//
	ASSERT(m_Mappings[dwEntry].dwNumEntries <= 2);

	if (2 == m_Mappings[dwEntry].dwNumEntries)
		return;
		
	//
	// Add the double zero
	//
	DWORD	dwNext;
	
	dwNext = dwEntry + 1;
	if (dwNext >= MAX_ENTRIES)
		dwNext -= MAX_ENTRIES;

	m_DuckEntries[dwNext].PhysAddr 	= 0;
	m_DuckEntries[dwNext].dwSize 		= 0;

	m_Mappings[dwEntry].dwNumEntries += 1;
	
	m_dwCount++;
	
	m_dwHead = dwEntry + m_Mappings[dwEntry].dwNumEntries;
	if (m_dwHead >= MAX_ENTRIES)
		m_dwHead -= MAX_ENTRIES;
	
	//
	// Tell the DSP about it
	//	
	m_pDspCommObject->AddBuffer( m_wPipeIndex );	
	
	//ECHO_DEBUGPRINTF(("Added double zero\n"));
		
}	// AddDoubleZero


//===========================================================================
//
//	Wrap
// 
// Put a "next PLE" pointer at the end of the duck to make the DSP
// wrap around to the start; this creates a circular buffer.
//
//===========================================================================

void CDaffyDuck::Wrap()
{
	ASSERT(m_dwCount != MAX_ENTRIES);
	
	//
	// Put in the address of the start of the duck entries
	// and a count of zero; a count of zero tells the DSP 
	// "Go look again for a duck entry at this address"
	//	
	m_DuckEntries[m_dwHead].PhysAddr		= SWAP( m_dwDuckEntriesPhys );
	m_DuckEntries[m_dwHead].dwSize		= 0;

	m_Mappings[m_dwHead].dwNumEntries 	= 1;
	
	m_dwHead++;
	if (m_dwHead >= MAX_ENTRIES)
		m_dwHead -= MAX_ENTRIES;
	
	m_dwCount++;
	
	m_fWrapped = TRUE;
	
}	// Wrap


//===========================================================================
//
// ReleaseUsedMapping
//
// Find a mapping that's been consumed and return it
//
//===========================================================================

ECHOSTATUS CDaffyDuck::ReleaseUsedMapping
(
	ULONGLONG 	ullDmaPos,
	DWORD			&dwTag
)
{

#ifdef INTERGRITY_CHECK
	CheckIntegrity();	
#endif

	//
	// See if there are any mappings and if the oldest mapping has
	// been consumed by the DSP.
	//
	if ((0 != m_dwCount) && (ullDmaPos >= m_Mappings[m_dwTail].ullEndPos))
	{
		DWORD dwNumEntries;
	
		//
		// Time to release the mapping at the tail
		//
		dwTag = m_Mappings[m_dwTail].dwTag;
		
		//
		// Remove the mapping from the circular buffer,
		// taking dwNumEntries into consideration 
		//
		dwNumEntries = m_Mappings[m_dwTail].dwNumEntries;
		m_Mappings[m_dwTail].dwNumEntries = 0;
		ASSERT(m_dwCount >= dwNumEntries);
		
		m_dwTail += dwNumEntries;
		if (m_dwTail >= MAX_ENTRIES)
			m_dwTail -= MAX_ENTRIES;
			
		m_dwCount -= dwNumEntries;

#ifdef INTEGRITY_CHECK
		CheckIntegrity();
#endif	

#if 0
//#ifdef _DEBUG
		ULONGLONG ullRemaining;
		
		ullRemaining = m_ullLastEndPos - ullDmaPos;

		ECHO_DEBUGPRINTF(("Released mapping for pipe index %d - bytes remaining %I64x\n",
								m_wPipeIndex,ullRemaining));
#endif								

		return ECHOSTATUS_OK;
	}

	//
	// No bananas today, thanks.
	//
	return ECHOSTATUS_NO_DATA;
		
}	// ReleaseUsedMappings


//===========================================================================
//
// RevokeMappings
//
// Returns the number actually revoked
//
//===========================================================================

DWORD CDaffyDuck::RevokeMappings(DWORD dwFirstTag,DWORD dwLastTag)
{
	MAPPING 		*TempMappings = NULL;
	DUCKENTRY	*TempEntries = NULL;
	DWORD			dwNumTemp;
	DWORD			dwOffset;
	DWORD			dwNumRevoked = 0;
	
	//
	// Allocate the arrays
	//
	OsAllocateNonPaged(	sizeof(MAPPING) * MAX_ENTRIES,	// fixme make this part of the duck
							  	(void **) &TempMappings );
	OsAllocateNonPaged(	sizeof(DUCKENTRY) * MAX_ENTRIES,						  
								(void **) &TempEntries );
	if (	(NULL == TempMappings) ||
			(NULL == TempEntries))
	{
		goto exit;		
	}
	
	//
	// Tweak the end position so it's set OK
	// when the duck is rebuilt
	//
	SetLastEndPos(GetStartPos());
	

	//----------------------------------------------------------------------
	//
	// The tags are 32 bit counters, so it is possible that they will
	// wrap around (that is, dwLastTag may be less than dwFirstTag).  If the
	// tags have wrapped, use an offset so the compares work correctly.
	//
	//----------------------------------------------------------------------

	dwOffset = 0;	
	if (dwLastTag < dwFirstTag)
	{
		dwOffset = 0x80000000;
		
		dwLastTag -= dwOffset;
		dwFirstTag -= dwOffset;
	}
	
	//----------------------------------------------------------------------
	//
	// Empty out the duck into the temp arrays, throwing away
	// the revoked mappings.
	//
	//----------------------------------------------------------------------
	
	ECHOSTATUS 	Status;
	DWORD			dwAdjustedTag;

	dwNumTemp = 0;	
	do	
	{
		Status = GetTail(	TempEntries + dwNumTemp,
								TempMappings + dwNumTemp);
		if (ECHOSTATUS_OK == Status)
		{
			dwAdjustedTag = TempMappings[dwNumTemp].dwTag - dwOffset;

			if ((dwFirstTag <= dwAdjustedTag) && 
				 (dwAdjustedTag <= dwLastTag))
			{
				//
				// Revoke this tag
				//
				dwNumRevoked++;
			}
			else
			{
				//
				// Save this tag
				//
				dwNumTemp++;						
			}
			
		}
	} while (ECHOSTATUS_OK == Status);
	

	//----------------------------------------------------------------------
	//
	// Add all the saved mappings back into this duck.
	//
	//----------------------------------------------------------------------
		
	DWORD i,dwDummy;
	
	//
	// Partially reset this duck
	//
	m_dwHead = 0;
	m_dwTail = 0;
	m_dwCount = 0;

	//
	// Put all the non-revoked mappings back
	//	
	for (i = 0; i < dwNumTemp; i++)
	{
		AddMapping(	TempEntries[i].PhysAddr,
						TempEntries[i].dwSize,
						TempMappings[i].dwTag,
						TempMappings[i].dwNumEntries - 1,
						dwDummy);
	}
	
	ECHO_DEBUGPRINTF(("CDaffyDuck::RevokeMappings for pipe index %d - m_dwHead %ld  m_dwTail %ld  m_dwCount %ld\n",
							m_wPipeIndex,m_dwHead,m_dwTail,m_dwCount));

exit:
	if (TempEntries) OsFreeNonPaged(TempEntries);
	if (TempMappings) OsFreeNonPaged(TempMappings);	
	
	return dwNumRevoked;	

}	// RevokeMappings


//===========================================================================
//
// GetTail
//
// Unconditionally returns the mapping information from the tail of the 
// circular buffer.
//
//===========================================================================

ECHOSTATUS CDaffyDuck::GetTail(DUCKENTRY *pDuckEntry,MAPPING *pMapping)
{
	//
	// Punt if there are no entries in the circular buffer
	//
	if (0 == m_dwCount)
		return ECHOSTATUS_NO_DATA;
		
	//
	// Copy the data
	//		
	OsCopyMemory(	pDuckEntry,
						m_DuckEntries + m_dwTail,
						sizeof(DUCKENTRY));
	OsCopyMemory(	pMapping,
						m_Mappings + m_dwTail,
						sizeof(MAPPING));
	
	//
	// Buffer housekeeping
	//
	DWORD dwNumEntries = m_Mappings[m_dwTail].dwNumEntries;
	m_Mappings[m_dwTail].dwNumEntries = 0;	
	
	ASSERT(m_dwCount >= dwNumEntries);

	m_dwTail += dwNumEntries;
	if (m_dwTail >= MAX_ENTRIES)
		m_dwTail -= MAX_ENTRIES;
		
	m_dwCount -= dwNumEntries;
	
#ifdef INTEGRITY_CHECK
	CheckIntegrity();
#endif	
	
	return ECHOSTATUS_OK;
	
}	// GetTail


//===========================================================================
//
// CheckIntegrity
//
// Debug code - makes sure that the buffer count, head, and tail all agree
//
//===========================================================================

#ifdef INTEGRITY_CHECK

void CDaffyDuck::CheckIntegrity()
{
	DWORD dwDiff;
	
	dwDiff = m_dwHead - m_dwTail;
	if (dwDiff > 0x80000000)
		dwDiff += MAX_ENTRIES;
	
	if ((0 == dwDiff) && (MAX_ENTRIES == m_dwCount))
		return;
	
	if (dwDiff != m_dwCount)
	{
		ECHO_DEBUGPRINTF(("CDaffyDuck integrity check fail!  m_dwHead %ld  m_dwTail %ld  "
								"m_dwCount %ld  m_Mappings[m_dwHead].dwNumEntries %ld\n",
								m_dwHead,m_dwTail,m_dwCount,m_Mappings[m_dwHead].dwNumEntries));
		ECHO_DEBUGBREAK();		
	}
	
}	// CheckIntegrity

#endif // INTEGRITY_CHECK


// *** CDaffyDuck.cpp ***
