// 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\smpsoak\t_smpsoak.cpp

//  User Includes
#include "t_smpsoak.h"

void ParseCommandLine ();

// Global Variables
static TInt gPageSize;
//Timeout 2 Minutes
static TUint gTimeout = 120;

//class for smpsoak thread and it creates memory, device, timer and spin threads.
class CSMPSoakThread
	{
public:
	CSMPSoakThread();
	~CSMPSoakThread();
	void CreateThread();
	void ResumeThread();
	void CreateChildProcess(TInt aIndex);
	void ResumeChildProcess();
	void TerminateChildProcess();	
private:
    //Thread Functions
	static TInt SMPStressMemoryThread(TAny*);
	static TInt SMPStressDeviceThread(TAny*);
	static TInt SMPStressTimerThread(TAny*);
	static TInt SMPStressSpinThread(TAny*);
	//Thread Priority
	void DoCreateThread(TAny*);
	void SetThreadPriority();
private:
    //Utils for memory thread
	void CreateChunk(TChunkInfo * aChunkInfo, TMemory * aMemoryTablePtr);
	void CommitChunk(TChunkInfo * aChunkInfo, TMemory * aMemoryTablePtr);
	void WriteReadChunk(TChunkInfo * aChunkInfo, TMemory * aMemoryTablePtr);
private:
    //Memebers for threads 
    TInt DoSMPStressMemoryThread();
    TInt DoSMPStressDeviceThread();
    TInt DoSMPStressTimerThread();
    TInt DoSMPStressSpinThread();
private:
    TThreadData iThreadData;
    RProcess    iProcess;
    RThread     iThread;
    TInt        iPriority;
private:
// Thread Data for each thread- low priority
static TThread KThreadTableLow[];
// Thread Data for each thread- high priority
static TThread KThreadTableHigh[];
//Process Data for each process
static const TProcess KProcessTable[];
//Memory table for memory thread
static const TMemory KMemoryTable[];
//Device table for device thread
static const TDesC* KDeviceTable[];

	};
TThread CSMPSoakThread::KThreadTableLow[] =
    {
        { _L("Memory Thread"), CSMPSoakThread::SMPStressMemoryThread, {{EPriorityAbsoluteLow, EPriorityAbsoluteVeryLow,   EPriorityNormal, 0}, EPriorityList, KCpuAffinityAny, 250, 1, (TAny *)&KMemoryTable, NULL, NULL}},
		{ _L("Device Thread"), CSMPSoakThread::SMPStressDeviceThread, {{EPriorityAbsoluteLow, EPriorityAbsoluteVeryLow, EPriorityNormal, 0}, EPriorityList, KCpuAffinityAny, 300, 1, &KDeviceTable, NULL, NULL}},
		{ _L("Spin Thread 0"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityAbsoluteVeryLow, EPriorityNormal,   EPriorityNormal, EPriorityNormal}, EPriorityList, KCpuAffinityAny, 200, 0, NULL, NULL, NULL}},
		{ _L("Spin Thread 1"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityNormal, EPriorityAbsoluteVeryLow,   EPriorityNormal, EPriorityNormal}, EPriorityList, KCpuAffinityAny, 300, 0, NULL, NULL, NULL}},
		{ _L("Spin Thread 2"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityNormal, EPriorityNormal,   EPriorityAbsoluteVeryLow, EPriorityNormal}, EPriorityList, KCpuAffinityAny, 400, 0, NULL, NULL, NULL}},
		{ _L("Spin Thread 3"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityNormal, EPriorityNormal,   EPriorityAbsoluteLow, EPriorityAbsoluteVeryLow}, EPriorityList, KCpuAffinityAny, 500, 0, NULL, NULL, NULL}},
		{ _L("Timer Thread"), CSMPSoakThread::SMPStressTimerThread, {{EPriorityNormal, 0, 0, 0}, EPriorityList, KCpuAffinityAny, 1000, 4, NULL}},
    };
