// Copyright (c) 2005-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\d_demandpaging.cpp
// 
//

#include <kernel/kern_priv.h>
#include <kernel/cache.h>
#include "d_demandpaging.h"

/// Page attributes, cut-n-paste'd from mmubase.h
enum TType
	{
//	EInvalid=0,			// No physical RAM exists for this page
//	EFixed=1,			// RAM fixed at boot time
//	EUnused=2,			// Page is unused
//	EChunk=3,
//	ECodeSeg=4,
//	EHwChunk=5,
//	EPageTable=6,
//	EPageDir=7,
//	EPtInfo=8,
//	EShadow=9,

	EPagedROM=10,
	EPagedCode=11,
	EPagedData=12,
	EPagedCache=13,
	EPagedFree=14,
	};

enum TState
	{
	EStateNormal = 0,		// no special state
	EStatePagedYoung = 1,
	EStatePagedOld = 2,
	EStatePagedDead = 3,
	EStatePagedLocked = 4
	};

//
// Class definitions
//

class DDemandPagingTestFactory : public DLogicalDevice
	{
public:
	~DDemandPagingTestFactory();
	virtual TInt Install();
	virtual void GetCaps(TDes8& aDes) const;
	virtual TInt Create(DLogicalChannelBase*& aChannel);
	};

class DDemandPagingTestChannel : public DLogicalChannelBase
	{
public:
	DDemandPagingTestChannel();
	~DDemandPagingTestChannel();
	virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion& aVer);
	virtual TInt Request(TInt aFunction, TAny* a1, TAny* a2);
	TInt LockTest(const TAny* aBuffer, TInt aSize);
	TInt LockTest2();
	TInt DoConsumeContiguousRamTest(TInt aAlign, TInt aPages);
	TInt DoCreatePlatHwChunk(TInt aSize, TAny* aLinAddr);
	TInt DoDestroyPlatHwChunk();
	TInt ReadHoldingMutexTest(TAny* aDest);

	TBool CheckPagedIn(TLinAddr aAddress);
	TBool CheckPagedOut(TLinAddr aAddress);
	TBool CheckLocked(TLinAddr aAddress);

	TInt FreeRam();
public:
	DDemandPagingTestFactory*	iFactory;
	DDemandPagingLock iLock;

	DPlatChunkHw* iHwChunk;
    TInt iChunkSize;
	TPhysAddr iPhysBase;		// This will be base physical address of the chunk
    TLinAddr iLinearBase;		// This will be base linear address of the chunk
	};

//
// DDemandPagingTestFactory
//

TInt DDemandPagingTestFactory::Install()
	{
	return SetName(&KDemandPagingTestLddName);
	}

DDemandPagingTestFactory::~DDemandPagingTestFactory()
	{
	}

void DDemandPagingTestFactory::GetCaps(TDes8& /*aDes*/) const
	{
	// Not used but required as DLogicalDevice::GetCaps is pure virtual
	}

TInt DDemandPagingTestFactory::Create(DLogicalChannelBase*& aChannel)
	{
	aChannel = NULL;
	DDemandPagingTestChannel* channel=new DDemandPagingTestChannel;
	if(!channel)
		return KErrNoMemory;
	channel->iFactory = this;
	aChannel = channel;
	return KErrNone;
	}

DECLARE_STANDARD_LDD()
	{
	return new DDemandPagingTestFactory;
	}

//
// DDemandPagingTestChannel
//

TInt DDemandPagingTestChannel::DoCreate(TInt /*aUnit*/, const TDesC8* /*aInfo*/, const TVersion& /*aVer*/)
	{
	return KErrNone;
	}

DDemandPagingTestChannel::DDemandPagingTestChannel()
	{
	}

DDemandPagingTestChannel::~DDemandPagingTestChannel()
	{
	DoDestroyPlatHwChunk();
	}

