// 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 "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:
// Test driver for DMA V2 framework
//
//

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

_LIT(KClientPanicCat, "D_DMA2");
_LIT(KDFCThreadName,"D_DMA_DFC_THREAD");
_LIT(KIsrCbDfcThreadName,"D_DMA_IsrCb_thread");
const TInt KDFCThreadPriority=26;

class TStopwatch
	{
public:
	TStopwatch()
		:iStart(0), iStop(0)
		{}

	void Start()
		{iStart = NKern::FastCounter();}

	void Stop()
		{
		iStop = NKern::FastCounter();

		__KTRACE_OPT(KDMA, Kern::Printf(">TStopwatch::Stop FastCounter ticks: iStart=0x%lx iStop=0x%lx", iStart, iStop));
		}

	TUint64 ReadMicroSecs() const
		{
#ifndef __SMP__
		TUint64 diff = 0;
		if(iStart > iStop)
			{
			diff = (KMaxTUint64 - iStart) + iStop;
			}
		else
			{
			diff = iStop - iStart;
			}
		return FastCountToMicroSecs(diff);
#else
		//TODO On SMP it is possible for the value returned from
		//NKern::FastCounter to depend on the current CPU (ie.
		//NaviEngine)
		//
		//One solution would be to tie DFC's and ISR's to the same
		//core as the client, but this would reduce the usefulness of
		//SMP testing.
		return 0;
#endif
		}
private:

	TUint64 FastCountToMicroSecs(TUint64 aCount) const
		{
		const TUint64 countsPerS = NKern::FastCounterFrequency();

		TUint64 timeuS = (aCount*1000000)/countsPerS;
		__KTRACE_OPT(KDMA, Kern::Printf(">TStopwatch::FastCountToMicroSecs FastCounter ticks: aCount=0x%lx countsPerS=0x%lx time=0x%lx", aCount, countsPerS, timeuS));
		return timeuS;
		}

	TUint64 iStart;
	TUint64 iStop;
	};

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

class DClientDmaRequest;
/**
Driver channel. Only accessible by a single client thread
*/
class DDmaTestSession : public DLogicalChannelBase
	{
public:
	DDmaTestSession();
	virtual ~DDmaTestSession();
protected:
	// from DLogicalChannelBase
	virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion& aVer);
	virtual TInt Request(TInt aFunction, TAny* a1, TAny* a2);
	virtual TInt RequestUserHandle(DThread* aThread, TOwnerType aType);
private:
	TInt DoGetInfo(TAny* aInfo);

	TInt OpenDmaChannel(TUint aPslCookie, TUint& aDriverCookie);
	TInt CloseDmaChannelByCookie(TUint aDriverCookie);
	TInt PauseDmaChannelByCookie(TUint aDriverCookie);
	TInt ResumeDmaChannelByCookie(TUint aDriverCookie);
	TInt GetChannelCapsByCookie(TUint aDriverCookie, SDmacCaps& aChannelCaps);
	TInt GetChannelCapsByCookie(TUint aDriverCookie, TDmacTestCaps& aChannelCaps);
	TInt CancelAllByCookie(TUint aDriverCookie);
	TInt IsrRedoRequestByCookie(TUint aDriverCookie,TUint32 aSrcAddr,TUint32 aDstAddr,TInt aTransferCount,TUint32 aPslRequestInfo,TBool aIsrCb);
	TInt IsQueueEmptyByCookie(TUint aDriverCookie, TBool& aQueueEmpty);		
	TInt ChannelIsOpenedByCookie(TUint aDriverCookie, TBool& aChannelOpen);		
	void CloseDmaChannelByIndex(TInt aIndex);
	void CancelAllByIndex(TInt aIndex);
	TInt PauseDmaChannelByIndex(TInt aIndex);
	TInt ResumeDmaChannelByIndex(TInt aIndex);		
	TInt IsrRedoRequestByIndex(TInt aIndex,TUint32 aSrcAddr,TUint32 aDstAddr,TInt aTransferCount,TUint32 aPslRequestInfo,TBool aIsrCb);
	TInt CreateSharedChunk();
	TUint OpenSharedChunkHandle();

	/**
	Creates a new kernel-side DMA request object, associated with a previously
	opened channel

	@param aChannelCookie - A channel cookie as returned by OpenDmaChannel
	@param aRequestCookie - On success will be a cookie by which the dma request can be referred to
	@param aNewCallback - If true, then a new style DMA callback will be used
	*/
	TInt CreateDmaRequest(TUint aChannelCookie, TUint& aRequestCookie, TBool aNewCallback = EFalse, TInt aMaxFragmentSizeBytes=0);

	//TODO what happens if a client closes a channel that
	//it still has dma requests associated with?
	
	/**
	Destroys a previously created dma request object
	*/
	TInt DestroyDmaRequestByCookie(TUint aRequestCookie);

	void DestroyDmaRequestByIndex(TInt aIndex);


	TInt CookieToChannelIndex(TUint aDriverCookie) const;
	TInt CookieToRequestIndex(TUint aRequestCookie) const;

	void FixupTransferArgs(TDmaTransferArgs& aTransferArgs) const;
	TInt FragmentRequest(TUint aRequestCookie, const TDmaTransferArgs& aTransferArgs, TBool aLegacy=ETrue);

	TInt QueueRequest(TUint aRequestCookie, TRequestStatus* aStatus, TCallbackRecord* aRecord, TUint64* aDurationMicroSecs);
	DClientDmaRequest* RequestFromCookie(TUint aRequestCookie) const;
	TInt RequestFragmentCount(TUint aRequestCookie);

	TDmaV2TestInfo ConvertTestInfo(const TDmaTestInfo& aOldInfo) const;
