First public contribution.
1 // Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
2 // All rights reserved.
3 // This component and the accompanying materials are made available
4 // under the terms of the License "Eclipse Public License v1.0"
5 // which accompanies this distribution, and is available
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
8 // Initial Contributors:
9 // Nokia Corporation - initial contribution.
14 // e32\nkern\nk_timer.cpp
15 // Fast Millisecond Timer Implementation
16 // This file is just a template - you'd be mad not to machine code this
22 const TInt KTimerQDfcPriority=6;
24 GLDEF_D NTimerQ TheTimerQ;
26 #ifndef __MSTIM_MACHINE_CODED__
28 #define __DEBUG_CALLBACK(n) {if (iDebugFn) (*iDebugFn)(iDebugPtr,n);}
30 #define __DEBUG_CALLBACK(n)
34 /** Starts a nanokernel timer in one-shot mode with ISR callback.
36 Queues the timer to expire in the specified number of nanokernel ticks. The
37 actual wait time will be at least that much and may be up to one tick more.
38 The expiry handler will be called in ISR context.
40 Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
42 @param aTime Timeout in nanokernel ticks
44 @return KErrNone if no error; KErrInUse if timer is already active.
48 @see NKern::TimerTicks()
50 EXPORT_C TInt NTimer::OneShot(TInt aTime)
52 return OneShot(aTime,FALSE);
56 /** Starts a nanokernel timer in one-shot mode with ISR or DFC callback.
58 Queues the timer to expire in the specified number of nanokernel ticks. The
59 actual wait time will be at least that much and may be up to one tick more.
60 The expiry handler will be called in either ISR context or in the context
61 of the nanokernel timer thread (DfcThread1).
63 Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
65 @param aTime Timeout in nanokernel ticks
66 @param aDfc TRUE if DFC callback required, FALSE if ISR callback required.
68 @return KErrNone if no error; KErrInUse if timer is already active.
72 @see NKern::TimerTicks()
74 EXPORT_C TInt NTimer::OneShot(TInt aTime, TBool aDfc)
76 __NK_ASSERT_DEBUG(aTime>=0);
78 /** iFunction could be set to NULL after NTimer::OneShot(TInt, TDfc&) call.
79 Call-back mechanism cannot be changed in the life time of a timer. */
80 __NK_ASSERT_DEBUG(iFunction!=NULL);
82 TInt irq=NKern::DisableAllInterrupts();
85 NKern::RestoreInterrupts(irq);
88 iCompleteInDfc=TUint8(aDfc?1:0);
89 iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime;
91 NKern::RestoreInterrupts(irq);
95 /** Starts a nanokernel timer in one-shot mode with callback in dfc thread that provided DFC belongs to.
97 Queues the timer to expire in the specified number of nanokernel ticks. The
98 actual wait time will be at least that much and may be up to one tick more.
99 On expiry aDfc will be queued in ISR context.
101 Note that NKern::TimerTicks() can be used to convert milliseconds to ticks.
103 @param aTime Timeout in nanokernel ticks
104 @param aDfc - Dfc to be queued when the timer expires.
106 @return KErrNone if no error; KErrInUse if timer is already active.
110 @see NKern::TimerTicks()
112 EXPORT_C TInt NTimer::OneShot(TInt aTime, TDfc& aDfc)
114 __NK_ASSERT_DEBUG(aTime>=0);
115 TInt irq=NKern::DisableAllInterrupts();
118 NKern::RestoreInterrupts(irq);
123 iPtr = (TAny*) &aDfc;
124 iTriggerTime=TheTimerQ.iMsCount+(TUint32)aTime;
126 NKern::RestoreInterrupts(irq);
131 /** Starts a nanokernel timer in zero-drift periodic mode with ISR or DFC callback.
133 Queues the timer to expire in the specified number of nanokernel ticks,
134 measured from the time at which it last expired. This allows exact periodic
135 timers to be implemented with no drift caused by delays in requeueing the
138 The expiry handler will be called in the same context as the previous timer
139 expiry. Generally the way this is used is that NTimer::OneShot() is used to start
140 the first time interval and this specifies whether the callback is in ISR context
141 or in the context of the nanokernel timer thread (DfcThread1) or other Dfc thread.
142 The expiry handler then uses NTimer::Again() to requeue the timer.
144 @param aTime Timeout in nanokernel ticks
146 @return KErrNone if no error; KErrInUse if timer is already active;
147 KErrArgument if the requested expiry time is in the past.
151 EXPORT_C TInt NTimer::Again(TInt aTime)
153 // Wait aTime from last trigger time - used for periodic timers
156 __NK_ASSERT_DEBUG(aTime>0);
157 TInt irq=NKern::DisableAllInterrupts();
160 NKern::RestoreInterrupts(irq);
163 TUint32 nextTick=TheTimerQ.iMsCount;
164 TUint32 trigger=iTriggerTime+(TUint32)aTime;
165 TUint32 d=trigger-nextTick;
168 NKern::RestoreInterrupts(irq);
169 return KErrArgument; // requested time is in the past
171 iTriggerTime=trigger;
173 NKern::RestoreInterrupts(irq);
178 /** Cancels a nanokernel timer.
180 Removes this timer from the nanokernel timer queue. Does nothing if the
181 timer is inactive or has already expired.
182 Note that if the timer was queued and DFC callback requested it is possible
183 for the expiry handler to run even after Cancel() has been called. This will
184 occur in the case where DfcThread1 is preempted just before calling the
185 expiry handler for this timer and the preempting thread/ISR/IDFC calls
186 Cancel() on the timer.
189 @return TRUE if timer was actually cancelled
190 @return FALSE if timer was not cancelled - this could be because it was not
191 active or because its expiry handler was already running on
192 another CPU or in the timer DFC.
194 EXPORT_C TBool NTimer::Cancel()
197 TInt irq=NKern::DisableAllInterrupts();
198 if (iState>ETransferring) // idle or transferring timers are not on a queue
202 case ETransferring: // signal DFC to abort this iteration
203 TheTimerQ.iTransferringCancelled=TRUE;
205 case ECritical: // signal DFC to abort this iteration
206 TheTimerQ.iCriticalCancelled=TRUE;
210 // Need to clear bit in iPresent if both final queues now empty
211 // NOTE: Timer might actually be on the completed queue rather than the final queue
212 // but the check is harmless in any case.
213 TInt i=iTriggerTime & NTimerQ::ETimerQMask;
214 NTimerQ::STimerQ& q=TheTimerQ.iTickQ[i];
215 if (q.iIntQ.IsEmpty() && q.iDfcQ.IsEmpty())
216 TheTimerQ.iPresent &= ~(1<<i);
219 case EIdle: // nothing to do
221 case EHolding: // just deque
222 case EOrdered: // just deque
226 NKern::RestoreInterrupts(irq);
232 /** Check if a nanokernel timer is pending or not
234 @return TRUE if the timer is pending (OneShot() etc. would return KErrInUse)
235 @return FALSE if the timer is idle (OneShot() etc. would succeed)
241 EXPORT_C TBool NTimer::IsPending()
243 return iState != EIdle;
247 /** Obtains the address of the nanokernel timer queue object.
249 Not intended for general use. Intended only for base ports in order to get
250 the address used to call NTimerQ::Tick() with.
252 @return The address of the nanokernel timer queue object
255 EXPORT_C TAny* NTimerQ::TimerAddress()
261 : iDfc(NTimerQ::DfcFn,this,NULL,KTimerQDfcPriority)
263 // NOTE: All other members are initialised to zero since the single instance
264 // of NTimerQ resides in .bss
267 void NTimerQ::Init1(TInt aTickPeriod)
269 TheTimerQ.iTickPeriod=aTickPeriod;
270 __KTRACE_OPT(KBOOT,DEBUGPRINT("NTimerQ::Init1 - period %d us",aTickPeriod));
271 __KTRACE_OPT(KMEMTRACE, DEBUGPRINT("MT:P %d",aTickPeriod));
274 void NTimerQ::Init3(TDfcQue* aDfcQ)
276 __KTRACE_OPT(KBOOT,DEBUGPRINT("NTimerQ::Init3 DFCQ at %08x",aDfcQ));
277 TheTimerQ.iDfc.SetDfcQ(aDfcQ);
280 #ifndef __MSTIM_MACHINE_CODED__
281 void NTimerQ::Add(NTimer* aTimer)
283 // Internal function to add a timer to the queue.
284 // Enter and return with all interrupts disabled.
287 TInt t=TInt(aTimer->iTriggerTime-iMsCount);
288 if (t<ENumTimerQueues)
292 // >=32ms to expiry, so put on holding queue
293 aTimer->iState=NTimer::EHolding;
294 iHoldingQ.Add(aTimer);
298 void NTimerQ::AddFinal(NTimer* aTimer)
300 // Internal function to add a timer to the corresponding final queue.
301 // Enter and return with all interrupts disabled.
304 TInt i=aTimer->iTriggerTime & ETimerQMask;
306 if (aTimer->iCompleteInDfc)
311 aTimer->iState=NTimer::EFinal;
315 void NTimerQ::DfcFn(TAny* aPtr)
317 ((NTimerQ*)aPtr)->Dfc();
322 // Do deferred timer queue processing and/or DFC completions
327 // First transfer entries on the Ordered queue to the Final queues
330 irq=NKern::DisableAllInterrupts();
331 if (iOrderedQ.IsEmpty())
333 NTimer* pC=(NTimer*)iOrderedQ.First();
334 TInt remain=pC->iTriggerTime-iMsCount;
335 if (remain>=ENumTimerQueues)
338 // If remaining time <32 ticks, add it to final queue;
339 // also if remain < 0 we've 'missed it' so add to final queue.
342 NKern::RestoreInterrupts(irq);
345 NKern::RestoreInterrupts(irq);
348 // Next transfer entries on the Holding queue to the Ordered queue or final queue
351 irq=NKern::DisableAllInterrupts();
352 if (iHoldingQ.IsEmpty())
354 NTimer* pC=(NTimer*)iHoldingQ.First();
356 pC->iState=NTimer::ETransferring;
357 iTransferringCancelled=FALSE;
358 TUint32 trigger=pC->iTriggerTime;
359 if (TInt(trigger-iMsCount)<ENumTimerQueues)
361 // <32ms remaining so put it on final queue
368 NKern::RestoreInterrupts(irq);
371 // we now need to walk ordered queue to find correct position for pC
372 SDblQueLink* anchor=&iOrderedQ.iA;
373 iCriticalCancelled=FALSE;
374 irq=NKern::DisableAllInterrupts();
375 NTimer* pN=(NTimer*)iOrderedQ.First();
376 while (pN!=anchor && !iTransferringCancelled)
378 if ((pN->iTriggerTime-trigger)<0x80000000u)
379 break; // insert before pN
380 pN->iState=NTimer::ECritical;
381 NKern::RestoreInterrupts(irq);
383 irq=NKern::DisableAllInterrupts();
384 if (iCriticalCancelled)
386 pN->iState=NTimer::EOrdered;
387 pN=(NTimer*)pN->iNext;
390 if (iTransferringCancelled)
391 break; // this one has been cancelled, go on to next one
392 if (!iCriticalCancelled)
394 pC->InsertBefore(pN);
395 pC->iState=NTimer::EOrdered;
396 break; // done this one
400 NKern::RestoreInterrupts(irq);
403 NKern::RestoreInterrupts(irq);
406 // Finally do call backs for timers which requested DFC callback
409 irq=NKern::DisableAllInterrupts();
410 if (iCompletedQ.IsEmpty())
412 NTimer* pC=(NTimer*)iCompletedQ.First();
414 pC->iState=NTimer::EIdle;
416 NTimerFn f=pC->iFunction;
417 NKern::RestoreInterrupts(irq);
421 NKern::RestoreInterrupts(irq);
425 /** Tick over the nanokernel timer queue.
426 This function should be called by the base port in the system tick timer ISR.
427 It should not be called at any other time.
428 The value of 'this' to pass is the value returned by NTimerQ::TimerAddress().
430 @see NTimerQ::TimerAddress()
432 EXPORT_C void NTimerQ::Tick()
435 // If there are threads waiting to be released by the tick, enqueue the dfc
436 if (!TheScheduler.iDelayedQ.IsEmpty())
437 TheScheduler.iDelayDfc.Add();
439 TheScheduler.TimesliceTick();
440 TInt irq=NKern::DisableAllInterrupts();
441 TInt i=iMsCount & ETimerQMask;
443 STimerQ* pQ=iTickQ+i;
446 if (!pQ->iDfcQ.IsEmpty())
448 // transfer DFC completions to completed queue and queue DFC
449 iCompletedQ.MoveFrom(&pQ->iDfcQ);
452 if ((i&(ETimerQMask>>1))==0)
454 // Every 16 ticks we check if a DFC is required.
455 // This allows a DFC latency of up to 16 ticks before timers are missed.
456 if (!iHoldingQ.IsEmpty())
457 doDfc=TRUE; // if holding queue nonempty, queue DFC to sort
458 else if (!iOrderedQ.IsEmpty())
460 // if first ordered queue entry expires in <32ms, queue the DFC to transfer
461 NTimer* pC=(NTimer*)iOrderedQ.First();
463 __ASSERT_WITH_MESSAGE_DEBUG(iMsCount<=pC->iTriggerTime, "iMsCount has exceeded pC->iTriggerTime; function called later than expected ","NTimerQ::Tick()");
465 if (TInt(pC->iTriggerTime-iMsCount)<ENumTimerQueues)
469 if (!pQ->iIntQ.IsEmpty())
471 // transfer ISR completions to a temporary queue
472 // careful here - higher priority interrupts could dequeue timers!
473 SDblQue q(&pQ->iIntQ,0);
476 NTimer* pC=(NTimer*)q.First();
478 pC->iState=NTimer::EIdle;
479 NKern::RestoreInterrupts(irq);
481 (*pC->iFunction)(pC->iPtr);
483 ((TDfc*)(pC->iPtr))->Add();
484 irq=NKern::DisableAllInterrupts();
487 NKern::RestoreInterrupts(irq);
493 /** Return the number of ticks before the next nanokernel timer expiry.
494 May on occasion return a pessimistic estimate (i.e. too low).
495 Used by base port to disable the system tick interrupt when the system
498 @return The number of ticks before the next nanokernel timer expiry.
500 @pre Interrupts must be disabled.
502 @post Interrupts are disabled.
504 EXPORT_C TInt NTimerQ::IdleTime()
506 CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::IdleTime");
508 // If there are threads waiting to be released by the tick we can't idle
509 if (!TheScheduler.iDelayedQ.IsEmpty())
512 NTimerQ& m=TheTimerQ;
513 TUint32 next=m.iMsCount; // number of next tick
514 TUint32 p=m.iPresent;
518 // Final queues nonempty
519 TInt nx=next&0x1f; // number of next tick modulo 32
520 p=(p>>nx)|(p<<(32-nx)); // rotate p right by nx (so lsb corresponds to next tick)
521 r=__e32_find_ls1_32(p); // find number of zeros before LS 1
523 if (!m.iHoldingQ.IsEmpty())
525 // Sort operation required - need to process next tick divisible by 16
526 TInt nx=next&0x0f; // number of next tick modulo 16
527 TInt r2=nx?(16-nx):0; // number of ticks before next divisible by 16
531 if (!m.iOrderedQ.IsEmpty())
533 // Timers present on ordered queue
534 NTimer* pC=(NTimer*)m.iOrderedQ.First();
535 TUint32 tt=pC->iTriggerTime;
536 tt=(tt&~0x0f)-16; // time at which transfer to final queue would occur
537 TInt r3=(TInt)(tt-next);
546 /** Advance the nanokernel timer queue by the specified number of ticks.
547 It is assumed that no timers expire as a result of this.
548 Used by base port when system comes out of idle mode after disabling the
549 system tick interrupt to bring the timer queue up to date.
551 @param aTicks Number of ticks skipped due to tick suppression
553 @pre Interrupts must be disabled.
555 @post Interrupts are disabled.
557 EXPORT_C void NTimerQ::Advance(TInt aTicks)
559 CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED,"NTimerQ::Advance");
560 TheTimerQ.iMsCount+=(TUint32)aTicks;
564 /** Returns the period of the nanokernel timer.
565 @return Period in microseconds
569 EXPORT_C TInt NKern::TickPeriod()
571 return TheTimerQ.iTickPeriod;
575 /** Converts a time interval to timer ticks.
577 @param aMilliseconds time interval in milliseconds.
578 @return Number of nanokernel timer ticks. Non-integral results are rounded up.
580 @pre aMilliseconds should be <=2147483 to avoid integer overflow.
583 EXPORT_C TInt NKern::TimerTicks(TInt aMilliseconds)
585 __ASSERT_WITH_MESSAGE_DEBUG(aMilliseconds<=2147483,"aMilliseconds should be <=2147483","NKern::TimerTicks");
586 TUint32 msp=TheTimerQ.iTickPeriod;
587 if (msp==1000) // will be true except on pathological hardware
588 return aMilliseconds;
589 TUint32 us=(TUint32)aMilliseconds*1000;
590 return (us+msp-1)/msp;