TInt DDemandPagingTestChannel::Request(TInt aFunction, TAny* a1, TAny* a2)
	{
	switch(aFunction)
		{
	case RDemandPagingTestLdd::ELockTest:
		{
		TInt r = LockTest(a1,(TInt)a2);
		if (r == KErrNone)
			r = LockTest2();
		return r;
		}

	case RDemandPagingTestLdd::ESetRealtimeTrace:
		{
#if defined(_DEBUG)
		TUint32 bit = TUint32(1<<(KREALTIME&31));
		__e32_atomic_axo_ord32(&Kern::SuperPage().iDebugMask[KREALTIME>>5], ~bit, a1?bit:0);
#if 0 // can enable this to help debugging
		bit = (1<<(KPAGING&31));
		__e32_atomic_axo_ord32(&Kern::SuperPage().iDebugMask[KPAGING>>5], ~bit, a1?bit:0);
#endif
#endif //_DEBUG
		}
		return KErrNone;

	case RDemandPagingTestLdd::EDoConsumeContiguousRamTest:
		{
		return DDemandPagingTestChannel::DoConsumeContiguousRamTest((TInt)a1, (TInt)a2);
		}

	case RDemandPagingTestLdd::ECreatePlatHwChunk:
		{
		return DDemandPagingTestChannel::DoCreatePlatHwChunk((TInt)a1, a2);
		}

	case RDemandPagingTestLdd::EDestroyPlatHwChunk:
		{
		return DDemandPagingTestChannel::DoDestroyPlatHwChunk();
		}

	case RDemandPagingTestLdd::ELock:
		{
		TInt r=iLock.Alloc((TInt)a2);
		if(r!=KErrNone)
			return r;
		return iLock.Lock(&Kern::CurrentThread(),(TLinAddr)a1,(TInt)a2);
		}

	case RDemandPagingTestLdd::EUnlock:
		{
		iLock.Free();
		return KErrNone;
		}

	case RDemandPagingTestLdd::EReadHoldingMutexTest:
		return ReadHoldingMutexTest((TAny*)a1);

	default:
		return KErrNotSupported;
		}
	}

// 
// DDemandPagingTestChannel::DoCreatePlatHwChunk
//
// For some of the tests of IPC from demand-paged memory, we need a writable
// globally-mapped buffer; so this function creates a suitable chunk and
// returns its (global, virtual) address to the userland caller.  The caller
// should call DoDestroyPlatHwChunk() to release the memory when the tests
// are finished.
//
TInt DDemandPagingTestChannel::DoCreatePlatHwChunk(TInt aSize, TAny* aLinAddr)
	{
	TInt mapAttr = EMapAttrUserRw;		// Supervisor and user both have read/write permissions

	NKern::ThreadEnterCS();
	if (iHwChunk)						// Only one chunk at a atime
		{
		NKern::ThreadLeaveCS();
		return KErrAlreadyExists;
		}

	iChunkSize = Kern::RoundToPageSize(aSize);

	Kern::Printf("*** Attempting to allocate contiguous physical RAM ***");
	TInt free = Kern::FreeRamInBytes();
	Kern::Printf("      requested:  %08x", iChunkSize);
	Kern::Printf("      total free: %08x", free);
	
	TInt r = Epoc::AllocPhysicalRam(iChunkSize, iPhysBase, 0);	// Allocate RAM; result in iPhysBase
	if (r)
		{
		NKern::ThreadLeaveCS();
		Kern::Printf("      failed with error %d", r);
		return r;
		}
	else
		Kern::Printf("      success");

	r = DPlatChunkHw::New(iHwChunk, iPhysBase, iChunkSize, mapAttr);	// Create chunk
	if (r)
		{
		Epoc::FreePhysicalRam(iPhysBase, iChunkSize);
		iHwChunk = 0;
		NKern::ThreadLeaveCS();
		return r;
		}
	NKern::ThreadLeaveCS();

	// Return the virtual address to userland
	iLinearBase = iHwChunk->LinearAddress();
	kumemput(aLinAddr, &iLinearBase, sizeof(iLinearBase));

	Kern::Printf("CreatePlatHwChunk@%08x: iLinearBase %08x, iPhysBase %08x, size %d",
		iHwChunk, iLinearBase, iPhysBase, iChunkSize);

	return KErrNone;
	}

TInt DDemandPagingTestChannel::DoDestroyPlatHwChunk()
	{
	Kern::Printf("DestroyPlatHwChunk@%08x: iLinearBase %08x, iPhysBase %08x, size %d",
		iHwChunk, iLinearBase, iPhysBase, iChunkSize);
	NKern::ThreadEnterCS();
	if (iHwChunk)
		{
		iHwChunk->Close(NULL);
		Epoc::FreePhysicalRam(iPhysBase, iChunkSize);
		iPhysBase = 0;
		iChunkSize = 0;
		iHwChunk = 0;
		}
	NKern::ThreadLeaveCS();
	return KErrNone;
	}