TThread CSMPSoakThread::KThreadTableHigh[] =
 {
        { _L("Memory Thread"), CSMPSoakThread::SMPStressMemoryThread, {{EPriorityAbsoluteLow, EPriorityAbsoluteVeryLow,   EPriorityNormal, 0}, EPriorityList, KCpuAffinityAny, 250, 1, (TAny *)&KMemoryTable, NULL, NULL}},
        { _L("Device Thread"), CSMPSoakThread::SMPStressDeviceThread, {{EPriorityAbsoluteLow, EPriorityAbsoluteVeryLow, EPriorityNormal, 0}, EPriorityList, KCpuAffinityAny, 300, 1, &KDeviceTable, NULL, NULL}},
        { _L("Spin Thread 0"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityAbsoluteVeryLow, EPriorityNormal,   EPriorityNormal, EPriorityNormal}, EPriorityList, KCpuAffinityAny, 200, 0, NULL, NULL, NULL}},
        { _L("Spin Thread 1"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityNormal, EPriorityAbsoluteVeryLow,   EPriorityNormal, EPriorityNormal}, EPriorityList, KCpuAffinityAny, 300, 0, NULL, NULL, NULL}},
        { _L("Spin Thread 2"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityNormal, EPriorityNormal,   EPriorityAbsoluteVeryLow, EPriorityNormal}, EPriorityList, KCpuAffinityAny, 400, 0, NULL, NULL, NULL}},
        { _L("Spin Thread 3"), CSMPSoakThread::SMPStressSpinThread, {{EPriorityNormal, EPriorityNormal,   EPriorityAbsoluteLow, EPriorityAbsoluteVeryLow}, EPriorityList, KCpuAffinityAny, 500, 0, NULL, NULL, NULL}},
        { _L("Timer Thread"), CSMPSoakThread::SMPStressTimerThread, {{EPriorityNormal, 0, 0, 0}, EPriorityList, KCpuAffinityAny, 1000, 4, NULL}},
    };
const TProcess CSMPSoakThread::KProcessTable[] =
    {
        { _L("t_smpsoakprocess.exe"), _L("-W"), KCpuAffinityAny},
        { _L("t_smpsoakprocess.exe"), _L("-R"), KCpuAffinityAny},
        { _L("t_smpsoakprocess.exe"), _L("-F"), KCpuAffinityAny},
        { _L("t_smpsoakprocess.exe"), _L("-T"), KCpuAffinityAny},
        { _L("t_smpsoakprocess.exe"), _L("-O"), KCpuAffinityAny},
    };
const TMemory CSMPSoakThread::KMemoryTable[] =
    {
        {_L(""), EChunkNormalThread, 0, 10, 100 },
        {_L("Global Chunk 1"), EChunkNormalThread, 0, 20, 200 },
        {_L(""), EChunkDisconnectedThread, 3, 30, 300 },
        {_L("Global Chunk 2"), EChunkDisconnectedThread, 4, 40, 400 },
        {_L(""), EChunkDoubleEndedThread, 5, 50, 500 },
        {_L("Global Chunk 3"), EChunkDoubleEndedThread, 6, 60, 600 },
        {_L(""), EChunkNormalProcess, 0, 10, 100 },
        {_L("Global Chunk 4"), EChunkNormalProcess, 0, 20, 200 },
        {_L(""), EChunkDisconnectedProcess, 3, 30, 300 },
        {_L("Global Chunk 5"), EChunkDisconnectedProcess, 4, 40, 400 },
        {_L(""), EChunkDoubleEndedProcess, 5, 50, 500 },
        {_L("Global Chunk 6"), EChunkDoubleEndedProcess, 6, 60, 600 },
        {_L(""), EChunkNone, 0, 0, 0 },
    };
const TDesC* CSMPSoakThread::KDeviceTable[] =
    {
    &KDevices, &KDevLdd1, &KDevLdd1Name, &KDevLdd2, &KDevLdd2Name, &KDevLdd3, &KDevLdd3Name,
    &KDevLdd4, &KDevLdd4Name, &KDevLdd5, &KDevLdd5Name, NULL
    };

//Constructor
CSMPSoakThread::CSMPSoakThread()
    { 
    }
//Destructor
CSMPSoakThread::~CSMPSoakThread()
    {    
    }
//All child process creation
void CSMPSoakThread::CreateChildProcess(TInt aIndex)
    {
    if(TestSilent)  
            gCmdLine.Format(KCmdLineBackground,(KProcessTable[aIndex].operation).Ptr());
    else if (Period)
        gCmdLine.Format(KCmdLinePeriod,gPeriod,(KProcessTable[aIndex].operation).Ptr());
    else
        gCmdLine.Format(KCmdLineProcess,(KProcessTable[aIndex].operation).Ptr());
    
    TInt r = iProcess.Create(KProcessTable[aIndex].processFileName,gCmdLine);
    test_KErrNone(r);
    iProcess.SetPriority(EPriorityLow);
    gSMPStressDrv.ChangeThreadAffinity(&iThread, KProcessTable[aIndex].cpuAffinity);
    PRINT ((_L("SetProcessPriority  CPU %d Priority %d\n"),gSMPStressDrv.GetThreadCPU(&iThread), iProcess.Priority()));
    }
//Terminate process when user press "Esc key"
void CSMPSoakThread::ResumeChildProcess()
    {
    iProcess.Resume();
    }
//Terminate process when user press "Esc key"
void CSMPSoakThread::TerminateChildProcess()
    {
    iProcess.Kill(KErrNone);
    }
