// Copyright (c) 1999-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\realtime\t_lat2.cpp
// 
//

#include <e32test.h>
#include <e32svr.h>
#include <e32property.h>
#include <e32atomics.h>
#include "runtests.h"
#include "d_latncy.h"

_LIT(KLatencyLddFileName,"D_LATNCY");
_LIT(KThreadName,"LatencyThreadU");

RTest test(_L("Latency"));
RThread Main;
TUint TicksPerMs;

struct SFullLatencyResults : public SLatencyResults
	{
	TUint iKernRetAddr;
	TUint iUserRetAddr;
	TInt64 iCount;
	TInt64 iSumIntTicks;
	TInt64 iSumKernTicks;
	TInt64 iSumUserTicks;
	TUint iIntCpsr;
	TUint iKernCpsr;
	TUint iKernR14;
	TUint iUserCpsr;
	TUint iUserR14;

	void Update(SLatencyResults& aResults);
	};

SFullLatencyResults Latencies;
volatile TUint32 UpdateCount=0;

TUint TimerToMicroseconds(TUint aTimerValue)
	{
	return (aTimerValue*1000+TicksPerMs-1)/TicksPerMs;
	}

void SFullLatencyResults::Update(SLatencyResults& aResults)
	{
	__e32_atomic_add_acq32(&UpdateCount, 1);

	// memory barrier

	if (aResults.iIntTicks>iIntTicks)
		{
		iIntTicks=aResults.iIntTicks;
		iIntRetAddr=aResults.iIntRetAddr;
#ifdef __CAPTURE_EXTRAS
		iIntCpsr=aResults.iIntSpsr;
		iIntR14=aResults.iIntR14;
#endif
		}
	if (aResults.iKernThreadTicks>iKernThreadTicks)
		{
		iKernThreadTicks=aResults.iKernThreadTicks;
		iKernRetAddr=aResults.iIntRetAddr;
#ifdef __CAPTURE_EXTRAS
		iKernCpsr=aResults.iIntSpsr;
		iKernR14=aResults.iIntR14;
#endif
		}
	if (aResults.iUserThreadTicks>iUserThreadTicks)
		{
		iUserThreadTicks=aResults.iUserThreadTicks;
		iUserRetAddr=aResults.iIntRetAddr;
#ifdef __CAPTURE_EXTRAS
		iUserCpsr=aResults.iIntSpsr;
		iUserR14=aResults.iIntR14;
#endif
		}
	iSumIntTicks+=aResults.iIntTicks;
	iSumKernTicks+=aResults.iKernThreadTicks;
	iSumUserTicks+=aResults.iUserThreadTicks;
	++iCount;

	// memory barrier

	__e32_atomic_add_rel32(&UpdateCount, 1);
	}

TInt LatencyThread(TAny* aStatus)
	{
	TRequestStatus* pS=(TRequestStatus*)aStatus;
	RLatency l;
	TInt r=l.Open();
	if (r!=KErrNone)
		return r;
	TicksPerMs=l.TicksPerMs();
	Mem::FillZ(&Latencies,sizeof(Latencies));
	Main.RequestComplete(pS,0);
	SLatencyResults results;

	l.Start();
	FOREVER
		{
		User::WaitForAnyRequest();
		l.GetResults(results);
		Latencies.Update(results);
		}
	return r;
	}

void GetLatencies(SFullLatencyResults& aResults)
	{
	FOREVER
		{
		TUint32 u1 = UpdateCount;
		__e32_memory_barrier();
		aResults=Latencies;
		__e32_memory_barrier();
		TUint32 u2 = UpdateCount;
		if (u1==u2 && !(u1&1))	// no good if it changed partway through or was changing when we started
			break;
		}
	}

