/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include "lint.h"
#include "thr_uberdata.h"

static uint32_t _semvaluemax;

/*
 * Check to see if anyone is waiting for this semaphore.
 */
#pragma weak sema_held = _sema_held
int
_sema_held(sema_t *sp)
{
	return (sp->count == 0);
}

#pragma weak sema_init = _sema_init
/* ARGSUSED2 */
int
_sema_init(sema_t *sp, unsigned int count, int type, void *arg)
{
	if (_semvaluemax == 0)
		_semvaluemax = (uint32_t)_sysconf(_SC_SEM_VALUE_MAX);
	if ((type != USYNC_THREAD && type != USYNC_PROCESS) ||
	    (count > _semvaluemax))
		return (EINVAL);
	(void) _memset(sp, 0, sizeof (*sp));
	sp->count = count;
	sp->type = (uint16_t)type;
	sp->magic = SEMA_MAGIC;
	return (0);
}

#pragma weak sema_destroy = _sema_destroy
int
_sema_destroy(sema_t *sp)
{
	sp->magic = 0;
	tdb_sync_obj_deregister(sp);
	return (0);
}

static int
sema_wait_impl(sema_t *sp, timespec_t *tsp)
{
	lwp_sema_t *lsp = (lwp_sema_t *)sp;
	ulwp_t *self = curthread;
	uberdata_t *udp = self->ul_uberdata;
	tdb_sema_stats_t *ssp = SEMA_STATS(sp, udp);
	hrtime_t begin_sleep = 0;
	uint_t count;
	int error = 0;

	/*
	 * All variations of sema_wait() are cancellation points.
	 */
	_cancelon();

	if (ssp)
		tdb_incr(ssp->sema_wait);

	self->ul_sp = stkptr();
	self->ul_wchan = lsp;
	if (__td_event_report(self, TD_SLEEP, udp)) {
		self->ul_td_evbuf.eventnum = TD_SLEEP;
		self->ul_td_evbuf.eventdata = lsp;
		tdb_event(TD_SLEEP, udp);
	}
	/* just a guess, but it looks like we will sleep */
	if (ssp && lsp->count == 0) {
		begin_sleep = gethrtime();
		if (lsp->count == 0)	/* still looks like sleep */
			tdb_incr(ssp->sema_wait_sleep);
		else			/* we changed our mind */
			begin_sleep = 0;
	}

	if (lsp->type == USYNC_PROCESS) {		/* kernel-level */
		set_parking_flag(self, 1);
		if (self->ul_cursig != 0 ||
		    (self->ul_cancelable && self->ul_cancel_pending))
			set_parking_flag(self, 0);
		/* the kernel always does FIFO queueing */
		error = ___lwp_sema_timedwait(lsp, tsp, 1);
		set_parking_flag(self, 0);
	} else if (!udp->uberflags.uf_mt &&		/* single threaded */
	    lsp->count != 0) {				/* and non-blocking */
		/*
		 * Since we are single-threaded, we don't need the
		 * protection of queue_lock().  However, we do need
		 * to block signals while modifying the count.
		 */
		sigoff(self);
		lsp->count--;
		sigon(self);
	} else {				/* multithreaded or blocking */
		queue_head_t *qp;
		ulwp_t *ulwp;
		lwpid_t lwpid = 0;

		qp = queue_lock(lsp, CV);
		while (error == 0 && lsp->count == 0) {
			/*
			 * SUSV3 requires FIFO queueing for semaphores,
			 * at least for SCHED_FIFO and SCHED_RR scheduling.
			 */
			enqueue(qp, self, 1);
			lsp->sema_waiters = 1;
			set_parking_flag(self, 1);
			queue_unlock(qp);
			/*
			 * We may have received SIGCANCEL before we
			 * called queue_lock().  If so and we are
			 * cancelable we should return EINTR.
			 */
			if (self->ul_cursig != 0 ||
			    (self->ul_cancelable && self->ul_cancel_pending))
				set_parking_flag(self, 0);
			error = __lwp_park(tsp, 0);
			set_parking_flag(self, 0);
			qp = queue_lock(lsp, CV);
			if (self->ul_sleepq)	/* timeout or spurious wakeup */
				lsp->sema_waiters = dequeue_self(qp);
		}
		if (error == 0)
			lsp->count--;
		if (lsp->count != 0 && lsp->sema_waiters) {
			int more;
			if ((ulwp = dequeue(qp, &more)) != NULL) {
				no_preempt(self);
				lwpid = ulwp->ul_lwpid;
			}
			lsp->sema_waiters = more;
		}
		queue_unlock(qp);
		if (lwpid) {
			(void) __lwp_unpark(lwpid);
			preempt(self);
		}
	}

	self->ul_wchan = NULL;
	self->ul_sp = 0;
	if (ssp) {
		if (error == 0) {
			/* we just decremented the count */
			count = lsp->count;
			if (ssp->sema_min_count > count)
				ssp->sema_min_count = count;
		}
		if (begin_sleep)
			ssp->sema_wait_sleep_time += gethrtime() - begin_sleep;
	}

	if (error == EINTR)
		_canceloff();
	else
		_canceloff_nocancel();
	return (error);
}