//Changes the thread priority each time time, for each thread by Random, Increment, from List, Fixed.
//pick up the priority option from thread table
void CSMPSoakThread::SetThreadPriority()
    {
    static TInt64 randSeed = KRandSeed;
    static const TThreadPriority priorityTable[]=
        {
        EPriorityMuchLess, EPriorityLess, EPriorityNormal, EPriorityMore, EPriorityMuchMore,
        EPriorityRealTime, EPriorityRealTime, EPriorityAbsoluteVeryLow, EPriorityAbsoluteLowNormal,
        EPriorityAbsoluteLow, EPriorityAbsoluteBackgroundNormal, EPriorityAbsoluteBackground,
        EPriorityAbsoluteForegroundNormal, EPriorityAbsoluteForeground, EPriorityAbsoluteHighNormal, EPriorityAbsoluteHigh
        };
    TInt priorityIndex = 0;
    switch (iThreadData.threadPriorityChange)
        {
        case EpriorityFixed:
            break;

        case EPriorityList:
            if (++iPriority >= KPriorityOrder)
                iPriority = 0;
            if (iThreadData.threadPriorities[iPriority] == 0)
                iPriority = 0;
          //  PRINT(_L("SetPriority List CPU %d index %d priority %d\n"),gSMPStressDrv.GetThreadCPU(&iThread),iPriority, iThreadData.threadPriorities[iPriority]);
            iThread.SetPriority((TThreadPriority)iThreadData.threadPriorities[iPriority]);
            break;

        case EPriorityIncrement:
            while (priorityTable[priorityIndex] <= iPriority)
                {
                priorityIndex++;
                }
            iPriority = priorityTable[priorityIndex];
            if (iPriority > iThreadData.threadPriorities[2])
                iPriority = iThreadData.threadPriorities[1];
          //  PRINT(_L("SetPriority Increment CPU %d iPriority %d\n"),gSMPStressDrv.GetThreadCPU(&iThread), iPriority);
            iThread.SetPriority((TThreadPriority)iPriority);
            break;

        case EPriorityRandom:
            iPriority = Math::Rand(randSeed) % (iThreadData.threadPriorities[2] - iThreadData.threadPriorities[1] + 1);
            iPriority += iThreadData.threadPriorities[1];
            while (priorityTable[priorityIndex] < iPriority)
                {
                priorityIndex++;
                }
            iPriority = priorityTable[priorityIndex];
         //   PRINT(_L("SetPriority Random CPU %d priority %d\n"),gSMPStressDrv.GetThreadCPU(&iThread), iPriority);
            iThread.SetPriority((TThreadPriority)iPriority);
            break;
        }
    }
//Resume each thread
void CSMPSoakThread::ResumeThread()
    {
    iThread.Resume();
    }
//thread creation
void CSMPSoakThread::CreateThread()
    {
    CSMPSoakThread* smpthread = new CSMPSoakThread[KNumThreads];
    for (TInt i = 0; i < KNumThreads ; i++)
        {
        if(ThreadPriorityLow)
            {
            PRINT ((_L("Thread Table - Priority Low \n")));
            smpthread[i].DoCreateThread(&KThreadTableLow[i]);
            }
        else
            {
            PRINT ((_L("Thread Table - Priority High \n")));
            smpthread[i].DoCreateThread(&KThreadTableHigh[i]);
            }
        }
    PRINT (_L("Resuming all threads\n"));
    for (TInt i = 0; i < KNumThreads; i++)
           smpthread[i].ResumeThread();
    }
/**
 * CSMPSoakThread Thread Creation.
 * @param aIndex to exercise each row(thread) in the thread table          
 *
 * @return  N/A
 *
 * @pre     Initialize thread Table values
 * @post    None
 */
void CSMPSoakThread::DoCreateThread(TAny* aThread)
    {
    //Initialize each thread data
       iThreadData = ((TThread*)aThread)->threadData;
       test.Next(_L("Create Thread"));
       PRINT ((_L("%s   CPU affinity %d  Priority %d\n"),((TThread*)aThread)->threadName.Ptr(),iThreadData.cpuAffinity,iThreadData.threadPriorities[0]));
       TInt r = iThread.Create(((TThread*)aThread)->threadName, ((TThread*)aThread)->threadFunction, KDefaultStackSize, KHeapMinSize, KHeapMaxSize,(TAny*)this);
       test_KErrNone(r);
       if (iThreadData.threadPriorityChange == EPriorityList)
           {
           iPriority = 0;
           }
       else
           {
           iPriority = iThreadData.threadPriorities[0];
           }
       iThread.SetPriority((TThreadPriority)iThreadData.threadPriorities[0]);
       //Set the thread CPU Affinity
       gSMPStressDrv.ChangeThreadAffinity(&iThread, iThreadData.cpuAffinity);
      }
