// Copyright (c) 1996-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:
// f32test\demandpaging\t_fragment.cpp
// This test exercises the fragmentation of write requests carried out 
// by the Local Media subsystem, when the request is for a partition 
// driven by a Media driver that supports paging.
// 002 Check if LFFS drive (Mount LFFS if required)
// 003 Testing Fragmentation of writes to writable drives in paging media
// 004 Testing concurrent Fragmentation of writes on the same media
// 005 Check Disk
// 
//

//! @SYMTestCaseID			KBASE-T_FRAGMENTDP-0333
//! @SYMTestType			UT
//! @SYMPREQ				PREQ1110
//! @SYMTestCaseDesc		Demand Paging Page cache fragmentation tests.
//! @SYMTestActions			001 Starting tests...
//! @SYMTestExpectedResults All tests should pass.
//! @SYMTestPriority        High
//! @SYMTestStatus          Implemented

#include <f32file.h>
#include <e32test.h>
#include "t_server.h"
#include <u32hal.h>
#include <e32rom.h>
#include <f32dbg.h>
#include "testdefs.h"

#ifdef __VC32__
    // Solve compilation problem caused by non-English locale
    #pragma setlocale("english")
#endif

const TInt KMuliplySize=10;
const TInt KFileSizeInBytes=302498;

LOCAL_D TBuf8<KMuliplySize*KFileSizeInBytes> Buffer;
LOCAL_D RSemaphore WriteSemaphore;

GLDEF_D RTest test(_L("T_FRAGMENTDP"));

void DoTestF(TInt aDrvNum, TBool aNand);	// may want to do something weird on NAND later (e.g. trigger Garbage Collection)
void DoTestC(TInt aDrvNum, TInt aNotherDrvNum, TBool aNand);	// may want to do something weird on NAND later (e.g. trigger Garbage Collection)
TInt CreateEmptyFile(RFs& aFs, const TDesC& aFileName, TUint aFileSize);
TInt GetLocDrvNumber(TInt aDrvNo);

/*
  This plain looking test exercises the fragmentation of write requests carried out by the Local
  Media subsystem, when the request is for a partition driven by a Media driver that supports
  paging.
  It indirectly tests that the ELOCD fragmentation and EKERN locking mechanisms work as specified 
  to prevent deadlocks. It also causes an awful lot of paging activity.
*/

LOCAL_C TBool TestSimpleFragmentation()
//
// Find ROM address of file and write from it to another file in writable partition in the same media as the backing store for ROM
//

	{
	TDriveInfo driveInfo;
	TBool tested=EFalse;

	TFileName path;
	TInt r=TheFs.SessionPath(path);
	test(r==KErrNone);
	TInt drv;
	r=RFs::CharToDrive(path[0],drv);
	test(r==KErrNone);

    test(TheFs.Drive(driveInfo, drv) == KErrNone);

	//-- select a suitable drive for the testing. It shall be a writable drive on a media that services paging
	if(driveInfo.iMediaAtt&KMediaAttPageable)
		{
		TBool readOnly = driveInfo.iMediaAtt & KMediaAttWriteProtected;		// skip ROFS partitions
		if(!readOnly)
			{
			DoTestF(drv, (driveInfo.iType==EMediaNANDFlash)?(TBool)ETrue:(TBool)EFalse);
			tested=ETrue;
			}
		}
	if(!tested)
		test.Printf(_L("Skipped T_FRAGMENTDP on drive %c\n"), path[0]);
	return tested;
	}


