/* $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: str_q.c 1.22 90/02/13 $"

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


/*
 * STREAMS QUEUE MGT ROUTINES
 *                                                                        
 * This file contains routines which manipulate streams queues. 
 * The queue scheduling routines are in "stream.c".
 */



#include "sys/types.h"
#include "sys/param.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/sysmacros.h"
#include "sys/stropts.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/strstat.h"
#include "sys/conf.h"
#include "sys/var.h"
#include "sys/debug.h"


extern  queue_t *qpfreelist;
extern	ushort	rbsize[];
extern	ushort	bsize[];
extern	lock_t	qm_mutex;
extern	lock_t	svc_mutex;
extern	struct	strstat strst;

static queue_t zeroR =
  {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, QUSE|QREADR, 0, 0, 0, 0, 0};
static queue_t zeroW =
  {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, QUSE, 0, 0, 0, 0, 0};


/*  
 * Return the queue upstream from this one.
 * Stream assumed unlocked.
 */

queue_t *
backq(q)
	register queue_t *q;
{
	register spl_t s;

	ASSERT_DEBUG(q, "backq: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q = l_backq(q);
	v_lock(&q->q_str->sd_mutex, s);
	return(q);
}



/*
 * Same as getq.
 * Stream assumed locked.
 */

mblk_t *
l_getq(q)
	register queue_t *q;
{
	register mblk_t *bp;
	register mblk_t *tmp;

	if (!(bp = q->q_first)) {
		/* empty q */
		q->q_flag |= QWANTR;
	} else {
		if (!(q->q_first = bp->b_next))	{
			/* q is empty now, both first and last pointers nulled */
			q->q_last = NULL;
		} else {
			q->q_first->b_prev = NULL;
		}
		/* decrement q->count by weight of message dequeued  */
		for (tmp = bp; tmp; tmp = tmp->b_cont) {
			if (tmp->b_datap->db_class == NCLASS) {
				q->q_count -= (tmp->b_wptr - tmp->b_rptr);
			} else {
				q->q_count -= bsize[tmp->b_datap->db_class];
			}
		}

		if (q->q_count < q->q_hiwat) {
			q->q_flag &= ~QFULL;
		}
		q->q_flag &= ~QWANTR;
		bp->b_next = bp->b_prev = NULL;
	}

	if (q->q_count <= q->q_lowat && q->q_flag & QWANTW) {
		q->q_flag &= ~QWANTW;

		/* find nearest back queue with service proc */
		for (q = l_backq(q); q && !q->q_qinfo->qi_srvp; q = l_backq(q));

		/* enable it */
		if (q) {
			l_qenable(q);
		}
	}
	return(bp);
}


/*
 * Get a message off head of queue.
 *
 * If queue has no buffers then mark queue with QWANTR. (queue wants
 * to be read by someone when data becomes available)
 * 
 * If there is something to take off then do so.  If queue falls below
 * hi water mark turn off QFULL flag.  Decrement weighted count of
 * queue.  Also turn off QWANTR because queue is being read.
 *
 * If queue count is below the lo water mark and QWANTW is set, enable
 * the closest backq which has a service procedure and turn off the
 * QWANTW flag.
 *
 * Stream assumed unlocked.
 */

mblk_t *
getq(q)
	register queue_t *q;
{
	register mblk_t *bp;
	register spl_t s;

	ASSERT_DEBUG(q, "streams: getq: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	bp = l_getq(q);
	v_lock(&q->q_str->sd_mutex, s);
	return(bp);
}


/*
 * l_rmvq:
 * The queue count and other flow control parameters are adjusted and
 * the back queue enabled if necessary. 
 * Stream assumed locked.
 */

l_rmvq(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	register mblk_t *tmp;

	ASSERT_DEBUG(q, "streams: l_rmvq: null q");
	ASSERT_DEBUG(mp, "streams: l_rmvq: null msg");

	if (mp->b_prev) {
		mp->b_prev->b_next = mp->b_next;
	} else {
		q->q_first = mp->b_next;
	}

	if (mp->b_next) {
		mp->b_next->b_prev = mp->b_prev;
	} else {
		q->q_last = mp->b_prev;
	}

	mp->b_next = mp->b_prev = NULL;

	/* decrement q->count by weight of message dequeued  */
	for (tmp = mp; tmp; tmp = tmp->b_cont) {
		if (tmp->b_datap->db_class == NCLASS) {
			q->q_count -= (tmp->b_wptr - tmp->b_rptr);
		} else {
			q->q_count -= bsize[tmp->b_datap->db_class];
		}
	}

	if (q->q_count < q->q_hiwat) {
		q->q_flag &= ~QFULL;
	}

	if (q->q_count <= q->q_lowat && q->q_flag & QWANTW) {
		q->q_flag &= ~QWANTW;

		/* find nearest back queue with service proc */
		for (q = l_backq(q); q && !q->q_qinfo->qi_srvp; q = l_backq(q));

		/* enable it */
		if (q) {
			l_qenable(q);
		}
	}
}


/*
 * Remove a message from a queue.  The queue count and other
 * flow control parameters are adjusted and the back queue
 * enabled if necessary.
 *
 * Stream assumed unlocked.
 */

rmvq(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	register spl_t s;

#ifdef	lint
	/*
	 *  Although not used in the base kernel, this function must exist
	 *  for SVVS3 compatibility.
	 */
	rmvq(q, mp);
#endif	/* lint */

	ASSERT_DEBUG(q, "streams: rmvq: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	l_rmvq(q, mp);
	v_lock(&q->q_str->sd_mutex, s);
}

/*
 * l_flushq:
 * If flag is set, remove all messages.  Otherwise, remove only
 * non-control messages.  If queue falls below its low water mark, and
 * QWANTW was set before the flush, enable the nearest upstream
 * service procedure.
 *
 * Stream assumed locked.
 */

l_flushq(q, flag)
	register queue_t *q;
{
	register mblk_t *bp, *nbp;
	register wantw;

	wantw = q->q_flag & QWANTW;

	/* get the whole list of the q first */
	bp = q->q_first;
	q->q_first = NULL;
	q->q_last = NULL;
	q->q_count = 0;
	q->q_flag &= ~(QFULL|QWANTW);

	/* now sieve thru the list */
	while (bp) {
		nbp = bp->b_next;
		if (!flag && !datamsg(bp->b_datap->db_type)) {
			/* non-data message, must be put back */
			l_putq(q, bp); /* this updates count also */
		} else {
			/* data message or flush-all case: throw it */
			freemsg(bp);
		}
		bp = nbp;
	}

	if ((q->q_count <= q->q_lowat) && wantw) {
		/* find nearest back queue with service proc */
		for (q = l_backq(q); q && !q->q_qinfo->qi_srvp; q = l_backq(q));

		/* enable it */
		if (q) {
			l_qenable(q);
		}
	} else {
		/* reset the old value of wantw */
		q->q_flag |= wantw;
	}
}

/*
 * Empty a queue.  
 * 
 * If flag is set, remove all messages.  Otherwise, remove only
 * non-control messages.  If queue falls below its low water mark, and
 * QWANTW was set before the flush, enable the nearest upstream
 * service procedure.
 *
 * Stream assumed unlocked.
 */

flushq(q, flag)
	register queue_t *q;
{
	register spl_t s;

	ASSERT_DEBUG(q, "streams: flushq: null q");

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	l_flushq(q, flag);
	v_lock(&q->q_str->sd_mutex, s);
}


/*
 * Same as canput.
 * Stream assumed locked.
 */

int
l_canput(q)
	register queue_t *q;
{
	if (!q) {
		return(0);
	}
	
	/* search for next q with a service procedure defined */
	while (q->q_next && !q->q_qinfo->qi_srvp) {
		q = q->q_next;
	}

	/*  if q is already full, arrange for callback and return failure */
	if (q->q_flag & QFULL) {
		q->q_flag |= QWANTW;
		return(0);
	}

	/* q was not full so return success */
	return(1);
}


/*
 * First traverses q->q_next links until it finds a queue with a service
 * procedure.  Return 1 if this queue is not full.  If the queue is full,
 * return 0 (may not put message) and set QWANTW flag (caller wants to write
 * to the queue).
 *
 * Stream assumed unlocked.
 */

int
canput(q)
	register queue_t *q;
{
	register spl_t s;
	register int ret;

	ASSERT_DEBUG(q, "streams: canput: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	ret = l_canput(q);
	v_lock(&q->q_str->sd_mutex, s);
	return(ret);
}



/*
 * Logically equivalent to canput(q->q_next), except that takes care of
 * locking details.
 *
 * Stream assumed unlocked.
 */

int
canputnext(q)
	register queue_t *q;
{
	register spl_t s;
	register int ret;

	ASSERT_DEBUG(q, "streams: canput: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	ret = l_canput(q->q_next);
	v_lock(&q->q_str->sd_mutex, s);
	return(ret);
}


/*
 * l_putq:
 * Messages are enqueued on a priority basis.  The priority classes
 * are PRIORITY (type >= QPCTL) and NORMAL (type < QPCTL).
 *
 * Add appropriate weighted data block sizes to queue count.
 * If queue hits high water mark then set QFULL flag.
 *
 * QWANTW is reset if the message is not PRIORITY, because the back
 * module will not want to write again unless it indicates such by
 * first calling canput().
 *
 * If QNOENAB is not set (putq is allowed to enable the queue), enable
 * the queue only if the message is PRIORITY, or the QWANTR flag is
 * set (indicating that the service procedure is ready to read the
 * queue.  This implies that a service procedure must NEVER put a
 * priority message back on its own queue, as this would result in an
 * infinite loop (!).
 *
 * Stream assumed locked.
 */

l_putq(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register mblk_t *tmp;
	register mcls = queclass(bp);

	ASSERT_DEBUG(bp, "streams: l_putq: null bp");

	/* 
	 * If queue is empty or queue class of message is less than
	 * that of the last one on the queue, tack on to the end.
	 */
	if (!q->q_first || (mcls <= queclass(q->q_last))) {
		if (q->q_first) {
			q->q_last->b_next = bp;
			bp->b_prev = q->q_last;
		} else {
			q->q_first = bp;
			bp->b_prev = NULL;
		}
		bp->b_next = NULL;
		q->q_last = bp;

	} else {
		/* walk the q until end of higher or equal priority messages */
		register mblk_t *nbp = q->q_first;

		while (queclass(nbp) >= mcls) {
			nbp = nbp->b_next;
		}

		/* nbp is lower priority, so make it bp's right neighbour */

		bp->b_next = nbp;
		bp->b_prev = nbp->b_prev;
		if (nbp->b_prev) {
			nbp->b_prev->b_next = bp;
		} else {
			q->q_first = bp;
		}
		nbp->b_prev = bp;
	}
	/* increment q->count by the weight of the added message */
	for (tmp = bp; tmp; tmp = tmp->b_cont) {
		if (tmp->b_datap->db_class == NCLASS) {
			q->q_count += (tmp->b_wptr - tmp->b_rptr);
		} else {
			q->q_count += bsize[tmp->b_datap->db_class];
		}
	}

	if (q->q_count >= q->q_hiwat) {
		q->q_flag |= QFULL;
	}

	/*
	 * Setup the enabling of service procedure if: 
	 *	msg was priority message || 
	 * 	enabling is allowed && it wants to read q 
	 */
	if ((mcls > QNORM) || (canenable(q) && (q->q_flag & QWANTR))) {
		l_qenable(q);
	}
}


/*
 * Put a message on a queue.  
 *
 * Messages are enqueued on a priority basis.  The priority classes
 * are PRIORITY (type >= QPCTL) and NORMAL (type < QPCTL).
 *
 * Add appropriate weighted data block sizes to queue count.
 * If queue hits high water mark then set QFULL flag.
 *
 * QWANTW is reset if the message is not PRIORITY, because the back
 * module will not want to write again unless it indicates such by
 * first calling canput().
 *
 * If QNOENAB is not set (putq is allowed to enable the queue), enable
 * the queue only if the message is PRIORITY, or the QWANTR flag is
 * set (indicating that the service procedure is ready to read the
 * queue.  This implies that a service procedure must NEVER put a
 * priority message back on its own queue, as this would result in an
 * infinite loop (!).
 *
 * Stream assumed unlocked.
 */

putq(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register spl_t s;

	ASSERT_DEBUG(q, "streams: putq: null q");

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	l_putq(q, bp);
	v_lock(&q->q_str->sd_mutex, s);
}


/*
 * l_putbq:  (similar to putq).
 * See comment on putq above for details. QWANTW is not reset as
 * this is internal to the module.
 * Stream assumed locked.
 */
l_putbq(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register mblk_t *tmp;
	register mcls = queclass(bp);

	ASSERT_DEBUG(bp, "streams: l_putbq: null bp");
	ASSERT_DEBUG(bp->b_next == NULL, "streams: l_putbq: invalid bp");
	/* 
	 * If queue is empty of queue class of message >= that of the
	 * first message, place on the front of the queue.
	 */
	if ( !q->q_first || (mcls >= queclass(q->q_first))) {
		bp->b_next = q->q_first;
		bp->b_prev = NULL;
		if (q->q_first) {
			q->q_first->b_prev = bp;
		} else {
			q->q_last = bp;
		}
		q->q_first = bp;
	} else {
		/* walk the q until end of higher priority messages */
		register mblk_t *nbp = q->q_first;

		while ((nbp->b_next) && (queclass(nbp->b_next) > mcls)) {
			nbp = nbp->b_next;
		}

		/* nbp->next is lower or equal priority, so make it bp's right neighbour */

		if (bp->b_next = nbp->b_next) {
			nbp->b_next->b_prev = bp;
		} else {
			q->q_last = bp;
		}
		nbp->b_next = bp;
		bp->b_prev = nbp;
	}

	/* increment q->count by the weight of the added message */
	for (tmp = bp; tmp; tmp = tmp->b_cont) {
		if (tmp->b_datap->db_class == NCLASS) {
			q->q_count += (tmp->b_wptr - tmp->b_rptr);
		} else {
			q->q_count += bsize[tmp->b_datap->db_class];
		}
	}

	if (q->q_count >= q->q_hiwat) {
		q->q_flag |= QFULL;
	}

	/*
	 * Setup the enabling of service procedure if: 
	 *	msg was priority message ||
	 *	enabling is allowed && it wants to read q 
	 */
	if ((mcls > QNORM) || (canenable(q) && (q->q_flag & QWANTR))) {
		l_qenable(q);
	}
}


/*
 * Put stuff back at beginning of Q according to priority order.
 * See comment on putq above for details. QWANTW is not reset as
 * this is internal to the module.
 *
 * Stream assumed unlocked.
 */

putbq(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register spl_t s;

	ASSERT_DEBUG(q, "streams: putbq: null q");

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);	
	l_putbq(q, bp);
	v_lock(&q->q_str->sd_mutex, s);
}

/*
 * Insert a message before an existing message on the queue.  If the
 * existing message is NULL, the new messages is placed on the end of
 * the queue.  The queue class of the new message is ignored.
 * All flow control parameters are updated.
 *
 * Stream assumed locked.
 */

l_insq(q, emp, mp)
	register queue_t *q;
	register mblk_t *emp, *mp;
{
	register mblk_t *tmp;

	ASSERT_DEBUG(q, "streams: l_insq: null q");
	ASSERT_DEBUG(mp, "streams: l_insq: null msg");

	/* emp is already enqueued, mp must be enqueued after it */

	if (mp->b_next = emp) {
		/* emp is non-null */
		if (mp->b_prev = emp->b_prev) {
			emp->b_prev->b_next = mp;
		} else {
			q->q_first = mp;
		}
		emp->b_prev = mp;
	} else {
		/* emp is null, so enqueue mp at the end of the queue */
		if (mp->b_prev = q->q_last) {
			q->q_last->b_next = mp;
		} else {
			q->q_first = mp;
		}
		q->q_last = mp;
	}

	/* increment q->count by the weight of the added message */
	for (tmp = mp; tmp; tmp = tmp->b_cont) {
		if (tmp->b_datap->db_class == NCLASS) {
			q->q_count += (tmp->b_wptr - tmp->b_rptr);
		} else {
			q->q_count += bsize[tmp->b_datap->db_class];
		}
	}

	if (q->q_count >= q->q_hiwat) {
		q->q_flag |= QFULL;
	}
	
	/*
	 * Setup the back-enabling if: 
	 *	enabling is allowed && someone wants to read it
	 */
	if (canenable(q) && (q->q_flag & QWANTR)) {
		l_qenable(q);
	}
}

/*
 * Insert a message before an existing message on the queue.  If the
 * existing message is NULL, the new messages is placed on the end of
 * the queue.  The queue class of the new message is ignored.
 * All flow control parameters are updated.
 *
 * Stream assumed unlocked.
 */

insq(q, emp, mp)
	register queue_t *q;
	register mblk_t *emp, *mp;
{
	register spl_t s;

	ASSERT_DEBUG(q, "streams: insq: null q");
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	l_insq(q, emp, mp);
	v_lock(&q->q_str->sd_mutex, s);
}

/*
 * Create and put a control message on queue.
 * Stream assumed unlocked.
 */

int
putctl(q, type)
	register queue_t *q;
{
	register mblk_t *bp;
	register spl_t s;

	if (datamsg(type) || !(bp = allocb(0, BPRI_HI))) {
		return(0);
	}

	bp->b_datap->db_type = type;

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	ASSERT_DEBUG(*q->q_qinfo->qi_putp, "streams: null put procedure");
	(*q->q_qinfo->qi_putp)(q, bp);

	(void) p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s);

	return(1);
}



/*
 * Putctl with control message with a single-byte parameter.
 * Stream assumed unlocked.
 */

int
putctl1(q, type, param)
	register queue_t *q;
{
	register mblk_t *bp;
	register spl_t s;

	if (datamsg(type) ||!(bp = allocb(1, BPRI_HI))) {
		return(0);
	}

	bp->b_datap->db_type = type;
	*bp->b_wptr++ = param;

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	ASSERT_DEBUG(*q->q_qinfo->qi_putp, "streams: null put procedure");
	(*q->q_qinfo->qi_putp)(q, bp);

	(void) p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s);

	return(1);
}


/*
 * Create and put a control message on q->q_next.
 * Stream assumed unlocked.
 */

int
putnextctl(q, type)
	register queue_t *q;
{
	register mblk_t *bp;
	register spl_t s;

	if (datamsg(type) || !(bp = allocb(0, BPRI_HI))) {
		return(0);
	}

	bp->b_datap->db_type = type;

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q = q->q_next;
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	ASSERT_DEBUG(*q->q_qinfo->qi_putp, "streams: null put procedure");
	(*q->q_qinfo->qi_putp)(q, bp);

	(void) p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s);

	return(1);
}



/*
 * Same as putnextclt with control message with a single-byte parameter.
 * Stream assumed unlocked.
 */
int
putnextctl1(q, type, param)
	register queue_t *q;
{
	register mblk_t *bp;
	register spl_t s;

	if (datamsg(type) ||!(bp = allocb(1, BPRI_HI))) {
		return(0);
	}

	bp->b_datap->db_type = type;
	*bp->b_wptr++ = param;

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q = q->q_next;
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	ASSERT_DEBUG(*q->q_qinfo->qi_putp, "streams: null put procedure");
	(*q->q_qinfo->qi_putp)(q, bp);

	(void) p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s);

	return(1);
}