//Create Chunk - different types 
void CSMPSoakThread::CreateChunk (TChunkInfo * aChunkInfo, TMemory * aMemoryTablePtr)
	{
	//RDebug::Print(_L("Creating chunk name %s type %d bottom %d top %d max %d\n"),aMemoryTablePtr->globalChunkName.Ptr(),aMemoryTablePtr->chunkType,aMemoryTablePtr->initialBottom,aMemoryTablePtr->initialTop,aMemoryTablePtr->maxSize);
	TOwnerType ownerType = EOwnerProcess;
	aChunkInfo->lastBottom = aMemoryTablePtr->initialBottom;
	aChunkInfo->lastTop = aMemoryTablePtr->initialTop;
	switch (aMemoryTablePtr->chunkType)
		{
		case EChunkNormalThread:
			ownerType = EOwnerThread;			// drop through to create chunk
		case EChunkNormalProcess:
			if (aMemoryTablePtr->globalChunkName.Length())
				{
				test_KErrNone(aChunkInfo->chunk.CreateGlobal(aMemoryTablePtr->globalChunkName,aMemoryTablePtr->initialTop*gPageSize,aMemoryTablePtr->maxSize*gPageSize,ownerType));
				}
			else
				{
				test_KErrNone(aChunkInfo->chunk.CreateLocal(aMemoryTablePtr->initialTop*gPageSize,aMemoryTablePtr->maxSize*gPageSize,ownerType));
				}
			aChunkInfo->lastBottom = 0;			// ensure that this is zero
			break;

		case EChunkDisconnectedThread:
			ownerType = EOwnerThread;			// drop through to create chunk
		case EChunkDisconnectedProcess:
			if (aMemoryTablePtr->globalChunkName.Length())
				{
				test_KErrNone(aChunkInfo->chunk.CreateDisconnectedGlobal(aMemoryTablePtr->globalChunkName,aMemoryTablePtr->initialBottom*gPageSize,aMemoryTablePtr->initialTop*gPageSize,aMemoryTablePtr->maxSize*gPageSize,ownerType));
				}
			else
				{
				test_KErrNone(aChunkInfo->chunk.CreateDisconnectedLocal(aMemoryTablePtr->initialBottom*gPageSize,aMemoryTablePtr->initialTop*gPageSize,aMemoryTablePtr->maxSize*gPageSize,ownerType));
				}
			break;

		case EChunkDoubleEndedThread:
			ownerType = EOwnerThread;			// drop through to create chunk
		case EChunkDoubleEndedProcess:
			if (aMemoryTablePtr->globalChunkName.Length())
				{
				test_KErrNone(aChunkInfo->chunk.CreateDoubleEndedGlobal(aMemoryTablePtr->globalChunkName,aMemoryTablePtr->initialBottom*gPageSize,aMemoryTablePtr->initialTop*gPageSize,aMemoryTablePtr->maxSize*gPageSize,ownerType));
				}
			else
				{
				test_KErrNone(aChunkInfo->chunk.CreateDoubleEndedLocal(aMemoryTablePtr->initialBottom*gPageSize,aMemoryTablePtr->initialTop*gPageSize,aMemoryTablePtr->maxSize*gPageSize,ownerType));
				}
			break;
		}
	}
//Commit chunk
void CSMPSoakThread::CommitChunk (TChunkInfo * aChunkInfo, TMemory * aMemoryTablePtr)
	{
	TInt commitPages;

	switch (aMemoryTablePtr->chunkType)
		{
		case EChunkNormalThread:
		case EChunkNormalProcess:
			if (aChunkInfo->lastTop < (aMemoryTablePtr->maxSize - 1))
				{
				aChunkInfo->lastTop += (aMemoryTablePtr->maxSize - 1 - aChunkInfo->lastTop) / 2 + 1;
				//PRINT(_L("Adjust chunk memory - top %d pagesize %d\n"),aChunkInfo->lastTop,gPageSize);
				test_KErrNone(aChunkInfo->chunk.Adjust(aChunkInfo->lastTop*gPageSize));
				}
			break;

		case EChunkDisconnectedThread:
		case EChunkDisconnectedProcess:
			commitPages = ((aChunkInfo->lastTop - aChunkInfo->lastBottom) / 2) + 1;
			//PRINT(_L("Decommitting from bottom %d of %d pages\n"),aChunkInfo->lastBottom,commitPages);
			test_KErrNone(aChunkInfo->chunk.Decommit(aChunkInfo->lastBottom*gPageSize,commitPages * gPageSize));
			if ((aChunkInfo->lastBottom > 0) && (aChunkInfo->lastTop <= aMemoryTablePtr->initialTop))
				{
				aChunkInfo->lastTop = aChunkInfo->lastBottom + commitPages - 1;
				aChunkInfo->lastBottom /= 2;
				commitPages = aChunkInfo->lastTop - aChunkInfo->lastBottom + 1;
				}
			else
				{
				if (aChunkInfo->lastTop < (aMemoryTablePtr->maxSize -1))
					{
					if (aChunkInfo->lastTop <= aMemoryTablePtr->initialTop)
						{
						aChunkInfo->lastBottom = aMemoryTablePtr->initialTop + 1;
						}
					else
						{
						aChunkInfo->lastBottom = aChunkInfo->lastTop + 1;
						}
					commitPages = ((aMemoryTablePtr->maxSize - aChunkInfo->lastBottom) / 2) + 1;
					aChunkInfo->lastTop = aChunkInfo->lastBottom + commitPages - 1;
					}
				else
					{
					commitPages = 0;
					}
				}
			if (commitPages)
				{
				//PRINT(_L("Commit chunk memory bottom %d size %d pages\n"),aChunkInfo->lastBottom,commitPages);
				test_KErrNone(aChunkInfo->chunk.Commit(aChunkInfo->lastBottom*gPageSize,commitPages*gPageSize));
				}
		break;

		case EChunkDoubleEndedThread:
		case EChunkDoubleEndedProcess:
			if (aChunkInfo->lastBottom > 0 || aChunkInfo->lastTop < (aMemoryTablePtr->maxSize - 1))
				{
				if (aChunkInfo->lastBottom > 0)
					{
					aChunkInfo->lastBottom--;
					}
				if (aChunkInfo->lastTop < (aMemoryTablePtr->maxSize - 1))
					{
					aChunkInfo->lastTop++;
					}
			//	PRINT(_L("Adjust Double Ended bottom %d top %d\n"),aChunkInfo->lastBottom,aChunkInfo->lastTop);
				test_KErrNone(aChunkInfo->chunk.AdjustDoubleEnded(aChunkInfo->lastBottom*gPageSize,aChunkInfo->lastTop*gPageSize));
				}
			break;
		}
	}