void DoTestF(TInt aDrvNum, TBool aNand)
	{
	TInt pos=0;
	TInt size, size1;
	TInt r;
	TFileName fileName;

	test.Next(_L("Testing Fragmentation of writes to writable drives in paging media"));
	if(aNand)
		test.Printf(_L("Testing on NAND\n"));

    fileName.Format(_L("Testing drive %c:\n"), 'A'+aDrvNum);
    test.Printf(fileName);

	_LIT(KTPagedCpp, "Z:\\test\\TEST_PAGED.cpp");
	_LIT(KTUnpagedCpp, "Z:\\test\\TEST_UNPAGED.CPP");

	if(TheFs.IsFileInRom(KTPagedCpp) != NULL && TheFs.IsFileInRom(KTUnpagedCpp) != NULL)	// .oby must include these files
		{
		RFile f;
		r=f.Open(TheFs,KTUnpagedCpp,EFileStream);
		test(r==KErrNone);
		r=f.Seek(ESeekAddress,pos);
		test(r==KErrNone);
		TText8* ptrPos=*(TText8**)&pos;			// start address of unpaged file in ROM
		test.Printf(_L("Start address of section to copy 0x%x\n"), ptrPos);

		r=f.Size(size);							// size of unpaged file
		test(r==KErrNone);
		size+=((~(size&0xf)&0xf)+1);			// adjust for ROM alignement (EABI, 8 bytes)
		f.Close();

		r=f.Open(TheFs,KTPagedCpp,EFileStream);
		test(r==KErrNone);
		r=f.Size(size1);							// size of paged file
		test(r==KErrNone);
		size1+=((~(size1&0xf)&0xf)+1);			// adjust for ROM alignement (EABI, 8 bytes)
		f.Close();

		size+=size1;
		test.Printf(_L("Set descriptor with size %d (paged+unpaged sections+ROMFS padding)\n"), size);
		TPtrC8 ptr(ptrPos,size);

		fileName.Format(_L("%c:\\TestFragFile.bin"), aDrvNum+'A');
		TheFs.Delete(fileName); //-- just in case

		test.Printf(_L("Create and open destination file\n"));
		r = CreateEmptyFile(TheFs, fileName, (size));		// create file to hold both sizes
		test(r == KErrNone);
		r=f.Open(TheFs,fileName,EFileRead|EFileWrite);
		test(r == KErrNone);

		test.Printf(_L("Attempt to flush paged section\n"));
		TInt r=UserSvr::HalFunction(EHalGroupVM,EVMHalFlushCache,0,0);
		if(r==KErrNotSupported)
			test.Printf(_L("Not Supported\n"));

		test.Printf(_L("Write paged and unpaged sections, synchronoulsy\n"));
		r=f.Write(ptr);
		test(r==KErrNone);

		test.Printf(_L("Read back and compare\n"));
		pos=0;
		r=f.Seek(ESeekStart,pos);
		test(r==KErrNone);
		TUint end=(TUint)ptrPos+(size);
		TBuf8<1024> readBuf;
		TPtrC8 memBuf(ptrPos,1024);

		while((TUint)ptrPos+1024<end)
			{
			r=f.Read(readBuf);
			test(r==KErrNone);
			test(readBuf.Length()==readBuf.MaxLength());
			if(memBuf!=readBuf)
				{
				test.Printf(_L("Failed on descriptor starting at address %x\n"), ptrPos);
				test(0);
				}
			ptrPos+=1024;
			memBuf.Set(ptrPos,1024);
			}
		r=f.Read(readBuf);
		test(r==KErrNone);
		test(readBuf.Length()==(TInt)(end-(TUint)ptrPos));
		memBuf.Set(ptrPos,(end-(TUint)ptrPos));
		if(memBuf!=readBuf)
			{
			test.Printf(_L("Failed on descriptor starting at address %x\n"), ptrPos);
			test(0);
			}
		f.Close();
		}
	else
		{
		test.Printf(_L("Required test files not present\n"));
		test(0);
		}
	}

#if defined(_DEBUG) || defined(_DEBUG_RELEASE)
LOCAL_C TInt ConcurrThread(TAny* aArg);
/*
  This equally unimpressive looking test further exercises the fragmentation of write requests 
  carried out by the Local Media subsystem. This time write requests where the request source is
  in paged out ROM are issued concurrently.
  By having concurrent writes it indirectly tests both page in and fragment deferral mechaninsms.
*/

