// sampler.cpp
// 
// Copyright (c) 1999 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//

#include <platform.h>

#include "sampler.h"
//#include <nkern.h>
//#include <kernel.h>
#include <kern_priv.h>
_LIT(KLddName,"topsampler");

const TInt KMinRate=10;
const TInt KMaxRate=1000;
const TInt KRawBufSize = 20000; // Enough for 20 seconds of samples - since max expected refresh rate in UI is 10s we can do everything nice and simply with one buffer

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

class DProfile : public DLogicalChannel
	{
public:
	DProfile();
	~DProfile();
protected:
	virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion& aVer);
	virtual void HandleMsg(TMessageBase* aMsg);
private:
	TInt StartSampling(TInt aRate);
	TInt StopSampling();
	TInt Reset();
	//TInt GetErrors(TDes8* aDes);
	TInt ProcessReadRequest();
	void Complete(TInt aResult);
	inline TBool Running()
		{return iTimer.iState!=NTimer::EIdle;}
private:
	static void Sample(TAny*);
	void DoSample();

	
private:
	TUint32 iStartTime;
	TInt iRepeat;
	TInt iPeriod;
	DThread* iClient;
	TRequestStatus* iReqStatus;
	TDes8* iClientDes;		// client des pointer
	TUint32 iBuf1[KRawBufSize]; // First word is the current position in the buffer
	TUint32 iBuf2[KRawBufSize]; // Have two buffers that we can switch between, this avoids us having to stop the sampler timer, or disable interrupts or whatever, when we want to drain the buffer
	TUint32* iCurrentBuf; // Pointer to either iBuf1 or iBuf2

	struct TReport
		{
		TUint iRawBufferErrCounter;
		TUint iCodeSegErrCounter;
		TInt  iReportMask; 
		} iReport;

	NTimer iTimer;
	};

DECLARE_STANDARD_LDD()
	{
	return new DDeviceSampler;
	}

DDeviceSampler::DDeviceSampler()
//
// Constructor
//
	{
	//iParseMask=0;
	//iUnitsMask=0;
	iVersion=TVersion(1,0,0);
	}

TInt DDeviceSampler::Install()
//
// Install the device driver.
//
	{
	TInt r=SetName(&KLddName);
	return r;
	}

void DDeviceSampler::GetCaps(TDes8& /*aDes*/) const
//
// Return the capabilities.
//
	{
	}

TInt DDeviceSampler::Create(DLogicalChannelBase*& aChannel)
//
// Create a channel on the device.
//
	{
	aChannel=new DProfile;
	return aChannel?KErrNone:KErrNoMemory;
	}

DProfile::DProfile()
	:	iTimer(Sample,this)
//
// Constructor
//
	{
	iCurrentBuf = iBuf1;
	iCurrentBuf[0] = 1;
	}

DProfile::~DProfile()
//
// Destructor
//
	{
	iTimer.Cancel();
	Kern::SafeClose((DObject*&)iClient, NULL);
	}

TInt DProfile::DoCreate(TInt /*aUnit*/, const TDesC8* /*anInfo*/, const TVersion& aVer)
//
// Create the channel from the passed info.
//
	{
	if (!Kern::QueryVersionSupported(TVersion(1,0,1),aVer))
		return KErrNotSupported;

	iClient=&Kern::CurrentThread();
	iClient->Open();
	//Kern::SetThreadPriority(24);
	SetDfcQ(Kern::DfcQue0());
	iMsgQ.Receive();
	return KErrNone;
	}

void DProfile::Complete(TInt aResult)
//Completes user request
	{
	DEBUG_PROFILER(Kern::Printf("C");)
	Kern::RequestComplete(iClient,iReqStatus,aResult);
	}

TInt DProfile::StartSampling(TInt aRate)
	{
	DEBUG_PROFILER(Kern::Printf("START");)
	//Activate timer
	aRate=Min(KMaxRate, Max(KMinRate, aRate));
	iPeriod=1000/aRate;

	if (!Running())
		{
		iCurrentBuf = iBuf1;
		iCurrentBuf[0] = 1;
		iTimer.OneShot(iPeriod);
		}
	
	DEBUG_PROFILER(Kern::Printf("START end");)
	return KErrNone;
	}

TInt DProfile::Reset()
	{
//
// Resets the device. It is the first message sent by profiler application.
//	
	if (Running())
		return KErrGeneral;
	
	DEBUG_PROFILER(Kern::Printf("RST %d", aXIPOnly);)

	iTimer.Cancel();
	iPeriod=1;
	iReqStatus=NULL;
	iClientDes=NULL;

	iReport.iRawBufferErrCounter = 0;
	iReport.iCodeSegErrCounter = 0;
	iReport.iReportMask = 0;
	DEBUG_PROFILER(Kern::Printf("RST end");)
	return KErrNone;
	}