private:
	DThread* iClient;
	TDynamicDfcQue* iDfcQ;
	TDynamicDfcQue* iIsrCallbackDfcQ; // Will be used by requests which complete with an ISR callback
	static const TInt KMaxChunkSize = 8 * KMega;
	TLinAddr iChunkBase;
	DChunk* iChunk;

	RPointerArray<TDmaChannel> iChannels;
	RPointerArray<DClientDmaRequest> iClientDmaReqs;
	};


/**
Allows a TClientRequest to be associated with a DDmaRequest
*/
class DClientDmaRequest : public DDmaRequest
	{
public:
	static DClientDmaRequest* Construct(DThread* aClient, TDfcQue* const aDfcQ, TDmaChannel& aChannel, TBool aNewStyle=EFalse, TInt aMaxTransferSize=0);
	~DClientDmaRequest();

	TInt Queue(TRequestStatus* aRequestStatus, TCallbackRecord* aRecord, TUint64* aDurationMicroSecs);
	void AddRequeArgs(const TIsrRequeArgsSet& aRequeArgSet);

	TUint64 GetDuration()
		{return iStopwatch.ReadMicroSecs();}

protected:
	TInt Create();
	/** Construct with old style callback */
	DClientDmaRequest(DThread* aClient, TDfcQue* const aDfcQ, TDmaChannel& aChannel, TInt aMaxTransferSize);

	/** Construct with new style callback */
	DClientDmaRequest(DThread* aClient, TDfcQue* const aDfcQ, TDmaChannel& aChannel, TBool aNewStyle, TInt aMaxTransferSize);

private:
	static void CallbackOldStyle(TResult aResult, TAny* aRequest);
	static void Callback(TUint, TDmaResult, TAny*, SDmaDesHdr*);
	static void CompleteCallback(TAny* aRequest);

	void DoCallback(TUint, TDmaResult);
	TBool RedoRequest();

	//!< Used to return a TCallbackRecord and transfer time
	TClientDataRequest2<TCallbackRecord, TUint64>* iClientDataRequest;

	DThread* const iClient;
	TDfcQue* const iDfcQ; //!< Use the DDmaTestSession's dfc queue
	TDfc iDfc;

	TStopwatch iStopwatch;
	TIsrRequeArgsSet iIsrRequeArgSet;
	};

DClientDmaRequest* DClientDmaRequest::Construct(DThread* aClient, TDfcQue* const aDfcQ, TDmaChannel& aChannel, TBool aNewStyle, TInt aMaxTransferSize)
	{
	DClientDmaRequest* dmaRequest = NULL;
	if(aNewStyle)
		{
#ifdef DMA_APIV2
		dmaRequest = new DClientDmaRequest(aClient, aDfcQ, aChannel, aNewStyle, aMaxTransferSize);
#else
		TEST_FAULT; // if a new style dma request was requested it should have been caught earlier
#endif
		}
	else
		{
		dmaRequest = new DClientDmaRequest(aClient, aDfcQ, aChannel, aMaxTransferSize);
		}

	if(dmaRequest == NULL)
		{
		return dmaRequest;
		}

	const TInt r = dmaRequest->Create();
	if(r != KErrNone)
		{
		delete dmaRequest;
		dmaRequest = NULL;
		}
	return dmaRequest;
	}

DClientDmaRequest::DClientDmaRequest(DThread* aClient, TDfcQue* const aDfcQ, TDmaChannel& aChannel, TInt aMaxFragmentSize)
	:DDmaRequest(aChannel, &CallbackOldStyle, this, aMaxFragmentSize),
	iClientDataRequest(NULL),
	iClient(aClient),
	iDfcQ(aDfcQ),
	iDfc(CompleteCallback,NULL, iDfcQ, KMaxDfcPriority)
	{
	}
#ifdef DMA_APIV2
DClientDmaRequest::DClientDmaRequest(DThread* aClient, TDfcQue* const aDfcQ, TDmaChannel& aChannel, TBool /*aNewStyle*/, TInt aMaxFragmentSize)
	:DDmaRequest(aChannel, &Callback, this, aMaxFragmentSize),
	iClientDataRequest(NULL),
	iClient(aClient),
	iDfcQ(aDfcQ),
	iDfc(CompleteCallback,NULL, iDfcQ, KMaxDfcPriority)
	{
	}
#endif

TInt DClientDmaRequest::Create()
	{
	return Kern::CreateClientDataRequest2(iClientDataRequest);
	}

DClientDmaRequest::~DClientDmaRequest()
	{
	__KTRACE_OPT(KDMA, Kern::Printf(">DClientDmaRequest::~DClientDmaRequest")); 
	if(iClientDataRequest)
		{
		Kern::DestroyClientRequest(iClientDataRequest);
		}
	}

/**
Queue the DClientDmaRequest.

@param aRequestStatus Pointer to the client's request status
@param aRecord Pointer to the user's TCallbackRecord, may be null
@return
   -KErrInUse The client request is in use
   -KErrNone success
*/
TInt DClientDmaRequest::Queue(TRequestStatus* aRequestStatus, TCallbackRecord* aRecord, TUint64* aDurationMicroSecs)
	{
	__NK_ASSERT_ALWAYS(aRecord);
	__NK_ASSERT_ALWAYS(aDurationMicroSecs);

	//erase results from last transfer
	iClientDataRequest->Data1().Reset();
	iClientDataRequest->SetDestPtr1(aRecord);

	iClientDataRequest->SetDestPtr2(aDurationMicroSecs);


	TInt r = iClientDataRequest->SetStatus(aRequestStatus);
	if(r != KErrNone)
		{
		return r;
		}

	iStopwatch.Start();
#ifdef DMA_APIV2
	r = DDmaRequest::Queue();
#else
	// old version of queue did not return an error code
	DDmaRequest::Queue();
	r = KErrNone;
#endif

	return r;
	}

void DClientDmaRequest::AddRequeArgs(const TIsrRequeArgsSet& aRequeArgSet)
	{
	iIsrRequeArgSet = aRequeArgSet;
	}

