kerneltest/e32test/demandpaging/t_datapaging.cpp
changeset 0 a41df078684a
child 33 0173bcd7697c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/demandpaging/t_datapaging.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,1242 @@
+// Copyright (c) 2008-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\demandpaging\t_datapaging.cpp
+// Functional tests for data paging.
+// 002 Test UserHeap::ChunkHeap data paging attributes
+// 003 Test RThread::Create data paging attributes
+// 
+//
+
+//! @SYMTestCaseID			KBASE-T_DATAPAGING
+//! @SYMTestType			UT
+//! @SYMPREQ				PREQ1954
+//! @SYMTestCaseDesc		Data Paging functional tests.
+//! @SYMTestActions			001 Test RChunk data paging attributes
+//! @SYMTestExpectedResults All tests should pass.
+//! @SYMTestPriority        High
+//! @SYMTestStatus          Implemented
+
+#define __E32TEST_EXTENSION__
+#include <e32test.h>
+#include <dptest.h>
+#include <e32hal.h>
+#include <u32exec.h>
+#include <e32svr.h>
+#include <e32panic.h>
+#include "u32std.h"
+#include <e32msgqueue.h>
+#include <e32atomics.h>
+#include <e32math.h>
+
+#include "t_dpcmn.h"
+#include "../mmu/mmudetect.h"
+#include "../mmu/d_memorytest.h"
+#include "../mmu/paging_info.h"
+
+RTest test(_L("T_DATAPAGING"));
+
+_LIT(KChunkName, "t_datapaging chunk");
+
+class TRandom
+	{
+public:
+	TRandom();
+	TUint32 Next();
+
+private:
+	enum
+		{
+		KA = 1664525,
+		KB = 1013904223
+		};
+	TUint32 iV;
+	};
+
+TRandom::TRandom()
+	{
+	iV = RThread().Id() + User::NTickCount() + 23;
+	}
+
+TUint32 TRandom::Next()
+	{
+	iV = KA * iV + KB;
+	return iV;
+	}
+
+void CreatePagedChunk(TInt aSizeInPages, TInt aWipeByte = -1)
+	{
+	test_Equal(0,gChunk.Handle());
+	
+	TChunkCreateInfo createInfo;
+	TInt size = aSizeInPages * gPageSize;
+	createInfo.SetNormal(size, size);
+	createInfo.SetPaging(TChunkCreateInfo::EPaged);
+	createInfo.SetOwner(EOwnerProcess);
+	createInfo.SetGlobal(KChunkName);
+	if (aWipeByte != -1)
+		createInfo.SetClearByte(aWipeByte);
+	test_KErrNone(gChunk.Create(createInfo));
+	test(gChunk.IsPaged()); // this is only ever called if data paging is supported
+	}
+
+// The contents of a page is represented as type from enum below ORed with a byte value
+enum TPageContent
+	{
+	ETypeUniform    = 0 << 8,
+	ETypeIncreasing = 1 << 8,
+
+	EContentValueMask = 255,
+	EContentTypeMask  = 255 << 8
+	};
+
+// Write to a page to page it in and verify its previous contents
+void WritePage(TInt aIndex, TUint aExpectedContents, TUint aNewContents)
+	{
+	test.Printf(_L("  %3d Write %x\n"), aIndex, aNewContents);
+	
+	TUint oldType = aExpectedContents & EContentTypeMask;
+	TUint oldValue = aExpectedContents & EContentValueMask;
+	
+	TUint type = aNewContents & EContentTypeMask;
+	TUint value = aNewContents & EContentValueMask;
+	
+	TUint8* page = gChunk.Base() + (gPageSize * aIndex);
+
+	// write first byte first so page is paged in or rejuvenated with write permissions
+	page[0] = 0;
+	
+	for (TInt i = 0 ; i < gPageSize ; ++i)
+		{
+		if (i != 0)
+			test_Equal(oldValue, page[i]);
+		if (oldType == ETypeIncreasing)
+			oldValue = (oldValue + 1) & 255;
+		
+		page[i] = value;
+		if (type == ETypeIncreasing)
+			value = (value + 1) & 255;
+		}
+	}
+
+// Read a page and verify its contents
+void ReadPage(TInt aIndex, TUint aExpectedContents)
+	{
+	test.Printf(_L("  %3d Read  %x\n"), aIndex, aExpectedContents);
+	TUint type = aExpectedContents & EContentTypeMask;
+	TUint value = aExpectedContents & EContentValueMask;
+	TUint8* page = gChunk.Base() + (gPageSize * aIndex);
+	for (TInt i = 0 ; i < gPageSize ; ++i)
+		{
+		test_Equal(value, page[i]);
+		if (type == ETypeIncreasing)
+			value = (value + 1) & 255;
+		}
+	}
+
+void PageOut()
+	{
+	test.Printf(_L("      PageOut\n"));
+	DPTest::FlushCache();
+	}
+
+void TestOnePage()
+	{
+	CreatePagedChunk(1, 0xed);
+
+	// Test initial contents (read)
+	ReadPage(0, ETypeUniform | 0xed);
+
+	// Test read initial contents after flush (may or may not actually been paged out)
+	PageOut();
+	ReadPage(0, ETypeUniform | 0xed);
+
+	// Test page out / page in (read) of dirty contents
+	WritePage(0, ETypeUniform | 0xed, ETypeIncreasing | 0x1a);
+	PageOut();
+	ReadPage(0, ETypeIncreasing | 0x1a);
+
+	// Test page out / page in (read) of clean contents
+	PageOut();
+	ReadPage(0, ETypeIncreasing | 0x1a);
+ 
+	// Test page out / page in (write) of dirty contents
+	WritePage(0, ETypeIncreasing | 0x1a, ETypeIncreasing | 0x23);
+	PageOut();
+	WritePage(0, ETypeIncreasing | 0x23, ETypeIncreasing | 0x45);
+
+	CLOSE_AND_WAIT(gChunk);
+	CreatePagedChunk(1, 0x0d);
+
+	// Test initial contents (write)
+	WritePage(0, ETypeUniform | 0x0d, ETypeIncreasing | 0x1a);
+
+	// Test page out / page in (read) of dirty contents
+	PageOut();
+	ReadPage(0, ETypeIncreasing | 0x1a);
+	
+	CLOSE_AND_WAIT(gChunk);
+	}
+
+TInt PageInThreadFunc(TAny* aArg)
+	{
+	TUint8* page = (TUint8*)aArg;
+	for (;;)
+		{
+		DPTest::FlushCache();
+		RDebug::Printf("Start page in...");
+		volatile TInt i = page[0];
+		(void)i;
+		RDebug::Printf("  done.");
+		}
+	}
+
+TInt PageOutThreadFunc(TAny* aArg)
+	{
+	TUint8* page = (TUint8*)aArg;
+	for (;;)
+		{
+		page[0] = 1;  // make page dirty
+		RDebug::Printf("Start page out...");
+		DPTest::FlushCache();
+		RDebug::Printf("  done.");
+		}
+	}
+
+void TestKillThread(TThreadFunction aFunc, TInt aIterations)
+	{
+	__KHEAP_MARK;
+	TRandom random;
+	CreatePagedChunk(1);
+	TUint8* page = gChunk.Base();
+	page[0] = 0;  // make page dirty
+	DPTest::FlushCache();
+	for (TInt i = 0 ; i < aIterations ; ++i)
+		{
+		RThread thread;
+		test_KErrNone(thread.Create(KNullDesC, aFunc, gPageSize, NULL, page));
+		TRequestStatus status;
+		thread.Logon(status);
+		thread.Resume();
+		User::AfterHighRes((random.Next() % 50 + 1) * 1000);
+		thread.Kill(123);
+		User::WaitForRequest(status);
+		test_Equal(123, status.Int());
+		CLOSE_AND_WAIT(thread);
+		}
+	CLOSE_AND_WAIT(gChunk);
+	User::After(1000000);
+	__KHEAP_MARKEND;
+	}
+
+struct SSoakTestArgs
+	{
+	TInt iThreadIndex;
+	TInt iPages;
+	};
+
+TUint32* PageBasePtr(TInt aPage)
+	{
+	return (TUint32*)(gChunk.Base() + (gPageSize * aPage));
+	}
+
+TUint32* PageDataPtr(TInt aPage, TInt aThreadIndex)
+	{
+	return (TUint32*)((TUint8*)PageBasePtr(aPage) + ((aThreadIndex * 2 + 1) * sizeof(TUint32)));
+	}
+
+TUint32 PageTag(TInt aPage)
+	{
+	return 0x80000000 | aPage;
+	}	
+
+void StopSoakTest(RMsgQueue<TInt> aMsgQueue)
+	{
+	while(aMsgQueue.Send(0) != KErrOverflow)
+		;
+	}
+
+TBool ContinueSoakTest(RMsgQueue<TInt> aMsgQueue)
+	{
+	TInt msg;
+	return aMsgQueue.Receive(msg) == KErrUnderflow;
+	}
+
+_LIT(KMsgQueueName, "t_datapaging_queue");
+
+TInt PinPagesFunc(TAny* aArg)
+	{
+	SSoakTestArgs* args = (SSoakTestArgs*)aArg;
+
+	RMemoryTestLdd ldd;
+	TInt r = ldd.Open();
+	if (r != KErrNone)
+		return r;
+	r = ldd.CreateVirtualPinObject();
+	if (r != KErrNone)
+		return r;
+
+	RMsgQueue<TInt> msgQueue;
+	r = msgQueue.OpenGlobal(KMsgQueueName, EOwnerThread);
+	if (r != KErrNone)
+		return r;
+
+	TInt i = 0;
+	TRandom random;
+	while (ContinueSoakTest(msgQueue))
+		{
+		TInt count = 1 + random.Next() % (args->iPages / 4);
+		TInt start = random.Next() % (args->iPages - count);
+		TInt sleepInMs = 1 + random.Next() % 20;
+		TUint32* ptr = PageBasePtr(start);
+
+		r = ldd.PinVirtualMemory((TLinAddr)ptr, count * gPageSize);
+		if (r != KErrNone)
+			return r;
+
+		User::AfterHighRes(sleepInMs * 1000);
+
+		r = ldd.UnpinVirtualMemory();
+		if (r != KErrNone)
+			return r;
+	
+		++i;
+		}
+
+	msgQueue.Close();
+
+	r = ldd.DestroyVirtualPinObject();
+	if (r != KErrNone)
+		return r;
+	ldd.Close();
+					
+	RDebug::Printf("  thread %d performed %d iterations (pinning)", args->iThreadIndex, i);
+	return KErrNone;
+	}
+
+TBool TestReadWord(TUint32* aPtr, TUint32 aExpected, TInt aThread, TInt aPage, TInt aIteration, TInt aLine, RMsgQueue<TInt> aMsgQueue)
+	{
+	TUint32 aActual = *aPtr;
+	if (aActual != aExpected)
+		{
+		StopSoakTest(aMsgQueue);
+		RDebug::Printf("  thread %d failure reading page %d at iteration %d address %08x: expected %08x but got %08x",
+					   aThread, aPage, aIteration, aPtr, aExpected, aActual);
+		return EFalse;
+		}
+	return ETrue;
+	}
+
+TInt SoakTestFunc(TAny* aArg)
+	{
+	SSoakTestArgs* args = (SSoakTestArgs*)aArg;
+
+	
+	RMsgQueue<TInt> msgQueue;
+	TInt r = msgQueue.OpenGlobal(KMsgQueueName, EOwnerThread);
+	if (r != KErrNone)
+		return r;
+
+	TUint32* contents = new TUint32[args->iPages];
+	if (contents == NULL)
+		return KErrNoMemory;
+	Mem::Fill(contents, args->iPages * sizeof(TUint32), 0);
+
+	TInt i = 0;
+	TRandom random;
+	while (ContinueSoakTest(msgQueue))
+		{
+		TUint32 rand = random.Next();
+		TInt page = rand % args->iPages;
+		TUint32* ptr = PageDataPtr(page, args->iThreadIndex);
+		TInt action = rand >> 31;
+		if (action == 0)
+			{
+			if (!TestReadWord(PageBasePtr(page), PageTag(page), args->iThreadIndex, page, i, __LINE__, msgQueue))
+				return KErrGeneral;
+			if (!TestReadWord(&ptr[0], contents[page], args->iThreadIndex, page, i, __LINE__, msgQueue))
+				return KErrGeneral;
+			if (!TestReadWord(&ptr[1], contents[page], args->iThreadIndex, page, i, __LINE__, msgQueue))
+				return KErrGeneral;
+			}
+		else
+			{
+			TUint newContents = args->iThreadIndex+0x100+(contents[page]&~0xff);
+			ptr[0] = newContents;
+			if (!TestReadWord(PageBasePtr(page), PageTag(page), args->iThreadIndex, page, i, __LINE__, msgQueue))
+				return KErrGeneral;
+			if (!TestReadWord(&ptr[1], contents[page], args->iThreadIndex, page, i, __LINE__, msgQueue))
+				return KErrGeneral;
+			ptr[1] = newContents;
+			contents[page] = newContents;
+			}
+		++i;
+		}
+	
+	for (TInt j = 0 ; j < args->iPages ; ++j)
+		{
+		TUint32* ptr = PageDataPtr(j, args->iThreadIndex);
+		if (!TestReadWord(PageBasePtr(j), PageTag(j), args->iThreadIndex, j, i, __LINE__, msgQueue))
+			return KErrGeneral;
+		if (!TestReadWord(&ptr[0], contents[j], args->iThreadIndex, j, i, __LINE__, msgQueue))
+			return KErrGeneral;
+		if (!TestReadWord(&ptr[1], contents[j], args->iThreadIndex, j, i, __LINE__, msgQueue))
+			return KErrGeneral;
+		}
+
+	delete [] contents;
+	msgQueue.Close();
+
+	RDebug::Printf("  thread %d performed %d iterations", args->iThreadIndex, i);
+	return KErrNone;
+	}
+
+TInt SoakProcess(TInt aProcessIndex, TInt aThreads, TInt aPages, TBool aPinPages)
+	{
+	TInt pinThreadIndex = aPinPages ? aThreads++ : -1;
+
+	test_KErrNone(gChunk.OpenGlobal(KChunkName, EFalse));
+	
+	SSoakTestArgs* testArgs = new SSoakTestArgs[aThreads];
+	test_NotNull(testArgs);
+			
+	RThread* threads = new RThread[aThreads];
+	test_NotNull(threads);
+	
+	TRequestStatus* statuses = new TRequestStatus[aThreads];
+	test_NotNull(statuses);
+	
+	TInt i;
+	for (i = 0 ; i < aThreads ; ++i)
+		{
+		testArgs[i].iThreadIndex = aProcessIndex * aThreads + i;
+		testArgs[i].iPages = aPages;
+		TThreadFunction func = i == pinThreadIndex ? PinPagesFunc : SoakTestFunc;
+		test_KErrNone(threads[i].Create(KNullDesC, func, gPageSize, NULL, &testArgs[i]));
+		threads[i].Logon(statuses[i]);
+		}
+
+	// todo: rendezvous here?
+	
+	for (i = 0 ; i < aThreads ; ++i)
+		threads[i].Resume();
+	
+	TBool ok = ETrue;		
+	for (i = 0 ; i < aThreads ; ++i)
+		{
+		User::WaitForRequest(statuses[i]);
+		if (threads[i].ExitType() != EExitKill || statuses[i].Int() != KErrNone)
+			ok = EFalse;
+		threads[i].Close();
+		}
+	
+	delete [] testArgs;
+	delete [] threads;
+	delete [] statuses;
+	gChunk.Close();
+	
+	return ok ? KErrNone : KErrGeneral;
+	}
+
+TInt RunSoakProcess()
+	{
+	TBuf<80> buf;
+	if (User::CommandLineLength() > buf.MaxLength())
+		return KErrArgument;
+	User::CommandLine(buf);
+	TLex lex(buf);
+
+	TInt index;
+	TInt r = lex.Val(index);
+	if (r != KErrNone)
+		return r;
+	lex.SkipSpace();
+
+	TInt threads;
+	r = lex.Val(threads);
+	if (r != KErrNone)
+		return r;
+	lex.SkipSpace();
+	
+	TInt pages;
+	r = lex.Val(pages);
+	if (r != KErrNone)
+		return r;
+	lex.SkipSpace();
+	
+	TBool pinPages;
+	r = lex.Val(pinPages);
+	if (r != KErrNone)
+		return r;
+	
+	return SoakProcess(index, threads, pages, pinPages);
+	}
+
+void SoakTest(TInt aProcesses, TInt aThreads, TInt aPages, TBool aPinPages, TInt aDurationInSeconds)
+	{
+	RDebug::Printf("Soak test: %d processes, %d threads, %d pages, %s pinning for %d seconds",
+				   aProcesses, aThreads, aPages, (aPinPages ? "with" : "without"), aDurationInSeconds);
+	DPTest::FlushCache();
+	
+	TInt totalThreads = (aThreads + (aPinPages ? 1 : 0)) * aProcesses;
+	test(totalThreads < 512); // each thread uses two words in a page
+
+	TMediaPagingStats dummy=EMediaPagingStatsRomAndCode;
+	PagingInfo::ResetBenchmarks(-1, dummy);	// Don't worry about locmedia stats.
+
+	RMsgQueue<TInt> msgQueue;
+	test_KErrNone(msgQueue.CreateGlobal(KMsgQueueName, totalThreads, EOwnerThread));
+
+	CreatePagedChunk(aPages, 0);
+	TInt i;
+	for (i = 0 ; i < aPages ; ++i)
+		*PageBasePtr(i) = PageTag(i);
+			
+	RProcess* processes = new RProcess[aProcesses];
+	TRequestStatus* statuses = new TRequestStatus[aProcesses];
+	for (i = 0 ; i < aProcesses ; ++i)
+		{
+		TBuf<80> args;
+		args.AppendFormat(_L("%d %d %d %d"), i, aThreads, aPages, aPinPages);
+		test_KErrNone(processes[i].Create(_L("t_datapaging"), args));
+		processes[i].Logon(statuses[i]);
+		}
+
+	RThread().SetPriority(EPriorityMore); // so we don't get starved of CPU by worker threads
+
+	for (i = 0 ; i < aProcesses ; ++i)
+		processes[i].Resume();
+
+	User::After(aDurationInSeconds * 1000000);
+	StopSoakTest(msgQueue);
+	
+	TBool ok = ETrue;		
+	for (i = 0 ; i < aProcesses ; ++i)
+		{
+		User::WaitForRequest(statuses[i]);
+		if (processes[i].ExitType() != EExitKill || statuses[i].Int() != KErrNone)
+			{
+			ok = EFalse;
+			RDebug::Printf("  process %i died with %d,%d", i, processes[i].ExitType(), statuses[i].Int());
+			}
+		processes[i].Close();
+		}
+
+	RThread().SetPriority(EPriorityNormal);
+
+	if (!ok)
+		{
+		for (i = 0 ; i < aPages ; ++i)
+			{
+			test.Printf(_L("%3d %08x"), i, *PageBasePtr(i));
+			for (TInt j = 0 ; j < totalThreads ; ++j)
+				{
+				TUint32* ptr = PageDataPtr(i, j);
+				test.Printf(_L(" %08x,%08x"), ptr[0], ptr[1]);
+				}
+			test.Printf(_L("\n"), i);
+			}
+		}
+	test(ok);	
+
+	gChunk.Close();
+	
+	User::After(1000000);
+	RDebug::Printf("  done");
+	RDebug::Printf("\n");
+	
+	msgQueue.Close();
+	delete [] processes;
+	delete [] statuses;
+
+	PagingInfo::PrintBenchmarks(-1, dummy);	// Don't worry about locmedia stats.
+	}
+
+void CommitPage(RChunk chunk, TInt aPageIndex)
+	{
+	test_KErrNone(chunk.Commit(aPageIndex * gPageSize, gPageSize));
+	}
+
+void DecommitPage(RChunk chunk, TInt aPageIndex)
+	{
+	test_KErrNone(chunk.Decommit(aPageIndex * gPageSize, gPageSize));
+	}
+
+void WaitForNotifiers()
+	{
+	// wait until notifiers have had chance to signal us...
+	UserSvr::HalFunction(EHalGroupKernel, EKernelHalSupervisorBarrier, 0, 0);
+	}
+
+void TestSwapHal()
+	{
+	test.Next(_L("Test EVMHalGetSwapInfo"));
+
+	TChunkCreateInfo createInfo;
+	createInfo.SetDisconnected(0, 0, 256 * gPageSize);
+	createInfo.SetPaging(TChunkCreateInfo::EPaged);
+	RChunk chunk;
+	test_KErrNone(chunk.Create(createInfo));
+	if (gDataPagingSupported)
+		test(chunk.IsPaged());
+	
+	SVMSwapInfo swapInfo;
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalGetSwapInfo, &swapInfo, 0));
+	test(swapInfo.iSwapFree <= swapInfo.iSwapSize);
+	test.Printf(_L("  Swap size == 0x%x bytes\n"), swapInfo.iSwapSize);
+	test.Printf(_L("  Swap free == 0x%x bytes\n"), swapInfo.iSwapFree);
+	if (!gDataPagingSupported)
+		{
+		test_Equal(0, swapInfo.iSwapSize);
+		}
+	else
+		{
+		test(swapInfo.iSwapSize != 0);
+		
+		CommitPage(chunk, 0);
+		SVMSwapInfo swapInfo2;
+		test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalGetSwapInfo, &swapInfo2, 0));
+		test_Equal(swapInfo.iSwapSize, swapInfo2.iSwapSize);
+		test_Equal(swapInfo.iSwapFree - gPageSize, swapInfo2.iSwapFree);
+		
+		DecommitPage(chunk, 0);
+		test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalGetSwapInfo, &swapInfo2, 0));
+		test_Equal(swapInfo.iSwapSize, swapInfo2.iSwapSize);
+		test_Equal(swapInfo.iSwapFree, swapInfo2.iSwapFree);
+
+		// Test that closing the chunk releases the swap page.
+		CommitPage(chunk, 0);
+		test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalGetSwapInfo, &swapInfo2, 0));
+		test_Equal(swapInfo.iSwapSize, swapInfo2.iSwapSize);
+		test_Equal(swapInfo.iSwapFree - gPageSize, swapInfo2.iSwapFree);
+		
+		chunk.Close();
+		test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalGetSwapInfo, &swapInfo2, 0));
+		test_Equal(swapInfo.iSwapSize, swapInfo2.iSwapSize);
+		test_Equal(swapInfo.iSwapFree, swapInfo2.iSwapFree);
+
+		// Chunk must be created for rest of testing.
+		test_KErrNone(chunk.Create(createInfo));
+		if (gDataPagingSupported)
+			test(chunk.IsPaged());
+		}
+	
+	//	EVMHalSetSwapThresholds,
+	test.Next(_L("Test EVMHalSetSwapThresholds"));
+	SVMSwapThresholds thresholds;
+	thresholds.iLowThreshold = 1;
+	thresholds.iGoodThreshold = 0;
+	test_Equal(KErrArgument, UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, &thresholds, 0));
+	thresholds.iLowThreshold = swapInfo.iSwapSize + 1;
+	thresholds.iGoodThreshold = swapInfo.iSwapSize + 1;
+	test_Equal(KErrArgument, UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, &thresholds, 0));
+	thresholds.iLowThreshold = 0;
+	thresholds.iGoodThreshold = 0;
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, &thresholds, 0));
+	thresholds.iLowThreshold = swapInfo.iSwapSize;
+	thresholds.iGoodThreshold = swapInfo.iSwapSize;
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, &thresholds, 0));
+
+	// test thresholds trigger ok
+	
+	RChangeNotifier changes;
+	test_KErrNone(changes.Create());
+	TRequestStatus status;
+	test_KErrNone(changes.Logon(status));
+	User::WaitForRequest(status);
+	test_KErrNone(changes.Logon(status));
+	test_Equal(KRequestPending, status.Int());
+	
+	thresholds.iLowThreshold = swapInfo.iSwapFree - 2 * gPageSize;
+	thresholds.iGoodThreshold = swapInfo.iSwapFree - gPageSize;
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, &thresholds, 0));
+
+	CommitPage(chunk, 0);
+	CommitPage(chunk, 1);
+	WaitForNotifiers();
+	test_Equal(KRequestPending, status.Int());
+	CommitPage(chunk, 2);
+	WaitForNotifiers();
+	test_Equal(EChangesFreeMemory | EChangesLowMemory, status.Int());
+	User::WaitForRequest(status);
+	
+	test_KErrNone(changes.Logon(status));
+	DecommitPage(chunk, 2);
+	WaitForNotifiers();
+	test_Equal(KRequestPending, status.Int());
+	DecommitPage(chunk, 1);
+	WaitForNotifiers();
+	test_Equal(EChangesFreeMemory, status.Int());
+	User::WaitForRequest(status);
+	DecommitPage(chunk, 0);
+	
+	CLOSE_AND_WAIT(changes);
+
+	// leave some sensible thresholds set
+	thresholds.iLowThreshold = (10 * swapInfo.iSwapSize) / 100;
+	thresholds.iGoodThreshold = (20 * swapInfo.iSwapSize) / 100;
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, &thresholds, 0));
+
+	CLOSE_AND_WAIT(chunk);
+	}
+
+void TestSwapHalNotSupported()
+	{
+	test_Equal(KErrNotSupported, UserSvr::HalFunction(EHalGroupVM, EVMHalGetSwapInfo, 0, 0));
+	test_Equal(KErrNotSupported, UserSvr::HalFunction(EHalGroupVM, EVMHalSetSwapThresholds, 0, 0));
+	}
+
+void TestHal()
+	{
+	if (gDataPagingSupported)
+		TestSwapHal();
+	else
+		TestSwapHalNotSupported();
+	}
+
+
+TBool gStealEnable = false;
+
+TInt DecommitThread(TAny*)
+	{
+	RThread().SetPriority(EPriorityLess); // so this thread gets pre-empted by StealThread
+	TUint8* base = gChunk.Base();
+	TInt size = gChunk.MaxSize();
+	for(;;)
+		{
+		// dirty all pages
+		for(TInt i=0; i<size; i+=gPageSize)
+			base[i] = 0;
+		// free pages...
+		gStealEnable = true;
+		gChunk.Adjust(0);
+		gStealEnable = false;
+		// recommit pages...
+		TInt r = gChunk.Adjust(size);
+		if(r!=KErrNone)
+			return r; // error
+		}
+	}
+
+
+TInt StealThread(TAny*)
+	{
+	for(;;)
+		{
+		while(!gStealEnable)
+			User::AfterHighRes(0);
+		DPTest::FlushCache();
+		}
+	}
+
+
+void TestDecommitAndStealInteraction(TInt aSeconds)
+	{
+	__KHEAP_MARK;
+
+	CreatePagedChunk(256);
+
+	RThread thread1;
+	test_KErrNone(thread1.Create(_L("DecommitThread"), DecommitThread, gPageSize, NULL, 0));
+	TRequestStatus status1;
+	thread1.Logon(status1);
+
+	RThread thread2;
+	test_KErrNone(thread2.Create(_L("StealThread"), StealThread, gPageSize, NULL, 0));
+	TRequestStatus status2;
+	thread1.Logon(status2);
+
+	RTimer timer;
+	test_KErrNone(timer.CreateLocal());
+	TRequestStatus timeoutStatus;
+	timer.After(timeoutStatus,aSeconds*1000000);
+
+	thread1.Resume();
+	thread2.Resume();
+	User::WaitForAnyRequest();
+
+	thread1.Kill(123);
+	User::WaitForRequest(status1);
+	test_Equal(123, status1.Int());
+	CLOSE_AND_WAIT(thread1);
+
+	thread2.Kill(123);
+	User::WaitForRequest(status2);
+	test_Equal(123, status2.Int());
+	CLOSE_AND_WAIT(thread2);
+
+	CLOSE_AND_WAIT(timer);
+	test_KErrNone(timeoutStatus.Int());
+	
+	CLOSE_AND_WAIT(gChunk);
+	__KHEAP_MARKEND;
+	}
+
+TInt ThreadAtomic64Flush(TAny*)
+	{
+	TInt64 seed = 0x33333333;
+	FOREVER
+		{
+		DPTest::FlushCache();
+		User::After(Math::Rand(seed) & 0x48);
+		}
+	}
+
+enum TAtomic64Test
+	{
+	EAtomic64Add,
+	EAtomic64Logic,
+	EAtomic64Cas,
+	EAtomic64Steps,
+	};
+
+struct SAtomic64Args
+	{
+	TUint iIters;
+	TUint64* iData;
+	TInt iIncs;
+	TUint iClears[64];
+	TUint iSets[64];
+	};
+
+
+TInt ThreadAtomic64Cas(TAny* aArgs)
+	{
+	SAtomic64Args& args = *(SAtomic64Args*)aArgs;
+	for (TUint i = 0; i < args.iIters; i++)
+		{
+		TUint64 setMask = UI64LIT(0xffffffffffffffff);
+		TUint64 clrMask = 0;
+		if (__e32_atomic_cas_ord64(args.iData, &setMask, clrMask))
+			args.iClears[0]++;
+		// Undo any clearing of setMask which will happen if iData is 0.
+		setMask = UI64LIT(0xffffffffffffffff);
+		if (__e32_atomic_cas_ord64(args.iData, &clrMask, setMask))
+			args.iSets[0]++;
+		}
+	return KErrNone;
+	}
+
+
+TInt ThreadAtomic64Logic(TAny* aArgs)
+	{
+	TInt r = KErrNone;
+	SAtomic64Args& args = *(SAtomic64Args*)aArgs;
+	for(TUint i = 0; i < args.iIters; i++)
+		{
+		TUint bitNo = (i & 0x3f);
+		TUint64 bitMask = ((TUint64)1) << bitNo;
+		TUint64 andMask = ~bitMask;
+
+		TUint64 old = __e32_atomic_and_ord64(args.iData, andMask);
+		if (old & bitMask)
+			args.iClears[bitNo]++;
+
+		old = __e32_atomic_ior_ord64(args.iData, bitMask);
+		if (!(old & bitMask))
+			args.iSets[bitNo]++;
+
+		old = __e32_atomic_xor_ord64(args.iData, bitMask);
+		if (old & bitMask)
+			args.iClears[bitNo]++;
+		else
+			args.iSets[bitNo]++;
+
+		old = __e32_atomic_axo_ord64(args.iData, UI64LIT(0xffffffffffffffff), bitMask);
+		if (old & bitMask)
+			args.iClears[bitNo]++;
+		else
+			args.iSets[bitNo]++;
+		
+		}
+	return r;
+	}
+
+
+TInt ThreadAtomic64Add(TAny* aArgs)
+	{
+	TInt r = KErrNone;
+	SAtomic64Args& args = *(SAtomic64Args*)aArgs;
+	for(TUint i = 0; i < args.iIters; i++)
+		{
+		TUint64 old = __e32_atomic_add_ord64(args.iData, 1);
+		args.iIncs += 1;
+		old = __e32_atomic_tau_ord64(args.iData, 1000, 1, 2);
+		args.iIncs += (old >= 1000)? 1 : 2;
+		old = __e32_atomic_tas_ord64(args.iData, 1000, 1, -1);
+		args.iIncs += (old >= 1000)? 1 : -1;
+		}
+	return r;
+	}
+
+
+void TestAtomic64()
+	{
+	CreatePagedChunk(sizeof(TUint64));
+	TUint64* data = (TUint64*)gChunk.Base();
+
+	const TUint KThreads = 25;
+	RThread threads[KThreads];
+	TRequestStatus stats[KThreads];
+	SAtomic64Args* args = new SAtomic64Args[KThreads];
+	test_NotNull(args);
+
+	for (TInt testStep = EAtomic64Add; testStep < EAtomic64Steps; testStep++)
+		{
+		switch (testStep)
+			{
+			case EAtomic64Add:
+				test.Next(_L("Test 64-bit atomic addition operations"));
+					break;
+			case EAtomic64Logic:
+				test.Next(_L("Test 64-bit atomic logic operations"));
+				break;
+			case EAtomic64Cas:
+				test.Next(_L("Test 64-bit atomic cas operations"));
+				break;
+			}
+		*data = 0;
+		RThread threadFlush;
+		test_KErrNone(threadFlush.Create(_L("ThreadAtomicFlush"), ThreadAtomic64Flush, gPageSize, NULL, NULL));
+		TRequestStatus status1;
+		threadFlush.Logon(status1);
+		threadFlush.SetPriority(EPriorityAbsoluteHigh);
+
+		memclr(args, sizeof(SAtomic64Args)*KThreads);
+		TUint i = 0;
+		for (; i < KThreads; i++)
+			{
+			args[i].iIters = 10000;
+			args[i].iData = data;
+			switch (testStep)
+				{
+				case EAtomic64Add:
+					test_KErrNone(threads[i].Create(KNullDesC, ThreadAtomic64Add, gPageSize, NULL, (TAny*)&args[i]));
+					break;
+				case EAtomic64Logic:
+					test_KErrNone(threads[i].Create(KNullDesC, ThreadAtomic64Logic, gPageSize, NULL, (TAny*)&args[i]));
+					break;
+				case EAtomic64Cas:
+					test_KErrNone(threads[i].Create(KNullDesC, ThreadAtomic64Cas, gPageSize, NULL, (TAny*)&args[i]));
+					break;
+				}
+			threads[i].Logon(stats[i]);
+			}
+		threadFlush.Resume();
+		for (i = 0; i < KThreads; i++)
+			{
+			threads[i].Resume();
+			}
+
+		// Wait for add threads to complete and kill flushing thread.
+		for (i = 0; i < KThreads; i++)
+			{
+			User::WaitForRequest(stats[i]);
+			test_KErrNone(stats[i].Int());
+			}
+		threadFlush.Kill(KErrNone);
+		User::WaitForRequest(status1);
+		test_KErrNone(status1.Int());
+		TInt64 expected = 0;
+		switch (testStep)
+			{
+			case EAtomic64Add:
+				{
+				for (TUint i = 0; i < KThreads; i++)
+					{
+					threads[i].Close();
+					expected += args[i].iIncs;
+					}
+				break;
+				}
+			case EAtomic64Logic:
+				{
+				TUint totalSets[64];
+				TUint totalClears[64];
+				memclr(totalSets, sizeof(TUint)*64);
+				memclr(totalClears, sizeof(TUint)*64);
+				for (TUint i = 0; i < KThreads; i++)
+					{
+					threads[i].Close();
+					for (TUint j = 0; j < 64; j++)
+						{
+						totalSets[j] += args[i].iSets[j];
+						totalClears[j] += args[i].iClears[j];
+						}
+					}
+				for (TUint j = 0; j < 64; j++)
+					{
+					TUint64 bitMask = 1 << j;
+					if (totalSets[j] > totalClears[j])
+						{
+						test_Equal(totalSets[j] - 1, totalClears[j]);
+						expected |= bitMask;
+						}
+					else
+						{// Can only clear a bit if it was previously set.
+						test_Equal(totalClears[j], totalSets[j]);
+						}
+					}
+				break;
+				}
+			case EAtomic64Cas:
+				{
+				TUint totalSets = 0;
+				TUint totalClears = 0;
+				for (TUint i = 0; i < KThreads; i++)
+					{
+					threads[i].Close();
+					totalSets += args[i].iSets[0];
+					totalClears += args[i].iClears[0];
+					}
+				if (totalSets > totalClears)
+					{
+					test_Equal(totalSets - 1, totalClears);
+					expected = UI64LIT(0xffffffffffffffff);
+					}
+				else
+					{// Can only clear a word if it was previously set.
+					test_Equal(totalClears, totalSets);
+					}
+				break;
+				}
+			}
+		test_Equal(expected, *data);
+		CLOSE_AND_WAIT(threadFlush);
+		}
+	delete[] args;
+	CLOSE_AND_WAIT(gChunk);
+	}
+
+
+//
+// soak test for writeable paged code...
+//
+
+const TUint KCodeStride = 20; // spacing between generated code
+
+void CodeStart(TUint8* aCode, TUint8* aTarget, TUint32 aInit)
+	{
+#if defined(__CPU_X86)
+	aCode[0] = 0xb8; *(TUint32*)&(aCode[1]) = aInit;				// mov eax,aInit
+	aCode[5] = 0xe9; *(TUint32*)&(aCode[6]) = aTarget-(aCode+10);	// jmp aTarget
+	__ASSERT_COMPILE(KCodeStride>=10);
+
+#elif defined(__CPU_ARM)
+	*(TUint32*)&(aCode[0]) = 0xe59f0000;			// ldr r0, [pc, #0]
+	TInt32 offset = (aTarget-aCode-4-8)/4;
+	if(offset&0xff000000u)
+		{
+		offset ^= 0xff000000u;
+		test_Equal(0,offset&0xff000000u);
+		}
+	*(TUint32*)&(aCode[4]) = 0xea000000|offset;		// b aTarget
+	*(TUint32*)&(aCode[8]) = aInit;					// dcd aInit
+	__ASSERT_COMPILE(KCodeStride>=12);
+
+#else
+#error Unknown CPU
+#endif
+	}
+
+
+void CodeStep(TUint8* aCode, TUint8* aTarget, TUint32 aAdd)
+	{
+#if defined(__CPU_X86)
+	aCode[0] = 0xd1; aCode[1] = 0xc0;								// rol eax, 1
+	aCode[2] = 0x05; *(TUint32*)&(aCode[3]) = aAdd;					// add eax, aAdd
+	aCode[7] = 0xe9; *(TUint32*)&(aCode[8]) = aTarget-(aCode+12);	// jmp aTarget
+	__ASSERT_COMPILE(KCodeStride>=12);
+
+#elif defined(__CPU_ARM)
+	*(TUint32*)&(aCode[0]) = 0xe1a00fe0;			// ror r0, r0, #31
+	*(TUint32*)&(aCode[4]) = 0xe59f1004;			// ldr r1, [pc, #4]
+	*(TUint32*)&(aCode[8]) = 0xe0800001;			// add r0, r0, r1
+	TInt32 offset = (aTarget-aCode-12-8)/4;
+	if(offset&0xff000000u)
+		{
+		offset ^= 0xff000000u;
+		test_Equal(0,offset&0xff000000u);
+		}
+	*(TUint32*)&(aCode[12]) = 0xea000000|offset;	// b aTarget
+	*(TUint32*)&(aCode[16]) = aAdd;					// dcd aAdd
+	__ASSERT_COMPILE(KCodeStride>=20);
+
+#else
+#error Unknown CPU
+#endif
+	}
+
+
+void CodeEnd(TUint8* aCode)
+	{
+#if defined(__CPU_X86)
+	aCode[0] = 0xc3;						// ret
+	__ASSERT_COMPILE(KCodeStride>=1);
+
+#elif defined(__CPU_ARM)
+	*(TUint32*)&(aCode[0]) = 0xe12fff1e;	// bx lr
+	__ASSERT_COMPILE(KCodeStride>=4);
+
+#else
+#error Unknown CPU
+#endif
+	}
+
+
+void TestExecutableMemory()
+	{
+	__KHEAP_MARK;
+
+#if defined(__CPU_ARM)
+	const TUint KMaxChunkSize = 31*1024*1024; // ARM branch instruction limit
+#else
+	const TUint KMaxChunkSize = 1024*1024*1024; // 1GB
+#endif
+	const TUint KMaxPages = KMaxChunkSize/gPageSize;
+	TUint sizeInPages = gMaxCacheSize*2;
+	if(sizeInPages>KMaxPages)
+		sizeInPages = KMaxPages;
+
+	// create code chunk...
+	test.Start(_L("Create code chunk"));
+	TChunkCreateInfo createInfo;
+	TInt size = sizeInPages * gPageSize;
+	createInfo.SetCode(size, size);
+	createInfo.SetPaging(TChunkCreateInfo::EPaged);
+	createInfo.SetClearByte(0);
+	RChunk chunk;
+	test_KErrNone(chunk.Create(createInfo));
+	test(chunk.IsPaged()); // this is only ever called if data paging is supported
+	TUint8* base = chunk.Base();
+
+	// create code path through the pages in the chunk with quadratic distribution...
+	test.Next(_L("Weave path"));
+	TInt pathLength = 0;
+	const TUint maxStepsPerPage = gPageSize/KCodeStride;
+	const TInt maxPathLength = sizeInPages*maxStepsPerPage;
+	TUint8** path = (TUint8**)User::Alloc(maxPathLength*sizeof(TUint8*));
+	test(path!=0);
+	for(TUint page=0; page<sizeInPages; ++page)
+		{
+		TUint step = (maxStepsPerPage-1)*(page*page)/(sizeInPages*sizeInPages)+1;
+		do path[pathLength++] = base+page*gPageSize+step*KCodeStride;
+		while(--step);
+		}
+	TUint32 rand = 0x12345678;
+	for(TUint scramble=pathLength*4; scramble>0; --scramble)
+		{
+		// swap random pair of entries on path...
+		TUint i = (TUint)(TUint64(TUint64(rand)*TUint64(pathLength))>>32);
+		rand = rand*69069+1;
+		TUint j = (TUint)(TUint64(TUint64(rand)*TUint64(pathLength))>>32);
+		rand = rand*69069+1;
+		TUint8* t = path[i];
+		path[i] = path[j];
+		path[j] = t;
+		}
+
+	// write code to generated path...
+	test.Next(_L("Write code"));
+	TUint32 a = 0;
+	TUint32 (*code)() = (TUint32 (*)())path[pathLength-1];
+	CodeStart(path[pathLength-1],path[pathLength-2],a);
+	while(--pathLength>1)
+		{
+		rand = rand*69069+1;
+		CodeStep(path[pathLength-1],path[pathLength-2],rand);
+		a = (a<<1)+(a>>31);
+		a += rand;
+		}
+	CodeEnd(path[0]);
+	--pathLength;
+	test_Equal(0,pathLength);
+	test.Next(_L("IMB"));
+	User::IMB_Range(base,base+chunk.Size());
+
+	// run code...
+	TMediaPagingStats dummy=EMediaPagingStatsRomAndCode;
+	PagingInfo::ResetBenchmarks(-1, dummy);	// Don't worry about locmedia stats.
+	test.Next(_L("Execute code"));
+	TUint32 result = code();
+	test_Equal(a,result);
+	PagingInfo::PrintBenchmarks(-1, dummy);	// Don't worry about locmedia stats.
+
+	// cleanup...
+	test.Next(_L("Cleanup"));
+	User::Free(path);
+	CLOSE_AND_WAIT(chunk);
+
+	test.End();
+
+	UserSvr::HalFunction(EHalGroupKernel, EKernelHalSupervisorBarrier, 0, 0);
+	__KHEAP_MARKEND;
+	}
+
+
+
+TInt E32Main()
+	{
+	test_KErrNone(UserHal::PageSizeInBytes(gPageSize));
+	
+	if (User::CommandLineLength() != 0)
+		return RunSoakProcess();
+	
+	test.Title();
+	test_KErrNone(GetGlobalPolicies());
+
+	test.Start(_L("Test HAL APIs"));
+	TestHal();
+
+	if (gDataPagingSupported)
+		{
+		test.Next(_L("Test 64-bit atomic operations are atomic with paged out data"));
+		TestAtomic64();
+
+		test.Next(_L("Test reading and writing to a single page"));
+		TestOnePage();
+
+		test.Next(_L("Test interaction between decommit and steal"));
+		TestDecommitAndStealInteraction(10);
+
+		test.Next(_L("Test killing a thread while it's paging in"));
+		TestKillThread(PageInThreadFunc, 200);
+				
+		test.Next(_L("Test killing a thread while it's paging out"));
+		TestKillThread(PageOutThreadFunc, 200);
+		
+		test.Next(_L("Test executable memory"));
+		TestExecutableMemory();
+
+		test.Next(_L("Soak tests"));
+		DPTest::FlushCache();
+		for (TUint totalThreads = 1 ; totalThreads <= 64 ; totalThreads *= 4)
+			{
+			for (TUint processes = 1 ; processes <= 16 && processes <= totalThreads ; processes *= 4)
+				{
+				TUint threads = totalThreads / processes;
+				for (TUint pages = gMaxCacheSize / 2 ; pages <= gMaxCacheSize * 2 ; pages *= 2)
+					{
+					for (TUint pin = 0 ; pin <= 1 ; ++pin)
+						{
+						test.Printf(_L("processes=%d threads=%d pages=%d pin=%d\r\n"),processes, threads, pages, pin);
+						SoakTest(processes, threads, pages, pin, 3);
+						}
+					}
+				}
+			}
+		}
+
+	test.End();
+	return 0;
+	}