/*
 * Puts a message of the next queue of the given queue.
 * Stream assumed unlocked.
 */
putnext(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	register spl_t s_ipl;

	ASSERT_DEBUG(q && mp, "streams: putnext: null q or bp");

	s_ipl = p_lock(&q->q_str->sd_mutex, SPLSTR);
	ASSERT_DEBUG(q->q_next, "streams: putnext: invalid q");
	q = q->q_next;
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s_ipl);

	/* call module's put procedure - note that lock is not held */
	(*q->q_qinfo->qi_putp)(q, mp);

	s_ipl = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s_ipl);
}

/*
 * Send a block back up the queue in reverse from this
 * one (e.g. to respond to ioctls).
 *
 * Stream assumed unlocked.
 */
qreply(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register spl_t s_ipl;

	ASSERT_DEBUG(q && bp, "streams: qreply: null q or bp");
	q = OTHERQ(q);

	s_ipl = p_lock(&q->q_str->sd_mutex, SPLSTR);
	ASSERT_DEBUG(q->q_next, "streams: qreply: invalid q");
	q = q->q_next;
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s_ipl);

	/* call module's put procedure - note that lock is not held */
	(*q->q_qinfo->qi_putp)(q, bp);

	s_ipl = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s_ipl);
}

/*
 * Send a block back up the queue in reverse from this
 * one (e.g. to respond to ioctls)
 *
 * Stream assumed unlocked.
 */

