/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "primpl.h"

#if !defined (USE_SVR4_THREADS)

/*
         * using only NSPR threads here
 */

#include <setjmp.h>

void _MD_EarlyInit(void)
{
}

PRWord *_MD_HomeGCRegisters(PRThread *t, int isCurrent, int *np)
{
    if (isCurrent) {
	(void) setjmp(CONTEXT(t));
    }
    *np = sizeof(CONTEXT(t)) / sizeof(PRWord);
    return (PRWord *) CONTEXT(t);
}

#ifdef ALARMS_BREAK_TCP /* I don't think they do */

PRInt32 _MD_connect(PRInt32 osfd, const PRNetAddr *addr, PRInt32 addrlen,
                        PRIntervalTime timeout)
{
    PRInt32 rv;

    _MD_BLOCK_CLOCK_INTERRUPTS();
    rv = _connect(osfd,addr,addrlen);
    _MD_UNBLOCK_CLOCK_INTERRUPTS();
}

PRInt32 _MD_accept(PRInt32 osfd, PRNetAddr *addr, PRInt32 addrlen,
                        PRIntervalTime timeout)
{
    PRInt32 rv;

    _MD_BLOCK_CLOCK_INTERRUPTS();
    rv = _accept(osfd,addr,addrlen);
    _MD_UNBLOCK_CLOCK_INTERRUPTS();
    return(rv);
}
#endif

/*
 * These are also implemented in pratom.c using NSPR locks.  Any reason
 * this might be better or worse?  If you like this better, define
 * _PR_HAVE_ATOMIC_OPS in include/md/unixware.h
 */
#ifdef _PR_HAVE_ATOMIC_OPS
/* Atomic operations */
#include  <stdio.h>
static FILE *_uw_semf;

void
_MD_INIT_ATOMIC(void)
{
    /* Sigh.  Sure wish SYSV semaphores weren't such a pain to use */
    if ((_uw_semf = tmpfile()) == NULL)
        PR_ASSERT(0);

    return;
}

void
_MD_ATOMIC_INCREMENT(PRInt32 *val)
{
    flockfile(_uw_semf);
    (*val)++;
    unflockfile(_uw_semf);
}

void
_MD_ATOMIC_ADD(PRInt32 *ptr, PRInt32 val)
{
    flockfile(_uw_semf);
    (*ptr) += val;
    unflockfile(_uw_semf);
}

void
_MD_ATOMIC_DECREMENT(PRInt32 *val)
{
    flockfile(_uw_semf);
    (*val)--;
    unflockfile(_uw_semf);
}

void
_MD_ATOMIC_SET(PRInt32 *val, PRInt32 newval)
{
    flockfile(_uw_semf);
    *val = newval;
    unflockfile(_uw_semf);
}
#endif

void
_MD_SET_PRIORITY(_MDThread *thread, PRUintn newPri)
{
    return;
}

PRStatus
_MD_InitializeThread(PRThread *thread)
{
	return PR_SUCCESS;
}

PRStatus
_MD_WAIT(PRThread *thread, PRIntervalTime ticks)
{
    PR_ASSERT(!(thread->flags & _PR_GLOBAL_SCOPE));
    _PR_MD_SWITCH_CONTEXT(thread);
    return PR_SUCCESS;
}

PRStatus
_MD_WAKEUP_WAITER(PRThread *thread)
{
    if (thread) {
	PR_ASSERT(!(thread->flags & _PR_GLOBAL_SCOPE));
    }
    return PR_SUCCESS;
}

/* These functions should not be called for Unixware */
void
_MD_YIELD(void)
{
    PR_NOT_REACHED("_MD_YIELD should not be called for Unixware.");
}

PRStatus
_MD_CREATE_THREAD(
    PRThread *thread,
    void (*start) (void *),
    PRThreadPriority priority,
    PRThreadScope scope,
    PRThreadState state,
    PRUint32 stackSize)
{
    PR_NOT_REACHED("_MD_CREATE_THREAD should not be called for Unixware.");
}

#else  /* USE_SVR4_THREADS */

/* NOTE:
 * SPARC v9 (Ultras) do have an atomic test-and-set operation.  But
 * SPARC v8 doesn't.  We should detect in the init if we are running on
 * v8 or v9, and then use assembly where we can.
 */

