// 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\nkerns.cpp
// 
//

// NThreadBase member data
#define __INCLUDE_NTHREADBASE_DEFINES__

#include <e32cmn.h>
#include <e32cmn_private.h>
#include "nk_priv.h"

extern "C" void ExcFault(TAny*);

/******************************************************************************
 * Fast mutex
 ******************************************************************************/
/** Create a fast mutex

	@publishedPartner
	@released
*/
EXPORT_C NFastMutex::NFastMutex()
	: iHoldingThread(0), iMutexLock(TSpinLock::EOrderFastMutex)
	{
	}

/******************************************************************************
 * NSchedulable
 ******************************************************************************/
NSchedulable::NSchedulable()
	:	iSSpinLock(TSpinLock::EOrderThread)
	{
	iPriority = 0;
	iReady = 0;
	iCurrent = 0;
	iLastCpu = 0;
	iNSchedulableSpare1 = 0;
	iPauseCount = 0;
	iSuspended = 0;
	iNSchedulableSpare2 = 0;
	iCpuChange = 0;
	iStopping = 0;
	iFreezeCpu = 0;
	iParent = (NSchedulable*)0xdeadbeef;
	iCpuAffinity = 0;
	new (i_IDfcMem) TDfc(&DeferredReadyIDfcFn, this);
	iEventState = 0;
	iTotalCpuTime64 = 0;
	}

/******************************************************************************
 * NThreadGroup
 ******************************************************************************/
NThreadGroup::NThreadGroup()
	{
	iParent = 0;
	iThreadCount = 0;
	new (&iSSpinLock) TSpinLock(TSpinLock::EOrderThreadGroup);
	}

/** Create a thread group

	@publishedPartner
	@prototype
*/
EXPORT_C TInt NKern::GroupCreate(NThreadGroup* aGroup, SNThreadGroupCreateInfo& aInfo)
	{
	new (aGroup) NThreadGroup();
	aGroup->iCpuAffinity = aInfo.iCpuAffinity;
	return KErrNone;
	}


/** Destroy a thread group

	@pre Call in thread context, interrupts enabled, preemption enabled
	@pre No fast mutex held
	@pre Calling thread in critical section
	@pre All threads have left the group

	@publishedPartner
	@prototype
*/
EXPORT_C void NKern::GroupDestroy(NThreadGroup* aGroup)
	{
	NKern::ThreadEnterCS();
	aGroup->DetachTiedEvents();
	NKern::ThreadLeaveCS();
	}


/******************************************************************************
 * Thread
 ******************************************************************************/
void InvalidExec()
	{
	FAULT();
	}

static const SFastExecTable DefaultFastExecTable={0,{0}};
static const SSlowExecTable DefaultSlowExecTable={0,(TLinAddr)InvalidExec,0,{{0,0}}};

const SNThreadHandlers NThread_Default_Handlers =
	{
	NTHREAD_DEFAULT_EXIT_HANDLER,
	NTHREAD_DEFAULT_STATE_HANDLER,
	NTHREAD_DEFAULT_EXCEPTION_HANDLER,
	NTHREAD_DEFAULT_TIMEOUT_HANDLER
	};

NThreadWaitState::NThreadWaitState()
	:	iTimer(&TimerExpired, this)
	{
	iWtSt64 = 0;
	iTimer.iTriggerTime = 0;
	iTimer.iNTimerSpare1 = 0;
	}

NThreadBase::NThreadBase()
	:	iRequestSemaphore(), iWaitState()
	{
	iParent = this;
	iWaitLink.iPriority = 0;
	iBasePri = 0;
	iMutexPri = 0;
	i_NThread_Initial = 0;
	iLinkedObjType = EWaitNone;
	i_ThrdAttr = 0;
	iNThreadBaseSpare10 = 0;
	iFastMutexDefer = 0;
	iRequestSemaphore.iOwningThread = (NThreadBase*)this;
	iTime = 0;
	iTimeslice = 0;
	iSavedSP = 0;
	iAddressSpace = 0;
	iHeldFastMutex = 0;
	iUserModeCallbacks = 0;
	iLinkedObj = 0;
	iNewParent = 0;
	iFastExecTable = 0;
	iSlowExecTable = 0;
	iCsCount = 0;
	iCsFunction = 0;
	iHandlers = 0;
	iSuspendCount = 0;
	iStackBase = 0;
	iStackSize = 0;
	iExtraContext = 0;
	iExtraContextSize = 0;
	iNThreadBaseSpare6 = 0;
	iNThreadBaseSpare7 = 0;
	iNThreadBaseSpare8 = 0;
	iNThreadBaseSpare9 = 0;

	// KILL
	iTag = 0;
	iVemsData = 0;
	}

