kerneltest/e32test/dma/t_dma.cpp
changeset 0 a41df078684a
child 36 538db54a451d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/dma/t_dma.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,980 @@
+// 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:
+// e32test\dma\t_dma.cpp
+// Overview:
+// Test the DMA channel functionality.
+// API Information:
+// RBusLogicalChannel, DLogicalChannelBase, DLogicalDevice
+// Details:	
+// - Load the DMA LDD, create a critical section, an active scheduler and
+// a CPeriodic object.
+// - Test one shot single buffer transfers: test simple transfer, request 
+// reconfiguration and cancelling. Verify results are as expected.
+// - Test one shot double buffer transfers: test simple transfer, request 
+// reconfiguration and cancelling. Verify results are as expected.
+// - Test streaming single buffer transfers: test simple transfer and
+// cancelling. Test that framework behaves correctly if one or more DMA
+// interrupts are missed. Verify results are as expected.
+// - Test streaming double buffer transfers: test simple transfer and
+// cancelling. Test that framework behaves correctly if one or more DMA
+// interrupts are missed. Verify results are as expected.
+// - Test streaming scatter/gather transfers: test simple transfer and
+// cancelling. Test that framework behaves correctly if one or more DMA
+// interrupts are missed. Verify results are as expected.
+// Platforms/Drives/Compatibility:
+// Hardware (Automatic).
+// Assumptions/Requirement/Pre-requisites:
+// Failures and causes:
+// Base Port information:
+// 
+//
+
+#define __E32TEST_EXTENSION__
+#include <e32test.h>
+#include "d_dma.h"
+#include <e32debug.h>
+#include <e32svr.h>
+#include <e32def.h>
+#include <e32def_private.h>
+#include "u32std.h"
+
+#ifdef __DMASIM__
+RTest test(_L("T_DMASIM"));
+#else
+RTest test(_L("T_DMA"));
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+// Mini-framework for running tests either in a single thread or in
+// several concurrent ones.
+
+RTestDma::TInfo Info;
+TBool JitEnabled;
+RCriticalSection TheCriticalSection;						// protect following variables
+TInt ThreadCount;											// decremented when tester thread dies
+CPeriodic* Bipper;											// display dots during tests to detect lock-ups
+
+// Test macro used inside tester threads
+_LIT(KTestFailure, "XTEST");
+static void TestPanic(TInt aLine, TUint32 a1, TUint32 a2, TUint32 a3)
+	{
+	RDebug::Printf("Line %d test failed a1=%08x a2=%08x a3=%08x", aLine, a1, a2, a3);
+	RThread().Panic(KTestFailure, aLine);
+	}
+#define XTEST(e)				if (!(e)) TestPanic(__LINE__, 0, 0, 0)
+#define XTEST1(e,a1)			if (!(e)) TestPanic(__LINE__, (a1), 0, 0)
+#define XTEST2(e,a1,a2)			if (!(e)) TestPanic(__LINE__, (a1), (a2), 0)
+#define XTEST3(e,a1,a2,a3)		if (!(e)) TestPanic(__LINE__, (a1), (a2), (a3))
+
+
+/**
+Specifies a DMA test
+@note Have not inherited from CBase so that implicit copy ctors are used
+*/
+class CTest
+	{
+public:
+	typedef void (*TTestFunction)(RTestDma aChannel, TInt aMaxFragment, TInt aFragmentSize);
+
+	CTest(TTestFunction aFn, TInt aMaxIter)
+		:iTestFn(aFn), iChannelId(0), iMaxIter(aMaxIter)
+		{}
+
+	virtual ~CTest()
+		{}
+
+	TInt RunTest();
+
+	virtual TBool OpenChannel(TInt aDesCount, TInt aMaxFragmentSize=0);
+
+	virtual void AnnounceTest(TDes& aDes)
+		{aDes.AppendFormat(_L("Channel Id %d, iMaxIter %d"), iChannelId, iMaxIter);}
+	virtual void ReportState(TDes& aDes)
+		{aDes.AppendFormat(_L("Channel Id %d, iCurIter %d"), iChannelId, iCurIter);}
+
+
+	void SetChannelId(TUint32 aChannelId)
+		{iChannelId = aChannelId;}
+
+	TInt MaxIter() const {return iMaxIter;}
+	TInt CurIter() const {return iCurIter;}
+
+	/**
+	@return A copy of this test
+	*/	
+	virtual	CTest* Clone() const =0;
+
+protected:
+	TInt virtual DoRunTest() =0;
+
+	const TTestFunction iTestFn;
+	TUint32 iChannelId;
+	const TInt iMaxIter;
+	TInt iCurIter;
+	RTestDma iChannel;
+	};	
+
+/**
+Specifies a DMA test where the maximum fragmentation is
+explicitly limited. This tests that requests are split
+in to the number of fragments expected.
+
+This test also requires that physically contiguous buffers
+are used. For this reason the product of iMaxFragment and
+iMaxFragmentSize should be kept small
+*/
+class CFragmentationTest : public CTest
+	{
+public:
+	CFragmentationTest(TTestFunction aFn, TInt aMaxIter, TInt aMaxFragment, TInt aMaxFragmentSize)
+		: CTest(aFn, aMaxIter), iMaxFragment(aMaxFragment), iMaxFragmentSize(aMaxFragmentSize), iCurFragment(0)
+	{}
+
+	TInt virtual DoRunTest();
+
+	virtual void AnnounceTest(TDes& aDes)
+		{
+		aDes.AppendFormat(_L("CFragmentationTest: Frag count = [1..%d], Max Frag Size = 0x%08x bytes: "), iMaxFragment, iMaxFragmentSize);
+		CTest::AnnounceTest(aDes);
+		}
+
+	virtual void ReportState(TDes& aDes)
+		{
+		aDes.AppendFormat(_L("CFragmentationTest: Current Fragment %d: "), iCurFragment);
+		CTest::ReportState(aDes);
+		}
+
+	CTest* Clone() const
+		{return new CFragmentationTest(*this);}
+
+private:
+	const TInt iMaxFragment;
+	TInt iMaxFragmentSize;
+	TInt iCurFragment;
+	};
+
+/**
+Specifies a DMA test where the maximum fragment size is
+not limited - and we do not care how many fragments are
+used
+
+- This checks that transfers work correctly with the DMAC's
+default fragment size
+*/
+class CDefaultFragTest : public CTest
+	{
+public:
+	CDefaultFragTest(TTestFunction aFn, TInt aMaxIter, TUint aTotalTransferSize)
+		: CTest(aFn, aMaxIter), iTotalTransferSize(aTotalTransferSize)
+		{}
+
+	TInt virtual DoRunTest();
+
+	virtual void AnnounceTest(TDes& aDes)
+		{
+		aDes.AppendFormat(_L("CDefaultFragTest: Transfer = 0x%08x bytes: "), iTotalTransferSize);
+		CTest::AnnounceTest(aDes);
+		}
+
+	CTest* Clone() const
+		{return new CDefaultFragTest(*this);}
+
+	const TInt iTotalTransferSize;
+	};
+
+
+//
+// Active object used to create a tester thread, log on to it and
+// interpret its exit status.
+//
+class CTesterThread : public CActive
+	{
+public:
+	CTesterThread(TInt aIdx, CTest* aTest);
+	~CTesterThread()
+		{
+		delete iTest;
+		}
+private:
+	static TInt ThreadFunction(TAny* aSelf);
+	TInt StartThread();
+	// from CActive
+	virtual void DoCancel();
+	virtual void RunL();
+private:
+	RThread iThread;
+	CTest* iTest;
+	};
+
+
+/**
+Run the test for iMaxIter iterations
+*/
+TInt CTest::RunTest()
+	{
+	TInt r = KErrNone;
+	for (iCurIter=0; iCurIter<iMaxIter; ++iCurIter)
+		{
+		r =  DoRunTest();
+		if(KErrNone != r)
+			break;
+		}
+	return r;
+	}
+
+/**
+Open iChannel
+
+@pre iChannel is not open
+@return
+   - KErrNotSupported Channel does not exist on DMAC
+   - KErrNone Success
+   - KErrInUse
+*/
+TInt CTest::OpenChannel(TInt aDesCount, TInt aMaxFragmentSize)
+		{
+		ASSERT(!iChannel.Handle());
+		const TInt r = iChannel.Open(iChannelId, aDesCount, aMaxFragmentSize);
+		if (r == KErrNotSupported)
+			return r;
+		XTEST1(KErrNone == r || KErrInUse == r, r);
+		
+		if(KErrInUse == r)
+			{
+			// Channel is in use.
+			RDebug::Printf("\nDMA Channel %d is in use",iChannelId);
+			if(0 == iCurIter)
+				{
+				// Terminate thread by returning this error code KErrInUse.
+				return r;
+				}
+			else
+				{
+#ifdef __WINS__
+#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant
+#endif // __WINS__
+				XTEST1(EFalse, iCurIter);
+#ifdef __WINS__
+#pragma warning( default : 4127 ) // warning C4127: conditional expression is constant
+#endif // __WINS__
+				}
+			}
+		return r;
+		}
+
+
+
+// Spawn thread. Will auto-delete when thread exits.
+CTesterThread::CTesterThread(TInt aIdx, CTest* aTest)
+	: CActive(EPriorityStandard), iTest(aTest)
+	{
+	CActiveScheduler::Add(this);
+	TBuf<16> name;
+	name = _L("TESTER-");
+	name.AppendNum(aIdx);
+	test(iThread.Create(name, ThreadFunction, 0x1000, NULL, this) == KErrNone);
+	iThread.SetPriority(EPriorityLess);
+	iThread.Logon(iStatus);
+	SetActive();
+	iThread.Resume();
+	}
+
+
+TInt CTesterThread::ThreadFunction(TAny* aSelf)
+	{
+	CTesterThread* self = (CTesterThread*)aSelf;
+	return self->StartThread();
+	}
+
+TInt CTesterThread::StartThread()
+	{
+	return iTest->RunTest();
+	}
+
+
+
+TInt CFragmentationTest::DoRunTest()
+	{
+	// In case iMaxFragmentSize was larger than suppported (we need to know what fragment
+	// size will actually be used)
+	iMaxFragmentSize = Min(iMaxFragmentSize, Info.iMaxTransferSize);
+
+	// Open channel with enough descriptors for 3 open DMA
+	// requests (see TestStreaming).
+	TInt r = OpenChannel(3* iMaxFragment, iMaxFragmentSize);
+	if(r != KErrNone)
+		return r;
+
+	//we are controlling fragment size, so we know how
+	//many to expect
+	for (iCurFragment=1; iCurFragment<=iMaxFragment; iCurFragment*=2)
+		{
+		const TInt size = iCurFragment * ( iMaxFragmentSize & ~Info.iMemAlignMask);
+		iTestFn(iChannel, iCurFragment, size);
+		}
+	iChannel.Close();
+	return KErrNone;
+	}
+
+TInt CDefaultFragTest::DoRunTest()
+	{
+	// +1 so we don't underestimate maxFragount for inexact division
+	const TUint maxFragCount = (iTotalTransferSize / Info.iMaxTransferSize) +1;
+
+	// Open channel with enough descriptors for 3 open DMA
+	// requests (see TestStreaming).
+	const TUint descriptorCount = 3 * maxFragCount;
+
+	TInt r = OpenChannel(descriptorCount);
+	if(r != KErrNone)
+		return r;
+
+	iTestFn(iChannel, 0, iTotalTransferSize);
+	
+	iChannel.Close();
+	return KErrNone;
+	}
+
+
+// Called when thread completed.
+void CTesterThread::RunL()
+	{
+	TExitType et = iThread.ExitType();
+	TInt er = iThread.ExitReason();
+	TExitCategoryName ec = iThread.ExitCategory();
+	TName name = iThread.Name();
+	CLOSE_AND_WAIT(iThread);
+
+	switch (et)
+		{
+	case EExitKill:
+		// nothing to do
+		break;
+	case EExitPanic:
+			{
+			User::SetJustInTime(JitEnabled);
+			TBuf<128> buffer;
+			iTest->ReportState(buffer);
+			test.Printf(_L("Tester Thread Panic: %S: Test: %S\n"),
+						&name, &buffer);
+			if (ec.Match(KTestFailure) == 0)
+				test.Panic(_L("Test failure line %d"), er);
+			else
+				test.Panic(_L("Unexpected panic: %S-%d"), &ec, er);
+			break;
+			}
+	default:
+		test.Panic(_L("Invalid thread exit type"));
+		}
+
+	TheCriticalSection.Wait();
+	if (--ThreadCount == 0)
+		{
+		Bipper->Cancel();
+		test.Console()->Printf(_L("\n"));
+		CActiveScheduler::Stop();
+		}
+	TheCriticalSection.Signal();
+
+	// We commit suicide as the alternative (being deleted by
+	// RunTest()) implies keeping a list of all instances in
+	// RunTest().
+	delete this;
+	}
+
+
+void CTesterThread::DoCancel()
+	{
+	test.Panic(_L("CTesterThread::DoCancel called"));
+	}
+
+
+static TInt Bip(TAny*)
+	{
+	test.Console()->Printf(_L("."));
+	return 0;
+	}
+
+
+// Execute provided test object in one or more tester threads.
+void RunTest(TUint32 aChannelIds[], TInt aMaxThread, CTest* aTest)			 
+	{
+	test_NotNull(aTest);
+
+	if (aMaxThread == 0)
+		{
+		delete aTest;
+		test.Printf(_L("transfer mode not supported - skipped\n"));
+		return;
+		}
+
+	test.Printf(_L("Using %d thread(s)\n"), aMaxThread);
+
+	// We don't want JIT debugging here because the tester threads may panic
+	JitEnabled = User::JustInTime();
+	User::SetJustInTime(EFalse);
+
+	// must be set before spawning threads to avoid premature active scheduler stop
+	ThreadCount = aMaxThread;
+
+	TBuf<128> buffer;
+	for (TInt i=0; i<aMaxThread; ++i)
+		{
+		//each CTesterThread needs its own CTest object
+		CTest* dmaTest = aTest->Clone();
+		test_NotNull(dmaTest);
+
+		dmaTest->SetChannelId(aChannelIds[i]);
+
+		buffer.Zero();
+		dmaTest->AnnounceTest(buffer);
+		test.Printf(_L("Thread %d: %S\n"), i, &buffer);
+		
+		test(new CTesterThread(i, dmaTest) != NULL);
+		dmaTest = NULL; //ownership transferred to CTesterThread
+		}
+	//the orginal isn't needed
+	delete aTest;
+	aTest = NULL;
+
+	const TTimeIntervalMicroSeconds32 KPeriod = 1000000;	// 1s
+	Bipper->Start(KPeriod, KPeriod, Bip);
+
+	CActiveScheduler::Start();
+
+	User::SetJustInTime(JitEnabled);
+	}
+
+
+inline void RunSbTest(TInt aMaxThread, CTest* aTest)
+	{
+	RunTest(Info.iSbChannels, Min(aMaxThread,Info.iMaxSbChannels), aTest);
+	}
+
+inline void RunDbTest(TInt aMaxThread, CTest* aTest)
+	{
+	RunTest(Info.iDbChannels, Min(aMaxThread,Info.iMaxDbChannels), aTest);
+	}
+
+inline void RunSgTest(TInt aMaxThread, CTest* aTest)
+	{
+	RunTest(Info.iSgChannels, Min(aMaxThread,Info.iMaxSgChannels), aTest);
+	}
+//////////////////////////////////////////////////////////////////////////////
+
+static void GetChannelInfo()
+	{
+	RTestDma channel;
+	test(channel.GetInfo(Info) == KErrNone);
+	test(Info.iMaxSbChannels>0 || Info.iMaxDbChannels>0 || Info.iMaxSgChannels>0);
+	}
+
+
+static void TestOneShot(RTestDma aChannel, TInt aFragmentCount, TInt aSize)
+	{
+	const TInt KRequest = 0;
+	const TInt KSrcBuf = 0;
+	const TInt KDestBuf1 = 1;
+	const TInt KDestBuf2 = 2;
+
+	TInt r = aChannel.AllocBuffer(KSrcBuf, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	aChannel.FillBuffer(KSrcBuf, 'A');
+	r = aChannel.AllocBuffer(KDestBuf1, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	r = aChannel.AllocBuffer(KDestBuf2, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	aChannel.FillBuffer(KDestBuf2, '\0');
+
+	// Test simple transfer
+	TRequestStatus rs;
+	r = aChannel.Fragment(KRequest, KSrcBuf, KDestBuf1, aSize, &rs);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest, aFragmentCount));
+	r = aChannel.Execute(_L8("Q0"));
+	XTEST1(r == KErrNone, r);
+	User::WaitForRequest(rs);
+	XTEST1(rs == KErrNone, rs.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf1, 'A'));
+
+	// Test request reconfiguration.
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	r = aChannel.Fragment(KRequest, KSrcBuf, KDestBuf2, aSize, &rs);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest, aFragmentCount));
+	r = aChannel.Execute(_L8("Q0"));
+	XTEST1(r == KErrNone, r);
+	User::WaitForRequest(rs);
+	XTEST1(rs == KErrNone, rs.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf1, '\0'));			// previous dest unchanged?
+	XTEST(aChannel.CheckBuffer(KDestBuf2, 'A'));
+
+	// Test cancelling
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	r = aChannel.Fragment(KRequest, KSrcBuf, KDestBuf1, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest, aFragmentCount));
+	r = aChannel.Execute(_L8("Q0C"));
+	XTEST1(r == KErrNone, r);
+	// Part of the destination buffer should be unchanged if the
+	// cancel occured before the transfer completed.
+#ifdef __DMASIM__
+	// At least part of the last destination buffer should be
+	// unchanged if cancel occured before the transfer completed.
+	// Assert only on WINS as real DMACs are too fast.
+	XTEST(! aChannel.CheckBuffer(KDestBuf2, 'C'));
+#endif
+
+	// Perform another transfer to ensure cancel operation let the
+	// framework in a consistent state.
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	r = aChannel.Fragment(KRequest, KSrcBuf, KDestBuf1, aSize, &rs);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest, aFragmentCount));
+	r = aChannel.Execute(_L8("Q0"));
+	XTEST1(r == KErrNone, r);
+	User::WaitForRequest(rs);
+	XTEST1(rs == KErrNone, rs.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf1, 'A'));
+
+	//
+	// Test failure if the underlying DMA kernel extension allows it.
+	//
+	// As long as only "CancelAllFragments" is supported, it's okay to
+	// always fail on the first fragment.
+	//
+
+	if (aChannel.FailNext(1) == KErrNone)
+		{
+		aChannel.FillBuffer(KDestBuf1, '\0');
+		r = aChannel.Fragment(KRequest, KSrcBuf, KDestBuf1, aSize, &rs);
+		XTEST2(r == KErrNone, r, aSize);
+		test(aChannel.FragmentCheck(KRequest, aFragmentCount));
+		r = aChannel.Execute(_L8("Q0"));
+		XTEST1(r == KErrNone, r);
+		User::WaitForRequest(rs);
+		XTEST1(rs != KErrNone, rs.Int());
+		XTEST(! aChannel.CheckBuffer(KDestBuf1, 'A'));
+		r = aChannel.Execute(_L8("C"));
+		XTEST1(r == KErrNone, r);
+
+		// Perform another transfer to ensure we are still in a
+		// consistent state.
+		aChannel.FillBuffer(KDestBuf1, '\0');
+		r = aChannel.Fragment(KRequest, KSrcBuf, KDestBuf1, aSize, &rs);
+		XTEST2(r == KErrNone, r, aSize);
+		test(aChannel.FragmentCheck(KRequest, aFragmentCount));
+		r = aChannel.Execute(_L8("Q0"));
+		XTEST1(r == KErrNone, r);
+		User::WaitForRequest(rs);
+		XTEST1(rs == KErrNone, rs.Int());
+		XTEST(aChannel.CheckBuffer(KDestBuf1, 'A'));
+		}
+
+	aChannel.FreeAllBuffers();
+	}
+
+
+static void TestStreaming(RTestDma aChannel, TInt aFragmentCount, TInt aSize)
+	{
+	const TInt KRequest0 = 0;
+	const TInt KRequest1 = 1;
+	const TInt KRequest2 = 2;
+	const TInt KSrcBuf0 = 0;
+	const TInt KSrcBuf1 = 1;
+	const TInt KSrcBuf2 = 2;
+	const TInt KDestBuf0 = 3;
+	const TInt KDestBuf1 = 4;
+	const TInt KDestBuf2 = 5;
+
+	//
+	// Allocate and initialise source buffers
+	//
+
+	TInt r = aChannel.AllocBuffer(KSrcBuf0, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	aChannel.FillBuffer(KSrcBuf0, 'A');
+
+	r = aChannel.AllocBuffer(KSrcBuf1, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	aChannel.FillBuffer(KSrcBuf1, 'B');
+
+	r = aChannel.AllocBuffer(KSrcBuf2, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	aChannel.FillBuffer(KSrcBuf2, 'C');
+
+	//
+	// Allocate destination buffers
+	//
+
+	r = aChannel.AllocBuffer(KDestBuf0, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	r = aChannel.AllocBuffer(KDestBuf1, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	r = aChannel.AllocBuffer(KDestBuf2, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+
+	//
+	// Test simple transfer.
+	// (no need to test for request reconfiguration afterwards because
+	// this was exercised in the one-shot test case)
+	//
+
+	TRequestStatus rs0;
+	r = aChannel.Fragment(KRequest0, KSrcBuf0, KDestBuf0, aSize, &rs0);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest0, aFragmentCount));
+	TRequestStatus rs1;
+ 	r = aChannel.Fragment(KRequest1, KSrcBuf1, KDestBuf1, aSize, &rs1);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest1, aFragmentCount));
+	TRequestStatus rs2;
+	r = aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize, &rs2);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+
+	r = aChannel.Execute(_L8("Q0Q1Q2"));
+	XTEST1(r == KErrNone, r);
+	User::WaitForRequest(rs0);
+	XTEST1(rs0 == KErrNone, rs0.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf0, 'A'));
+	User::WaitForRequest(rs1);
+	XTEST1(rs1 == KErrNone, rs1.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf1, 'B'));
+	User::WaitForRequest(rs2);
+	XTEST1(rs2 == KErrNone, rs2.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf2, 'C'));
+
+	//
+	// Test cancel
+	//
+
+	aChannel.FillBuffer(KDestBuf0, '\0');
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	aChannel.FillBuffer(KDestBuf2, '\0');
+
+	r = aChannel.Fragment(KRequest0, KSrcBuf0, KDestBuf0, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest0, aFragmentCount));
+ 	r = aChannel.Fragment(KRequest1, KSrcBuf1, KDestBuf1, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest1, aFragmentCount));
+	r = aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+
+	r = aChannel.Execute(_L8("Q0Q1Q2C"));
+	XTEST1(r == KErrNone, r);
+#ifdef __DMASIM__
+	// At least part of the last destination buffer should be
+	// unchanged if cancel occured before the transfer completed.
+	// Assert only on WINS as real DMACs are too fast.
+	XTEST(! aChannel.CheckBuffer(KDestBuf2, 'C'));
+#endif
+
+	//
+	// Perform another transfer to ensure cancel operation let the
+	// framework in a consistent state.
+	//
+
+	aChannel.FillBuffer(KDestBuf0, '\0');
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	aChannel.FillBuffer(KDestBuf2, '\0');
+	// Reconfigure last request to enable transfer completion notification
+	r = aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize, &rs2);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+	r = aChannel.Execute(_L8("Q0Q1Q2"));
+	XTEST1(r == KErrNone, r);
+	User::WaitForRequest(rs2);
+	XTEST1(rs2 == KErrNone, rs2.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf0, 'A'));
+	XTEST(aChannel.CheckBuffer(KDestBuf1, 'B'));
+	XTEST(aChannel.CheckBuffer(KDestBuf2, 'C'));
+
+	//
+	// Test for proper implementation of UnlinkHwDes() in the PSL.
+	//
+
+	aChannel.FillBuffer(KDestBuf0, '\0');
+	aChannel.FillBuffer(KDestBuf1, '\0');
+	aChannel.FillBuffer(KDestBuf2, '\0');
+	// Reconfigure last request to enable transfer completion notification
+	r = aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize, &rs2);
+	XTEST2(r == KErrNone, r, aSize);
+	test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+	// Queue first request (Q0)
+	r = aChannel.Execute(_L8("Q0"));
+	// Wait a second, so next request will be queued on its own
+	// (instead of being appended to the previous one)
+	User::After(1000000);
+	// Queue third request (Q2)
+	r = aChannel.Execute(_L8("Q2"));
+	XTEST1(r == KErrNone, r);
+	User::WaitForRequest(rs2);
+	XTEST1(rs2 == KErrNone, rs2.Int());
+	XTEST(aChannel.CheckBuffer(KDestBuf0, 'A'));
+	// KDestBuf1 should have been left untouched!
+	// If we find all B's in KDestBuf1, that means the last descriptor of the
+	// first request (Q0) wasn't properly unlinked and still points to the Q1
+	// descriptor chain from the previous run.
+	XTEST(aChannel.CheckBuffer(KDestBuf1, '\0'));
+	XTEST(aChannel.CheckBuffer(KDestBuf2, 'C'));
+
+	//
+	// Test failure if the underlying DMA kernel extension allows it.
+	//
+	// As long as only "CancelAllFragments" is supported, it's okay to
+	// always fail on the first fragment.
+	//
+
+	if (aChannel.FailNext(1) == KErrNone)
+		{
+		aChannel.FillBuffer(KDestBuf0, '\0');
+		aChannel.FillBuffer(KDestBuf1, '\0');
+		aChannel.FillBuffer(KDestBuf2, '\0');
+		XTEST(aChannel.Fragment(KRequest0, KSrcBuf0, KDestBuf0, aSize, &rs0) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest0, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest1, KSrcBuf1, KDestBuf1, aSize) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest1, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+		XTEST(aChannel.Execute(_L8("Q0Q1Q2")) == KErrNone);
+		User::WaitForRequest(rs0);
+		XTEST(rs0 != KErrNone);
+		XTEST(! aChannel.CheckBuffer(KDestBuf0, 'A'));
+		XTEST(! aChannel.CheckBuffer(KDestBuf1, 'B'));
+		XTEST(! aChannel.CheckBuffer(KDestBuf2, 'C'));
+		XTEST(aChannel.Execute(_L8("C")) == KErrNone);
+
+		// Transfer again to ensure cancel cleaned-up correctly
+		aChannel.FillBuffer(KDestBuf0, '\0');
+		aChannel.FillBuffer(KDestBuf1, '\0');
+		aChannel.FillBuffer(KDestBuf2, '\0');
+		XTEST(aChannel.Fragment(KRequest0, KSrcBuf0, KDestBuf0, aSize, &rs0) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest0, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest1, KSrcBuf1, KDestBuf1, aSize, &rs1) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest1, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize, &rs2) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+		XTEST(aChannel.Execute(_L8("Q0Q1Q2")) == KErrNone);
+		User::WaitForRequest(rs0);
+		XTEST(rs0 == KErrNone);
+		User::WaitForRequest(rs1);
+		XTEST(rs1 == KErrNone);
+		User::WaitForRequest(rs2);
+		XTEST(rs2 == KErrNone);
+		XTEST(aChannel.CheckBuffer(KDestBuf0, 'A'));
+		XTEST(aChannel.CheckBuffer(KDestBuf1, 'B'));
+		XTEST(aChannel.CheckBuffer(KDestBuf2, 'C'));
+		}
+
+	//
+	// Test that framework behaves correctly if one or more DMA interrupts are
+	// missed.
+	//
+
+	if (aChannel.MissNextInterrupts(1) == KErrNone)
+		{
+		aChannel.FillBuffer(KDestBuf0, '\0');
+		aChannel.FillBuffer(KDestBuf1, '\0');
+		aChannel.FillBuffer(KDestBuf2, '\0');
+		XTEST(aChannel.Fragment(KRequest0, KSrcBuf0, KDestBuf0, aSize, &rs0) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest0, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest1, KSrcBuf1, KDestBuf1, aSize, &rs1) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest1, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize, &rs2) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+		XTEST(aChannel.Execute(_L8("Q0Q1Q2")) == KErrNone);
+		User::WaitForRequest(rs0);
+		XTEST(rs0 == KErrNone);
+		User::WaitForRequest(rs1);
+		XTEST(rs1 == KErrNone);
+		User::WaitForRequest(rs2);
+		XTEST(rs2 == KErrNone);
+		XTEST(aChannel.CheckBuffer(KDestBuf0, 'A'));
+		XTEST(aChannel.CheckBuffer(KDestBuf1, 'B'));
+		XTEST(aChannel.CheckBuffer(KDestBuf2, 'C'));
+		}
+
+	if (aChannel.MissNextInterrupts(2) == KErrNone)
+		{
+		aChannel.FillBuffer(KDestBuf0, '\0');
+		aChannel.FillBuffer(KDestBuf1, '\0');
+		aChannel.FillBuffer(KDestBuf2, '\0');
+		XTEST(aChannel.Fragment(KRequest0, KSrcBuf0, KDestBuf0, aSize, &rs0) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest0, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest1, KSrcBuf1, KDestBuf1, aSize, &rs1) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest1, aFragmentCount));
+		XTEST(aChannel.Fragment(KRequest2, KSrcBuf2, KDestBuf2, aSize, &rs2) == KErrNone);
+		test(aChannel.FragmentCheck(KRequest2, aFragmentCount));
+		XTEST(aChannel.Execute(_L8("Q0Q1Q2")) == KErrNone);
+		User::WaitForRequest(rs0);
+		XTEST(rs0 == KErrNone);
+		User::WaitForRequest(rs1);
+		XTEST(rs1 == KErrNone);
+		User::WaitForRequest(rs2);
+		XTEST(rs2 == KErrNone);
+		XTEST(aChannel.CheckBuffer(KDestBuf0, 'A'));
+		XTEST(aChannel.CheckBuffer(KDestBuf1, 'B'));
+		XTEST(aChannel.CheckBuffer(KDestBuf2, 'C'));
+		}
+
+	aChannel.FreeAllBuffers();
+	}
+
+
+static TBool ParseCmdLine(TBool& aCrashDbg, TInt& aMaxfrag, TInt& aMaxIter, TInt& aMaxchannel, TInt& aMaxFragSize)
+//
+// The command line. Syntax is:
+//
+//     t_dma [enableCrashDebugger [aMaxFrag [aMaxIter [aMaxchannel [aMaxFragSize]]]]]
+//
+	{
+	TBuf<256> cmdline;
+	User::CommandLine(cmdline);
+	TLex lex(cmdline);
+
+	lex.SkipSpace();
+
+	if (lex.Eos())
+		return ETrue;
+	if (lex.Val(aCrashDbg) != KErrNone)
+		return EFalse;
+	lex.SkipSpace();
+	if (lex.Eos())
+		return ETrue;
+	if (lex.Val(aMaxfrag) != KErrNone)
+		return EFalse;
+	lex.SkipSpace();
+	if (lex.Eos())
+		return ETrue;
+	if (lex.Val(aMaxIter) != KErrNone)
+		return EFalse;
+	lex.SkipSpace();
+	if (lex.Eos())
+		return ETrue;
+	if (lex.Val(aMaxchannel) != KErrNone)
+		return EFalse;
+	lex.SkipSpace();
+	if (lex.Eos())
+		return ETrue;
+
+	return lex.Val(aMaxFragSize) == KErrNone;
+	}
+
+
+TInt E32Main()
+	{
+	test.Title();
+
+	test.Start(_L("Parsing command-line"));
+	// Default values when run with empty command-line
+	TInt maxfrag = 16; // 5 fragments needed to exercise fully double-buffering state machine
+	TInt maxIter = 3;
+	TInt maxchannel = KMaxTInt;
+	TBool crashDbg = EFalse;
+	TInt maxFragSize = 0x4000; //16k
+
+	(void) ParseCmdLine(crashDbg, maxfrag, maxIter, maxchannel, maxFragSize);
+
+	if (crashDbg)
+		{
+		User::SetCritical(User::ESystemCritical);
+		User::SetProcessCritical(User::ESystemCritical);
+		}
+
+	TInt r;
+#if defined(__DMASIM__) && defined(__WINS__)
+	test.Next(_L("Loading DMA simulator"));
+	r = User::LoadLogicalDevice(_L("DMASIM.DLL"));
+	test(r == KErrNone || r == KErrAlreadyExists);
+#endif
+
+	test.Next(_L("Loading test LDD"));
+#ifdef __DMASIM__
+	r = User::LoadLogicalDevice(_L("D_DMASIM"));
+#else
+	r = User::LoadLogicalDevice(_L("D_DMA"));
+	if (r == KErrNotFound)
+		{
+		test.Printf(_L("DMA not supported - test skipped\n"));
+		return 0;
+		}
+#endif
+	test(r == KErrNone || r == KErrAlreadyExists);
+
+	// Turn off evil lazy dll unloading
+	RLoader l;
+	test(l.Connect()==KErrNone);
+	test(l.CancelLazyDllUnload()==KErrNone);
+	l.Close();
+
+	__UHEAP_MARK;
+	__KHEAP_MARK;
+
+	test.Next(_L("Creating critical section"));
+	test(TheCriticalSection.CreateLocal() == KErrNone);
+
+	test.Next(_L("Creating active scheduler"));
+	CActiveScheduler* pS = new CActiveScheduler;
+	test(pS != NULL);
+	CActiveScheduler::Install(pS);
+
+	test.Next(_L("Creating bipper"));
+	Bipper = CPeriodic::New(CActive::EPriorityStandard);
+	test(Bipper != NULL);
+
+	test.Next(_L("Getting channel info"));
+	GetChannelInfo();
+
+	// Size for the single transfer test
+	TInt totalTransferSize = 64 * KKilo;
+
+	test.Next(_L("Testing one shot single buffer transfer"));
+	RunSbTest(maxchannel, new CFragmentationTest(TestOneShot, maxIter, maxfrag, maxFragSize));
+	RunSbTest(maxchannel, new CDefaultFragTest(TestOneShot, maxIter, totalTransferSize));
+
+	test.Next(_L("Testing one shot double buffer transfer"));
+	RunDbTest(maxchannel, new CFragmentationTest(TestOneShot, maxIter, maxfrag, maxFragSize));
+	RunDbTest(maxchannel, new CDefaultFragTest(TestOneShot, maxIter, totalTransferSize));
+
+	test.Next(_L("Testing one shot scatter/gather transfer"));
+	RunSgTest(maxchannel, new CFragmentationTest(TestOneShot, maxIter, maxfrag, maxFragSize));
+	RunSgTest(maxchannel, new CDefaultFragTest(TestOneShot, maxIter, totalTransferSize));
+
+	test.Next(_L("Testing streaming single buffer transfer"));
+	RunSbTest(maxchannel, new CFragmentationTest(TestStreaming, maxIter, maxfrag, maxFragSize));
+	RunSbTest(maxchannel, new CDefaultFragTest(TestStreaming, maxIter, totalTransferSize));
+
+	test.Next(_L("Testing streaming double buffer transfer"));
+	RunDbTest(maxchannel, new CFragmentationTest(TestStreaming, maxIter, maxfrag, maxFragSize));
+	RunDbTest(maxchannel, new CDefaultFragTest(TestStreaming, maxIter, totalTransferSize));
+
+	test.Next(_L("Testing streaming scatter/gather transfer"));
+	RunSgTest(maxchannel, new CFragmentationTest(TestStreaming, maxIter, maxfrag, maxFragSize));
+	RunSgTest(maxchannel, new CDefaultFragTest(TestStreaming, maxIter, totalTransferSize));
+
+	delete pS;
+	delete Bipper;
+	TheCriticalSection.Close();
+
+	UserSvr::HalFunction(EHalGroupKernel, EKernelHalSupervisorBarrier, (TAny*)5000, 0);
+	__KHEAP_MARKEND;
+	__UHEAP_MARKEND;
+
+	test.End();
+	return 0;
+	}