#pragma weak sema_wait = _sema_wait
int
_sema_wait(sema_t *sp)
{
	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
	return (sema_wait_impl(sp, NULL));
}

#pragma weak sema_reltimedwait = _sema_reltimedwait
int
_sema_reltimedwait(sema_t *sp, timespec_t *reltime)
{
	timespec_t tslocal = *reltime;

	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
	return (sema_wait_impl(sp, &tslocal));
}

#pragma weak sema_timedwait = _sema_timedwait
int
_sema_timedwait(sema_t *sp, timespec_t *abstime)
{
	timespec_t tslocal;

	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);
	abstime_to_reltime(CLOCK_REALTIME, abstime, &tslocal);
	return (sema_wait_impl(sp, &tslocal));
}

#pragma weak sema_trywait = _sema_trywait
int
_sema_trywait(sema_t *sp)
{
	lwp_sema_t *lsp = (lwp_sema_t *)sp;
	ulwp_t *self = curthread;
	uberdata_t *udp = self->ul_uberdata;
	tdb_sema_stats_t *ssp = SEMA_STATS(sp, udp);
	uint_t count;
	int error = 0;

	ASSERT(!curthread->ul_critical || curthread->ul_bindflags);

	if (ssp)
		tdb_incr(ssp->sema_trywait);

	if (lsp->type == USYNC_PROCESS) {	/* kernel-level */
		error = __lwp_sema_trywait(lsp);
	} else if (!udp->uberflags.uf_mt) {	/* single threaded */
		sigoff(self);
		if (lsp->count == 0)
			error = EBUSY;
		else
			lsp->count--;
		sigon(self);
	} else {				/* multithreaded */
		queue_head_t *qp;
		ulwp_t *ulwp;
		lwpid_t lwpid = 0;

		qp = queue_lock(lsp, CV);
		if (lsp->count == 0)
			error = EBUSY;
		else if (--lsp->count != 0 && lsp->sema_waiters) {
			int more;
			if ((ulwp = dequeue(qp, &more)) != NULL) {
				no_preempt(self);
				lwpid = ulwp->ul_lwpid;
			}
			lsp->sema_waiters = more;
		}
		queue_unlock(qp);
		if (lwpid) {
			(void) __lwp_unpark(lwpid);
			preempt(self);
		}
	}

	if (error == 0) {
		if (ssp) {
			/* we just decremented the count */
			count = lsp->count;
			if (ssp->sema_min_count > count)
				ssp->sema_min_count = count;
		}
	} else {
		if (ssp)
			tdb_incr(ssp->sema_trywait_fail);
		if (__td_event_report(self, TD_LOCK_TRY, udp)) {
			self->ul_td_evbuf.eventnum = TD_LOCK_TRY;
			tdb_event(TD_LOCK_TRY, udp);
		}
	}

	return (error);
}

#pragma weak sema_post = _sema_post
int
_sema_post(sema_t *sp)
{
	lwp_sema_t *lsp = (lwp_sema_t *)sp;
	ulwp_t *self = curthread;
	uberdata_t *udp = self->ul_uberdata;
	tdb_sema_stats_t *ssp = SEMA_STATS(sp, udp);
	uint_t count;
	int error = 0;

	if (ssp)
		tdb_incr(ssp->sema_post);
	if (_semvaluemax == 0)
		_semvaluemax = (uint32_t)_sysconf(_SC_SEM_VALUE_MAX);

	if (lsp->type == USYNC_PROCESS) {	/* kernel-level */
		error = __lwp_sema_post(lsp);
	} else if (!udp->uberflags.uf_mt) {	/* single threaded */
		sigoff(self);
		if (lsp->count >= _semvaluemax)
			error = EOVERFLOW;
		else
			lsp->count++;
		sigon(self);
	} else {				/* multithreaded */
		queue_head_t *qp;
		ulwp_t *ulwp;
		lwpid_t lwpid = 0;

		qp = queue_lock(lsp, CV);
		if (lsp->count >= _semvaluemax)
			error = EOVERFLOW;
		else if (lsp->count++ == 0 && lsp->sema_waiters) {
			int more;
			if ((ulwp = dequeue(qp, &more)) != NULL) {
				no_preempt(self);
				lwpid = ulwp->ul_lwpid;
			}
			lsp->sema_waiters = more;
		}
		queue_unlock(qp);
		if (lwpid) {
			(void) __lwp_unpark(lwpid);
			preempt(self);
		}
	}

	if (error == 0) {
		if (ssp) {
			/* we just incremented the count */
			count = lsp->count;
			if (ssp->sema_max_count < count)
				ssp->sema_max_count = count;
		}
	}

	return (error);
}