/**
If a transfer complete callback in ISR context s received this will be
called to redo the request with the first entry in the array

@return ETrue If the redo was successful - indicates that another callback is comming
*/
TBool DClientDmaRequest::RedoRequest()
	{
	TIsrRequeArgs args = iIsrRequeArgSet.GetArgs();
	const TInt r = args.Call(iChannel);
	TCallbackRecord& record = iClientDataRequest->Data1();
	record.IsrRedoResult(r);
	return (r == KErrNone);
	}


/**
Calls TDmaChannel::IsrRedoRequest on aChannel
with this object's parameters
*/
TInt TIsrRequeArgs::Call(TDmaChannel& aChannel)
	{
#ifdef DMA_APIV2
	return aChannel.IsrRedoRequest(iSrcAddr, iDstAddr, iTransferCount, iPslRequestInfo, iIsrCb);
#else
	TEST_FAULT;
	return KErrNotSupported;
#endif
	}

/**
Check that both source and destination of ISR reque args will
lie within the range specified by aStart and aSize.

@param aStart The linear base address of the region
@param aSize The size of the region
*/
TBool TIsrRequeArgs::CheckRange(TLinAddr aStart, TUint aSize) const
	{
	TUint physStart = Epoc::LinearToPhysical(aStart);
	TEST_ASSERT(physStart != KPhysAddrInvalid);

	TAddrRange chunk(physStart, aSize);
	TBool sourceOk = (iSrcAddr == KPhysAddrInvalid) ? ETrue : chunk.Contains(SourceRange());

	TBool destOk = (iDstAddr == KPhysAddrInvalid) ? ETrue : chunk.Contains(DestRange());

	return sourceOk && destOk;
	}

TBool TIsrRequeArgsSet::CheckRange(TLinAddr aAddr, TUint aSize) const
	{
	for(TInt i=0; i<iCount; i++)
		{
		if(!iRequeArgs[i].CheckRange(aAddr, aSize))
			return EFalse;
		}
	return ETrue;
	}

/**
Translate an old style dma callback to a new-style one
*/
void DClientDmaRequest::CallbackOldStyle(TResult aResult, TAny* aArg)
	{
	__KTRACE_OPT(KDMA, Kern::Printf(">DClientDmaRequest::CallBackOldStyle: TResult result=%d", aResult));
	TEST_ASSERT(aResult != EBadResult);
	//translate result code
	const TDmaResult result = (aResult == EOk) ? EDmaResultOK : EDmaResultError;

	//call the new-style callback
	Callback(EDmaCallbackRequestCompletion, result, aArg, NULL);
	}


/**
The new style callback called by the DMA framework
may be called in either thread or ISR context
*/
void DClientDmaRequest::Callback(TUint aCallbackType, TDmaResult aResult, TAny* aArg, SDmaDesHdr* aHdr)
	{
	const TInt context = NKern::CurrentContext();
	__KTRACE_OPT(KDMA, Kern::Printf(">DClientDmaRequest::CallBack: TDmaResult result = %d, NKern::TContext context = %d", aResult, context));
	
	DClientDmaRequest& self = *reinterpret_cast<DClientDmaRequest*>(aArg);
	self.DoCallback(aCallbackType, aResult);

	// decide if callback is complete
	const TBool transferComplete = aCallbackType & EDmaCallbackRequestCompletion;
	if(!transferComplete)
		{
		return;
		}

	// If there are reque args then redo this request
	// another callback would then be expected.
	// Requests can only be re-queued in ISR context, but we
	// do not check that here as it is up to the client to get
	// it right - also, we want to test that the PIL catches this
	// error
	if(!self.iIsrRequeArgSet.IsEmpty())
		{
		// If redo call was succesful, return and wait for next call back
		if(self.RedoRequest())
			return;
		}

	switch(context)
		{
	case NKern::EThread:
		{
		CompleteCallback(aArg);
		break;
		}
	case NKern::EInterrupt:
		{
		self.iDfc.iPtr = aArg;
		self.iDfc.Add();
		break;
		}
	case NKern::EIDFC: //fall-through
	case NKern::EEscaped:
	default:
		TEST_FAULT;
		}
	}

/**
Log results of callback. May be called in either thread or ISR context
*/
void DClientDmaRequest::DoCallback(TUint aCallbackType, TDmaResult aResult)
	{
	iStopwatch.Stop(); //sucessive calls will simply over write the stop time

	// This will always be done whether the client requested a
	// callback record or not
	TCallbackRecord& record = iClientDataRequest->Data1();
	record.ProcessCallback(aCallbackType, aResult);
	}

/**
This function may either be called directly or queued as a DFC
*/
void DClientDmaRequest::CompleteCallback(TAny* aArg)
	{
	__KTRACE_OPT(KDMA, Kern::Printf(">DClientDmaRequest::CompleteCallBack thread %O", &Kern::CurrentThread()));
	__ASSERT_NOT_ISR;

	DClientDmaRequest& self = *reinterpret_cast<DClientDmaRequest*>(aArg);

	self.iClientDataRequest->Data2() = self.iStopwatch.ReadMicroSecs();

	//Assert that we called SetRequestStatus on this object before
	//queueing
	__NK_ASSERT_DEBUG(self.iClientDataRequest->IsReady());

	// This is an inelegant, temporary, solution to the following problem:
	//
	// If a dma request completes with an ISR callback the test
	// framework will queue this function as a DFC which
	// will then signal the user-side client. As a consequence of
	// this the user side client may then decide to destroy this
	// request. However, untill the DMA framework's DFC has run
	// and called OnDeque() on this request, it is still considered as
	// queued. Since it is possible that this DFC could run
	// before the DMA fw's DFC, this request could get destroyed while
	// it is stil queued, triggering a PIL assertion.
	//
	// The real fix is likely be for the PIL to call the callback
	// twice, but with different arguments, once to annonunce the
	// ISR and again to announce the dequeue.
	//
	// Here we poll and wait for this request to be dequeued. Note,
	// this DFC is currently run on a separate DFC queue, otherwise
	// it could get deadlocked. An alternative to polling would be
	// to use DCondVar, but that would require PIL modification

	if(NKern::CurrentThread() == self.iDfcQ->iThread)
		{
		// Only need to poll if we aren't on the channel's DFC queue
		for(;;)
			{
			// once the request has been unqueued it
			// can only be queued again by the client
			const TBool queued = __e32_atomic_load_acq32(&self.iQueued);
			if(!queued)
				break;
			__KTRACE_OPT(KDMA, Kern::Printf("Waiting for requeuest to be dequeued"));
			NKern::Sleep(10);
			}
		}
	else
		{
		// If we are on the channel's DFCQ we should be dequeued
		// already
		__NK_ASSERT_DEBUG(!__e32_atomic_load_acq32(&self.iQueued));
		}

	// We can always complete with KErrNone, the actual DMA result is
	// logged in the TCallbackRecord
	Kern::QueueRequestComplete(self.iClient, self.iClientDataRequest, KErrNone);
	}

