diff -r 000000000000 -r a41df078684a kernel/eka/nkern/nk_timer.cpp --- /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<iTriggerTime-iMsCount); + if (t=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<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)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<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)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 (r2iTriggerTime; + tt=(tt&~0x0f)-16; // time at which transfer to final queue would occur + TInt r3=(TInt)(tt-next); + if (r3