/* $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.
 */

/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: strpipe.c 1.12 90/07/14 $"

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

/*
 * SP - Stream "pipe" device.  Any two minor devices may
 * be opened and connected to each other so that each user
 * is at the end of a single stream.  This provides a full
 * duplex communications path and allows for the passing
 * of file descriptors as well.  
 *
 * WARNING - an interprocess stream does not have the same
 * 	     semantics as a pipe, and this does not replace
 *	     pipes.
 */

#include "sys/types.h"
#include "sys/systm.h"
#include "sys/param.h"
#include "sys/sysmacros.h"
#include "sys/errno.h"
#include "sys/stropts.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/intctl.h"
#include "sys/strpipe.h"
#include "sys/strid.h"
#include "sys/devsw.h"
#include "sys/cmn_err.h"
#include "sys/debug.h"
#include "sys/mutex.h"


#ifdef DEBUG
int sp_debug = 0;
#endif

static struct module_info strpipem_info = {
/* module id	module name	min pt sz	max pkt size	hiwat 	lowat */
    STRID_SPX,	   "strpipe",	    0,		    INFPSZ,	 5120,	 1024 };

static int strpipeopen(), strpipeclose(), strpipersrv(), strpipewsrv();

static struct qinit strpiperinit = {
/* put	   server	open		close	admin	 minfo	      mstat */
   NULL, strpipersrv, strpipeopen, strpipeclose, NULL, &strpipem_info, NULL };

static struct qinit strpipewinit = {
/* put	   server	open	close	admin	 minfo	        mstat */
   putq, strpipewsrv,    NULL,   NULL,   NULL,  &strpipem_info, NULL};

struct streamtab strpipeinfo = { &strpiperinit, &strpipewinit, NULL, NULL };

lock_t	strpipe_lock;

/*
 * Each stream pipe head has a strpipe structure which contains
 * pointers to that pipe's read queue, the read queue of the other
 * stream pipe it's connected to (if it is connected), and a pointer
 * to a "synchronization node" structure.  The synchronization node
 * contains a lock for synchronizing activities between the two
 * streams.  This lock, along with the global lock "strpipe_lock"
 * coordinate the construction and descruction of a pair of connected
 * stream pipes.  Rules for usage area:
 *
 *	If both strpipe_lock and a synchronization lock must be held,
 *	assert the strpipe_lock first (deadlock avoidance).
 *
 *	In order for a pipe to disconnect itself (close) from a pair, it must
 *	first hold the synchronization node lock for that pair.
 *
 *	A pipe's service procedure must hold the synchronization lock
 *	before doing a put onto the other queue.  This is to prevent
 *	the other queue from disconnecting during the put.
 *
 *	When a pipe does a disconnect (close), it must assert the
 *	pipe_lock to avoid races with other connects and disconnects. 
 *
 *	The disconnecting pipe end must assert
 *	the synchronization node lock to avoid races with puts on the
 *	other queue.  (Since this is last close for this end of the
 *	pipe, there can be no puts on this end of the queue).  The
 *	first to disconnect from a pair can only disconnect itself
 *	from  the other pipe and the synchronization node.  It cannot
 *	free the synchronization node because the other pipe end may
 *	have put's busy-wait testing that node trying to do a put.
 *
 *	When the second pipe end of a pair finally closes, IT can free
 *	the synchronization node as well.
 *
 *	It NOT possible for a pipe end which was previously connected
 *	and has been disconnected to be involved in another pipe
 *	connection until after it has been closed.  The driver is
 *	coded to DISALLOW this.
 */


extern struct devops strpipe_devops;

/*
 * Called from main at boot time.
 */