const TInt DDmaTestSession::KMaxChunkSize;

TInt DDmaTestSession::RequestUserHandle(DThread* aThread, TOwnerType aType)
	{
	if (aType!=EOwnerThread || aThread!=iClient)
		return KErrAccessDenied;
	return KErrNone;
	}

DDmaTestSession::DDmaTestSession()
	: iClient(NULL), iDfcQ(NULL), iIsrCallbackDfcQ(NULL), iChunkBase(0), iChunk(NULL)
	{}

// called in thread critical section
TInt DDmaTestSession::DoCreate(TInt /*aUnit*/, const TDesC8* aInfo, const TVersion& /*aVer*/)
	{
	__NK_ASSERT_ALWAYS(iDfcQ == NULL);
	__NK_ASSERT_ALWAYS(iIsrCallbackDfcQ == NULL);

	TInt r = Kern::DynamicDfcQCreate(iDfcQ, KDFCThreadPriority, KDFCThreadName);
	if (r != KErrNone)
		return r;
	NKern::ThreadSetCpuAffinity((NThread*)(iDfcQ->iThread), KCpuAffinityAny);

	r = Kern::DynamicDfcQCreate(iIsrCallbackDfcQ, KDFCThreadPriority, KIsrCbDfcThreadName);
	if (r != KErrNone)
		return r;
	NKern::ThreadSetCpuAffinity((NThread*)(iIsrCallbackDfcQ->iThread), KCpuAffinityAny);

	iClient = &Kern::CurrentThread();

	r = CreateSharedChunk();
	return r;
	}

DDmaTestSession::~DDmaTestSession()
	{
	//Destroy requests before channels
	//or we will trigger an assertion
	while(iClientDmaReqs.Count())
		{
		DestroyDmaRequestByIndex(0);
		}
	iClientDmaReqs.Close();

	while(iChannels.Count())
		{
		CloseDmaChannelByIndex(0);
		}
	iChannels.Close();


	if (iDfcQ)
		{
		iDfcQ->Destroy();
		}

	if (iIsrCallbackDfcQ)
		{
		iIsrCallbackDfcQ->Destroy();
		}

	if(iChunk)
		{
		Kern::ChunkClose(iChunk);
		iChunk = NULL;
		}
	}

