/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: sema.c 1.24 90/10/18 $"

/*
 * sema.c
 *	C Version of Semaphore routines.
 *
 * i386 version.
 */

/* $Log:	sema.c,v $
 */

#include "sys/signal.h"
#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/user.h"
#include "sys/timer.h"
#include "sys/proc.h"

#include "sys/intctl.h"
#include "sys/gate.h"

/*
 * DEBUG and MFG kernels enable "logging".
 * In this implementation, "logging" stores pid of process that last did a
 * "p_sema" in the semaphore's s_flags field.  This is only 8-bits, so it
 * can be ambiguous (but typically useful enough).
 *
 * Performance monitoring counters are accessed behind the semaphore gate.
 */

#include "sys/vmmeter.h"

/*
 * init_sema()
 *	init the semaphore.
 *
 * This is now a macro in mc_mutex.h
 */

/*
 * p_sema()
 *	P a semaphore.
 *
 * If priority is greater than PZERO signals may wake up the sleeping
 * process.
 *
 * If pri <= PZERO, then the SNWAKE bit in p_flag will be set
 * indicating no disturbance during sleep.
 * If pri > PZERO and signal pending, then longjmp out unless PCATCH is set.
 * If PCATCH is set then return PCATCH.
 * If there is no signal to process, but a STOP signal was processed
 * by issig, return as if the process was V'ed. This is because the
 * STOP can last a very long time and the whole world may have changed.
 * Therefore, the caller must recheck the situation again and call
 * p_sema again if appropriate.
 * Otherwise, decrement the count. If count >= 0, return.
 * Otherwise, put self on semaphore queue, switch away, and sleep
 * waiting for somebody to v_sema().
 *
 * Returns at spl0.
 */

int
p_sema(sema, pri)
	register sema_t *sema;
	int pri;
{
	register struct	proc *p;
	register	tmpdisp;
	int		sigret;

	tmpdisp = pri & PMASK;
	if (tmpdisp <= PZERO) {
		/*
		 * If can maybe get it, go for it.  If not, need to lock
		 * proc-state before lock sema gate, since gate only masks
		 * interrupt at processor, not SLIC, and when release gate
		 * could take queued interrupt, then deadlock.
		 *
		 * This optimizes case where typically get the sema (eg,
		 * buf-cache hit).
		 */
		if (sema->s_count > 0) {
			P_GATE(sema->s_gate);
			if (sema->s_count > 0) {
				--sema->s_count;
				sema->s_semstat.ss_visit++;
#ifdef	SEMLOG
				sema->s_flags = u.u_procp->p_pid;
#endif	/* SEMLOG  */
				V_GATE(sema->s_gate);
				return(0);
			}
			V_GATE(sema->s_gate);	/* lost race */
		}
		/*
		 * Sema wasn't avail or lost race.  Grab proc-state, then
		 * go for sema again.  Need to handle race with a V().
		 */
		p = u.u_procp;
		(void) p_lock(&p->p_state, SPLHI);
		VOID_P_GATE(sema->s_gate);
		sema->s_semstat.ss_visit++;
		if (--sema->s_count >= 0) {		/* raced with a V() */
#ifdef	SEMLOG
			sema->s_flags = p->p_pid;
#endif	/* SEMLOG  */
			V_GATE(sema->s_gate);
			v_lock(&p->p_state, SPL0);
			return(0);
		}
		/*
		 * Sema not available.
		 * mark as not signalable.
		 */
		p->p_flag |= SNWAKE;
	} else {
		/*
		 * Pri > PZERO
		 */
		p = u.u_procp;
		(void) p_lock(&p->p_state, SPLHI);
		/*
		 * mark as signalable.
		 */
		p->p_flag &= ~SNWAKE;
		if (QUEUEDSIG(p) && (sigret = issig((lock_t *)NULL)) != NOSIG) {
			p->p_flag |= SIGWOKE;
			v_lock(&p->p_state, SPL0);
			/*
			 * If the process has been stopped - return.
			 * Since the whole world may have changed while
			 * the process was stopped. The caller is
			 * assumed to tolerate this.
			 */
			if (sigret == STOPPED)
				return(STOPPED);
			if (pri & PCATCH)
				return(PCATCH);
			longjmp(&u.u_qsave);
			/* NOTREACHED */
		}

		VOID_P_GATE(sema->s_gate);
		sema->s_semstat.ss_visit++;
		if (--sema->s_count >= 0) {
#ifdef	SEMLOG
			sema->s_flags = p->p_pid;
#endif	/* SEMLOG  */
			V_GATE(sema->s_gate);
			v_lock(&p->p_state, SPL0);
			return(0);
		}
	}

	/*
	 * Semaphore unavailable, so go to sleep.
	 */

	insque(p, sema->s_tail);
	sema->s_semstat.ss_block++;
	sema->s_semstat.ss_totqlen += -sema->s_count;
	V_GATE(sema->s_gate);			/* proc-state still locked */

	p->p_stat = SSLEEP;
	p->p_wchan = sema;
	p->p_slptime = 0;
	p->p_pri = tmpdisp;
	u.u_pst.ps_volcsw++;			/* voluntary context switch */
	swtch(p);				/* returns at spl0 */

	if (p->p_flag & SIGWOKE) {
		(void) p_lock(&p->p_state, SPLHI);
		if ((sigret = issig((lock_t *)NULL)) != NOSIG) {
			v_lock(&p->p_state, SPL0);

			/*
			 * Check for stop signal
			 */
			if (sigret == STOPPED)
				return(STOPPED);

			/*
			 * If they're ready to handle interruptions, just
			 * return indication.
			 */
			if (pri & PCATCH)
				return(PCATCH);
			longjmp(&u.u_qsave);
			/* NOTREACHED */
		}

		/*
		 * The only way we can get here is by receiving a job-control
		 * stop signal that was subsequently removed by a SIGCONT
		 * signal, or the process is/was being traced via ptrace(2) or
		 * mptrace(2), and the signal was removed.  In either case,
		 * we were in the SSTOP state, and did NOT get the lock.
		 */
		v_lock(&p->p_state, SPL0);
		return(STOPPED);
	}
#ifdef	SEMLOG
	sema->s_flags = p->p_pid;
#endif	/* SEMLOG  */
	return(0);
}