TInt NThreadBase::Create(SNThreadCreateInfo& aInfo, TBool aInitial)
	{
	__KTRACE_OPT(KNKERN,DEBUGPRINT(">NThreadBase::Create %08x(%08x,%d)", this, &aInfo, aInitial));
	if (aInfo.iPriority<0 || aInfo.iPriority>63)
		return KErrArgument;
	if (aInfo.iPriority==0 && !aInitial)
		return KErrArgument;
//	if (aInfo.iCpu!=KCpuAny && aInfo.iCpu>=TheScheduler.iNumCpus)
//		return KErrArgument;
	iStackBase=(TLinAddr)aInfo.iStackBase;
	iStackSize=aInfo.iStackSize;
	iTimeslice=(aInfo.iTimeslice>0)?aInfo.iTimeslice:-1;
	iTime=iTimeslice;
	iPriority=TUint8(aInfo.iPriority);
	iBasePri=TUint8(aInfo.iPriority);
	iCpuAffinity = aInfo.iCpuAffinity;
	iHandlers = aInfo.iHandlers ? aInfo.iHandlers : &NThread_Default_Handlers;
	iFastExecTable=aInfo.iFastExecTable?aInfo.iFastExecTable:&DefaultFastExecTable;
	iSlowExecTable=(aInfo.iSlowExecTable?aInfo.iSlowExecTable:&DefaultSlowExecTable)->iEntries;
	i_ThrdAttr=(TUint8)aInfo.iAttributes;
	if (aInitial)
		{
		TSubScheduler& ss = SubScheduler();
		iLastCpu = (TUint8)ss.iCpuNum;
		iReady = (TUint8)(iLastCpu | EReadyOffset);
		iCurrent = iReady;
		iCpuAffinity = iLastCpu;
		iEventState = (iLastCpu<<EEventCpuShift) | (iLastCpu<<EThreadCpuShift);
		ss.Add(this);
		i_NThread_Initial = TRUE;
		ss.iInitialThread = (NThread*)this;
		NKern::Unlock();		// now that current thread is defined
		}
	else
		{
		iSuspendCount = 1;
		iSuspended = 1;
		TInt ecpu;
		if (iCpuAffinity & NTHREADBASE_CPU_AFFINITY_MASK)
			{
			ecpu = __e32_find_ls1_32(iCpuAffinity);
			if (ecpu >= TheScheduler.iNumCpus)
				ecpu = 0;	// FIXME: Inactive CPU?
			}
		else
			ecpu = iCpuAffinity;
		iEventState = (ecpu<<EEventCpuShift) | (ecpu<<EThreadCpuShift);
		if (aInfo.iGroup)
			{
			NKern::Lock();
			AcqSLock();
			aInfo.iGroup->AcqSLock();
			iParent = (NSchedulable*)aInfo.iGroup;
			++aInfo.iGroup->iThreadCount;
			iEventState |= EEventParent;
			RelSLock();
			NKern::Unlock();
			}
		}
	__KTRACE_OPT(KNKERN,DEBUGPRINT("<NThreadBase::Create OK"));
	return KErrNone;
	}

void NThread_Default_State_Handler(NThread* __DEBUG_ONLY(aThread), TInt __DEBUG_ONLY(aOperation), TInt __DEBUG_ONLY(aParameter))
	{
//	__KTRACE_OPT(KPANIC,DEBUGPRINT("Unknown NState %d: thread %T op %08x par %08x",aThread,aThread->iNState,aOperation,aParameter));
#ifdef _DEBUG
	DEBUGPRINT("UnknownState: thread %T op %08x par %08x",aThread,aOperation,aParameter);
#endif
	FAULT();
	}