/*ARGSUSED*/
void
strpipe_init(ndevs)
	int ndevs;
{
        register int i;
        struct dev *dp;
	extern int strpipe_c_major;

	init_lock(&strpipe_lock);
	for (i = 0; i < strpipe_cnt; i++) {
		init_lock(&strpipe_sy[i].sp_lock);
		/* add dev entry for each stream pipe end*/

		dp = (struct dev *)calloc(sizeof(struct dev));
		dp->d_dev = makedev(strpipe_c_major, i);
		dp->d_ops = &strpipe_devops;
		if (devsw_add(dp) != 0) {
			cmn_err(CE_WARN,
			"strpipe: failure to add dev minor %d to devsw\n",
			i);
			/*
			*+ The kernel encountered a problem when 
			*+ it attempted to add a stream
			*+ pipe dev to the devsw data structure.  This is
			*+ due to a driver that is adding a dev to
			*+ devsw; the major/minor number for the dev
			*+ already belongs to another dev.
			*/
		}
	}
	/*
	 * Add one clone for the pipe driver
	 */
	if (clonedev_add(makedev(strpipe_c_major, 0))) {
		cmn_err(CE_WARN,
			"pty: failure to add strpipe clone dev to devsw\n");
		/*
		 *+ The kernel encountered a problem when it 
		 *+ attempted to add a stream pipe
		 *+ dev to the clone devsw data structure.  This is
		 *+ due to a driver that is adding a dev to
		 *+ devsw; the major/minor number for the dev
		 *+ already belongs to another dev.
		 */
	}
}

/*
 * assert strpipe_lock while searching list of available slots, etc.
 *	This is to avoid races with other opens also searching for
 *	available strpipe structures.
 */
/*ARGSUSED*/
static
strpipeopen(q, dev, flag, sflag)
register queue_t *q;
register int dev;
{
	register struct strpipe *spp;
	spl_t	ipl;

	dev = minor(dev);
	ipl = p_lock(&strpipe_lock, SPL_SPX);
	switch (sflag) {
	case MODOPEN:
		dev = (struct strpipe *)q->q_ptr - strpipe_sp;
#ifdef DEBUG
		if (sp_debug) printf("strpipe modopen dev %x\n", dev);
#endif
		break;

	case CLONEOPEN:
		for (dev=0, spp = strpipe_sp;
			((dev<strpipe_cnt) && spp->sp_rdq); dev++, spp++);
#ifdef DEBUG
		if (sp_debug) printf("strpipe cloneopen dev %x\n", dev);
#endif
		break;
	default:
#ifdef DEBUG
		if (sp_debug) printf("strpipe devopen dev %x sflag %x \n",
			dev, sflag);
#endif
		break;
	}
	if ((dev < 0) || (dev >= strpipe_cnt))  {
		v_lock(&strpipe_lock, ipl);
#ifdef DEBUG
		if (sp_debug) printf("returning OPENFAIL\n");
#endif
		return(OPENFAIL);
	}
	spp = &strpipe_sp[dev];
	if (!spp->sp_rdq) {
		spp->sp_rdq = q;
		q->q_ptr = WR(q)->q_ptr = (caddr_t)spp;
	}
	v_lock(&strpipe_lock, ipl);
	return(dev);
}

/*
 * There are a couple senarios here.
 *	1. We may be closing a pipe end which has never been
 *	connected.
 *
 *	2. We may be closing a pipe end which was previously connected
 *	but is no longer.
 *
 *	3. We may be closing a pipe end which is connected, and we are
 *	now disconnecting it.
 *
 *	in all cases, we must be avoid races with other pipe ends
 *	which may be trying to connect with the end we're closing.
 *	Use strpipe_lock to exclude these races.
 *
 *	In case three, we must also be concerned about races with
 *	the other pipe end doing put's onto this end, or also trying
 *	to close it's end of the pipe.
 */
