// 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\mmu\t_mmustress.cpp
// Stress test for memory management services performed by the kernel's memory model.
// 
//

/**
 @file
*/

#define __E32TEST_EXTENSION__
#include <e32test.h>
#include "u32std.h"
#include <u32hal.h>
#include <e32svr.h>
#include <dptest.h>
#include <e32def.h>
#include <e32def_private.h>
#include "d_memorytest.h"
#include "../defrag/d_pagemove.h"

TBool TRACE = 0;

LOCAL_D RTest test(_L("T_MMUSTRESS"));

TUint32 MemModelAttributes;
TUint32 MemModel;
TInt PageSize;
TInt PageMask;

#if !defined(__WINS__) && !defined(__X86__)
const TPtrC KMoveLddFileName=_L("D_PAGEMOVE.LDD");
RPageMove MoveLdd;
#endif

RMemoryTestLdd Ldd;

const TUint KNumTestChunks = 6;
RChunk Chunks[KNumTestChunks];
TInt Committed[KNumTestChunks] = {0}; // for each chunk, is the 'owned' region uncommited(0), commited(1) or mixed(-1)
class TNicePtr8 : public TPtr8 { public: TNicePtr8() : TPtr8(0,0) {} } ChunkPtr[KNumTestChunks];

const TUint KNumSlaveProcesses = 4;
RProcess Slaves[KNumSlaveProcesses];
TRequestStatus SlaveLogons[KNumSlaveProcesses];
TRequestStatus SlaveRendezvous[KNumSlaveProcesses];

TInt SlaveNumber = -1; // master process is slave -1

const TInt KLocalIpcBufferSize = 0x10000;
TUint8* LocalIpcBuffer = 0;

RSemaphore StartSemaphore;

//
// Random number generation
//

TUint32 RandomSeed;

TUint32 Random()
	{
	RandomSeed = RandomSeed*69069+1;
	return RandomSeed;
	}

TUint32 Random(TUint32 aRange)
	{
	return (TUint32)((TUint64(Random())*TUint64(aRange))>>32);
	}

void RandomInit(TUint32 aSeed)
	{
	RandomSeed = aSeed+(aSeed<<8)+(aSeed<<16)+(aSeed<<24);
	Random();
	Random();
	}



//
// Chunk utils
//

TBuf<KMaxKernelName> ChunkName(TInt aChunkNumber)
	{
	TBuf<KMaxKernelName> name;
	name.Format(_L("T_MMUSTRESS-Chunk%d"),aChunkNumber);
	return name;
	}

#ifdef __WINS__
TInt KChunkShift = 16;
#elif defined(__X86__)
TInt KChunkShift = 22;
#else
TInt KChunkShift = 20;
#endif

TInt ChunkSize(TInt aChunkNumber)
	{
	// biggest chunk (number 0) is big enough for each slave to own a region which is
	// 2 page tables ('chunks') in size...
	return (2*KNumSlaveProcesses)<<(KChunkShift-aChunkNumber);
	}

// check smallest chunk is less than 'chunk' size...
__ASSERT_COMPILE((2*KNumSlaveProcesses>>(KNumTestChunks-1))==0);


/* Memory region 'owned' by this slave process */
void ChunkOwnedRegion(TInt aChunkNumber,TInt& aOffset,TInt& aSize)
	{
	TInt size = ChunkSize(aChunkNumber)/KNumSlaveProcesses;
	aSize = size;
	aOffset = SlaveNumber*size;
	test_Equal(0,size&PageMask);
	}

void ChunkMarkRegion(TInt aChunkNumber,TInt aOffset,TInt aSize)
	{
	TInt pageSize = PageSize;
	TUint32 mark = aOffset|aChunkNumber|(SlaveNumber<<4);
	TUint8* ptr = Chunks[aChunkNumber].Base()+aOffset;
	TUint8* ptrEnd = ptr+aSize;
	while(ptr<ptrEnd)
		{
		((TUint32*)ptr)[0] = mark;
		((TUint32*)ptr)[1] = ~mark;
		mark += pageSize;
		ptr += pageSize;
		}
	}

void ChunkCheckRegion(TInt aChunkNumber,TInt aOffset,TInt aSize)
	{
	TInt pageSize = PageSize;
	TUint32 mark = aOffset|aChunkNumber|(SlaveNumber<<4);
	TUint8* ptr = Chunks[aChunkNumber].Base()+aOffset;
	TUint8* ptrEnd = ptr+aSize;
	while(ptr<ptrEnd)
		{
		test_Equal(mark,((TUint32*)ptr)[0]);
		test_Equal(~mark,((TUint32*)ptr)[1]);
		mark += pageSize;
		ptr += pageSize;
		}
	}

