kernel/eka/nkern/win32/ncthrd.cpp
changeset 0 a41df078684a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kernel/eka/nkern/win32/ncthrd.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,650 @@
+// 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\ncthrd.cpp
+// 
+//
+
+// NThreadBase member data
+#define __INCLUDE_NTHREADBASE_DEFINES__
+
+#include "nk_priv.h"
+#include <emulator.h>
+
+extern "C" void ExcFault(TAny*);
+
+// initial Win32 thread stack size
+const TInt KInitialStackSize = 0x1000;
+
+// maximum size of the parameter block passed to a new thread
+const TInt KMaxParameterBlock = 512;
+
+// data passed to new thread to enable hand-off of the parameter block
+struct SCreateThread
+	{
+	const SNThreadCreateInfo* iInfo;
+	NFastMutex iHandoff;
+	};
+
+/**
+ * Set the Win32 thread priority based on the thread type.
+ * Interrupt/Event threads must be able to preempt normal nKern threads,
+ * so they get a higher priority.
+ */
+static void SetPriority(HANDLE aThread, TEmulThreadType aType)
+	{
+	TInt p;
+	switch (aType)
+		{
+	default:
+		FAULT();
+	case EThreadEvent:
+		p = THREAD_PRIORITY_ABOVE_NORMAL;
+		break;
+	case EThreadNKern:
+		p = THREAD_PRIORITY_NORMAL;
+		break;
+		}
+
+	__NK_ASSERT_ALWAYS(SetThreadPriority(aThread, p));
+	SetThreadPriorityBoost(aThread, TRUE);		// disable priority boost (for NT)
+	}
+
+
+/**	Create a Win32 thread for use in the emulator.
+
+	@param	aType Type of thread (Event or NKern) - determines Win32 priority
+	@param	aThreadFunc Entry point of thread
+	@param	aPtr Argument passed to entry point
+	@param	aRun TRUE if thread should be resumed immediately
+	@return	The Win32 handle to the thread, 0 if an error occurred
+
+	@pre    Call either in thread context.
+	@pre	Do not call from bare Win32 threads.
+
+	@see TEmulThreadType
+ */
+EXPORT_C HANDLE CreateWin32Thread(TEmulThreadType aType, LPTHREAD_START_ROUTINE aThreadFunc, LPVOID aPtr, TBool aRun)
+	{
+	__NK_ASSERT_DEBUG(!TheScheduler.iCurrentThread || NKern::CurrentContext() == NKern::EThread);
+
+	__LOCK_HOST;
+	
+	DWORD id;
+	HANDLE handle = CreateThread(NULL , KInitialStackSize, aThreadFunc, aPtr, CREATE_SUSPENDED, &id);
+	if (handle)
+		{
+		SetPriority(handle, aType);
+		if (aRun)
+			ResumeThread(handle);
+		}
+	return handle;
+	}
+
+
+/** Set some global properties of the emulator
+	Called by the Win32 base port during boot.
+
+	@param	aTrace TRUE means trace Win32 thread ID for every thread created
+	@param	aSingleCpu TRUE means lock the emulator process to a single CPU
+
+	@internalTechnology
+ */
+EXPORT_C void NThread::SetProperties(TBool aTrace, TInt aSingleCpu)
+	{
+	Win32TraceThreadId = aTrace;
+	Win32SingleCpu = aSingleCpu;
+	}
+
+#if defined(__CW32__) && __MWERKS__ < 0x3200
+DWORD NThread__ExceptionHandler(EXCEPTION_RECORD* aException, TAny* /*aRegistrationRecord*/, CONTEXT* aContext)
+//
+// Hook into exception handling for old version of CW
+//
+	{
+	return NThread::ExceptionHandler(aException, aContext);
+	}
+#endif // old __CW32__
+
+DWORD WINAPI NThread::StartThread(LPVOID aParam)
+//
+// Win32 thread function for nKern threads.
+//
+// The thread first enters this function after the nScheduler has resumed
+// it, following the context switch induced by the hand-off mutex.
+//
+// The parameter block for this thread needs to be copied into its
+// own context, before releasing the mutex and handing control back to
+// the creating thread.
+//
+	{
+	SCreateThread* init = static_cast<SCreateThread*>(aParam);
+	NThread& me=*static_cast<NThread*>(init->iHandoff.iHoldingThread);
+	me.iWinThreadId = GetCurrentThreadId();
+	SchedulerRegister(me);
+#ifdef BTRACE_FAST_MUTEX
+	BTraceContext4(BTrace::EFastMutex,BTrace::EFastMutexWait,&init->iHandoff);
+#endif
+	NKern::Unlock();
+
+#if defined(__CW32__) && __MWERKS__ < 0x3200
+	// intercept the win32 exception mechanism manually
+    asm {
+    	push	ebp
+    	mov		eax, -1
+    	push	eax
+    	push	eax
+    	push	offset NThread__ExceptionHandler
+    	push	fs:[0]
+    	mov		fs:[0], esp
+
+		// realign the stack
+    	sub		esp, 0x20
+    	and		esp, ~0x1f
+		}
+#else // ! old __CW32__
+	// intercept win32 exceptions in a debuggabble way
+__try {
+#endif // old __CW32__
+
+	// save the thread entry point and parameter block
+	const SNThreadCreateInfo& info = *init->iInfo;
+	TUint8 parameterBlock[KMaxParameterBlock];
+	TAny* parameter=(TAny*)info.iParameterBlock;
+	if (info.iParameterBlockSize)
+		{
+		__NK_ASSERT_DEBUG(TUint(info.iParameterBlockSize)<=TUint(KMaxParameterBlock));
+		parameter=parameterBlock;
+		memcpy(parameterBlock,info.iParameterBlock,info.iParameterBlockSize);
+		}
+	NThreadFunction threadFunction=info.iFunction;
+
+	// Calculate stack base
+	me.iUserStackBase = (((TLinAddr)&parameterBlock)+0xfff)&~0xfff; // base address of stack
+
+	// some useful diagnostics for debugging
+	if (Win32TraceThreadId)
+		KPrintf("Thread %T created @ 0x%x - Win32 Thread ID 0x%x",init->iHandoff.iHoldingThread,init->iHandoff.iHoldingThread,GetCurrentThreadId());
+
+#ifdef MONITOR_THREAD_CPU_TIME
+	me.iLastStartTime = 0;  // Don't count NThread setup in cpu time
+#endif
+ 
+	// start-up complete, release the handoff mutex, which will re-suspend us
+	NKern::FMSignal(&init->iHandoff);
+
+	// thread has been resumed: invoke the thread function
+	threadFunction(parameter);
+
+#if !defined(__CW32__) || __MWERKS__ >= 0x3200
+	// handle win32 exceptions
+} __except (ExceptionFilter(GetExceptionInformation())) {
+	// Do nothing - filter does all the work and hooks
+	// into EPOC h/w exception mechanism if necessary
+	// by thread diversion
+}
+#endif // !old __CW32__
+
+	NKern::Exit();
+
+	return 0;
+	}
+
+static HANDLE InitThread()
+//
+// Set up the initial thread and return the thread handle
+//
+	{
+	HANDLE p = GetCurrentProcess();
+	HANDLE me;
+	__NK_ASSERT_ALWAYS(DuplicateHandle(p, GetCurrentThread(), p, &me, 0, FALSE, DUPLICATE_SAME_ACCESS));
+	SetPriority(me, EThreadNKern);
+	return me;
+	}
+
+TInt NThread::Create(SNThreadCreateInfo& aInfo, TBool aInitial)
+	{
+	iWinThread = NULL;
+	iWinThreadId = 0;
+	iScheduleLock = NULL;
+	iInKernel = 1;
+	iDivert = NULL;
+	iWakeup = aInitial ? ERelease : EResumeLocked;	// mark new threads as created (=> win32 suspend)
+
+	TInt r=NThreadBase::Create(aInfo,aInitial);
+	if (r!=KErrNone)
+		return r;
+
+	// the rest has to be all or nothing, we must complete it
+	iScheduleLock = CreateEventA(NULL, FALSE, FALSE, NULL);
+	if (iScheduleLock == NULL)
+		return Emulator::LastError();
+
+	if (aInitial)
+		{
+		iWinThread = InitThread();
+		FastCounterInit();
+#ifdef MONITOR_THREAD_CPU_TIME
+		iLastStartTime = NKern::FastCounter();
+#endif
+		iUserStackBase = (((TLinAddr)&r)+0xfff)&~0xfff; // base address of stack
+		SchedulerInit(*this);
+		return KErrNone;
+		}
+
+	// create the thread proper
+	//
+	SCreateThread start;
+	start.iInfo = &aInfo;
+
+	iWinThread = CreateWin32Thread(EThreadNKern, &StartThread, &start, FALSE);
+	if (iWinThread == NULL)
+		{
+		r = Emulator::LastError();
+		CloseHandle(iScheduleLock);
+		return r;
+		}
+
+#ifdef BTRACE_THREAD_IDENTIFICATION
+	BTrace4(BTrace::EThreadIdentification,BTrace::ENanoThreadCreate,this);
+#endif
+	// switch to the new thread to hand over the parameter block
+	NKern::Lock();
+	ForceResume();	// mark the thread as ready
+	// give the thread ownership of the handoff mutex
+	start.iHandoff.iHoldingThread = this;
+	iHeldFastMutex = &start.iHandoff;
+	Suspend(1);		// will defer as holding a fast mutex (implicit critical section)
+	// do the hand-over
+	start.iHandoff.Wait();
+	start.iHandoff.Signal();
+	NKern::Unlock();
+
+	return KErrNone;
+	}
+
+void NThread__HandleException(TWin32ExcInfo aExc)
+//
+// Final stage NKern exception handler.
+//
+// Check for a fatal exception when the kernel is locked
+//
+// Note that the parameter struct is passed by value, this allows for
+// direct access to the exception context created on the call stack by
+// NThread::Exception().
+//
+	{
+	if (TheScheduler.iKernCSLocked)
+		ExcFault(&aExc);
+
+	// Complete the exception data. Note that the call to EnterKernel() in
+	// ExceptionFilter() will have incremented iInKernel after the exception
+	// occurred.
+	NThread* me = static_cast<NThread*>(TheScheduler.iCurrentThread);
+	__NK_ASSERT_DEBUG(me->iInKernel);
+	aExc.iFlags = me->iInKernel == 1 ? 0 : TWin32ExcInfo::EExcInKernel;
+	aExc.iHandler = NULL;
+
+	// run NThread exception handler in 'kernel' mode
+	me->iHandlers->iExceptionHandler(&aExc, me);
+	LeaveKernel();
+
+	// If a 'user' handler is set by the kernel handler, run it
+	if (aExc.iHandler)
+		aExc.iHandler(aExc.iParam[0], aExc.iParam[1]);
+	}
+
+void NKern__Unlock()
+//
+// CW asm ICE workaround
+//
+	{
+	NKern::Unlock();
+	}
+
+__NAKED__ void NThread::Exception()
+//
+// Trampoline to nKern exception handler
+// must preserve all registers in the structure defined by TWin32Exc
+//
+	{
+	// this is the TWin32Exc structure
+	__asm push Win32ExcAddress			// save return address followed by EBP first to help debugger
+	__asm push ebp
+	__asm mov ebp, esp
+	__asm push cs
+	__asm pushfd
+	__asm push gs
+	__asm push fs
+	__asm push es
+	__asm push ds
+	__asm push ss
+	__asm push edi
+	__asm push esi
+	__asm lea esi, [ebp+8]
+	__asm push esi		// original esp
+	__asm push ebx
+	__asm push edx
+	__asm push ecx
+	__asm push eax
+	__asm push Win32ExcDataAddress
+	__asm push Win32ExcCode
+	__asm sub esp, 20	// struct init completed by NThread__HandleException()
+
+	__asm call NKern__Unlock
+
+	__asm call NThread__HandleException
+
+	__asm add esp, 28
+	__asm pop eax
+	__asm pop ecx
+	__asm pop edx
+	__asm pop ebx
+	__asm pop esi		// original ESP - ignore
+	__asm pop esi
+	__asm pop edi
+	__asm pop ebp		// original SS - ignore
+	__asm pop ds
+	__asm pop es
+	__asm pop fs
+	__asm pop gs
+	__asm popfd
+	__asm pop ebp		// original CS - ignore
+	__asm pop ebp
+	__asm ret
+	}
+
+LONG WINAPI NThread::ExceptionFilter(EXCEPTION_POINTERS* aExc)
+//
+// Filter wrapper for main Win32 exception handler
+//
+	{
+	LONG ret = EXCEPTION_CONTINUE_SEARCH;
+
+	switch (ExceptionHandler(aExc->ExceptionRecord, aExc->ContextRecord))
+		{
+		case ExceptionContinueExecution:
+			{
+			ret = EXCEPTION_CONTINUE_EXECUTION;
+			}
+			break;
+		case ExceptionContinueSearch:
+		default:
+			{
+			}
+			break;
+		}
+
+	return ret;
+	}
+
+// From e32/commmon/win32/seh.cpp
+extern DWORD CallFinalSEHHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext);
+
+extern void DivertHook();
+
+DWORD NThread::ExceptionHandler(EXCEPTION_RECORD* aException, CONTEXT* aContext)
+//
+// Win32 exception handler for EPOC threads
+//
+	{
+	if (aException->ExceptionCode == EXCEPTION_BREAKPOINT)
+		{
+		// Hardcoded breakpoint
+		//
+		// Jump directly to NT's default unhandled exception handler which will
+		// either display a dialog, directly invoke the JIT debugger or do nothing
+		// dependent upon the AeDebug and ErrorMode registry settings.
+		//
+		// Note this handler is always installed on the SEH chain and is always
+		// the last handler on this chain, as it is installed by NT in kernel32.dll
+		// before invoking the Win32 thread function.
+		return CallFinalSEHHandler(aException, aContext);
+		}
+
+	// deal with conflict between preemption and diversion
+	// the diversion will have been applied to the pre-exception context, not
+	// the current context, and thus will get 'lost'. Wake-up of a pre-empted
+	// thread with a diversion will not unlock the kernel, so need to deal with
+	// the possibility that the kernel may be locked if a diversion exists
+
+	NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
+	if (me.iDiverted && me.iDivert)
+		{
+		// The thread is being forced to exit - run the diversion outside of Win32 exception handler
+		__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
+		aContext->Eip = (TUint32)&DivertHook;
+		}
+	else
+		{
+		if (me.iDiverted)
+			{
+			// The thread is being prodded to pick up its callbacks.  This will happen when the
+			// exception handler calls LeaveKernel(), so we can remove the diversion
+			__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
+			if (aException->ExceptionAddress == &DivertHook)
+				aException->ExceptionAddress = me.iDivertReturn;
+			me.iDiverted = EFalse;
+			me.iDivertReturn = NULL;
+			EnterKernel(FALSE);
+			}
+		else
+			{
+			EnterKernel();
+			TheScheduler.iKernCSLocked = 1;	// prevent pre-emption
+			}
+		
+		// If the kernel was already locked, this will be detected in the next stage handler
+		// run 2nd stage handler outside of Win32 exception context
+		Win32ExcAddress = aException->ExceptionAddress;
+		Win32ExcDataAddress = (TAny*)aException->ExceptionInformation[1];
+		Win32ExcCode = aException->ExceptionCode;
+		aContext->Eip = (TUint32)&Exception;
+		}
+	return ExceptionContinueExecution;
+	}
+
+void NThread::Diverted()
+//
+// Forced diversion go through here, in order to 'enter' the kernel
+//
+	{
+	NThread& me = *static_cast<NThread*>(TheScheduler.iCurrentThread);
+	__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
+	__NK_ASSERT_ALWAYS(me.iDiverted);
+	NThread::TDivert divert = me.iDivert;
+	me.iDiverted = EFalse;
+	me.iDivert = NULL;
+	me.iDivertReturn = NULL;
+	EnterKernel(FALSE);
+	if (divert)
+		divert();	// does not return
+	NKern::Unlock();
+	LeaveKernel();
+	}
+
+void NThread__Diverted()
+	{
+	NThread::Diverted();
+	}
+
+__NAKED__ void DivertHook()
+	{
+	// The stack frame is set up like this:
+	//
+	//		| return address |
+	//		| frame pointer  |
+	//		| flags			 |
+	//		| saved eax		 |
+	//		| saved ecx		 |
+	//      | saved edx		 |
+	//		
+	__asm push eax					// reserve word for return address
+	__asm push ebp
+	__asm mov ebp, esp 
+	__asm pushfd
+	__asm push eax
+	__asm push ecx
+	__asm push edx
+	__asm mov eax, [TheScheduler.iCurrentThread]
+	__asm mov eax, [eax]NThread.iDivertReturn
+	__asm mov [esp + 20], eax		// store return address
+	__asm call NThread__Diverted
+	__asm pop edx
+	__asm pop ecx
+	__asm pop eax
+	__asm popfd
+	__asm pop ebp
+	__asm ret
+	}
+
+
+void NThread::ApplyDiversion()
+	{
+	// Called with interrupts disabled and kernel locked
+	__NK_ASSERT_ALWAYS(TheScheduler.iKernCSLocked == 1);
+	if (iDiverted)
+		return;
+	CONTEXT c;
+	c.ContextFlags=CONTEXT_FULL;
+	GetThreadContext(iWinThread, &c);
+	__NK_ASSERT_ALWAYS(iDivertReturn == NULL);
+	iDivertReturn = (TAny*)c.Eip;
+	c.Eip=(TUint32)&DivertHook;
+	SetThreadContext(iWinThread, &c);
+	iDiverted = ETrue;
+	}
+
+void NThread::Divert(TDivert aDivert)
+//
+// Divert the thread from its current path
+// The diversion function is called with the kernel locked and interrupts enabled
+//
+	{
+	iDivert = aDivert;
+	if (iWakeup == EResume)
+		iWakeup = EResumeDiverted;
+	else
+		__NK_ASSERT_ALWAYS(iWakeup == ERelease);
+	}
+
+void NThread::ExitSync()
+//
+// Diversion used to terminate 'stillborn' threads.
+// On entry, kernel is locked, interrupts are enabled and we hold an interlock mutex
+//
+	{
+	NThreadBase& me=*TheScheduler.iCurrentThread;
+	me.iHeldFastMutex->Signal();	// release the interlock
+	me.iNState=EDead;				// mark ourselves as dead which will take thread out of scheduler
+	TheScheduler.Remove(&me);
+	RescheduleNeeded();
+	TScheduler::Reschedule();	// this won't return
+	FAULT();
+	}
+
+void NThread::Stillborn()
+//
+// Called if the new thread creation was aborted - so it will not be killed in the usual manner
+//
+// This function needs to exit the thread synchronously as on return we will destroy the thread control block
+// Thus wee need to use an interlock that ensure that the target thread runs the exit handler before we continue
+//
+	{
+	// check if the Win32 thread was created
+	if (!iWinThread)
+		return;
+
+	NKern::Lock();
+	Divert(&ExitSync);
+	ForceResume();
+	// create and assign mutex to stillborn thread
+	NFastMutex interlock;
+	interlock.iHoldingThread=this;
+	iHeldFastMutex=&interlock;
+	interlock.Wait();			// interlock on thread exit handler
+	interlock.Signal();
+	NKern::Unlock();
+	}
+
+void NThread::ExitAsync()
+//
+// Diversion used to terminate 'killed' threads.
+// On entry, kernel is locked and interrupts are enabled
+//
+	{
+	NThreadBase& me = *TheScheduler.iCurrentThread;
+	me.iCsCount = 0;
+	__NK_ASSERT_ALWAYS(static_cast<NThread&>(me).iInKernel>0);
+	me.Exit();
+	}
+
+void NThreadBase::OnKill()
+	{
+	}
+
+void NThreadBase::OnExit()
+	{
+	}
+
+inline void NThread::DoForceExit()
+	{
+	__NK_ASSERT_DEBUG(TheScheduler.iKernCSLocked);
+//
+	Divert(&ExitAsync);
+	}
+
+void NThreadBase::ForceExit()
+//
+// Called to force the thread to exit when it resumes
+//
+	{
+	static_cast<NThread*>(this)->DoForceExit();
+	}
+
+//
+// We need a global lock in the emulator to avoid scheduling reentrancy problems with the host
+// in particular, some host API calls acquire host mutexes, preempting such services results
+// in suspension of those threads which can cause deadlock if another thread requires that host
+// mutex.
+//
+// Because thread dreaction and code loading also require the same underlying mutex (used
+// by NT to protect DLL entrypoint calling), this would be rather complex with a fast mutex.
+// For now, keep it simple and use the preemption lock. Note that this means that the
+// MS timer DFC may be significantly delayed when loading large DLL trees, for example.
+//
+
+void SchedulerLock()
+//
+// Acquire the global lock. May be called before scheduler running, so handle that case
+//
+	{
+	if (TheScheduler.iCurrentThread)
+		{
+		EnterKernel();
+		NKern::Lock();
+		}
+	}
+
+void SchedulerUnlock()
+//
+// Release the global lock. May be called before scheduler running, so handle that case
+//
+	{
+	if (TheScheduler.iCurrentThread)
+		{
+		NKern::Unlock();
+		LeaveKernel();
+		}
+	}
+