kernel/eka/drivers/dma/dmapil.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 8 538db54a451d
child 20 597aaf25e343
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

// Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// 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:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// e32\drivers\dmapil.cpp
// DMA Platform Independent Layer (PIL)
// 
//

#include <drivers/dma.h>
#include <kernel/kern_priv.h>


static const char KDmaPanicCat[] = "DMA";

NFastMutex DmaChannelMgr::Lock;

class TDmaCancelInfo : public SDblQueLink
	{
public:
	TDmaCancelInfo();
	void Signal();
public:
	NFastSemaphore iSem;
	};

TDmaCancelInfo::TDmaCancelInfo()
	:	iSem(0)
	{
	iNext = this;
	iPrev = this;
	}

void TDmaCancelInfo::Signal()
	{
	TDmaCancelInfo* p = this;
	FOREVER
		{
		TDmaCancelInfo* next = (TDmaCancelInfo*)p->iNext;
		if (p!=next)
			p->Deque();
		NKern::FSSignal(&p->iSem);	// Don't dereference p after this
		if (p==next)
			break;
		p = next;
		}
	}

//////////////////////////////////////////////////////////////////////////////

#ifdef __DMASIM__
#ifdef __WINS__
typedef TLinAddr TPhysAddr;
#endif
static inline TPhysAddr LinToPhys(TLinAddr aLin) {return aLin;}
#else
static inline TPhysAddr LinToPhys(TLinAddr aLin) {return Epoc::LinearToPhysical(aLin);}
#endif

//
// Return minimum of aMaxSize and size of largest physically contiguous block
// starting at aLinAddr.
//
static TInt MaxPhysSize(TLinAddr aLinAddr, const TInt aMaxSize)
	{
	const TPhysAddr physBase = LinToPhys(aLinAddr);
	TLinAddr lin = aLinAddr;
	TInt size = 0;
	for (;;)
		{
		// Round up the linear address to the next MMU page boundary
		const TLinAddr linBoundary = Kern::RoundToPageSize(lin + 1);
		size += linBoundary - lin;
		if (size >= aMaxSize)
			return aMaxSize;
		if ((physBase + size) != LinToPhys(linBoundary))
			return size;
		lin = linBoundary;
		}
	}


//////////////////////////////////////////////////////////////////////////////
// TDmac

TDmac::TDmac(const SCreateInfo& aInfo)
	: iMaxDesCount(aInfo.iDesCount),
	  iAvailDesCount(aInfo.iDesCount),
	  iDesSize(aInfo.iDesSize),
	  iCaps(aInfo.iCaps)
	{
	__DMA_ASSERTD(iMaxDesCount > 0);
	__DMA_ASSERTD((iCaps & ~KCapsBitHwDes) == 0); // undefined bits set?
	__DMA_ASSERTD(iDesSize > 0);
	}

//
// Second-phase c'tor
//

TInt TDmac::Create(const SCreateInfo& aInfo)
	{
	iHdrPool = new SDmaDesHdr[iMaxDesCount];
	if (iHdrPool == NULL)
		return KErrNoMemory;

	TInt r = AllocDesPool(aInfo.iDesChunkAttribs);
	if (r != KErrNone)
		return KErrNoMemory;

	// Link all descriptor headers together on the free list
	iFreeHdr = iHdrPool;
	TInt i;
	for (i = 0; i < iMaxDesCount - 1; i++)
		iHdrPool[i].iNext = iHdrPool + i + 1;
	iHdrPool[iMaxDesCount-1].iNext = NULL;

	__DMA_INVARIANT();
	return KErrNone;
	}


TDmac::~TDmac()
	{
	__DMA_INVARIANT();

	FreeDesPool();
	delete[] iHdrPool;
	}


