/*	$NetBSD: sem.c,v 1.7 2003/11/24 23:54:13 cl Exp $	*/

/*-
 * Copyright (c) 2003 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of Wasabi Systems, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (C) 2000 Jason Evans <jasone@freebsd.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice(s), this list of conditions and the following disclaimer as
 *    the first lines of this file unmodified other than the possible
 *    addition of one or more copyright notices.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice(s), this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*#define PTHREAD_DEBUG_SEMS*/

#include <sys/cdefs.h>

#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <stdarg.h>

#include <pthread.h>

#ifdef PTHREAD_DEBUG_SEMS
#include <unixlib/os.h>
#endif

struct pthread_queue_t {
	pthread_t thread; /* this thread */
	struct pthread_queue_t *next;    /* next element */
};

#if 0
typedef unsigned int semid_t;

struct _sem_st {
	unsigned int	usem_magic;
#define	USEM_MAGIC	0x09fa4012

	semid_t		usem_semid;	/* 0 -> user (non-shared) */
#define	USEM_USER	0		/* assumes kernel does not use NULL */
	/* Protects data below. */
	pthread_rwlock_t	usem_interlock;

	struct pthread_queue_t *usem_waiters;
	unsigned int	usem_count;
};
#endif

#define	USEM_MAGIC	0x09fa4012
#define	USEM_USER	0	/* assumes kernel does not use NULL */


static int sem_alloc(unsigned int value, semid_t semid, sem_t *semp);
static void sem_free(sem_t *sem);

static void
sem_free(sem_t *sem)
{
#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_free: Freeing semaphore (");
	__os_prhex((int)sem);
	__os_print(")\r\n");
#endif
	sem->usem_magic = 0;
	free(sem);
}

