--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/kernel/eka/nkern/win32/ncsched.cpp Thu Dec 17 09:24:54 2009 +0200
@@ -0,0 +1,942 @@
+// 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\win32\ncsched.cpp
+//
+//
+
+// NThreadBase member data
+#define __INCLUDE_NTHREADBASE_DEFINES__
+
+#include <e32cmn.h>
+#include <e32cmn_private.h>
+#include "nk_priv.h"
+
+#ifdef __EMI_SUPPORT__
+extern void EMI_AddTaskSwitchEvent(TAny* aPrevious, TAny* aNext);
+extern void EMI_CheckDfcTag(TAny* aNext);
+#endif
+typedef void (*ProcessHandler)(TAny* aAddressSpace);
+
+static DWORD TlsIndex = TLS_OUT_OF_INDEXES;
+
+static NThreadBase* SelectThread(TScheduler& aS)
+//
+// Select the next thread to run.
+// This is the heart of the rescheduling algorithm.
+//
+ {
+ NThreadBase* t = static_cast<NThreadBase*>(aS.First());
+ __NK_ASSERT_DEBUG(t);
+#ifdef _DEBUG
+ if (t->iHeldFastMutex)
+ {
+ __KTRACE_OPT(KSCHED2,DEBUGPRINT("Resched init->%T, Holding %M",t,t->iHeldFastMutex));
+ }
+ else
+ {
+ __KTRACE_OPT(KSCHED2,DEBUGPRINT("Resched init->%T",t));
+ }
+#endif
+ if (t->iTime == 0 && !t->Alone())
+ {
+ // round robin
+ // get here if thread's timeslice has expired and there is another
+ // thread ready at the same priority
+ if (t->iHeldFastMutex)
+ {
+ // round-robin deferred due to fast mutex held
+ t->iHeldFastMutex->iWaiting = 1;
+ return t;
+ }
+ t->iTime = t->iTimeslice; // reset old thread time slice
+ t = static_cast<NThreadBase*>(t->iNext); // next thread
+ aS.iQueue[t->iPriority] = t; // make it first in list
+ __KTRACE_OPT(KSCHED2,DEBUGPRINT("RoundRobin->%T",t));
+ }
+ if (t->iHeldFastMutex)
+ {
+ if (t->iHeldFastMutex == &aS.iLock)
+ {
+ // thread holds system lock: use it
+ return t;
+ }
+ if ((t->i_ThrdAttr & KThreadAttImplicitSystemLock) != 0 && aS.iLock.iHoldingThread)
+ t->iHeldFastMutex->iWaiting = 1;
+ __NK_ASSERT_DEBUG((t->i_ThrdAttr & KThreadAttAddressSpace) == 0);
+/*
+ Check for an address space change. Not implemented for Win32, but useful as
+ documentaiton of the algorithm.
+
+ if ((t->i_ThrdAttr & KThreadAttAddressSpace) != 0 && t->iAddressSpace != aS.iAddressSpace)
+ t->iHeldFastMutex->iWaiting = 1;
+*/
+ }
+ else if (t->iWaitFastMutex && t->iWaitFastMutex->iHoldingThread)
+ {
+ __KTRACE_OPT(KSCHED2,DEBUGPRINT("Resched inter->%T, Blocked on %M",t->iWaitFastMutex->iHoldingThread,t->iWaitFastMutex));
+ t = t->iWaitFastMutex->iHoldingThread;
+ }
+ else if (t->i_ThrdAttr & KThreadAttImplicitSystemLock)
+ {
+ // implicit system lock required
+ if (aS.iLock.iHoldingThread)
+ {
+ // system lock held, switch to that thread
+ t = aS.iLock.iHoldingThread;
+ __KTRACE_OPT(KSCHED2,DEBUGPRINT("Resched inter->%T (IMP SYS)",t));
+ t->iHeldFastMutex->iWaiting = 1; // aS.iLock.iWaiting = 1;
+ return t;
+ }
+ __NK_ASSERT_DEBUG((t->i_ThrdAttr & KThreadAttAddressSpace) == 0);
+/*
+ Check for an address space change. Not implemented for Win32, but useful as
+ documentaiton of the algorithm.
+
+ if ((t->i_ThrdAttr & KThreadAttAddressSpace) != 0 || t->iAddressSpace != aS.iAddressSpace)
+ {
+ // what do we do now?
+ __NK_ASSERT_DEBUG(FALSE);
+ }
+*/
+ }
+ return t;
+ }
+
+// from NThread
+#undef i_ThrdAttr
+
+TBool NThread::WakeUp()
+//
+// Wake up the thread. What to do depends on whether we were preempted or voluntarily
+// rescheduled.
+//
+// Return TRUE if we need to immediately reschedule again because we had to unlock
+// the kernel but there are DFCs pending. In this case, the thread does not wake up.
+//
+// NB. kernel is locked
+//
+ {
+ switch (iWakeup)
+ {
+ default:
+ FAULT();
+ case EIdle:
+ __NK_ASSERT_ALWAYS(TheScheduler.iCurrentThread == this);
+ __NK_ASSERT_ALWAYS(SetEvent(iScheduleLock));
+ break;
+ case ERelease:
+ TheScheduler.iCurrentThread = this;
+ __NK_ASSERT_ALWAYS(SetEvent(iScheduleLock));
+ break;
+ case EResumeLocked:
+ // The thread is Win32 suspended and must be resumed.
+ //
+ // A newly created thread does not need the kernel unlocked so we can
+ // just resume the suspended thread
+ //
+ __KTRACE_OPT(KSCHED,DEBUGPRINT("Win32Resume->%T",this));
+ iWakeup = ERelease;
+ TheScheduler.iCurrentThread = this;
+ if (TheScheduler.iProcessHandler)
+ (*ProcessHandler(TheScheduler.iProcessHandler))(iAddressSpace); // new thread will need to have its static data updated
+ __NK_ASSERT_ALWAYS(TInt(ResumeThread(iWinThread)) > 0); // check thread was previously suspended
+ break;
+ case EResumeDiverted:
+ // The thread is Win32 suspended and must be resumed.
+ //
+ // The thread needs to be diverted, and does not need the kernel
+ // unlocked.
+ //
+ // It's safe the divert the thread here because we called
+ // IsSafeToPreempt() when we suspended it - otherwise the diversion
+ // could get lost.
+ //
+ __KTRACE_OPT(KSCHED,DEBUGPRINT("Win32Resume->%T (Resuming diverted thread)",this));
+ iWakeup = ERelease;
+ ApplyDiversion();
+ TheScheduler.iCurrentThread = this;
+ __NK_ASSERT_ALWAYS(TInt(ResumeThread(iWinThread)) == 1);
+ break;
+ case EResume:
+ // The thread is Win32 suspended and must be resumed.
+ //
+ // the complication here is that we have to unlock the kernel on behalf of the
+ // pre-empted thread. This means that we have to check to see if there are more DFCs
+ // pending or a reschedule required, as we unlock the kernel. That check is
+ // carried out with interrupts disabled.
+ //
+ // If so, we go back around the loop in this thread context
+ //
+ // Otherwise, we unlock the kernel (having marked us as not-preempted),
+ // enable interrupts and then resume the thread. If pre-emption occurs before the thread
+ // is resumed, it is the new thread that is pre-empted, not the running thread, so we are guaranteed
+ // to be able to call ResumeThread. If pre-emption occurs, and we are rescheduled to run before
+ // that occurs, we will once again be running with the kernel locked and the other thread will
+ // have been re-suspended by Win32: so all is well.
+ //
+ {
+ __KTRACE_OPT(KSCHED,DEBUGPRINT("Win32Resume->%T",this));
+ TInt irq = NKern::DisableAllInterrupts();
+ if (TheScheduler.iDfcPendingFlag || TheScheduler.iRescheduleNeededFlag)
+ {
+ // we were interrrupted... back to the top
+ TheScheduler.iRescheduleNeededFlag = TRUE; // ensure we do the reschedule
+ return TRUE;
+ }
+ iWakeup = ERelease;
+ TheScheduler.iCurrentThread = this;
+ if (TheScheduler.iProcessHandler)
+ (*ProcessHandler(TheScheduler.iProcessHandler))(iAddressSpace); // threads resumed after interrupt or locks need to have static data updated
+
+ if (iInKernel == 0 && iUserModeCallbacks != NULL)
+ ApplyDiversion();
+ else
+ TheScheduler.iKernCSLocked = 0; // have to unlock the kernel on behalf of the new thread
+
+ TheScheduler.iCurrentThread = this;
+ NKern::RestoreInterrupts(irq);
+ __NK_ASSERT_ALWAYS(TInt(ResumeThread(iWinThread)) > 0); // check thread was previously suspended
+ }
+ break;
+ }
+ return FALSE;
+ }
+
+static void ThreadExit(NThread& aCurrent, NThread& aNext)
+//
+// The final context switch of a thread.
+// Wake up the next thread and then destroy this one's Win32 resources.
+//
+// Return without terminating if we need to immediately reschedule again because
+// we had to unlock the kernel but there are DFCs pending.
+//
+ {
+ // the thread is dead
+ // extract win32 handles from dying NThread object before rescheduling
+ HANDLE sl = aCurrent.iScheduleLock;
+ HANDLE th = aCurrent.iWinThread;
+
+ // wake up the next thread
+ if (aNext.WakeUp())
+ return; // need to re-reschedule in this thread
+
+ // we are now a vanilla win32 thread, nKern no longer knows about us
+ // release resources and exit cleanly
+ CloseHandle(sl);
+ CloseHandle(th);
+ ExitThread(0); // does not return
+ }
+
+#ifdef MONITOR_THREAD_CPU_TIME
+static inline void UpdateThreadCpuTime(NThread& aCurrent, NThread& aNext)
+ {
+ TUint32 timestamp = NKern::FastCounter();
+ if (aCurrent.iLastStartTime)
+ aCurrent.iTotalCpuTime += timestamp - aCurrent.iLastStartTime;
+ aNext.iLastStartTime = timestamp;
+ }
+#else
+static inline void UpdateThreadCpuTime(NThread& /*aCurrent*/, NThread& /*aNext*/)
+ {
+ }
+#endif
+
+static void SwitchThreads(NThread& aCurrent, NThread& aNext)
+//
+// The fundamental context switch - wake up the next thread and wait for reschedule
+// trivially is aNext.WakeUp(), Wait(aCurrent.iScheduleLock), but we may be able to
+// optimise the signal-and-wait
+//
+ {
+ UpdateThreadCpuTime(aCurrent, aNext);
+ if (aCurrent.iNState == NThread::EDead)
+ ThreadExit(aCurrent, aNext);
+ else if (Win32AtomicSOAW && aNext.iWakeup==NThread::ERelease)
+ {
+ // special case optimization for normally blocked threads using atomic Win32 primitive
+ TheScheduler.iCurrentThread = &aNext;
+ DWORD result=SignalObjectAndWait(aNext.iScheduleLock,aCurrent.iScheduleLock, INFINITE, FALSE);
+ if (result != WAIT_OBJECT_0)
+ {
+ __NK_ASSERT_ALWAYS(result == 0xFFFFFFFF);
+ KPrintf("SignalObjectAndWait() failed with %d (%T->%T)",GetLastError(),&aCurrent,&aNext);
+ FAULT();
+ }
+ }
+ else
+ {
+ if (aNext.WakeUp())
+ return; // need to re-reschedule in this thread
+ __NK_ASSERT_ALWAYS(WaitForSingleObject(aCurrent.iScheduleLock, INFINITE) == WAIT_OBJECT_0);
+ }
+ }
+
+void TScheduler::YieldTo(NThreadBase*)
+//
+// Directed context switch to the nominated thread.
+// Enter with kernel locked, exit with kernel unlocked but interrupts disabled.
+//
+ {
+ RescheduleNeeded();
+ TScheduler::Reschedule();
+ }
+
+void TScheduler::Reschedule()
+//
+// Enter with kernel locked, exit with kernel unlocked, interrupts disabled.
+// If the thread is dead do not return, but terminate the thread.
+//
+ {
+ __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
+ NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
+ for (;;)
+ {
+ NKern::DisableAllInterrupts();
+ if (TheScheduler.iDfcPendingFlag)
+ TheScheduler.QueueDfcs();
+ if (!TheScheduler.iRescheduleNeededFlag)
+ break;
+ NKern::EnableAllInterrupts();
+ TheScheduler.iRescheduleNeededFlag = FALSE;
+ NThread* t = static_cast<NThread*>(SelectThread(TheScheduler));
+ __KTRACE_OPT(KSCHED,DEBUGPRINT("Reschedule->%T (%08x%08x)",t,TheScheduler.iPresent[1],TheScheduler.iPresent[0]));
+#ifdef __EMI_SUPPORT__
+ EMI_AddTaskSwitchEvent(&me,t);
+ EMI_CheckDfcTag(t);
+#endif
+#ifdef BTRACE_CPU_USAGE
+ if(TheScheduler.iCpuUsageFilter)
+ TheScheduler.iBTraceHandler(BTRACE_HEADER_C(4,BTrace::ECpuUsage,BTrace::ENewThreadContext),0,(TUint32)t,0,0,0,0,0);
+#endif
+ SwitchThreads(me, *t);
+
+ // we have just been scheduled to run... check for diversion/new Dfcs
+ NThread::TDivert divert = me.iDivert;
+ if (divert)
+ {
+ // diversion (e.g. force exit)
+ me.iDivert = NULL;
+ divert(); // does not return
+ }
+ }
+ if (TheScheduler.iProcessHandler)
+ (*ProcessHandler(TheScheduler.iProcessHandler))(me.iAddressSpace);
+ // interrrupts are disabled, the kernel is still locked
+ TheScheduler.iKernCSLocked = 0;
+ }
+
+/** Put the emulator into 'idle'.
+ This is called by the idle thread when there is nothing else to do.
+
+ @internalTechnology
+ */
+EXPORT_C void NThread::Idle()
+//
+// Rather than spin, we go to sleep on the schedule lock. Preemption detects
+// this state (Win32Idling) and pokes the event rather than diverting the thread.
+//
+// enter and exit with kernel locked
+//
+ {
+ NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
+ me.iWakeup = EIdle;
+ __NK_ASSERT_ALWAYS(WaitForSingleObject(me.iScheduleLock, INFINITE) == WAIT_OBJECT_0);
+ // something happened, and we've been prodded by an interrupt
+ // the kernel was locked by the interrupt, and now reschedule
+ me.iWakeup = ERelease;
+ TScheduler::Reschedule();
+ NKern::EnableAllInterrupts();
+ }
+
+void SchedulerInit(NThread& aInit)
+//
+// Initialise the win32 nKern scheduler
+//
+ {
+ DWORD procaffin,sysaffin;
+ if (GetProcessAffinityMask(GetCurrentProcess(),&procaffin,&sysaffin))
+ {
+ DWORD cpu;
+ switch (Win32SingleCpu)
+ {
+ default:
+ // bind the emulator to a nominated CPU on the host PC
+ cpu = (1<<Win32SingleCpu);
+ if (!(sysaffin & cpu))
+ cpu = procaffin; // CPU selection invalid
+ break;
+ case NThread::ECpuSingle:
+ // bind the emulator to a single CPU on the host PC, pick one
+ cpu = procaffin ^ (procaffin & (procaffin-1));
+ break;
+ case NThread::ECpuAll:
+ // run the emulator on all CPUs on the host PC
+ cpu=sysaffin;
+ break;
+ }
+ SetProcessAffinityMask(GetCurrentProcess(), cpu);
+ }
+ // identify if we can use the atomic SignalObjectAndWait API in Win32 for rescheduling
+ Win32AtomicSOAW = (SignalObjectAndWait(aInit.iScheduleLock, aInit.iScheduleLock, INFINITE, FALSE) == WAIT_OBJECT_0);
+ //
+ // allocate the TLS used for thread identification, and set it for the init thread
+ TlsIndex = TlsAlloc();
+ __NK_ASSERT_ALWAYS(TlsIndex != TLS_OUT_OF_INDEXES);
+ SchedulerRegister(aInit);
+ //
+ Interrupt.Init();
+
+ Win32FindNonPreemptibleFunctions();
+ }
+
+void SchedulerRegister(NThread& aSelf)
+ {
+ TlsSetValue(TlsIndex,&aSelf);
+ }
+
+NThread* SchedulerThread()
+ {
+ if (TlsIndex != TLS_OUT_OF_INDEXES)
+ return static_cast<NThread*>(TlsGetValue(TlsIndex));
+ else
+ return NULL; // not yet initialised
+ }
+
+inline TBool IsScheduledThread()
+ {
+ return SchedulerThread() == TheScheduler.iCurrentThread;
+ }
+
+NThread& CheckedCurrentThread()
+ {
+ NThread* t = SchedulerThread();
+ __NK_ASSERT_ALWAYS(t == TheScheduler.iCurrentThread);
+ return *t;
+ }
+
+
+/** Disable normal 'interrupts'.
+
+ @param aLevel Ignored
+ @return Cookie to be passed into RestoreInterrupts()
+ */
+EXPORT_C TInt NKern::DisableInterrupts(TInt /*aLevel*/)
+ {
+ return Interrupt.Mask();
+ }
+
+
+/** Disable all maskable 'interrupts'.
+
+ @return Cookie to be passed into RestoreInterrupts()
+ */
+EXPORT_C TInt NKern::DisableAllInterrupts()
+ {
+ return Interrupt.Mask();
+ }
+
+
+/** Enable all maskable 'interrupts'
+
+ @internalComponent
+ */
+EXPORT_C void NKern::EnableAllInterrupts()
+ {
+ Interrupt.Restore(0);
+ }
+
+
+/** Restore interrupt mask to state preceding a DisableInterrupts() call
+
+ @param aLevel Cookie returned by Disable(All)Interrupts()
+ */
+EXPORT_C void NKern::RestoreInterrupts(TInt aLevel)
+ {
+ Interrupt.Restore(aLevel);
+ }
+
+
+/** Unlocks the kernel.
+
+ Decrements iKernCSLocked; if it becomes zero and IDFCs or a reschedule are
+ pending, calls the scheduler to process them.
+
+ @pre Call either in a thread or an IDFC context.
+ @pre Do not call from an ISR.
+ @pre Do not call from bare Win32 threads.
+ */
+EXPORT_C void NKern::Unlock()
+//
+// using this coding sequence it is possible to call Reschedule unnecessarily
+// if we are preempted after testing the flags (lock is zero at this point).
+// However, in the common case this is much faster because 'disabling interrupts'
+// can be very expensive.
+//
+ {
+ CHECK_PRECONDITIONS(MASK_NOT_ISR,"NKern::Unlock");
+ __ASSERT_WITH_MESSAGE_DEBUG(IsScheduledThread(),"Do not call from bare Win32 threads","NKern::Unlock"); // check that we are a scheduled thread
+ __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked > 0); // Can't unlock if it isn't locked!
+ if (--TheScheduler.iKernCSLocked == 0)
+ {
+ if (TheScheduler.iRescheduleNeededFlag || TheScheduler.iDfcPendingFlag)
+ {
+ TheScheduler.iKernCSLocked = 1;
+ TScheduler::Reschedule();
+ NKern::EnableAllInterrupts();
+ }
+ }
+ }
+
+
+/** Locks the kernel.
+
+ Increments iKernCSLocked, thereby deferring IDFCs and preemption.
+
+ @pre Call either in a thread or an IDFC context.
+ @pre Do not call from an ISR.
+ @pre Do not call from bare Win32 threads.
+ */
+EXPORT_C void NKern::Lock()
+ {
+ CHECK_PRECONDITIONS(MASK_NOT_ISR,"NKern::Lock");
+ __ASSERT_WITH_MESSAGE_ALWAYS(IsScheduledThread(),"Do not call from bare Win32 threads","NKern::Lock"); // check that we are a scheduled thread
+ ++TheScheduler.iKernCSLocked;
+ }
+
+
+/** Locks the kernel and returns a pointer to the current thread
+ Increments iKernCSLocked, thereby deferring IDFCs and preemption.
+
+ @pre Call either in a thread or an IDFC context.
+ @pre Do not call from an ISR.
+ @pre Do not call from bare Win32 threads.
+ */
+EXPORT_C NThread* NKern::LockC()
+ {
+ CHECK_PRECONDITIONS(MASK_NOT_ISR,"NKern::Lock");
+ __ASSERT_WITH_MESSAGE_ALWAYS(IsScheduledThread(),"Do not call from bare Win32 threads","NKern::Lock"); // check that we are a scheduled thread
+ ++TheScheduler.iKernCSLocked;
+ return (NThread*)TheScheduler.iCurrentThread;
+ }
+
+
+/** Allows IDFCs and rescheduling if they are pending.
+
+ If IDFCs or a reschedule are pending and iKernCSLocked is exactly equal to 1
+ calls the scheduler to process the IDFCs and possibly reschedule.
+
+ @return Nonzero if a reschedule actually occurred, zero if not.
+
+ @pre Call either in a thread or an IDFC context.
+ @pre Do not call from an ISR.
+ @pre Do not call from bare Win32 threads.
+ */
+EXPORT_C TInt NKern::PreemptionPoint()
+ {
+ CHECK_PRECONDITIONS(MASK_NOT_ISR,"NKern::PreemptionPoint");
+ __ASSERT_WITH_MESSAGE_DEBUG(IsScheduledThread(),"Do not call from bare Win32 threads","NKern::PreemptionPoint"); // check that we are a scheduled thread
+ if (TheScheduler.iKernCSLocked == 1 &&
+ (TheScheduler.iRescheduleNeededFlag || TheScheduler.iDfcPendingFlag))
+ {
+ TScheduler::Reschedule();
+ TheScheduler.iKernCSLocked = 1;
+ NKern::EnableAllInterrupts();
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+
+/** Mark the start of an 'interrupt' in the Win32 emulator.
+ This must be called in interrupt threads before using any other kernel APIs,
+ and should be paired with a call to EndOfInterrupt().
+
+ @pre Win32 'interrupt' thread context
+ */
+EXPORT_C void StartOfInterrupt()
+ {
+ __ASSERT_WITH_MESSAGE_DEBUG(!IsScheduledThread(),"Win32 'interrupt' thread context","StartOfInterrupt"); // check that we are a scheduled thread
+ Interrupt.Begin();
+ }
+
+
+/** Mark the end of an 'interrupt' in the Win32 emulator.
+ This checks to see if we need to reschedule.
+
+ @pre Win32 'interrupt' thread context
+ */
+EXPORT_C void EndOfInterrupt()
+ {
+ __ASSERT_WITH_MESSAGE_DEBUG(!IsScheduledThread(),"Win32 'interrupt' thread context","EndOfInterrupt"); // check that we are a scheduled thread
+ Interrupt.End();
+ }
+
+
+void Win32Interrupt::Init()
+ {
+ iQ=CreateSemaphoreA(NULL, 0, KMaxTInt, NULL);
+ __NK_ASSERT_ALWAYS(iQ);
+ //
+ // create the NThread which exists solely to service reschedules for interrupts
+ // this makes the End() much simpler as it merely needs to kick this thread
+ SNThreadCreateInfo ni;
+ memclr(&ni, sizeof(ni));
+ ni.iFunction=&Reschedule;
+ ni.iTimeslice=-1;
+ ni.iPriority=1;
+ NKern::ThreadCreate(&iScheduler, ni);
+ NKern::Lock();
+ TScheduler::YieldTo(&iScheduler);
+ Restore(0);
+ }
+
+TInt Win32Interrupt::Mask()
+ {
+ if (!iQ)
+ return 0; // interrupt scheme not enabled yet
+ DWORD id=GetCurrentThreadId();
+ if (__e32_atomic_add_ord32(&iLock, 1))
+ {
+ if (id==iOwner)
+ return iLevel++;
+ __NK_ASSERT_ALWAYS(WaitForSingleObject(iQ,INFINITE) == WAIT_OBJECT_0);
+ iRescheduleOnExit=IsScheduledThread() &&
+ (TheScheduler.iRescheduleNeededFlag || TheScheduler.iDfcPendingFlag);
+ }
+ else
+ iRescheduleOnExit=FALSE;
+ __NK_ASSERT_ALWAYS(iOwner==0 && iLevel==0);
+ iOwner=id;
+ iLevel=1;
+ return 0;
+ }
+
+void Win32Interrupt::Restore(TInt aLevel)
+ {
+ if (!iQ)
+ return; // interrupt scheme not enabled yet
+ DWORD id=GetCurrentThreadId();
+ for (;;)
+ {
+ __NK_ASSERT_ALWAYS(id == iOwner);
+ TInt count = iLevel - aLevel;
+ if (count <= 0)
+ return; // alredy restored to that level
+ TBool reschedule = FALSE;
+ iLevel = aLevel; // update this value before releasing the lock
+ if (aLevel == 0)
+ {
+ // we release the lock
+ iOwner = 0;
+ if (iRescheduleOnExit && TheScheduler.iKernCSLocked == 0)
+ reschedule = TRUE; // need to trigger reschedule on full release
+ }
+ // now release the lock
+ if (__e32_atomic_add_ord32(&iLock, TUint32(-count)) == (TUint32)count)
+ { // fully released, check for reschedule
+ if (!reschedule)
+ return;
+ }
+ else
+ { // not fully released
+ if (aLevel == 0)
+ __NK_ASSERT_ALWAYS(ReleaseSemaphore(iQ,1,NULL));
+ return;
+ }
+ // unlocked everything but a reschedule may be required
+ TheScheduler.iKernCSLocked = 1;
+ TScheduler::Reschedule();
+ // return with the kernel unlocked, but interrupts disabled
+ // instead of going recursive with a call to EnableAllInterrupts() we iterate
+ aLevel=0;
+ }
+ }
+
+void Win32Interrupt::Begin()
+ {
+ Mask();
+ __NK_ASSERT_ALWAYS(iInterrupted==0); // check we haven't done this already
+ __NK_ASSERT_ALWAYS(!IsScheduledThread()); // check that we aren't a scheduled thread
+ NThread* pC;
+ for (;;)
+ {
+ pC=static_cast<NThread*>(TheScheduler.iCurrentThread);
+ DWORD r=SuspendThread(pC->iWinThread);
+ if (pC == TheScheduler.iCurrentThread)
+ {
+ // there was no race while suspending the thread, so we can carry on
+ __NK_ASSERT_ALWAYS(r != 0xffffffff);
+ break;
+ }
+ // We suspended the thread while doing a context switch, resume it and try again
+ if (r != 0xffffffff)
+ __NK_ASSERT_ALWAYS(TInt(ResumeThread(pC->iWinThread)) > 0); // check thread was previously suspended
+ }
+#ifdef BTRACE_CPU_USAGE
+ BTrace0(BTrace::ECpuUsage,BTrace::EIrqStart);
+#endif
+ iInterrupted = pC;
+ }
+
+void Win32Interrupt::End()
+ {
+ __NK_ASSERT_ALWAYS(iOwner == GetCurrentThreadId()); // check we are the interrupting thread
+ NThread* pC = iInterrupted;
+ __NK_ASSERT_ALWAYS(pC==TheScheduler.iCurrentThread);
+ iInterrupted = 0;
+ if (iLock == 1 && TheScheduler.iKernCSLocked == 0 &&
+ (TheScheduler.iRescheduleNeededFlag || TheScheduler.iDfcPendingFlag) &&
+ pC->IsSafeToPreempt())
+ {
+ TheScheduler.iKernCSLocked = 1; // prevent further pre-emption
+ if (pC->iWakeup == NThread::EIdle)
+ {
+ // wake up the NULL thread, it will always reschedule immediately
+ pC->WakeUp();
+ }
+ else
+ {
+ // pre-empt the current thread and poke the 'scheduler' thread
+ __NK_ASSERT_ALWAYS(pC->iWakeup == NThread::ERelease);
+ pC->iWakeup = NThread::EResume;
+ UpdateThreadCpuTime(*pC, iScheduler);
+ RescheduleNeeded();
+ NKern::EnableAllInterrupts();
+ iScheduler.WakeUp();
+ return;
+ }
+ }
+ else
+ {
+ // no thread reschedle, so emit trace...
+#ifdef BTRACE_CPU_USAGE
+ BTrace0(BTrace::ECpuUsage,BTrace::EIrqEnd);
+#endif
+ }
+
+ if (((NThread*)pC)->iInKernel == 0 && // thread is running in user mode
+ pC->iUserModeCallbacks != NULL && // and has callbacks queued
+ TheScheduler.iKernCSLocked == 0 && // and is not currently processing a diversion
+ pC->IsSafeToPreempt()) // and can be safely prempted at this point
+ {
+ TheScheduler.iKernCSLocked = 1;
+ pC->ApplyDiversion();
+ }
+ NKern::EnableAllInterrupts();
+ __NK_ASSERT_ALWAYS(TInt(ResumeThread(pC->iWinThread)) > 0); // check thread was previously suspended
+ }
+
+void Win32Interrupt::Reschedule(TAny*)
+//
+// The entry-point for the interrupt-rescheduler thread.
+//
+// This spends its whole life going around the TScheduler::Reschedule() loop
+// selecting another thread to run.
+//
+ {
+ TheScheduler.iKernCSLocked = 1;
+ RescheduleNeeded();
+ TScheduler::Reschedule();
+ FAULT();
+ }
+
+void Win32Interrupt::ForceReschedule()
+ {
+ RescheduleNeeded();
+ iScheduler.WakeUp();
+ }
+
+void SchedulerEscape()
+ {
+ NThread& me=CheckedCurrentThread();
+ EnterKernel();
+ __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked==0); // Can't call Escape() with the Emulator/kernel already locked
+ NKern::ThreadEnterCS();
+ NKern::Lock();
+ me.iNState=NThreadBase::EBlocked;
+ TheScheduler.Remove(&me);
+ me.iWakeup=NThread::EEscaped;
+ SetThreadPriority(me.iWinThread,THREAD_PRIORITY_ABOVE_NORMAL);
+ Interrupt.ForceReschedule(); // schedules some other thread so we can carry on outside the scheduler domain
+ // this will change the value of iCurrentThread to ensure the 'escaped' invariants are set
+ }
+
+void ReenterDfc(TAny* aPtr)
+ {
+ NThread& me = *static_cast<NThread*>(aPtr);
+ me.iWakeup = NThread::ERelease;
+ me.CheckSuspendThenReady();
+ }
+
+void SchedulerReenter()
+ {
+ NThread* me=SchedulerThread();
+ __NK_ASSERT_ALWAYS(me);
+ __NK_ASSERT_ALWAYS(me->iWakeup == NThread::EEscaped);
+ TDfc idfc(&ReenterDfc, me);
+ StartOfInterrupt();
+ idfc.Add();
+ EndOfInterrupt();
+ SetThreadPriority(me->iWinThread,THREAD_PRIORITY_NORMAL);
+ __NK_ASSERT_ALWAYS(WaitForSingleObject(me->iScheduleLock, INFINITE) == WAIT_OBJECT_0);
+ // when released, the kernel is locked and handed over to us
+ // need to complete the reschedule protocol in this thread now
+ TScheduler::Reschedule();
+ NKern::EnableAllInterrupts();
+ NKern::ThreadLeaveCS();
+ LeaveKernel();
+ }
+
+
+/** Return the current processor context type
+ (thread, IDFC, interrupt or escaped thread)
+
+ @return A value from NKern::TContext enumeration (including EEscaped)
+ @pre Any context
+
+ @see NKern::TContext
+ */
+EXPORT_C TInt NKern::CurrentContext()
+ {
+ NThread* t = SchedulerThread();
+ if (!t)
+ return NKern::EInterrupt;
+ if (TheScheduler.iInIDFC)
+ return NKern::EIDFC;
+ if (t->iWakeup == NThread::EEscaped)
+ return NKern::EEscaped;
+ __NK_ASSERT_ALWAYS(NKern::Crashed() || t == TheScheduler.iCurrentThread);
+ return NKern::EThread;
+ }
+
+//
+// We use SuspendThread and ResumeThread to preempt threads. This can cause
+// deadlock if the thread is using windows synchronisation primitives (eg
+// critical sections). This isn't too much of a problem most of the time,
+// because threads generally use the symbian environment rather than the native
+// windows APIs. However exceptions are an issue - they can happen at any time,
+// and cause execution of native windows code over which we have no control.
+//
+// To work around this we examine the call stack to see if the thread is inside
+// one of the windows exception handling functions. If so, preemption is
+// deferred.
+//
+
+#include <winnt.h>
+
+const TInt KWin32NonPreemptibleFunctionCount = 2;
+
+struct TWin32FunctionInfo
+ {
+ TUint iStartAddr;
+ TUint iLength;
+ };
+
+static TWin32FunctionInfo Win32NonPreemptibleFunctions[KWin32NonPreemptibleFunctionCount];
+
+TWin32FunctionInfo Win32FindExportedFunction(const char* aModuleName, const char* aFunctionName)
+ {
+ HMODULE library = GetModuleHandleA(aModuleName);
+ __NK_ASSERT_ALWAYS(library != NULL);
+
+ // Find the start address of the function
+ TUint start = (TUint)GetProcAddress(library, aFunctionName);
+ __NK_ASSERT_ALWAYS(start);
+
+ // Now have to check all other exports to find the end of the function
+ TUint end = 0xffffffff;
+ TInt i = 1;
+ for (;;)
+ {
+ TUint addr = (TUint)GetProcAddress(library, MAKEINTRESOURCEA(i));
+ if (!addr)
+ break;
+ if (addr > start && addr < end)
+ end = addr;
+ ++i;
+ }
+ __NK_ASSERT_ALWAYS(end != 0xffffffff);
+
+ TWin32FunctionInfo result = { start, end - start };
+ return result;
+ }
+
+void Win32FindNonPreemptibleFunctions()
+ {
+ Win32NonPreemptibleFunctions[0] = Win32FindExportedFunction("kernel32.dll", "RaiseException");
+ Win32NonPreemptibleFunctions[1] = Win32FindExportedFunction("ntdll.dll", "KiUserExceptionDispatcher");
+ }
+
+TBool Win32IsThreadInNonPreemptibleFunction(HANDLE aWinThread, TLinAddr aStackTop)
+ {
+ const TInt KMaxSearchDepth = 16; // 12 max observed while handling exceptions
+ const TInt KMaxStackSize = 1024 * 1024; // Default reserved stack size on windows
+ const TInt KMaxFrameSize = 4096;
+
+ CONTEXT c;
+ c.ContextFlags=CONTEXT_FULL;
+ GetThreadContext(aWinThread, &c);
+
+ TUint eip = c.Eip;
+ TUint ebp = c.Ebp;
+ TUint lastEbp = c.Esp;
+
+ // Walk the call stack
+ for (TInt i = 0 ; i < KMaxSearchDepth ; ++i)
+ {
+ for (TInt j = 0 ; j < KWin32NonPreemptibleFunctionCount ; ++j)
+ {
+ const TWin32FunctionInfo& info = Win32NonPreemptibleFunctions[j];
+ if (TUint(eip - info.iStartAddr) < info.iLength)
+ {
+ __KTRACE_OPT(KSCHED, DEBUGPRINT("Thread is in non-preemptible function %d at frame %d: eip == %08x", j, i, eip));
+ return TRUE;
+ }
+ }
+
+ // Check frame pointer is valid before dereferencing it
+ if (TUint(aStackTop - ebp) > KMaxStackSize || TUint(ebp - lastEbp) > KMaxFrameSize || ebp & 3)
+ break;
+
+ TUint* frame = (TUint*)ebp;
+ lastEbp = ebp;
+ ebp = frame[0];
+ eip = frame[1];
+ }
+
+ return FALSE;
+ }
+
+TBool NThread::IsSafeToPreempt()
+ {
+ return !Win32IsThreadInNonPreemptibleFunction(iWinThread, iUserStackBase);
+ }
+
+void LeaveKernel()
+ {
+ TInt& k=CheckedCurrentThread().iInKernel;
+ __NK_ASSERT_DEBUG(k>0);
+ if (k==1) // just about to leave kernel
+ {
+ NThread& t = CheckedCurrentThread();
+ __NK_ASSERT_ALWAYS(t.iCsCount==0);
+ __NK_ASSERT_ALWAYS(t.iHeldFastMutex==0);
+ __NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked==0);
+ NKern::DisableAllInterrupts();
+ t.CallUserModeCallbacks();
+ NKern::EnableAllInterrupts();
+ }
+ --k;
+ }
+