LOCAL_C void TestConcurrentFragmentation()
	{
	// concurrently write from paged out ROM addresses to either files in separate writebla partitions or different locations in the same partition
	TDriveList driveList;
	TDriveInfo driveInfo;
	TBool concurr=EFalse;

	TFileName path;
	TInt r=TheFs.SessionPath(path);
	test(r==KErrNone);
	TInt drvNum;
	r=RFs::CharToDrive(path[0],drvNum);
	test(r==KErrNone);

	r=TheFs.DriveList(driveList);
    test(r == KErrNone);

	test(TheFs.Drive(driveInfo, drvNum) == KErrNone);

	//-- select suitable drives for the testing. They shall be writable drives on a media that services paging
	if((driveInfo.iMediaAtt&KMediaAttPageable) && !(driveInfo.iMediaAtt&KMediaAttWriteProtected))
		{
		for (TInt drvNum1=drvNum+1; drvNum1<KMaxDrives; drvNum1++)	// if yes search for more drives suitable for concurrent fragmentation
			{
			if(!driveList[drvNum1])
				continue;   //-- skip unexisting drive

			TDriveInfo driveInfo2;	// for second drive
			test(TheFs.Drive(driveInfo2, drvNum1) == KErrNone);
			if ((driveInfo2.iMediaAtt&KMediaAttPageable) && 
				!(driveInfo2.iMediaAtt&KMediaAttWriteProtected) &&
				(driveInfo.iType == driveInfo2.iType))
				{
				DoTestC(drvNum, drvNum1, (driveInfo.iType==EMediaNANDFlash)?(TBool)ETrue:(TBool)EFalse);		// test concurrent
				concurr=ETrue;
				}
			}
		}
	if(!concurr)
		test.Printf(_L("Skipped concurrent test\n"));
	}


void silentFormat(TInt driveNo) 
	{    
    TBuf<4> driveBuf=_L("?:\\");
    RFormat format;
    TInt    count;
    
	driveBuf[0] = (TText)(driveNo + 'A');
    
    TInt r = format.Open(TheFs, driveBuf, EHighDensity, count);
    test(r == KErrNone);
    
    while(count) 
		{
        r=format.Next(count);
        test(r == KErrNone);
		}
    
    format.Close();
	}


void DoTestC(TInt aDrvNum, TInt aNotherDrvNum, TBool aNand)
	{
	TInt pos=0;
	TInt size=0;
	TInt r;
	TRequestStatus logonStat;
	RThread concurrThread;
	TInt locDriveNumber;
	TBusLocalDrive drive;
	TLocalDriveCapsV4 driveCaps;
	SDeferStats stats;

	test.Next(_L("Testing concurrent Fragmentation of writes on the same media"));
	if(aNand)
		test.Printf(_L("Testing on NAND\n"));
	test.Printf(_L("Testing on writable drives %c and %c\n"), 'A'+aDrvNum,'A'+aNotherDrvNum);

	_LIT(KTPagedCpp, "Z:\\test\\TEST_PAGED.cpp");
	_LIT(KTPaged1Cpp, "Z:\\test\\TEST_PAGED1.cpp");

	if(TheFs.IsFileInRom(KTPagedCpp) != NULL && TheFs.IsFileInRom(KTPaged1Cpp) != NULL)	// .oby must include these files
		{
		RFile f;
		r=f.Open(TheFs,KTPagedCpp,EFileStream);		// source 1
		test(r==KErrNone);
		r=f.Seek(ESeekAddress,pos);
		test(r==KErrNone);
		TText8* ptrPos=*(TText8**)&pos;			// start address of paged file 1 in ROM
		test.Printf(_L("Main thread->Start address of paged out file 1 0x%x\n"), ptrPos);
		r=f.Size(size);							// size of paged file 1
		test(r==KErrNone);
		f.Close();

		TRomHeader* romHeader = (TRomHeader*)UserSvr::RomHeaderAddress();
		TUint fsize=Min(KMuliplySize*size,romHeader->iPageableRomSize);

		test.Printf(_L("Main thread->Set descriptor with size %d to point to paged out file 1 +...\n"), fsize);
		TPtrC8 ptr(ptrPos,fsize);

		Buffer.SetLength(fsize);
		TPtr8 readBuf(&Buffer[0],fsize,fsize);

		test.Printf(_L("Create and resume concurrent thread\n"));
		const TInt KHeapSize=0x2000;

		locDriveNumber = GetLocDrvNumber(aNotherDrvNum);
		TInt r = concurrThread.Create(_L("ConcurrentWriteThread"),ConcurrThread,KDefaultStackSize,KHeapSize,KHeapSize,(TAny*)locDriveNumber);
		test(r==KErrNone);
		concurrThread.Logon(logonStat);

		locDriveNumber = GetLocDrvNumber(aDrvNum);
		test.Printf(_L("Connect to local drive %d\n"),locDriveNumber);
		TBool changeFlag = EFalse;
		r = drive.Connect(locDriveNumber,changeFlag);
		TPckg<TLocalDriveCapsV4>	capsPack(driveCaps);
		drive.Caps(capsPack);
		test(r == KErrNone);

		test(WriteSemaphore.CreateLocal(0)==KErrNone);

		// try to ensure there is no other thread activity as this may prevent the 
		// large write from being pre-empted by the ConcurrentWriteThread
		test.Printf(_L("Waiting 2 secs for file server threads to quieten down...."));
		User::After(2000000);

		concurrThread.Resume();

		WriteSemaphore.Wait();
		WriteSemaphore.Signal();

		// long write...
//		test.Printf(_L("Starting file 1 write\n"));	
		r = drive.Write(0,ptr);
		test(r==KErrNone);

		test.Printf(_L("Main thread->Write 1 completed\n"));

		if(aNand)
			{
			test.Printf(_L("Read stats\n"));
			TPtr8 statsBuf((TUint8*) &stats, sizeof(stats));
	 		test(drive.ControlIO(KNandGetDeferStats, statsBuf, 0)==KErrNone);
			test.Printf(_L("Fragmentation clashes %d Fragmentation deferrals %d Page In deferrals %d Other deferrals %d\n"),stats.iClashFragmenting,  stats.iNormalFragmenting, stats.iPageOther, stats.iNormalOther);
			}

		test.Printf(_L("Read back file 1 and compare\n"));
		r = drive.Read(0,fsize,readBuf);
		test(r==KErrNone);
		test(ptr==readBuf);
		test.Printf(_L("Verify file 1 OK\n"));
		drive.Disconnect();

		WriteSemaphore.Signal();
		User::WaitForRequest(logonStat);
		test(logonStat==KErrNone);
		concurrThread.Close();
		WriteSemaphore.Close();

		silentFormat(aDrvNum);
		silentFormat(aNotherDrvNum);
		}
	else
		{
		test.Printf(_L("Required test files not present\n"));
		test(0);
		}
	}

	