// Calling thread must be in CS
TInt TDmac::AllocDesPool(TUint aAttribs)
	{
	TInt r;
	if (iCaps & KCapsBitHwDes)
		{
		TInt size = iMaxDesCount*iDesSize;
#ifdef __WINS__
		(void)aAttribs;
		iDesPool = new TUint8[size];
		r = iDesPool ? KErrNone : KErrNoMemory;
#else
		// Chunk not mapped as supervisor r/w user none? incorrect mask passed by PSL
		__DMA_ASSERTD((aAttribs & EMapAttrAccessMask) == EMapAttrSupRw);
		TPhysAddr phys;
		r = Epoc::AllocPhysicalRam(size, phys);
		if (r == KErrNone)
			{
			r = DPlatChunkHw::New(iHwDesChunk, phys, size, aAttribs);
			if (r == KErrNone)
				{
				iDesPool = (TAny*)iHwDesChunk->LinearAddress();
				__KTRACE_OPT(KDMA, Kern::Printf("descriptor hw chunk created lin=0x%08X phys=0x%08X, size=0x%X",
												iHwDesChunk->iLinAddr, iHwDesChunk->iPhysAddr, size));
				}
			else
				Epoc::FreePhysicalRam(phys, size);
			}
#endif
		}
	else
		{
		iDesPool = new SDmaPseudoDes[iMaxDesCount];
		r = iDesPool ? KErrNone : KErrNoMemory;
		}
	return r;
	}


// Calling thread must be in CS
void TDmac::FreeDesPool()
	{
	if (iCaps & KCapsBitHwDes)
		{
#ifdef __WINS__
		delete[] iDesPool;
#else
		if (iHwDesChunk)
			{
			TPhysAddr phys = iHwDesChunk->PhysicalAddress();
			TInt size = iHwDesChunk->iSize;
			iHwDesChunk->Close(NULL);
			Epoc::FreePhysicalRam(phys, size);
			}
#endif
		}
	else
		Kern::Free(iDesPool); 
	}


/**
 Prealloc the given number of descriptors.
 */

TInt TDmac::ReserveSetOfDes(TInt aCount)
	{
	__KTRACE_OPT(KDMA, Kern::Printf(">TDmac::ReserveSetOfDes count=%d", aCount));
	__DMA_ASSERTD(aCount > 0);
	TInt r = KErrTooBig;
	Wait();
	if (iAvailDesCount - aCount >= 0)
		{
		iAvailDesCount -= aCount;
		r = KErrNone;
		}
	Signal();
	__DMA_INVARIANT();
	__KTRACE_OPT(KDMA, Kern::Printf("<TDmac::ReserveSetOfDes r=%d", r));
	return r;
	}


/**
 Return the given number of preallocated descriptors to the free pool.
 */

void TDmac::ReleaseSetOfDes(TInt aCount)
	{
	__DMA_ASSERTD(aCount >= 0);
	Wait();
	iAvailDesCount += aCount;
	Signal();
	__DMA_INVARIANT();
	}


/**
 Queue DFC and update word used to communicate with DFC.

 Called in interrupt context by PSL.
 */

void TDmac::HandleIsr(TDmaChannel& aChannel, TBool aIsComplete)
	{
	//__KTRACE_OPT(KDMA, Kern::Printf("TDmac::HandleIsr channel=%d complete=%d", aChannelIdx, aIsComplete));

	// Queue DFC if necessary.  The possible scenarios are:
	// * no DFC queued --> need to queue DFC
	// * DFC queued (not running yet) --> just need to update iIsrDfc
	// * DFC running / iIsrDfc already reset --> need to requeue DFC
	// * DFC running /  iIsrDfc not reset yet --> just need to update iIsrDfc
	// Set error flag if necessary.
	TUint32 inc = aIsComplete ? 1u : TUint32(TDmaChannel::KErrorFlagMask)|1u;
	TUint32 orig = __e32_atomic_tau_ord32(&aChannel.iIsrDfc, TUint32(TDmaChannel::KCancelFlagMask), 0, inc);

	// As transfer should be suspended when an error occurs, we
	// should never get there with the error flag already set.
	__DMA_ASSERTD((orig & inc & (TUint32)TDmaChannel::KErrorFlagMask) == 0);

	if (orig == 0)
		aChannel.iDfc.Add();
	}


void TDmac::InitDes(const SDmaDesHdr& aHdr, TUint32 aSrc, TUint32 aDest, TInt aCount,
					TUint aFlags, TUint32 aPslInfo, TUint32 aCookie)
	{
 	if (iCaps & KCapsBitHwDes)
		InitHwDes(aHdr, aSrc, aDest, aCount, aFlags, aPslInfo, aCookie);
	else
		{
		SDmaPseudoDes& des = HdrToDes(aHdr);
		des.iSrc = aSrc;
		des.iDest = aDest;
		des.iCount = aCount;
		des.iFlags = aFlags;
		des.iPslInfo = aPslInfo;
		des.iCookie = aCookie;
		}
	}