_LIT(KPrefixRuntests, "RUNTESTS: RT");
void DisplayMaxValues(const TDesC& aPrefix)
	{
	SFullLatencyResults v;
	GetLatencies(v);
	TUint i=TimerToMicroseconds(v.iIntTicks);
	TUint k=TimerToMicroseconds(v.iKernThreadTicks);
	TUint u=TimerToMicroseconds(v.iUserThreadTicks);
	TUint ia=v.iIntRetAddr;
	TUint ka=v.iKernRetAddr;
	TUint ua=v.iUserRetAddr;
	test.Printf(_L("%SMAX: Int %4d %08x Kern %4d %08x User %4d %08x\n"),&aPrefix,i,ia,k,ka,u,ua);
	}

void DisplayAvgValues(const TDesC& aPrefix)
	{
	SFullLatencyResults v;
	GetLatencies(v);
	TUint i=TimerToMicroseconds(I64LOW(v.iSumIntTicks/v.iCount));
	TUint k=TimerToMicroseconds(I64LOW(v.iSumKernTicks/v.iCount));
	TUint u=TimerToMicroseconds(I64LOW(v.iSumUserTicks/v.iCount));
	test.Printf(_L("%SAVG: Int %4d Kern %4d User %4d Count %Ld\n"),&aPrefix,i,k,u,v.iCount);
	}

#ifdef __CAPTURE_EXTRAS
void DisplayExtras(const TDesC& aPrefix)
	{
	SFullLatencyResults v;
	GetLatencies(v);
	test.Printf(_L("%SInt : Cpsr %08x R14 %08x\n"),&aPrefix,v.iIntCpsr,v.iIntR14);
	test.Printf(_L("%SKern: Cpsr %08x R14 %08x\n"),&aPrefix,v.iKernCpsr,v.iKernR14);
	test.Printf(_L("%SUser: Cpsr %08x R14 %08x\n"),&aPrefix,v.iUserCpsr,v.iUserR14);
	}
#endif

void ClearMaxValues()
	{
	Mem::FillZ(&Latencies,6*sizeof(TUint));
	}

void ClearAvgValues()
	{
	Mem::FillZ(&Latencies.iCount,4*sizeof(TInt64));
	}

_LIT_SECURITY_POLICY_PASS(KPersistencePropReadPolicy);
_LIT_SECURITY_POLICY_PASS(KPersistencePropWritePolicy);
void AnnouncePersistence()
	{
	TInt r = RProperty::Define(KRuntestsIntentionalPersistenceKey, RProperty::EInt, KPersistencePropReadPolicy, KPersistencePropWritePolicy);
	test(r==KErrNone || r==KErrAlreadyExists);
	r = RProperty::Set(RProcess().SecureId(), KRuntestsIntentionalPersistenceKey, KRuntestsIntentionalPersistenceValue);
	test(r==KErrNone);
	}

class CConsoleReader : public CActive
	{
public:
	CConsoleReader();
	static void New();
	void Start();
	virtual void RunL();
	virtual void DoCancel();
public:
	CConsoleBase* iConsole;
	};

CConsoleReader::CConsoleReader()
	:	CActive(0)
	{
	}

void CConsoleReader::RunL()
	{
	TKeyCode k = iConsole->KeyCode();
	switch(k)
		{
		case '1':
			test.Printf(_L("Clearing Maximum Values\n"));
			ClearMaxValues();
			break;
		case '2':
			DisplayMaxValues(KNullDesC);
			break;
		case '3':
			test.Printf(_L("Clearing Average Values\n"));
			ClearAvgValues();
			break;
		case '4':
			DisplayAvgValues(KNullDesC);
			break;
#ifdef __CAPTURE_EXTRAS
		case '5':
			DisplayExtras(KNullDesC);
			break;
#endif
		case 'x':
		case 'X':
			CActiveScheduler::Stop();
			return;
		default:
			break;
		}
	Start();
	}

void CConsoleReader::DoCancel()
	{
	iConsole->ReadCancel();
	}