GLDEF_D RFs TheFsT;
GLDEF_D RTest testT(_L("T_CONCURRENT_WRITE_THREAD"));

LOCAL_C TInt ConcurrThread(TAny* aArg)
	{
	// the whole test is dodgy and hangs if this thread fails an assert,
	// so at least make sure thread panic takes out whole test process...
	User::SetCritical(User::EProcessCritical);

	RFile f;
	TInt pos=0;
	TInt size=0;
	TInt locDriveNumber;
	TBusLocalDrive drive;
	TLocalDriveCapsV4 driveCaps;
	SDeferStats stats;
	RThread thisThread;

	TInt r = TheFsT.Connect();
	_LIT(KTPaged1Cpp, "Z:\\test\\TEST_PAGED1.cpp");
	r=f.Open(TheFsT,KTPaged1Cpp,EFileStream);		// source 2
	testT(r==KErrNone);
	r=f.Seek(ESeekAddress,pos);
	testT(r==KErrNone);
	TText8* ptrPos=*(TText8**)&pos;			// start address of paged file 2 in ROM
	testT.Printf(_L("ConcurrThread->Start address of paged out file 2 0x%x\n"), ptrPos);
	r=f.Size(size);							// size of paged file 2
	testT(r==KErrNone);
	f.Close();

	testT.Printf(_L("ConcurrThread->Set descriptor with size %d to point to paged out file 2\n"), size);
	TPtrC8 ptr(ptrPos,size);

	TPtr8 readBuf(&Buffer[0],size,size);

	locDriveNumber = (TInt)aArg;
	testT.Printf(_L("Connect to local drive %d\n"),locDriveNumber);
	TBool changeFlag = EFalse;
	r = drive.Connect(locDriveNumber,changeFlag);
	TPckg<TLocalDriveCapsV4>	capsPack(driveCaps);
	drive.Caps(capsPack);
	testT(r == KErrNone);

	if (driveCaps.iType == EMediaNANDFlash)
		{
		testT.Printf(_L("Zero stats\n"));
		TPtr8 statsBuf((TUint8*) &stats, sizeof(stats));
 		testT(drive.ControlIO(KNandGetDeferStats, statsBuf, 0)==KErrNone);
		}

	r=UserSvr::HalFunction(EHalGroupVM,EVMHalFlushCache,0,0);
	if(r==KErrNotSupported)
		testT.Printf(_L("ConcurrThread->Flushing of paging not Supported\n"));

	// pause one second to make sure main thread has executed WriteSemaphore.Wait();
	User::After(1000000);

	WriteSemaphore.Signal();
	WriteSemaphore.Wait();
	// up our priority
	thisThread.SetPriority(EPriorityMore);

	// wait a very short time to give the other thread a better chance to initiate the write
	User::After(1);	
//	testT.Printf(_L("Starting file 2 write\n"));

	// write
	r = drive.Write(0,ptr);
	testT(r==KErrNone);
	// read back
	r = drive.Read(0,size,readBuf);
	testT(r==KErrNone);
	// erase
	r=drive.Format(0,size);
	testT(r==KErrNone);

	testT.Printf(_L("ConcurrThread->Write of file 2 completed\n"));

	WriteSemaphore.Wait();
	testT.Printf(_L("Read back file 2 and compare\n"));
	testT(ptr==readBuf);
	testT.Printf(_L("Verify file 2 OK\n"));

	drive.Disconnect();
	TheFsT.Close();
	return KErrNone;
	}