/*
 * cp_sema( &sema )
 *	Conditionally P a semaphore.
 *
 * If the count is positive, decrement and return TRUE; else return
 * FALSE.
 */

bool_t
cp_sema(sema)
	register sema_t *sema;
{

	/*
	 * First check without going for gate.
	 */

	if (!sema_avail(sema))
		return(0);

	P_GATE(sema->s_gate);
	if (sema->s_count > 0) {
		--sema->s_count;
		sema->s_semstat.ss_visit++;
#ifdef	SEMLOG
		/*
		 * cp_sema() can be called from interrupt routine on idle
		 * processor -- careful about reading thru u.u_procp.
		 */
		sema->s_flags = (l.noproc ? 0 : u.u_procp->p_pid);
#endif	/* SEMLOG  */
		V_GATE(sema->s_gate);
		return(1);
	} else {
		V_GATE(sema->s_gate);
		return(0);
	}
}

/*
 * v_sema( &sema )
 *	V a semaphore.
 *
 * Bump the count.  If there are waiters, wake up the first.
 *
 * Coordinates with force_v_sema() to insure only one wakeup of a
 * give process occurs.
 */

void
v_sema(sema)
	register sema_t *sema;
{
	register struct proc *p;
	spl_t	s_ipl;

	P_GATE(sema->s_gate);

	/*
	 * Bump the count.  > 0 ==> return.
	 */

	if (++sema->s_count > 0) {
		V_GATE(sema->s_gate);
		return;
	}

	/*
	 * Pull 1st guy off queue, release gate, and make
	 * process ready to run.
	 *
	 * Ok to v_gate() here once process is dequeued IFF we
	 * zap proc's links to tell force_v_sema() that the process is
	 * removed (ie, avoid race with psignal()).  The process
	 * can't be dispatched since it's not on run-Q.  Thus, only
	 * fuss with the process is swapping daemon who
	 * also locks process-state.
	 *
	 * Reset SIGWOKE flag in process-flags to tell p_sema()
	 * a v_sema() woke the process up, not a force_v_sema().
	 *
	 * Note that once setrq(), the process *can* dispatch.
	 */

	p = sema->s_head;
	remque(p);
	p->p_link = NULL;		/* for interlock with force_v_sema() */
	V_GATE(sema->s_gate);

	s_ipl = p_lock(&p->p_state, SPLHI);

	p->p_flag &= ~SIGWOKE;		/* v_sema(), not force_v_sema() */
	p->p_stat = SRUN;
	p->p_wchan = (sema_t *)NULL;
	if (p->p_flag & SLOAD) {
		VOID_P_GATE(G_RUNQ);
		setrq(p);
		V_GATE(G_RUNQ);
		v_lock(&p->p_state, s_ipl);
	} else {
		v_lock(&p->p_state, s_ipl);
		L_INCR(&procstate.ps_swinq);
		v_sema(&runout);
	}
}

