baseport/syborg/soundsc/virtio_queue.cpp
author Maciej Jablonski <maciej.jablonski@sosco.com>
Wed, 12 May 2010 23:13:16 +0100
changeset 72 d00bf4f57250
parent 45 01c1ffcc4fca
permissions -rw-r--r--
Fix bug 873. Fixes to syborg virtio-audio code as well as to the audio driver

/*
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Accenture Ltd
*
* Contributors:
*
* Description: This file is a part of sound driver for Syborg adaptation.
*
*/

#include "virtio_queue.h"

// #define ENABLE_QEMU_AUDIO_MODEL_BUG_WORKAROUND
#define ENABLE_QEMU_VIRTIO_CLEANUP_BUG_WORKAROUND
#define PHYS_ADDR(x) Epoc::LinearToPhysical( reinterpret_cast<TUint32>(x))
	
namespace VirtIo
{


DQueue::DQueue( MIo& aVirtIo, TUint aId, TUint aCount ) 
	: iVirtIo( aVirtIo ), iId( aId ), iCount( aCount )
	{
	}
	
TInt DQueue::Construct()
	{
	TInt r = AllocQueue();
	if (r!=KErrNone)
		{ return r; }
	PreInitQueue();
	iVirtIo.SetQueueBase( iId, iDesc );
	PostInitQueue();
	return KErrNone;
	}

void DQueue::Wipe()
	{
	if ( iChunk )
		{
		iChunk->Close( NULL );
		iChunk = NULL;
		}
#ifndef ENABLE_QEMU_VIRTIO_CLEANUP_BUG_WORKAROUND		
	if ( iPhysAddr )
		{ Epoc::FreePhysicalRam( iPhysAddr ); }
#endif
	iPhysAddr = 0;
	}
	
DQueue::~DQueue()
	{
	Wipe();
	}	
	
//
// Implementation here is that descs are allocated from the one starting with lowest index, 
TInt DQueue::AddBuf( const TAddrLen aScatterList[], TUint32 aOutNum, TUint32 aInNum, Token aToken)
	{
	SYBORG_VIRTIO_DEBUG("AddBuf Q%x %x l%x %x,%x %x",
		iId, aScatterList[0].iAddr, aScatterList[0].iLen, aOutNum, aInNum, aToken );
	TUint outLeft = aOutNum;
	TUint inLeft = aInNum;
	TInt first = -1;
	TInt last = -1;
	TUint totalLen = 0;
	// @TODO maintain freedescs counter to know if the below is going to success from the outset.
	// alloc and initialise descs
	for ( TUint i = 0, j = 0; (i < iCount) && (outLeft+inLeft); ++i )
		{
		if (iDesc[i].iFlags == KFreeDescriptorMarker)
			{
			if (first<0)
				{ first = i; }
			iDesc[i].iFlags = ((outLeft)? 0 : TRingDesc::EFlagWrite);
			iDesc[i].iNext = KFreeDescriptorMarker;
			iDesc[i].iAddr = aScatterList[j].iAddr;
			iDesc[i].iLen = aScatterList[j].iLen;
			totalLen += aScatterList[j].iLen;
			iToken[i].iToken = aToken;
			iToken[i].iTotalLen = 0;
			if ( last >=0 )
				{ 
				iDesc[last].iNext = i; 
				iDesc[last].iFlags |= TRingDesc::EFlagNext;
				}
			last = i;
			if (outLeft) 
				{ --outLeft; }
			else 
				{ --inLeft; }
			j++;
			}
		}
	if (outLeft+inLeft) // rollback descs if not all could have been claimed
		{
		if (first>=0)
			{ FreeDescs(first); }
		SYBORG_VIRTIO_DEBUG("AddBuf Q%x - not ready", iId ); 			
		return KErrNotReady;
		}
	iToken[first].iTotalLen = totalLen;
	
	// fill a slot in avail ring
	iAvail->iRing[Slot(iAvail->iIdx)] = first;
	++iAvail->iIdx;
	
	return KErrNone;
	}	
	
// bases on the assumption that the lowest desc index with given aToken value is used (see addBuf)
// @todo make sure the buffer is not yet posted	
TInt DQueue::DetachBuf( Token aToken )
	{
	TUint myDescId = KFreeDescriptorMarker;
	for ( TIdx i = iNextAvailToSync; i != iAvail->iIdx ; ++i )
		{
		TUint availSlot = Slot( i );
		TUint descId = iAvail->iRing[availSlot];
		if ( descId < iCount )
			{
			if (iToken[descId].iToken == aToken)
				{
				myDescId = descId;
				break;
				}
			}
		}
	if ( myDescId != KFreeDescriptorMarker )
		{ return KErrNotFound; }
	FreeDescs( myDescId );
	return KErrNone;
	}	
	
Token DQueue::GetBuf( TUint& aLen )
	{
	TIdx usedIdx = iUsed->iIdx;
	ASSERT( ((TIdx)iNextUsedToRead) <= usedIdx );
	if (usedIdx == ((TIdx)iNextUsedToRead))
		{ return 0; }
	TUint nextUsedSlot = Slot(iNextUsedToRead);
	TUint len = iUsed->iRing[nextUsedSlot].iLen;
	TUint descId = iUsed->iRing[nextUsedSlot].iId;
	ASSERT(descId<iCount);
	Token token = iToken[descId].iToken;		
	SYBORG_VIRTIO_DEBUG( "GetBuf Q%x %x ..%x t%x D%x L%x OL%x", iId, iNextUsedToRead, usedIdx, token, descId, len, iToken[descId].iTotalLen );

	++iNextUsedToRead;
	FreeDescs( descId );
	
#ifdef ENABLE_QEMU_AUDIO_MODEL_BUG_WORKAROUND	
	aLen = len?len:iToken[descId].iTotalLen; // @TODO kind of a hack to solve virtio-audio's failure to report len by syborg on the side of qemu
#else
    aLen = len;
#endif
	return token;
	}	
	
TInt DQueue::Processing()
	{ return  ((TIdx)(iAvail->iIdx - iFirstEverToSync)) 
		- ((TIdx)(iUsed->iIdx - iFirstEverToRead)); }	
	
TInt DQueue::Completed()
	{ return  ((TIdx)(iUsed->iIdx - iNextUsedToRead)); }		

void DQueue::Sync()
	{
	SYBORG_VIRTIO_DEBUG("Sync Q%d, %x..%x",
		iId, iNextAvailToSync, iAvail->iIdx );	
	if ( ((TIdx)iNextAvailToSync) == iAvail->iIdx)
		{ return; }
	DumpAvailPending();

	iNextAvailToSync = iAvail->iIdx;
	iVirtIo.PostQueue( iId );
	}
	
void DQueue::DumpUsedPending()
	{
	for (TIdx i = iNextUsedToRead; i != iUsed->iIdx; ++i )
		{ DumpUsed( Slot(i) ); }
	}	
	
void DQueue::DumpUsed(TUint usedSlot)
	{
	SYBORG_VIRTIO_DEBUG("Usedslot = %x, aLen = %x, descId=%x", usedSlot, (TUint32) iUsed->iRing[usedSlot].iLen, iUsed->iRing[usedSlot].iId);
	TUint descId = iUsed->iRing[usedSlot].iId;
	DumpDescs( descId );
	}	
	
void DQueue::DumpAvailPending()
	{
	for (TIdx i = iNextAvailToSync; i != iAvail->iIdx; ++i )
		{ DumpAvail( Slot(i) ); }
	}
	
void DQueue::DumpAvail(TUint availSlot)
	{
	SYBORG_VIRTIO_DEBUG("Q%d, availslot = %x", iId, availSlot);
	TUint descId = iAvail->iRing[availSlot];
	DumpDescs( descId );
	}
	
void DQueue::DumpDescs(TUint descId )
	{
	do {
		TRingDesc& d = iDesc[descId];
        if ( (-1) == ((signed)d.iAddr) )
            break;
		SYBORG_VIRTIO_DEBUG(" Desc %x,addr %x, len %x, flags %x, next %x",
			(TUint32)descId, (TUint32)d.iAddr, (TUint32)d.iLen, (TUint32)d.iFlags, (TUint32)d.iNext );
		if ((d.iFlags&TRingDesc::EFlagNext)==0)
			{ break; }
        if ( KFreeDescriptorMarker == d.iNext )
            break;
		descId = d.iNext;
		} while (ETrue);
	}
	
void DQueue::DumpAll()
	{
	DumpAvailPending();
	DumpUsedPending();
	}	
		
void DQueue::FreeDescs( TUint firstDescIdx )
	{
	TInt i = firstDescIdx;
	Token token = iToken[firstDescIdx].iToken;
	while (i>=0)
		{
		ASSERT( ( ((TUint)i) < iCount ) );
		TUint flags = iDesc[i].iFlags;
		ASSERT( flags != KFreeDescriptorMarker );
		iDesc[i].iFlags = KFreeDescriptorMarker;
		ASSERT( iToken[i].iToken == token );
		iToken[i].iToken = 0;
		iToken[i].iTotalLen = 0;
        if ((flags&TRingDesc::EFlagNext)==0)
            break;
		i = iDesc[i].iNext;
		}
	}		
	
TUint8* DQueue::AllocMem( TUint aSize )
	{
	TInt r = KErrNone;
	
#ifdef ENABLE_QEMU_VIRTIO_CLEANUP_BUG_WORKAROUND		
	// note this is second part of workaround that deals
	// with the issue that memory once assigned to queuebase cannot be 
	// changed,
	// if queuebase != 0 this is because it was set when the driver
	// was loaded previous time

	iPhysAddr = (TUint32) iVirtIo.GetQueueBase( iId );
	iPhysMemReallyAllocated = (iPhysAddr == 0);
	if (iPhysMemReallyAllocated)
		{ r = Epoc::AllocPhysicalRam(aSize, iPhysAddr, 0 ); }

#endif

	if (r!=KErrNone ) 
		{ return NULL; }
	
	r = DPlatChunkHw::New( iChunk, iPhysAddr, aSize, 
		EMapAttrSupRw | EMapAttrL2Uncached | EMapAttrL1Uncached );

	if (r!=KErrNone ) 
		{
		if ( iPhysMemReallyAllocated )
			{ Epoc::FreePhysicalRam( iPhysAddr, aSize ); }
		iChunk->Close( NULL );
		return NULL;
		}

	ASSERT( r == KErrNone );
	return reinterpret_cast<TUint8*>( iChunk->LinearAddress() );
	}	
	
TInt DQueue::AllocQueue()
	{
	iDescSize = iCount * sizeof(TRingDesc);
	iAvailSize = sizeof(TRingAvail) + (iCount-1) * sizeof(((TRingAvail*)0)->iRing[0]);
	iTokenSize = iCount * sizeof(TTransactionInfo);
	TUint usedOffset = Align( iDescSize +  iAvailSize, KVirtIoAlignment );
	iUsedSize = sizeof(TRingUsed) + (iCount-1) * sizeof(((TRingUsed*)0)->iRing[0]);
	TUint size = usedOffset + iUsedSize;
	TUint8* iMemAligned;

	iMemAligned = AllocMem( size );
	
	if (!iMemAligned)
		{ return KErrNoMemory; }
	
	iDesc = reinterpret_cast<TRingDesc*>( iMemAligned );
	iAvail = reinterpret_cast<TRingAvail*>( iMemAligned + iDescSize );
	iUsed = reinterpret_cast<TRingUsed*>( iMemAligned + usedOffset );
	iToken = reinterpret_cast<TTransactionInfo*>( Kern::Alloc( iTokenSize ) );
	SYBORG_VIRTIO_DEBUG("DQueue %d, Virt iDesc=%x/%x,iAvail=%x/%x,iToken=%x,iUsed=%x/%x",
		iId, iDesc, iDescSize, iAvail, iAvailSize, iToken, iUsed, iUsedSize );
	SYBORG_VIRTIO_DEBUG("DQueue %d, Phys iDesc=%x, iUsed=%x",
		iId, PHYS_ADDR(iDesc), PHYS_ADDR(iUsed) );
	ASSERT( ((PHYS_ADDR(iUsed)-PHYS_ADDR(iDesc))) == ((TUint32)((TUint8*)iUsed-(TUint8*)iDesc)) );
	return KErrNone;
	}	
	
void DQueue::PreInitQueue()
	{
	memset(iDesc, -1, iDescSize );
	memset( ((TUint8*) iAvail) + 4, -1, iAvailSize - 4 );
	memset( ((TUint8*) iUsed) + 4, -1, iUsedSize - 4 );
	
	iAvail->iFlags = 0; // no notifications from control queue
	iUsed->iFlags = 0;
	if ( iPhysMemReallyAllocated )
		{
		iAvail->iIdx = 0;
		iUsed->iIdx = 0;
		}
	}	
	
void DQueue::PostInitQueue()
	{
	iFirstEverToSync = iNextAvailToSync = iAvail->iIdx;
	iFirstEverToRead = iNextUsedToRead = iUsed->iIdx;
	}		


} // namespace VirtIo