diff -r 000000000000 -r a41df078684a kernel/eka/nkern/win32/ncsched.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kernel/eka/nkern/win32/ncsched.cpp Mon Oct 19 15:55:17 2009 +0100 @@ -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 +#include +#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(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(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(TheScheduler.iCurrentThread); + for (;;) + { + NKern::DisableAllInterrupts(); + if (TheScheduler.iDfcPendingFlag) + TheScheduler.QueueDfcs(); + if (!TheScheduler.iRescheduleNeededFlag) + break; + NKern::EnableAllInterrupts(); + TheScheduler.iRescheduleNeededFlag = FALSE; + NThread* t = static_cast(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(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<(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(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(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 + +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; + } +