#include <thread.h>
#include <synch.h>

static mutex_t _unixware_atomic = DEFAULTMUTEX;

#define TEST_THEN_ADD(where, inc) \
    if (mutex_lock(&_unixware_atomic) != 0)\
        PR_ASSERT(0);\
    *where += inc;\
    if (mutex_unlock(&_unixware_atomic) != 0)\
        PR_ASSERT(0);

#define TEST_THEN_SET(where, val) \
    if (mutex_lock(&_unixware_atomic) != 0)\
        PR_ASSERT(0);\
    *where = val;\
    if (mutex_unlock(&_unixware_atomic) != 0)\
        PR_ASSERT(0);

void
_MD_INIT_ATOMIC(void)
{
}

void
_MD_ATOMIC_INCREMENT(PRInt32 *val)
{
    TEST_THEN_ADD(val, 1);
}

void
_MD_ATOMIC_ADD(PRInt32 *ptr, PRInt32 val)
{
    TEST_THEN_ADD(ptr, val);
}

void
_MD_ATOMIC_DECREMENT(PRInt32 *val)
{
    TEST_THEN_ADD(val, 0xffffffff);
}

void
_MD_ATOMIC_SET(PRInt32 *val, PRInt32 newval)
{
    TEST_THEN_SET(val, newval);
}

#include <signal.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/lwp.h>
#include <sys/procfs.h>
#include <sys/syscall.h>


THREAD_KEY_T threadid_key;
THREAD_KEY_T cpuid_key;
THREAD_KEY_T last_thread_key;
static sigset_t set, oldset;

void _MD_EarlyInit(void)
{
    THR_KEYCREATE(&threadid_key, NULL);
    THR_KEYCREATE(&cpuid_key, NULL);
    THR_KEYCREATE(&last_thread_key, NULL);
    sigemptyset(&set);
    sigaddset(&set, SIGALRM);
}

PRStatus _MD_CREATE_THREAD(PRThread *thread, 
					void (*start)(void *), 
					PRThreadPriority priority,
					PRThreadScope scope, 
					PRThreadState state, 
					PRUint32 stackSize) 
{
	long flags;
	
    /* mask out SIGALRM for native thread creation */
    thr_sigsetmask(SIG_BLOCK, &set, &oldset); 

    flags = (state == PR_JOINABLE_THREAD ? THR_SUSPENDED/*|THR_NEW_LWP*/ 
			   : THR_SUSPENDED|THR_DETACHED/*|THR_NEW_LWP*/);
	if (_PR_IS_GCABLE_THREAD(thread) ||
							(scope == PR_GLOBAL_BOUND_THREAD))
		flags |= THR_BOUND;

    if (thr_create(NULL, thread->stack->stackSize,
                  (void *(*)(void *)) start, (void *) thread, 
				  flags,
                  &thread->md.handle)) {
        thr_sigsetmask(SIG_SETMASK, &oldset, NULL); 
	return PR_FAILURE;
    }


    /* When the thread starts running, then the lwpid is set to the right
     * value. Until then we want to mark this as 'uninit' so that
     * its register state is initialized properly for GC */

    thread->md.lwpid = -1;
    thr_sigsetmask(SIG_SETMASK, &oldset, NULL); 
    _MD_NEW_SEM(&thread->md.waiter_sem, 0);

	if ((scope == PR_GLOBAL_THREAD) || (scope == PR_GLOBAL_BOUND_THREAD)) {
		thread->flags |= _PR_GLOBAL_SCOPE;
    }

    /* 
    ** Set the thread priority.  This will also place the thread on 
    ** the runQ.
    **
    ** Force PR_SetThreadPriority to set the priority by
    ** setting thread->priority to 100.
    */
    {
    int pri;
    pri = thread->priority;
    thread->priority = 100;
    PR_SetThreadPriority( thread, pri );

    PR_LOG(_pr_thread_lm, PR_LOG_MIN, 
            ("(0X%x)[Start]: on to runq at priority %d",
            thread, thread->priority));
    }

    /* Activate the thread */
    if (thr_continue( thread->md.handle ) ) {
	return PR_FAILURE;
    }
    return PR_SUCCESS;
}