void TDmac::InitHwDes(const SDmaDesHdr& /*aHdr*/, TUint32 /*aSrc*/, TUint32 /*aDest*/, TInt /*aCount*/,
					  TUint /*aFlags*/, TUint32 /*aPslInfo*/, TUint32 /*aCookie*/)
	{
	// concrete controller must override if KCapsBitHwDes set
	__DMA_CANT_HAPPEN();
	}


void TDmac::ChainHwDes(const SDmaDesHdr& /*aHdr*/, const SDmaDesHdr& /*aNextHdr*/)
	{
	// concrete controller must override if KCapsBitHwDes set
	__DMA_CANT_HAPPEN();
	}


void TDmac::AppendHwDes(const TDmaChannel& /*aChannel*/, const SDmaDesHdr& /*aLastHdr*/,
						const SDmaDesHdr& /*aNewHdr*/)
	{
 	// concrete controller must override if KCapsBitHwDes set
	__DMA_CANT_HAPPEN();
	}


void TDmac::UnlinkHwDes(const TDmaChannel& /*aChannel*/, SDmaDesHdr& /*aHdr*/)
	{
 	// concrete controller must override if KCapsBitHwDes set
	__DMA_CANT_HAPPEN();
	}


TInt TDmac::FailNext(const TDmaChannel& /*aChannel*/)
	{
	return KErrNotSupported;
	}


TInt TDmac::MissNextInterrupts(const TDmaChannel& /*aChannel*/, TInt /*aInterruptCount*/)
	{
	return KErrNotSupported;
	}


TInt TDmac::Extension(TDmaChannel& /*aChannel*/, TInt /*aCmd*/, TAny* /*aArg*/)
	{
	// default implementation - NOP
	return KErrNotSupported;
	}


#ifdef _DEBUG

void TDmac::Invariant()
	{
	Wait();
	__DMA_ASSERTD(0 <= iAvailDesCount && iAvailDesCount <= iMaxDesCount);
	__DMA_ASSERTD(! iFreeHdr || IsValidHdr(iFreeHdr));
	for (TInt i = 0; i < iMaxDesCount; i++)
		__DMA_ASSERTD(iHdrPool[i].iNext == NULL || IsValidHdr(iHdrPool[i].iNext));
	Signal();
	}


TBool TDmac::IsValidHdr(const SDmaDesHdr* aHdr)
	{
	return (iHdrPool <= aHdr) && (aHdr < iHdrPool + iMaxDesCount);
	}

#endif

//////////////////////////////////////////////////////////////////////////////
// DDmaRequest


EXPORT_C DDmaRequest::DDmaRequest(TDmaChannel& aChannel, TCallback aCb, TAny* aCbArg, TInt aMaxTransferSize)
	: iChannel(aChannel),
	  iCb(aCb),
	  iCbArg(aCbArg),
	  iMaxTransferSize(aMaxTransferSize)
	{
	// iDesCount = 0;
	// iFirstHdr = iLastHdr = NULL;
	// iQueued = EFalse;
	iChannel.iReqCount++;
	__DMA_INVARIANT();
	}



EXPORT_C DDmaRequest::~DDmaRequest()
	{
	__DMA_ASSERTD(!iQueued);
	__DMA_INVARIANT();
	FreeDesList();
	iChannel.iReqCount--;
	}