/*
 * force_v_sema(procptr)
 *	Yank a (sleeping) process off a semaphore Q and make it
 *	runnable.
 *
 * Coordinates with v_sema() to insure only one wakes up the process.
 * Set SIGWOKE to indicate to p_sema() that a force_v_sema() did
 * the wakeup.
 *
 * Assumes caller locked process state.  Process state remains locked.
 * Note: there is no consistency check that the process is actually
 * on the semaphores queue here.
 */

void
force_v_sema(p)
	register struct proc *p;
{
	register sema_t *sema;

	sema = p->p_wchan;

	P_GATE(sema->s_gate);

	/*
	 * If v_sema() got to the process first, let it go;
	 * v_sema() will make it run.
	 */

	if (p->p_link == NULL) {
		V_GATE(sema->s_gate);
		return;
	}

	remque(p);			/* yank it out of double-link list */
	++sema->s_count;		/* well, we are waking it up! */
	V_GATE(sema->s_gate);

	p->p_flag |= SIGWOKE;		/* force_v_sema(), not v_sema() */
	p->p_stat = SRUN;
	p->p_wchan = (sema_t *)NULL;
	if(p->p_flag & SLOAD) {
		P_GATE(G_RUNQ);
		setrq(p);
		V_GATE(G_RUNQ);
	} else {
		L_INCR(&procstate.ps_swinq);
		v_sema(&runout);
	}
}

/*
 * cv_sema(sema)
 *	Conditionally V a semaphore.
 *
 * Return a boolean saying if a process was
 * awoken or not (ie, for vall_sema()).
 */

bool_t
cv_sema(sema)
	register sema_t *sema;
{
	register struct proc *p;
	spl_t	s_ipl;		/* saved ipl */

	/*
	 * first check without going for gate.
	 */

	if (!blocked_sema(sema))
		return(0);

	P_GATE(sema->s_gate);

	if (sema->s_count >= 0) {
		V_GATE(sema->s_gate);		/* lost race */
		return(0);
	}

	++sema->s_count;
	p = sema->s_head;
	remque(p);
	p->p_link = NULL;		/* for interlock with force_v_sema() */
	V_GATE(sema->s_gate);

	s_ipl = p_lock(&p->p_state, SPLHI);

	p->p_flag &= ~SIGWOKE;		/* v_sema(), not force_v_sema() */
	p->p_stat = SRUN;
	p->p_wchan = (sema_t *)NULL;
	if(p->p_flag & SLOAD) {
		VOID_P_GATE(G_RUNQ);
		setrq(p);
		V_GATE(G_RUNQ);
		v_lock(&p->p_state, s_ipl);
	} else {
		v_lock(&p->p_state, s_ipl);
		L_INCR(&procstate.ps_swinq);
		v_sema(&runout);
	}

	return(1);
}

/*
 * vall_sema(sema)
 *	Wake up all processes sleeping on the semaphore.
 *
 * The "qlen" variable avoids a problem of using a vanilla vall();
 * i.e. don't get in a loop waking up processes, which
 * dispatch on another processor and *re-block* on the sema, only
 * to be awoken again because the vall() didn't finish yet!
 *
 * Note that this open "windows" where another P() or V() could
 * occur.  The caller is assumed to tolerate this.
 */