TInt DDmaTestSession::Request(TInt aFunction, TAny* a1, TAny* a2)
	{
	__NK_ASSERT_DEBUG(&Kern::CurrentThread() == iClient);

	switch (aFunction)
		{
	case RDmaSession::EOpenChannel:
			{
			TUint pslCookie = (TUint)a1;
			TUint driverCookie = 0;
			TInt r = OpenDmaChannel(pslCookie, driverCookie);	
			umemput32(a2, &driverCookie, sizeof(TAny*));
			return r;
			}
	case RDmaSession::ECloseChannel:
			{
			TUint driverCookie = reinterpret_cast<TUint>(a1);
			TInt r = CloseDmaChannelByCookie(driverCookie);
			return r;
			}
	case RDmaSession::EChannelCaps:
			{
			TUint driverCookie = reinterpret_cast<TUint>(a1);
			TPckgBuf<TDmacTestCaps> capsBuf;
			TInt r = GetChannelCapsByCookie(driverCookie, capsBuf());
			Kern::KUDesPut(*reinterpret_cast<TDes8*>(a2), capsBuf);
			return r;
			}
	case RDmaSession::EPauseChannel:
			{
			TUint driverCookie = reinterpret_cast<TUint>(a1);
			TInt r = PauseDmaChannelByCookie(driverCookie);
			return r;
			}
	case RDmaSession::EResumeChannel:
			{
			TUint driverCookie = reinterpret_cast<TUint>(a1);
			TInt r = ResumeDmaChannelByCookie(driverCookie);
			return r;
			}
	case RDmaSession::EFragmentCount:
			{
			TUint requestCookie = reinterpret_cast<TUint>(a1);
			TInt r = RequestFragmentCount(requestCookie);
			return r;
			}
	case RDmaSession::ERequestOpen:
			{
			RDmaSession::TRequestCreateArgs createArgs(0, EFalse, 0);
			TPckg<RDmaSession::TRequestCreateArgs> package(createArgs);
			Kern::KUDesGet(package, *reinterpret_cast<TDes8*>(a1));

			const TUint channelCookie = createArgs.iChannelCookie;
			TUint requestCookie = 0;

			TInt r = CreateDmaRequest(channelCookie, requestCookie, createArgs.iNewStyle, createArgs.iMaxFragmentSize);

			umemput32(a2, &requestCookie, sizeof(TAny*));
			return r;
			}
	case RDmaSession::ERequestClose:
			{
			const TUint requestCookie = reinterpret_cast<TUint>(a1);
			return DestroyDmaRequestByCookie(requestCookie);
			}
	case RDmaSession::EFragmentLegacy:
	case RDmaSession::EFragment:
			{
			TPckgBuf<RDmaSession::TFragmentArgs> argsBuff;
			Kern::KUDesGet(argsBuff, *reinterpret_cast<TDes8*>(a1));
			const TUint requestCookie = argsBuff().iRequestCookie;

			//must remove constness as we actually need to
			//convert the src and dst offsets to addresses
			TDmaTransferArgs& transferArgs = const_cast<TDmaTransferArgs&>(argsBuff().iTransferArgs);

			//convert address offsets in to kernel virtual addresses
			FixupTransferArgs(transferArgs);

			TEST_ASSERT((TAddressParms(transferArgs).CheckRange(iChunkBase, iChunk->Size())));

			TInt r = KErrGeneral;

			TStopwatch clock;
			clock.Start();
			switch (aFunction)
				{
			case RDmaSession::EFragmentLegacy:
				r = FragmentRequest(requestCookie, transferArgs, ETrue); break;
			case RDmaSession::EFragment:
				r = FragmentRequest(requestCookie, transferArgs, EFalse); break;
			default:
				TEST_FAULT;
				}
			clock.Stop();

			const TUint64 time = clock.ReadMicroSecs();

			TUint64* const timePtr = argsBuff().iDurationMicroSecs;
			if(timePtr)
				{
				umemput(timePtr, &time, sizeof(time));
				}
			return r;
			}
	case RDmaSession::EQueueRequest:
			{
			TPckgBuf<RDmaSession::TQueueArgs> argsBuff;
			Kern::KUDesGet(argsBuff, *reinterpret_cast<TDes8*>(a1));

			//this is an Asynchronous request
			const TUint requestCookie = argsBuff().iRequestCookie;
			TRequestStatus* requestStatus = argsBuff().iStatus;
			TCallbackRecord* callbackRec = argsBuff().iCallbackRecord;
			TUint64* duration = argsBuff().iDurationMicroSecs;

			TInt r = QueueRequest(requestCookie, requestStatus, callbackRec, duration);
			if(r != KErrNone)
				{
				Kern::RequestComplete(requestStatus, r);
				}
			return r;
			}	
	case RDmaSession::EQueueRequestWithReque:
			{
			//TODO can common code with EQueueRequest be extracted?
			TPckgBuf<RDmaSession::TQueueArgsWithReque> argsBuff;
			Kern::KUDesGet(argsBuff, *reinterpret_cast<TDes8*>(a1));

			//this is an Asynchronous request
			const TUint requestCookie = argsBuff().iRequestCookie;
			TRequestStatus* requestStatus = argsBuff().iStatus;
			TCallbackRecord* callbackRec = argsBuff().iCallbackRecord;
			TUint64* duration = argsBuff().iDurationMicroSecs;

			TInt r = KErrNotFound;

			DClientDmaRequest* const request = RequestFromCookie(requestCookie);
			if(request != NULL)
				{
				argsBuff().iRequeSet.Fixup(iChunkBase);
				//TODO reque args must be substituted in order to
				//check the range. The original transfer args are not
				//available when queue is called, they could
				//however be stored within DClientDmaRequest
				//TEST_ASSERT((argsBuff().iRequeSet.CheckRange(iChunkBase, iChunk->Size())));
				request->AddRequeArgs(argsBuff().iRequeSet);

				r = QueueRequest(requestCookie, requestStatus, callbackRec, duration);
				}

			if(r != KErrNone)
				{
				Kern::RequestComplete(requestStatus, r);
				}
			return r;
			}
	case RDmaSession::EIsrRedoRequest:
			{
			TPckgBuf<RDmaSession::TIsrRedoReqArgs> argsBuff;
			Kern::KUDesGet(argsBuff, *reinterpret_cast<TDes8*>(a1));

			const TUint driverCookie = argsBuff().iDriverCookie;
			const TUint32 srcAddr = argsBuff().iSrcAddr;
			const TUint32 dstAddr = argsBuff().iDstAddr;
			const TInt transferCount = argsBuff().iTransferCount;
			const TUint32 pslRequestInfo = argsBuff().iPslRequestInfo;
			const TBool isrCb = argsBuff().iIsrCb;

			TInt r = IsrRedoRequestByCookie(driverCookie,srcAddr,dstAddr,transferCount,pslRequestInfo,isrCb);
			return r;
			}
	case RDmaSession::EIsOpened:
			{
			TUint driverCookie = (TUint)a1;
			TBool channelOpen = EFalse;;
			TInt r = ChannelIsOpenedByCookie(driverCookie,channelOpen);	
			umemput32(a2, &channelOpen, sizeof(TAny*));
			return r;		
			}
	case RDmaSession::EIsQueueEmpty:
			{
			TUint driverCookie = (TUint)a1;
			TBool queueEmpty = EFalse;;
			TInt r = IsQueueEmptyByCookie(driverCookie,queueEmpty);	
			umemput32(a2, &queueEmpty, sizeof(TAny*));
			return r;
			}
	case RDmaSession::ECancelAllChannel:
			{
			TUint driverCookie = reinterpret_cast<TUint>(a1);
			TInt r = CancelAllByCookie(driverCookie);
			return r;
			}
	case RDmaSession::EOpenSharedChunk:
			{
			return OpenSharedChunkHandle();
			}
	case RDmaSession::EGetTestInfo:
			{
#ifdef DMA_APIV2
			TPckgC<TDmaV2TestInfo> package(DmaTestInfoV2());
#else
			TPckgC<TDmaV2TestInfo> package(ConvertTestInfo(DmaTestInfo()));
#endif
			Kern::KUDesPut(*reinterpret_cast<TDes8*>(a1), package);
			return KErrNone;
			}
	default:
		Kern::PanicCurrentThread(KClientPanicCat, __LINE__);
		return KErrGeneral;
		}
	}