void _MD_cleanup_thread(PRThread *thread)
{
    thread_t hdl;
    PRMonitor *mon;

    hdl = thread->md.handle;

    /* 
    ** First, suspend the thread (unless it's the active one)
    ** Because we suspend it first, we don't have to use LOCK_SCHEDULER to
    ** prevent both of us modifying the thread structure at the same time.
    */
    if ( thread != _PR_MD_CURRENT_THREAD() ) {
        thr_suspend(hdl);
    }
    PR_LOG(_pr_thread_lm, PR_LOG_MIN,
            ("(0X%x)[DestroyThread]\n", thread));

    _MD_DESTROY_SEM(&thread->md.waiter_sem);
}

void _MD_SET_PRIORITY(_MDThread *md_thread, PRUintn newPri)
{
	if(thr_setprio((thread_t)md_thread->handle, newPri)) {
		PR_LOG(_pr_thread_lm, PR_LOG_MIN,
		   ("_PR_SetThreadPriority: can't set thread priority\n"));
	}
}

void _MD_WAIT_CV(
    struct _MDCVar *md_cv, struct _MDLock *md_lock, PRIntervalTime timeout)
{
    struct timespec tt;
    PRUint32 msec;
    int rv;
    PRThread *me = _PR_MD_CURRENT_THREAD();

    msec = PR_IntervalToMilliseconds(timeout);

    GETTIME (&tt);

    tt.tv_sec += msec / PR_MSEC_PER_SEC;
    tt.tv_nsec += (msec % PR_MSEC_PER_SEC) * PR_NSEC_PER_MSEC;
    /* Check for nsec overflow - otherwise we'll get an EINVAL */
    if (tt.tv_nsec >= PR_NSEC_PER_SEC) {
        tt.tv_sec++;
        tt.tv_nsec -= PR_NSEC_PER_SEC;
    }
    me->md.sp = unixware_getsp();


    /* XXX Solaris 2.5.x gives back EINTR occasionally for no reason
     * hence ignore EINTR for now */

    COND_TIMEDWAIT(&md_cv->cv, &md_lock->lock, &tt);
}

void _MD_lock(struct _MDLock *md_lock)
{
    mutex_lock(&md_lock->lock);
}

void _MD_unlock(struct _MDLock *md_lock)
{
    mutex_unlock(&((md_lock)->lock));
}


PRThread *_pr_current_thread_tls()
{
    PRThread *ret;

    thr_getspecific(threadid_key, (void **)&ret);
    return ret;
}

PRStatus
_MD_WAIT(PRThread *thread, PRIntervalTime ticks)
{
        _MD_WAIT_SEM(&thread->md.waiter_sem);
        return PR_SUCCESS;
}

PRStatus
_MD_WAKEUP_WAITER(PRThread *thread)
{
	if (thread == NULL) {
		return PR_SUCCESS;
	}
	_MD_POST_SEM(&thread->md.waiter_sem);
	return PR_SUCCESS;
}

_PRCPU *_pr_current_cpu_tls()
{
    _PRCPU *ret;

    thr_getspecific(cpuid_key, (void **)&ret);
    return ret;
}

PRThread *_pr_last_thread_tls()
{
    PRThread *ret;

    thr_getspecific(last_thread_key, (void **)&ret);
    return ret;
}

_MDLock _pr_ioq_lock;

void _MD_INIT_IO (void)
{
    _MD_NEW_LOCK(&_pr_ioq_lock);
}

PRStatus _MD_InitializeThread(PRThread *thread)
{
    if (!_PR_IS_NATIVE_THREAD(thread))
        return;
		/* prime the sp; substract 4 so we don't hit the assert that
		 * curr sp > base_stack
		 */
    thread->md.sp = (uint_t) thread->stack->allocBase - sizeof(long);
    thread->md.lwpid = _lwp_self();
    thread->md.handle = THR_SELF();

	/* all threads on Solaris are global threads from NSPR's perspective
	 * since all of them are mapped to Solaris threads.
	 */
    thread->flags |= _PR_GLOBAL_SCOPE;

 	/* For primordial/attached thread, we don't create an underlying native thread.
 	 * So, _MD_CREATE_THREAD() does not get called.  We need to do initialization
 	 * like allocating thread's synchronization variables and set the underlying
 	 * native thread's priority.
 	 */
	if (thread->flags & (_PR_PRIMORDIAL | _PR_ATTACHED)) {
	    _MD_NEW_SEM(&thread->md.waiter_sem, 0);
	    _MD_SET_PRIORITY(&(thread->md), thread->priority);
	}
	return PR_SUCCESS;
}