putother(q, bp)
	register queue_t *q;
	register mblk_t *bp;
{
	register spl_t s;

	ASSERT_DEBUG(q && bp, "streams: putother: null q or bp");
	q = OTHERQ(q);

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	ASSERT_DEBUG(*q->q_qinfo->qi_putp, "streams: null put procedure");
	(*q->q_qinfo->qi_putp)(q, bp);

	(void) p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s);
}


/*
 * Invoke the put procedure of the given queue.
 * Stream assumed unlocked.
 */
put(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	register spl_t s;

	ASSERT_DEBUG(q && mp, "streams: put: null q or bp");

	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt++;
	v_lock(&q->q_str->sd_mutex, s);

	/* call module's put procedure - note that lock is not held */
	ASSERT_DEBUG(*q->q_qinfo->qi_putp, "streams: null put procedure");
	(*q->q_qinfo->qi_putp)(q, mp);

	(void) p_lock(&q->q_str->sd_mutex, SPLSTR);
	q->q_putcnt--;
	if (q->q_flag & QFREEZE) {
		(void)cv_sema(&q->q_str->sd_freeze);
	}
	v_lock(&q->q_str->sd_mutex, s);
}

/* 
 * Same as qsize.
 * Stream assumed locked.
 */
int
l_qsize(qp)
	register queue_t *qp;
{
	register count = 0;
	register mblk_t *mp;

	ASSERT_DEBUG(qp, "streams: qsize: null q");
	for (mp = qp->q_first; mp; mp = mp->b_next) {
		count++;
	}
	return(count);
}