//Write then read chunk
void CSMPSoakThread::WriteReadChunk (TChunkInfo * aChunkInfo, TMemory * aMemoryTablePtr)
	{
	if (aChunkInfo->lastTop < (aMemoryTablePtr->maxSize - 1))
		{
		TInt chunkSize = aChunkInfo->lastTop*gPageSize - aChunkInfo->lastBottom*gPageSize;
		//RDebug::Print(_L("WriteReadChunk Last Top %d lastBottom %d\n"),aChunkInfo->lastTop,aChunkInfo->lastBottom);
		TUint8 *writeaddr = aChunkInfo->chunk.Base()+ aChunkInfo->lastBottom*gPageSize;
		TPtr8 write(writeaddr,chunkSize);
		write.Copy(pattern,sizeof(pattern));
		test_KErrNone(Mem::Compare(writeaddr,sizeof(pattern),pattern,sizeof(pattern)));
		}
	}
//Memory Thread : will do memory associated operation
//param aSmp - CSMPSoakUtil pointer
TInt CSMPSoakThread::SMPStressMemoryThread(TAny* aSmp)
    {
    CSMPSoakThread* self = (CSMPSoakThread*)aSmp;
     __ASSERT_ALWAYS(self !=NULL, User::Panic(_L("CSMPSoakThread::SMPStressMemoryThread Panic"),0));
    return self->DoSMPStressMemoryThread();
    }
// Member for thread function
TInt CSMPSoakThread::DoSMPStressMemoryThread()
	{
	RTest test(_L("SMPStressMemoryThread"));
	
	TMemory *memoryTablePtr;
	TChunkInfo chunkTable[KNumChunks];
	TInt ctIndex = 0;
	test_KErrNone(UserHal::PageSizeInBytes(gPageSize));

	FOREVER
		{
		SetThreadPriority();

		if (gAbort)
			break;

		memoryTablePtr = (TMemory *) (iThreadData.listPtr);
		ctIndex = 0;
		
		//Create different type of chunks and write/read/verfiy it
		while (memoryTablePtr->chunkType != EChunkNone)
			{
			PRINT((_L("Create Chunk")));
			CreateChunk (&chunkTable[ctIndex],memoryTablePtr);

			PRINT(_L("Write and Read Chunk"));
			WriteReadChunk (&chunkTable[ctIndex],memoryTablePtr);

			ctIndex++;
			memoryTablePtr++;
			}
		
		//Commit different type of chunks
		TBool anyCommit;
		do
			{
			anyCommit = EFalse;
			memoryTablePtr = (TMemory *) (iThreadData.listPtr);
			ctIndex = 0;
			while (memoryTablePtr->chunkType != EChunkNone)
				{
				//Commit Chunks
				PRINT((_L("Commit Chunk Memory")));
				PRINT ((_L("CommitChunk %d bottom %d top %d\n"),ctIndex,memoryTablePtr->initialBottom,memoryTablePtr->initialTop));
				CommitChunk (&chunkTable[ctIndex],memoryTablePtr);
				anyCommit = ETrue;
				
				//Write into Chunks
				WriteReadChunk (&chunkTable[ctIndex],memoryTablePtr);
				PRINT((_L("Write Read Chunk Size %d\n"), (memoryTablePtr->initialTop) - (memoryTablePtr->initialBottom)));
				ctIndex++;
				memoryTablePtr++;
				}
			}
		while (anyCommit);
		
		//Close the Chunks
		memoryTablePtr = (TMemory *) (iThreadData.listPtr);
		ctIndex = 0;
		while (memoryTablePtr->chunkType != EChunkNone)
			{
			chunkTable[ctIndex].chunk.Close();

			ctIndex++;
			memoryTablePtr++;
			}
		User::After(gPeriod);
		}
	return 0x00;
	}
