kernel/eka/nkern/nk_timer.cpp
changeset 0 a41df078684a
child 36 538db54a451d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kernel/eka/nkern/nk_timer.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,592 @@
+// Copyright (c) 1998-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:
+// e32\nkern\nk_timer.cpp
+// Fast Millisecond Timer Implementation
+// This file is just a template - you'd be mad not to machine code this
+// 
+//
+
+#include "nk_priv.h"
+
+const TInt KTimerQDfcPriority=6;
+
+GLDEF_D NTimerQ TheTimerQ;
+
+#ifndef __MSTIM_MACHINE_CODED__
+#ifdef _DEBUG
+#define __DEBUG_CALLBACK(n)	{if (iDebugFn) (*iDebugFn)(iDebugPtr,n);}
+#else
+#define __DEBUG_CALLBACK(n)
+#endif
+
+
+/** Starts a nanokernel timer in one-shot mode with ISR callback.
+	
+	Queues the timer to expire in the specified number of nanokernel ticks. The
+	actual wait time will be at least that much and may be up to one tick more.
+	The expiry handler will be called in ISR context.
+	
+	Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
+
+	@param	aTime Timeout in nanokernel ticks
+	
+	@return	KErrNone if no error; KErrInUse if timer is already active.
+	
+	@pre	Any context
+	
+	@see    NKern::TimerTicks()
+ */
+EXPORT_C TInt NTimer::OneShot(TInt aTime)
+	{
+	return OneShot(aTime,FALSE);
+	}
+
+
+/** Starts a nanokernel timer in one-shot mode with ISR or DFC callback.
+	
+	Queues the timer to expire in the specified number of nanokernel ticks. The
+	actual wait time will be at least that much and may be up to one tick more.
+	The expiry handler will be called in either ISR context or in the context
+	of the nanokernel timer thread (DfcThread1).
+
+    Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
+
+	@param	aTime Timeout in nanokernel ticks
+	@param	aDfc TRUE if DFC callback required, FALSE if ISR callback required.
+	
+	@return	KErrNone if no error; KErrInUse if timer is already active.
+	
+	@pre	Any context
+	
+	@see    NKern::TimerTicks()
+ */
+EXPORT_C TInt NTimer::OneShot(TInt aTime, TBool aDfc)
+	{
+	__NK_ASSERT_DEBUG(aTime>=0);
+
+	/** iFunction could be set to NULL after NTimer::OneShot(TInt, TDfc&) call.
+	Call-back mechanism cannot be changed in the life time of a timer. */
+	__NK_ASSERT_DEBUG(iFunction!=NULL); 
+
+	TInt irq=NKern::DisableAllInterrupts();
+	if (iState!=EIdle)
+		{
+		NKern::RestoreInterrupts(irq);
+		return KErrInUse;
+		}
+	iCompleteInDfc=TUint8(aDfc?1:0);
+	iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime;
+	TheTimerQ.Add(this);
+	NKern::RestoreInterrupts(irq);
+	return KErrNone;
+	}
+
+/** Starts a nanokernel timer in one-shot mode with callback in dfc thread that provided DFC belongs to.
+	
+	Queues the timer to expire in the specified number of nanokernel ticks. The
+	actual wait time will be at least that much and may be up to one tick more.
+	On expiry aDfc will be queued in ISR context.
+
+    Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
+
+	@param	aTime Timeout in nanokernel ticks
+	@param	aDfc - Dfc to be queued when the timer expires.
+	
+	@return	KErrNone if no error; KErrInUse if timer is already active.
+	
+	@pre	Any context
+	
+	@see    NKern::TimerTicks()
+ */
+EXPORT_C TInt NTimer::OneShot(TInt aTime, TDfc& aDfc)
+	{
+	__NK_ASSERT_DEBUG(aTime>=0);
+	TInt irq=NKern::DisableAllInterrupts();
+	if (iState!=EIdle)
+		{
+		NKern::RestoreInterrupts(irq);
+		return KErrInUse;
+		}
+	iCompleteInDfc = 0;
+	iFunction = NULL;
+	iPtr = (TAny*) &aDfc;
+	iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime;
+	TheTimerQ.Add(this);
+	NKern::RestoreInterrupts(irq);
+	return KErrNone;
+	}
+
+
+/** Starts a nanokernel timer in zero-drift periodic mode with ISR or DFC callback.
+
+	Queues the timer to expire in the specified number of nanokernel ticks,
+	measured from the time at which it last expired. This allows exact periodic
+	timers to be implemented with no drift caused by delays in requeueing the
+	timer.
+
+	The expiry handler will be called in the same context as the previous timer
+	expiry. Generally the way this is used is that NTimer::OneShot() is used to start 
+	the first time interval and this specifies whether the callback is in ISR context 
+	or in the context of the nanokernel timer thread (DfcThread1) or other Dfc thread.
+	The expiry handler then uses NTimer::Again() to requeue the timer.
+
+	@param	aTime Timeout in nanokernel ticks
+
+	@return	KErrNone if no error; KErrInUse if timer is already active;
+	        KErrArgument if the requested expiry time is in the past.
+	        
+	@pre	Any context
+ */
+EXPORT_C TInt NTimer::Again(TInt aTime)
+//
+// Wait aTime from last trigger time - used for periodic timers
+//
+	{
+	__NK_ASSERT_DEBUG(aTime>0);
+	TInt irq=NKern::DisableAllInterrupts();
+	if (iState!=EIdle)
+		{
+		NKern::RestoreInterrupts(irq);
+		return KErrInUse;
+		}
+	TUint32 nextTick=TheTimerQ.iMsCount;
+	TUint32 trigger=iTriggerTime+(TUint32)aTime;
+	TUint32 d=trigger-nextTick;
+	if (d>=0x80000000)
+		{
+		NKern::RestoreInterrupts(irq);
+		return KErrArgument;		// requested time is in the past
+		}
+	iTriggerTime=trigger;
+	TheTimerQ.Add(this);
+	NKern::RestoreInterrupts(irq);
+	return KErrNone;
+	}
+
+
+/** Cancels a nanokernel timer.
+
+	Removes this timer from the nanokernel timer queue. Does nothing if the
+	timer is inactive or has already expired.
+	Note that if the timer was queued and DFC callback requested it is possible
+	for the expiry handler to run even after Cancel() has been called. This will
+	occur in the case where DfcThread1 is preempted just before calling the
+	expiry handler for this timer and the preempting thread/ISR/IDFC calls
+	Cancel() on the timer.
+
+	@pre	Any context
+	@return	TRUE if timer was actually cancelled
+	@return	FALSE if timer was not cancelled - this could be because it was not
+				active or because its expiry handler was already running on
+				another CPU or in the timer DFC.
+ */
+EXPORT_C TBool NTimer::Cancel()
+	{
+	TBool result = TRUE;
+	TInt irq=NKern::DisableAllInterrupts();
+	if (iState>ETransferring)	// idle or transferring timers are not on a queue
+		Deque();
+	switch (iState)
+		{
+		case ETransferring:	// signal DFC to abort this iteration
+			TheTimerQ.iTransferringCancelled=TRUE;
+			break;
+		case ECritical:		// signal DFC to abort this iteration
+			TheTimerQ.iCriticalCancelled=TRUE;
+			break;
+		case EFinal:
+			{
+			// Need to clear bit in iPresent if both final queues now empty
+			// NOTE: Timer might actually be on the completed queue rather than the final queue
+			//		 but the check is harmless in any case.
+			TInt i=iTriggerTime & NTimerQ::ETimerQMask;
+			NTimerQ::STimerQ& q=TheTimerQ.iTickQ[i];
+			if (q.iIntQ.IsEmpty() && q.iDfcQ.IsEmpty())
+				TheTimerQ.iPresent &= ~(1<<i);
+			break;
+			}
+		case EIdle:			// nothing to do
+			result = FALSE;
+		case EHolding:		// just deque
+		case EOrdered:		// just deque
+			break;
+		}
+	iState=EIdle;
+	NKern::RestoreInterrupts(irq);
+	return result;
+	}
+#endif
+
+
+/** Check if a nanokernel timer is pending or not
+
+	@return	TRUE if the timer is pending (OneShot() etc. would return KErrInUse)
+	@return FALSE if the timer is idle (OneShot() etc. would succeed)
+	@pre	Any context
+
+	@publishedPartner
+	@prototype
+ */
+EXPORT_C TBool NTimer::IsPending()
+	{
+	return iState != EIdle;
+	}
+
+
+/** Obtains the address of the nanokernel timer queue object.
+
+	Not intended for general use. Intended only for base ports in order to get
+	the address used to call NTimerQ::Tick() with.
+
+	@return	The address of the nanokernel timer queue object
+	@pre	Any context
+ */
+EXPORT_C TAny* NTimerQ::TimerAddress()
+	{
+	return &TheTimerQ;
+	}
+
+NTimerQ::NTimerQ()
+	:	iDfc(NTimerQ::DfcFn,this,NULL,KTimerQDfcPriority)
+	{
+	// NOTE: All other members are initialised to zero since the single instance
+	//		 of NTimerQ resides in .bss
+	}
+
+void NTimerQ::Init1(TInt aTickPeriod)
+	{
+	TheTimerQ.iTickPeriod=aTickPeriod;
+	__KTRACE_OPT(KBOOT,DEBUGPRINT("NTimerQ::Init1 - period %d us",aTickPeriod));
+	__KTRACE_OPT(KMEMTRACE, DEBUGPRINT("MT:P %d",aTickPeriod));
+	}
+
+void NTimerQ::Init3(TDfcQue* aDfcQ)
+	{
+	__KTRACE_OPT(KBOOT,DEBUGPRINT("NTimerQ::Init3 DFCQ at %08x",aDfcQ));
+	TheTimerQ.iDfc.SetDfcQ(aDfcQ);
+	}
+
+#ifndef __MSTIM_MACHINE_CODED__
+void NTimerQ::Add(NTimer* aTimer)
+//
+//	Internal function to add a timer to the queue.
+//	Enter and return with all interrupts disabled.
+//
+	{
+	TInt t=TInt(aTimer->iTriggerTime-iMsCount);
+	if (t<ENumTimerQueues)
+		AddFinal(aTimer);
+	else
+		{
+		// >=32ms to expiry, so put on holding queue
+		aTimer->iState=NTimer::EHolding;
+		iHoldingQ.Add(aTimer);
+		}
+	}
+
+void NTimerQ::AddFinal(NTimer* aTimer)
+//
+//	Internal function to add a timer to the corresponding final queue.
+//	Enter and return with all interrupts disabled.
+//
+	{
+	TInt i=aTimer->iTriggerTime & ETimerQMask;
+	SDblQue* pQ;
+	if (aTimer->iCompleteInDfc)
+		pQ=&iTickQ[i].iDfcQ;
+	else
+		pQ=&iTickQ[i].iIntQ;
+	iPresent |= (1<<i);
+	aTimer->iState=NTimer::EFinal;
+	pQ->Add(aTimer);
+	}
+
+void NTimerQ::DfcFn(TAny* aPtr)
+	{
+	((NTimerQ*)aPtr)->Dfc();
+	}
+
+void NTimerQ::Dfc()
+//
+// Do deferred timer queue processing and/or DFC completions
+//
+	{
+	TInt irq;
+
+	// First transfer entries on the Ordered queue to the Final queues
+	FOREVER
+		{
+		irq=NKern::DisableAllInterrupts();
+		if (iOrderedQ.IsEmpty())
+			break;
+		NTimer* pC=(NTimer*)iOrderedQ.First();
+		TInt remain=pC->iTriggerTime-iMsCount;
+		if (remain>=ENumTimerQueues)
+			break;
+
+		// If remaining time <32 ticks, add it to final queue;
+		// also if remain < 0 we've 'missed it' so add to final queue.
+		pC->Deque();
+		AddFinal(pC);
+		NKern::RestoreInterrupts(irq);
+		__DEBUG_CALLBACK(0);
+		}
+	NKern::RestoreInterrupts(irq);
+	__DEBUG_CALLBACK(1);
+
+	// Next transfer entries on the Holding queue to the Ordered queue or final queue
+	FOREVER
+		{
+		irq=NKern::DisableAllInterrupts();
+		if (iHoldingQ.IsEmpty())
+			break;
+		NTimer* pC=(NTimer*)iHoldingQ.First();
+		pC->Deque();
+		pC->iState=NTimer::ETransferring;
+		iTransferringCancelled=FALSE;
+		TUint32 trigger=pC->iTriggerTime;
+		if (TInt(trigger-iMsCount)<ENumTimerQueues)
+			{
+			// <32ms remaining so put it on final queue
+			AddFinal(pC);
+			}
+		else
+			{
+			FOREVER
+				{
+				NKern::RestoreInterrupts(irq);
+				__DEBUG_CALLBACK(2);
+
+				// we now need to walk ordered queue to find correct position for pC
+				SDblQueLink* anchor=&iOrderedQ.iA;
+				iCriticalCancelled=FALSE;
+				irq=NKern::DisableAllInterrupts();
+				NTimer* pN=(NTimer*)iOrderedQ.First();
+				while (pN!=anchor && !iTransferringCancelled)
+					{
+					if ((pN->iTriggerTime-trigger)<0x80000000u)
+						break;	// insert before pN
+					pN->iState=NTimer::ECritical;
+					NKern::RestoreInterrupts(irq);
+					__DEBUG_CALLBACK(3);
+					irq=NKern::DisableAllInterrupts();
+					if (iCriticalCancelled)
+						break;
+					pN->iState=NTimer::EOrdered;
+					pN=(NTimer*)pN->iNext;
+					}
+
+				if (iTransferringCancelled)
+					break;		// this one has been cancelled, go on to next one
+				if (!iCriticalCancelled)
+					{
+					pC->InsertBefore(pN);
+					pC->iState=NTimer::EOrdered;
+					break;		// done this one
+					}
+				}
+			}
+		NKern::RestoreInterrupts(irq);
+		__DEBUG_CALLBACK(4);
+		}
+	NKern::RestoreInterrupts(irq);
+	__DEBUG_CALLBACK(5);
+
+	// Finally do call backs for timers which requested DFC callback
+	FOREVER
+		{
+		irq=NKern::DisableAllInterrupts();
+		if (iCompletedQ.IsEmpty())
+			break;
+		NTimer* pC=(NTimer*)iCompletedQ.First();
+		pC->Deque();
+		pC->iState=NTimer::EIdle;
+		TAny* p=pC->iPtr;
+		NTimerFn f=pC->iFunction;
+		NKern::RestoreInterrupts(irq);
+		__DEBUG_CALLBACK(7);
+		(*f)(p);
+		}
+	NKern::RestoreInterrupts(irq);
+	}
+
+
+/** Tick over the nanokernel timer queue.
+	This function should be called by the base port in the system tick timer ISR.
+	It should not be called at any other time.
+	The value of 'this' to pass is the value returned by NTimerQ::TimerAddress().
+
+	@see NTimerQ::TimerAddress()
+ */
+EXPORT_C void NTimerQ::Tick()
+	{
+#ifdef _DEBUG
+	// If there are threads waiting to be released by the tick, enqueue the dfc
+	if (!TheScheduler.iDelayedQ.IsEmpty())
+		TheScheduler.iDelayDfc.Add();
+#endif
+	TheScheduler.TimesliceTick();
+	TInt irq=NKern::DisableAllInterrupts();
+	TInt i=iMsCount & ETimerQMask;
+	iMsCount++;
+	STimerQ* pQ=iTickQ+i;
+	iPresent &= ~(1<<i);
+	TBool doDfc=FALSE;
+	if (!pQ->iDfcQ.IsEmpty())
+		{
+		// transfer DFC completions to completed queue and queue DFC
+		iCompletedQ.MoveFrom(&pQ->iDfcQ);
+		doDfc=TRUE;
+		}
+	if ((i&(ETimerQMask>>1))==0)
+		{
+		// Every 16 ticks we check if a DFC is required.
+		// This allows a DFC latency of up to 16 ticks before timers are missed.
+		if (!iHoldingQ.IsEmpty())
+			doDfc=TRUE;				// if holding queue nonempty, queue DFC to sort
+		else if (!iOrderedQ.IsEmpty())
+			{
+			// if first ordered queue entry expires in <32ms, queue the DFC to transfer
+			NTimer* pC=(NTimer*)iOrderedQ.First();
+#ifdef __EPOC32__
+			__ASSERT_WITH_MESSAGE_DEBUG(iMsCount<=pC->iTriggerTime, "iMsCount has exceeded pC->iTriggerTime; function called later than expected ","NKTimer::Tick()");
+#endif
+			if (TInt(pC->iTriggerTime-iMsCount)<ENumTimerQueues)
+				doDfc=TRUE;
+			}
+		}
+	if (!pQ->iIntQ.IsEmpty())
+		{
+		// transfer ISR completions to a temporary queue
+		// careful here - higher priority interrupts could dequeue timers!
+		SDblQue q(&pQ->iIntQ,0);
+		while(!q.IsEmpty())
+			{
+			NTimer* pC=(NTimer*)q.First();
+			pC->Deque();
+			pC->iState=NTimer::EIdle;
+			NKern::RestoreInterrupts(irq);
+			if (pC->iFunction)
+				(*pC->iFunction)(pC->iPtr);
+			else
+				((TDfc*)(pC->iPtr))->Add();
+			irq=NKern::DisableAllInterrupts();
+			}
+		}
+	NKern::RestoreInterrupts(irq);
+	if (doDfc)
+		iDfc.Add();
+	}
+
+
+/** Return the number of ticks before the next nanokernel timer expiry.
+	May on occasion return a pessimistic estimate (i.e. too low).
+	Used by base port to disable the system tick interrupt when the system
+	is idle.
+
+	@return	The number of ticks before the next nanokernel timer expiry.
+	
+	@pre	Interrupts must be disabled.
+	
+	@post	Interrupts are disabled.
+ */
+EXPORT_C TInt NTimerQ::IdleTime()
+	{
+	CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::IdleTime");	
+#ifdef _DEBUG
+	// If there are threads waiting to be released by the tick we can't idle
+	if (!TheScheduler.iDelayedQ.IsEmpty())
+		return 1;
+#endif
+	NTimerQ& m=TheTimerQ;
+	TUint32 next=m.iMsCount;	// number of next tick
+	TUint32 p=m.iPresent;
+	TInt r=KMaxTInt;
+	if (p)
+		{
+		// Final queues nonempty
+		TInt nx=next&0x1f;				// number of next tick modulo 32
+		p=(p>>nx)|(p<<(32-nx));			// rotate p right by nx (so lsb corresponds to next tick)
+		r=__e32_find_ls1_32(p);			// find number of zeros before LS 1
+		}
+	if (!m.iHoldingQ.IsEmpty())
+		{
+		// Sort operation required - need to process next tick divisible by 16
+		TInt nx=next&0x0f;				// number of next tick modulo 16
+		TInt r2=nx?(16-nx):0;			// number of ticks before next divisible by 16
+		if (r2<r)
+			r=r2;
+		}
+	if (!m.iOrderedQ.IsEmpty())
+		{
+		// Timers present on ordered queue
+		NTimer* pC=(NTimer*)m.iOrderedQ.First();
+		TUint32 tt=pC->iTriggerTime;
+		tt=(tt&~0x0f)-16;				// time at which transfer to final queue would occur
+		TInt r3=(TInt)(tt-next);
+		if (r3<r)
+			r=r3;
+		}
+	return r;
+	}
+#endif
+
+
+/** Advance the nanokernel timer queue by the specified number of ticks.
+	It is assumed that no timers expire as a result of this.
+	Used by base port when system comes out of idle mode after disabling the
+	system tick interrupt to bring the timer queue up to date.
+
+	@param	aTicks Number of ticks skipped due to tick suppression
+
+	@pre	Interrupts must be disabled.
+
+	@post	Interrupts are disabled.
+ */
+EXPORT_C void NTimerQ::Advance(TInt aTicks)
+	{
+	CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::Advance");	
+	TheTimerQ.iMsCount+=(TUint32)aTicks;
+	}
+
+
+/**	Returns the period of the nanokernel timer.
+	@return Period in microseconds
+	@pre any context
+	@see NTimer
+ */
+EXPORT_C TInt NKern::TickPeriod()
+	{
+	return TheTimerQ.iTickPeriod;
+	}
+
+
+/**	Converts a time interval to timer ticks.
+
+	@param aMilliseconds time interval in milliseconds.
+	@return Number of nanokernel timer ticks.  Non-integral results are rounded up.
+
+ 	@pre aMilliseconds should be <=2147483 to avoid integer overflow.
+	@pre any context
+ */
+EXPORT_C TInt NKern::TimerTicks(TInt aMilliseconds)
+	{
+	__ASSERT_WITH_MESSAGE_DEBUG(aMilliseconds<=2147483,"aMilliseconds should be <=2147483","NKern::TimerTicks");
+	TUint32 msp=TheTimerQ.iTickPeriod;
+	if (msp==1000)	// will be true except on pathological hardware
+		return aMilliseconds;
+	TUint32 us=(TUint32)aMilliseconds*1000;
+	return (us+msp-1)/msp;
+	}
+