/* 
 * Return number of messages on queue.
 *
 * Stream assumed unlocked.
 */

int
qsize(qp)
	register queue_t *qp;
{
	register count;
	register spl_t s;

	ASSERT_DEBUG(qp, "streams: qsize: null q");
	s = p_lock(&qp->q_str->sd_mutex, SPLSTR);
	count = l_qsize(qp);
	v_lock(&qp->q_str->sd_mutex, s);
	return(count);
}


/*
 * Allocate a pair of queues.  Returns pointer to read queue
 * if available and NULL if not.
 *
 * Assumes queue mgt is unlocked.
 */
queue_t *
allocq()
{
	register spl_t s;
	register queue_t *qp;

	/* Take first pair of the freelist. */

	s = p_lock(&qm_mutex, SPLMM);
	if (qpfreelist) {
		qp = qpfreelist;
		qpfreelist = qp->q_next;
		BUMPUP(strst.queue);
		v_lock(&qm_mutex, s);
		ASSERT_DEBUG(!(qp->q_svcflag&(QENAB|QSVCBUSY)), "allocq: bad qp");
		ASSERT_DEBUG(!(WR(qp)->q_svcflag&(QENAB|QSVCBUSY)), "allocq: bad wqp");
		*qp = zeroR;
		*WR(qp) = zeroW;
		return(qp);
	} 
	strst.queue.fail++;
	v_lock(&qm_mutex, s);
	return(NULL);
}