//Device Thread : will do device associated operation
//param aSmp - CSMPSoakUtil pointer
TInt CSMPSoakThread::SMPStressDeviceThread(TAny* aSmp)
    {
    CSMPSoakThread* self = (CSMPSoakThread*)aSmp;
     __ASSERT_ALWAYS(self !=NULL, User::Panic(_L("CSMPSoakThread::SMPStressDeviceThread Panic"),0));
    return self->DoSMPStressDeviceThread();
    }
// Member for thread function
TInt CSMPSoakThread::DoSMPStressDeviceThread()
	{
	RTest test(_L("SMPStressDeviceThread"));
	
	RTimer timer;
	RFs session;
	TFileName sessionPath;

	test_KErrNone(timer.CreateLocal());
	TRequestStatus s;

	TDesC** ptrDevices =  (TDesC**) (iThreadData.listPtr);
	PRINT ((_L("Devices  Number %d [%s]\n"), ptrDevices[0]->Length(), ptrDevices[0]->Ptr()));
	for (TInt i = 1; ptrDevices[i] ; i++)
		PRINT ((_L("LDD%d=%s "),i,ptrDevices[i]->Ptr()));
	PRINT (_L("\n"));

	FOREVER
		{
		for (TInt i = 0; i < ptrDevices[0]->Length(); i++)
			{
			TText driveLetter = (*ptrDevices[0])[i];
			PRINT ((_L("Device %c\n"),driveLetter));

			test_KErrNone(session.Connect());

			sessionPath=(_L("?:\\SESSION_TEST\\"));
			sessionPath[0]=driveLetter;
			test_KErrNone(session.SetSessionPath(sessionPath));

			TInt driveNumber;
			test_KErrNone(session.CharToDrive(driveLetter, driveNumber));

			TBuf<64> fileSystemName;
			test_KErrNone(session.FileSystemName(fileSystemName,driveNumber));

			PRINT ((_L("File System Name %s\n"),fileSystemName.PtrZ()));

			TDriveInfo driveInfo;
			test_KErrNone(session.Drive(driveInfo, driveNumber));

			TVolumeInfo volumeInfo;
			test_KErrNone(session.Volume(volumeInfo, driveNumber));

			session.Close();
			}
		for (TInt i = 1; ptrDevices[i] ; i += 2)
			{
			RDevice device;

			TInt r = User::LoadLogicalDevice(*ptrDevices[i]);
			test(r == KErrNone || r == KErrAlreadyExists);

			test_KErrNone(device.Open(*ptrDevices[i+1]));

			TBuf8<64> deviceCaps;
			device.GetCaps(deviceCaps);

			TVersion deviceVersion;
			device.QueryVersionSupported(deviceVersion);

			device.Close();
			}
		SetThreadPriority();
		timer.After(s, iThreadData.delayTime*1000);
		User::WaitForRequest(s);
		test (s == KErrNone);

		if (gAbort)
			break;
		User::After(gPeriod);
		}
	timer.Close();
	PRINT((_L("SMPStressDeviceThread MyTimer.Cancel() called\n")));
	return 0x00;
	}
//Spin Thread : will do thread sync 
//param aSmp - CSMPSoakUtil pointer
TInt CSMPSoakThread::SMPStressSpinThread(TAny* aSmp)
    {
    CSMPSoakThread* self = (CSMPSoakThread*)aSmp;
     __ASSERT_ALWAYS(self !=NULL, User::Panic(_L("CSMPSoakThread::SMPStressSpinThread Panic"),0));
    return self->DoSMPStressSpinThread();
    }
// Member for thread function
TInt CSMPSoakThread::DoSMPStressSpinThread()
	{
	RTest test(_L("SMPStressSpinThread"));

	TTime startTime;
	TTime endTime;
	TTimeIntervalMicroSeconds loopTimeMicroSeconds;
	PRINT (_L("SMPStressSpinThread\n"));
	FOREVER
		{
		SetThreadPriority();
		gSwitchSem.Wait();
		startTime.UniversalTime();
		do
		{
			endTime.UniversalTime();
			loopTimeMicroSeconds = endTime.MicroSecondsFrom(startTime);
		}while (loopTimeMicroSeconds <= iThreadData.delayTime*1000);

		if (gAbort)
			break;
		User::After(gPeriod);
		}
	return 0x00;
	}