void NThread_Default_Exception_Handler(TAny* aContext, NThread*)
	{
	ExcFault(aContext);
	}


/** Create a nanothread.

	This function is intended to be used by the EPOC kernel and by personality
	layers. A nanothread may not use most of the functions available to normal
	Symbian OS threads. Use Kern::ThreadCreate() to create a Symbian OS thread.

	@param aThread Pointer to control block for thread to create.
	@param aInfo Information needed for creating the thread.

	@see SNThreadCreateInfo
	@see Kern::ThreadCreate

	@pre	Call in a thread context.
	@pre	Interrupts must be enabled.
	@pre	Kernel must be unlocked.
 */
EXPORT_C TInt NKern::ThreadCreate(NThread* aThread, SNThreadCreateInfo& aInfo)
	{
	CHECK_PRECONDITIONS(MASK_KERNEL_UNLOCKED|MASK_INTERRUPTS_ENABLED|MASK_NOT_ISR|MASK_NOT_IDFC,"NKern::ThreadCreate");
	return aThread->Create(aInfo,FALSE);
	}

// User-mode callbacks

TUserModeCallback::TUserModeCallback(TUserModeCallbackFunc aFunc)
	:	iNext(KUserModeCallbackUnqueued),
		iFunc(aFunc)
	{
	}

TUserModeCallback::~TUserModeCallback()
	{
	__NK_ASSERT_DEBUG(iNext == KUserModeCallbackUnqueued);
	}

void NKern::CancelUserModeCallbacks()
	{
	// Call any queued callbacks with the EUserModeCallbackCancel reason code, in the current
	// thread.

	TUserModeCallback* listHead =
		(TUserModeCallback*)__e32_atomic_swp_ord_ptr(&NCurrentThread()->iUserModeCallbacks, NULL);
	while (listHead)
		{
		TUserModeCallback* callback = listHead;
		listHead = listHead->iNext;
		callback->iNext = KUserModeCallbackUnqueued;
		__e32_memory_barrier();
		callback->iFunc(callback, EUserModeCallbackCancel);
		}
	}

void NKern::MoveUserModeCallbacks(NThreadBase* aDestThread, NThreadBase* aSrcThread)
	{
	// Move all queued user-mode callbacks from the source thread to the destination thread, and
	// prevent any more from being queued.  Used by the kernel thread code so that callbacks get
	// cancelled in another thread if the thread they were originally queued on dies.

	// Atomically remove list of callbacks and set pointer to 1
	// The latter ensures any subsequent attempts to add callbacks fail
	TUserModeCallback* sourceListStart =
		(TUserModeCallback*)__e32_atomic_swp_ord_ptr(&aSrcThread->iUserModeCallbacks, (TAny*)1);
	__NK_ASSERT_DEBUG(((TUint)sourceListStart & 3) == 0);  // check this only gets called once per thread

	if (sourceListStart == NULL)
		return;
	
	TUserModeCallback* sourceListEnd = sourceListStart;
	while (sourceListEnd->iNext != NULL)
		sourceListEnd = sourceListEnd->iNext;
	
	NKern::Lock();
	TUserModeCallback* destListStart = aDestThread->iUserModeCallbacks;
	do
		{
		__NK_ASSERT_DEBUG(((TUint)destListStart & 3) == 0);  // dest thread must not die
		sourceListEnd->iNext = destListStart;
		} while (!__e32_atomic_cas_ord_ptr(&aDestThread->iUserModeCallbacks, &destListStart, sourceListStart));
	NKern::Unlock();
	}

/** Initialise the null thread
	@internalComponent
*/
void NKern::Init(NThread* aThread, SNThreadCreateInfo& aInfo)
	{
	aInfo.iFunction=NULL;			// irrelevant
	aInfo.iPriority=0;				// null thread has lowest priority
	aInfo.iTimeslice=0;				// null thread not timesliced
	aInfo.iAttributes=0;			// null thread does not require implicit locks
	aThread->Create(aInfo,TRUE);	// create the null thread
	}