TInt ChunkOpen(TInt aChunkNumber)
	{
	RChunk& chunk = Chunks[aChunkNumber];
	if(chunk.Handle()!=0)
		return KErrNone;

	if(TRACE) RDebug::Printf("%d %d Open",SlaveNumber,aChunkNumber);
	TInt r = chunk.OpenGlobal(ChunkName(aChunkNumber),false);
	if(r!=KErrNoMemory)
		test_KErrNone(r);
	return r;
	}

//
// Server utils
//

TBuf<KMaxKernelName> ServerName(TInt aSlaveNumber)
	{
	TBuf<KMaxKernelName> name;
	name.Format(_L("T_MMUSTRESS-Server%d"),aSlaveNumber);
	return name;
	}

RServer2 Server;
RMessage2 ServerMessage;
TRequestStatus ServerStatus;

class RTestSession : public RSessionBase
	{
public:
	TInt Connect(TInt aServerNumber)
		{
		return CreateSession(ServerName(aServerNumber),TVersion(),1,EIpcSession_Unsharable,0,&iStatus);
		}
	TInt Send(TInt aChunkNumber)
		{
		return RSessionBase::Send(0,TIpcArgs(SlaveNumber,aChunkNumber,&ChunkPtr[aChunkNumber]));
		}
	TRequestStatus iStatus;
	};
RTestSession Sessions[KNumSlaveProcesses];


//
//
//

void SlaveInit()
	{
	RDebug::Printf("Slave %d initialising",SlaveNumber);

	TBuf<KMaxKernelName> name;
	name.Format(_L("T_MMUSTRESS-Slave%d"),SlaveNumber);
	User::RenameThread(name);

	test_KErrNone(StartSemaphore.Open(2));
	TInt r;
#if !defined(__WINS__) && !defined(__X86__)
	// Move ldd may not be in the ROM so needs to be loaded.
	r=User::LoadLogicalDevice(KMoveLddFileName);
	test_Value(r, r==KErrNone || r==KErrAlreadyExists);
	test_KErrNone(MoveLdd.Open());
#endif

	test_KErrNone(Ldd.Open());
	test_KErrNone(Ldd.CreateVirtualPinObject());

	LocalIpcBuffer = (TUint8*)User::Alloc(KLocalIpcBufferSize);
	test(LocalIpcBuffer!=0);

	test_KErrNone(Server.CreateGlobal(ServerName(SlaveNumber)));

	TUint i;

	// create sessions with other slaves...
	for(i=0; i<KNumSlaveProcesses; i++)
		{
		for(;;)
			{
			r = Sessions[i].Connect(i);
//			RDebug::Printf("%d Session %d = %d,%d",SlaveNumber,i,r,Sessions[i].iStatus.Int());
			if(r==KErrNotFound)
				{
				// give other slaves time to create their servers...
				User::After(10000);
				continue;
				}
			test_KErrNone(r);
			break;
			}
		}

	// process session connect messages...
	for(i=0; i<KNumSlaveProcesses; i++)
		{
		RMessage2 m;
//		RDebug::Printf("%d Server waiting for connect message",SlaveNumber);
		Server.Receive(m);
		test_Equal(RMessage2::EConnect,m.Function())
		m.Complete(KErrNone);
		}

	// wait for our session connections...
	for(i=0; i<KNumSlaveProcesses; i++)
		{
//		RDebug::Printf("%d Session wait %d",SlaveNumber,i);
		User::WaitForRequest(Sessions[i].iStatus);
		}

	// prime server for receiving mesages...
	Server.Receive(ServerMessage,ServerStatus);

	// synchronise with other processes...
	RDebug::Printf("Slave %d waiting for trigger",SlaveNumber);
	RProcess::Rendezvous(KErrNone);
	StartSemaphore.Wait();
	RDebug::Printf("Slave %d started",SlaveNumber);
	}



//
// Test by random operations...
//