TInt DProfile::StopSampling()
//
// Stops sampling
//
	{
	DEBUG_PROFILER(Kern::Printf("STOP");)
	if (Running())
		{
		iTimer.Cancel();
		}
	if (iReqStatus)
		Complete(KErrNone);
	
	DEBUG_PROFILER(Kern::Printf("STOP end");)
	return KErrNone;
	}

TInt DProfile::ProcessReadRequest()
	{
	// Whatever the status of the profiler, return the contents of the current sampling buffer
	DEBUG_PROFILER(Kern::Printf("READ");)
	TInt max=Kern::ThreadGetDesMaxLength(iClient, iClientDes);
	if (max<0)
		return max;
	if (max==0)
		return KErrArgument;

	TUint32* otherBuf = (iCurrentBuf == iBuf1 ? iBuf2 : iBuf1);
	otherBuf[0] = 1; // Reset other buf
	TUint32* bufToWrite = iCurrentBuf;
	iCurrentBuf = otherBuf; // And switch do it, so we can safely mess with bufToWrite without the sampling ISR adding more data under our feet
	TInt numSamples = bufToWrite[0] - 1; // Because bufToWrite[0] is an index and the data actually starts at index 1
	TPtrC8 desToWrite((TUint8*)&bufToWrite[1], (numSamples)*sizeof(TUint32));
	
	TInt r = Kern::ThreadDesWrite(iClient, iClientDes, desToWrite, 0, 0, iClient);
	return r;
	}

void DProfile::HandleMsg(TMessageBase* aMsg)
//
// Client requests
//
	{
	TInt r=KErrNone;
	TThreadMessage& m=*(TThreadMessage*)aMsg;
	//BEGIN TOMSCI commented out this check as it is too strict
	/*
	if (m.Client()!=iClient)
		{
		m.PanicClient(_L("SAMPLER"),EAccessDenied);
		return;
		}
	*/
	//END TOMSCI
	TInt id=m.iValue;
	if (id==(TInt)ECloseMsg)
		{
		DEBUG_PROFILER(Kern::Printf("CLOSE");)
		iTimer.Cancel();
		m.Complete(KErrNone,EFalse);
		iMsgQ.CompleteAll(KErrServerTerminated);
		DEBUG_PROFILER(Kern::Printf("CLOSE end");)
		return;
		}
	else if (id<0)
		{
		if (id!=~RSampler::ERequestRead)
			{
			TRequestStatus* pS=(TRequestStatus*)m.Ptr0();
			Kern::RequestComplete(iClient,pS,KErrNotSupported);
			}
		if (iReqStatus)
			{
			m.PanicClient(_L("SAMPLER"),ERequestAlreadyPending);
			return;
			}
		iReqStatus=(TRequestStatus*)m.Ptr0();
		iClientDes=(TDes8*)m.Ptr1();
		TInt err = ProcessReadRequest();
		Complete(err);
		}
	else if (id==KMaxTInt)
		{
		TInt mask=m.Int0();
		if (mask & (1<<RSampler::ERequestRead))
			{
			Complete(KErrCancel);
			}
		}
	else
		{
		switch(id)
			{
			case RSampler::EControlStartProfile:
				r=StartSampling(m.Int0());
				break;
			case RSampler::EControlStopProfile:
				r=StopSampling();
				break;
			case RSampler::EControlResetProfile:
				r=Reset();
				break;
			//case RSampler::EControlGetErrors:
			//	r=GetErrors((TDes8*)m.Ptr0());
			//	break;
			default:
				r=KErrNotSupported;
				break;
			}
		}
	m.Complete(r,ETrue);
	}

void DProfile::Sample(TAny* aPtr)
	{
	DProfile& d=*(DProfile*)aPtr;
	d.DoSample();
	}

void DProfile::DoSample()
	{
	iTimer.Again(iPeriod);

	TUint32& currentPos = iCurrentBuf[0];
	if (currentPos < (TUint)KRawBufSize) // space in buffer
		{
		DThread* pT=Kern::NThreadToDThread(NKern::CurrentThread());
		if (pT!=NULL)
			{
			iCurrentBuf[currentPos] = pT->iId;
			currentPos++;
			}
		}
	else
		iReport.iRawBufferErrCounter++;
	}