static int
sem_alloc(unsigned int value, semid_t semid, sem_t *semp)
{
	sem_t *sem;

	if (value > SEM_VALUE_MAX)
		return (EINVAL);

	if ((sem = malloc(sizeof(sem_t))) == NULL)
		return (ENOSPC);

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_alloc: Allocating semaphore (");
	__os_prhex((int)semp);
	__os_print(" id: ");
	__os_prhex((int)semid);
	__os_print(" value: ");
	__os_prdec((int)value);
	__os_print(")\r\n");
#endif

	sem->usem_magic = USEM_MAGIC;
	pthread_rwlock_init(&sem->usem_interlock, NULL);
/*	PTQ_INIT(&sem->usem_waiters);
*/	sem->usem_waiters = NULL;
	sem->usem_count = value;
	sem->usem_semid = semid;

	*semp = *sem;
	return (0);
}

int
sem_init(sem_t *sem, int pshared, unsigned int value)
{
	semid_t	semid;
	int error;

	/* Semaphores shared between processes aren't supported on RISC OS */
	if (pshared) {
		errno = ENOSYS;
		return (-1);
	}

	semid = USEM_USER;

	if ((error = sem_alloc(value, semid, sem)) != 0) {
		errno = error;
		return (-1);
	}

	return (0);
}

int
sem_destroy(sem_t *sem)
{
#ifdef ERRORCHECK
	if (sem == NULL || *sem == NULL || (*sem)->usem_magic != USEM_MAGIC) {
		errno = EINVAL;
		return (-1);
	}
#endif

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_destroy: Attempting to destroy semaphore (");
	__os_prhex((int)sem);
	__os_print(")..........");
#endif

	pthread_rwlock_rdlock(&sem->usem_interlock);
	if (/*!PTQ_EMPTY(&(*sem)->usem_waiters)*/
	    sem->usem_waiters != NULL) {
		pthread_rwlock_unlock(&sem->usem_interlock);
#ifdef PTHREAD_DEBUG_SEMS
		__os_print("failed\r\n");
#endif
		errno = EBUSY;
		return (-1);
	}
	pthread_rwlock_unlock(&sem->usem_interlock);

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("succeeded\r\n");
#endif

	sem_free(sem);

	return (0);
}

sem_t *
sem_open(const char *name, int oflag, ...)
{
	/* not supported under RISC OS */
	errno = ENOSYS;
	return (SEM_FAILED);
}

int
sem_close(sem_t *sem)
{
	/* not supported under RISC OS */
	errno = ENOSYS;
	return (-1);
}

int
sem_unlink(const char *name)
{
	/* not supported under RISC OS */
	errno = ENOSYS;
	return (-1);
}

int
sem_wait(sem_t *sem)
{
	pthread_t self;
	struct pthread_queue_t *queue_entry, *tmp;

#ifdef ERRORCHECK
	if (sem == NULL || *sem == NULL || (*sem)->usem_magic != USEM_MAGIC) {
		errno = EINVAL;
		return (-1);
	}
#endif

	self = __pthread_running_thread;
	queue_entry = malloc(sizeof(struct pthread_queue_t));
	queue_entry->thread = self;
	queue_entry->next = NULL;

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_wait: waiting on semaphore (");
	__os_prhex((int)sem);
	__os_print(")\r\n");
#endif

	for (;;) {
		pthread_rwlock_wrlock(&sem->usem_interlock);
		__pthread_disable_ints();
/*		pthread_spinlock(self, &self->pt_statelock);
*/		if (self->cancelpending) {
/*			pthread_spinunlock(self, &self->pt_statelock);
*/			__pthread_enable_ints();
			pthread_rwlock_unlock(&sem->usem_interlock);
			pthread_exit(PTHREAD_CANCELED);
		}

		if (sem->usem_count > 0) {
/*			pthread_spinunlock(self, &self->pt_statelock);
*/			__pthread_enable_ints();
			break;
		}

/*		PTQ_INSERT_TAIL(&(*sem)->usem_waiters, self, pt_sleep);
*/		for (tmp = sem->usem_waiters;
		     tmp->next && tmp != queue_entry; tmp = tmp->next)
			;
		if (tmp != queue_entry)
			tmp->next = queue_entry;

		self->state = STATE_BLOCKED;
/*		self->pt_sleepobj = *sem;
		self->pt_sleepq = &(*sem)->usem_waiters;
		self->pt_sleeplock = &(*sem)->usem_interlock;
		pthread_spinunlock(self, &self->pt_statelock);
*/
		__pthread_enable_ints();
		/* XXX What about signals? */

		pthread_rwlock_unlock(&sem->usem_interlock);

/*		pthread__block(self, &(*sem)->usem_interlock);
*/		/* interlock is not held when we return */
		pthread_yield();
	}

	sem->usem_count--;

	pthread_rwlock_unlock(&sem->usem_interlock);

	return (0);
}

int
sem_trywait(sem_t *sem)
{
#ifdef ERRORCHECK
	if (sem == NULL || *sem == NULL || (*sem)->usem_magic != USEM_MAGIC) {
		errno = EINVAL;
		return (-1);
	}
#endif

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_trywait: trying to acquire semaphore (");
	__os_prhex((int)sem);
	__os_print(")..........");
#endif

	pthread_rwlock_wrlock(&sem->usem_interlock);

	if (sem->usem_count == 0) {
		pthread_rwlock_unlock(&sem->usem_interlock);
#ifdef PTHREAD_DEBUG_SEMS
		__os_print("failed\r\n");
#endif
		errno = EAGAIN;
		return (-1);
	}

	sem->usem_count--;

	pthread_rwlock_unlock(&sem->usem_interlock);

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("succeeded\r\n");
#endif

	return (0);
}

int
sem_post(sem_t *sem)
{
	pthread_t self, blocked;
	struct pthread_queue_t *tmp;

#ifdef ERRORCHECK
	if (sem == NULL || *sem == NULL || (*sem)->usem_magic != USEM_MAGIC) {
		errno = EINVAL;
		return (-1);
	}
#endif

	self = __pthread_running_thread;

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_post: releasing semaphore (");
	__os_prhex((int)sem);
	__os_print(")\r\n");
#endif

	pthread_rwlock_wrlock(&sem->usem_interlock);
	sem->usem_count++;

	if (sem->usem_waiters != NULL)
		blocked = sem->usem_waiters->thread;
	else
		blocked = NULL;
	if (blocked) {
/*		PTQ_REMOVE(&(*sem)->usem_waiters, blocked, pt_sleep);
*/		tmp = sem->usem_waiters;
		sem->usem_waiters = sem->usem_waiters->next;
		tmp->thread = tmp->thread = NULL;
		free(tmp);

		/* Give the head of the blocked queue another try. */
/*		pthread__sched(self, blocked);
*/		__pthread_disable_ints();
		blocked->state = STATE_RUNNING;
		__pthread_enable_ints();
	}
	pthread_rwlock_unlock(&sem->usem_interlock);

	pthread_yield();

	return (0);
}

int
sem_getvalue(sem_t * __restrict sem, int * __restrict sval)
{
#ifdef ERRORCHECK
	if (sem == NULL || *sem == NULL || (*sem)->usem_magic != USEM_MAGIC) {
		errno = EINVAL;
		return (-1);
	}
#endif

#ifdef PTHREAD_DEBUG_SEMS
	__os_print("-- sem_getvalue: Retrieving value of semaphore\r\n");
#endif

	pthread_rwlock_rdlock(&sem->usem_interlock);
	*sval = (int) sem->usem_count;
	pthread_rwlock_unlock(&sem->usem_interlock);

	return (0);
}