//Timer Thread : Timer operation and  thread sync 
//param aSmp - CSMPSoakUtil pointer
TInt CSMPSoakThread::SMPStressTimerThread(TAny* aSmp)
    {
    CSMPSoakThread* self = (CSMPSoakThread*)aSmp;
     __ASSERT_ALWAYS(self !=NULL, User::Panic(_L("CSMPSoakThread::SMPStressTimerThread Panic"),0));
    return self->DoSMPStressTimerThread();
    }
// Member for thread function
TInt CSMPSoakThread::DoSMPStressTimerThread()
	{
	RTest test(_L("SMPStressTimerThread"));

	PRINT (_L("SMPStressTimerThread\n"));
	RTimer timer;
	test_KErrNone(timer.CreateLocal());
	TRequestStatus s;

	FOREVER
		{
		timer.After(s, iThreadData.delayTime*1000);
		User::WaitForRequest(s);
		test (s == KErrNone);
		PRINT ((_L("*")));
		gSwitchSem.Signal(iThreadData.numThreads);

		if (gAbort)
			break;
		User::After(gPeriod);
		}
	timer.Cancel();
	PRINT((_L("SMPStressTimerThread MyTimer.Cancel() called\n")));
	return 0x00;
	}
// CActive class to monitor KeyStrokes from User
class CActiveConsole : public CActive
	{
public:
	CActiveConsole();
	~CActiveConsole();
	void GetCharacter();
	static TInt Callback(TAny* aCtrl);
	static CPeriodic* TimerL();
private:
	// Defined as pure virtual by CActive;
	// implementation provided by this class.
	virtual void DoCancel();
	// Defined as pure virtual by CActive;
	// implementation provided by this class,
	virtual void RunL();
	void ProcessKeyPressL(TChar aChar);
private:
    
	};
// Class CActiveConsole
CActiveConsole::CActiveConsole()
	: CActive(EPriorityHigh)
	{
	CActiveScheduler::Add(this);
	}

CActiveConsole::~CActiveConsole()
	{
	Cancel();
	}
CPeriodic* CActiveConsole::TimerL()
    {
    return(CPeriodic::NewL(EPriorityNormal));
    }
// Callback function for timer expiry
TInt CActiveConsole::Callback(TAny* aControl)
	{
	return KErrNone;
	}

void CActiveConsole::GetCharacter()
	{
	test.Console()->Read(iStatus);
	SetActive();
	}

void CActiveConsole::DoCancel()
	{
	PRINT(_L("CActiveConsole::DoCancel\n"));
	test.Console()->ReadCancel();
	}

void CActiveConsole::ProcessKeyPressL(TChar aChar)
	{
	if (aChar == EKeyEscape)
		{
		PRINT(_L("CActiveConsole: ESC key pressed -> stopping active scheduler...\n"));
		gAbort = ETrue;
		CActiveScheduler::Stop();
		return;
		}
	aChar.UpperCase();
	GetCharacter();
	}

void CActiveConsole::RunL()
	{
	ProcessKeyPressL(static_cast<TChar>(test.Console()->KeyCode()));
	}

// CActiveTimer class to monitor timeout expiry
class CActiveTimer : public CActive
    {
public:
    CActiveTimer();
    ~CActiveTimer();
    void Delay(TTimeIntervalMicroSeconds32 aDelay);
private:
    RTimer iTimer;
    // Defined as pure virtual by CActive;
    // implementation provided by this class.
    virtual void DoCancel();
    // Defined as pure virtual by CActive;
    // implementation provided by this class,
    virtual void RunL();
   
    };
// Class CActiveConsole
CActiveTimer::CActiveTimer()
    : CActive(EPriorityHigh)
    {
    CActiveScheduler::Add(this);
    User::LeaveIfError(iTimer.CreateLocal());
    }

CActiveTimer::~CActiveTimer()
    {
    Cancel();
    iTimer.Close();
    }


void CActiveTimer::Delay(TTimeIntervalMicroSeconds32 aDelay)
    {
    iTimer.After(iStatus, aDelay);
    SetActive();
    }

void CActiveTimer::DoCancel()
    {
    iTimer.Cancel();
    }

void CActiveTimer::RunL()
    {
    PRINT(_L("CActiveTimer: Application runtime expired..."));
    gAbort = ETrue;
    CActiveScheduler::Stop();
    return;
    }

