kernel/eka/nkern/win32/ncsched.cpp
author Tom Cosgrove <tom.cosgrove@nokia.com>
Fri, 28 May 2010 16:29:07 +0100
changeset 30 8aab599e3476
parent 0 a41df078684a
child 21 e7d2d738d3c2
permissions -rw-r--r--
Fix for bug 2283 (RVCT 4.0 support is missing from PDK 3.0.h) Have multiple extension sections in the bld.inf, one for each version of the compiler. The RVCT version building the tools will build the runtime libraries for its version, but make sure we extract all the other versions from zip archives. Also add the archive for RVCT4.

// 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;
	}