// 
// DDemandPagingTestChannel::DoConsumeContiguousRamTest
//
// This test attempts to consume all available Contiguous Ram until we need to ask the 
// demand paging code to release memory for it.
// 
// On completion free all the memory allocated.
//
#define CHECK(c) { if(!(c)) { Kern::Printf("Fail  %d", __LINE__); ; retVal = __LINE__;} }

TInt DDemandPagingTestChannel::DoConsumeContiguousRamTest(TInt aAlign, TInt aSize)
	{
	TInt retVal = KErrNone;
	TInt initialFreeRam = FreeRam();
	TInt totalBlocks = initialFreeRam/aSize;

	NKern::ThreadEnterCS();
	TPhysAddr*	 pAddrArray = (TPhysAddr *)Kern::Alloc(sizeof(TPhysAddr) * totalBlocks);
	NKern::ThreadLeaveCS();
	CHECK(pAddrArray);
	if(!pAddrArray)
		return retVal;
	
	SVMCacheInfo tempPages;

	// get the initial free ram again as the heap may have grabbed a page during the alloc
	initialFreeRam = FreeRam();
	Kern::Printf("ConsumeContiguousRamTest: align %d size %d initialFreeRam %d", aAlign, aSize, initialFreeRam);

	CHECK(Kern::HalFunction(EHalGroupVM,EVMHalGetCacheSize,&tempPages,0) == KErrNone);
	Kern::Printf("Start cache info: iMinSize %d iMaxSize %d iCurrentSize %d iMaxFreeSize %d",
				 tempPages.iMinSize, tempPages.iMaxSize, tempPages.iCurrentSize ,tempPages.iMaxFreeSize);

	TInt initialFreePages = tempPages.iMaxFreeSize;
	CHECK(initialFreePages != 0);
	
	// allocate blocks to use up RAM until we fail to allocate any further...
	TBool freedPagesToAlloc = EFalse;
	TInt index;
	TUint32 alignMask = (1 << aAlign) - 1;
	for (index = 0; index < totalBlocks; )
		{
		CHECK(Kern::HalFunction(EHalGroupVM,EVMHalGetCacheSize,&tempPages,0) == KErrNone);
		TInt beforePages = tempPages.iMaxFreeSize;

		NKern::ThreadEnterCS();
		TInt r = Epoc::AllocPhysicalRam(aSize, pAddrArray[index], aAlign);
		if(r==KErrNone)
			{
			// check the alignment of the returned pages
			CHECK((pAddrArray[index] & alignMask) == 0);
			++index;
			}
		NKern::ThreadLeaveCS();
		if(r!=KErrNone)
			{
			break;
			}
		CHECK(Kern::HalFunction(EHalGroupVM,EVMHalGetCacheSize,&tempPages,0) == KErrNone);
		TInt afterPages = tempPages.iMaxFreeSize;

		if (afterPages != beforePages)
			freedPagesToAlloc = ETrue; // the alloc reclaimed memory from the paging cache
		}

	if (!index)
		Kern::Printf("WARNING : DoConsumeContiguousRamTest no allocations were successful");
	// free the memory we allocated...
	while(--index>=0)
		{
		NKern::ThreadEnterCS();
		TInt r = Epoc::FreePhysicalRam(pAddrArray[index], aSize);
		NKern::ThreadLeaveCS();
		CHECK(r==KErrNone);
		}

	CHECK(FreeRam() == initialFreeRam);

	NKern::ThreadEnterCS();
	Kern::Free(pAddrArray);
	NKern::ThreadLeaveCS();

	CHECK(Kern::HalFunction(EHalGroupVM,EVMHalGetCacheSize,&tempPages,0) == KErrNone);
	Kern::Printf("End cache info: iMinSize %d iMaxSize %d iCurrentSize %d iMaxFreeSize %d",
				 tempPages.iMinSize, tempPages.iMaxSize, tempPages.iCurrentSize ,tempPages.iMaxFreeSize);

	if (!freedPagesToAlloc)
		Kern::Printf("WARNING : DoConsumeContiguousRamTest freedPagesToAlloc was eFalse");
	//CHECK(freedPagesToAlloc);		

	return retVal;
	}
#undef CHECK


TUint8 ReadByte(volatile TUint8* aPtr)
	{
	return *aPtr;
	}

