author | mikek |
Sun, 27 Jun 2010 21:43:55 +0100 | |
branch | GCC_SURGE |
changeset 181 | bd8f1e65581b |
parent 0 | a41df078684a |
permissions | -rw-r--r-- |
// 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