diff -r 000000000000 -r bde4ae8d615e os/kernelhwsrv/kernel/eka/nkernsmp/nkern.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/os/kernelhwsrv/kernel/eka/nkernsmp/nkern.cpp Fri Jun 15 03:10:57 2012 +0200 @@ -0,0 +1,2708 @@ +// Copyright (c) 2005-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\nkernsmp\nkern.cpp +// +// + +// NThreadBase member data +#define __INCLUDE_NTHREADBASE_DEFINES__ + +#include "nk_priv.h" + +/****************************************************************************** + * Fast mutex + ******************************************************************************/ + +/** Acquires the fast mutex. + + This will block until the mutex is available, and causes + the thread to enter an implicit critical section until the mutex is released. + + Generally threads would use NKern::FMWait() which manipulates the kernel lock + for you. + + @pre Kernel must be locked, with lock count 1. + @pre The calling thread holds no fast mutexes. + + @post Kernel is locked, with lock count 1. + @post The calling thread holds the mutex. + + @see NFastMutex::Signal() + @see NKern::FMWait() +*/ +EXPORT_C void NFastMutex::Wait() + { + NThreadBase* pC = NCurrentThreadL(); + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED_ONCE|MASK_NO_FAST_MUTEX,"NFastMutex::Wait"); + + pC->iHeldFastMutex = this; // to handle kill/suspend between here and setting iHeldFastMutex + DoWaitL(); + } + +void NFastMutex::DoWaitL() + { + NThreadBase* pC = NCurrentThreadL(); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T FMWait %M",pC,this)); + TBool waited = FALSE; // set if we actually had to wait + iMutexLock.LockOnly(); // acquire mutex spin lock + __e32_atomic_ior_rlx_ptr(&iHoldingThread, 1); // set contention flag to make sure any other thread must acquire the mutex spin lock + pC->AcqSLock(); + FOREVER + { + if (pC->iFastMutexDefer == 1) + --pC->iParent->iFreezeCpu; + pC->iFastMutexDefer = 0; + NThreadBase* pH = (NThreadBase*)(TLinAddr(iHoldingThread) &~ 1); + if (!pH) + { + // mutex is free + TInt wp = iWaitQ.HighestPriority(); // -1 if no other thread wants the mutex + + // don't grab mutex if we have been suspended/killed/migrated by the previous holding thread + if (!pC->iSuspended && pC->iCsFunction!=NThreadBase::ECSDivertPending && (!pC->iParent->iCpuChange || pC->iParent->iFreezeCpu)) + { + TInt p = pC->iPriority; + if (p>wp || (p==wp && waited)) + { + // if we are highest priority waiting thread or equal and we have waited then grab the mutex + // don't just grab it if we are equal priority and someone else was already waiting + // set contention flag if other threads waiting or if current thread has a round robin outstanding + pC->iMutexPri = (TUint8)(wp>=0 ? wp : 0); // pC's actual priority doesn't change since p>=wp + iHoldingThread = (wp>=0 || TUint32(pC->iTime)==0x80000000u) ? (NThreadBase*)(TLinAddr(pC)|1) : pC; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T got mutex %M CF=%d WP=%d",TLinAddr(iHoldingThread)&~1,this,TLinAddr(iHoldingThread)&1,wp)); + pC->RelSLock(); + iMutexLock.UnlockOnly(); +#ifdef BTRACE_FAST_MUTEX + BTraceContext4(BTrace::EFastMutex, BTrace::EFastMutexWait, this); +#endif + return; + } + } + } + pC->iFastMutexDefer = 2; // signal to scheduler to allow ctxsw without incrementing iParent->iFreezeCpu + if (!pC->iSuspended && pC->iCsFunction!=NThreadBase::ECSDivertPending && (!pC->iParent->iCpuChange || pC->iParent->iFreezeCpu)) + { + // this forces priority changes to wait for the mutex lock + pC->iLinkedObjType = NThreadBase::EWaitFastMutex; + pC->iLinkedObj = this; + pC->iWaitState.SetUpWait(NThreadBase::EWaitFastMutex, NThreadWaitState::EWtStObstructed, this); + pC->iWaitLink.iPriority = pC->iPriority; + iWaitQ.Add(&pC->iWaitLink); + pC->RelSLock(); + if (pH) + pH->SetMutexPriority(this); +do_pause: + iMutexLock.UnlockOnly(); + RescheduleNeeded(); +#ifdef BTRACE_FAST_MUTEX + BTraceContext4(BTrace::EFastMutex, BTrace::EFastMutexBlock, this); +#endif + NKern::PreemptionPoint(); // we block here until the mutex is released and we are 'nominated' for it or we are suspended/killed + iMutexLock.LockOnly(); + pC->AcqSLock(); + if (pC->iPauseCount || pC->iSuspended || pC->iCsFunction==NThreadBase::ECSDivertPending || (pC->iParent->iCpuChange && !pC->iParent->iFreezeCpu)) + { + pC->RelSLock(); + goto do_pause; // let pause/suspend/kill take effect + } + // if thread was suspended it will have been removed from the wait queue + if (!pC->iLinkedObj) + goto thread_suspended; + iWaitQ.Remove(&pC->iWaitLink); // take ourselves off the wait/contend queue while we try to grab the mutex + pC->iWaitLink.iNext = 0; + pC->iLinkedObj = 0; + pC->iLinkedObjType = NThreadBase::EWaitNone; + waited = TRUE; + // if we are suspended or killed, we loop round again and do the 'else' clause next time + } + else + { + pC->RelSLock(); + if (pC->iSuspended || pC->iCsFunction==NThreadBase::ECSDivertPending) + { + // wake up next thread to take this one's place + if (!pH && !iWaitQ.IsEmpty()) + { + NThreadBase* pT = _LOFF(iWaitQ.First(), NThreadBase, iWaitLink); + pT->AcqSLock(); + // if thread is still blocked on this fast mutex, release it but leave it on the wait queue + // NOTE: it can't be suspended + pT->iWaitState.UnBlockT(NThreadBase::EWaitFastMutex, this, KErrNone); + pT->RelSLock(); + } + } + iMutexLock.UnlockOnly(); + NKern::PreemptionPoint(); // thread suspends/dies/migrates here + iMutexLock.LockOnly(); + pC->AcqSLock(); +thread_suspended: + waited = FALSE; + // set contention flag to make sure any other thread must acquire the mutex spin lock + // need to do it again since mutex may have been released while thread was suspended + __e32_atomic_ior_rlx_ptr(&iHoldingThread, 1); + } + } + } + + +#ifndef __FAST_MUTEX_MACHINE_CODED__ +/** Releases a previously acquired fast mutex. + + Generally, threads would use NKern::FMSignal() which manipulates the kernel lock + for you. + + @pre The calling thread holds the mutex. + @pre Kernel must be locked. + + @post Kernel is locked. + + @see NFastMutex::Wait() + @see NKern::FMSignal() +*/ +EXPORT_C void NFastMutex::Signal() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED,"NFastMutex::Signal"); +#ifdef BTRACE_FAST_MUTEX + BTraceContext4(BTrace::EFastMutex, BTrace::EFastMutexSignal, this); +#endif + NThreadBase* pC = NCurrentThreadL(); + ((volatile TUint32&)pC->iHeldFastMutex) |= 1; // flag to indicate about to release mutex + + if (__e32_atomic_cas_rel_ptr(&iHoldingThread, &pC, 0)) + { + // tricky if suspend/kill here + // suspend/kill should check flag set above and aMutex->iHoldingThread + // if bit 0 of iHeldFastMutex set and iHoldingThread==pC then set iHeldFastMutex=0 and proceed + + // no-one else was waiting for the mutex - simple + pC->iHeldFastMutex = 0; + return; + } + + // there was contention so do it the hard way + DoSignalL(); + } +#endif + +void NFastMutex::DoSignalL() + { + NThreadBase* pC = NCurrentThreadL(); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T FMSignal %M",pC,this)); + __ASSERT_WITH_MESSAGE_DEBUG(HeldByCurrentThread(),"The calling thread holds the mutex","NFastMutex::Signal"); + + iMutexLock.LockOnly(); + if (!iWaitQ.IsEmpty()) + { + NThreadBase* pT = _LOFF(iWaitQ.First(), NThreadBase, iWaitLink); + pT->AcqSLock(); + + // if thread is still blocked on this fast mutex, release it but leave it on the wait queue + // NOTE: it can't be suspended + pT->iWaitState.UnBlockT(NThreadBase::EWaitFastMutex, this, KErrNone); + pT->RelSLock(); + iHoldingThread = (NThreadBase*)1; // mark mutex as released but contended + } + else + iHoldingThread = 0; // mark mutex as released and uncontended + __KTRACE_OPT(KNKERN,DEBUGPRINT("SiHT=%d",iHoldingThread)); + pC->AcqSLock(); + pC->iHeldFastMutex = 0; + iMutexLock.UnlockOnly(); + pC->iMutexPri = 0; + if (pC->iPriority != pC->iBasePri) + { + // lose any inherited priority + pC->LoseInheritedPriorityT(); + } + if (TUint32(pC->iTime)==0x80000000u) + { + pC->iTime = 0; + RescheduleNeeded(); // handle deferred timeslicing + __KTRACE_OPT(KNKERN,DEBUGPRINT("DTS %T",pC)); + } + if (pC->iFastMutexDefer) + { + pC->iFastMutexDefer = 0; + --pC->iParent->iFreezeCpu; + } + if (pC->iParent->iCpuChange && !pC->iParent->iFreezeCpu) + RescheduleNeeded(); // need to migrate to another CPU + if (!pC->iCsCount && pC->iCsFunction) + pC->DoCsFunctionT(); + pC->RelSLock(); + } + + +/** Checks if the current thread holds this fast mutex + + @return TRUE if the current thread holds this fast mutex + @return FALSE if not + @pre Call in thread context. +*/ +EXPORT_C TBool NFastMutex::HeldByCurrentThread() + { + return (TLinAddr(iHoldingThread)&~1) == (TLinAddr)NKern::CurrentThread(); + } + + +/** Returns the fast mutex held by the calling thread, if any. + + @return If the calling thread currently holds a fast mutex, this function + returns a pointer to it; otherwise it returns NULL. + @pre Call in thread context. +*/ +EXPORT_C NFastMutex* NKern::HeldFastMutex() + { + NThreadBase* t = NKern::CurrentThread(); + NFastMutex* m = (NFastMutex*)(TLinAddr(t->iHeldFastMutex)&~3); + return (m && m->HeldByCurrentThread()) ? m : 0; + } + + +#ifndef __FAST_MUTEX_MACHINE_CODED__ +/** Acquires a fast mutex. + + This will block until the mutex is available, and causes + the thread to enter an implicit critical section until the mutex is released. + + @param aMutex The fast mutex to acquire. + + @post The calling thread holds the mutex. + + @see NFastMutex::Wait() + @see NKern::FMSignal() + + @pre No fast mutex can be held. + @pre Call in a thread context. + @pre Kernel must be unlocked + @pre interrupts enabled + +*/ +EXPORT_C void NKern::FMWait(NFastMutex* aMutex) + { + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFMW %M", aMutex)); + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD,"NKern::FMWait"); + NThreadBase* pC = NKern::CurrentThread(); + + // If the reschedule IPI from an external suspend or kill occurs after this + // point the initiating CPU must observe the write to iHeldFastMutex before + // the cas operation. + pC->iHeldFastMutex = aMutex; // kill/suspend after this point should set mutex contention flag + NThreadBase* expect = 0; + if (__e32_atomic_cas_acq_ptr(&aMutex->iHoldingThread, &expect, pC)) + { + // mutex was free and we have just claimed it - simple +#ifdef BTRACE_FAST_MUTEX + BTraceContext4(BTrace::EFastMutex, BTrace::EFastMutexWait, aMutex); +#endif + return; + } + + // care required if suspend/kill here + + // there is contention so do it the hard way + NKern::Lock(); + aMutex->DoWaitL(); + NKern::Unlock(); + } + + +/** Releases a previously acquired fast mutex. + + @param aMutex The fast mutex to release. + + @pre The calling thread holds the mutex. + + @see NFastMutex::Signal() + @see NKern::FMWait() +*/ +EXPORT_C void NKern::FMSignal(NFastMutex* aMutex) + { + NThreadBase* pC = NKern::CurrentThread(); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFMS %M", aMutex)); +#ifdef BTRACE_FAST_MUTEX + BTraceContext4(BTrace::EFastMutex, BTrace::EFastMutexSignal, aMutex); +#endif + ((volatile TUint32&)pC->iHeldFastMutex) |= 1; // flag to indicate about to release mutex + + if (__e32_atomic_cas_rel_ptr(&aMutex->iHoldingThread, &pC, 0)) + { + // no-one else was waiting for the mutex and we have just released it + + // tricky if suspend/kill here + // suspend/kill should check flag set above and aMutex->iHoldingThread + // if bit 0 of iHeldFastMutex set and iHoldingThread==pC then set iHeldFastMutex=0 and proceed + + // If the reschedule IPI from an external suspend or kill occurs after this + // point the initiating CPU must observe the write to iHeldFastMutex after + // the cas operation. + pC->iHeldFastMutex = 0; + return; + } + + // there was contention so do it the hard way + NKern::Lock(); + aMutex->DoSignalL(); + NKern::Unlock(); + } + +/** Acquires the System Lock. + + This will block until the mutex is available, and causes + the thread to enter an implicit critical section until the mutex is released. + + @post System lock is held. + + @see NKern::UnlockSystem() + @see NKern::FMWait() + + @pre No fast mutex can be held. + @pre Call in a thread context. + @pre Kernel must be unlocked + @pre interrupts enabled + +*/ +EXPORT_C void NKern::LockSystem() + { + NKern::FMWait(&TheScheduler.iLock); + } + + +/** Releases the System Lock. + + @pre System lock must be held. + + @see NKern::LockSystem() + @see NKern::FMSignal() +*/ +EXPORT_C void NKern::UnlockSystem() + { + NKern::FMSignal(&TheScheduler.iLock); + } + + +/** Temporarily releases a fast mutex if there is contention. + + If there is another thread attempting to acquire the mutex, the calling + thread releases the mutex and then acquires it again. + + This is more efficient than the equivalent code: + + @code + NKern::FMSignal(); + NKern::FMWait(); + @endcode + + @return TRUE if the mutex was relinquished, FALSE if not. + + @pre The mutex must be held. + + @post The mutex is held. +*/ +EXPORT_C TBool NKern::FMFlash(NFastMutex* aM) + { + NThreadBase* pC = NKern::CurrentThread(); + __ASSERT_WITH_MESSAGE_DEBUG(aM->HeldByCurrentThread(),"The calling thread holds the mutex","NKern::FMFlash"); + TBool w = (pC->iMutexPri >= pC->iBasePri); // a thread of greater or equal priority is waiting + if (w) + { + NKern::Lock(); + aM->Signal(); + NKern::PreemptionPoint(); + aM->Wait(); + NKern::Unlock(); + } +#ifdef BTRACE_FAST_MUTEX + else + { + BTraceContext4(BTrace::EFastMutex, BTrace::EFastMutexFlash, aM); + } +#endif + return w; + } + + +/** Temporarily releases the System Lock if there is contention. + + If there + is another thread attempting to acquire the System lock, the calling + thread releases the mutex and then acquires it again. + + This is more efficient than the equivalent code: + + @code + NKern::UnlockSystem(); + NKern::LockSystem(); + @endcode + + Note that this can only allow higher priority threads to use the System + lock as lower priority cannot cause contention on a fast mutex. + + @return TRUE if the system lock was relinquished, FALSE if not. + + @pre System lock must be held. + + @post System lock is held. + + @see NKern::LockSystem() + @see NKern::UnlockSystem() +*/ +EXPORT_C TBool NKern::FlashSystem() + { + CHECK_PRECONDITIONS(MASK_SYSTEM_LOCKED,"NKern::FlashSystem"); + return NKern::FMFlash(&TheScheduler.iLock); + } +#endif + +/****************************************************************************** + * Fast semaphore + ******************************************************************************/ + +/** Sets the owner of a fast semaphore. + + @param aThread The thread to own this semaphore. If aThread==0, then the + owner is set to the current thread. + + @pre Kernel must be locked. + @pre If changing ownership form one thread to another, the there must be no + pending signals or waits. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. +*/ +EXPORT_C void NFastSemaphore::SetOwner(NThreadBase* aThread) + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NFastSemaphore::SetOwner"); + if (!aThread) + aThread = NCurrentThreadL(); + if (iOwningThread && iOwningThread!=aThread) + { + __NK_ASSERT_ALWAYS(!iCount); // Can't change owner if iCount!=0 + } + iOwningThread = aThread; + } + + +#ifndef __FAST_SEM_MACHINE_CODED__ +/** Waits on a fast semaphore. + + Decrements the signal count for the semaphore and + removes the calling thread from the ready-list if the semaphore becomes + unsignalled. Only the thread that owns a fast semaphore can wait on it. + + Note that this function does not block, it merely updates the NThread state, + rescheduling will only occur when the kernel is unlocked. Generally threads + would use NKern::FSWait() which manipulates the kernel lock for you. + + @pre The calling thread must own the semaphore. + @pre No fast mutex can be held. + @pre Kernel must be locked. + + @post Kernel is locked. + + @see NFastSemaphore::Signal() + @see NKern::FSWait() + @see NKern::Unlock() + */ +EXPORT_C void NFastSemaphore::Wait() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NO_FAST_MUTEX,"NFastSemaphore::Wait"); + NThreadBase* pC = NCurrentThreadL(); + __ASSERT_WITH_MESSAGE_ALWAYS(pC==iOwningThread,"The calling thread must own the semaphore","NFastSemaphore::Wait"); + pC->iWaitState.SetUpWait(NThreadBase::EWaitFastSemaphore, 0, this); + if (Dec(pC)) // full barrier + pC->iWaitState.CancelWait(); // don't have to wait + else + RescheduleNeeded(); // have to wait + } + + +/** Signals a fast semaphore. + + Increments the signal count of a fast semaphore by + one and releases any waiting thread if the semphore becomes signalled. + + Note that a reschedule will not occur before this function returns, this will + only take place when the kernel is unlocked. Generally threads + would use NKern::FSSignal() which manipulates the kernel lock for you. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + + @see NFastSemaphore::Wait() + @see NKern::FSSignal() + @see NKern::Unlock() + */ +EXPORT_C void NFastSemaphore::Signal() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NFastSemaphore::Signal"); + NThreadBase* t = Inc(1); // release semantics + if (t) + { + t->AcqSLock(); + t->iWaitState.UnBlockT(NThreadBase::EWaitFastSemaphore, this, KErrNone); + t->RelSLock(); + } + } + + +/** Signals a fast semaphore multiple times. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + + @internalComponent + */ +EXPORT_C void NFastSemaphore::SignalN(TInt aCount) + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NFastSemaphore::SignalN"); + __NK_ASSERT_DEBUG(aCount>=0); + if (aCount > 0) + { + NThreadBase* t = Inc(aCount); + if (t) + { + t->AcqSLock(); + t->iWaitState.UnBlockT(NThreadBase::EWaitFastSemaphore, this, KErrNone); + t->RelSLock(); + } + } + } + + +/** Cancels a wait on a fast semaphore. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + + @internalComponent + */ +void NFastSemaphore::WaitCancel() + { + Inc(1); + } + + +/** Waits for a signal on the current thread's I/O semaphore. + + @pre No fast mutex can be held. + @pre Call in a thread context. + @pre Kernel must be unlocked + @pre interrupts enabled + */ +EXPORT_C void NKern::WaitForAnyRequest() + { + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD,"NKern::WaitForAnyRequest"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("WfAR")); + NThreadBase* t = NKern::LockC(); + NFastSemaphore* s = &t->iRequestSemaphore; + t->iWaitState.SetUpWait(NThreadBase::EWaitFastSemaphore, 0, s); + if (s->Dec(t)) // fully ordered semantics + t->iWaitState.CancelWait(); // don't have to wait + else + RescheduleNeeded(); // have to wait + NKern::Unlock(); + } +#endif + + +/** Resets a fast semaphore. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + + @internalComponent + */ +EXPORT_C void NFastSemaphore::Reset() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NFastSemaphore::Reset"); + NThreadBase* t = DoReset(); + if (t) + { + t->AcqSLock(); + t->iWaitState.UnBlockT(NThreadBase::EWaitFastSemaphore, this, KErrNone); + t->RelSLock(); + } + } + + +/** Sets the owner of a fast semaphore. + + @param aSem The semaphore to change ownership off. + @param aThread The thread to own this semaphore. If aThread==0, then the + owner is set to the current thread. + + @pre If changing ownership form one thread to another, the there must be no + pending signals or waits. +*/ +EXPORT_C void NKern::FSSetOwner(NFastSemaphore* aSem,NThreadBase* aThread) + { + __KTRACE_OPT(KNKERN,DEBUGPRINT("NKern::FSSetOwner %m %T",aSem,aThread)); + NKern::Lock(); + aSem->SetOwner(aThread); + NKern::Unlock(); + } + +#ifndef __FAST_SEM_MACHINE_CODED__ +/** Waits on a fast semaphore. + + Decrements the signal count for the semaphore + and waits for a signal if the semaphore becomes unsignalled. Only the + thread that owns a fast semaphore can wait on it. + + @param aSem The semaphore to wait on. + + @pre The calling thread must own the semaphore. + @pre No fast mutex can be held. + + @see NFastSemaphore::Wait() +*/ +EXPORT_C void NKern::FSWait(NFastSemaphore* aSem) + { + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFSW %m",aSem)); + NKern::Lock(); + aSem->Wait(); + NKern::Unlock(); + } + + +/** Signals a fast semaphore. + + Increments the signal count of a fast semaphore + by one and releases any waiting thread if the semaphore becomes signalled. + + @param aSem The semaphore to signal. + + @see NKern::FSWait() + + @pre Interrupts must be enabled. + @pre Do not call from an ISR + */ +EXPORT_C void NKern::FSSignal(NFastSemaphore* aSem) + { + CHECK_PRECONDITIONS(MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR,"NKern::FSSignal(NFastSemaphore*)"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFSS %m",aSem)); + NKern::Lock(); + aSem->Signal(); + NKern::Unlock(); + } + + +/** Signals a fast semaphore multiple times. + + Increments the signal count of a + fast semaphore by aCount and releases any waiting thread if the semphore + becomes signalled. + + @param aSem The semaphore to signal. + @param aCount The number of times to signal the semaphore. + + @see NKern::FSWait() + + @pre Interrupts must be enabled. + @pre Do not call from an ISR + */ +EXPORT_C void NKern::FSSignalN(NFastSemaphore* aSem, TInt aCount) + { + CHECK_PRECONDITIONS(MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR,"NKern::FSSignalN(NFastSemaphore*, TInt)"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFSSN %m %d",aSem,aCount)); + __NK_ASSERT_DEBUG(aCount>=0); + if (aCount == 0) + return; + NKern::Lock(); + aSem->SignalN(aCount); + NKern::Unlock(); + } + + +/** Signals the request semaphore of a nanothread. + + This function is intended to be used by the EPOC layer and personality + layers. Device drivers should use Kern::RequestComplete instead. + + @param aThread Nanothread to signal. Must be non NULL. + + @see Kern::RequestComplete() + + @pre Interrupts must be enabled. + @pre Do not call from an ISR + */ +EXPORT_C void NKern::ThreadRequestSignal(NThread* aThread) + { + NKern::FSSignal(&aThread->iRequestSemaphore); + } + + +/** Signals the request semaphore of a nanothread several times. + + This function is intended to be used by the EPOC layer and personality + layers. Device drivers should use Kern::RequestComplete instead. + + @param aThread Nanothread to signal. If NULL, the current thread is signaled. + @param aCount Number of times the request semaphore must be signaled. + + @pre aCount >= 0 + + @see Kern::RequestComplete() + */ +EXPORT_C void NKern::ThreadRequestSignal(NThread* aThread, TInt aCount) + { + __ASSERT_WITH_MESSAGE_DEBUG(aCount >= 0,"aCount >= 0","NKern::ThreadRequestSignal"); + if (!aThread) + aThread = (NThread*)NKern::CurrentThread(); + NKern::FSSignalN(&aThread->iRequestSemaphore, aCount); + } +#endif + + + +/** Atomically signals a fast semaphore and releases a fast mutex. + + Rescheduling only occurs after both synchronisation operations are complete. + + @param aSem The semaphore to signal. + @param aMutex The mutex to release. If NULL, the System Lock is released + + @pre The calling thread must hold the mutex. + + @see NKern::FMSignal() + */ +EXPORT_C void NKern::FSSignal(NFastSemaphore* aSem, NFastMutex* aMutex) + { + if (!aMutex) + aMutex=&TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFSS %m +FM %M",aSem,aMutex)); + NKern::Lock(); + aSem->Signal(); + aMutex->Signal(); + NKern::Unlock(); + } + + +/** Atomically signals a fast semaphore multiple times and releases a fast mutex. + + Rescheduling only occurs after both synchronisation operations are complete. + + @param aSem The semaphore to signal. + @param aCount The number of times to signal the semaphore. + @param aMutex The mutex to release. If NULL, the System Lock is released. + + @pre The calling thread must hold the mutex. + + @see NKern::FMSignal() + */ +EXPORT_C void NKern::FSSignalN(NFastSemaphore* aSem, TInt aCount, NFastMutex* aMutex) + { + if (!aMutex) + aMutex=&TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("NFSSN %m %d + FM %M",aSem,aCount,aMutex)); + NKern::Lock(); + aSem->SignalN(aCount); + aMutex->Signal(); + NKern::Unlock(); + } + + +/****************************************************************************** + * Thread + ******************************************************************************/ + +void NThreadBase::DoCsFunctionT() + { + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nDoCsFuncT %d",this,iCsFunction)); + TInt f=iCsFunction; + if (f==0) + return; + if (f>0) + { + // suspend this thread f times + iCsFunction = 0; + iSuspendCount += f; + iSuspended = 1; + RescheduleNeeded(); + return; + } + if (f==ECSExitPending || f==ECSDivertPending) + { + // We need to exit now + RelSLock(); + Exit(); // this won't return + } +// UnknownState(ELeaveCS,f); // call into RTOS personality + __NK_ASSERT_ALWAYS(0); + } + +TBool NThreadBase::DoSuspendOrKillT(TInt aCount, TSubScheduler* aS) + { + TBool result = TRUE; + if (aCount>=0) + { + if (iSuspended) + result = FALSE; + iSuspendCount+=aCount; + iSuspended = 1; + if (!iCurrent) + { + if (aS) + UnReadyT(); + else if (iReady) + { + NThreadGroup* g = (NThreadGroup*)iParent; + g->iNThreadList.Remove(this); + } + } + if (this == NCurrentThreadL()) + RescheduleNeeded(); + if (aS) + aS->iReadyListLock.UnlockOnly(); + } + else + { + iCsFunction = ECSDivertPending; + iSuspendCount = 0; + iSuspended = 0; + if (aS) + aS->iReadyListLock.UnlockOnly(); + DoReleaseT(KErrDied,0); + if (!iReady && !iPauseCount) + ReadyT(0); + } + return result; + } + +// If aCount>=0 suspend the thread aCount times +// If aCount<0 kill the thread +TBool NThreadBase::SuspendOrKill(TInt aCount) + { + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nSuspendOrKill %d", this, aCount)); + if (aCount==0) + return FALSE; + TBool result = FALSE; + TBool concurrent = FALSE; + TSubScheduler* ss = 0; + AcqSLock(); + NFastMutex* wfm = 0; + if (iLinkedObj && iLinkedObjType==EWaitFastMutex) + wfm = (NFastMutex*)iLinkedObj; + if (iCsFunction<0) + goto done2; // if already exiting ignore suspend or kill + if (wfm) + { + // if thread is waiting on a fast mutex, need to acquire mutex lock + ++iPauseCount; + RelSLock(); + wfm->iMutexLock.LockOnly(); + AcqSLock(); + UnPauseT(); + } + if (iReady && iParent->iReady) + { + ss = TheSubSchedulers + (iParent->iReady & EReadyCpuMask); + ss->iReadyListLock.LockOnly(); + } + concurrent = (iCurrent && this!=NCurrentThreadL()); + if (iWaitState.ThreadIsDead()) // already dead so suspension/kill is a no-op + goto done; + if (concurrent) + { + // thread is actually running on another CPU + // interrupt that CPU and wait for it to enter interrupt mode + // this allows a snapshot of the thread state to be observed + // in this state, the thread cannot enter or leave a critical section + send_resched_ipi_and_wait(iLastCpu); + } + if (iCsCount) + { +suspend_or_kill_in_cs: + __KTRACE_OPT(KNKERN,DEBUGPRINT("n Suspend %T (CSF %d) %d",this,iCsFunction,aCount)); + if (aCount>0) // -ve means thread is about to exit + iCsFunction+=aCount; // so thread will suspend itself when it leaves the critical section + else + iCsFunction = ECSExitPending; + goto done; + } + // iCsCount==0 and it can't become nonzero until we release the thread spin lock + // (since threads may not set iCsCount to a nonzero value with the kernel lock held) + // Make sure the thread isn't actually about to exit by itself + if (iCsFunction<0) + goto done; // if already exiting ignore suspend or kill + if (wfm) + { + wfm->iWaitQ.Remove(&iWaitLink); // take thread off the wait/contend queue + iWaitLink.iNext = 0; + iLinkedObj = 0; + iLinkedObjType = EWaitNone; + result = DoSuspendOrKillT(aCount, ss); + if (aCount>0) + DoReleaseT(KErrGeneral, 0); // thread isn't blocked any more, just suspended + RelSLock(); + + // May need to adjust holding thread's inherited priority. + // May need to wake up next thread to take this one's place. + NThreadBase* pH = (NThreadBase*)(TLinAddr(wfm->iHoldingThread) &~ 1); + if (pH) + pH->SetMutexPriority(wfm); + else if (!pH && !wfm->iWaitQ.IsEmpty()) + { + NThreadBase* pT = _LOFF(wfm->iWaitQ.First(), NThreadBase, iWaitLink); + pT->AcqSLock(); + pT->iWaitState.UnBlockT(NThreadBase::EWaitFastMutex, wfm, KErrNone); + pT->RelSLock(); + } + wfm->iMutexLock.UnlockOnly(); + return result; + } + if (CheckFastMutexDefer()) + goto suspend_or_kill_in_cs; + + // thread not in critical section, so suspend it + result = DoSuspendOrKillT(aCount, ss); + goto done2; + +done: + if (wfm) + wfm->iMutexLock.UnlockOnly(); + if (ss) + ss->iReadyListLock.UnlockOnly(); +done2: + RelSLock(); + + return result; + } + + +/** Suspends a nanothread the specified number of times. + + For use by RTOS personality layers. + Do not use this function directly on a Symbian OS thread. + Since the kernel is locked on entry, any reschedule will be deferred until + it is unlocked. + The suspension will be deferred if the target thread is currently in a + critical section; in this case the suspension will take effect when it exits + the critical section. + The thread's unknown state handler will be invoked with function ESuspend and + parameter aCount if the current NState is not recognised and it is not in a + critical section. + + @param aCount = the number of times to suspend. + @return TRUE, if the suspension has taken immediate effect; + FALSE, if the thread is in a critical section or is already suspended. + + @pre Kernel must be locked. + @pre Call in a thread context. + + @post Kernel is locked. + */ +EXPORT_C TBool NThreadBase::Suspend(TInt aCount) + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NThreadBase::Suspend"); + __NK_ASSERT_ALWAYS(aCount>=0); + + // If thread is executing a critical section, we must defer the suspend + + return SuspendOrKill(aCount); + } + + +TBool NThreadBase::Resume(TBool aForce) + { + TBool result = FALSE; + AcqSLock(); + if (iWaitState.ThreadIsDead() || iCsFunction<0) // already dead or dying so resume is a no-op + goto done; + + if (iCsFunction>0) + { + if (aForce) + iCsFunction = 0; + else + --iCsFunction; + } + else if (iSuspendCount) + { + if (aForce) + iSuspendCount = 0; + else + --iSuspendCount; + if (!iSuspendCount) + { + result = TRUE; + iSuspended = 0; + if (!iPauseCount && !iReady && !iWaitState.iWtC.iWtStFlags) + ReadyT(0); + } + } + +done: + RelSLock(); + return result; + } + +/** Resumes a nanothread, cancelling one suspension. + + For use by RTOS personality layers. + Do not use this function directly on a Symbian OS thread. + Since the kernel is locked on entry, any reschedule will be deferred until + it is unlocked. + If the target thread is currently in a critical section this will simply + cancel one deferred suspension. + The thread's unknown state handler will be invoked with function EResume if + the current NState is not recognised and it is not in a critical section. + + @return TRUE, if the resumption has taken immediate effect; + FALSE, if the thread is in a critical section or is still suspended. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel must be locked. + */ +EXPORT_C TBool NThreadBase::Resume() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NThreadBase::Resume"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nRsm",this)); + + return Resume(FALSE); + } + + +/** Resumes a nanothread, cancelling all outstanding suspensions. + + For use by RTOS personality layers. + Do not use this function directly on a Symbian OS thread. + Since the kernel is locked on entry, any reschedule will be deferred until + it is unlocked. + If the target thread is currently in a critical section this will simply + cancel all deferred suspensions. + The thread's unknown state handler will be invoked with function EForceResume + if the current NState is not recognised and it is not in a critical section. + + @return TRUE, if the resumption has taken immediate effect; + FALSE, if the thread is in a critical section. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + */ +EXPORT_C TBool NThreadBase::ForceResume() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NThreadBase::ForceResume"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nFRsm",this)); + + return Resume(TRUE); + } + + +void NThreadBase::DoReleaseT(TInt aReturnCode, TUint aMode) + { + TAny* wobj = 0; + TUint32 b = iWaitState.ReleaseT(wobj, aReturnCode); // cancels timer if necessary + + // if wait pending or no wait, done + // if wait in effect and nothing else stopping it, make thread ready + // cancel any outstanding wait on fast semaphore if abnormal release + // FIXME: Potential problems with abnormal release of generic wait objects + if (aReturnCode<0 && ((b>>8)&0xff)==NThreadBase::EWaitFastSemaphore && wobj) + ((NFastSemaphore*)wobj)->WaitCancel(); + + if ((b & NThreadWaitState::EWtStWaitActive) && !iPauseCount && !iSuspended) + ReadyT(aMode); + } + +/** Releases a waiting nanokernel thread. + + For use by RTOS personality layers. + Do not use this function directly on a Symbian OS thread. + This function should make the thread ready (provided it is not explicitly + suspended) and cancel any wait timeout. It should also remove it from any + wait queues. + If aReturnCode is nonnegative it indicates normal completion of the wait. + If aReturnCode is negative it indicates early/abnormal completion of the + wait and so any wait object should be reverted as if the wait had never + occurred (eg semaphore count should be incremented as this thread has not + actually acquired the semaphore). + The thread's unknown state handler will be invoked with function ERelease + and parameter aReturnCode if the current NState is not recognised. + + @param aReturnCode The reason code for release. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + */ +EXPORT_C void NThreadBase::Release(TInt aReturnCode, TUint aMode) + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NThreadBase::Release"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nRel %d",this,aReturnCode)); + AcqSLock(); + DoReleaseT(aReturnCode, aMode); + RelSLock(); + } + + +/** Signals a nanokernel thread's request semaphore. + + This can also be used on Symbian OS threads. + + @pre Kernel must be locked. + @pre Call either in a thread or an IDFC context. + + @post Kernel is locked. + */ +EXPORT_C void NThreadBase::RequestSignal() + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR,"NThreadBase::RequestSignal"); + iRequestSemaphore.Signal(); + } + + +void exit_sync_fn(TAny* aDfc) + { + ((TDfc*)aDfc)->Enque(); + } + +void NThreadBase::Exit() + { + // The current thread is exiting + // Enter with kernel locked, don't return + __NK_ASSERT_DEBUG(this==NCurrentThreadL()); + + OnExit(); + + TInt threadCS = iCsCount; + TInt kernCS = SubScheduler().iKernLockCount; + iCsCount = 1; + AcqSLock(); + iCsFunction = ECSExitInProgress; + NFastMutex* m = NKern::HeldFastMutex(); + iHeldFastMutex = 0; + RelSLock(); + NKern::Unlock(); + __KTRACE_OPT(KSCHED,DEBUGPRINT("Exit %T %u",this,NTickCount())); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nExit, CSC %d HeldFM %M KernCS %d",this,threadCS,iHeldFastMutex,kernCS)); + if (kernCS!=1) + FAULT(); + if (m) + FAULT(); + if (threadCS) + FAULT(); + TDfc* pD = NULL; + NThreadExitHandler xh = iHandlers->iExitHandler; + if (xh) + pD = (*xh)((NThread*)this); // call exit handler + + // detach any tied events + DetachTiedEvents(); + + NKern::LeaveGroup(); // detach from group if exit handler didn't do it + + NKern::Lock(); +#ifdef BTRACE_THREAD_IDENTIFICATION + BTrace4(BTrace::EThreadIdentification,BTrace::ENanoThreadDestroy,this); +#endif + __NK_ASSERT_ALWAYS(iCsFunction == ECSExitInProgress); + iWaitState.SetDead(pD); // doesn't return + FAULT(); + } + +/** Kills a nanokernel thread. + + For use by RTOS personality layers. + Do not use this function directly on a Symbian OS thread. + + When acting on the calling thread, causes the calling thread to exit. + + When acting on another thread, causes that thread to exit unless it is + currently in a critical section. In this case the thread is marked as + "exit pending" and will exit as soon as it leaves the critical section. + + In either case the exiting thread first invokes its exit handler (if it + exists). The handler runs with preemption enabled and with the thread in a + critical section so that it may not be suspended or killed again. The + handler may return a pointer to a TDfc, which will be enqueued just before + the thread finally terminates (after the kernel has been relocked). This DFC + will therefore execute once the NThread has been safely removed from the + scheduler and is intended to be used to cleanup the NThread object and any + associated personality layer resources. + + @pre Kernel must be locked. + @pre Call in a thread context. + @pre If acting on calling thread, calling thread must not be in a + critical section; if it is the kernel will fault. Also, the kernel + must be locked exactly once (iKernCSLocked = 1). + + @post Kernel is locked, if not acting on calling thread. + @post Does not return if it acts on the calling thread. + */ +EXPORT_C void NThreadBase::Kill() + { + // Kill a thread + // Enter with kernel locked + // Exit with kernel locked if not current thread, otherwise does not return + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED_ONCE|MASK_NOT_IDFC|MASK_NOT_ISR,"NThreadBase::Kill"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nKill",this)); + OnKill(); // platform-specific hook + NThreadBase* pC = NCurrentThreadL(); + if (this==pC) + { + if (iCsFunction==ECSExitInProgress) + FAULT(); + Exit(); // this will not return + } + SuspendOrKill(-1); + } + + +/** Change the CPU affinity of a thread + + @pre Kernel must be locked. + @pre Call in a thread context. + + @param The number of the CPU to which this thread should be locked, or + KCpuAny if it should be able to run on any CPU. + @return The previous affinity mask. +*/ +TUint32 NThreadBase::SetCpuAffinity(TUint32 aAffinity) + { + // check aAffinity is valid + AcqSLock(); + TUint32 old_aff = iParent->iCpuAffinity; + TBool migrate = FALSE; + TBool make_ready = FALSE; + TSubScheduler* ss0 = &SubScheduler(); + TSubScheduler* ss = 0; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nSetCpu %08x->%08x, F:%d R:%02x PR:%02x",this,iParent->iCpuAffinity,aAffinity,iParent->iFreezeCpu,iReady,iParent->iReady)); + if (i_NThread_Initial) + goto done; // can't change affinity of initial thread + iParent->iCpuAffinity = aAffinity; // set new affinity, might not take effect yet + if (!iParent->iReady) + goto done; // thread/group not currently on a ready list so can just change affinity + migrate = !CheckCpuAgainstAffinity(iParent->iReady & EReadyCpuMask, aAffinity); // TRUE if thread's current CPU is incompatible with the new affinity + if (!migrate) + goto done; // don't need to move thread, so just change affinity + ss = TheSubSchedulers + (iParent->iReady & EReadyCpuMask); + ss->iReadyListLock.LockOnly(); + if (iParent->iCurrent) + { + iParent->iCpuChange = TRUE; // mark CPU change pending + if (ss == ss0) + RescheduleNeeded(); + else + // kick other CPU now so migration happens before acquisition of fast mutex + send_resched_ipi_and_wait(iParent->iReady & EReadyCpuMask); + } + else + { + // Note: Need to know here if any thread in group would return TRUE from CheckFastMutexDefer() + // This is handled by the scheduler - when a thread belonging to a group is context switched + // out while holding a fast mutex its iFastMutexDefer is set to 1 and the group's iFreezeCpu + // is incremented. + if (iParent->iFreezeCpu || (iParent==this && CheckFastMutexDefer())) + iParent->iCpuChange = TRUE; // CPU frozen or fast mutex held so just mark deferred CPU migration + else + { + ss->Remove(iParent); + iParent->iReady = 0; + make_ready = TRUE; + } + } + ss->iReadyListLock.UnlockOnly(); + if (make_ready) + iParent->ReadyT(0); +done: + RelSLock(); + return old_aff; + } + + +/****************************************************************************** + * Thread wait state + ******************************************************************************/ +#ifndef __NTHREAD_WAITSTATE_MACHINE_CODED__ +void NThreadWaitState::SetUpWait(TUint aType, TUint aFlags, TAny* aWaitObj) + { + SetUpWait(aType, aFlags, aWaitObj, 0); + } + +void NThreadWaitState::SetUpWait(TUint aType, TUint aFlags, TAny* aWaitObj, TUint32 aTimeout) + { + aFlags &= EWtStObstructed; + aFlags |= EWtStWaitPending; + aType &= 0xff; + TUint64 ws64 = (TUint32)aWaitObj; + ws64 <<= 32; + ws64 |= ((aType<<8)|aFlags); + TUint64 oldws64 = __e32_atomic_swp_rlx64(&iWtSt64, ws64); + if (I64LOW(oldws64)!=0) + __crash(); // ??we were already waiting for something else?? + iTimer.iTriggerTime = aTimeout; + } + +void NThreadWaitState::CancelWait() + { + TUint64 oldws64 = __e32_atomic_swp_rlx64(&iWtSt64, 0); + if (oldws64 & (EWtStDead|EWtStWaitActive)) + __crash(); + } + +TInt NThreadWaitState::DoWait() + { + TUint64 oldws64 = iWtSt64; + TUint64 ws64; + TUint32 timeout = iTimer.iTriggerTime; + TUint32 set = timeout ? (EWtStWaitActive|EWtStTimeout) : EWtStWaitActive; + do { + TUint32 ws32 = I64LOW(oldws64); + if (ws32 & EWtStDead) + return KErrDied; + if (!(ws32 & EWtStWaitPending)) + return KErrGeneral; + ws64 = oldws64; + ws64 &= ~TUint64(EWtStWaitPending); + ws64 |= TUint64(set); + } while(!__e32_atomic_cas_rlx64(&iWtSt64, &oldws64, ws64)); + if (timeout) + { + if (iTimer.OneShot(timeout, TRUE)!=KErrNone) + __crash(); + ++iTimer.iNTimerSpare1; + } + return TUint32(oldws64)>>8; + } + +TInt NThreadWaitState::UnBlockT(TUint aType, TAny* aWaitObj, TInt aReturnValue) + { + TUint64 exp = TUint32(aWaitObj); + exp <<= 32; + exp |= (aType<<8); + TUint64 oldws64 = iWtSt64; + TUint64 ws64; + do { + if ((oldws64 ^ exp) < TUint64(EWtStDead)) + ws64 = TUint64(TUint32(aReturnValue))<<32; + else + ws64 = oldws64; + } while(!__e32_atomic_cas_rel64(&iWtSt64, &oldws64, ws64)); + if ((oldws64 ^ exp) >= TUint64(EWtStDead)) + return KErrGeneral; // not unblocked - no matching wait + if (oldws64 & EWtStTimeout) + CancelTimerT(); + if (oldws64 & EWtStWaitActive) + { + NThreadBase* t = Thread(); + if (!t->iPauseCount && !t->iSuspended) + t->ReadyT(0); + } + return KErrNone; + } + +TUint32 NThreadWaitState::ReleaseT(TAny*& aWaitObj, TInt aReturnValue) + { + TUint64 leave = EWtStDead; + TUint64 set = TUint64(TUint32(aReturnValue))<<32; + TUint64 ws64 = __e32_atomic_axo_ord64(&iWtSt64, leave, set); + aWaitObj = (TAny*)I64HIGH(ws64); + TUint32 ws32 = I64LOW(ws64); + if (ws32 & EWtStTimeout) + CancelTimerT(); + return ws32; + } +#endif + +void NThreadWaitState::SetDead(TDfc* aKillDfc) + { + TDfc syncDfc(&exit_sync_fn, aKillDfc, TheTimerQ.iDfc.iDfcQ, 0); + NThreadBase* t = Thread(); + t->AcqSLock(); + iWtC.iWtStFlags = NThreadWaitState::EWtStDead; + iWtC.iWtObjType = NThreadBase::EWaitNone; + CancelTimerT(); + if (aKillDfc && iTimer.iNTimerSpare1) + { + // There is an outstanding timer expiry handler still running + // so we must synchronise with DfcThread1. + // Add a priority 0 DFC to DfcThread1 so this thread's exit DFC can + // only run after the timer expiry handler has completed. + aKillDfc = &syncDfc; + } + iWtC.iKillDfc = aKillDfc; + RescheduleNeeded(); + t->RelSLock(); + NKern::Unlock(); // this won't return + } + +void NThreadWaitState::CancelTimerT() + { + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nCancelTimerT ",Thread())); + if (iTimer.Cancel()) + --iTimer.iNTimerSpare1; + else + { + // Potential race condition - timer must have completed but expiry + // handler has not yet run. Signal to the handler that it should do + // nothing by flipping the bottom bit of iTimer.iPtr + // This condition cannot possibly recur until the expiry handler has + // run since all expiry handlers run in DfcThread1. + volatile TLinAddr& x = *(volatile TLinAddr*)&iTimer.iPtr; + x ^= 1; + } + } + +// Timeout handler, called in DfcThread1 +// NOTE: aPtr is sampled with the timer queue locked, so if Cancel() on the timer fails +// and iTimer.iPtr is then changed, aPtr here will differ from iTimer.iPtr. +// This fact is used here to detect expiry of cancelled timers. +void NThreadWaitState::TimerExpired(TAny* aPtr) + { + TLinAddr cookie = (TLinAddr)aPtr; + NThreadWaitState* pW = (NThreadWaitState*)(cookie &~ 3); + NThread* pT = (NThread*)pW->Thread(); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T nTmExp",pT)); + NThreadTimeoutHandler th = pT->iHandlers->iTimeoutHandler; + pT->LAcqSLock(); + TUint flags = pW->iWtSt32[0]; + if (!(flags & EWtStWaitActive) || ((flags>>8)&0xff)!=NThreadBase::EWaitBlocked) + th = 0; + if (th) + { + // Use higher level timeout handler + pT->RelSLockU(); + (*th)(pT, NThreadBase::ETimeoutPreamble); + TInt param = NThreadBase::ETimeoutPostamble; + pT->LAcqSLock(); + TLinAddr current_cookie = *(volatile TLinAddr*)&pW->iTimer.iPtr; + if ((cookie ^ current_cookie) & 1) + { + // The timer was cancelled just after expiring but before this function + // managed to acquire the thread spin lock, so it's spurious + param = NThreadBase::ETimeoutSpurious; + } + pT->RelSLockU(); + (*th)(pT, param); + pT->LAcqSLock(); + --pW->iTimer.iNTimerSpare1; // note timer has expired + pT->RelSLockU(); + return; + } + TLinAddr current_cookie = *(volatile TLinAddr*)&pW->iTimer.iPtr; + if ((cookie ^ current_cookie) & 1) + // The timer was cancelled just after expiring but before this function + // managed to acquire the thread spin lock, so just return without doing anything. + goto done; + pT->DoReleaseT(KErrTimedOut,0); +done: + pT->RelSLockU(); + } + + + +/****************************************************************************** + * NKern:: static functions + ******************************************************************************/ + +/** Suspends the execution of a thread. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadSuspend(). + + If the thread is in a critical section or holds a fast mutex, the suspension will + be deferred until the thread leaves the critical section or signals the fast mutex. + Otherwise the thread will be suspended with immediate effect. If the thread it's + running, the execution of the thread will be suspended and a reschedule will occur. + + @param aThread Thread to be suspended. + @param aCount Number of times to suspend this thread. + + @return TRUE, if the thread had changed the state from non-suspended to suspended; + FALSE, otherwise. + + @see Kern::ThreadSuspend() +*/ +EXPORT_C TBool NKern::ThreadSuspend(NThread* aThread, TInt aCount) + { + NKern::Lock(); + TBool r=aThread->Suspend(aCount); + NKern::Unlock(); + return r; + } + + +/** Resumes the execution of a thread. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadResume(). + + This function resumes the thread once. If the thread was suspended more than once + the thread will remain suspended. + If the thread is in a critical section, this function will decrease the number of + deferred suspensions. + + @param aThread Thread to be resumed. + + @return TRUE, if the thread had changed the state from suspended to non-suspended; + FALSE, otherwise. + + @see Kern::ThreadResume() +*/ +EXPORT_C TBool NKern::ThreadResume(NThread* aThread) + { + NKern::Lock(); + TBool r=aThread->Resume(); + NKern::Unlock(); + return r; + } + + +/** Resumes the execution of a thread and signals a mutex. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadResume(). + + This function resumes the thread once. If the thread was suspended more than once + the thread will remain suspended. + If the thread is in a critical section, this function will decrease the number of + deferred suspensions. + + @param aThread Thread to be resumed. + @param aMutex Mutex to be signalled. If NULL, the scheduler's mutex will be signalled. + + @return TRUE, if the thread had changed the state from suspended to non-suspended; + FALSE, otherwise. + + @see Kern::ThreadResume() +*/ +EXPORT_C TBool NKern::ThreadResume(NThread* aThread, NFastMutex* aMutex) + { + if (!aMutex) + aMutex=&TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NRsm + FM %M",aThread,aMutex)); + NKern::Lock(); + TBool r=aThread->Resume(); + aMutex->Signal(); + NKern::Unlock(); + return r; + } + + +/** Forces the execution of a thread to be resumed. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadResume(). + + This function cancels all suspensions on a thread. + + @param aThread Thread to be resumed. + + @return TRUE, if the thread had changed the state from suspended to non-suspended; + FALSE, otherwise. + + @see Kern::ThreadResume() +*/ +EXPORT_C TBool NKern::ThreadForceResume(NThread* aThread) + { + NKern::Lock(); + TBool r=aThread->ForceResume(); + NKern::Unlock(); + return r; + } + + +/** Forces the execution of a thread to be resumed and signals a mutex. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadResume(). + + This function cancels all suspensions on a thread. + + @param aThread Thread to be resumed. + @param aMutex Mutex to be signalled. If NULL, the scheduler's mutex will be signalled. + + @return TRUE, if the thread had changed the state from suspended to non-suspended; + FALSE, otherwise. + + @see Kern::ThreadResume() +*/ +EXPORT_C TBool NKern::ThreadForceResume(NThread* aThread, NFastMutex* aMutex) + { + if (!aMutex) + aMutex=&TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NFRsm + FM %M",aThread,aMutex)); + NKern::Lock(); + TBool r=aThread->ForceResume(); + aMutex->Signal(); + NKern::Unlock(); + return r; + } + + +/** Awakens a nanothread. + + This function is used to implement synchronisation primitives in the EPOC + kernel (e.g. DMutex and DSemaphore) and in personality layers. It is not + intended to be used directly by device drivers. + + If the nanothread is waiting on a fast semaphore, waiting for a DFC, or is + blocked in a call to NKern::Block, it is awakened and put back on the ready + list. Otherwise, the thread state is unchanged. In particular, nothing + happens if the nanothread has been explicitly suspended. + + @param aThread Thread to release. + @param aReturnValue Value returned by NKern::Block if the thread was blocked. + + @see NKern::Block() + + @pre Interrupts must be enabled. + @pre Do not call from an ISR + */ +EXPORT_C void NKern::ThreadRelease(NThread* aThread, TInt aReturnValue) + { + CHECK_PRECONDITIONS(MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR,"NKern::ThreadRelease(NThread*, TInt)"); + NKern::Lock(); + aThread->Release(aReturnValue,0); + NKern::Unlock(); + } + + +/** Atomically awakens a nanothread and signals a fast mutex. + + This function is used to implement synchronisation primitives in the EPOC + kernel (e.g. DMutex and DSemaphore) and in personality layers. It is not + intended to be used directly by device drivers. + + @param aThread Thread to release. + @param aReturnValue Value returned by NKern::Block if the thread was blocked. + @param aMutex Fast mutex to signal. If NULL, the system lock is signalled. + + @see NKern::ThreadRelease(NThread*, TInt) + @see NKern::Block() + + @pre Call in a thread context. + @pre Interrupts must be enabled. + @pre Kernel must be unlocked. + @pre Specified mutex must be held + */ +EXPORT_C void NKern::ThreadRelease(NThread* aThread, TInt aReturnValue, NFastMutex* aMutex) + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::ThreadRelease(NThread*,TInt,NFastMutex*)"); + if (!aMutex) + aMutex=&TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NRel ret %d + FM %M",aThread,aReturnValue,aMutex)); + NKern::Lock(); + aThread->Release(aReturnValue,0); + aMutex->Signal(); + NKern::Unlock(); + } + + +/** Changes the priority of a thread. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadSetPriority(). + + @param aThread Thread to receive the new priority. + @param aPriority New priority for aThread. + + @see Kern::SetThreadPriority() +*/ +EXPORT_C void NKern::ThreadSetPriority(NThread* aThread, TInt aPriority) + { + NKern::Lock(); + aThread->SetPriority(aPriority); + NKern::Unlock(); + } + + +/** Changes the priority of a thread and signals a mutex. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadSetPriority(). + + @param aThread Thread to receive the new priority. + @param aPriority New priority for aThread. + @param aMutex Mutex to be signalled. If NULL, the scheduler's mutex will be signalled. + + @see Kern::SetThreadPriority() +*/ +EXPORT_C void NKern::ThreadSetPriority(NThread* aThread, TInt aPriority, NFastMutex* aMutex) + { + if (!aMutex) + aMutex=&TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NSPri->%d + FM %M",aThread,aPriority,aMutex)); + NKern::Lock(); + aThread->SetPriority(aPriority); + aMutex->Signal(); + NKern::Unlock(); + } + + +/** Atomically signals the request semaphore of a nanothread and a fast mutex. + + This function is intended to be used by the EPOC layer and personality + layers. Device drivers should use Kern::RequestComplete instead. + + @param aThread Nanothread to signal. Must be non NULL. + @param aMutex Fast mutex to signal. If NULL, the system lock is signaled. + + @see Kern::RequestComplete() + + @pre Call in a thread context. + @pre Interrupts must be enabled. + @pre Kernel must be unlocked. + @pre Specified mutex must be held + */ +EXPORT_C void NKern::ThreadRequestSignal(NThread* aThread, NFastMutex* aMutex) + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::ThreadRequestSignal(NThread*,NFastMutex*)"); + if (!aMutex) + aMutex = &TheScheduler.iLock; + NKern::Lock(); + aThread->iRequestSemaphore.Signal(); + aMutex->Signal(); + NKern::Unlock(); + } + + +/** Kills a nanothread. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadKill(). + + This function does not return if the current thread is killed. + This function is asynchronous (i.e. the thread to kill may still be alive when the call returns). + + @param aThread Thread to kill. Must be non NULL. + + @pre If acting on calling thread, calling thread must not be in a + critical section + @pre Thread must not already be exiting. + + @see Kern::ThreadKill() + */ +EXPORT_C void NKern::ThreadKill(NThread* aThread) + { + NKern::Lock(); + aThread->Kill(); + NKern::Unlock(); + } + + +/** Atomically kills a nanothread and signals a fast mutex. + + This function is intended to be used by the EPOC layer and personality layers. + Do not use this function directly on a Symbian OS thread - use Kern::ThreadKill(). + + @param aThread Thread to kill. Must be non NULL. + @param aMutex Fast mutex to signal. If NULL, the system lock is signalled. + + @pre If acting on calling thread, calling thread must not be in a + critical section + @pre Thread must not already be exiting. + + @see NKern::ThreadKill(NThread*) + */ +EXPORT_C void NKern::ThreadKill(NThread* aThread, NFastMutex* aMutex) + { + if (!aMutex) + aMutex = &TheScheduler.iLock; + NThreadBase* pC = NKern::LockC(); + if (aThread==pC) + { + __NK_ASSERT_DEBUG(pC->iCsCount==0); // Make sure thread isn't in critical section + __NK_ASSERT_ALWAYS(aMutex->HeldByCurrentThread()); + pC->AcqSLock(); + aThread->iCsFunction = NThreadBase::ECSExitPending; + pC->RelSLock(); + aMutex->iHoldingThread = (NThreadBase*)(TLinAddr(aThread) | 1); + aMutex->Signal(); // this will make us exit + FAULT(); // should never get here + } + else + { + aThread->Kill(); + aMutex->Signal(); + } + NKern::Unlock(); + } + + +/** Enters thread critical section. + + This function can safely be used in device drivers. + + The current thread will enter its critical section. While in critical section + the thread cannot be suspended or killed. Any suspension or kill will be deferred + until the thread leaves the critical section. + Some API explicitly require threads to be in critical section before calling that + API. + Only User threads need to call this function as the concept of thread critical + section applies to User threads only. + + @pre Call in a thread context. + @pre Kernel must be unlocked. +*/ +EXPORT_C void NKern::ThreadEnterCS() + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::ThreadEnterCS"); + NThreadBase* pC = NKern::CurrentThread(); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NEntCS",pC)); + __NK_ASSERT_DEBUG(pC->iCsCount>=0); + ++pC->iCsCount; + } + +NThread* NKern::_ThreadEnterCS() + { + NThreadBase* pC = NKern::CurrentThread(); + __NK_ASSERT_DEBUG(pC->iCsCount>=0); + ++pC->iCsCount; + return (NThread*)pC; + } + + +/** Leaves thread critical section. + + This function can safely be used in device drivers. + + The current thread will leave its critical section. If the thread was suspended/killed + while in critical section, the thread will be suspended/killed after leaving the + critical section by calling this function. + Only User threads need to call this function as the concept of thread critical + section applies to User threads only. + + @pre Call in a thread context. + @pre Kernel must be unlocked. +*/ +EXPORT_C void NKern::ThreadLeaveCS() + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::ThreadLeaveCS"); + NThreadBase* pC = NKern::LockC(); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NLvCS",pC)); + pC->AcqSLock(); + __NK_ASSERT_DEBUG(pC->iCsCount>0); + if (--pC->iCsCount==0 && pC->iCsFunction!=0) + { + NFastMutex* m = HeldFastMutex(); + if (m) + m->iHoldingThread = (NThreadBase*)(TLinAddr(pC) | 1); + else + pC->DoCsFunctionT(); + } + pC->RelSLock(); + NKern::Unlock(); + } + +void NKern::_ThreadLeaveCS() + { + NThreadBase* pC = NKern::LockC(); + pC->AcqSLock(); + __NK_ASSERT_DEBUG(pC->iCsCount>0); + if (--pC->iCsCount==0 && pC->iCsFunction!=0) + { + NFastMutex* m = HeldFastMutex(); + if (m) + m->iHoldingThread = (NThreadBase*)(TLinAddr(pC) | 1); + else + pC->DoCsFunctionT(); + } + pC->RelSLock(); + NKern::Unlock(); + } + +/** Freeze the CPU of the current thread + + After this the current thread will not migrate to another processor + + @return A cookie to be passed to NKern::EndFreezeCpu() to allow nesting +*/ +EXPORT_C TInt NKern::FreezeCpu() + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::FreezeCpu"); + NKern::Lock(); + TSubScheduler& ss = SubScheduler(); + NThreadBase* pC = ss.iCurrentThread; + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NFrzCpu",pC)); + if (pC->iFreezeCpu) + { + NKern::Unlock(); + return 1; + } + pC->iFreezeCpu = 1; + if (pC->iParent != pC) + { + pC->AcqSLock(); + ++pC->iParent->iFreezeCpu; + pC->RelSLock(); + } + NKern::Unlock(); + return 0; + } + + +/** Unfreeze the current thread's CPU + + After this the current thread will again be eligible to migrate to another processor + + @param aCookie the value returned by NKern::FreezeCpu() +*/ +EXPORT_C void NKern::EndFreezeCpu(TInt aCookie) + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::EndFreezeCpu"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("%T NEndFrz %d",NKern::CurrentThread(),aCookie)); + if (aCookie) + return; + NKern::Lock(); + TSubScheduler& ss = SubScheduler(); + NThreadBase* pC = ss.iCurrentThread; + if (pC->iFreezeCpu) + { + pC->iFreezeCpu = 0; + mb(); + if (pC->iParent != pC) + { + pC->AcqSLock(); + if (!--pC->iParent->iFreezeCpu && pC->iParent->iCpuChange) + RescheduleNeeded(); + pC->RelSLock(); + } + else if (pC->iCpuChange) // deferred CPU change? + RescheduleNeeded(); + } + NKern::Unlock(); + } + + +/** Change the CPU affinity of a thread + + @pre Call in a thread context. + + @param The new CPU affinity mask + @return The old affinity mask + */ +EXPORT_C TUint32 NKern::ThreadSetCpuAffinity(NThread* aThread, TUint32 aAffinity) + { + NKern::Lock(); + TUint32 r = aThread->SetCpuAffinity(aAffinity); + NKern::Unlock(); + return r; + } + + +/** Modify a thread's timeslice + + @pre Call in a thread context. + + @param aTimeslice The new timeslice value + */ +EXPORT_C void NKern::ThreadSetTimeslice(NThread* aThread, TInt aTimeslice) + { + NKern::Lock(); + aThread->AcqSLock(); + if (aThread->iTimeslice == aThread->iTime || aTimeslice<0) + aThread->iTime = aTimeslice; + aThread->iTimeslice = aTimeslice; + aThread->RelSLock(); + NKern::Unlock(); + } + + +/** Blocks current nanothread. + + This function is used to implement synchronisation primitives in the EPOC + layer and in personality layers. It is not intended to be used directly by + device drivers. + + @param aTimeout If greater than 0, the nanothread will be blocked for at most + aTimeout microseconds. + @param aMode Bitmask whose possible values are documented in TBlockMode. + @param aMutex Fast mutex to operate on. If NULL, the system lock is used. + + @see NKern::ThreadRelease() + @see TBlockMode + + @pre Call in a thread context. + @pre Interrupts must be enabled. + @pre Kernel must be unlocked. + @pre Specified mutex must be held + */ +EXPORT_C TInt NKern::Block(TUint32 aTimeout, TUint aMode, NFastMutex* aMutex) + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::Block(TUint32,TUint,NFastMutex*)"); + if (!aMutex) + aMutex = &TheScheduler.iLock; + __KTRACE_OPT(KNKERN,DEBUGPRINT("NKern::Block time %d mode %d FM %M",aTimeout,aMode,aMutex)); + if (aMode & EEnterCS) + NKern::_ThreadEnterCS(); // NOTE: MUST DO THIS BEFORE CALLING NKern::Lock() + NThreadBase* pC = NKern::LockC(); + TUint flags = (aMode & NKern::EObstruct) ? NThreadWaitState::EWtStObstructed : 0; + pC->iWaitState.SetUpWait(NThreadBase::EWaitBlocked, flags, 0, aTimeout); + if (aMode & ERelease) + aMutex->Signal(); + RescheduleNeeded(); + NKern::Unlock(); // thread blocks here + TInt r = pC->iWaitState.iWtC.iRetVal; // sample here since it will be overwritten if we block on the fast mutex + if (aMode & EClaim) + FMWait(aMutex); + return r; + } + + +/** +@pre Call in a thread context. +@pre Interrupts must be enabled. +@pre Kernel must be unlocked. +@pre No fast mutex can be held +*/ +/** @see NKern::Block(TUint32, TUint, NFastMutex*) */ +EXPORT_C TInt NKern::Block(TUint32 aTimeout, TUint aMode) + { + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD,"NKern::Block(TUint32,TUint)"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NKern::Block time %d mode %d",aTimeout,aMode)); + if (aMode & EEnterCS) + NKern::_ThreadEnterCS(); // NOTE: MUST DO THIS BEFORE CALLING NKern::Lock() + NThreadBase* pC = NKern::LockC(); + TUint flags = (aMode & NKern::EObstruct) ? NThreadWaitState::EWtStObstructed : 0; + pC->iWaitState.SetUpWait(NThreadBase::EWaitBlocked, flags, 0, aTimeout); + RescheduleNeeded(); + NKern::Unlock(); // thread blocks here + return pC->iWaitState.iWtC.iRetVal; + } + + + + +/** +Places the current nanothread into a wait state on an externally +defined wait object. + +For use by RTOS personality layers. +Do not use this function directly on a Symbian OS thread. + +Since the kernel is locked on entry, any reschedule will be deferred until +it is unlocked. The thread should be added to any necessary wait queue after +a call to this function, since this function removes it from the ready list. +The thread's wait timer is started if aTimeout is nonzero. +The thread's NState and wait object are updated. + +Call NThreadBase::Release() when the wait condition is resolved. + +@param aTimeout The maximum time for which the thread should block, in nanokernel timer ticks. + A zero value means wait forever. + If the thread is still blocked when the timeout expires, + then the timeout state handler will be called. +@param aState The nanokernel thread state (N-State) value to be set. + This state corresponds to the externally defined wait object. + This value will be written into the member NThreadBase::iNState. +@param aWaitObj A pointer to an externally defined wait object. + This value will be written into the member NThreadBase::iWaitObj. + +@pre Kernel must be locked. +@pre Call in a thread context. + +@post Kernel is locked. + +@see NThreadBase::Release() +*/ +EXPORT_C void NKern::NanoBlock(TUint32 aTimeout, TUint aState, TAny* aWaitObj) + { + CHECK_PRECONDITIONS(MASK_KERNEL_LOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::NanoBlock"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NanoBlock time %d state %d obj %08x", aTimeout, aState, aWaitObj)); + NThreadBase* pC = NCurrentThreadL(); + pC->iWaitState.SetUpWait(aState, aState>>8, aWaitObj, aTimeout); + RescheduleNeeded(); + } + + + + +EXPORT_C void NKern::Sleep(TUint32 aTime) +/** +Puts the current nanothread to sleep for the specified duration. + +It can be called from Symbian OS threads. + +@param aTime sleep time in nanokernel timer ticks. + +@pre No fast mutex can be held. +@pre Kernel must be unlocked. +@pre Call in a thread context. +@pre Interrupts must be enabled. +*/ + { + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD,"NKern::Sleep"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NSlp %d",aTime)); + NThreadBase* pC = NKern::LockC(); + pC->iWaitState.SetUpWait(NThreadBase::EWaitSleep, 0, 0, aTime); + RescheduleNeeded(); + NKern::Unlock(); + } + + +/** Terminates the current nanothread. + + Calls to this function never return. + + For use by RTOS personality layers. + Do not use this function directly on a Symbian OS thread. + + @pre Call in a thread context. + @pre Interrupts must be enabled. + @pre Kernel must be unlocked. + */ +EXPORT_C void NKern::Exit() + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::Exit"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NExit")); + NKern::LockC()->Exit(); // this won't return + FAULT(); + } + + +/** Terminates the current nanothread at the next possible point. + + If the calling thread is not currently in a critical section and does not + currently hold a fast mutex, it exits immediately and this function does + not return. On the other hand if the thread is in a critical section or + holds a fast mutex the thread continues executing but it will exit as soon + as it leaves the critical section and/or releases the fast mutex. + + @pre Call in a thread context. + @pre Interrupts must be enabled. + @pre Kernel must be unlocked. + */ +EXPORT_C void NKern::DeferredExit() + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::DeferredExit"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NDefExit")); + NFastMutex* m = HeldFastMutex(); + NThreadBase* pC = NKern::LockC(); + if (!m && !pC->iCsCount) + pC->Exit(); // this won't return + pC->AcqSLock(); + if (pC->iCsFunction >= 0) // don't touch it if we are already exiting + pC->iCsFunction = NThreadBase::ECSExitPending; + pC->RelSLock(); + if (m && !pC->iCsCount) + m->iHoldingThread = (NThreadBase*)(TLinAddr(pC) | 1); + NKern::Unlock(); + } + + +/** Prematurely terminates the current thread's timeslice + + @pre Kernel must be unlocked. + @pre Call in a thread context. + + @post Kernel is unlocked. + */ +EXPORT_C void NKern::YieldTimeslice() + { + CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::YieldTimeslice"); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NKern::YieldTimeslice")); + NThreadBase* t = NKern::LockC(); + t->iTime = 0; + mb(); + if (t->iNext!=t || t->iParent->iNext!=t->iParent) + RescheduleNeeded(); + NKern::Unlock(); + } + + +/** Returns the number of CPUs available to Symbian OS + + @return the number of CPUs + + @pre Call in any context. +*/ +EXPORT_C TInt NKern::NumberOfCpus() + { + return TheScheduler.iNumCpus; + } + + +/** Rotates the specified CPU ready list for threads at the specified priority. + + For use by RTOS personality layers to allow external control of round-robin + scheduling. Not intended for direct use by device drivers. + + @param aPriority = priority at which threads should be rotated. + -1 means use calling thread's priority. + @param aCpu CPU to act on + + @pre Kernel must be unlocked. + @pre Call in a thread context. + + @post Kernel is unlocked. + */ + +EXPORT_C void NKern::RotateReadyList(TInt aPriority, TInt aCpu) + { +// CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::RotateReadyList"); +// __KTRACE_OPT(KNKERN,DEBUGPRINT("NKern::RotateReadyList %d",aPriority)); +// if (aPriority<0 || aPriority>=KNumPriorities) +// aPriority=NKern::CurrentThread()->iPriority; +// NKern::Lock(); +// TheScheduler.RotateReadyList(aPriority); +// NKern::Unlock(); + } + + +/** Rotates the ready list for threads at the specified priority. + + For use by RTOS personality layers to allow external control of round-robin + scheduling. Not intended for direct use by device drivers. + + @param aPriority = priority at which threads should be rotated. + -1 means use calling thread's priority. + + @pre Kernel must be unlocked. + @pre Call in a thread context. + + @post Kernel is unlocked. + */ +EXPORT_C void NKern::RotateReadyList(TInt aPriority) + { + RotateReadyList(aPriority, -1); + } + + +/** Returns a pointer to the thread group to which the current thread belongs, + if any. Returns NULL if current thread is a standalone thread. + + @pre Call in a thread context. + */ +EXPORT_C NThreadGroup* NKern::CurrentGroup() + { + NThreadBase* pC = NKern::CurrentThread(); + return (pC->iParent == pC) ? (NThreadGroup*)0 : (NThreadGroup*)pC->iParent; + } + + +/** Detaches the current thread from the group to which it currently belongs, + if any. Returns a pointer to the group (NULL if none). + + @pre Call in a thread context. + @pre Interrupts enabled + @pre Kernel unlocked + */ +EXPORT_C NThreadGroup* NKern::LeaveGroup() + { + CHECK_PRECONDITIONS(MASK_NOT_ISR|MASK_NOT_IDFC|MASK_INTERRUPTS_ENABLED|MASK_KERNEL_UNLOCKED, "NKern::LeaveGroup"); + NKern::Lock(); + TSubScheduler& ss = SubScheduler(); + NThreadBase* pC = ss.iCurrentThread; + pC->iNewParent = 0; // cancel any pending Join + NThreadGroup* g = (pC->iParent == pC) ? (NThreadGroup*)0 : (NThreadGroup*)pC->iParent; + TBool make_group_ready = FALSE; + __KTRACE_OPT(KNKERN,DEBUGPRINT("NLeaveGroup %T (%G)",pC,g)); + if (g) + { + while (!pC->TiedEventLeaveInterlock()) + { + TInt irq = NKern::DisableAllInterrupts(); + ss.QueueDfcs(); + NKern::RestoreInterrupts(irq); + } + pC->AcqSLock(); + ss.iReadyListLock.LockOnly(); + pC->UnReadyT(); + pC->iParent = pC; + g->iCurrent = 0; // since current thread is no longer in g + ss.AddHead(pC); + pC->iReady = TUint8(ss.iCpuNum | NSchedulable::EReadyOffset); + pC->iCpuAffinity = g->iCpuAffinity; // keep same CPU affinity + // if we're frozen, the group's freeze count was incremented + if (pC->iFreezeCpu) + --g->iFreezeCpu; + // if we've been marked as deferring, the group's freeze count was incremented + if (pC->iFastMutexDefer == 1) + { + --g->iFreezeCpu; + pC->iFastMutexDefer = 0; + } + // if the group was waiting to change cpu then this thread needs to change still + if (g->iCpuChange) + { + pC->iCpuChange = g->iCpuChange; + RescheduleNeeded(); + if (!g->iFreezeCpu) + { + // we were the last thread in the group stopping it from moving + // but there may be no other threads left after UnReadyT'ing this one + g->iCpuChange = FALSE; + if (g->iReady) + { + ss.Remove(g); + g->iReady = 0; + make_group_ready = TRUE; + } + } + } + ss.iReadyListLock.UnlockOnly(); + --g->iThreadCount; + if (make_group_ready) + g->ReadyT(0); + g->RelSLock(); // since pC is no longer attached to g + pC->RelSLock(); + } + NKern::Unlock(); + return g; + } + + +/** Adds the current thread to the specified group. + + @param aGroup = pointer to group to join + + @pre Call in a thread context, not in one of the idle threads. + @pre Interrupts enabled + @pre Kernel unlocked + @pre Thread does not hold a fast mutex + @pre Thread does not have a freeze on CPU migration + @pre Current thread is not already in a group + */ +EXPORT_C void NKern::JoinGroup(NThreadGroup* aGroup) + { + CHECK_PRECONDITIONS(MASK_THREAD_STANDARD, "NKern::JoinGroup"); + NKern::Lock(); + TSubScheduler& ss = SubScheduler(); + NThreadBase* pC = ss.iCurrentThread; + __ASSERT_WITH_MESSAGE_DEBUG(pC->iParent==pC, "Thread not already in a group", "NKern::JoinGroup"); + __ASSERT_WITH_MESSAGE_DEBUG(!pC->iFreezeCpu, "No interdiction on CPU migration", "NKern::JoinGroup"); + __ASSERT_WITH_MESSAGE_DEBUG(!pC->i_NThread_Initial, "Not idle thread", "NKern::JoinGroup"); + __NK_ASSERT_ALWAYS(pC->iParent==pC && !pC->iFreezeCpu); + __KTRACE_OPT(KNKERN,DEBUGPRINT("NJoinGroup %T->%G",pC,aGroup)); + pC->AcqSLock(); + aGroup->AcqSLock(); + TBool migrate = !CheckCpuAgainstAffinity(ss.iCpuNum, aGroup->iCpuAffinity); // TRUE if thread's current CPU is incompatible with the group's affinity + if (!aGroup->iReady || aGroup->iReady==pC->iReady) + { + // group not ready or ready on this CPU + if (!migrate) + { + ss.iReadyListLock.LockOnly(); + pC->UnReadyT(); + pC->iParent = aGroup; + aGroup->iNThreadList.AddHead(pC); + if (!aGroup->iReady) + { + aGroup->iPriority = pC->iPriority; + ss.AddHead(aGroup); + aGroup->iReady = TUint8(ss.iCpuNum | NSchedulable::EReadyOffset); + } + else if (pC->iPriority > aGroup->iPriority) + { + ss.ChangePriority(aGroup, pC->iPriority); + } + pC->iReady = NSchedulable::EReadyGroup; + aGroup->iCurrent = aGroup->iReady; + ss.iReadyListLock.UnlockOnly(); + ++aGroup->iThreadCount; + goto done; + } + } + // this thread needs to migrate to another CPU + pC->iNewParent = aGroup; + RescheduleNeeded(); + + // the following reschedule definitely joins the group even if the + // thread's CPU affinity is incompatible with that of the group + // (the thread's CPU affinity is subsequently determined by that of + // the group) + +done: + if (pC->iParent != aGroup) + aGroup->RelSLock(); + pC->RelSLock(); + while (!pC->TiedEventJoinInterlock()) + { + TInt irq = NKern::DisableAllInterrupts(); + ss.QueueDfcs(); + NKern::RestoreInterrupts(irq); + } + NKern::Unlock(); + } + + +/****************************************************************************** + * Priority Lists + ******************************************************************************/ + +#ifndef __PRI_LIST_MACHINE_CODED__ +/** Returns the priority of the highest priority item present on a priority list. + + @return The highest priority present or -1 if the list is empty. + */ +EXPORT_C TInt TPriListBase::HighestPriority() + { +// TUint64 present = MAKE_TUINT64(iPresent[1], iPresent[0]); +// return __e32_find_ms1_64(present); + return __e32_find_ms1_64(iPresent64); + } + + +/** Finds the highest priority item present on a priority list. + + If multiple items at the same priority are present, return the first to be + added in chronological order. + + @return A pointer to the item or NULL if the list is empty. + */ +EXPORT_C TPriListLink* TPriListBase::First() + { + TInt p = HighestPriority(); + return p >=0 ? static_cast(iQueue[p]) : NULL; + } + + +/** Adds an item to a priority list at the tail of the queue for its priority. + + @param aLink A pointer to the item - must not be NULL. + */ +EXPORT_C void TPriListBase::Add(TPriListLink* aLink) + { + TInt p = aLink->iPriority; + SDblQueLink* head = iQueue[p]; + if (head) + { + // already some at this priority + aLink->InsertBefore(head); + } + else + { + // 'create' new list + iQueue[p] = aLink; + aLink->iNext = aLink->iPrev = aLink; + iPresent[p>>5] |= 1u << (p & 0x1f); + } + } + + +/** Removes an item from a priority list. + + @param aLink A pointer to the item - must not be NULL. + */ +EXPORT_C void TPriListBase::Remove(TPriListLink* aLink) + { + if (!aLink->Alone()) + { + // not the last on this list + TInt p = aLink->iPriority; + if (iQueue[p] == aLink) + iQueue[p] = aLink->iNext; + aLink->Deque(); + } + else + { + TInt p = aLink->iPriority; + iQueue[p] = 0; + iPresent[p>>5] &= ~(1u << (p & 0x1f)); + KILL_LINK(aLink); + } + } + + +/** Changes the priority of an item on a priority list. + + @param aLink A pointer to the item to act on - must not be NULL. + @param aNewPriority A new priority for the item. + */ +EXPORT_C void TPriListBase::ChangePriority(TPriListLink* aLink, TInt aNewPriority) + { + if (aLink->iPriority!=aNewPriority) + { + Remove(aLink); + aLink->iPriority=TUint8(aNewPriority); + Add(aLink); + } + } +#endif + +/** Adds an item to a priority list at the head of the queue for its priority. + + @param aLink A pointer to the item - must not be NULL. + */ +EXPORT_C void TPriListBase::AddHead(TPriListLink* aLink) + { + TInt p = aLink->iPriority; + SDblQueLink* head = iQueue[p]; + iQueue[p] = aLink; + if (head) + { + // already some at this priority + aLink->InsertBefore(head); + } + else + { + // 'create' new list + aLink->iNext = aLink->iPrev = aLink; + iPresent[p>>5] |= 1u << (p & 0x1f); + } + } + + +/****************************************************************************** + * Generic IPIs + ******************************************************************************/ + +TGenIPIList::TGenIPIList() + : iGenIPILock(TSpinLock::EOrderGenericIPIList) + { + } + +TGenIPIList GenIPIList; + +extern "C" { +extern void send_generic_ipis(TUint32); + +void generic_ipi_isr(TSubScheduler* aS) + { + TGenericIPI* ipi = aS->iNextIPI; + if (!ipi) + return; + TUint32 m = aS->iCpuMask; + SDblQueLink* anchor = &GenIPIList.iA; + while (ipi != anchor) + { + __e32_atomic_and_acq32(&ipi->iCpusIn, ~m); + (*ipi->iFunc)(ipi); + TInt irq = GenIPIList.iGenIPILock.LockIrqSave(); + TGenericIPI* n = (TGenericIPI*)ipi->iNext; + ipi->iCpusOut &= ~m; + if (ipi->iCpusOut == 0) + { + ipi->Deque(); + mb(); + ipi->iNext = 0; + } + ipi = n; + while (ipi!=anchor && !(ipi->iCpusIn & m)) + ipi = (TGenericIPI*)ipi->iNext; + if (ipi == anchor) + aS->iNextIPI = 0; + GenIPIList.iGenIPILock.UnlockIrqRestore(irq); + } + } +} + +void TGenericIPI::Queue(TGenericIPIFn aFunc, TUint32 aCpuMask) + { + __KTRACE_OPT(KSCHED2,DEBUGPRINT("GenIPI F=%08x M=%08x", aFunc, aCpuMask)); + iFunc = aFunc; + TScheduler& s = TheScheduler; + TInt i; + TUint32 ipis = 0; + TInt irq = GenIPIList.iGenIPILock.LockIrqSave(); + if (aCpuMask & 0x80000000u) + { + if (aCpuMask==0xffffffffu) + aCpuMask = s.iActiveCpus2; + else if (aCpuMask==0xfffffffeu) + aCpuMask = s.iActiveCpus2 &~ SubScheduler().iCpuMask; + else + aCpuMask = 0; + } + iCpusIn = aCpuMask; + iCpusOut = aCpuMask; + if (!aCpuMask) + { + GenIPIList.iGenIPILock.UnlockIrqRestore(irq); + iNext = 0; + return; + } + GenIPIList.Add(this); + for (i=0; iiFlag) + { + __chill(); + } + } + +