EXPORT_C TInt DDmaRequest::Fragment(TUint32 aSrc, TUint32 aDest, TInt aCount, TUint aFlags, TUint32 aPslInfo)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("DDmaRequest::Fragment thread %O "
									"src=0x%08X dest=0x%08X count=%d flags=0x%X psl=0x%08X",
									&Kern::CurrentThread(), aSrc, aDest, aCount, aFlags, aPslInfo));
	__DMA_ASSERTD(aCount > 0);
	__DMA_ASSERTD(!iQueued);

	const TUint alignMask = iChannel.MemAlignMask(aFlags, aPslInfo);
	const TBool memSrc  = aFlags & KDmaMemSrc;
	const TBool memDest = aFlags & KDmaMemDest;

	// Memory buffers must satisfy alignment constraint
	__DMA_ASSERTD(!memSrc || ((aSrc & alignMask) == 0));
	__DMA_ASSERTD(!memDest || ((aDest & alignMask) == 0));

	// Ask the PSL what the maximum size possible for this transfer is
	TInt maxTransferSize = iChannel.MaxTransferSize(aFlags, aPslInfo);
	if (!maxTransferSize)
		{
		__KTRACE_OPT(KPANIC, Kern::Printf("Error: maxTransferSize == 0"));
		return KErrArgument;
		}

	if (iMaxTransferSize)
		{
		// User has set a size cap
		__DMA_ASSERTA((iMaxTransferSize <= maxTransferSize) || (maxTransferSize == -1));
		maxTransferSize = iMaxTransferSize;
		}
	else
		{
		// User doesn't care about max size
		if (maxTransferSize == -1)
			{
			// No maximum imposed by controller
			maxTransferSize = aCount;
			}
		}

	const TInt maxAlignedSize = (maxTransferSize & ~alignMask);
	__DMA_ASSERTD(maxAlignedSize > 0);						// bug in PSL if not true

	FreeDesList();

	TInt r = KErrNone;
	do
		{
		// Allocate fragment
		r = ExpandDesList();
		if (r != KErrNone)
			{
			FreeDesList();
			break;
			}

		// Compute fragment size
		TInt c = Min(maxTransferSize, aCount);
		if (memSrc && ((aFlags & KDmaPhysAddrSrc) == 0))
			c = MaxPhysSize(aSrc, c);
		if (memDest && ((aFlags & KDmaPhysAddrDest) == 0))
			c = MaxPhysSize(aDest, c);
		if ((memSrc || memDest) && (c < aCount) && (c > maxAlignedSize))
			{
			// This is not last fragment of transfer to/from memory. We must
			// round down fragment size so next one is correctly aligned.
			c = maxAlignedSize;
			}

		// Initialise fragment
		__KTRACE_OPT(KDMA, Kern::Printf("fragment: src=0x%08X dest=0x%08X count=%d", aSrc, aDest, c));
		iChannel.iController->InitDes(*iLastHdr, aSrc, aDest, c, aFlags, aPslInfo, iChannel.PslId());

		// Update for next iteration
		aCount -= c;
		if (memSrc)
			aSrc += c;
		if (memDest)
			aDest += c;
		}
	while (aCount > 0);

	__DMA_INVARIANT();
	return r;
	}



EXPORT_C void DDmaRequest::Queue()
	{
	__KTRACE_OPT(KDMA, Kern::Printf("DDmaRequest::Queue thread %O", &Kern::CurrentThread()));
	__DMA_ASSERTD(iDesCount > 0);	// Not configured? call Fragment() first !
	__DMA_ASSERTD(!iQueued);

	// append request to queue and link new descriptor list to existing one.
	iChannel.Wait();

	TUint32 req_count = iChannel.iQueuedRequests++;
	if (req_count == 0)
		{
		iChannel.Signal();
		iChannel.QueuedRequestCountChanged();
		iChannel.Wait();
		}

	if (!(iChannel.iIsrDfc & (TUint32)TDmaChannel::KCancelFlagMask))
		{
		iQueued = ETrue;
		iChannel.iReqQ.Add(&iLink);
		*iChannel.iNullPtr = iFirstHdr;
		iChannel.iNullPtr = &(iLastHdr->iNext);
		iChannel.DoQueue(*this);
		iChannel.Signal();
		}
	else
		{
		// Someone is cancelling all requests...
		req_count = --iChannel.iQueuedRequests;
		iChannel.Signal();
		if (req_count == 0)
			{
			iChannel.QueuedRequestCountChanged();
			}
		}

	__DMA_INVARIANT();
	}

EXPORT_C TInt DDmaRequest::ExpandDesList(TInt aCount)
	{
	__DMA_ASSERTD(!iQueued);
	__DMA_ASSERTD(aCount > 0);

	if (aCount > iChannel.iAvailDesCount)
		return KErrTooBig;

	iChannel.iAvailDesCount -= aCount;
	iDesCount += aCount;

	TDmac& c = *(iChannel.iController);
	c.Wait();

	if (iFirstHdr == NULL)
		{
		// handle empty list specially to simplify following loop
		iFirstHdr = iLastHdr = c.iFreeHdr;
		c.iFreeHdr = c.iFreeHdr->iNext;
		--aCount;
		}
	else
		iLastHdr->iNext = c.iFreeHdr;

	// Remove as many descriptors and headers from free pool as necessary and
	// ensure hardware descriptors are chained together.
	while (aCount-- > 0)
		{
		__DMA_ASSERTD(c.iFreeHdr != NULL);
		if (c.iCaps & TDmac::KCapsBitHwDes)
			c.ChainHwDes(*iLastHdr, *(c.iFreeHdr));
		iLastHdr = c.iFreeHdr;
		c.iFreeHdr = c.iFreeHdr->iNext;
		}

	c.Signal();

	iLastHdr->iNext = NULL;

	__DMA_INVARIANT();
	return KErrNone;
	}