//T_SMPSOAK Entry Point
TInt E32Main()
	{
	test.Title();
	__UHEAP_MARK;
	test.Start(_L("t_smpsoak.exe"));
	
	// When running as a stand alone test, 
	// there needs to be a timeout
	timeout = ETrue;

	ParseCommandLine();
	if (gAbort)
		return 0x00;

	PRINT (_L("Load device driver\n"));
	TInt r = User::LoadLogicalDevice(_L("d_smpsoak.ldd"));
	if (r == KErrNotFound)
		{
		PRINT (_L("Test not supported on this platform because the D_SMPSOAK.LDD Driver is Not Present\n"));
		test(EFalse);
		}
	PRINT (_L("Calling SMPStressDrv Open\n"));
	r = gSMPStressDrv.Open();
	test_KErrNone(r);

	PRINT (_L("Creating our local semaphore\n"));
	r=gSwitchSem.CreateLocal(0);
	test_KErrNone(r);

	CSMPSoakThread smpthread;
	PRINT ((_L("Creating all threads =%d\n"),KNumThreads));
	smpthread.CreateThread();
			
	CSMPSoakThread *smpprocess= new CSMPSoakThread[NumProcess];
	PRINT ((_L("Creating all process =%d\n"),NumProcess));
	for (TInt i = 0; i < NumProcess; i++)
	    smpprocess[i].CreateChildProcess(i);
	
	PRINT (_L("Resuming all process \n"));
	for (TInt i = 0; i < NumProcess; i++)
	    smpprocess[i].ResumeChildProcess();
	
	PRINT (_L("Starting ActiveScheduler\n"));
	test.Next(_L("Press ESC Key to Shutdown SMPSoak...\n"));
	CActiveScheduler* myScheduler = new (ELeave) CActiveScheduler();
	test(myScheduler != NULL);
	CActiveScheduler::Install(myScheduler);
	
	CPeriodic* theTimer=NULL;
	TRAPD(ret,theTimer=CActiveConsole::TimerL())
	test_KErrNone(ret);
	theTimer->Start(0,KTimerPeriod,TCallBack(CActiveConsole::Callback));
	if(timeout)
	    {
	    CActiveTimer* myActiveTimer = new CActiveTimer();
	    test(myActiveTimer != NULL);
	    myActiveTimer->Delay(gTimeout*1000000);
	    }
	CActiveConsole* myActiveConsole = new CActiveConsole();
	test(myActiveConsole != NULL);
	myActiveConsole->GetCharacter();
	CActiveScheduler::Start();
	if (gAbort)
			{
			PRINT (_L("gAbort TRUE \n"));
			for (TInt i = 0; i < NumProcess; i++)
			smpprocess[i].TerminateChildProcess();
			delete[] smpprocess;
			delete theTimer;
			gSMPStressDrv.Close();
			gSwitchSem.Close();
			return 0;
			}
	__UHEAP_MARKEND;
	test.End();
	return 0;
	}
void ParseCommandLine()
	{
	TBuf<256> args;
	User::CommandLine(args);
	TLex	lex(args);
	PRINT ((_L("****Command line = %s\n"), args.PtrZ()));

	FOREVER
		{
		TPtrC  token=lex.NextToken();
		if(token.Length()!=0)
			{
                if (token.Length()==0)
			        break;  // ignore trailing whitespace
                else if (token.Mid(0) == _L("-h"))
				{
                    PRINT (_L("T_SMPSOAK.EXE Usage Options:\n"));
                    PRINT (_L("Type t_smpsoak.exe -h\n"));
					ShowHelp();
					gAbort = ETrue;
					break;
				}
				else if (token.Mid(0) == _L("-l"))
				{
                    //Read OOM entry from KProcessTable and run
                    test.Printf(_L("SMPSOAK:lowmem\n"));
                    NumProcess = KNumProcess+1;
                    break;
				}
				else if (token.Mid(0) == _L("-b"))
				{
                    test.Printf(_L("SMPSOAK: Test Silent Mode\n")); 
                    ThreadPriorityLow = ETrue;
                    TestSilent = ETrue;
					// If we have tests running in the background
					// we want an endless loop
					timeout = EFalse;
                    break;
				}
				else if (token.Left(2) == _L("-t"))
				{
				    test.Printf(_L("SMPSOAK:Timeout\n"));
				    lex.SkipSpaceAndMark();
				    token.Set(lex.NextToken());
				    TLex lexNum(token);
				    lexNum.Val(gTimeout,EDecimal);   
				    test.Printf(_L("Timeout in Seconds=%u \n"),gTimeout);  
				    timeout = ETrue;
                    break;
				}
				else if (token.Left(2) == _L("-p"))
				{
                    test.Printf(_L("SMPSOAK:period\n"));
                    lex.SkipSpaceAndMark();
                    token.Set(lex.NextToken());
                    TLex lexNum(token);
                    lexNum.Val(gPeriod,EDecimal);   
				    test.Printf(_L("period in mSeconds=%d \n"),gPeriod);  
				    Period = ETrue;
				    break;
				}
				else
				{
                    test.Printf(_L("Error- Invalid SMPSOAK CMD Line Argument"));
				  	break;
				}
			}
		break;
		}
	}