void DoTest()
	{
	RandomInit(SlaveNumber);
	TInt r;
	for(;;)
		{
		// select random chunk...
		TInt chunkNumber = Random(KNumTestChunks);
		RChunk& chunk = Chunks[chunkNumber];

		// get the region of this chunk which this process 'owns'...
		TInt offset;
		TInt size;
		ChunkOwnedRegion(chunkNumber,offset,size);

		// calculate a random region in the owned part...
		TInt randomOffset = offset+(Random(size)&~PageMask);
		TInt randomSize = (Random(size-(randomOffset-offset))+PageMask)&~PageMask;
		if(!randomSize)
			continue; // try again

		// pick a random slave...
		TInt randomSlave = Random(KNumSlaveProcesses);

		// open chunk if it isn't already...
		r = ChunkOpen(chunkNumber);
		if(r==KErrNoMemory)
			continue; // can't do anything with chunk if we can't open it

		// check our contents of chunk...
		if(Committed[chunkNumber]==1)
			{
			if(TRACE) RDebug::Printf("%d %d Check %08x+%08x",SlaveNumber,chunkNumber,offset,size);
			ChunkCheckRegion(chunkNumber,offset,size);
			}

		// perform random operation...
		switch(Random(12))
			{
		case 0:
		case 1:
			// close chunk...
			if(TRACE) RDebug::Printf("%d %d Close",SlaveNumber,chunkNumber);
			chunk.Close();
			break;

		case 2:
			// commit all...
			if(TRACE) RDebug::Printf("%d %d Commit all %08x+%08x",SlaveNumber,chunkNumber,offset,size);
			if(Committed[chunkNumber]!=0)
				{
				r = chunk.Decommit(offset,size);
				test_KErrNone(r);
				Committed[chunkNumber] = 0;
				}
			r = chunk.Commit(offset,size);
			if(r!=KErrNoMemory)
				{
				test_KErrNone(r);
				Committed[chunkNumber] = 1;
				ChunkMarkRegion(chunkNumber,offset,size);
				}
			break;

		case 3:
			// decommit all...
			if(TRACE) RDebug::Printf("%d %d Decommit all %08x+%08x",SlaveNumber,chunkNumber,offset,size);
			r = chunk.Decommit(offset,size);
			test_KErrNone(r);
			Committed[chunkNumber] = 0;
			break;

		case 4:
		case 5:
			// commit random...
			if(TRACE) RDebug::Printf("%d %d Commit %08x+%08x",SlaveNumber,chunkNumber,randomOffset,randomSize);
			r = chunk.Commit(randomOffset,randomSize);
			if(r!=KErrNoMemory)
				{
				if(Committed[chunkNumber]==0)
					{
					test_KErrNone(r);
					Committed[chunkNumber] = -1;
					}
				else if(Committed[chunkNumber]==1)
					{
					test_Equal(KErrAlreadyExists,r);
					}
				else
					{
					if(r!=KErrAlreadyExists)
						test_KErrNone(r);
					}
				}
			break;

		case 6:
		case 7:
			// decommit random...
			if(TRACE) RDebug::Printf("%d %d Decommit %08x+%08x",SlaveNumber,chunkNumber,randomOffset,randomSize);
			r = chunk.Decommit(randomOffset,randomSize);
			test_KErrNone(r);
			if(Committed[chunkNumber]==1)
				Committed[chunkNumber] = -1;
			break;

		case 8:
			if(TRACE) RDebug::Printf("%d %d IPC Send->%d",SlaveNumber,chunkNumber,randomSlave);
//			ChunkPtr[chunkNumber].Set(chunk.Base(),ChunkSize(chunkNumber),ChunkSize(chunkNumber));
			ChunkPtr[chunkNumber].Set(chunk.Base()+offset,size,size);
			Sessions[randomSlave].Send(chunkNumber);
			break;

		case 9:
			// process IPC messages...
			if(ServerStatus.Int()==KRequestPending)
				continue;
			User::WaitForRequest(ServerStatus);

			{
			TInt sourceSlave = ServerMessage.Int0();
			chunkNumber = ServerMessage.Int1();
			if(TRACE) RDebug::Printf("%d %d IPC Receive<-%d",SlaveNumber,chunkNumber,sourceSlave);
			test_Equal(0,ServerMessage.Function());

			// get local descriptor for owned region in chunk...
			size = ServerMessage.GetDesMaxLength(2);
			test_NotNegative(size);
			if(size>KLocalIpcBufferSize)
				size = KLocalIpcBufferSize;
			TPtr8 local(LocalIpcBuffer,size,size);

//			if(Random(2))
				{
				// IPC read from other slave...
				if(TRACE) RDebug::Printf("%d %d IPC Read<-%d",SlaveNumber,chunkNumber,sourceSlave);
				TInt panicTrace = Ldd.SetPanicTrace(EFalse);
				r = ServerMessage.Read(2,local);
				Ldd.SetPanicTrace(panicTrace);
				if(r!=KErrBadDescriptor)
					test_KErrNone(r);
				}
//			else
//				{
//				// IPC write to other slave...
//				if(TRACE) RDebug::Printf("%d %d IPC Write->%d",SlaveNumber,chunkNumber,sourceSlave);
//				r = ServerMessage.Write(2,local,offset);
//				if(r!=KErrBadDescriptor)
//					test_KErrNone(r);
//				if(Committed[chunkNumber]==1)
//					ChunkMarkRegion(chunkNumber,offset,size);
//				}
			}

			ServerMessage.Complete(KErrNone);
			Server.Receive(ServerMessage,ServerStatus);
			break;

		case 10:
		case 11:
			// pin memory...
			{
			test_KErrNone(Ldd.UnpinVirtualMemory());
			for(TInt tries=10; tries>0; --tries)
				{
				TInt chunkSize = ChunkSize(chunkNumber);
				offset = Random(chunkSize);
				TInt maxSize = chunkSize-offset;
				if(maxSize>0x1000)
					maxSize = 0x1000;
				size = Random(maxSize);
				r = Ldd.PinVirtualMemory((TLinAddr)chunk.Base()+offset, size);
				if(r!=KErrNotFound && r!=KErrNoMemory)
					{
					test_KErrNone(r);
					break;
					}
				}
			}
			break;
		case 12:
		case 13:
			// Move any page in the chunk, not just the owned region.
			{
#if !defined(__WINS__) && !defined(__X86__)
			for(TInt tries=10; tries>0; --tries)
				{
				TInt chunkSize = ChunkSize(chunkNumber);
				offset = Random(chunkSize);
				MoveLdd.TryMovingUserPage((TAny*)(chunk.Base()+offset), ETrue);
				// Allow the move to fail for any reason as the page of the chunk
				// may or may not be currently committed, pinned, or accessed.
				}
#endif
			}
			break;
		default:
			test(false); // can't happen
			break;
			}
		}
	}