/*
 * Free a pair of queues.
 * Assumes queue mgt is unlocked.
 */
freeq(qp)
	register queue_t *qp;	 /* read queue */
{
	register spl_t s;

	ASSERT_DEBUG(!(qp->q_svcflag&(QENAB|QSVCBUSY)), "freeq: rq is n use");
	ASSERT_DEBUG(!(WR(qp)->q_svcflag&(QENAB|QSVCBUSY)), "freeq: wrq is in use");
	qp->q_flag = 0;
	WR(qp)->q_flag = 0;
	s = p_lock(&qm_mutex, SPLSTR);
	qp->q_next = qpfreelist;
	qpfreelist = qp;
	strst.queue.use--;
	v_lock(&qm_mutex, s);
}


/*
 * Special routine needed by SVVS3 only.  Returns 0 if queue is full,
 * 1 otherwise.  Does not affect flow-control flag (unlike canput).
 *
 * Stream assumed unlocked.
 */
int
canput1(q)
	register queue_t *q;
{
	register spl_t s;

#ifdef	lint
	/*
	 *  Although not used in the base kernel, this function must exist
	 *  for SVVS3 compatibility.
	 */
	(void) canput1(q);
#endif	/* lint */
	s = p_lock(&q->q_str->sd_mutex, SPLSTR);
	if (q->q_next->q_flag & QFULL) {
		v_lock(&q->q_str->sd_mutex, s);
		return(0);
	} else {
		v_lock(&q->q_str->sd_mutex, s);
		return(1);
	}
}