void CConsoleReader::New()
	{
	CConsoleReader* crdr = new CConsoleReader;
	test(crdr != NULL);
	crdr->iConsole = test.Console();
	CActiveScheduler::Add(crdr);
	crdr->Start();
	}

void CConsoleReader::Start()
	{
	iConsole->Read(iStatus);
	SetActive();
	}

class CPubSubWatcher : public CActive
	{
public:
	CPubSubWatcher();
	static void New();
	void Start();
	virtual ~CPubSubWatcher();
	virtual void RunL();
	virtual void DoCancel();
public:
	RProperty iProperty;
	};

CPubSubWatcher::CPubSubWatcher()
	:	CActive(0)
	{
	}

void CPubSubWatcher::RunL()
	{
	Start();
	DisplayMaxValues(KPrefixRuntests);
	DisplayAvgValues(KPrefixRuntests);
	}

void CPubSubWatcher::DoCancel()
	{
	iProperty.Cancel();
	}

void CPubSubWatcher::New()
	{
	CPubSubWatcher* psw = new CPubSubWatcher;
	test(psw != NULL);
	TInt r = psw->iProperty.Attach(KRuntestsCategory, KRuntestsCurrentTestKey, EOwnerThread);
	test(r==KErrNone);
	CActiveScheduler::Add(psw);
	psw->Start();
	}

void CPubSubWatcher::Start()
	{
	iProperty.Subscribe(iStatus);
	SetActive();
	}

CPubSubWatcher::~CPubSubWatcher()
	{
	iProperty.Close();
	}

GLDEF_C TInt E32Main()
	{
#ifdef _DEBUG
	// Don't run automatically on debug builds
	TUint32 creator_sid = User::CreatorSecureId();
	if (creator_sid == TUint32(KRuntestsCategoryValue))
		return KErrNone;
#endif
	// disable anything which will interfere, e.g. plat sec diagnostics
	User::SetDebugMask(UserSvr::DebugMask(2)|4, 2);

	test.Title();
	
	test.Printf(_L("*** Please note ***\n"));
	test.Printf(_L("\n"));
	test.Printf(_L("t_lat2 runs in the backgroud to measure latency while other tests are\n"));
	test.Printf(_L("running.  It should not be run as a standalone test, only as part of a\n"));
	test.Printf(_L("test run coordinated by runtests.  If run on its owm, it will simply wait\n"));
	test.Printf(_L("forever.\n"));
	test.Printf(_L("\n"));
	
	test.Start(_L("Load LDD"));
	TInt r=User::LoadLogicalDevice(KLatencyLddFileName);
	test(r==KErrNone || r==KErrAlreadyExists);

	test.Next(_L("Duplicate handle"));
	r=Main.Duplicate(RThread());
	test(r==KErrNone);

	test.Next(_L("Create thread"));
	RThread t;
	TRequestStatus sx;
	TRequestStatus sc;
	r=t.Create(KThreadName,LatencyThread,0x1000,NULL,&sc);
	test(r==KErrNone);
	t.Logon(sx);
	t.Resume();
	User::WaitForRequest(sx,sc);
	if (sx!=KRequestPending)
		{
		if (t.ExitType()==EExitKill && t.ExitReason()==KErrAlreadyExists)
			{
			test.Printf(_L("T_LAT2 already running.\n"));
			test.End();
			return 0;
			}
		test.Printf(_L("Initialisation failed, error %d\n"),sx.Int());
		test(0);
		}
	test(sc==KErrNone);

	CTrapCleanup* tcln = CTrapCleanup::New();
	test(tcln != NULL);
	CActiveScheduler* as = new CActiveScheduler;
	test(as != NULL);
	CActiveScheduler::Install(as);
	CConsoleReader::New();
	CPubSubWatcher::New();
	AnnouncePersistence();
	RProcess::Rendezvous(KErrNone);

	CActiveScheduler::Start();

	// latency test over
	User::SetDebugMask(UserSvr::DebugMask(2)&~4, 2);

	test.End();
	return 0;
	}