/** @internalTechnology */
EXPORT_C void NKern::RecordIntLatency(TInt /*aLatency*/, TInt /*aIntMask*/)
	{
	}


/** @internalTechnology */
EXPORT_C void NKern::RecordThreadLatency(TInt /*aLatency*/)
	{
	}

/********************************************
 * Deterministic Priority List Implementation
 ********************************************/


/** Construct a priority list with the specified number of priorities

	@param aNumPriorities The number of priorities (must be 1-64).
 */
EXPORT_C TPriListBase::TPriListBase(TInt aNumPriorities)
	{
	memclr(this, sizeof(TPriListBase)+(aNumPriorities-1)*sizeof(SDblQueLink*) );
	}


/********************************************
 * Miscellaneous
 ********************************************/

/** Get the current value of the high performance counter.

    If a high performance counter is not available, this uses the millisecond
    tick count instead.
*/
EXPORT_C TUint32 NKern::FastCounter()
	{
	return (TUint32)Timestamp();
	}


/** Get the frequency of counter queried by NKern::FastCounter().
*/
EXPORT_C TInt NKern::FastCounterFrequency()
	{
	return (TInt)TimestampFrequency();
	}


extern "C" {
TUint32 CrashState;
}

EXPORT_C TBool NKern::Crashed()
	{
	return CrashState!=0;
	}


/**	Returns number of nanokernel timer ticks since system started.
	@return tick count
	@pre any context
 */
EXPORT_C TUint32 NKern::TickCount()
	{
	return NTickCount();
	}


TUint32 BTrace::BigTraceId = 0;

TBool BTrace::DoOutBig(TUint32 a0, TUint32 a1, const TAny* aData, TInt aDataSize, TUint32 aContext, TUint32 aPc)
	{
	SBTraceData& traceData = BTraceData;

	// see if trace is small enough to fit in single record...
	if(TUint(aDataSize)<=TUint(KMaxBTraceDataArray+4))
		{
		a0 += aDataSize;
		TUint32 a2 = 0;
		TUint32 a3 = 0;
		if(aDataSize)
			{
			a2 = *((TUint32*&)aData)++; // first 4 bytes into a2
			if(aDataSize>=4 && aDataSize<=8)
				a3 = *(TUint32*)aData; // only 4 more bytes, so pass by value, not pointer
			else
				a3 = (TUint32)aData;
			}
		__ACQUIRE_BTRACE_LOCK();
		TBool r = traceData.iHandler(a0,0,aContext,a1,a2,a3,0,aPc);
		__RELEASE_BTRACE_LOCK();
		return r;
		}

	// adjust for header2, extra, and size word...
	a0 |= BTrace::EHeader2Present<<(BTrace::EFlagsIndex*8)|BTrace::EExtraPresent<<(BTrace::EFlagsIndex*8);
	a0 += 12;

	TUint32 traceId = __e32_atomic_add_ord32(&BigTraceId, 1);
	TUint32 header2 = BTrace::EMultipartFirst;
	TInt offset = 0;
	do
		{
		TUint32 size = aDataSize-offset;
		if(size>KMaxBTraceDataArray)
			size = KMaxBTraceDataArray;
		else
			header2 = BTrace::EMultipartLast;
		if(size<=4)
			*(TUint32*)&aData = *(TUint32*)aData; // 4 bytes or less are passed by value, not pointer

		__ACQUIRE_BTRACE_LOCK();
		TBool result = traceData.iHandler(a0+size,header2,aContext,aDataSize,a1,(TUint32)aData,traceId,aPc);
		__RELEASE_BTRACE_LOCK();
		if (!result)
			return result;

		offset += size;
		*(TUint8**)&aData += size;

		header2 = BTrace::EMultipartMiddle;
		a1 = offset;
		}
	while(offset<aDataSize);

	return TRUE;
	}

EXPORT_C TSpinLock* BTrace::LockPtr()
	{
#ifdef __USE_BTRACE_LOCK__
	return &BTraceLock;
#else
	return 0;
#endif
	}