#define CHECK(c) { if(!(c)) return __LINE__; }

#define READ(a) ReadByte((volatile TUint8*)(a))

TInt DDemandPagingTestChannel::LockTest(const TAny* aBuffer, TInt aSize)
	{
	// Get page size info
	TInt pageSize = 0;
	CHECK(Kern::HalFunction(EHalGroupKernel,EKernelHalPageSizeInBytes,&pageSize,0)==KErrNone);
	TInt pageMask = pageSize-1;

	// See if were running of the Flexible Memory Model
  	TUint32 memModelAttrib = (TUint32)Kern::HalFunction(EHalGroupKernel,EKernelHalMemModelInfo,0,0);	
	TBool fmm = (memModelAttrib&EMemModelTypeMask)==EMemModelTypeFlexible;

	// Round buffer to page boundaries
	TLinAddr start = ((TLinAddr)aBuffer+pageMask)&~pageMask;
	TLinAddr end = ((TLinAddr)aBuffer+aSize)&~pageMask;
	aSize = end-start;
	Kern::Printf("Test buffer is %08x, %x\n",start,aSize);
	CHECK(aSize>pageSize*2);

	// Flush all paged memory
	Kern::HalFunction(EHalGroupVM,EVMHalFlushCache,0,0);

	TInt initialFreeRam;
	TInt freeRam1;
	TInt freeRam2;
	TLinAddr addr;
	TUint lockBytesUsed = fmm ? 0 : 0; // free ram change on locking (zero or aSize depending on implementation)

	{ // this brace is essential for correctness
	DDemandPagingLock lock2; // construct a lock;

	Kern::Printf("Check reading from buffer pages it in\n");
	for(addr=start; addr<end; addr+=pageSize) READ(addr);
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckPagedIn(addr));
	initialFreeRam = FreeRam();

	Kern::Printf("Check Alloc reserves pages\n");
	CHECK(iLock.Alloc(aSize)==KErrNone);
	freeRam1 = FreeRam();

	Kern::Printf("Check flushing pages out the buffer\n");
	Kern::HalFunction(EHalGroupVM,EVMHalFlushCache,0,0);
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckPagedOut(addr));

	Kern::Printf("Check Lock\n");
	CHECK(iLock.Lock(&Kern::CurrentThread(),start,aSize));
	CHECK((TUint)FreeRam()==TUint(freeRam1-lockBytesUsed));
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckLocked(addr));

	Kern::Printf("Check flushing doesn't page out the buffer\n");
	Kern::HalFunction(EHalGroupVM,EVMHalFlushCache,0,0);
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckLocked(addr));
	CHECK((TUint)FreeRam()==TUint(freeRam1-lockBytesUsed));

	Kern::Printf("Check second Alloc\n");
	CHECK(lock2.Alloc(aSize)==KErrNone);
	freeRam2 = FreeRam();

	Kern::Printf("Check second Lock\n");
	CHECK(lock2.Lock(&Kern::CurrentThread(),start,aSize));
	CHECK(FreeRam()==freeRam2);
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckLocked(addr));

	Kern::Printf("Check deleting second lock\n");
	// lock2 is deleted here because it goes out of scope...
	} // this brace is essential for correctness
	CHECK((TUint)FreeRam()==TUint(freeRam1-lockBytesUsed));
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckLocked(addr));

	Kern::Printf("Check Unlock\n");
	iLock.Unlock();
	CHECK(FreeRam()==freeRam1);
	for(addr=start; addr<end; addr+=pageSize) CHECK(CheckPagedIn(addr));
	iLock.Unlock();
	CHECK(FreeRam()==initialFreeRam);

	Kern::Printf("Check Free\n");
	iLock.Free();
	CHECK(FreeRam()==initialFreeRam);
	iLock.Free();
	CHECK(FreeRam()==initialFreeRam);

	return KErrNone;
	}

#undef CHECK
#define CHECK(c) { if(!(c)) { r = __LINE__; goto cleanup; } }