#endif // #if defined(_DEBUG) || defined(_DEBUG_RELEASE)

//--------------------------------------------------------

/**
    Create an empty file of specified size.
    @param  aFs		    ref. to the FS
    @param  aFileName   name of the file
    @param  aFileSize   size of the file to be created
    @return    KErrNone on success, system-wide error code otherwise
*/
TInt CreateEmptyFile(RFs& aFs, const TDesC& aFileName, TUint aFileSize)
	{
    RFile   file;
	TInt    nRes;

	nRes = file.Create(aFs, aFileName, EFileWrite);
    if(nRes != KErrNone)
        return nRes;

	nRes = file.SetSize(aFileSize);
    if(nRes != KErrNone)
        return nRes;

    file.Close();

    return KErrNone;
	}

TInt GetLocDrvNumber(TInt aDrvNo)
	{
	test.Printf(_L("GetLocDrvNumber\r\n"));
	TInt locDriveNumber;
	RFile file;
	TBuf<256> fileName;	
	fileName.Append((TChar)('A'+aDrvNo));
	fileName+=_L(":\\f32-tst\\");
	TInt r=TheFs.MkDirAll(fileName);
	test(r==KErrNone || r== KErrAlreadyExists);
	fileName += _L("maggots.txt");
	r=file.Replace(TheFs,fileName,EFileWrite|EFileWriteDirectIO);
	if (r!=KErrNone)
		test.Printf(_L("Error %d: file '%S' could not be created\n"),r,&fileName);
	test(r==KErrNone);
	r=file.Write(_L8("Writhing bundles of maggots, this was truly their finest hour"));
	if (r!=KErrNone)
		test.Printf(_L("Error %d: could not write to file\n"),r);
	test(r==KErrNone);

	SBlockMapInfo info;
	TInt64 start=0;
	r=file.BlockMap(info,start, -1,ETestDebug);
	if (r!=KErrNone && r!=KErrCompletion)
		test.Printf(_L("Error %d: could not obtain block map\n"),r);
	test(r==KErrNone || r==KErrCompletion);
	locDriveNumber=info.iLocalDriveNumber;
	test.Printf(_L("From drive: %c to Local drive %d\r\n"), aDrvNo+'A',locDriveNumber);
	file.Close();
	return locDriveNumber;
	}

GLDEF_C void CallTestsL()
	{
	TRomHeader* romHeader = (TRomHeader*)UserSvr::RomHeaderAddress();
	if(!romHeader->iPageableRomStart)
		{
		test.Printf(_L("Test not supported (not a paged ROM)\n"));
		return; // Not a paged ROM, skip test
		}
	test.Title();

	TBool r=TestSimpleFragmentation();
	if(!r)
		return;
#if defined(_DEBUG) || defined(_DEBUG_RELEASE)
	TestConcurrentFragmentation();
#endif
	}