TInt E32Main()
	{
	// get system info...
	MemModelAttributes = UserSvr::HalFunction(EHalGroupKernel, EKernelHalMemModelInfo, NULL, NULL);
	MemModel = MemModelAttributes&EMemModelTypeMask;
	UserHal::PageSizeInBytes(PageSize);
	PageMask = PageSize-1;

	// see if we are a slave process...
	if(User::GetTIntParameter(1,SlaveNumber)==KErrNone)
		{
		// do testing...
		SlaveInit();
		DoTest();
		return KErrGeneral; // shouldn't have returned from testing
		}

	// master process...
	TBool pass = true; // final test result
	test.Title();
	if((MemModelAttributes&EMemModelAttrVA)==false)
		{
		test.Start(_L("TESTS NOT RUN - Not relevent for the memory model"));
		test.End();
		return KErrNone;
		}

	// get time to run tests for...
	TInt timeout = 10; // time in seconds
	TInt cmdLineLen = User::CommandLineLength();
	if(cmdLineLen)
		{
		// get timeout value from command line
		RBuf cmdLine;
		test_KErrNone(cmdLine.Create(cmdLineLen));
		User::CommandLine(cmdLine);
		test_KErrNone(TLex(cmdLine).Val(timeout));
		if(timeout==0)
			timeout = KMaxTInt;
		}
	TTimeIntervalMicroSeconds32 tickTime;
	test_KErrNone(UserHal::TickPeriod(tickTime));
	TInt ticksPerSecond = 1000000/tickTime.Int();
	TInt timeoutTicks;
	if(timeout<KMaxTInt/ticksPerSecond)
		timeoutTicks = timeout*ticksPerSecond;
	else
		{
		timeoutTicks = KMaxTInt;
		timeout = timeoutTicks/ticksPerSecond;
		}

	// master process runs at higher priority than slaves so it can timeout and kill them...
	RThread().SetPriority(EPriorityMore);

	test.Start(_L("Creating test chunks"));
	TUint i;
	for(i=0; i<KNumTestChunks; i++)
		{
		test.Printf(_L("Size %dkB\r\n"),ChunkSize(i)>>10);
		test_KErrNone(Chunks[i].CreateDisconnectedGlobal(ChunkName(i),0,0,ChunkSize(i)));
		}

	test.Next(_L("Spawning slave processes"));
	test_KErrNone(StartSemaphore.CreateGlobal(KNullDesC,0));
	TFileName processFile(RProcess().FileName());
	for(i=0; i<KNumSlaveProcesses; i++)
		{
		test.Printf(_L("Slave %d\r\n"),i);
		RProcess& slave = Slaves[i];
		test_KErrNone(slave.Create(processFile,KNullDesC));
		test_KErrNone(slave.SetParameter(1,i));
		test_KErrNone(slave.SetParameter(2,StartSemaphore));
		slave.Logon(SlaveLogons[i]);
		test_Equal(KRequestPending,SlaveLogons[i].Int());
		slave.Rendezvous(SlaveRendezvous[i]);
		test_Equal(KRequestPending,SlaveRendezvous[i].Int());
		}

	test.Next(_L("Create timer"));
	RTimer timer;
	test_KErrNone(timer.CreateLocal());

	test.Next(_L("Resuming slave processes"));
	for(i=0; i<KNumSlaveProcesses; i++)
		Slaves[i].Resume();

	// this test must now take care not to die (e.g. panic due to assert fail)
	// until it has killed the slave processes

	test.Next(_L("Change paging cache size"));
	TUint cacheOriginalMin = 0;
	TUint cacheOriginalMax = 0;
	TUint cacheCurrentSize = 0;
	DPTest::CacheSize(cacheOriginalMin, cacheOriginalMax, cacheCurrentSize);
	DPTest::SetCacheSize(1, 2*ChunkSize(0)); // big enough for all the test chunks

	test.Next(_L("Wait for slaves to initialise"));
	TRequestStatus timeoutStatus;
	timer.After(timeoutStatus,10*1000000); // allow short time for slaves to initialise
	for(i=0; i<KNumSlaveProcesses; i++)
		{
		User::WaitForAnyRequest(); // wait for a rendexvous
		if(timeoutStatus.Int()!=KRequestPending)
			{
			test.Printf(_L("Timeout waiting for slaves to initialise\r\n"));
			pass = false;
			break;
			}
		}

	test.Next(_L("Restore paging cache size"));
	DPTest::SetCacheSize(cacheOriginalMin, cacheOriginalMax);

	if(pass)
		{
		timer.Cancel();
		User::WaitForAnyRequest(); // swallow timer signal

		test.Next(_L("Check slaves are ready"));
		for(i=0; i<KNumSlaveProcesses; i++)
			{
			if(SlaveRendezvous[i].Int()!=KErrNone || Slaves[i].ExitType()!=EExitPending)
				{
				test.Printf(_L("Slaves not ready or died!\r\n"));
				pass = false;
				break;
				}
			}
		}

	if(pass)
		{
		test.Next(_L("Setup simulated kernel heap failure"));
		__KHEAP_SETFAIL(RAllocator::EDeterministic,100);

		TBuf<80> text;
		text.Format(_L("Stressing for %d seconds..."),timeout);
		test.Next(text);
		timer.AfterTicks(timeoutStatus,timeoutTicks);
		StartSemaphore.Signal(KNumSlaveProcesses); // release slaves to start testing
		User::WaitForAnyRequest(); // wait for timeout or slave death via logon completion

		pass = timeoutStatus.Int()==KErrNone; // timeout means slaves are still running OK

		test.Next(_L("Check slaves still running"));
		for(i=0; i<KNumSlaveProcesses; i++)
			if(Slaves[i].ExitType()!=EExitPending)
				pass = false;

		test.Next(_L("Clear kernel heap failure"));
		TUint kheapFails = __KHEAP_CHECKFAILURE;
		__KHEAP_RESET;
		test.Printf(_L("Number of simulated memory failures = %d\r\n"),kheapFails);
		}

	test.Next(_L("Killing slave processes"));
	for(i=0; i<KNumSlaveProcesses; i++)
		Slaves[i].Kill(0);

	test.Next(_L("Assert test passed"));
	test(pass);

	test.End();

	for(i=0; i<KNumSlaveProcesses; i++)
		Slaves[i].Close();
	for(i=0; i<KNumTestChunks; i++)
		Chunks[i].Close();
	timer.Close();
	for(i=0; i<KNumSlaveProcesses; i++)
		User::WaitForRequest(SlaveLogons[i]);

	UserSvr::HalFunction(EHalGroupKernel, EKernelHalSupervisorBarrier, (TAny*)5000, 0);

	return KErrNone;
	}