TInt DDemandPagingTestChannel::LockTest2()
	{
	Kern::Printf("Check allocating locks eventually increases size of live list\n");
	TInt r = KErrNone;

	DDemandPagingLock* lock = NULL;
	RPointerArray<DDemandPagingLock> lockArray;
	
	const TInt KLockMax = 1000; // make this a bit bigger than current min page count?
	TInt i;
	
	NKern::ThreadEnterCS();
	for (i = 0 ; i < KLockMax ; ++i)
		{
		lock = new DDemandPagingLock;
		CHECK(lock);
		CHECK(lockArray.Append(lock) == KErrNone);
		lock = NULL;

		TInt initialFreeRam = FreeRam();
		CHECK(lockArray[i]->Alloc(1) == KErrNone);
		if (FreeRam() < initialFreeRam)
			{
			Kern::Printf("Live list size increased after %d locks allocated", i + 1);
			break;
			}
		}

	CHECK(i < KLockMax);
	
cleanup:

	delete lock;
	lock = NULL;
	for (i = 0 ; i < lockArray.Count() ; ++i)
		{
		delete lockArray[i];
		lockArray[i] = NULL;
		}
	lockArray.Reset();

	NKern::ThreadLeaveCS();

	return r;
	}

TInt DDemandPagingTestChannel::FreeRam()
	{
	Kern::HalFunction(EHalGroupKernel, EKernelHalSupervisorBarrier, 0, 0);
	TInt freeRam = Kern::FreeRamInBytes();
	Kern::Printf("...free RAM: %x\n",freeRam);
	return freeRam;
	}


TUint32 PageState(TLinAddr aAddress)
	{
	TUint32 state = Kern::HalFunction(EHalGroupVM, EVMPageState, (TAny*)aAddress, 0);
	Kern::Printf("PageState: %08x=%08x",aAddress,state);
	return state;
	}


TBool DDemandPagingTestChannel::CheckPagedIn(TLinAddr aAddress)
	{
	TUint32 state = PageState(aAddress);
	return (state&0xff00) == (EStatePagedYoung<<8);
	}


TBool DDemandPagingTestChannel::CheckPagedOut(TLinAddr aAddress)
	{
	TUint32 state = PageState(aAddress);
	return (state&0xffff) == 0;
	}


TInt DDemandPagingTestChannel::CheckLocked(TLinAddr aAddress)
	{
	TUint32 state = PageState(aAddress);
	return (state&0xff00) == (EStatePagedLocked<<8);
	}


TInt DDemandPagingTestChannel::ReadHoldingMutexTest(TAny* aDest)
	{
	_LIT(KMutexName, "DPTestMutex");

	NKern::ThreadEnterCS();
	
	DMutex* mutex;
	TInt r = Kern::MutexCreate(mutex, KMutexName, KMutexOrdDebug);  // Mutex order < demand paging
	if (r != KErrNone)
		{
		NKern::ThreadLeaveCS();
		return r;
		}
	Kern::MutexWait(*mutex);
	
	const TRomHeader& romHeader = Epoc::RomHeader();
	TLinAddr unpagedRomStart = (TLinAddr)&romHeader;
	TLinAddr unpagedRomEnd;
	if (romHeader.iPageableRomStart)
		unpagedRomEnd = unpagedRomStart + romHeader.iPageableRomStart;
	else
		unpagedRomEnd = unpagedRomStart + romHeader.iUncompressedSize;
	
	const TInt length = 16;
	TUint8 localBuf[length];
	if(!aDest)
		aDest = localBuf;
	Kern::Printf("Local buffer at %08x", aDest);

	TAny* src1 = (TAny*)unpagedRomStart;
	TAny* src2 = (TAny*)(unpagedRomEnd - length);
	
	DThread* thread = &Kern::CurrentThread();

	Kern::Printf("Attempting to access %08x", src1);
	Kern::ThreadRawWrite(thread, aDest, src1, length);
	Kern::Printf("Attempting to access %08x", src2);
	Kern::ThreadRawWrite(thread, aDest, src2, length);

	TUint8 stackData[length];
	Kern::Printf("Attempting to access %08x", stackData);
	Kern::ThreadRawWrite(thread, aDest, stackData, length);
	
	TAny* heapData = Kern::Alloc(length);
	if (heapData)
		{
		Kern::Printf("Attempting to access %08x", heapData);
		Kern::ThreadRawWrite(thread, aDest, heapData, length);
		Kern::Free(heapData);
		}
	else
		r = KErrNoMemory;
	
	Kern::MutexSignal(*mutex);
	mutex->Close(NULL);
	
	NKern::ThreadLeaveCS();
	
	return r;  // a kernel fault indicates that the test failed
	}