TInt DDmaTestSession::OpenDmaChannel(TUint aPslCookie, TUint& aDriverCookie )
	{
	TDmaChannel::SCreateInfo info;
	info.iCookie = aPslCookie;
	info.iDfcQ = iDfcQ;
	info.iDfcPriority = 3;
	info.iDesCount = 128;

	TDmaChannel* channel = NULL;

	//cs so thread can't be killed between
	//opening channel and adding to array
	NKern::ThreadEnterCS();
	TInt r = TDmaChannel::Open(info, channel);
	if(KErrNone == r)
		{
		__NK_ASSERT_ALWAYS(channel);
		
		__KTRACE_OPT(KDMA, Kern::Printf("OpenDmaChannel: channel@ 0x%08x", channel)); 


		TInt err = iChannels.Append(channel);
		if(KErrNone == err)
			{
			aDriverCookie = reinterpret_cast<TUint>(channel);
			}
		else
			{
			channel->Close();
			r = KErrNoMemory;
			}
		}
	NKern::ThreadLeaveCS();

	return r;
	}

TInt DDmaTestSession::CookieToChannelIndex(TUint aDriverCookie) const
	{
	const TInt r = iChannels.Find(reinterpret_cast<TDmaChannel*>(aDriverCookie));

	if(r < 0)
		{
		__KTRACE_OPT(KDMA, Kern::Printf("CookieToChannelIndex: cookie 0x%08x not found!", aDriverCookie)); 
		}
	return r;
	}

TInt DDmaTestSession::CookieToRequestIndex(TUint aRequestCookie) const
	{
	const TInt r = iClientDmaReqs.Find(reinterpret_cast<DClientDmaRequest*>(aRequestCookie));

	if(r < 0)
		{
		__KTRACE_OPT(KDMA, Kern::Printf("CookieToRequestIndex: cookie 0x%08x not found!", aRequestCookie)); 
		}
	return r;
	}

void DDmaTestSession::CloseDmaChannelByIndex(TInt aIndex)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("CloseDmaChannelByIndex: %d", aIndex)); 
	__NK_ASSERT_DEBUG(aIndex < iChannels.Count()); 
	// cs so client thread can't be killed between removing channel from
	// array and closing it.
	NKern::ThreadEnterCS();
	TDmaChannel* channel = iChannels[aIndex];
	iChannels.Remove(aIndex);
	channel->Close();
	NKern::ThreadLeaveCS();
	}

TInt DDmaTestSession::CloseDmaChannelByCookie(TUint aDriverCookie)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("CloseDmaChannelByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		CloseDmaChannelByIndex(index);
		return KErrNone;
		}
	else
		{
		return KErrNotFound;
		}
	}

TInt DDmaTestSession::CancelAllByCookie(TUint aDriverCookie)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("CancelAllByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		CancelAllByIndex(index);
		return KErrNone;
		}
	else
		{
		return KErrNotFound;
		}
	}

void DDmaTestSession::CancelAllByIndex(TInt aIndex)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("CancelAllByIndex: %d", aIndex)); 
	__NK_ASSERT_DEBUG(aIndex < iChannels.Count()); 
	
	TDmaChannel* channel = iChannels[aIndex];
	iChannels.Remove(aIndex);
	channel->CancelAll();
	}

TInt DDmaTestSession::PauseDmaChannelByIndex(TInt aIndex)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("PauseDmaChannelByIndex: %d", aIndex)); 
	__NK_ASSERT_DEBUG(aIndex < iChannels.Count()); 

#ifdef DMA_APIV2
	TDmaChannel* channel = iChannels[aIndex];
	return channel->Pause();
#else
	return KErrNotSupported;
#endif	
	}

TInt DDmaTestSession::PauseDmaChannelByCookie(TUint aDriverCookie)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("PauseDmaChannelByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		TInt r = PauseDmaChannelByIndex(index);
		return r;
		}
	else
		{
		return KErrNotFound;
		}
	}

TInt DDmaTestSession::ResumeDmaChannelByIndex(TInt aIndex)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("ResumeDmaChannelByIndex: %d", aIndex)); 
	__NK_ASSERT_DEBUG(aIndex < iChannels.Count()); 

#ifdef DMA_APIV2
	TDmaChannel* channel = iChannels[aIndex];
	return channel->Resume();
#else
	return KErrNotSupported;
#endif
	}

TInt DDmaTestSession::ResumeDmaChannelByCookie(TUint aDriverCookie)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("ResumeDmaChannelByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		TInt r = ResumeDmaChannelByIndex(index);
		return r;
		}
	else
		{
		return KErrNotFound;
		}
	}

TInt DDmaTestSession::IsrRedoRequestByCookie(TUint aDriverCookie,TUint32 aSrcAddr,TUint32 aDstAddr,TInt aTransferCount,TUint32 aPslRequestInfo,TBool aIsrCb)
{
	__KTRACE_OPT(KDMA, Kern::Printf("IsrRedoRequestByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		TInt r = IsrRedoRequestByIndex(index,aSrcAddr,aDstAddr,aTransferCount,aPslRequestInfo,aIsrCb);
		return r;
		}
	else
		{
		return KErrNotFound;
		}
}

TInt DDmaTestSession::IsrRedoRequestByIndex(TInt aIndex,TUint32 aSrcAddr,TUint32 aDstAddr,TInt aTransferCount,TUint32 aPslRequestInfo,TBool aIsrCb)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("IsrRedoRequestByIndex: %d", aIndex)); 
	__NK_ASSERT_DEBUG(aIndex < iChannels.Count()); 

#ifdef DMA_APIV2
	TDmaChannel* channel = iChannels[aIndex];
	return channel->IsrRedoRequest(aSrcAddr,aDstAddr,aTransferCount,aPslRequestInfo,aIsrCb);
