kernel/eka/nkern/arm/nctimer.cia
author John Imhofe
Mon, 19 Oct 2009 15:55:17 +0100
changeset 0 a41df078684a
permissions -rw-r--r--
Convert Kernelhwsrv package from SFL to EPL kernel\eka\compsupp is subject to the ARM EABI LICENSE userlibandfileserver\fatfilenameconversionplugins\unicodeTables is subject to the Unicode license kernel\eka\kernel\zlib is subject to the zlib license

// 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\arm\nctimer.cia
// Fast Millisecond Timer Implementation
// 
//

#include <e32cia.h>
#include <arm.h>

#ifdef _DEBUG
#define ASM_KILL_LINK(rp,rs)	asm("mov "#rs", #0xdf ");\
								asm("orr "#rs", "#rs", "#rs", lsl #8 ");\
								asm("orr "#rs", "#rs", "#rs", lsl #16 ");\
								asm("str "#rs", ["#rp"] ");\
								asm("str "#rs", ["#rp", #4] ");

#define ASM_KILL_LINK_OFFSET(rp,rs,offset)	asm("mov "#rs", #0xdf ");\
											asm("orr "#rs", "#rs", "#rs", lsl #8 ");\
											asm("orr "#rs", "#rs", "#rs", lsl #16 ");\
											asm("str "#rs", ["#rp", #"#offset"] ");\
											asm("str "#rs", ["#rp", #"#offset"+4] ");
#else
#define ASM_KILL_LINK(rp,rs)
#define ASM_KILL_LINK_OFFSET(rp,rs,offset)
#endif

#ifdef __MSTIM_MACHINE_CODED__

#ifdef _DEBUG
#define __DEBUG_CALLBACK(n)	asm("stmfd sp!, {r0-r3,r12,lr} ");		\
							asm("ldr r0, __TheTimerQ ");			\
							asm("ldr r12, [r0, #%a0]!" : : "i" _FOFF(NTimerQ,iDebugFn));	\
							asm("cmp r12, #0 ");					\
							asm("movne r1, #" #n );					\
							asm("ldrne r0, [r0, #4] ");				\
							asm("movne lr, pc ");					\
							__JUMP(ne,r12);							\
							asm("ldmfd sp!, {r0-r3,r12,lr} ")
#else
#define __DEBUG_CALLBACK(n)
#endif


/** Start 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
	@return	KErrInUse if timer is already active
	@return	KErrArgument if the requested expiry time is in the past
	@pre	Any context
 */
__NAKED__ EXPORT_C TInt NTimer::Again(TInt /*aTime*/)
	{
	asm("mrs r12, cpsr ");
	INTS_OFF(r3, r12, INTS_ALL_OFF);	// all interrupts off
	asm("ldrb r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));		// r3=iState
	asm("ldr r2, __TheTimerQ ");
	asm("cmp r3, #%a0" : : "i" ((TInt)EIdle));
	asm("ldreq r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// r3=iTriggerTime
	asm("bne add_mscb_in_use ");		// if already queued return KErrInUse
	asm("add r3, r3, r1 ");				// add requested time interval
	asm("ldr r1, [r2, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));		// r1=iMsCount
	asm("subs r1, r3, r1 ");			// r1=trigger time-next tick time
	asm("strpl r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// iTriggerTime+=aTime
	asm("bpl AddMsCallBack ");			// if time interval positive, ok
	asm("mov r0, #%a0" : : "i" ((TInt)KErrArgument));	// else return KErrArgument
	asm("b add_mscb_0 ");
	}


/** Start 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.

	@param	aTime Timeout in nanokernel ticks
	@return	KErrNone if no error
	@return	KErrInUse if timer is already active
	@pre	Any context
 */
__NAKED__ EXPORT_C TInt NTimer::OneShot(TInt /*aTime*/)
	{
	asm("mov r2, #0 ");
	// fall through
	}


/** Start 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).

	@param	aTime Timeout in nanokernel ticks
	@param	aDfc TRUE if DFC callback required, FALSE if ISR callback required.
	@return	KErrNone if no error
	@return	KErrInUse if timer is already active
	@pre	Any context
 */
__NAKED__ EXPORT_C TInt NTimer::OneShot(TInt /*aTime*/, TBool /*aDfc*/)
	{
	asm("mrs r12, cpsr ");
	INTS_OFF(r3, r12, INTS_ALL_OFF);	// all interrupts off
	asm("ldrb r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));		// r3=iState
	asm("cmp r3, #%a0" : : "i" ((TInt)EIdle));
	asm("bne add_mscb_in_use ");		// if already queued return KErrInUse
	asm("strb r2, [r0, #%a0]" : : "i" _FOFF(NTimer,iCompleteInDfc));	// iCompleteInDfc=aDfc
	asm("ldr r2, __TheTimerQ ");
	asm("ldr r3, [r2, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));	// r3=iMsCount
	asm("add r3, r3, r1 ");
	asm("str r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// iTriggerTime=ms count + aTime

	// r0->CallBack, r2=TheTimerQ, r1=time interval, r3=trigger time
	asm("AddMsCallBack: ");
	asm("cmp r1, #32 ");				// compare interval with 32ms
	asm("bge add_mscb_holding ");		// if >=32ms put it on holding queue
	asm("ldrb r1, [r0, #%a0]" : : "i" _FOFF(NTimer,iCompleteInDfc));	// r1=iCompleteInDfc
	asm("and r3, r3, #0x1f ");			// r3=trigger time & 0x1f
	asm("cmp r1, #0 ");
	asm("add r1, r2, r3, lsl #4 ");		// r1->IntQ corresponding to trigger time
	asm("addne r1, r1, #8 ");			// if (iCompleteInDfc), r1 points to DfcQ
	asm("ldr r3, [r1, #4] ");			// r3=pQ->iA.iPrev
	asm("str r0, [r1, #4] ");			// pQ->iA.iPrev=pC
	asm("str r0, [r3, #0] ");			// pQ->iA.iPrev->iNext=pC
	asm("stmia r0, {r1,r3} ");			// pC->iNext=&pQ->iA, pC->iPrev=pQ->iA.iPrev
	asm("mov r1, #%a0" : : "i" ((TInt)EFinal));
	asm("strb r1, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));		// iState=EFinal
	asm("ldr r0, [r0, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// r0=iTriggerTime
	asm("ldr r3, [r2, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));	// r3=TheTimerQ->iPresent
	asm("and r0, r0, #0x1f ");
	asm("mov r1, #1 ");
	asm("orr r3, r3, r1, lsl r0 ");		// iPresent |= (1<<index)
	asm("str r3, [r2, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));
	asm("mov r0, #0 ");					// return KErrNone
	asm("msr cpsr, r12 ");
	__JUMP(,lr);

	asm("add_mscb_holding: ");
	asm("ldr r3, [r2, #%a0]!" : : "i" _FOFF(NTimerQ,iHoldingQ.iA.iPrev));	// r3=pQ->iPrev, r2=&iHoldingQ.iA.iPrev
	asm("mov r1, #%a0" : : "i" ((TInt)EHolding));
	asm("strb r1, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));	// iState=EHolding
	asm("str r0, [r2], #-4 ");			// pQ->iPrev=pC, r2=&iHoldingQ
	asm("str r0, [r3, #0] ");			// pQ->iPrev->iNext=pC
	asm("stmia r0, {r2,r3} ");			// pC->iNext=pQ, pC->iPrev=pQ->iPrev
	asm("mov r0, #0 ");					// return KErrNone

	asm("add_mscb_0: ");
	asm("msr cpsr, r12 ");
	__JUMP(,lr);

	asm("add_mscb_in_use: ");
	asm("mov r0, #%a0" : : "i" ((TInt)KErrInUse));		// return KErrInUse
	asm("msr cpsr, r12 ");
	__JUMP(,lr);

	asm("__TheTimerQ: ");
	asm(".word TheTimerQ ");
	}


/** 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()
 */

__NAKED__ EXPORT_C TInt NTimer::OneShot(TInt /*aTime*/, TDfc& /*aDfc*/)
	{
	asm("mrs r12, cpsr ");
	INTS_OFF(r3, r12, INTS_ALL_OFF);	// all interrupts off
	asm("ldrb r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));		// r3=iState
	asm("cmp r3, #%a0" : : "i" ((TInt)EIdle));
	asm("bne add_mscb_in_use ");		// if already queued return KErrInUse
	asm("mov r3, #0 ");
	asm("strb r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iCompleteInDfc));	// iCompleteInDfc=0
	asm("str r3,  [r0, #%a0]" : : "i" _FOFF(NTimer,iFunction));			// iFunction=NULL
	asm("str r2,  [r0, #%a0]" : : "i" _FOFF(NTimer,iPtr));				// iPtr= &aDfc
	asm("ldr r2, __TheTimerQ ");
	asm("ldr r3, [r2, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));	// r3=iMsCount
	asm("add r3, r3, r1 ");
	asm("str r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// iTriggerTime=ms count + aTime
	asm("b AddMsCallBack ");
	}


/** Cancel 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 __NAKED__ TBool NTimer::Cancel()
	{
	asm("mrs r12, cpsr ");
	INTS_OFF(r3, r12, INTS_ALL_OFF);	// all interrupts off
	asm("ldrb r3, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));
	asm("mov r1, #0 ");
	asm("cmp r3, #%a0" : : "i" ((TInt)ETransferring));
	asm("movcc r0, #0 ");				// if EIdle, nothing to do, return FALSE
	asm("bcc cancel_idle ");
	asm("strb r1, [r0, #%a0]" : : "i" _FOFF(NTimer,iState));	// iState=EIdle
	asm("beq cancel_xfer ");			// if ETransferring, branch
	asm("ldmia r0, {r1,r2} ");			// If queued, dequeue - r1=next, r2=prev
	asm("cmp r3, #%a0" : : "i" ((TInt)ECritical));
	asm("str r1, [r2, #0] ");			// if queued, prev->next=next
	asm("str r2, [r1, #4] ");			// and next->prev=prev
	ASM_KILL_LINK(r0,r1);
	asm("ldrcs r1, __TheTimerQ ");
	asm("ldrhi r0, [r0, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// r0=iTriggerTime
	asm("ldrhi r3, [r1, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));	// r3=iMsCount
	asm("bcc cancel_done ");			// if EHolding or EOrdered, finished
	asm("beq cancel_critical ");		// if ECritical, branch
										// r1->ms timer, state was EFinal
	asm("subs r3, r0, r3 ");			// r3=trigger time - next tick
	asm("bmi cancel_done ");			// if trigger time already expired, don't touch iPresent (was on iCompletedQ)
	asm("and r0, r0, #0x1f ");			// r0=iTriggerTime&0x1f = queue index
	asm("mov r3, r1 ");
	asm("ldr r2, [r3, r0, lsl #4]! ");	// r3->iIntQ for this timer, r2=iIntQ head pointer
	asm("cmp r2, r3 ");
	asm("bne cancel_done ");			// iIntQ not empty so finished
	asm("ldr r2, [r3, #8]! ");			// r2=iDfcQ head pointer
	asm("cmp r2, r3 ");
	asm("bne cancel_done ");			// iDfcQ not empty so finished
	asm("ldr r2, [r1, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));	// r2=TheTimerQ->iPresent
	asm("mov r3, #1 ");
	asm("bic r2, r2, r3, lsl r0 ");		// iPresent &= ~(1<<index)
	asm("str r2, [r1, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));

	asm("cancel_done: ");
	asm("mov r0, #1 ");					// return TRUE

	asm("cancel_idle: ");
	asm("msr cpsr, r12 ");
	__JUMP(,lr);

	asm("cancel_xfer: ");
	asm("ldr r1, __TheTimerQ ");		// r1->ms timer, state was ETransferring
	asm("strb r3, [r1, #%a0]" : : "i" _FOFF(NTimerQ,iTransferringCancelled));
	asm("msr cpsr, r12 ");
	asm("mov r0, #1 ");					// return TRUE
	__JUMP(,lr);

	asm("cancel_critical: ");			// r1->ms timer, state was ECritical
	asm("strb r3, [r1, #%a0]" : : "i" _FOFF(NTimerQ,iCriticalCancelled));
	asm("msr cpsr, r12 ");
	asm("mov r0, #1 ");					// return TRUE
	__JUMP(,lr);
	}


/** 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 __NAKED__ TInt NTimerQ::IdleTime()
	{
	ASM_CHECK_PRECONDITIONS(MASK_INTERRUPTS_DISABLED);
#ifdef _DEBUG
	asm("ldr r1, __TheScheduler ");
	asm("ldr r2, [r1, #%a0]! " : : "i" _FOFF(TScheduler,iDelayedQ));
	// r2 = iA.iNext, r1 = iA
	asm("cmp r2, r1 ");		
	asm("movne r0, #1 ");		// if there are delayed threads, prevent idle
	__JUMP(ne,lr);
#endif
	asm("ldr r12, __TheTimerQ ");
	asm("mvn r0, #0x80000000 ");		// set r0=KMaxTInt initially
	asm("ldr r2, [r12, #%a0]!" : : "i" _FOFF(NTimerQ,iOrderedQ));	// r12->iOrderedQ, r2=iOrderedQ first
	asm("ldr r3, [r12, #-12] ");		// r3=next tick number
	asm("cmp r2, r12 ");				// check if iOrderedQ empty
	asm("ldrne r0, [r2, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// if not, r0=ordered Q first->trigger time
	asm("ldr r1, [r12, #-8]! ");		// r1=iHoldingQ first, r12->iHoldingQ
	asm("bicne r0, r0, #0x0f ");
	asm("subne r0, r0, #16 ");			// r0=tick at which transfer to final queue would occur
	asm("subne r0, r0, r3 ");			// return value = trigger time - iMsCount
	asm("cmp r1, r12 ");				// holding Q empty?
	asm("ldr r1, [r12, #-8] ");			// r1=iPresent
	asm("and r12, r3, #0x1f ");			// r12=next tick mod 32
	asm("beq 1f ");						// branch if holding Q empty
	asm("ands r2, r3, #0x0f ");			// else r2=next tick no. mod 16
	asm("rsbne r2, r2, #16 ");			// if nonzero, subtract from 16 to give #ticks before next multiple of 16
	asm("cmp r2, r0 ");
	asm("movlt r0, r2 ");				// update result if necessary
	asm("1: ");
	asm("movs r1, r1, ror r12 ");		// r1=iPresent rotated so that LSB corresponds to next tick
	__JUMP(eq,lr);						// if iPresent=0, finished
	asm("mov r3, #0 ");					// r3 will accumulate bit number of least significant 1
	asm("movs r2, r1, lsl #16 ");
	asm("movne r1, r2 ");
	asm("addeq r3, r3, #16 ");
	asm("movs r2, r1, lsl #8 ");
	asm("movne r1, r2 ");
	asm("addeq r3, r3, #8 ");
	asm("movs r2, r1, lsl #4 ");
	asm("movne r1, r2 ");
	asm("addeq r3, r3, #4 ");
	asm("movs r2, r1, lsl #2 ");
	asm("movne r1, r2 ");
	asm("addeq r3, r3, #2 ");
	asm("movs r2, r1, lsl #1 ");
	asm("addeq r3, r3, #1 ");
	asm("cmp r3, r0 ");
	asm("movlt r0, r3 ");				// update result if necessary
	__JUMP(,lr);
	}


/** 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()
 */
__NAKED__ EXPORT_C void NTimerQ::Tick()
	{


#ifdef _DEBUG
	asm("ldr r1, __TheScheduler ");
	asm("ldr r2, [r1, #%a0]! " : : "i" _FOFF(TScheduler,iDelayedQ));
	// r2 = iA.iNext, r1 = iA
	asm("cmp r2, r1 ");		
	asm("beq 1f ");				// no delayed threads, don't queue dfc
	asm("stmfd sp!, {r0,lr} ");
	asm("ldr r1, __TheScheduler ");
	asm("add r0, r1, #%a0 " : : "i" _FOFF(TScheduler,iDelayDfc));
	asm("bl " CSM_ZN4TDfc3AddEv);
	asm("ldmfd sp!, {r0,lr} ");
	asm("1: ");
#endif

	// Enter with r0 pointing to NTimerQ
	asm("ldr r1, __TheScheduler ");
	asm("mrs r12, cpsr ");

	// do the timeslice tick - on ARM __SCHEDULER_MACHINE_CODED is mandatory
	asm("ldr r2, [r1, #%a0]" : : "i" _FOFF(TScheduler,iCurrentThread));
	asm("ldr r3, [r2, #%a0]" : : "i" _FOFF(NThread,iTime));
	asm("subs r3, r3, #1 ");
	asm("strge r3, [r2, #%a0]" : : "i" _FOFF(NThread,iTime));
	asm("streqb r12, [r1, #%a0]" : : "i" _FOFF(TScheduler,iRescheduleNeededFlag));	// r12 lower byte is never 0
	INTS_OFF(r3, r12, INTS_ALL_OFF);	// disable all interrupts

	asm("ldr r1, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));	// r1=iMsCount
	asm("ldr r3, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));	// r3=iPresent
	asm("and r2, r1, #0x1f ");			// r2=iMsCount & 0x1f
	asm("add r1, r1, #1 ");
	asm("str r1, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));	// iMsCount++
	asm("mov r1, #1 ");
	asm("tst r3, r1, lsl r2 ");			// test iPresent bit for this tick
	asm("bic r1, r3, r1, lsl r2 ");		// clear iPresent bit
	asm("strne r1, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));	// update iPresent if necessary
	asm("bne mstim_tick_1 ");			// if bit was set, we have work to do
	asm("tst r2, #0x0f ");				// else test for tick 0 or 16
	__MSR_CPSR_C(ne, r12);			// if neither return
	__JUMP(ne,lr);

	asm("mstim_tick_1: ");				// get here if timers complete this tick
	asm("stmfd sp!, {r4-r6,lr} ");
	asm("add r1, r0, r2, lsl #4 ");		// r1->IntQ for this tick
	asm("ldr r3, [r1, #8]! ");			// r1->DfcQ and r3=DfcQ first
	asm("mov r5, #0 ");					// r5=doDfc=FALSE
	asm("cmp r3, r1 ");
	asm("beq mstim_tick_2 ");			// skip if DfcQ empty

	// Move DFC completions from iDfcQ to iCompletedQ
	asm("ldr lr, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iCompletedQ.iA.iPrev));	// lr=last completed
	asm("ldr r4, [r1, #4] ");			// r4=DfcQ last
	asm("add r5, r0, #%a0" : : "i" _FOFF(NTimerQ,iDfc));	// doDfc=TRUE
	asm("str r3, [lr, #0] ");			// old last pending->next = DfcQ first
	asm("str r4, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iCompletedQ.iA.iPrev));	// last pending=DfcQ last
	asm("str lr, [r3, #4] ");			// DfcQ first->prev = old last pending
	asm("add lr, r0, #%a0" : : "i" _FOFF(NTimerQ,iCompletedQ));				// lr=&iCompletedQ.iA
	asm("str lr, [r4, #0] ");			// DfcQ last->next=&iPending
	asm("str r1, [r1, #0] ");			// DfcQ first=&DfcQ
	asm("str r1, [r1, #4] ");			// DfcQ last=&DfcQ

	asm("mstim_tick_2: ");
	asm("tst r2, #0x0f ");				// check for tick 0 or 16
	asm("bne mstim_tick_3 ");			// skip if not

	// Tick 0 or 16 - must check holding queue and ordered queue
	asm("ldr r3, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iHoldingQ));	// r3=iHoldingQ first
	asm("add r6, r0, #%a0" : : "i" _FOFF(NTimerQ,iHoldingQ));	// r6=&iHoldingQ
	asm("cmp r3, r6 ");
	asm("addne r5, r0, #%a0" : : "i" _FOFF(NTimerQ,iDfc));	// if iHoldingQ nonempty, doDfc=TRUE and skip ordered queue check
	asm("bne mstim_tick_3 ");			// skip if iHoldingQ nonempty
	asm("ldr r3, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iOrderedQ));	// r3=iOrderedQ first
	asm("add r6, r0, #%a0" : : "i" _FOFF(NTimerQ,iOrderedQ));	// r6=&iOrderedQ
	asm("cmp r3, r6 ");
	asm("beq mstim_tick_3 ");			// skip if iOrderedQ empty
	asm("ldr r4, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iMsCount));		// else r4=iMsCount
	asm("ldr r3, [r3, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// and r3=trigger time of first on ordered queue
	asm("sub r3, r3, r4 ");				// r3=trigger time-iMsCount
	asm("cmp r3, #31 ");
	asm("addls r5, r0, #%a0" : : "i" _FOFF(NTimerQ,iDfc));	// if first expiry in <=31ms, doDfc=TRUE

	// Check iIntQ
	asm("mstim_tick_3: ");
	asm("ldr r3, [r1, #-8]! ");			// r1->iIntQ, r3=iIntQ first
	asm("mov r6, r12 ");				// r6=original cpsr
	asm("cmp r3, r1 ");					// test if iIntQ empty
	asm("beq mstim_tick_4 ");			// branch if it is

	// Transfer iIntQ to a temporary queue
	asm("ldr r4, [r1, #4] ");			// r4=iIntQ last
	asm("str r1, [r1, #0] ");			// clear iIntQ
	asm("str r1, [r1, #4] ");
	asm("stmfd sp!, {r3,r4} ");			// copy queue onto stack
	asm("str sp, [r4, #0] ");			// iIntQ last->next=sp
	asm("str sp, [r3, #4] ");			// iIntQ first->prev=sp
	INTS_OFF_1(r4, r6, INTS_ALL_OFF);	// r4=cpsr with all interrupts off

	// Walk the temporary queue and complete timers
	asm("mstim_tick_5: ");
	INTS_OFF_2(r4, r6, INTS_ALL_OFF);	// all interrupts off
	asm("ldr r0, [sp, #0] ");			// r0=q.iNext
	asm("mov r3, #%a0" : : "i" ((TInt)NTimer::EIdle));
	asm("cmp r0, sp ");					// end of queue?
	asm("beq mstim_tick_6 ");			// if so, branch out
	asm("ldmia r0!, {r1,r2} ");			// r1=next r2=prev, r0->iPtr
	asm("strb r3, [r0, #%a0]" : : "i" (_FOFF(NTimer,iState)-8));	// iState=EIdle
	ASM_KILL_LINK_OFFSET(r0,r12,-8);
	asm("ldmia r0, {r0,r12} ");			// r0=iPtr, r12=iFunction
	asm("str r1, [r2, #0] ");			// prev->next=next
	asm("str r2, [r1, #4] ");			// next->prev=prev
	asm("adr lr, mstim_tick_5 ");		// return to mstim_tick_5
	asm("msr cpsr, r6 ");				// restore interrupts
	asm("cmp r12, #0 ");				// iFunction==NULL ?
	asm("beq mstim_tick_7 ");			// if so queue Dfc (iPtr is a pointer to TDfc )
	__JUMP(,r12);						// call timer callback with r0=iPtr
	asm("b mstim_tick_6 ");				// skip queuing of Dfc

	asm("mstim_tick_7: ");
	asm("b  " CSM_ZN4TDfc3AddEv);		// add the DFC with r0=iPtr - a pointer to TDfc

	asm("mstim_tick_6: ");
	asm("add sp, sp, #8 ");				// take temporary queue off stack

	asm("mstim_tick_4: ");
	asm("msr cpsr, r6 ");				// restore original interrupt state
	asm("movs r0, r5 ");				// DFC needed? if so, r0->iDfc
	asm("ldmfd sp!, {r4-r6,lr} ");		// restore registers
	asm("bne  " CSM_ZN4TDfc3AddEv);				// add the DFC if required
	__JUMP(,lr);						// if no DFC needed, return

	asm("__TheScheduler: ");
	asm(".word TheScheduler ");
	}

__NAKED__ void NTimerQ::DfcFn(TAny* /*aPtr*/)
	{
	// Enter with r0 pointing to NTimerQ
	asm("stmfd sp!, {r7-r11,lr} ");
	SET_INTS_1(r11, MODE_SVC, INTS_ALL_ON);		// always called from SVC mode 
	SET_INTS_1(r10, MODE_SVC, INTS_ALL_OFF);	// with interruts enabled

	// First transfer entries on the Ordered queue to the Final queues
	asm("mstim_dfc_0: ");
	SET_INTS_2(r10, MODE_SVC, INTS_ALL_OFF);	// disable interrupts
	asm("ldr r1, [r0, #%a0]!" : : "i" _FOFF(NTimerQ,iOrderedQ));	// r0->iOrderedQ, r1=orderedQ first
	asm("cmp r1, r0 ");
	asm("beq mstim_dfc_1 ");			// ordered Q empty so move to next stage
	asm("ldr r2, [r1, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// r2=r1->trigger time
	asm("ldr r3, [r0, #-12] ");			// r3=iMsCount
	asm("subs r3, r2, r3 ");			// r3=trigger time-iMsCount
	asm("cmp r3, #31 ");				// test if remaining time <32ms or has already passed
	asm("bgt mstim_dfc_1 ");			// if >31ms, move to next stage (signed comparison to catch already passed case)
	asm("sub r0, r0, #%a0" : : "i" _FOFF(NTimerQ,iOrderedQ));		// r0->NTimerQ
	asm("bl dequeaddfinal ");			// <=31ms, so deque and add to final queue
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	__DEBUG_CALLBACK(0);
	asm("b mstim_dfc_0 ");

	asm("mstim_dfc_1: ");
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	asm("sub r0, r0, #%a0" : : "i" _FOFF(NTimerQ,iOrderedQ));		// r0->NTimerQ
	__DEBUG_CALLBACK(1);

	// Next transfer entries on the Holding queue to the Ordered queue or final queue
	asm("mstim_dfc_2: ");
	SET_INTS_2(r10, MODE_SVC, INTS_ALL_OFF);	// disable interrupts
	asm("ldr r1, [r0, #%a0]!" : : "i" _FOFF(NTimerQ,iHoldingQ));	// r0->iHoldingQ, r1=holdingQ first
	asm("cmp r1, r0 ");
	asm("beq mstim_dfc_3 ");			// holding Q empty so move to next stage
	asm("ldr r2, [r1, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// r2=r1->trigger time
	asm("ldr r3, [r0, #-4] ");			// r3=iMsCount
	asm("sub r0, r0, #%a0" : : "i" _FOFF(NTimerQ,iHoldingQ));		// r0->NTimerQ
	asm("subs r3, r2, r3 ");			// r3=trigger time-iMsCount
	asm("cmp r3, #31 ");				// test if remaining time <32ms or has already passed
	asm("bgt mstim_dfc_4 ");			// if >31ms, need to put it on the ordered Q (signed comparison to catch late case)
	asm("bl dequeaddfinal ");			// <=31ms or already passed, so deque and add to final queue

	asm("mstim_dfc_7: ");
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	__DEBUG_CALLBACK(2);
	asm("b mstim_dfc_2 ");				// process next holding Q entry

	// need to put entry r1 trigger time r2 on the ordered Q
	asm("mstim_dfc_4: ");
	asm("ldmia r1, {r3,r12} ");			// r3=r1->next, r12=r1->prev
	asm("mov r9, #0 ");
	asm("strb r9, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iTransferringCancelled));	// iTransferringCancelled=0
	asm("str r3, [r12, #0] ");			// prev->next=next
	asm("str r12, [r3, #4] ");			// next->prev=prev
	asm("mov r3, #%a0" : : "i" ((TInt)NTimer::ETransferring));
	asm("strb r3, [r1, #%a0]" : : "i" _FOFF(NTimer,iState));		// r1->iState=ETransferring

	asm("mstim_dfc_5: ");
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	asm("add lr, r0, #%a0" : : "i" _FOFF(NTimerQ,iOrderedQ));	// lr=&iOrderedQ.iA
	__DEBUG_CALLBACK(3);
	SET_INTS_2(r10, MODE_SVC, INTS_ALL_OFF);	// disable interrupts

	asm("mstim_dfc_9: ");
	asm("ldrb r12, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iTransferringCancelled));
	asm("ldr r3, [lr, #0] ");			// r3=iOrderedQ first
	asm("cmp r12, #0 ");
	asm("bne mstim_dfc_7 ");			// Entry r1 has been cancelled so move to next one
	asm("strb r9, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iCriticalCancelled));	// iCriticalCancelled=0

	// Walk iOrderedQ to find correct position for this entry
	asm("mstim_dfc_6: ");
	asm("cmp r3, lr ");					// reached end of ordered Q?
	asm("ldrne r12, [r3, #%a0]" : : "i" _FOFF(NTimer,iTriggerTime));	// if not, r12=r3->trigger time
	asm("beq mstim_dfc_8 ");			// branch if we have
	asm("mov r8, #%a0" : : "i" ((TInt)NTimer::ECritical));
	asm("subs r12, r12, r2 ");			// r12=r3->trigger - r1->trigger
	asm("bpl mstim_dfc_8 ");			// branch if r3 expires after r1
	asm("strb r8, [r3, #%a0]" : : "i" _FOFF(NTimer,iState));		// r3->iState=ECritical
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	asm("mov r8, #%a0" : : "i" ((TInt)NTimer::EOrdered));
	__DEBUG_CALLBACK(4);
	SET_INTS_2(r10, MODE_SVC, INTS_ALL_OFF);	// disable interrupts
	asm("ldr r12, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iTransferringCancelled));
	asm("tst r12, #0xff00 ");			// test iCriticalCancelled
	asm("streqb r8, [r3, #%a0]" : : "i" _FOFF(NTimer,iState));	// if not set, r3->iState=EOrdered
	asm("cmp r12, #0 ");				// test iTransferringCancelled and iCriticalCancelled
	asm("ldreq r3, [r3, #0] ");			// if neither set r3=r3->iNext
	asm("beq mstim_dfc_6 ");			// and inspect next ordered Q entry
	asm("b mstim_dfc_9 ");				// if either set, start again from beginning of ordered Q

	asm("mstim_dfc_8: ");				// if we get to here we need to insert r1 before r3
	asm("ldr r12, [r3, #4] ");			// r12=r3->iPrev
	asm("mov r8, #%a0" : : "i" ((TInt)NTimer::EOrdered));
	asm("strb r8, [r1, #%a0]" : : "i" _FOFF(NTimer,iState));		// r1->iState=EOrdered
	asm("str r1, [r3, #4] ");			// r3->prev=r1
	asm("str r1, [r12, #0] ");			// r3->prev->next=r1
	asm("stmia r1, {r3,r12} ");			// r1->next=r3, r1->prev=r3->prev
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	__DEBUG_CALLBACK(5);
	asm("b mstim_dfc_2 ");				// process next holding Q entry

	// Get here when all holding Q entries processed
	asm("mstim_dfc_3: ");
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	__DEBUG_CALLBACK(6);
	asm("add r8, r0, #16 ");			// r8->iCompletedQ

	// Finally do call backs for timers which requested DFC callback
	asm("mstim_dfc_10: ");
	SET_INTS_2(r10, MODE_SVC, INTS_ALL_OFF);	// disable interrupts
	asm("ldr r9, [r8, #0] ");			// r9=completed Q first
	asm("mov r3, #%a0" : : "i" ((TInt)NTimer::EIdle));
	asm("cmp r9, r8 ");					// Is completed Q empty?
	asm("beq mstim_dfc_11 ");			// branch out if it is
	asm("ldmia r9!, {r1,r2} ");			// r1=r9->next, r2=r9->prev, r9->iPtr of completed entry
	asm("strb r3, [r9, #%a0]" : : "i" (_FOFF(NTimer,iState)-8));	// iState=EIdle for completed entry
	asm("ldmia r9, {r0,r3} ");			// r0=iPtr, r3=function address
	ASM_KILL_LINK_OFFSET(r9,r12,-8);
	asm("str r1, [r2, #0] ");			// prev->next=next
	asm("str r2, [r1, #4] ");			// next->prev=prev
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	__DEBUG_CALLBACK(7);
	asm("adr lr, mstim_dfc_10 ");		// return to mstim_dfc_10
	__JUMP(,r3);						// call back with r0=iPtr

	// All done
	asm("mstim_dfc_11: ");	
	SET_INTS_2(r11, MODE_SVC, INTS_ALL_ON);	// let interrupts in
	asm("ldmfd sp!, {r7-r11,pc} ");		// and return

	// Subroutine dequeaddfinal
	// Deque the NTimer pointed to by r1 and put it on its final queue
	// Enter with r0->NTimerQ, r1->NTimer, r2=r1->iTriggerTime
	// Enter and leave with interrupts disabled
	// Can modify r1-r3,r8,r9,r12
	asm("dequeaddfinal: ");
	asm("ldmia r1, {r8,r9} ");			// r8=r1->next, r9=r1->prev
	asm("mov r3, #%a0" : : "i" ((TInt)NTimer::EFinal));
	asm("strb r3, [r1, #%a0]" : : "i" _FOFF(NTimer,iState));	// iState=EFinal
	asm("str r8, [r9, #0] ");			// prev->next=next
	asm("str r9, [r8, #4] ");			// next->prev=prev
	asm("ldr r12, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));	// r12=timer iPresent
	asm("and r2, r2, #0x1f ");			// r2=trigger time & 0x1f
	asm("mov r3, #1 ");
	asm("orr r12, r12, r3, lsl r2 ");	// set bit in iPresent
	asm("ldrb r3, [r1, #%a0]" : : "i" _FOFF(NTimer,iCompleteInDfc));	// r3=iCompleteInDfc
	asm("str r12, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iPresent));
	asm("add r2, r0, r2, lsl #4 ");		// r2->iIntQ for this timer
	asm("cmp r3, #0 ");
	asm("addne r2, r2, #8 ");			// if iCompleteInDfc, r2->iDfcQ for this timer
	asm("ldr r12, [r2, #4] ");			// r12->last on queue
	asm("str r1, [r2, #4] ");			// queue->last=this
	asm("str r1, [r12, #0] ");			// last->next=this
	asm("stmia r1, {r2,r12} ");			// this->next=&queue, this->prev=last on queue
	__JUMP(,lr);
	}
#endif