static
strpipeclose(q)
	queue_t *q;
{
	struct strpipe *spp, *osp;
	queue_t *orq;
	struct sp_synch	*sy;
	mblk_t *mp;
	spl_t	ipl;


	/*
	 * Need to mess with the OTHER queue??
	 *	flush?
	 *	disable?
	 *
	 *	Note, can't hold lock during disable/flush due to
	 *	sleeps in flushq/disable_procs.
	 */
	flushq(WR(q), FLUSHALL);
	disable_procs(q);
	spp = (struct strpipe *)q->q_ptr;
#ifdef DEBUG
		if (sp_debug) printf("strpipe close\n");
#endif

	ipl = p_lock(&strpipe_lock, SPL_SPX);
	sy = spp->sp_master;
	if (sy != NULL) {
		(void)p_lock(&sy->sp_lock, SPL_SPX);
	}
	spp->sp_master = NULL;
	spp->sp_rdq = NULL;
	q->q_ptr = WR(q)->q_ptr = NULL;
	if ((orq = spp->sp_ordq) && (orq != q)) {
		/*
		 * there is another end connected.
		 *	disconnect it.  We're know we're
		 *	already holding the synchronization node
		 *	locked.  But we can't free that node due
		 *	to the other end's reference to it.
		 *
		 *	Send a hangup signal to the other end.
		 */
		osp = (struct strpipe *)orq->q_ptr;
		osp->sp_ordq = NULL;
		spp->sp_ordq = NULL;
		if (mp = allocb(0, BPRI_MED)) { 
			mp->b_datap->db_type = M_HANGUP;
			putnext(orq, mp);
		} else {
			cmn_err(CE_WARN, "SP: WARNING - close could not allocate block\n");
			/*
			 *+ The streams pipe driver couldn't allocate
			 *+ a streams buffer to send the hangup
			 *+ message.
			 */
		}
		v_lock(&sy->sp_lock, SPL_SPX);
	} else {
		/*
		 * There is NOT another end.  (or the other end is
		 * "us"  i.e. a loop-back).  If we have a
		 * synchronization node, we must release it's lock
		 * and free it.
		 */
		spp->sp_ordq = NULL;
		if (sy != NULL) {
			v_lock(&sy->sp_lock, SPL_SPX);
			sy->sp_flag = 0;
		}
	}
	v_lock(&strpipe_lock, ipl);
}


/*
 * strpipewsrv()
 *
 *	stream pipe write server.
 *	if connected, pass messages on to the other stream pipe queue.
 *	If not connected, deal with conection requests.
 *	If an improper message, send an error message back up the queue.
 */
