// Copyright (c) 2002-2010 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();
}
#ifndef DISABLE_MISSED_IRQ_RECOVERY
// (iController may be NULL here if the channel was closed in the client callback.)
else if (!error &&
iController && iController->IsIdle(*this) &&
!iReqQ.IsEmpty() &&
!iDfc.Queued())
{
// Wait for a bit. If during that time the condition goes away then it
// was a 'spurious missed interrupt', in which case we just do nothing.
TBool spurious = EFalse;
const TUint32 nano_secs_per_loop = 1000 * 1000; // 1ms
for (TInt i = 5; i > 0; i--)
{
if (!iController->IsIdle(*this))
{
__KTRACE_OPT(KDMA, Kern::Printf("DMAC no longer idle (i = %d)", i));
spurious = ETrue;
break;
}
else if (iDfc.Queued())
{
__KTRACE_OPT(KDMA, Kern::Printf("DFC now queued (i = %d)", i));
spurious = ETrue;
break;
}
Kern::NanoWait(nano_secs_per_loop);
}
if (!spurious)
{
__KTRACE_OPT(KDMA,
Kern::Printf("Missed interrupt(s) - draining request queue on ch %d",
PslId()));
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();
}
#endif // #ifndef DISABLE_MISSED_IRQ_RECOVERY
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);
}