#else
	return KErrNotSupported;
#endif
	}

/**
aChannelCaps will be set to "NULL" values
*/
TInt DDmaTestSession::GetChannelCapsByCookie(TUint aDriverCookie, TDmacTestCaps& aChannelCaps)
	{
	SDmacCaps caps = {0,}; //initialise with NULL values
	TInt r = GetChannelCapsByCookie(aDriverCookie, caps);

	if(r == KErrNotSupported)
		{
		//If we can not query caps it means
		//that we are using the v1 driver
		//we construct a empty TDmacTestCaps
		//but with an iPILVersion of 1
		const TDmacTestCaps nullCapsV1(caps, 1);
		aChannelCaps = nullCapsV1;
		r = KErrNone;
		}
	else if(r == KErrNone)
		{
		const TDmacTestCaps capsV2(caps, 2);
		aChannelCaps = capsV2;
		}

	return r;
	}

/**
Will return the capabilities of the DMA channel.
Querying SDmacCaps is not possible on V1 of the DMA framework.
In that case an error of KErrNotSupported will be returned
*/
TInt DDmaTestSession::GetChannelCapsByCookie(TUint aDriverCookie, SDmacCaps& aChannelCaps)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("GetChannelCapsByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	if(index >= 0)
		{
#ifdef DMA_APIV2
		aChannelCaps = iChannels[index]->DmacCaps();
		return KErrNone;
#else
		return KErrNotSupported;
#endif
		}
	else
		{
		return KErrNotFound;
		}
	}

TInt DDmaTestSession::IsQueueEmptyByCookie(TUint aDriverCookie, TBool& aQueueEmpty)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("IsQueueEmptyByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		aQueueEmpty=iChannels[index]->IsQueueEmpty();
		return KErrNone;
		}
	else
		{
		return KErrNotFound;
		}
	}

TInt DDmaTestSession::ChannelIsOpenedByCookie(TUint aDriverCookie, TBool& aChannelOpen)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("ChannelIsOpenedByCookie: 0x%08x", aDriverCookie)); 
	const TInt index = CookieToChannelIndex(aDriverCookie);
	
	if(index >= 0)
		{
		aChannelOpen=iChannels[index]->IsOpened();
		return KErrNone;
		}
	else
		{
		return KErrNotFound;
		}
	}

TInt DDmaTestSession::CreateDmaRequest(TUint aChannelCookie, TUint& aRequestCookie, TBool aNewCallback, TInt aMaxFragmentSizeBytes)
	{
#ifndef DMA_APIV2
	if(aNewCallback)
		return KErrNotSupported;
#endif

	TInt channelIndex = CookieToChannelIndex(aChannelCookie);
	if(channelIndex < 0)
		return channelIndex;

	NKern::ThreadEnterCS();
	DClientDmaRequest* request = DClientDmaRequest::Construct(iClient, iIsrCallbackDfcQ, *iChannels[channelIndex], aNewCallback, aMaxFragmentSizeBytes);
	if(request == NULL)
		{
		NKern::ThreadLeaveCS();
		return KErrNoMemory;
		}

	TInt r = iClientDmaReqs.Append(request);
	if(r == KErrNone)
		{
		aRequestCookie = reinterpret_cast<TUint>(request);
		}
	else
		{
		delete request;
		}
	NKern::ThreadLeaveCS();
	
	return r;
	}

TInt DDmaTestSession::DestroyDmaRequestByCookie(TUint aRequestCookie)
	{
	TInt requestIndex = CookieToRequestIndex(aRequestCookie);
	if(requestIndex < 0)
		return requestIndex;

	DestroyDmaRequestByIndex(requestIndex);

	return KErrNone;
	}

void DDmaTestSession::DestroyDmaRequestByIndex(TInt aIndex)
	{
	__KTRACE_OPT(KDMA, Kern::Printf("DestroyDmaRequestByIndex: %d", aIndex)); 
	__NK_ASSERT_DEBUG(aIndex < iClientDmaReqs.Count()); 
	NKern::ThreadEnterCS();

	DClientDmaRequest* request = iClientDmaReqs[aIndex];
	iClientDmaReqs.Remove(aIndex);
	delete request;

	NKern::ThreadLeaveCS();
	}

TInt DDmaTestSession::CreateSharedChunk()
	{
    // Enter critical section so we can't die and leak the objects we are creating
    // I.e. the TChunkCleanup and DChunk (Shared Chunk)
    NKern::ThreadEnterCS();

    // Create the chunk
    TChunkCreateInfo info;
    info.iType         = TChunkCreateInfo::ESharedKernelSingle;
    info.iMaxSize      = KMaxChunkSize;
    info.iMapAttr      = EMapAttrFullyBlocking | EMapAttrUserRw;
    info.iOwnsMemory   = ETrue;
    info.iDestroyedDfc = NULL;

    DChunk* chunk;
	TUint32 mapAttr;
    TInt r = Kern::ChunkCreate(info, chunk, iChunkBase, mapAttr);
    if(r!=KErrNone)
        {
        NKern::ThreadLeaveCS();
        return r;
        }

    // Map our device's memory into the chunk (at offset 0)
	TUint32 physicalAddr;
	r = Kern::ChunkCommitContiguous(chunk,0,KMaxChunkSize, physicalAddr);
    if(r!=KErrNone)
        {
        // Commit failed so tidy-up...
        Kern::ChunkClose(chunk);
        }
    else
        {
        iChunk = chunk;
        }

    // Can leave critical section now that we have saved pointers to created objects
    NKern::ThreadLeaveCS();

    return r;
	}

TUint DDmaTestSession::OpenSharedChunkHandle()
	{
	NKern::ThreadEnterCS();
	const TInt r = Kern::MakeHandleAndOpen(NULL, iChunk);
	NKern::ThreadLeaveCS();
	return r;
	}

