sl@0: // Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies). sl@0: // All rights reserved. sl@0: // This component and the accompanying materials are made available sl@0: // under the terms of the License "Eclipse Public License v1.0" sl@0: // which accompanies this distribution, and is available sl@0: // at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: // sl@0: // Initial Contributors: sl@0: // Nokia Corporation - initial contribution. sl@0: // sl@0: // Contributors: sl@0: // sl@0: // Description: sl@0: // e32\nkern\nk_timer.cpp sl@0: // Fast Millisecond Timer Implementation sl@0: // This file is just a template - you'd be mad not to machine code this sl@0: // sl@0: // sl@0: sl@0: #include "nk_priv.h" sl@0: sl@0: const TInt KTimerQDfcPriority=6; sl@0: sl@0: GLDEF_D NTimerQ TheTimerQ; sl@0: sl@0: #ifndef __MSTIM_MACHINE_CODED__ sl@0: #ifdef _DEBUG sl@0: #define __DEBUG_CALLBACK(n) {if (iDebugFn) (*iDebugFn)(iDebugPtr,n);} sl@0: #else sl@0: #define __DEBUG_CALLBACK(n) sl@0: #endif sl@0: sl@0: sl@0: /** Starts a nanokernel timer in one-shot mode with ISR callback. sl@0: sl@0: Queues the timer to expire in the specified number of nanokernel ticks. The sl@0: actual wait time will be at least that much and may be up to one tick more. sl@0: The expiry handler will be called in ISR context. sl@0: sl@0: Note that NKern::TimerTicks() can be used to convert milliseconds to ticks. sl@0: sl@0: @param aTime Timeout in nanokernel ticks sl@0: sl@0: @return KErrNone if no error; KErrInUse if timer is already active. sl@0: sl@0: @pre Any context sl@0: sl@0: @see NKern::TimerTicks() sl@0: */ sl@0: EXPORT_C TInt NTimer::OneShot(TInt aTime) sl@0: { sl@0: return OneShot(aTime,FALSE); sl@0: } sl@0: sl@0: sl@0: /** Starts a nanokernel timer in one-shot mode with ISR or DFC callback. sl@0: sl@0: Queues the timer to expire in the specified number of nanokernel ticks. The sl@0: actual wait time will be at least that much and may be up to one tick more. sl@0: The expiry handler will be called in either ISR context or in the context sl@0: of the nanokernel timer thread (DfcThread1). sl@0: sl@0: Note that NKern::TimerTicks() can be used to convert milliseconds to ticks. sl@0: sl@0: @param aTime Timeout in nanokernel ticks sl@0: @param aDfc TRUE if DFC callback required, FALSE if ISR callback required. sl@0: sl@0: @return KErrNone if no error; KErrInUse if timer is already active. sl@0: sl@0: @pre Any context sl@0: sl@0: @see NKern::TimerTicks() sl@0: */ sl@0: EXPORT_C TInt NTimer::OneShot(TInt aTime, TBool aDfc) sl@0: { sl@0: __NK_ASSERT_DEBUG(aTime>=0); sl@0: sl@0: /** iFunction could be set to NULL after NTimer::OneShot(TInt, TDfc&) call. sl@0: Call-back mechanism cannot be changed in the life time of a timer. */ sl@0: __NK_ASSERT_DEBUG(iFunction!=NULL); sl@0: sl@0: TInt irq=NKern::DisableAllInterrupts(); sl@0: if (iState!=EIdle) sl@0: { sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrInUse; sl@0: } sl@0: iCompleteInDfc=TUint8(aDfc?1:0); sl@0: iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime; sl@0: TheTimerQ.Add(this); sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrNone; sl@0: } sl@0: sl@0: /** Starts a nanokernel timer in one-shot mode with callback in dfc thread that provided DFC belongs to. sl@0: sl@0: Queues the timer to expire in the specified number of nanokernel ticks. The sl@0: actual wait time will be at least that much and may be up to one tick more. sl@0: On expiry aDfc will be queued in ISR context. sl@0: sl@0: Note that NKern::TimerTicks() can be used to convert milliseconds to ticks. sl@0: sl@0: @param aTime Timeout in nanokernel ticks sl@0: @param aDfc - Dfc to be queued when the timer expires. sl@0: sl@0: @return KErrNone if no error; KErrInUse if timer is already active. sl@0: sl@0: @pre Any context sl@0: sl@0: @see NKern::TimerTicks() sl@0: */ sl@0: EXPORT_C TInt NTimer::OneShot(TInt aTime, TDfc& aDfc) sl@0: { sl@0: __NK_ASSERT_DEBUG(aTime>=0); sl@0: TInt irq=NKern::DisableAllInterrupts(); sl@0: if (iState!=EIdle) sl@0: { sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrInUse; sl@0: } sl@0: iCompleteInDfc = 0; sl@0: iFunction = NULL; sl@0: iPtr = (TAny*) &aDfc; sl@0: iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime; sl@0: TheTimerQ.Add(this); sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrNone; sl@0: } sl@0: sl@0: sl@0: /** Starts a nanokernel timer in zero-drift periodic mode with ISR or DFC callback. sl@0: sl@0: Queues the timer to expire in the specified number of nanokernel ticks, sl@0: measured from the time at which it last expired. This allows exact periodic sl@0: timers to be implemented with no drift caused by delays in requeueing the sl@0: timer. sl@0: sl@0: The expiry handler will be called in the same context as the previous timer sl@0: expiry. Generally the way this is used is that NTimer::OneShot() is used to start sl@0: the first time interval and this specifies whether the callback is in ISR context sl@0: or in the context of the nanokernel timer thread (DfcThread1) or other Dfc thread. sl@0: The expiry handler then uses NTimer::Again() to requeue the timer. sl@0: sl@0: @param aTime Timeout in nanokernel ticks sl@0: sl@0: @return KErrNone if no error; KErrInUse if timer is already active; sl@0: KErrArgument if the requested expiry time is in the past. sl@0: sl@0: @pre Any context sl@0: */ sl@0: EXPORT_C TInt NTimer::Again(TInt aTime) sl@0: // sl@0: // Wait aTime from last trigger time - used for periodic timers sl@0: // sl@0: { sl@0: __NK_ASSERT_DEBUG(aTime>0); sl@0: TInt irq=NKern::DisableAllInterrupts(); sl@0: if (iState!=EIdle) sl@0: { sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrInUse; sl@0: } sl@0: TUint32 nextTick=TheTimerQ.iMsCount; sl@0: TUint32 trigger=iTriggerTime+(TUint32)aTime; sl@0: TUint32 d=trigger-nextTick; sl@0: if (d>=0x80000000) sl@0: { sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrArgument; // requested time is in the past sl@0: } sl@0: iTriggerTime=trigger; sl@0: TheTimerQ.Add(this); sl@0: NKern::RestoreInterrupts(irq); sl@0: return KErrNone; sl@0: } sl@0: sl@0: sl@0: /** Cancels a nanokernel timer. sl@0: sl@0: Removes this timer from the nanokernel timer queue. Does nothing if the sl@0: timer is inactive or has already expired. sl@0: Note that if the timer was queued and DFC callback requested it is possible sl@0: for the expiry handler to run even after Cancel() has been called. This will sl@0: occur in the case where DfcThread1 is preempted just before calling the sl@0: expiry handler for this timer and the preempting thread/ISR/IDFC calls sl@0: Cancel() on the timer. sl@0: sl@0: @pre Any context sl@0: @return TRUE if timer was actually cancelled sl@0: @return FALSE if timer was not cancelled - this could be because it was not sl@0: active or because its expiry handler was already running on sl@0: another CPU or in the timer DFC. sl@0: */ sl@0: EXPORT_C TBool NTimer::Cancel() sl@0: { sl@0: TBool result = TRUE; sl@0: TInt irq=NKern::DisableAllInterrupts(); sl@0: if (iState>ETransferring) // idle or transferring timers are not on a queue sl@0: Deque(); sl@0: switch (iState) sl@0: { sl@0: case ETransferring: // signal DFC to abort this iteration sl@0: TheTimerQ.iTransferringCancelled=TRUE; sl@0: break; sl@0: case ECritical: // signal DFC to abort this iteration sl@0: TheTimerQ.iCriticalCancelled=TRUE; sl@0: break; sl@0: case EFinal: sl@0: { sl@0: // Need to clear bit in iPresent if both final queues now empty sl@0: // NOTE: Timer might actually be on the completed queue rather than the final queue sl@0: // but the check is harmless in any case. sl@0: TInt i=iTriggerTime & NTimerQ::ETimerQMask; sl@0: NTimerQ::STimerQ& q=TheTimerQ.iTickQ[i]; sl@0: if (q.iIntQ.IsEmpty() && q.iDfcQ.IsEmpty()) sl@0: TheTimerQ.iPresent &= ~(1<iTriggerTime-iMsCount); sl@0: if (t=32ms to expiry, so put on holding queue sl@0: aTimer->iState=NTimer::EHolding; sl@0: iHoldingQ.Add(aTimer); sl@0: } sl@0: } sl@0: sl@0: void NTimerQ::AddFinal(NTimer* aTimer) sl@0: // sl@0: // Internal function to add a timer to the corresponding final queue. sl@0: // Enter and return with all interrupts disabled. sl@0: // sl@0: { sl@0: TInt i=aTimer->iTriggerTime & ETimerQMask; sl@0: SDblQue* pQ; sl@0: if (aTimer->iCompleteInDfc) sl@0: pQ=&iTickQ[i].iDfcQ; sl@0: else sl@0: pQ=&iTickQ[i].iIntQ; sl@0: iPresent |= (1<iState=NTimer::EFinal; sl@0: pQ->Add(aTimer); sl@0: } sl@0: sl@0: void NTimerQ::DfcFn(TAny* aPtr) sl@0: { sl@0: ((NTimerQ*)aPtr)->Dfc(); sl@0: } sl@0: sl@0: void NTimerQ::Dfc() sl@0: // sl@0: // Do deferred timer queue processing and/or DFC completions sl@0: // sl@0: { sl@0: TInt irq; sl@0: sl@0: // First transfer entries on the Ordered queue to the Final queues sl@0: FOREVER sl@0: { sl@0: irq=NKern::DisableAllInterrupts(); sl@0: if (iOrderedQ.IsEmpty()) sl@0: break; sl@0: NTimer* pC=(NTimer*)iOrderedQ.First(); sl@0: TInt remain=pC->iTriggerTime-iMsCount; sl@0: if (remain>=ENumTimerQueues) sl@0: break; sl@0: sl@0: // If remaining time <32 ticks, add it to final queue; sl@0: // also if remain < 0 we've 'missed it' so add to final queue. sl@0: pC->Deque(); sl@0: AddFinal(pC); sl@0: NKern::RestoreInterrupts(irq); sl@0: __DEBUG_CALLBACK(0); sl@0: } sl@0: NKern::RestoreInterrupts(irq); sl@0: __DEBUG_CALLBACK(1); sl@0: sl@0: // Next transfer entries on the Holding queue to the Ordered queue or final queue sl@0: FOREVER sl@0: { sl@0: irq=NKern::DisableAllInterrupts(); sl@0: if (iHoldingQ.IsEmpty()) sl@0: break; sl@0: NTimer* pC=(NTimer*)iHoldingQ.First(); sl@0: pC->Deque(); sl@0: pC->iState=NTimer::ETransferring; sl@0: iTransferringCancelled=FALSE; sl@0: TUint32 trigger=pC->iTriggerTime; sl@0: if (TInt(trigger-iMsCount)iTriggerTime-trigger)<0x80000000u) sl@0: break; // insert before pN sl@0: pN->iState=NTimer::ECritical; sl@0: NKern::RestoreInterrupts(irq); sl@0: __DEBUG_CALLBACK(3); sl@0: irq=NKern::DisableAllInterrupts(); sl@0: if (iCriticalCancelled) sl@0: break; sl@0: pN->iState=NTimer::EOrdered; sl@0: pN=(NTimer*)pN->iNext; sl@0: } sl@0: sl@0: if (iTransferringCancelled) sl@0: break; // this one has been cancelled, go on to next one sl@0: if (!iCriticalCancelled) sl@0: { sl@0: pC->InsertBefore(pN); sl@0: pC->iState=NTimer::EOrdered; sl@0: break; // done this one sl@0: } sl@0: } sl@0: } sl@0: NKern::RestoreInterrupts(irq); sl@0: __DEBUG_CALLBACK(4); sl@0: } sl@0: NKern::RestoreInterrupts(irq); sl@0: __DEBUG_CALLBACK(5); sl@0: sl@0: // Finally do call backs for timers which requested DFC callback sl@0: FOREVER sl@0: { sl@0: irq=NKern::DisableAllInterrupts(); sl@0: if (iCompletedQ.IsEmpty()) sl@0: break; sl@0: NTimer* pC=(NTimer*)iCompletedQ.First(); sl@0: pC->Deque(); sl@0: pC->iState=NTimer::EIdle; sl@0: TAny* p=pC->iPtr; sl@0: NTimerFn f=pC->iFunction; sl@0: NKern::RestoreInterrupts(irq); sl@0: __DEBUG_CALLBACK(7); sl@0: (*f)(p); sl@0: } sl@0: NKern::RestoreInterrupts(irq); sl@0: } sl@0: sl@0: sl@0: /** Tick over the nanokernel timer queue. sl@0: This function should be called by the base port in the system tick timer ISR. sl@0: It should not be called at any other time. sl@0: The value of 'this' to pass is the value returned by NTimerQ::TimerAddress(). sl@0: sl@0: @see NTimerQ::TimerAddress() sl@0: */ sl@0: EXPORT_C void NTimerQ::Tick() sl@0: { sl@0: #ifdef _DEBUG sl@0: // If there are threads waiting to be released by the tick, enqueue the dfc sl@0: if (!TheScheduler.iDelayedQ.IsEmpty()) sl@0: TheScheduler.iDelayDfc.Add(); sl@0: #endif sl@0: TheScheduler.TimesliceTick(); sl@0: TInt irq=NKern::DisableAllInterrupts(); sl@0: TInt i=iMsCount & ETimerQMask; sl@0: iMsCount++; sl@0: STimerQ* pQ=iTickQ+i; sl@0: iPresent &= ~(1<iDfcQ.IsEmpty()) sl@0: { sl@0: // transfer DFC completions to completed queue and queue DFC sl@0: iCompletedQ.MoveFrom(&pQ->iDfcQ); sl@0: doDfc=TRUE; sl@0: } sl@0: if ((i&(ETimerQMask>>1))==0) sl@0: { sl@0: // Every 16 ticks we check if a DFC is required. sl@0: // This allows a DFC latency of up to 16 ticks before timers are missed. sl@0: if (!iHoldingQ.IsEmpty()) sl@0: doDfc=TRUE; // if holding queue nonempty, queue DFC to sort sl@0: else if (!iOrderedQ.IsEmpty()) sl@0: { sl@0: // if first ordered queue entry expires in <32ms, queue the DFC to transfer sl@0: NTimer* pC=(NTimer*)iOrderedQ.First(); sl@0: #ifdef __EPOC32__ sl@0: __ASSERT_WITH_MESSAGE_DEBUG(iMsCount<=pC->iTriggerTime, "iMsCount has exceeded pC->iTriggerTime; function called later than expected ","NTimerQ::Tick()"); sl@0: #endif sl@0: if (TInt(pC->iTriggerTime-iMsCount)iIntQ.IsEmpty()) sl@0: { sl@0: // transfer ISR completions to a temporary queue sl@0: // careful here - higher priority interrupts could dequeue timers! sl@0: SDblQue q(&pQ->iIntQ,0); sl@0: while(!q.IsEmpty()) sl@0: { sl@0: NTimer* pC=(NTimer*)q.First(); sl@0: pC->Deque(); sl@0: pC->iState=NTimer::EIdle; sl@0: NKern::RestoreInterrupts(irq); sl@0: if (pC->iFunction) sl@0: (*pC->iFunction)(pC->iPtr); sl@0: else sl@0: ((TDfc*)(pC->iPtr))->Add(); sl@0: irq=NKern::DisableAllInterrupts(); sl@0: } sl@0: } sl@0: NKern::RestoreInterrupts(irq); sl@0: if (doDfc) sl@0: iDfc.Add(); sl@0: } sl@0: sl@0: sl@0: /** Return the number of ticks before the next nanokernel timer expiry. sl@0: May on occasion return a pessimistic estimate (i.e. too low). sl@0: Used by base port to disable the system tick interrupt when the system sl@0: is idle. sl@0: sl@0: @return The number of ticks before the next nanokernel timer expiry. sl@0: sl@0: @pre Interrupts must be disabled. sl@0: sl@0: @post Interrupts are disabled. sl@0: */ sl@0: EXPORT_C TInt NTimerQ::IdleTime() sl@0: { sl@0: CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::IdleTime"); sl@0: #ifdef _DEBUG sl@0: // If there are threads waiting to be released by the tick we can't idle sl@0: if (!TheScheduler.iDelayedQ.IsEmpty()) sl@0: return 1; sl@0: #endif sl@0: NTimerQ& m=TheTimerQ; sl@0: TUint32 next=m.iMsCount; // number of next tick sl@0: TUint32 p=m.iPresent; sl@0: TInt r=KMaxTInt; sl@0: if (p) sl@0: { sl@0: // Final queues nonempty sl@0: TInt nx=next&0x1f; // number of next tick modulo 32 sl@0: p=(p>>nx)|(p<<(32-nx)); // rotate p right by nx (so lsb corresponds to next tick) sl@0: r=__e32_find_ls1_32(p); // find number of zeros before LS 1 sl@0: } sl@0: if (!m.iHoldingQ.IsEmpty()) sl@0: { sl@0: // Sort operation required - need to process next tick divisible by 16 sl@0: TInt nx=next&0x0f; // number of next tick modulo 16 sl@0: TInt r2=nx?(16-nx):0; // number of ticks before next divisible by 16 sl@0: if (r2iTriggerTime; sl@0: tt=(tt&~0x0f)-16; // time at which transfer to final queue would occur sl@0: TInt r3=(TInt)(tt-next); sl@0: if (r3