--- /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)¶meterBlock)+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();
+ }
+ }
+