void
vall_sema(sema)
	sema_t	*sema;
{
	register int qlen;

	qlen = sema_count(sema);
	while (qlen++ < 0 && cv_sema(sema))
		continue;
}

/*
 * p_sema_v_lock()
 *	Get self queued on the semaphore (if must sleep) and
 *	*then* v_lock() the lock.  If not going to sleep, just
 *	v_lock() the lock before returning.
 *
 * Just like p_sema(), but v_lock(held_lock,spl) just
 * before swtch() or return (depending on if we're going
 * to sleep). Also, the same STOP semantics as p_sema are enforced.
 * If issig is going to process a STOP, then it releases the "held_lock"
 *
 * Returns at spl0.
 */

int
p_sema_v_lock(sema, pri, lock)
	register sema_t	*sema;
	int	pri;
	lock_t	*lock;
{
	register struct	proc *p;
	register	tmpdisp;
	spl_t		s;
	int		sigret;

	tmpdisp = pri & PMASK;
	if (tmpdisp <= PZERO) {
		/*
		 * If can maybe get it, go for it.  If not, need to lock
		 * proc-state before lock sema gate, since gate only masks
		 * interrupt at processor, not SLIC, and when release gate
		 * could take queued interrupt, then deadlock.
		 *
		 * This optimizes case where typically get the sema (eg,
		 * buf-cache hit).
		 */
		if (sema->s_count > 0) {
			P_GATE(sema->s_gate);
			if (sema->s_count > 0) {
				--sema->s_count;
				sema->s_semstat.ss_visit++;
#ifdef	SEMLOG
				sema->s_flags = u.u_procp->p_pid;
#endif	/* SEMLOG  */
				V_GATE(sema->s_gate);
				v_lock(lock, SPL0);
				return(0);
			}
			V_GATE(sema->s_gate);	/* lost race */
		}
		/*
		 * Sema wasn't avail or lost race.  Grab proc-state, then
		 * go for sema again.  Need to handle race with a V().
		 */
		p = u.u_procp;
		(void) p_lock(&p->p_state, SPLHI);
		VOID_P_GATE(sema->s_gate);
		sema->s_semstat.ss_visit++;
		if (--sema->s_count >= 0) {		/* raced with a V() */
#ifdef	SEMLOG
			sema->s_flags = p->p_pid;
#endif	/* SEMLOG  */
			V_GATE(sema->s_gate);
			v_lock(&p->p_state, SPLHI);
			v_lock(lock, SPL0);
			return(0);
		}
		/*
		 * Sema not available.
		 * mark as not signalable.
		 */
		p->p_flag |= SNWAKE;
	} else {
		/*
		 * Pri > PZERO
		 */
		p = u.u_procp;
		/*
		 * mark as signalable.
		 */
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag &= ~SNWAKE;
		if (QUEUEDSIG(p) && (sigret = issig(lock)) != NOSIG) {
			p->p_flag |= SIGWOKE;
			v_lock(&p->p_state, s);
			/*
			 * If the process has been stopped - return.
			 * Since the whole world may have changed while
			 * the process was stopped. The caller is
			 * assumed to tolerate this.  Since stopped, return
			 * at spl0 (in case held lock, need to avoid splx
			 * nesting problems).
			 */
			if (sigret == STOPPED) {
				(void) spl0();
				return(STOPPED);
			}
			/*
			 * If the process hasn't been stopped,
			 * then release the lock before longjmp'n
			 * If STPGOTSIG the held_lock has been
			 * released by issig().
			 */
			if (sigret == GOTSIG)
				v_lock(lock, SPL0);
			(void) spl0();
			if (pri & PCATCH) {
				return(PCATCH);
			}
			longjmp(&u.u_qsave);
			/* NOTREACHED */
		}

		VOID_P_GATE(sema->s_gate);
		sema->s_semstat.ss_visit++;
		if (--sema->s_count >= 0) {
#ifdef	SEMLOG
			sema->s_flags = p->p_pid;
#endif	/* SEMLOG  */
			V_GATE(sema->s_gate);
			v_lock(&p->p_state, SPLHI);
			v_lock(lock, SPL0);
			return(0);
		}
	}

	/*
	 * Semaphore unavailable, so go to sleep.
	 */

	insque(p, sema->s_tail);
	sema->s_semstat.ss_block++;
	sema->s_semstat.ss_totqlen += -sema->s_count;
	V_GATE(sema->s_gate);			/* proc-state still locked */
	v_lock(lock, SPLHI);			/* for v_lock() */

	p->p_stat = SSLEEP;
	p->p_wchan = sema;
	p->p_slptime = 0;
	p->p_pri = tmpdisp;
	u.u_pst.ps_volcsw++;			/* voluntary context switch */
	swtch(p);				/* returns at spl0 */

	if (p->p_flag & SIGWOKE) {
		(void) p_lock(&p->p_state, SPLHI);
		if ((sigret = issig((lock_t *)NULL)) != NOSIG) {
			v_lock(&p->p_state, SPL0);

			/*
			 * Check for stop signal
			 */
			if (sigret == STOPPED)
				return(STOPPED);

			/*
			 * If they're ready to handle interruptions, just
			 * return indication.
			 */
			if (pri & PCATCH)
				return(PCATCH);
			longjmp(&u.u_qsave);
			/* NOTREACHED */
		}

		/*
		 * The only way we can get here is by receiving a job-control
		 * stop signal that was subsequently removed by a SIGCONT
		 * signal, or the process is/was being traced via ptrace(2) or
		 * mptrace(2), and the signal was removed.  In either case,
		 * we were in the SSTOP state, and did NOT get the lock.
		 */
		v_lock(&p->p_state, SPL0);
		return (STOPPED);
	}
#ifdef	SEMLOG
	sema->s_flags = p->p_pid;
#endif	/* SEMLOG  */
	return(0);
}