EXPORT_C void DDmaRequest::FreeDesList()
	{
	__DMA_ASSERTD(!iQueued);
	if (iDesCount > 0)
		{
		iChannel.iAvailDesCount += iDesCount;
		TDmac& c = *(iChannel.iController);
		c.Wait();
		iLastHdr->iNext = c.iFreeHdr;
		c.iFreeHdr = iFirstHdr;
		c.Signal();
		iFirstHdr = iLastHdr = NULL;
		iDesCount = 0;
		}
	}


#ifdef _DEBUG

void DDmaRequest::Invariant()
	{
	iChannel.Wait();
	__DMA_ASSERTD(iChannel.IsOpened());
	__DMA_ASSERTD(0 <= iMaxTransferSize);
	__DMA_ASSERTD(0 <= iDesCount && iDesCount <= iChannel.iMaxDesCount);
	if (iDesCount == 0)
		{
		__DMA_ASSERTD(!iQueued);
		__DMA_ASSERTD(!iFirstHdr && !iLastHdr);
		}
	else
		{
		__DMA_ASSERTD(iChannel.iController->IsValidHdr(iFirstHdr));
		__DMA_ASSERTD(iChannel.iController->IsValidHdr(iLastHdr));
		}
	iChannel.Signal();
	}

#endif


//////////////////////////////////////////////////////////////////////////////
// TDmaChannel


EXPORT_C TInt TDmaChannel::StaticExtension(TInt aCmd, TAny* aArg)
	{
	return DmaChannelMgr::StaticExtension(aCmd, aArg);
	}


TDmaChannel::TDmaChannel()
	: iController(NULL),
	  iPslId(0),
	  iCurHdr(NULL),
	  iNullPtr(&iCurHdr),
	  iDfc(Dfc, NULL, 0),
	  iMaxDesCount(0),
	  iAvailDesCount(0),
	  iIsrDfc(0),
	  iReqQ(),
	  iReqCount(0),
	  iQueuedRequests(0),
	  iCancelInfo(NULL)
	{
	__DMA_INVARIANT();
	}


EXPORT_C TInt TDmaChannel::Open(const SCreateInfo& aInfo, TDmaChannel*& aChannel)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("TDmaChannel::Open thread %O", &Kern::CurrentThread()));
	__DMA_ASSERTD(aInfo.iDfcQ != NULL);
	__DMA_ASSERTD(aInfo.iDfcPriority < KNumDfcPriorities);
	__DMA_ASSERTD(aInfo.iDesCount >= 1);

	aChannel = NULL;

	DmaChannelMgr::Wait();
	TDmaChannel* pC = DmaChannelMgr::Open(aInfo.iCookie);
	DmaChannelMgr::Signal();
	if (!pC)
		return KErrInUse;

	TInt r = pC->iController->ReserveSetOfDes(aInfo.iDesCount);
	if (r != KErrNone)
		{
		pC->Close();
		return r;
		}
	pC->iAvailDesCount = pC->iMaxDesCount = aInfo.iDesCount;

	new (&pC->iDfc) TDfc(&Dfc, pC, aInfo.iDfcQ, aInfo.iDfcPriority);

	aChannel = pC;

#ifdef _DEBUG
	pC->Invariant();
#endif
	__KTRACE_OPT(KDMA, Kern::Printf("opened channel %d", pC->iPslId));
	return KErrNone;
	}


EXPORT_C void TDmaChannel::Close()
	{
	__KTRACE_OPT(KDMA, Kern::Printf("TDmaChannel::Close %d", iPslId));
	__DMA_ASSERTD(IsOpened());
	__DMA_ASSERTD(IsQueueEmpty());
	__DMA_ASSERTD(iReqCount == 0);

	__DMA_ASSERTD(iQueuedRequests == 0);

	// descriptor leak? bug in request code
	__DMA_ASSERTD(iAvailDesCount == iMaxDesCount);

	iController->ReleaseSetOfDes(iMaxDesCount);
	iAvailDesCount = iMaxDesCount = 0;

	DmaChannelMgr::Wait();
	DmaChannelMgr::Close(this);
	iController = NULL;
	DmaChannelMgr::Signal();

	__DMA_INVARIANT();
	}