static sigset_t old_mask;	/* store away original gc thread sigmask */
static int gcprio;		/* store away original gc thread priority */
static lwpid_t *all_lwps=NULL;	/* list of lwps that we suspended */
static int num_lwps ;
static int suspendAllOn = 0;

#define VALID_SP(sp, bottom, top)	\
       (((uint_t)(sp)) > ((uint_t)(bottom)) && ((uint_t)(sp)) < ((uint_t)(top)))

void unixware_preempt_off()
{
    sigset_t set;
    (void)sigfillset(&set);
    sigprocmask (SIG_SETMASK, &set, &old_mask);
}

void unixware_preempt_on()
{
    sigprocmask (SIG_SETMASK, &old_mask, NULL);      
}

void _MD_Begin_SuspendAll()
{
    unixware_preempt_off();

    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin_SuspendAll\n"));
    /* run at highest prio so I cannot be preempted */
    thr_getprio(thr_self(), &gcprio);
    thr_setprio(thr_self(), 0x7fffffff); 
    suspendAllOn = 1;
}

void _MD_End_SuspendAll()
{
}

void _MD_End_ResumeAll()
{
    PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("End_ResumeAll\n"));
    thr_setprio(thr_self(), gcprio);
    unixware_preempt_on();
    suspendAllOn = 0;
}

void _MD_Suspend(PRThread *thr)
{
   int lwp_fd, result;
   int lwp_main_proc_fd = 0;

    thr_suspend(thr->md.handle);
    if (!_PR_IS_GCABLE_THREAD(thr))
      return;
    /* XXX Primordial thread can't be bound to an lwp, hence there is no
     * way we can assume that we can get the lwp status for primordial
     * thread reliably. Hence we skip this for primordial thread, hoping
     * that the SP is saved during lock and cond. wait. 
     * XXX - Again this is concern only for java interpreter, not for the
     * server, 'cause primordial thread in the server does not do java work
     */
    if (thr->flags & _PR_PRIMORDIAL)
      return;

    /* if the thread is not started yet then don't do anything */
    if (!suspendAllOn || thr->md.lwpid == -1)
      return;

}
void _MD_Resume(PRThread *thr)
{
   if (!_PR_IS_GCABLE_THREAD(thr) || !suspendAllOn){
     /*XXX When the suspendAllOn is set, we will be trying to do lwp_suspend
      * during that time we can't call any thread lib or libc calls. Hence
      * make sure that no resume is requested for Non gcable thread
      * during suspendAllOn */
      PR_ASSERT(!suspendAllOn);
      thr_continue(thr->md.handle);
      return;
   }
   if (thr->md.lwpid == -1)
     return;
 
   if ( _lwp_continue(thr->md.lwpid) < 0) {
      PR_ASSERT(0);  /* ARGH, we are hosed! */
   }
}


PRWord *_MD_HomeGCRegisters(PRThread *t, int isCurrent, int *np)
{
    if (isCurrent) {
	(void) getcontext(CONTEXT(t));	/* XXX tune me: set md_IRIX.c */
    }
    *np = NGREG;
    if (t->md.lwpid == -1)
      memset(&t->md.context.uc_mcontext.gregs[0], 0, NGREG * sizeof(PRWord));
    return (PRWord*) &t->md.context.uc_mcontext.gregs[0];
}

int
_pr_unixware_clock_gettime (struct timespec *tp)
{
    struct timeval tv;
 
    gettimeofday(&tv, NULL);
    tp->tv_sec = tv.tv_sec;
    tp->tv_nsec = tv.tv_usec * 1000;
    return 0;
}


#endif /* USE_SVR4_THREADS */