/*
 * v_event( &sema )
 *	V an event semaphore.
 *
 * Bump the count.  If there are waiters, wake up the first.
 * Dont let the count go above 1 - just let p_event know somthing
 * happened not how many.
 *
 * Coordinates with force_v_sema() to insure only one wakeup of a
 * given process occurs.
 * (Only one process ought to be waiting).
 */

void
v_event(sema)
	register sema_t *sema;
{
	register struct proc *p;
	spl_t	s_ipl;

	P_GATE(sema->s_gate);

	/*
	 * Bump the count.  > 0 ==> return.
	 */

	if (++sema->s_count > 0) {
		sema->s_count = 1;	/* nail it down */
		V_GATE(sema->s_gate);
		return;
	}

	/*
	 * Pull 1st guy off queue, release gate, and make
	 * process ready to run.
	 *
	 * Ok to v_gate() here once process is dequeued IFF we
	 * zap proc's links to tell force_v_sema() that the process is
	 * removed (ie, avoid race with psignal()).  The process
	 * can't be dispatched since it's not on run-Q.  Thus, only
	 * fuss with the process is swapping daemon who
	 * also locks process-state.
	 *
	 * Reset SIGWOKE flag in process-flags to tell p_sema()
	 * a v_sema() woke the process up, not a force_v_sema().
	 *
	 * Note that once setrq(), the process *can* dispatch.
	 */

	p = sema->s_head;
	remque(p);
	p->p_link = NULL;		/* for interlock with force_v_sema() */
	V_GATE(sema->s_gate);

	s_ipl = p_lock(&p->p_state, SPLHI);

	p->p_flag &= ~SIGWOKE;		/* v_sema(), not force_v_sema() */
	p->p_stat = SRUN;
	p->p_wchan = (sema_t *)NULL;
	if (p->p_flag & SLOAD) {
		VOID_P_GATE(G_RUNQ);
		setrq(p);
		V_GATE(G_RUNQ);
		v_lock(&p->p_state, s_ipl);
	} else {
		v_lock(&p->p_state, s_ipl);
		L_INCR(&procstate.ps_swinq);
		v_sema(&runout);
	}
}