EXPORT_C void TDmaChannel::CancelAll()
	{
	__KTRACE_OPT(KDMA, Kern::Printf("TDmaChannel::CancelAll thread %O channel - %d",
									&Kern::CurrentThread(), iPslId));
	__DMA_ASSERTD(IsOpened());

	NThread* nt = NKern::CurrentThread();
	TBool wait = FALSE;
	TDmaCancelInfo c;
	TDmaCancelInfo* waiters = 0;

	NKern::ThreadEnterCS();
	Wait();
	const TUint32 req_count_before = iQueuedRequests;
	NThreadBase* dfcnt = iDfc.Thread();
	__e32_atomic_store_ord32(&iIsrDfc, (TUint32)KCancelFlagMask);
	// ISRs after this point will not post a DFC, however a DFC may already be queued or running or both
	if (!IsQueueEmpty())
		{
		// There is a transfer in progress.  It may complete before the DMAC
		// has stopped, but the resulting ISR will not post a DFC.
		// ISR should not happen after this function returns.
		iController->StopTransfer(*this);

		ResetStateMachine();

		// Clean-up the request queue.
		SDblQueLink* pL;
		while ((pL = iReqQ.GetFirst()) != NULL)
			{
			iQueuedRequests--;
			DDmaRequest* pR = _LOFF(pL, DDmaRequest, iLink);
			pR->OnDeque();
			}
		}
	if (!dfcnt || dfcnt==nt)
		{
		// no DFC queue or DFC runs in this thread, so just cancel it and we're finished
		iDfc.Cancel();

		// if other calls to CancelAll() are waiting for the DFC, release them here
		waiters = iCancelInfo;
		iCancelInfo = 0;

		// reset the ISR count
		__e32_atomic_store_rel32(&iIsrDfc, 0);
		}
	else
		{
		// DFC runs in another thread. Make sure it's queued and then wait for it to run.
		if (iCancelInfo)
			c.InsertBefore(iCancelInfo);
		else
			iCancelInfo = &c;
		wait = TRUE;
		iDfc.Enque();
		}
	const TUint32 req_count_after = iQueuedRequests;
	Signal();
	if (waiters)
		waiters->Signal();
	if (wait)
		NKern::FSWait(&c.iSem);
 	NKern::ThreadLeaveCS();

	// Only call PSL if there were requests queued when we entered AND there
	// are now no requests left on the queue.
	if ((req_count_before != 0) && (req_count_after == 0))
		{
		QueuedRequestCountChanged();
		}

	__DMA_INVARIANT();
	}


/**
 DFC callback function (static member).
 */

void TDmaChannel::Dfc(TAny* aArg)
	{
	((TDmaChannel*)aArg)->DoDfc();
	}