static
strpipewsrv(q)
	queue_t *q;
{
	int btype;
	struct sp_synch *sy;
	struct strpipe *spp;
	spl_t	ipl;
	mblk_t *bp;

	spp = (struct strpipe *)(q->q_ptr);
	sy = spp->sp_master;

	while ((bp = getq(q)) != NULL) {
		switch (btype = bp->b_datap->db_type) {
	
		case M_IOCTL:
			/*
			 * fail all ioctl requests.
			 *	no locks need be asserted.
			 *	because the other end of pipe isn't
			 *	involved here.
			 */
			bp->b_datap->db_type = M_IOCNAK;
			qreply(q, bp);
			continue;
	
		case M_FLUSH:
			/*
			 * The meaning of read and write sides must be reversed
			 * for the destination stream head.
			 */
			if (sy != NULL)
				ipl = p_lock(&sy->sp_lock, SPL_SPX);
				
			/*
			 * If there's no other end connected,
			 *	just send the flush back up this end's
			 *	read queue.
			 *
			 *	observe: the test on sy == NULL is NOT
			 *	redundant with sp_ordq test.  It's
			 *	possible that between the test on sy
			 *	above, and the test on sp_ordq below
			 *	that another queue has done a connect
			 *	with us.
			 */
			if ((sy == NULL) || (spp->sp_ordq == NULL)) {
				if (sy != NULL)
					v_lock(&sy->sp_lock, ipl);
				*bp->b_rptr &= ~FLUSHW;
				if (*bp->b_rptr & FLUSHR)
					qreply(q, bp);
				continue;
			}
			/*
			 * at this point, we know we've got two queues in the 
			 * pair, and the connection is locked.
			 */
			ASSERT(sy != NULL,
				"strpipewsrv: null synch node in flush");
			/*
			 *+ A null synchronization node pointer was
			 *+ encountered in a connected stream pipe
			 *+ pair.  This is an inconsistent state and
			 *+ indicates a probable stream pipe driver
			 *+ bug.
			 */
			switch (*bp->b_rptr) {
			case FLUSHR: *bp->b_rptr = FLUSHW; break;
			case FLUSHW: *bp->b_rptr = FLUSHR; break;
			}
			if (!pcmsg(bp->b_datap->db_type) &&
				!canput(spp->sp_ordq)) {
				v_lock(&sy->sp_lock, ipl);
				putbq(q, bp);
				return;
			}
			putnext(spp->sp_ordq, bp);		
			v_lock(&sy->sp_lock, ipl);
			continue;
	
		default:
			if (sy != NULL)
				ipl = p_lock(&sy->sp_lock, SPL_SPX);
				
			/*
			 * If there's another end connected,
			 *	send the message to the other end's
			 *	read queue.
			 *
			 *	observe: the test on sy != NULL is NOT
			 *	redundant with sp_ordq test.  It's
			 *	possible that between the test on sy
			 *	above, and the test on sp_ordq below
			 *	that another queue has done a connect
			 *	with us.
			 */
			if ((sy != NULL) && (spp->sp_ordq != NULL)) {
				if (!pcmsg(bp->b_datap->db_type) &&
					!canput(spp->sp_ordq)) {
					v_lock(&sy->sp_lock, ipl);
					putbq(q, bp);
					return;
				}
#ifdef DEBUG
				if (sp_debug) printf("strpipewsrv: pass data\n");
#endif
				putnext(spp->sp_ordq, bp);
				v_lock(&sy->sp_lock, ipl);
				continue;
			} else if (btype == M_PROTO) {
				/*
				 * trying to do a connect.
				 */
				if (sy != NULL)
					v_lock(&sy->sp_lock, ipl);
				/*
				 * non-zero return indicates a
				 * there IS a connection now, and
				 * we should continue processing
				 * messages.
				 */
				if (sp_tryconnect(q, bp))
					continue;
			} else if (sy != NULL) {
				v_lock(&sy->sp_lock, ipl);
			}
			break;
		}
				
		/*
		 * The stream has not been connected yet.
		 */
#ifdef DEBUG
		if (sp_debug) printf("strpipewsrv: unconnected\n");
#endif
		bp->b_datap->db_type = M_ERROR;
		bp->b_wptr = bp->b_rptr = bp->b_datap->db_base;
		*bp->b_wptr++ = EIO;
		qreply(q, bp);
	}
}

/*
 * sp_alloc_sy()
 *	allocate a synchronization node to synchronize a pair of
 *	stream pipes.
 *
 *	Since there are as many synchronization nodes allocated as
 *	are stream pipe descriptors, it should be IMPOSSIBLE to ever
 *	run out of these structues.  Hence, the panic case below.
 *
 *	Should be called with the global strpipe_lock held.
 */
static struct sp_synch *
sp_alloc_sy()
{
	struct sp_synch *pi;

	for (pi = strpipe_sy; pi < &strpipe_sy[strpipe_cnt]; pi++)
		if ((pi->sp_flag & SP_BUSY) == 0) {
			pi->sp_flag = SP_BUSY;
			return(pi);
		}

	cmn_err(CE_PANIC, "sp_alloc_sy: out of synchronization nodes");
	/*
	 *+ While the kernel was trying to connect two streams pipes, 
	 *+ the array of
	 *+ synchronization nodes was exhausted.  This implies a
	 *+ software bug in the streams pipe driver.
	 */
	 /*NOTREACHED*/
}