void DDmaTestSession::FixupTransferArgs(TDmaTransferArgs& aTransferArgs) const
	{
	aTransferArgs.iSrcConfig.iAddr += iChunkBase;
	aTransferArgs.iDstConfig.iAddr += iChunkBase;
	}

#ifndef DMA_APIV2
static TInt FragmentCount(DDmaRequest* aRequest)
	{
	TInt count = 0;
	for (SDmaDesHdr* pH = aRequest->iFirstHdr; pH != NULL; pH = pH->iNext)
		count++;
	return count;
	}
#endif

TInt DDmaTestSession::RequestFragmentCount(TUint aRequestCookie)
	{
	TInt requestIndex = CookieToRequestIndex(aRequestCookie);
	if(requestIndex < 0)
		return requestIndex;
#ifdef DMA_APIV2
	TInt r = iClientDmaReqs[requestIndex]->FragmentCount();
#else
	TInt r = FragmentCount(iClientDmaReqs[requestIndex]);
#endif

	return r;
	}

TInt DDmaTestSession::FragmentRequest(TUint aRequestCookie, const TDmaTransferArgs& aTransferArgs, TBool aLegacy)
	{
	__KTRACE_OPT(KDMA, Kern::Printf(">FragmentRequest: cookie=0x%08x, legacy=%d", aRequestCookie, aLegacy)); 
	TInt requestIndex = CookieToRequestIndex(aRequestCookie);
	if(requestIndex < 0)
		return requestIndex;

	TInt r = KErrNotSupported;
	if(aLegacy)
		{
		// TODO we can extract the required info from the struct to
		// set flags
		TUint flags = KDmaMemSrc | KDmaIncSrc | KDmaMemDest | KDmaIncDest;

		const TUint src = aTransferArgs.iSrcConfig.iAddr;
		const TUint dst = aTransferArgs.iDstConfig.iAddr;
		r = iClientDmaReqs[requestIndex]->Fragment(src, dst, aTransferArgs.iTransferCount, flags, NULL);
		}
	else
		{
#ifdef DMA_APIV2
		r = iClientDmaReqs[requestIndex]->Fragment(aTransferArgs);
#else
		r = KErrNotSupported;
#endif
		}
	return r;
	}

/**
Queue the request refered to by aRequestCookie

@param aRequestCookie Client identifier for the DDmaRequest
@param aStatus Pointer to the client's TRequestStatus
@param aRecord Pointer to the client's TCallbackRecord
@return
   - KErrNotFound - aRequestCookie was invalid
   - KErrNone - Success
*/
TInt DDmaTestSession::QueueRequest(TUint aRequestCookie, TRequestStatus* aStatus, TCallbackRecord* aRecord, TUint64* aDurationMicroSecs)
	{
	__KTRACE_OPT(KDMA, Kern::Printf(">QueueRequest: 0x%08x", aRequestCookie)); 

	DClientDmaRequest* request = RequestFromCookie(aRequestCookie);
	if(request == NULL)
		return KErrNotFound;

	return request->Queue(aStatus, aRecord, aDurationMicroSecs);
	}

DClientDmaRequest* DDmaTestSession::RequestFromCookie(TUint aRequestCookie) const
	{
	TInt requestIndex = CookieToRequestIndex(aRequestCookie);
	if(requestIndex < 0)
		return NULL;

	return (iClientDmaReqs[requestIndex]);
	}

TDmaV2TestInfo DDmaTestSession::ConvertTestInfo(const TDmaTestInfo& aOldInfo) const
	{
	TDmaV2TestInfo newInfo;
	newInfo.iMaxTransferSize = aOldInfo.iMaxTransferSize;
	newInfo.iMemAlignMask = aOldInfo.iMemAlignMask;
	newInfo.iMemMemPslInfo = aOldInfo.iMemMemPslInfo;

	newInfo.iMaxSbChannels = aOldInfo.iMaxSbChannels;
	for(TInt i=0; i<aOldInfo.iMaxSbChannels; i++)
		newInfo.iSbChannels[i] = aOldInfo.iSbChannels[i];

	newInfo.iMaxDbChannels = aOldInfo.iMaxDbChannels;
	for(TInt i=0; i<aOldInfo.iMaxDbChannels; i++)
		newInfo.iDbChannels[i] = aOldInfo.iDbChannels[i];

	newInfo.iMaxSgChannels = aOldInfo.iMaxSgChannels;
	for(TInt i=0; i<aOldInfo.iMaxSgChannels; i++)
		newInfo.iSgChannels[i] = aOldInfo.iSgChannels[i];

	//TODO will want to add initialisation for Asym channels
	//when these are available

	return newInfo;
	}
//////////////////////////////////////////////////////////////////////////////

class DDmaTestFactory : public DLogicalDevice
	{
public:
	DDmaTestFactory();
	// from DLogicalDevice
	virtual ~DDmaTestFactory()
		{
		__KTRACE_OPT(KDMA, Kern::Printf(">DDmaTestFactory::~DDmaTestFactory"));
		}
	virtual TInt Install();
	virtual void GetCaps(TDes8& aDes) const;
	virtual TInt Create(DLogicalChannelBase*& aChannel);
	};


DDmaTestFactory::DDmaTestFactory()
    {
    iVersion = TestDmaLddVersion();
    iParseMask = KDeviceAllowUnit;							// no info, no PDD
    // iUnitsMask = 0;										// Only one thing
    }


TInt DDmaTestFactory::Create(DLogicalChannelBase*& aChannel)
    {
	aChannel=new DDmaTestSession;
	return aChannel ? KErrNone : KErrNoMemory;
    }


TInt DDmaTestFactory::Install()
    {
    return SetName(&KTestDmaLddName);
    }


void DDmaTestFactory::GetCaps(TDes8& /*aDes*/) const
    {
    }

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

DECLARE_STANDARD_LDD()
	{
    return new DDmaTestFactory;
	}