void TDmaChannel::DoDfc()
	{
	Wait();

	// Atomically fetch and reset the number of DFC queued by ISR and the error
	// flag. Leave the cancel flag alone for now.
	const TUint32 w = __e32_atomic_and_ord32(&iIsrDfc, (TUint32)KCancelFlagMask);
	TUint32 count = w & KDfcCountMask;
	const TBool error = w & (TUint32)KErrorFlagMask;
	TBool stop = w & (TUint32)KCancelFlagMask;
	__DMA_ASSERTD(count>0 || stop);
	const TUint32 req_count_before = iQueuedRequests;
	TUint32 req_count_after = 0;

	while(count && !stop)
		{
		--count;

		// If an error occurred it must have been reported on the last interrupt since transfers are
		// suspended after an error.
		DDmaRequest::TResult res = (count==0 && error) ? DDmaRequest::EError : DDmaRequest::EOk;
		__DMA_ASSERTD(!iReqQ.IsEmpty());
		DDmaRequest* pCompletedReq = NULL;
		DDmaRequest* pCurReq = _LOFF(iReqQ.First(), DDmaRequest, iLink);
		DDmaRequest::TCallback cb = 0;
		TAny* arg = 0;

		if (res == DDmaRequest::EOk)
			{
			// Update state machine, current fragment, completed fragment and
			// tell DMAC to transfer next fragment if necessary.
			SDmaDesHdr* pCompletedHdr = NULL;
			DoDfc(*pCurReq, pCompletedHdr);

			// If just completed last fragment from current request, switch to next
			// request (if any).
			if (pCompletedHdr == pCurReq->iLastHdr)
				{
				pCompletedReq = pCurReq;
				pCurReq->iLink.Deque();
				iQueuedRequests--;
				if (iReqQ.IsEmpty())
					iNullPtr = &iCurHdr;
				pCompletedReq->OnDeque();
				}
			}
		else if (res == DDmaRequest::EError)
			pCompletedReq = pCurReq;
		else
			__DMA_CANT_HAPPEN();
		if (pCompletedReq)
			{
			cb = pCompletedReq->iCb;
			arg = pCompletedReq->iCbArg;
			Signal();
			__KTRACE_OPT(KDMA, Kern::Printf("notifying DMA client result=%d", res));
			(*cb)(res,arg);
			Wait();
			}
		if (pCompletedReq || Flash())
			stop = __e32_atomic_load_acq32(&iIsrDfc) & (TUint32)KCancelFlagMask;
		}

	// Some interrupts may be missed (double-buffer and scatter-gather
	// controllers only) if two or more transfers complete while interrupts are
	// disabled in the CPU. If this happens, the framework will go out of sync
	// and leave some orphaned requests in the queue.
	//
	// To ensure correctness we handle this case here by checking that the request
	// queue is empty when all transfers have completed and, if not, cleaning up
	// and notifying the client of the completion of the orphaned requests.
	//
	// Note that if some interrupts are missed and the controller raises an
	// error while transferring a subsequent fragment, the error will be reported
	// on a fragment which was successfully completed.  There is no easy solution
	// to this problem, but this is okay as the only possible action following a
	// failure is to flush the whole queue.
	if (stop)
		{
		TDmaCancelInfo* waiters = iCancelInfo;
		iCancelInfo = 0;

		// make sure DFC doesn't run again until a new request completes
		iDfc.Cancel();

		// reset the ISR count - new requests can now be processed
		__e32_atomic_store_rel32(&iIsrDfc, 0);

		req_count_after = iQueuedRequests;
		Signal();

		// release threads doing CancelAll()
		waiters->Signal();
		}
	else if (!error && !iDfc.Queued() && !iReqQ.IsEmpty() && iController->IsIdle(*this))
		{
		__KTRACE_OPT(KDMA, Kern::Printf("Missed interrupt(s) - draining request queue"));
		ResetStateMachine();

		// Move orphaned requests to temporary queue so channel queue can
		// accept new requests.
		SDblQue q;
		q.MoveFrom(&iReqQ);

		SDblQueLink* pL;
		while ((pL = q.GetFirst()) != NULL)
			{
			iQueuedRequests--;
			DDmaRequest* pR = _LOFF(pL, DDmaRequest, iLink);
			__KTRACE_OPT(KDMA, Kern::Printf("Removing request from queue and notifying client"));
			pR->OnDeque();
			DDmaRequest::TCallback cb = pR->iCb;
			TAny* arg = pR->iCbArg;
			if (cb)
				{
				Signal();
				(*cb)(DDmaRequest::EOk, arg);
				Wait();
				}
			}
		req_count_after = iQueuedRequests;
		Signal();
		}
	else
		{
		req_count_after = iQueuedRequests;
		Signal();
		}

	// Only call PSL if there were requests queued when we entered AND there
	// are now no requests left on the queue (after also having executed all
	// client callbacks).
	if ((req_count_before != 0) && (req_count_after == 0))
		{
		QueuedRequestCountChanged();
		}

	__DMA_INVARIANT();
	}


/** Reset state machine only, request queue is unchanged */

void TDmaChannel::ResetStateMachine()
	{
	DoCancelAll();
	iCurHdr = NULL;
	iNullPtr = &iCurHdr;
	}


/** Unlink the last item of a LLI chain from the next chain.
	Default implementation does nothing. This is overridden by scatter-gather channels. */

void TDmaChannel::DoUnlink(SDmaDesHdr& /*aHdr*/)
	{
	}


/** PSL may override */
void TDmaChannel::QueuedRequestCountChanged()
	{
#ifdef _DEBUG
	Wait();
	__KTRACE_OPT(KDMA,
				 Kern::Printf("TDmaChannel::QueuedRequestCountChanged() %d",
							  iQueuedRequests));
	__DMA_ASSERTA(iQueuedRequests >= 0);
	Signal();
#endif
	}


#ifdef _DEBUG

