/*     
 * $RCSfile: semaphore.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: semaphore.c,v $
 * Revision 1.1  1996/11/22 15:48:40  dorgival
 * Initial revision
 *
 *
 */

/*
 * Counting semaphores implemented on top of threads package.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#include "xk_semaphore.h"
#include "xk_thread.h"
#include "trace.h"
#include "xk_debug.h"
#include "event.h"
#include "x_util.h"

int tracesemaphores;

void semDestroy( Semaphore* s );

static void semCheckSignature( Semaphore* s );

extern xk_thread_t* xk_lock_owner(void);

static void
semCheckSignature( Semaphore* s )
{
    if ( s->sem_signature != s ) {
	xTrace1(semaphores,TR_ALWAYS,"semCheckSignature: sem 0x%x", s);
    }
    if ( s->mutex_signature != &s->mutex ) {
	xTrace1(semaphores,TR_ALWAYS,"semCheckSignature: sem 0x%x mutex", s );
    }
    if ( s->cond_signature != &s->cond ) {
	xTrace1(semaphores,TR_ALWAYS,"semCheckSignature: sem 0x%x cond", s );
    }
}

/*
 * wake_sem is used to implement Delay... 
 */
static void wake_sem( Event ev, VOID *arg );

void
semInit(Semaphore* s, u_int initialVal)
{
    xTrace3(semaphores,TR_FUNCTIONAL_TRACE,"semInit(0x%x,%d) by thread 0x%x",
            s,initialVal,pthread_self() );
    s->sem_signature   = s;
    s->mutex_signature = &s->mutex;
    s->cond_signature  = &s->cond;
    s->count = initialVal;
    pthread_mutex_init(&(s->mutex), pthread_mutexattr_default);
    pthread_cond_init(&(s->cond), pthread_condattr_default);
    semCheckSignature(s);
}

void
semDestroy( Semaphore* s )
{
    xTrace2(semaphores,TR_FUNCTIONAL_TRACE,"semDestroy(0x%x) by thread 0x%x",
            s,pthread_self() );
    semCheckSignature(s);
    if ( pthread_mutex_destroy(&(s->mutex)) < 0 ) {
	if ( errno == EBUSY ) {
	    xTrace2(semaphores,TR_SOFT_ERRORS,
	            "semDestroy(0x%x) failed (thread 0x%x): mutex is locked",
		    s,pthread_self() );
        } else {
	    xTrace2(semaphores,TR_SOFT_ERRORS,
	            "semDestroy(0x%x) failed (thread 0x%x): invalid mutex?",
		    s,pthread_self() );
	}
    }
    if ( pthread_cond_destroy(&(s->cond)) < 0 ) {
	if ( errno == EBUSY ) {
	    xTrace2(semaphores,TR_SOFT_ERRORS,
	    "semDestroy(0x%x) failed (thread 0x%x): cond is being waited on",
		    s,pthread_self() );
        } else {
	    xTrace2(semaphores,TR_SOFT_ERRORS,
	            "semDestroy(0x%x) failed (thread 0x%x): invalid cond?",
		    s,pthread_self() );
	}
    }
}

/*
 * Wait and signal are simplified by the way condition variables are
 * handled by the p-threads package: the wait/signal calls already
 * deal with the problem of releasing/acquiring a mutex lock.
 * All that is necessary is to pass the master mutex in those calls.
 */

void
semWait(Semaphore* s)
{
    xk_thread_t* this_thread = threadSelf();

    xTrace2(semaphores,TR_FUNCTIONAL_TRACE,"semWait(0x%x) by thread 0x%x",
            s,this_thread );
    semCheckSignature(s);
    assert(xk_lock_owner() == this_thread);
    pthread_mutex_lock(&(s->mutex));
    if (--s->count < 0) {
	xTrace2(semaphores,TR_FUNCTIONAL_TRACE,
	        "semWait(0x%x) by thread 0x%x will really wait",s,this_thread );
	this_thread->status |= XK_SEM_WAIT;
	xk_all_unlock();
	while (s->count < 0 ) {
	    pthread_cond_wait(&(s->cond),&(s->mutex));
	}
	this_thread->status &= ~XK_SEM_WAIT;
	xTrace2(semaphores,TR_FUNCTIONAL_TRACE,
	        "semWait(0x%x) by thread 0x%x back from wait",s,this_thread);
	xk_internal_lock();
    }
    this_thread->status |= XK_SEM_OWN;
    pthread_mutex_unlock(&(s->mutex));
    xTrace2(semaphores,TR_FUNCTIONAL_TRACE,"semWait(0x%x): thread 0x%x returning",
            s,this_thread );
} 

void
semSignal(Semaphore* s)
{
    xk_thread_t* this_thread = threadSelf();

    xTrace2(semaphores,TR_FUNCTIONAL_TRACE,"semSignal(0x%x) by thread 0x%x",
            s,this_thread );
    semCheckSignature(s);
    assert(xk_lock_owner() == this_thread);
    this_thread->status &= ~XK_SEM_OWN;
    pthread_mutex_lock(&(s->mutex));
    if (s->count++ < 0) {
	xTrace2(semaphores,TR_FUNCTIONAL_TRACE,
	        "semSignal(0x%x) by thread 0x%x signalling other threads",
		s,this_thread );
	pthread_cond_signal(&(s->cond));
    }
    pthread_mutex_unlock(&(s->mutex));
    xTrace2(semaphores,TR_FUNCTIONAL_TRACE,"semSignal(0x%x) thread 0x%x returning",
            s,this_thread );
}


int
semCount (Semaphore* s)
{
    semCheckSignature(s);
    return s->count;
}


extern void
semSignalAll (Semaphore* s)
{
    int i, nblocked = -s->count;

    xTrace3(semaphores,TR_FUNCTIONAL_TRACE,
            "semSignalAll(0x%x) by thread 0x%x will wake %d threads",
	    s,pthread_self(), nblocked );
    semCheckSignature(s);
    for (i = 0; i < nblocked; ++i) {
	semSignal(s);
    }
    xTrace2(semaphores,TR_FUNCTIONAL_TRACE,
            "semSignalAll(0x%x) by thread 0x%x returning",s,pthread_self() );
}

void
Delay( int n )
{
    Semaphore s;
    xk_thread_t* this_thread = threadSelf();

    xTrace2(semaphores,TR_MAJOR_EVENTS,
            "Delay(%d) by thread 0x%x",n,this_thread );
    assert(xk_lock_owner() == this_thread);
    this_thread->status |= XK_DELAYED;
    semInit(&s, 0);
    evDetach( evSchedule(wake_sem, &s, n*1000) );
    semWait(&s);
    this_thread->status &= ~XK_DELAYED;
    semDestroy(&s);
}

static void
wake_sem( Event ev, VOID *arg )
{
  semSignal((Semaphore *)arg);
}