/*
 * sp_tryconnect()
 *	try to connect the stream pipe "q" another stream pipe
 *	indicated by the contents of the message bp.
 *
 *	should be called with NO locks held.
 *
 *	will return zero if the connection "failed" (i.e., if on exit
 *	from this procedure, this head is NOT connected with another
 *	head).  Otherwise, the return value is non-zero.
 */
static int
sp_tryconnect(q, bp)
	queue_t *q;
	mblk_t *bp;
{
	spl_t	ipl;
	queue_t *oq;
	struct strpipe *osp;
	struct strpipe *spp;
	struct sp_synch *sy;
	int i;

#ifdef DEBUG
	if (sp_debug) printf("sp_tryconnect: entered\n");
#endif

	spp = (struct strpipe *)(q->q_ptr);
	oq = *((queue_t **)bp->b_rptr);

	ipl = p_lock(&strpipe_lock, SPL_SPX);

	/*
	 * must double-check that some other head hasn't connected
	 * with this one.  If so, put this message back on the queue
	 * and let it be processed again.
	 */
	if (spp->sp_ordq != NULL) {
		v_lock(&strpipe_lock, ipl);
#ifdef DEBUG
		if (sp_debug) printf("strpipersrv: connect race\n");
#endif
		putbq(q, bp);
		return(1);
	}

	/*
	 * since we're not connected to another stream pipe
	 *	If we already have a synchronization node, we
	 *	must refuse connect requests until we've been
	 *	closed.
	 */
	sy = spp->sp_master;
	if (sy != NULL) {
		v_lock(&strpipe_lock, ipl);
		return(0);
	}

	/*
	 * Check if the message is a reasonable connect request.
	 */
	if (bp->b_cont != 0) {
		v_lock(&strpipe_lock, ipl);
		return(0);
	}
	if ((bp->b_wptr - bp->b_rptr) != sizeof(queue_t *)) {
		v_lock(&strpipe_lock, ipl);
		return(0);
	}

	/*
	 * see if the pointer passed to us corresponds to another
	 * unconnected stream pipe head.  If so, try connecting with
	 * it.  If the other pipe head has been connected and
	 * disconnected, refuse to connect to it until it's done
	 * a close on itself.
	 */
	for (i=0, osp=strpipe_sp; ((i<strpipe_cnt) && (oq!=osp->sp_rdq));
		i++, osp++);
	if ((i==strpipe_cnt) || (osp->sp_ordq) || (osp->sp_master)) {
		v_lock(&strpipe_lock, ipl);
		return(0);
	}
#ifdef DEBUG
	if (sp_debug) printf("strpipersrv: doing connect\n");
#endif

	/*
	 * We've located another stream pipe we can connect with.
	 */
	sy = sp_alloc_sy();
	(void)p_lock(&sy->sp_lock, SPL_SPX);
	spp->sp_master = osp->sp_master = sy;

	/*
	 * interconnect the queue's.
	 */
	spp->sp_ordq = oq;
	osp->sp_ordq = RD(q);
	v_lock(&sy->sp_lock, SPL_SPX);
	v_lock(&strpipe_lock, ipl);
	freemsg(bp);
	return(1);
}



/*
 * should get involved only in back-enabling
 * queue's due to flow control
 */
static
strpipersrv(q)
register queue_t *q;
{
	struct sp_synch *sy;
	struct strpipe *spp;
	spl_t	ipl;

#ifdef DEBUG
	if (sp_debug) printf("strpipersrv: entered\n");
#endif
	spp = (struct strpipe *)(q->q_ptr);
	sy = spp->sp_master;
	if (sy != NULL)
		ipl = p_lock(&sy->sp_lock, SPL_SPX);
	if ((sy != NULL) && (spp->sp_ordq != NULL)) {
#ifdef DEBUG
		if (sp_debug) printf("strpipersrv: qenable\n");
#endif
		qenable(WR(spp->sp_ordq));
	}
	if (sy != NULL)
		v_lock(&sy->sp_lock, ipl);
	
}