void TDmaChannel::Invariant()
	{
	Wait();

	__DMA_ASSERTD(iReqCount >= 0);
	// should always point to NULL pointer ending fragment queue
	__DMA_ASSERTD(*iNullPtr == NULL);

	__DMA_ASSERTD(0 <= iAvailDesCount && iAvailDesCount <= iMaxDesCount);

	__DMA_ASSERTD(iCurHdr == NULL || iController->IsValidHdr(iCurHdr));

	if (IsOpened())
		{
		__DMA_ASSERTD((iCurHdr && !IsQueueEmpty()) || (!iCurHdr && IsQueueEmpty()));
		if (iCurHdr == NULL)
			__DMA_ASSERTD(iNullPtr == &iCurHdr);
		}
	else
		{
		__DMA_ASSERTD(iCurHdr == NULL);
		__DMA_ASSERTD(iNullPtr == &iCurHdr);
		__DMA_ASSERTD(IsQueueEmpty());
		}

	Signal();
	}

#endif

//////////////////////////////////////////////////////////////////////////////
// TDmaSbChannel

void TDmaSbChannel::DoQueue(DDmaRequest& /*aReq*/)
	{
	if (!iTransferring)
		{
		iController->Transfer(*this, *iCurHdr);
		iTransferring = ETrue;
		}
	}


void TDmaSbChannel::DoCancelAll()
	{
	__DMA_ASSERTD(iTransferring);
	iTransferring = EFalse;
	}


void TDmaSgChannel::DoUnlink(SDmaDesHdr& aHdr)
	{
	iController->UnlinkHwDes(*this, aHdr);
	}


void TDmaSbChannel::DoDfc(DDmaRequest& /*aCurReq*/, SDmaDesHdr*& aCompletedHdr)
	{
	__DMA_ASSERTD(iTransferring);
	aCompletedHdr = iCurHdr;
	iCurHdr = iCurHdr->iNext;
	if (iCurHdr != NULL)
		iController->Transfer(*this, *iCurHdr);
	else
		iTransferring = EFalse;
	}


//////////////////////////////////////////////////////////////////////////////
// TDmaDbChannel

void TDmaDbChannel::DoQueue(DDmaRequest& aReq)
	{
	switch (iState)
		{
	case EIdle:
		iController->Transfer(*this, *iCurHdr);
		if (iCurHdr->iNext)
			{
			iController->Transfer(*this, *(iCurHdr->iNext));
			iState = ETransferring;
			}
		else
			iState = ETransferringLast;
		break;
	case ETransferring:
		// nothing to do
		break;
	case ETransferringLast:
		iController->Transfer(*this, *(aReq.iFirstHdr));
		iState = ETransferring;
		break;
	default:
		__DMA_CANT_HAPPEN();
		}
	}


void TDmaDbChannel::DoCancelAll()
	{
	iState = EIdle;
	}


void TDmaDbChannel::DoDfc(DDmaRequest& /*aCurReq*/, SDmaDesHdr*& aCompletedHdr)
	{
	aCompletedHdr = iCurHdr;
	iCurHdr = iCurHdr->iNext;
	switch (iState)
		{
	case ETransferringLast:
		iState = EIdle;
		break;
	case ETransferring:
		if (iCurHdr->iNext == NULL)
			iState = ETransferringLast;
		else
			iController->Transfer(*this, *(iCurHdr->iNext));
		break;
	default:
		__DMA_CANT_HAPPEN();
		}
	}


//////////////////////////////////////////////////////////////////////////////
// TDmaSgChannel

void TDmaSgChannel::DoQueue(DDmaRequest& aReq)
	{
	if (iTransferring)
		{
		__DMA_ASSERTD(!aReq.iLink.Alone());
		DDmaRequest* pReqPrev = _LOFF(aReq.iLink.iPrev, DDmaRequest, iLink);
		iController->AppendHwDes(*this, *(pReqPrev->iLastHdr), *(aReq.iFirstHdr));
		}
	else
		{
		iController->Transfer(*this, *(aReq.iFirstHdr));
		iTransferring = ETrue;
		}
	}


void TDmaSgChannel::DoCancelAll()
	{
	__DMA_ASSERTD(iTransferring);
	iTransferring = EFalse;
	}


void TDmaSgChannel::DoDfc(DDmaRequest& aCurReq, SDmaDesHdr*& aCompletedHdr)
	{
	__DMA_ASSERTD(iTransferring);
	aCompletedHdr = aCurReq.iLastHdr;
	iCurHdr = aCompletedHdr->iNext;
	iTransferring = (iCurHdr != NULL);
	}