andre@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
andre@0: /* This Source Code Form is subject to the terms of the Mozilla Public
andre@0:  * License, v. 2.0. If a copy of the MPL was not distributed with this
andre@0:  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
andre@0: 
andre@0: #include "primpl.h"
andre@0: 
andre@0: _PRCPU *_pr_primordialCPU = NULL;
andre@0: 
andre@0: PRInt32 _pr_md_idle_cpus;       /* number of idle cpus */
andre@0: /*
andre@0:  * The idle threads in MxN models increment/decrement _pr_md_idle_cpus.
andre@0:  * If _PR_HAVE_ATOMIC_OPS is not defined, they can't use the atomic
andre@0:  * increment/decrement routines (which are based on PR_Lock/PR_Unlock),
andre@0:  * because PR_Lock asserts that the calling thread is not an idle thread.
andre@0:  * So we use a _MDLock to protect _pr_md_idle_cpus.
andre@0:  */
andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
andre@0: #ifndef _PR_HAVE_ATOMIC_OPS
andre@0: static _MDLock _pr_md_idle_cpus_lock;
andre@0: #endif
andre@0: #endif
andre@0: PRUintn _pr_numCPU;
andre@0: PRInt32 _pr_cpus_exit;
andre@0: PRUint32 _pr_cpu_affinity_mask = 0;
andre@0: 
andre@0: #if !defined (_PR_GLOBAL_THREADS_ONLY)
andre@0: 
andre@0: static PRUintn _pr_cpuID;
andre@0: 
andre@0: static void PR_CALLBACK _PR_CPU_Idle(void *);
andre@0: 
andre@0: static _PRCPU *_PR_CreateCPU(void);
andre@0: static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread);
andre@0: 
andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY)
andre@0: static void _PR_RunCPU(void *arg);
andre@0: #endif
andre@0: 
andre@0: void  _PR_InitCPUs()
andre@0: {
andre@0:     PRThread *me = _PR_MD_CURRENT_THREAD();
andre@0: 
andre@0:     if (_native_threads_only)
andre@0:         return;
andre@0: 
andre@0:     _pr_cpuID = 0;
andre@0:     _MD_NEW_LOCK( &_pr_cpuLock);
andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
andre@0: #ifndef _PR_HAVE_ATOMIC_OPS
andre@0:     _MD_NEW_LOCK(&_pr_md_idle_cpus_lock);
andre@0: #endif
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_LOCAL_THREADS_ONLY
andre@0: 
andre@0: #ifdef HAVE_CUSTOM_USER_THREADS
andre@0:     _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
andre@0: #endif
andre@0: 
andre@0:     /* Now start the first CPU. */
andre@0:     _pr_primordialCPU = _PR_CreateCPU();
andre@0:     _pr_numCPU = 1;
andre@0:     _PR_StartCPU(_pr_primordialCPU, me);
andre@0: 
andre@0:     _PR_MD_SET_CURRENT_CPU(_pr_primordialCPU);
andre@0: 
andre@0:     /* Initialize cpu for current thread (could be different from me) */
andre@0:     _PR_MD_CURRENT_THREAD()->cpu = _pr_primordialCPU;
andre@0: 
andre@0:     _PR_MD_SET_LAST_THREAD(me);
andre@0: 
andre@0: #else /* Combined MxN model */
andre@0: 
andre@0:     _pr_primordialCPU = _PR_CreateCPU();
andre@0:     _pr_numCPU = 1;
andre@0:     _PR_CreateThread(PR_SYSTEM_THREAD,
andre@0:                      _PR_RunCPU,
andre@0:                      _pr_primordialCPU,
andre@0:                      PR_PRIORITY_NORMAL,
andre@0:                      PR_GLOBAL_THREAD,
andre@0:                      PR_UNJOINABLE_THREAD,
andre@0:                      0,
andre@0:                      _PR_IDLE_THREAD);
andre@0: 
andre@0: #endif /* _PR_LOCAL_THREADS_ONLY */
andre@0: 
andre@0:     _PR_MD_INIT_CPUS();
andre@0: }
andre@0: 
andre@0: #ifdef WINNT
andre@0: /*
andre@0:  * Right now this function merely stops the CPUs and does
andre@0:  * not do any other cleanup.
andre@0:  *
andre@0:  * It is only implemented for WINNT because bug 161998 only
andre@0:  * affects the WINNT version of NSPR, but it would be nice
andre@0:  * to implement this function for other platforms too.
andre@0:  */
andre@0: void _PR_CleanupCPUs(void)
andre@0: {
andre@0:     PRUintn i;
andre@0:     PRCList *qp;
andre@0:     _PRCPU *cpu;
andre@0: 
andre@0:     _pr_cpus_exit = 1;
andre@0:     for (i = 0; i < _pr_numCPU; i++) {
andre@0:         _PR_MD_WAKEUP_WAITER(NULL);
andre@0:     }
andre@0:     for (qp = _PR_CPUQ().next; qp != &_PR_CPUQ(); qp = qp->next) {
andre@0:         cpu = _PR_CPU_PTR(qp);
andre@0:         _PR_MD_JOIN_THREAD(&cpu->thread->md);
andre@0:     }
andre@0: }
andre@0: #endif
andre@0: 
andre@0: static _PRCPUQueue *_PR_CreateCPUQueue(void)
andre@0: {
andre@0:     PRInt32 index;
andre@0:     _PRCPUQueue *cpuQueue;
andre@0:     cpuQueue = PR_NEWZAP(_PRCPUQueue);
andre@0:  
andre@0:     _MD_NEW_LOCK( &cpuQueue->runQLock );
andre@0:     _MD_NEW_LOCK( &cpuQueue->sleepQLock );
andre@0:     _MD_NEW_LOCK( &cpuQueue->miscQLock );
andre@0: 
andre@0:     for (index = 0; index < PR_PRIORITY_LAST + 1; index++)
andre@0:         PR_INIT_CLIST( &(cpuQueue->runQ[index]) );
andre@0:     PR_INIT_CLIST( &(cpuQueue->sleepQ) );
andre@0:     PR_INIT_CLIST( &(cpuQueue->pauseQ) );
andre@0:     PR_INIT_CLIST( &(cpuQueue->suspendQ) );
andre@0:     PR_INIT_CLIST( &(cpuQueue->waitingToJoinQ) );
andre@0: 
andre@0:     cpuQueue->numCPUs = 1;
andre@0: 
andre@0:     return cpuQueue;
andre@0: }
andre@0: 
andre@0: /*
andre@0:  * Create a new CPU.
andre@0:  *
andre@0:  * This function initializes enough of the _PRCPU structure so
andre@0:  * that it can be accessed safely by a global thread or another
andre@0:  * CPU.  This function does not create the native thread that
andre@0:  * will run the CPU nor does it initialize the parts of _PRCPU
andre@0:  * that must be initialized by that native thread.
andre@0:  *
andre@0:  * The reason we cannot simply have the native thread create
andre@0:  * and fully initialize a new CPU is that we need to be able to
andre@0:  * create a usable _pr_primordialCPU in _PR_InitCPUs without
andre@0:  * assuming that the primordial CPU thread we created can run
andre@0:  * during NSPR initialization.  For example, on Windows while
andre@0:  * new threads can be created by DllMain, they won't be able
andre@0:  * to run during DLL initialization.  If NSPR is initialized
andre@0:  * by DllMain, the primordial CPU thread won't run until DLL
andre@0:  * initialization is finished.
andre@0:  */
andre@0: static _PRCPU *_PR_CreateCPU(void)
andre@0: {
andre@0:     _PRCPU *cpu;
andre@0: 
andre@0:     cpu = PR_NEWZAP(_PRCPU);
andre@0:     if (cpu) {
andre@0:         cpu->queue = _PR_CreateCPUQueue();
andre@0:         if (!cpu->queue) {
andre@0:             PR_DELETE(cpu);
andre@0:             return NULL;
andre@0:         }
andre@0:     }
andre@0:     return cpu;
andre@0: }
andre@0: 
andre@0: /*
andre@0:  * Start a new CPU.
andre@0:  *
andre@0:  * 'cpu' is a _PRCPU structure created by _PR_CreateCPU().
andre@0:  * 'thread' is the native thread that will run the CPU.
andre@0:  *
andre@0:  * If this function fails, 'cpu' is destroyed.
andre@0:  */
andre@0: static PRStatus _PR_StartCPU(_PRCPU *cpu, PRThread *thread)
andre@0: {
andre@0:     /*
andre@0:     ** Start a new cpu. The assumption this code makes is that the
andre@0:     ** underlying operating system creates a stack to go with the new
andre@0:     ** native thread. That stack will be used by the cpu when pausing.
andre@0:     */
andre@0: 
andre@0:     PR_ASSERT(!_native_threads_only);
andre@0: 
andre@0:     cpu->last_clock = PR_IntervalNow();
andre@0: 
andre@0:     /* Before we create any threads on this CPU we have to
andre@0:      * set the current CPU 
andre@0:      */
andre@0:     _PR_MD_SET_CURRENT_CPU(cpu);
andre@0:     _PR_MD_INIT_RUNNING_CPU(cpu);
andre@0:     thread->cpu = cpu;
andre@0: 
andre@0:     cpu->idle_thread = _PR_CreateThread(PR_SYSTEM_THREAD,
andre@0:                                         _PR_CPU_Idle,
andre@0:                                         (void *)cpu,
andre@0:                                         PR_PRIORITY_NORMAL,
andre@0:                                         PR_LOCAL_THREAD,
andre@0:                                         PR_UNJOINABLE_THREAD,
andre@0:                                         0,
andre@0:                                         _PR_IDLE_THREAD);
andre@0: 
andre@0:     if (!cpu->idle_thread) {
andre@0:         /* didn't clean up CPU queue XXXMB */
andre@0:         PR_DELETE(cpu);
andre@0:         return PR_FAILURE;
andre@0:     } 
andre@0:     PR_ASSERT(cpu->idle_thread->cpu == cpu);
andre@0: 
andre@0:     cpu->idle_thread->no_sched = 0;
andre@0: 
andre@0:     cpu->thread = thread;
andre@0: 
andre@0:     if (_pr_cpu_affinity_mask)
andre@0:         PR_SetThreadAffinityMask(thread, _pr_cpu_affinity_mask);
andre@0: 
andre@0:     /* Created and started a new CPU */
andre@0:     _PR_CPU_LIST_LOCK();
andre@0:     cpu->id = _pr_cpuID++;
andre@0:     PR_APPEND_LINK(&cpu->links, &_PR_CPUQ());
andre@0:     _PR_CPU_LIST_UNLOCK();
andre@0: 
andre@0:     return PR_SUCCESS;
andre@0: }
andre@0: 
andre@0: #if !defined(_PR_GLOBAL_THREADS_ONLY) && !defined(_PR_LOCAL_THREADS_ONLY)
andre@0: /*
andre@0: ** This code is used during a cpu's initial creation.
andre@0: */
andre@0: static void _PR_RunCPU(void *arg)
andre@0: {
andre@0:     _PRCPU *cpu = (_PRCPU *)arg;
andre@0:     PRThread *me = _PR_MD_CURRENT_THREAD();
andre@0: 
andre@0:     PR_ASSERT(NULL != me);
andre@0: 
andre@0:     /*
andre@0:      * _PR_StartCPU calls _PR_CreateThread to create the
andre@0:      * idle thread.  Because _PR_CreateThread calls PR_Lock,
andre@0:      * the current thread has to remain a global thread
andre@0:      * during the _PR_StartCPU call so that it can wait for
andre@0:      * the lock if the lock is held by another thread.  If
andre@0:      * we clear the _PR_GLOBAL_SCOPE flag in
andre@0:      * _PR_MD_CREATE_PRIMORDIAL_THREAD, the current thread
andre@0:      * will be treated as a local thread and have trouble
andre@0:      * waiting for the lock because the CPU is not fully
andre@0:      * constructed yet.
andre@0:      *
andre@0:      * After the CPU is started, it is safe to mark the
andre@0:      * current thread as a local thread.
andre@0:      */
andre@0: 
andre@0: #ifdef HAVE_CUSTOM_USER_THREADS
andre@0:     _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(me);
andre@0: #endif
andre@0: 
andre@0:     me->no_sched = 1;
andre@0:     _PR_StartCPU(cpu, me);
andre@0: 
andre@0: #ifdef HAVE_CUSTOM_USER_THREADS
andre@0:     me->flags &= (~_PR_GLOBAL_SCOPE);
andre@0: #endif
andre@0: 
andre@0:     _PR_MD_SET_CURRENT_CPU(cpu);
andre@0:     _PR_MD_SET_CURRENT_THREAD(cpu->thread);
andre@0:     me->cpu = cpu;
andre@0: 
andre@0:     while(1) {
andre@0:         PRInt32 is;
andre@0:         if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is);
andre@0: 	    _PR_MD_START_INTERRUPTS();
andre@0:         _PR_MD_SWITCH_CONTEXT(me);
andre@0:     }
andre@0: }
andre@0: #endif
andre@0: 
andre@0: static void PR_CALLBACK _PR_CPU_Idle(void *_cpu)
andre@0: {
andre@0:     _PRCPU *cpu = (_PRCPU *)_cpu;
andre@0:     PRThread *me = _PR_MD_CURRENT_THREAD();
andre@0: 
andre@0:     PR_ASSERT(NULL != me);
andre@0: 
andre@0:     me->cpu = cpu;
andre@0:     cpu->idle_thread = me;
andre@0:     if (_MD_LAST_THREAD())
andre@0:         _MD_LAST_THREAD()->no_sched = 0;
andre@0:     if (!_PR_IS_NATIVE_THREAD(me)) _PR_MD_SET_INTSOFF(0);
andre@0:     while(1) {
andre@0:         PRInt32 is;
andre@0:         PRIntervalTime timeout;
andre@0:         if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is);
andre@0: 
andre@0:         _PR_RUNQ_LOCK(cpu);
andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
andre@0: #ifdef _PR_HAVE_ATOMIC_OPS
andre@0:         _PR_MD_ATOMIC_INCREMENT(&_pr_md_idle_cpus);
andre@0: #else
andre@0:         _PR_MD_LOCK(&_pr_md_idle_cpus_lock);
andre@0:         _pr_md_idle_cpus++;
andre@0:         _PR_MD_UNLOCK(&_pr_md_idle_cpus_lock);
andre@0: #endif /* _PR_HAVE_ATOMIC_OPS */
andre@0: #endif
andre@0:         /* If someone on runq; do a nonblocking PAUSECPU */
andre@0:         if (_PR_RUNQREADYMASK(me->cpu) != 0) {
andre@0:             _PR_RUNQ_UNLOCK(cpu);
andre@0:             timeout = PR_INTERVAL_NO_WAIT;
andre@0:         } else {
andre@0:             _PR_RUNQ_UNLOCK(cpu);
andre@0: 
andre@0:             _PR_SLEEPQ_LOCK(cpu);
andre@0:             if (PR_CLIST_IS_EMPTY(&_PR_SLEEPQ(me->cpu))) {
andre@0:                 timeout = PR_INTERVAL_NO_TIMEOUT;
andre@0:             } else {
andre@0:                 PRThread *wakeThread;
andre@0:                 wakeThread = _PR_THREAD_PTR(_PR_SLEEPQ(me->cpu).next);
andre@0:                 timeout = wakeThread->sleep;
andre@0:             }
andre@0:             _PR_SLEEPQ_UNLOCK(cpu);
andre@0:         }
andre@0: 
andre@0:         /* Wait for an IO to complete */
andre@0:         (void)_PR_MD_PAUSE_CPU(timeout);
andre@0: 
andre@0: #ifdef WINNT
andre@0:         if (_pr_cpus_exit) {
andre@0:             /* _PR_CleanupCPUs tells us to exit */
andre@0:             _PR_MD_END_THREAD();
andre@0:         }
andre@0: #endif
andre@0: 
andre@0: #if !defined(_PR_LOCAL_THREADS_ONLY) && !defined(_PR_GLOBAL_THREADS_ONLY)
andre@0: #ifdef _PR_HAVE_ATOMIC_OPS
andre@0:         _PR_MD_ATOMIC_DECREMENT(&_pr_md_idle_cpus);
andre@0: #else
andre@0:         _PR_MD_LOCK(&_pr_md_idle_cpus_lock);
andre@0:         _pr_md_idle_cpus--;
andre@0:         _PR_MD_UNLOCK(&_pr_md_idle_cpus_lock);
andre@0: #endif /* _PR_HAVE_ATOMIC_OPS */
andre@0: #endif
andre@0: 
andre@0: 		_PR_ClockInterrupt();
andre@0: 
andre@0: 		/* Now schedule any thread that is on the runq
andre@0: 		 * INTS must be OFF when calling PR_Schedule()
andre@0: 		 */
andre@0: 		me->state = _PR_RUNNABLE;
andre@0: 		_PR_MD_SWITCH_CONTEXT(me);
andre@0: 		if (!_PR_IS_NATIVE_THREAD(me)) _PR_FAST_INTSON(is);
andre@0:     }
andre@0: }
andre@0: #endif /* _PR_GLOBAL_THREADS_ONLY */
andre@0: 
andre@0: PR_IMPLEMENT(void) PR_SetConcurrency(PRUintn numCPUs)
andre@0: {
andre@0: #if defined(_PR_GLOBAL_THREADS_ONLY) || defined(_PR_LOCAL_THREADS_ONLY)
andre@0: 
andre@0:     /* do nothing */
andre@0: 
andre@0: #else /* combined, MxN thread model */
andre@0: 
andre@0:     PRUintn newCPU;
andre@0:     _PRCPU *cpu;
andre@0:     PRThread *thr;
andre@0: 
andre@0: 
andre@0:     if (!_pr_initialized) _PR_ImplicitInitialization();
andre@0: 
andre@0: 	if (_native_threads_only)
andre@0: 		return;
andre@0:     
andre@0:     _PR_CPU_LIST_LOCK();
andre@0:     if (_pr_numCPU < numCPUs) {
andre@0:         newCPU = numCPUs - _pr_numCPU;
andre@0:         _pr_numCPU = numCPUs;
andre@0:     } else newCPU = 0;
andre@0:     _PR_CPU_LIST_UNLOCK();
andre@0: 
andre@0:     for (; newCPU; newCPU--) {
andre@0:         cpu = _PR_CreateCPU();
andre@0:         thr = _PR_CreateThread(PR_SYSTEM_THREAD,
andre@0:                               _PR_RunCPU,
andre@0:                               cpu,
andre@0:                               PR_PRIORITY_NORMAL,
andre@0:                               PR_GLOBAL_THREAD,
andre@0:                               PR_UNJOINABLE_THREAD,
andre@0:                               0,
andre@0:                               _PR_IDLE_THREAD);
andre@0:     }
andre@0: #endif
andre@0: }
andre@0: 
andre@0: PR_IMPLEMENT(_PRCPU *) _PR_GetPrimordialCPU(void)
andre@0: {
andre@0:     if (_pr_primordialCPU)
andre@0:         return _pr_primordialCPU;
andre@0:     else
andre@0:         return _PR_MD_CURRENT_CPU();
andre@0: }