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


/*
 * DESCRIPTION
 *
 * ttymux - Multiplexer driver for multiplexing termio compliant streams onto
 * a single upper stream.
 *
 * ADD2FRONT macro can be used to specify the order in which a console
 * device is put in the queue of multiplexed physical serial devices,
 * during the association and disassociation of a console interface.
 * When this macro is defined, the device is placed in front of the queue,
 * otherwise by default it is placed at the end.
 * Console I/O happens to each of the physical devices in the order of
 * their position in this queue.
 */

#include <sys/types.h>
#include <sys/file.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/strlog.h>
#include <sys/strsun.h>
#include <sys/modctl.h>
#include <sys/debug.h>
#include <sys/kbio.h>
#include <sys/devops.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/consdev.h>
#include <sys/tty.h>
#include <sys/ptyvar.h>
#include <sys/termio.h>
#include <sys/fcntl.h>
#include <sys/mkdev.h>
#include <sys/ser_sync.h>
#include <sys/esunddi.h>
#include <sys/policy.h>

#include <sys/ttymux.h>
#include "ttymux_impl.h"

/*
 * Extern declarations
 */
extern mblk_t *mkiocb(uint_t);
extern int nulldev();
extern uintptr_t space_fetch(char *key);

extern int sm_ioctl_cmd(sm_uqi_t *, mblk_t *);
extern int ttymux_abort_ioctl(mblk_t *);
extern int ttymux_device_fini(sm_lqi_t *);
extern int ttymux_device_init(sm_lqi_t *);

/*
 * Exported interfaces
 */
int sm_disassociate(int, sm_lqi_t *, ulong_t);
int sm_associate(int, sm_lqi_t *, ulong_t, uint_t, char *);

/*
 * Variables defined here and visible only internally
 */
sm_ss_t		*sm_ssp = 0;
static int	sm_instance = 0;
static int	smctlunit;

static uint_t	sm_default_trflag = 0;
uint_t		sm_max_units = 6;
uint_t		sm_minor_cnt = 0;
static uint_t	sm_refuse_opens = 0;

/*
 * Local definitions.
 */

/* force these flags to be unset on console devices */
static ulong_t	sm_cmask = (ulong_t)(CRTSXOFF|CRTSCTS);

/*
 * SECTION
 * Implementation Section:
 */
void
sm_debug(char *msg, ...)
{
	va_list	args;
	char	buf[256];
	int	sz;

	va_start(args, msg);
	sz = vsnprintf(buf, sizeof (buf), msg, args);
	va_end(args);

	if (sz < 0)
		(void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1,
		    SL_TRACE, "vsnprintf parse error\n");
	else if (sz > sizeof (buf)) {
		char *b;
		size_t	len = sz + 1;

		b = kmem_alloc(len, KM_SLEEP);
		va_start(args, msg);
		sz = vsnprintf(b, len, msg, args);
		va_end(args);
		if (sz > 0)
			(void) strlog(ddi_driver_major(sm_ssp->sm_dip),
			    sm_instance, 1, SL_TRACE, b);
		kmem_free(b, len);
	} else {

		(void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance,
		    1, SL_TRACE, buf);
	}
}

void
sm_log(char *msg, ...)
{
	va_list	args;
	char	buf[128];
	int	sz;

	va_start(args, msg);
	sz = vsnprintf(buf, sizeof (buf), msg, args);
	va_end(args);

	if (sz < 0)
		(void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1,
		    SL_TRACE, "vsnprintf parse error\n");
	else if (sz > sizeof (buf)) {
		char *b;
		size_t	len = sz + 1;

		b = kmem_alloc(len, KM_SLEEP);
		va_start(args, msg);
		sz = vsnprintf(b, len, msg, args);
		va_end(args);
		if (sz > 0)
			(void) strlog(ddi_driver_major(sm_ssp->sm_dip),
			    sm_instance, 1, SL_NOTE, b);
		kmem_free(b, len);
	} else {

		(void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance,
		    1, SL_NOTE, buf);
	}
}

/*
 * Should only be called if the caller can guarantee that the vnode
 * and/or the stream won't disappear while finding the dip.
 * This routine is only called during an I_PLINK request so it's safe.
 * The routine obtains the dev_t for a linked se stream.
 */
static void
sm_setdip(queue_t *q, sm_lqi_t *lqi)
{
	lqi->sm_dev = q && STREAM(q) ? STREAM(q)->sd_vnode->v_rdev : NODEV;
}

/*
 * Called from driver close, state change reports and I_PUNLINK ioctl.
 * A lower stream has been unlinked - clean up the state associated with it.
 */
void
sm_lqifree(sm_lqi_t *lqi)
{
	int mu_owned;
	sm_lqi_t **pplqi;

	ASSERT(mutex_owned(lqi->sm_umutex));
	ASSERT(SM_RQ(lqi) != 0);

	/*
	 * Clear all state associated with this lower queue except
	 * the identity of the queues themselves and the link id which
	 * can only be cleared by issuing a streams I_PUNLINK ioctl.
	 *
	 * The association of a lower queue is a two step process:
	 * 1. initialise the lower q data structure on I_PLINK
	 * 2. associate an upper q with the lower q on SM_CMD_ASSOCIATE.
	 *
	 * If step 2 has ocurred then
	 * remove this lower queue info from the logical unit.
	 */
	if (lqi->sm_uqi) {
		sm_dbg('Y', ("lqifree unit %d, ", lqi->sm_uqi->sm_lunit));
		if ((mu_owned = mutex_owned(lqi->sm_uqi->sm_umutex)) == 0)
			LOCK_UNIT(lqi->sm_uqi);

		pplqi = &lqi->sm_uqi->sm_lqs;
		while (*pplqi != lqi) {
			ASSERT(*pplqi);
			pplqi = &((*pplqi)->sm_nlqi);
		}
		*pplqi = lqi->sm_nlqi;
		lqi->sm_uqi->sm_nlqs--;

		if (mu_owned == 0)
			UNLOCK_UNIT(lqi->sm_uqi);

		lqi->sm_uqi = 0;
	}
}

/*
 * Given a q return the associated lower queue data structure or NULL.
 * Return the data locked.
 */
static sm_lqi_t *
get_lqi_byq(queue_t *q)
{
	int i;
	sm_lqi_t *lqi, *flqi = 0;

	for (i = 0; i < MAX_LQS; i++) {
		lqi = &sm_ssp->sm_lqs[i];
		LOCK_UNIT(lqi);
		if (flqi == 0 && lqi->sm_linkid == 0) /* assumes muxids != 0 */
			flqi = lqi;
		else if (SM_RQ(lqi) == q || SM_WQ(lqi) == q) {
			if (flqi)
				UNLOCK_UNIT(flqi);
			return (lqi);
		}
		else
			UNLOCK_UNIT(lqi);
	}
	return (flqi);
}

/*
 * Given a streams link identifier return the associated lower queue data
 * structure or NULL.
 */
sm_lqi_t *
get_lqi_byid(int linkid)
{
	int i;
	sm_lqi_t *lqi;

	if (linkid == 0)
		return (NULL);
	for (i = 0; i < MAX_LQS; i++) {
		lqi = &sm_ssp->sm_lqs[i];
		if (lqi->sm_linkid == linkid)
			return (lqi);
	}
	return (NULL);
}

/*
 * Given a dev_t for a lower stream return the associated lower queue data
 * structure or NULL.
 */
sm_lqi_t *
get_lqi_bydevt(dev_t dev)
{
	int i;
	sm_lqi_t *lqi;

	if (dev == NODEV)
		return (NULL);

	for (i = 0; i < MAX_LQS; i++) {
		lqi = &sm_ssp->sm_lqs[i];
		if (lqi->sm_dev == dev)
			return (lqi);
	}
	return (NULL);
}

/*
 * Determine whether the input flag is set on at least
 * howmany queues.
 */
static int
sm_is_flag_set(sm_uqi_t *uqi, uint_t flag, uint_t howmany)
{
	sm_lqi_t *lqi;

	if (howmany == 0)
		return (0);

	for (lqi = uqi->sm_lqs; lqi; lqi = lqi->sm_nlqi) {
		if (lqi->sm_flags & flag)
			if (--howmany == 0)
				return (1);
	}
	return (0);
}

/*
 * How many usable queues are associated with a given upper stream
 */
static int
sm_uwq_error(sm_uqi_t *uqi)
{
	return (sm_is_flag_set(uqi, (WERROR_MODE|HANGUP_MODE), uqi->sm_nlqs));
}

/*
 * How many of the queues associated with a given upper stream
 * - do not - have the given flags set.
 */
static int
sm_q_count(sm_uqi_t *uqi, uint_t flag)
{
	sm_lqi_t *lqi;
	int count = 0;

	for (lqi = uqi->sm_lqs; lqi; lqi = lqi->sm_nlqi) {
		if ((lqi->sm_flags & flag) == 0)
			count++;
	}
	return (count);
}

/*
 * How many of the queues associated with a given upper stream
 * - do not - have the given flags set.
 */
static int
sm_qs_without(sm_uqi_t *uqi, uint_t flag, uint_t ioflag)
{
	sm_lqi_t *lqi;
	int count = 0;

	for (lqi = uqi->sm_lqs; lqi; lqi = lqi->sm_nlqi) {
		if ((lqi->sm_flags & flag) == 0 &&
		    (lqi->sm_ioflag & ioflag) == 0)
			count++;
	}
	return (count);
}

/*
 * How many usable queues are associated with a given upper stream
 */
static int
sm_good_qs(sm_uqi_t *uqi)
{
	return (sm_q_count(uqi, (WERROR_MODE|HANGUP_MODE)));
}

static int
sm_cnt_oqs(sm_uqi_t *uqi)
{
	return (sm_qs_without(uqi, (WERROR_MODE|HANGUP_MODE),
	    (uint_t)FOROUTPUT));
}

/*
 * Send an ioctl downstream and remember that it was sent so that
 * its response can be caught on the way back up.
 */
static void
sm_issue_ioctl(void *arg)
{
	sm_lqi_t *lqi = arg;
	uint_t cmdflag = 0;
	queue_t *q = SM_WQ(lqi);
	int iocmd, size;

	LOCK_UNIT(lqi);

	lqi->sm_bid = 0;
	if ((lqi->sm_flags & (WERROR_MODE|HANGUP_MODE)) == 0 &&
	    (lqi->sm_flags & (WANT_CDSTAT|WANT_TCSET))) {
		mblk_t *pioc;

		if (lqi->sm_flags & WANT_TCSET) {
			lqi->sm_flags &= ~WANT_TCSET;
			iocmd = TCSETS;
			cmdflag = WANT_TCSET;
		} else if (lqi->sm_flags & WANT_SC) {
			lqi->sm_flags &= ~WANT_SC;
			iocmd = TIOCGSOFTCAR;
			cmdflag = WANT_SC;
		} else if (lqi->sm_flags & WANT_CD) {
			lqi->sm_flags &= ~WANT_CD;
			iocmd = TIOCMGET;
		} else if (lqi->sm_flags & WANT_CL) {
			lqi->sm_flags &= ~WANT_CL;
			iocmd = TCGETS;
			cmdflag = WANT_CL;
		} else {
			UNLOCK_UNIT(lqi);
			return;
		}

		if (pioc = mkiocb(iocmd)) {
			if (cmdflag == WANT_TCSET) {
				pioc->b_cont =
				    sm_allocb(sizeof (struct termios),
				    BPRI_MED);
				if (pioc->b_cont == 0) {
					freemsg(pioc);
					pioc = 0;
				} else {
					struct termios *tc = (struct termios *)
					    pioc->b_cont->b_wptr;

					bzero((caddr_t)tc,
					    sizeof (struct termios));
					tc->c_cflag = lqi->sm_ttycommon->
					    t_cflag;
					pioc->b_cont->b_rptr =
					    pioc->b_cont->b_wptr;
					pioc->b_cont->b_wptr +=
					    sizeof (struct termios);
				}
				size = sizeof (struct iocblk) +
				    sizeof (struct termios);
			}
			else
				size = sizeof (struct iocblk);
		}
		else
			size = sizeof (struct iocblk);

		if (pioc != 0) {

			lqi->sm_piocid = ((struct iocblk *)pioc->b_rptr)->
			    ioc_id;
			lqi->sm_flags |= SM_IOCPENDING;

			/* lqi->sm_flags |= cmdflag; */
			UNLOCK_UNIT(lqi);
			(void) putq(q, pioc);
		} else {
			UNLOCK_UNIT(lqi);
			lqi->sm_bid = qbufcall(WR(q), size, BPRI_MED,
			    sm_issue_ioctl, lqi);
		}
	}
	else
		UNLOCK_UNIT(lqi);
}

/*
 * Associate one of the drivers minor nodes with a serial device.
 */
int
sm_associate(int unit, sm_lqi_t *plqi, ulong_t tag, uint_t ioflag, char *dp)
{
	sm_uqi_t *uqi;
	int rval = 0;

	sm_dbg('Y', ("sm_associate(%d, %d, %d): ",
	    (plqi) ? plqi->sm_linkid : 0, unit, ioflag));
	/*
	 * Check the data is valid.
	 * Associate a lower queue with a logical unit.
	 */

	if (unit < 0 || unit >= NLUNITS || plqi == 0 ||
	    (uqi = get_uqi(sm_ssp, unit)) == 0) {
		sm_dbg('@', (" invalid: lqi=0x%p lui=0x%p:", plqi, uqi));
		rval = EINVAL;
	} else {
		if ((ioflag & FORIO) == 0)
			ioflag = FORIO;

		LOCK_UNIT(plqi);

		if (plqi->sm_uqi) {
			if (plqi->sm_uqi->sm_lunit == unit) {
				if ((ioflag & (uint_t)FORIO) != 0)
					plqi->sm_ioflag =
					    (ioflag & (uint_t)FORIO);
				rval = 0;
			} else {
				sm_dbg('@', ("already associated with unit %d:",
				    plqi->sm_uqi->sm_lunit));
				rval = EINVAL;
			}
		} else {

			LOCK_UNIT(uqi);

			if ((ioflag & (uint_t)FORIO) != 0)
				plqi->sm_ioflag = (ioflag & (uint_t)FORIO);

			plqi->sm_ttycommon->t_cflag = uqi->sm_ttycommon->
			    t_cflag;
			plqi->sm_ttycommon->t_flags = uqi->sm_ttycommon->
			    t_flags;
			plqi->sm_uqi = uqi;
			plqi->sm_mbits = 0;
			plqi->sm_tag = tag;

			if (*dp == '/')
				(void) strncpy(plqi->sm_path, dp, MAXPATHLEN);
			else
				*(plqi->sm_path) = '\0';

			plqi->sm_flags |= WANT_TCSET;
#ifdef ADD2FRONT
			plqi->sm_nlqi = uqi->sm_lqs;
			uqi->sm_lqs = plqi;
#else
			plqi->sm_nlqi = 0;
			if (uqi->sm_lqs) {
				sm_lqi_t *lq;
				for (lq = uqi->sm_lqs; lq->sm_nlqi;
				    lq = lq->sm_nlqi) {
				}
				lq->sm_nlqi = plqi;
			} else
				uqi->sm_lqs = plqi;
#endif
			uqi->sm_nlqs++;

			(void) ttymux_device_init(plqi);

			UNLOCK_UNIT(uqi);
			rval = 0;
			/*
			 * Everything looks good so it's now ok to enable lower
			 * queue processing.
			 * Note the lower queue should be enabled as soon as
			 * I_PLINK returns (used in sm_get_ttymodes etc).
			 * Schedule ioctls to obtain the terminal settings.
			 */

			if ((uqi->sm_flags & FULLY_OPEN) || uqi->sm_waitq)
				plqi->sm_uqflags |= SM_UQVALID;

			qenable(SM_RQ(plqi));
			if (plqi->sm_flags & (WANT_CDSTAT|WANT_TCSET)) {
				/*
				 * Bypass the lower half of the driver (hence
				 * no qwriter) and apply the current termio
				 * settings on the lower stream.
				 */
				UNLOCK_UNIT(plqi);
				if (plqi->sm_bid) {
					qunbufcall(SM_WQ(plqi), plqi->sm_bid);
					plqi->sm_bid = 0;
				}
				/*
				 * Only set cflags on the lower q if we know
				 * the settings on any other lower queue.
				 */
				sm_issue_ioctl(plqi);
				LOCK_UNIT(plqi);

			}
		}

		UNLOCK_UNIT(plqi);
	}
	sm_dbg('Y', ("sm_associate: rval=%d.\n", rval));
	return (rval);
}

/*
 * Break an association between one of the driver's minor nodes and
 * a serial device.
 */
int
sm_disassociate(int unit, sm_lqi_t *plqi, ulong_t tag)
{
	sm_uqi_t *uqi;
	int rval = 0;

	sm_dbg('Y', ("sm_disassociate: link %d, unit %d: ",
	    (plqi) ? plqi->sm_linkid : 0, unit));
	/*
	 * Check the data is valid.
	 * Disassociate a lower queue with a logical unit.
	 */
	if (unit < 0 || unit >= NLUNITS || plqi == 0 ||
	    (uqi = get_uqi(sm_ssp, unit)) == 0) {
		sm_dbg('@', ("invalid: lqi=0x%p lui=0x%p", plqi, uqi));
		rval = EINVAL;
	} else {
		LOCK_UNIT(plqi);

		if (plqi->sm_uqi == NULL) {
			sm_dbg('@', ("unit not associated"));
			rval = EINVAL;
		} else if (plqi->sm_uqi->sm_lunit != unit) {
			sm_dbg('@', ("unit and linkid not related",
			    plqi->sm_uqi->sm_lunit));
			rval = EINVAL;
		} else if (plqi->sm_tag != tag) {
			sm_dbg('@',
			    ("Invalid tag for TTYMUX_DISASSOC ioctl\n"));
			rval = EPERM;
		} else {
			sm_dbg('Y', ("disassociating "));

			(void) ttymux_device_fini(plqi);

			/*
			 * Indicate that carrier status is no
			 * longer required and that the upper
			 * queue should not be used by plqi
			 */
			plqi->sm_flags &= ~(WANT_CDSTAT|WANT_TCSET);
			plqi->sm_uqflags &= ~(SM_UQVALID|SM_OBPCNDEV);
			plqi->sm_ioflag = 0u;

			sm_lqifree(plqi);
			rval = 0;
		}
		UNLOCK_UNIT(plqi);
	}
	sm_dbg('Y', (" rval=%d.\n", rval));
	return (rval);

}

/*
 * Streams helper routines;
 */

/*
 * Schedule a qbufcall for an upper queue.
 * Must be called within the perimiter of the parameter q.
 * fn must reenable the q.
 * Called:
 *	 whenever a message must be placed on multiple queues and allocb fails;
 */
static void
sm_sched_uqcb(queue_t *q, int memreq, int pri, void (*fn)())
{
	sm_uqi_t	*uqi = q->q_ptr;

	if (uqi->sm_ttybid != 0)
		qunbufcall(q, uqi->sm_ttybid);

	noenable(q);

	uqi->sm_ttybid = qbufcall(q, memreq, pri, fn, uqi);
}

/*
 * qbufcall routine to restart the queues when memory is available.
 */
static void
sm_reenable_q(sm_uqi_t *uqi)
{
	queue_t *wq = SM_WQ(uqi);

	if ((uqi->sm_flags & SM_STOPPED) == 0) {
		enableok(wq);
		qenable(wq);
	}
}

/*
 * Place a message on the write queue of each stream associated with
 * the given upper stream.
 */
static void
sm_senddown(sm_uqi_t *uqi)
{
	sm_lqi_t *lqi;

	for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) {
		if (lqi->sm_mp != 0) {
			putnext(SM_WQ(lqi), lqi->sm_mp);
			lqi->sm_mp = 0;
		}
	}
}

/*
 * For each lower device that should receive a write message duplicate
 * the message block.
 */
static int
sm_dupmsg(sm_uqi_t *uqi, mblk_t *mp)
{
	sm_lqi_t	*lqi;
	mblk_t	*origmp = mp;

	for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) {
		lqi->sm_mp = 0;
		if (lqi->sm_flags & WERROR_MODE) {
			continue;
		}
		if ((lqi->sm_ioflag & (uint_t)FOROUTPUT) == 0) {
			if (DB_TYPE(mp) == M_DATA)
				continue;
		}
		if (lqi->sm_nlqi == 0) {
			lqi->sm_mp = mp;
			origmp = NULL;
		} else if ((lqi->sm_mp = sm_copymsg(mp)) == 0) {
			sm_lqi_t *flqi;

			for (flqi = uqi->sm_lqs; flqi != lqi;
			    flqi = flqi->sm_nlqi) {
				if (lqi->sm_mp) {
				/* must have been sm_copymsg */
					sm_freemsg(lqi->sm_mp);
					lqi->sm_mp = 0;
				}
			}
			return (sm_cnt_oqs(uqi) * msgdsize(mp));
		}
	}
	if (origmp != NULL)
		freemsg(origmp);
	return (0);
}

/*
 * Return 1 if all associated lower devices have room for another message
 * otherwise return 0.
 */
static int
sm_cansenddown(sm_uqi_t *uqi)
{

	register sm_lqi_t	*lqi;

	if (uqi->sm_lqs == 0)
		return (0);

	for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) {
		if ((lqi->sm_flags & WERROR_MODE) == 0 &&
		    canputnext(SM_WQ(lqi)) == 0)
			return (0);
	}
	return (1);
}

/*
 * Put a message down all associated lower queues.
 * Return 1 if the q function was called.
 */
static int
sm_putqs(queue_t *q, mblk_t *mp, int (*qfn)())
{
	register sm_uqi_t *uqi = (sm_uqi_t *)q->q_ptr;
	register int memreq;
	int pri = (DB_TYPE(mp) < QPCTL) ? BPRI_MED : BPRI_HI;
	int rval = 0;

	if (uqi->sm_lqs == 0 || (uqi->sm_flags & WERROR_MODE)) {

		sm_dbg('Q', ("sm_putqs: freeing (0x%p 0x%p).\n", uqi->sm_lqs,
		    uqi->sm_flags));
		freemsg(mp);
	} else if (pri != BPRI_HI && sm_cansenddown(uqi) == 0) {
		/* a lower q is flow controlled */
		(void) qfn(q, mp);
		rval = 1;
	} else if ((memreq = sm_dupmsg(uqi, mp)) == 0) {

		sm_senddown(uqi);

	} else {
		sm_log("sm_putqs: msg 0x%x - can't alloc %d bytes (pri %d).\n",
		    DB_TYPE(mp), memreq, pri);
		sm_sched_uqcb(q, memreq, pri, sm_reenable_q);

		(void) qfn(q, mp);
		rval = 1;

	}

	return (rval);
}

/*
 * Service a streams link and unlink requests.
 */
static void
sm_link_req(queue_t *wq, mblk_t *mp)
{
	struct linkblk *linkp;
	int rval;
	int cmd;
	sm_lqi_t *plqi;

	ASSERT(DB_TYPE(mp) == M_IOCTL);

	cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd;
	switch (cmd) {

	case I_LINK:
	case I_PLINK:
		sm_dbg('G', ("sm_link_req: M_IOCTL %x (I_PLINK).\n", cmd));

		linkp = (struct linkblk *)mp->b_cont->b_rptr;

		/*
		 * 1.	Sanity check the link block.
		 * 2.	Validate that the queue is not already linked
		 *		(and resources available).
		 * 3.	Validate that the lower queue is not associated with
		 *		a logical unit.
		 * 4.	Remember that this lower queue is linked to the driver.
		 */
		if ((linkp == NULL) || (MBLKL(mp) < sizeof (*linkp)) ||
		    linkp->l_qbot == NULL) {
			sm_dbg('I', ("sm_link_req: invalid link block.\n"));
			rval = EINVAL;
		} else if ((plqi = get_lqi_byq(linkp->l_qbot)) == 0) {
			sm_dbg('I', ("sm_link_req: out of resources.\n"));
			rval = EBUSY; /* out of resources */
		} else if (plqi->sm_uqi) {
			UNLOCK_UNIT(plqi); /* was aquired by get_lqi_byq */
			sm_dbg('I', ("sm_link_req: already associated.\n"));
			rval = EBUSY; /* already linked */
		} else {
			SM_WQ(plqi) = linkp->l_qbot;
			SM_RQ(plqi)	= OTHERQ(linkp->l_qbot);

			linkp->l_qbot->q_ptr =
			    OTHERQ(linkp->l_qbot)->q_ptr = plqi;
			plqi->sm_linkid = linkp->l_index;
			UNLOCK_UNIT(plqi); /* was aquired by get_lqi_byq */

			sm_dbg('H', ("sm_link_req: linkid = %d.\n",
			    linkp->l_index));

			sm_setdip(linkp->l_qbot, plqi);
			plqi->sm_ttycommon->t_flags = 0;
			plqi->sm_ttycommon->t_cflag = 0;
			plqi->sm_mbits = 0;
			(void) ttymux_device_init(plqi);
			rval = 0;
		}

		break;

	case I_UNLINK:
	case I_PUNLINK:
		sm_dbg('G', ("sm_link_req: M_IOCTL (I_PUNLINK).\n"));

		linkp = (struct linkblk *)mp->b_cont->b_rptr;

		if ((linkp == NULL) ||
		    (MBLKL(mp) < sizeof (*linkp)) ||
		    linkp->l_qbot == NULL) {
			rval = EINVAL;
		} else if ((plqi = get_lqi_byid(linkp->l_index)) == 0) {
			rval = EINVAL;
		} else {
			sm_uqi_t *uqi;
			int werrmode;

			/*
			 * Mark the lower q as invalid.
			 */
			sm_dbg('G', ("I_PUNLINK: freeing link %d\n",
			    linkp->l_index));

			if (plqi->sm_bid) {
				qunbufcall(SM_RQ(plqi), plqi->sm_bid);
				plqi->sm_bid = 0;
			}
			if (plqi->sm_ttybid) {
				qunbufcall(SM_RQ(plqi), plqi->sm_ttybid);
				plqi->sm_ttybid = 0;
			}

			uqi = plqi->sm_uqi;


			(void) ttymux_device_fini(plqi);

			if (uqi)
				(void) sm_disassociate(uqi->sm_lunit,
				    plqi, plqi->sm_tag);

			LOCK_UNIT(plqi);

			plqi->sm_piocid = 0;

			werrmode = (plqi->sm_flags & (WERROR_MODE|HANGUP_MODE))
			    ? 1 : 0;

			plqi->sm_mbits = 0;
			plqi->sm_flags = 0;

			ttycommon_close(plqi->sm_ttycommon);
			/* SM_RQ(plqi) = SM_WQ(plqi) = 0; */
			plqi->sm_ttycommon->t_flags = 0;
			plqi->sm_ttycommon->t_cflag = 0;
			plqi->sm_ttycommon->t_iflag = 0;
			plqi->sm_linkid = 0;
			plqi->sm_dev = NODEV;
			plqi->sm_hadkadbchar = 0;
			plqi->sm_nachar = sm_ssp->sm_abs;

			UNLOCK_UNIT(plqi);
			if (uqi &&
			    werrmode &&
			    (uqi->sm_flags & FULLY_OPEN) &&
			    sm_uwq_error(uqi) &&
			    putnextctl(SM_RQ(uqi), M_HANGUP) == 0) {
				sm_log("sm_link_req: putnextctl(M_HANGUP)"
				    " failed.\n");
			}

			rval = 0;
		}

		break;
	default:
		rval = EINVAL;
	}
	if (rval != 0)
		miocnak(wq, mp, 0, rval);
	else
		miocack(wq, mp, 0, 0);
}

static int
sm_getiocinfo(mblk_t *mp, struct sm_iocinfo *info)
{
	switch (DB_TYPE(mp)) {
	case M_COPYOUT:
		info->sm_id = ((struct copyreq *)mp->b_rptr)->cq_id;
		info->sm_cmd = ((struct copyreq *)mp->b_rptr)->cq_cmd;
		info->sm_data = (((struct copyreq *)mp->b_rptr)->cq_size &&
		    mp->b_cont) ? (void *)mp->b_cont->b_rptr : 0;
		break;
	case M_COPYIN:
		info->sm_id = ((struct copyresp *)mp->b_rptr)->cp_id;
		info->sm_cmd = ((struct copyresp *)mp->b_rptr)->cp_cmd;
		info->sm_data = 0;
		break;
	case M_IOCACK:
		info->sm_id = ((struct iocblk *)mp->b_rptr)->ioc_id;
		info->sm_cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd;
		/* the se driver has bug so we cannot use ioc_count */
		info->sm_data = (((struct iocblk *)mp->b_rptr)->
		    ioc_error == 0 && mp->b_cont) ?
		    (void *)mp->b_cont->b_rptr : 0;
		break;
	case M_IOCNAK:
		info->sm_id = ((struct iocblk *)mp->b_rptr)->ioc_id;
		info->sm_cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd;
		info->sm_data = 0;
		break;
	case M_IOCDATA:
		info->sm_id = ((struct copyresp *)mp->b_rptr)->cp_id;
		info->sm_cmd = ((struct copyresp *)mp->b_rptr)->cp_cmd;
		info->sm_data = (((struct copyresp *)mp->b_rptr)->
		    cp_rval == 0 && mp->b_cont) ?
		    (void *)mp->b_cont->b_rptr : 0;
		break;
	case M_IOCTL:
		info->sm_id = ((struct iocblk *)mp->b_rptr)->ioc_id;
		info->sm_cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd;
		info->sm_data = 0;
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

/*
 * Record the termio settings that have been set on the upper stream
 */
static int
sm_update_ttyinfo(mblk_t *mp, sm_uqi_t *uqi)
{
	int err;
	struct sm_iocinfo info;

	if ((err = sm_getiocinfo(mp, &info)) != 0)
		return (err);

	switch (info.sm_cmd) {
	case TIOCSPPS:
	case TIOCGPPS:
	case TIOCGPPSEV:
		return (ENOTSUP);
	case TIOCGWINSZ:
	case TIOCSWINSZ:
		break;
	case TCSBRK:
	case TIOCSBRK:
	case TIOCCBRK:
		break;
	case TCSETSF:
		uqi->sm_flags |= FLUSHR_PEND;
		sm_dbg('I', ("TCSETSF: FLUSH is pending\n"));
		/*FALLTHROUGH*/
	case TCSETSW:
	case TCSETS:
	case TCGETS:
		if (info.sm_data != 0) {
			((struct termios *)info.sm_data)->c_cflag &=
			    (tcflag_t)(~uqi->sm_cmask);
			uqi->sm_ttycommon->t_cflag =
			    ((struct termios *)info.sm_data)->c_cflag;
		}
		break;
	case TCSETAF:
		sm_dbg('I', ("TCSETAF: FLUSH is pending\n"));
		uqi->sm_flags |= FLUSHR_PEND;
		/*FALLTHROUGH*/
	case TCSETAW:
	case TCSETA:
	case TCGETA:
		if (info.sm_data != 0) {
			((struct termio *)info.sm_data)->c_cflag &=
			    (tcflag_t)(~uqi->sm_cmask);
			uqi->sm_ttycommon->t_cflag =
			    (tcflag_t)((struct termio *)info.sm_data)->c_cflag;
		}
		break;
	case TIOCSSOFTCAR:
	case TIOCGSOFTCAR:
		if (info.sm_data != 0) {
			if (*(int *)info.sm_data == 1)
				uqi->sm_ttycommon->t_flags |= TS_SOFTCAR;
			else
				uqi->sm_ttycommon->t_flags &= ~TS_SOFTCAR;
		}
		break;
	case TIOCMSET:
	case TIOCMGET:
		if (info.sm_data != 0)
			uqi->sm_mbits = *(int *)info.sm_data;
		break;
	case TIOCMBIS:
		if (info.sm_data != 0)
			uqi->sm_mbits |= *(int *)info.sm_data;
		break;
	case TIOCMBIC:
		if (info.sm_data != 0)
			uqi->sm_mbits &= ~(*(int *)info.sm_data);
		break;
	default:
		return (EINVAL);
		/* NOTREACHED */
	} /* end switch cmd */

	if ((uqi->sm_mbits & TIOCM_CD) ||
	    (uqi->sm_ttycommon->t_flags & TS_SOFTCAR) ||
	    (uqi->sm_ttycommon->t_cflag & CLOCAL))
		uqi->sm_flags |= SM_CARON;
	else
		uqi->sm_flags &= ~SM_CARON;

	return (0);
}

/*
 * SECTION
 * STREAM's interface to the OS.
 * Routines directly callable from the OS.
 */

/*
 * Processes high priority messages comming from modules above the
 * multiplexor.
 * Return 1 if the queue was disabled.
 */
static int
sm_hp_uwput(queue_t *wq, mblk_t *mp)
{
	sm_uqi_t	*uqi = (sm_uqi_t *)(wq->q_ptr);
	int	rval = 0;
	sm_lqi_t	*plqi;
	int	msgtype = DB_TYPE(mp);

	switch (msgtype) {

	case M_FLUSH:
		/*
		 * How to flush the bottom half:
		 * putctl1(SM_WQ(plqi), *mp->b_rptr)
		 * will work on the bottom half but if FLUSHR is set
		 * when is the right time to flush the upper read queue.
		 *
		 * Could set uqi->sm_flags & WANT_FLUSH but then what happens
		 * if FLUSHR is set and the driver sends up a FLUSHR
		 * before it handles the current FLUSHR request
		 * (if only there was an id for the message that could
		 * be matched when it returns back from the drivers.
		 *
		 * Thus I'm going by the book - the bottom half acts like
		 * a stream head and turns around FLUSHW back down to
		 * the driver (see lrput). The upper half acts like a
		 * driver and turns around FLUSHR:
		 */

		sm_dbg('I', ("sm_hp_uwput: FLUSH request 0x%x\n", *mp->b_rptr));
		/* flush the upper write queue */
		if (*mp->b_rptr & FLUSHW)
			flushq(wq, FLUSHDATA);

		/*
		 * flush each associated lower write queue
		 * and pass down the driver (ignore the FLUSHR and deal with
		 * it when it comes back up the read side.
		 */
		for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) {
			if ((plqi->sm_flags & WERROR_MODE) == 0 &&
			    SM_WQ(plqi)) {
				sm_dbg('I', ("flush lq 0x%p\n", SM_WQ(plqi)));
				if (*mp->b_rptr & FLUSHW)
					flushq(SM_WQ(plqi), FLUSHDATA);
				(void) putnextctl1(SM_WQ(plqi), M_FLUSH,
				    *mp->b_rptr);
			}
		}
		break;

	case M_STARTI:
		for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) {
			plqi->sm_flags &= ~SM_ISTOPPED;
			if ((plqi->sm_flags & WERROR_MODE) == 0)
				(void) putnextctl(SM_WQ(plqi), msgtype);
		}
		break;

	case M_STOPI:
		for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) {
			plqi->sm_flags |= SM_ISTOPPED;
			if ((plqi->sm_flags & WERROR_MODE) == 0)
				(void) putnextctl(SM_WQ(plqi), msgtype);
		}
		break;

	case M_STOP:	/* must never be queued */
		uqi->sm_flags |= SM_STOPPED;
		noenable(wq);
		for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi)
			if ((plqi->sm_flags & WERROR_MODE) == 0)
				(void) putnextctl(SM_WQ(plqi), msgtype);

		rval = 1;
		break;

	case M_START:	/* never be queued */
		uqi->sm_flags &= ~SM_STOPPED;
		enableok(wq);
		qenable(wq);
		for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi)
			if ((plqi->sm_flags & WERROR_MODE) == 0)
				(void) putnextctl(SM_WQ(plqi), msgtype);

		break;

	case M_PCSIG:
	case M_COPYOUT:
	case M_COPYIN:
	case M_IOCACK:
	case M_IOCNAK:
		/* Wrong direction for message */
		break;
	case M_READ:
		break;
	case M_PCPROTO:
	case M_PCRSE:
	default:
		sm_dbg('I', ("sm_hp_uwput: default case %d.\n", msgtype));
		break;
	} /* end switch on high pri message type */

	freemsg(mp);
	return (rval);
}

static int
sm_default_uwioctl(queue_t *wq, mblk_t *mp, int (*qfn)())
{
	int	err;
	struct iocblk	*iobp;
	sm_uqi_t	*uqi;

	uqi = (sm_uqi_t *)(wq->q_ptr);
	iobp = (struct iocblk *)mp->b_rptr;

	switch (iobp->ioc_cmd) {
	case TIOCEXCL:
	case TIOCNXCL:
	case TIOCSTI:
		/*
		 * The three ioctl types we support do not require any
		 * additional allocation and should not return a pending
		 * ioctl state. For this reason it is safe for us to ignore
		 * the return value from ttycommon_ioctl().
		 * Additionally, we translate any error response from
		 * ttycommon_ioctl() into EINVAL.
		 */
		(void) ttycommon_ioctl(uqi->sm_ttycommon, wq, mp, &err);
		if (err < 0)
			miocnak(wq, mp, 0, EINVAL);
		else
			miocack(wq, mp, 0, 0);
		return (0);
	default:
		break;
	}
	if ((err = sm_update_ttyinfo(mp, uqi)) != 0) {
		miocnak(wq, mp, 0, err);
		return (0);
	}

	/*
	 * If uqi->sm_siocdata.sm_iocid just overwrite it since the stream
	 * head will have timed it out
	 */
	uqi->sm_siocdata.sm_iocid = iobp->ioc_id;
	uqi->sm_siocdata.sm_acked = 0;
	uqi->sm_siocdata.sm_nacks = sm_good_qs(uqi);
	uqi->sm_siocdata.sm_acnt = 0;
	uqi->sm_siocdata.sm_policy = uqi->sm_policy;
	uqi->sm_siocdata.sm_flags = 0;
	sm_dbg('Z', (" want %d acks for id %d.\n",
	    uqi->sm_siocdata.sm_nacks, iobp->ioc_id));

	return (sm_putqs(wq, mp, qfn));
}

/*
 *
 * sm_uwput - put function for an upper STREAM write.
 */
static int
sm_uwput(queue_t *wq, mblk_t *mp)
{
	sm_uqi_t		*uqi;
	uchar_t		msgtype;
	int		cmd;
	struct iocblk	*iobp;

	uqi = (sm_uqi_t *)(wq->q_ptr);
	msgtype = DB_TYPE(mp);

	ASSERT(uqi != 0 && sm_ssp != 0);

	if (msgtype >= QPCTL && msgtype != M_IOCDATA) {
		(void) sm_hp_uwput(wq, mp);
		return (0);
	}

	switch (DB_TYPE(mp)) {
	case M_DATA:
	case M_DELAY:
	case M_BREAK:
	default:
		(void) sm_putqs(wq, mp, putq);
		break;

	case M_CTL:
		if (((struct iocblk *)mp->b_rptr)->ioc_cmd == MC_CANONQUERY) {
			(void) putnextctl1(OTHERQ(wq), M_CTL, MC_NOCANON);
		}
		freemsg(mp);
		break;
	case M_IOCDATA: /* not handled as high pri because may need to putbq */
		sm_dbg('M', ("sm_uwput(M_IOCDATA)\n"));
		/*FALLTHROUGH*/
	case M_IOCTL:
		cmd = (msgtype == M_IOCDATA) ?
		    ((struct copyresp *)mp->b_rptr)->cp_cmd :
		    ((struct iocblk *)mp->b_rptr)->ioc_cmd;

		iobp = (struct iocblk *)mp->b_rptr;
		iobp->ioc_rval = 0;

		sm_dbg('M', ("sm_uwput(M_IOCTL:%d)\n", cmd));

		switch (cmd) {

		case CONSGETABORTENABLE:
			iobp->ioc_error = ttymux_abort_ioctl(mp);
			DB_TYPE(mp) = iobp->ioc_error ? M_IOCNAK : M_IOCACK;
			qreply(wq, mp);
			break;
		case CONSSETABORTENABLE:
			iobp->ioc_error =
			    secpolicy_sys_config(iobp->ioc_cr, B_FALSE) != 0 ?
			    EPERM : ttymux_abort_ioctl(mp);
			DB_TYPE(mp) = iobp->ioc_error ? M_IOCNAK : M_IOCACK;
			qreply(wq, mp);
			break;
		case TTYMUX_SETABORT:
			if (secpolicy_sys_config(iobp->ioc_cr, B_FALSE) != 0) {
				iobp->ioc_error = EPERM;
				DB_TYPE(mp) = M_IOCNAK;
				qreply(wq, mp);
				break;
			}
			/*FALLTHROUGH*/
		case TTYMUX_GETABORT:
		case TTYMUX_GETABORTSTR:
		case TTYMUX_ASSOC:
		case TTYMUX_DISASSOC:
		case TTYMUX_SETCTL:
		case TTYMUX_GETLINK:
		case TTYMUX_CONSDEV:
		case TTYMUX_GETCTL:
		case TTYMUX_LIST:
			(void) sm_ioctl_cmd(uqi, mp);
			qreply(wq, mp);
			break;
		case I_LINK:
		case I_PLINK:
		case I_UNLINK:
		case I_PUNLINK:
			qwriter(wq, mp, sm_link_req, PERIM_OUTER);
			break;
		case TCSETSW:
		case TCSETSF:
		case TCSETAW:
		case TCSETAF:
		case TCSBRK:
			if (wq->q_first) {
				sm_dbg('A', ("sm_uwput: TCSET-> on srv q.\n"));
				/* keep message order intact */
				(void) putq(wq, mp);
				break;
			}
			/*FALLTHROUGH*/
		default:
			(void) sm_default_uwioctl(wq, mp, putq);
			break;
		}

		break; /* M_IOCTL */

	} /* end switch on message type */

	return (0);
}

/*
 * sm_uwsrv - service function for an upper STREAM write.
 * 'sm_uwsrv' takes a q parameter.	The q parameter specifies the queue
 * which is to be serviced.	This function reads the messages which are on
 * this service queue and passes them to the appropriate lower driver queue.
 */
static int
sm_uwsrv(queue_t *q)
{
	mblk_t	*mp;
	sm_uqi_t	*uqi = (sm_uqi_t *)(q->q_ptr);
	int		msgtype;

	ASSERT(q == SM_WQ(uqi));

	/*
	 * Empty the queue unless explicitly stopped.
	 */
	while (mp = getq(q)) {
		msgtype = DB_TYPE(mp);

		if (msgtype >= QPCTL && msgtype != M_IOCDATA)
			if (sm_hp_uwput(q, mp)) {
				sm_dbg('T', ("sm_uwsrv: flowcontrolled.\n"));
				break; /* indicates that the is disabled */
			}
			else
				continue;

		if (uqi->sm_flags & SM_STOPPED) {
			(void) putbq(q, mp);
			sm_dbg('T', ("sm_uwsrv: SM_STOPPED.\n"));
			break;
		}

		/*
		 * Read any ttycommon data that may
		 * change (TS_SOFTCAR, CREAD, etc.).
		 */
		switch (DB_TYPE(mp)) {
		case M_IOCTL:
		case M_IOCDATA:
			if (sm_default_uwioctl(q, mp, putbq))
				return (0);
			break;

		default:
			if (sm_putqs(q, mp, putbq))
				return (0);
		}
	}
	return (0);
}

/*
 * Lower write side service routine used for backenabling upstream
 * flow control.
 */
static int
sm_lwsrv(queue_t *q)
{
	sm_lqi_t *lqi = (sm_lqi_t *)q->q_ptr;
	queue_t *uwq;

	LOCK_UNIT(lqi);
	if (lqi->sm_uqflags & SM_UQVALID) {
		/*
		 * It's safe to lock uqi since lwsrv runs asynchronously
		 * with the upper write routines so this cannot be an
		 * upper half thread. While holding the lqi lock and
		 * if SM_UQVALID is set we are guaranteed that
		 * lqi->sm_uqi will be valid.
		 */
		sm_dbg('I', ("sm_lwsrv: re-enabling upper queue.\n"));

		uwq = SM_WQ(lqi->sm_uqi);
		UNLOCK_UNIT(lqi);
		qenable(uwq);
	} else  {
		UNLOCK_UNIT(lqi);
	}
	return (0);
}

/*
 * Upper read queue ioctl response handler for messages
 * passed from the lower half of the driver.
 */
static int
sm_uriocack(queue_t *rq, mblk_t *mp)
{
	sm_uqi_t		*uqi = (sm_uqi_t *)rq->q_ptr;
	int		err, flag;
	sm_iocdata_t	*iodp;
	struct sm_iocinfo	info;

	if ((err = sm_getiocinfo(mp, &info)) != 0) {
		sm_dbg('I', ("Unknown ioctl response\n"));
		return (err);
	}

	if (info.sm_id == uqi->sm_piocdata.sm_iocid) {
		iodp = &uqi->sm_piocdata;
	} else if (info.sm_id == uqi->sm_siocdata.sm_iocid) {
		iodp = &uqi->sm_siocdata;
	} else {
		sm_log("Unexpected ioctl response\n");
		sm_dbg('I', ("Unexpected ioctl response (id %d)\n",
		    info.sm_id));

		/*
		 * If the response is sent up it will result in
		 * duplicate ioctl responses. The ioctl has probably been
		 * timed out by the stream head so dispose of the response
		 * (since it has arrived too late.
		 */
		goto out;
	}

	flag = SM_COPYIN;

	switch (DB_TYPE(mp)) {
	case M_COPYOUT:
		flag = SM_COPYOUT;
		/*FALLTHRU*/
	case M_COPYIN:
		if (iodp->sm_flags & flag)
			goto out;
		iodp->sm_flags |= flag;

		break;
	case M_IOCACK:
		iodp->sm_ackcnt += 1;
		iodp->sm_acnt += 1;
		if (iodp->sm_policy == FIRSTACK) {
			if (iodp->sm_acnt == iodp->sm_nacks)
				iodp->sm_iocid = 0;
			if (iodp->sm_acnt == 1)
				iodp->sm_acked = 1;
			else
				goto out;
		} else {
			if (iodp->sm_acnt == iodp->sm_nacks) {
				iodp->sm_iocid = 0;
				iodp->sm_acked = 1;
			} else
				goto out;
		}
		break;
	case M_IOCNAK:
		iodp->sm_nakcnt += 1;
		iodp->sm_acnt += 1;
		if (iodp->sm_acnt == iodp->sm_nacks) {
			iodp->sm_iocid = 0;
			if (iodp->sm_acked == 0) {
				iodp->sm_acked = 1;
				break;
			}
		}
		goto out;
	default:
		goto out;
	}

	/*
	 * Merge the tty settings each of the associated lower streams.
	 */
	if (info.sm_data)
		(void) sm_update_ttyinfo(mp, uqi);

	if (iodp == &uqi->sm_piocdata) {
		if (iodp->sm_iocid == 0) {
			uqi->sm_flags &= ~SM_IOCPENDING;
		}
	} else {
		sm_dbg('I', ("sm_uriocack: forwarding response for %d.\n",
		    info.sm_id));
		putnext(rq, mp);
		return (0);
	}
out:
	sm_dbg('I', ("sm_uriocack: freeing response for %d.\n", info.sm_id));
	freemsg(mp);
	return (0);
}

/*
 * Transfer a message from the lower read side of the multiplexer onto
 * the associated upper stream.
 */
static int
sm_ursendup(queue_t *q, mblk_t *mp)
{
	sm_uqi_t	*uqi = (sm_uqi_t *)q->q_ptr;

	if (!canputnext(q) && DB_TYPE(mp) < QPCTL) {
		sm_dbg('I', ("sm_ursendup: flow controlled.\n"));
		return (1);
	}

	switch (DB_TYPE(mp)) {
	case M_COPYIN:
	case M_COPYOUT:
	case M_IOCACK:
	case M_IOCNAK:
		(void) sm_uriocack(q, mp);
		break;
	case M_HANGUP:
		if (sm_uwq_error(uqi)) {
			/* there are no usable lower q's */
			uqi->sm_flags &= ~SM_CARON;
			putnext(q, mp);
		} else {
			/* there are still usable q's - don't send up */
			freemsg(mp);
		}
		break;
	case M_ERROR:
		if (sm_uwq_error(uqi)) {
			/* there are no usable lower q's */
			uqi->sm_flags &= ~SM_CARON;
			putnext(q, mp);
		} else if (*mp->b_rptr == NOERROR) {
			/* the error has cleared */
			uqi->sm_flags &= ~ERROR_MODE;
			putnext(q, mp);
		} else {
			/* there are still usable q's - don't send up */
			freemsg(mp);
		}
		break;
	case M_FLUSH:
		flushq(q, FLUSHDATA);
		putnext(q, mp);	/* time to use FLUSHR_PEND flag */
		break;
	case M_CTL:
		/* wrong direction - must have come from sm_close */
		uqi->sm_flags |= SM_CLOSE;
		sm_dbg('I', ("sm_ursrv: had SM_CLOSE.\n"));
		freemsg(mp);
		break;
	case M_UNHANGUP:
		/* just pass them all up - they're harmless */
		uqi->sm_flags |= SM_CARON;
		/* FALLTHROUGH */
	default:
		putnext(q, mp);
		break;
	}

	return (0);
}

/*
 * sm_urput - put function for a lower STREAM read.
 */
static int
sm_urput(queue_t *q, mblk_t *mp)
{
	if (sm_ursendup(q, mp) != 0)
		(void) putq(q, mp);

	return (0);
}

/*
 * Upper read side service routine.
 * Read side needs to be fast so only check for duplicate M_IOCTL acks.
 */
static int
sm_ursrv(queue_t *q)
{
	sm_uqi_t	*uqi = (sm_uqi_t *)q->q_ptr;
	mblk_t	*mp;
	int	flags = uqi->sm_flags;

	while ((mp = getq(q))) {
		if (sm_ursendup(q, mp) != 0) {
			sm_dbg('I', ("sm_ursrv: flow controlled.\n"));
			(void) putbq(q, mp);
			uqi->sm_flags |= WANT_RENB;
			break;
		}
	}

	/*
	 * If the q service was called because it was no longer
	 * flow controled then enable each of the driver queues.
	 */
	if ((flags & WANT_RENB) && !(uqi->sm_flags & WANT_RENB)) {
		sm_lqi_t *lqi;
		queue_t *drq; /* read q of linked driver */

		uqi->sm_flags &= ~WANT_RENB;
		for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) {
			drq = SM_RQ(lqi)->q_next;
			if (drq && drq->q_first != 0)
				qenable(drq);
		}
	}

	return (0);
}

/*
 * Check a message sent from a linked device for abort requests and
 * for flow control.
 */
static int
sm_lrmsg_check(queue_t *q, mblk_t *mp)
{
	sm_lqi_t	*lqi	= (sm_lqi_t *)q->q_ptr;

	switch (DB_TYPE(mp)) {
	case M_DATA:
		LOCK_UNIT(lqi);
		/*
		 * check for abort - only allow abort on I/O consoles
		 * known to OBP -
		 * fix it when we do polled io
		 */
		if ((lqi->sm_ioflag & (uint_t)FORINPUT) == 0) {
			freemsg(mp);
			UNLOCK_UNIT(lqi);
			return (1);
		}
		if ((lqi->sm_uqflags & SM_OBPCNDEV) &&
		    lqi->sm_ctrla_abort_on &&
		    abort_enable == KIOCABORTALTERNATE) {

			uchar_t		*rxc;
			boolean_t	aborted = B_FALSE;

			for (rxc = mp->b_rptr;
			    rxc != mp->b_wptr;
			    rxc++)

				if (*rxc == *lqi->sm_nachar) {
					lqi->sm_nachar++;
					if (*lqi->sm_nachar == '\0') {
						abort_sequence_enter(
						    (char *)NULL);
						lqi->sm_nachar = sm_ssp->sm_abs;
						aborted = B_TRUE;
					}
				} else
					lqi->sm_nachar = (*rxc == *sm_ssp->
					    sm_abs) ?
					    sm_ssp->
					    sm_abs + 1 :
					    sm_ssp->sm_abs;

			if (aborted) {
				freemsg(mp);
				UNLOCK_UNIT(lqi);
				return (1);
			}
		}
		UNLOCK_UNIT(lqi);
		break;
	case M_BREAK:	/* we'll eventually see this as a flush */
		LOCK_UNIT(lqi);
		/*
		 * Only allow abort on OBP devices. When polled I/O is
		 * supported allow abort on any console device.
		 * Parity errors are reported upstream as breaks so
		 * ensure that there is no data in the message before
		 * deciding whether to abort.
		 */
		if ((lqi->sm_uqflags & SM_OBPCNDEV) && /* console stream */
		    (mp->b_wptr - mp->b_rptr == 0 &&
		    msgdsize(mp) == 0)) {	/* not due to parity */

			if (lqi->sm_break_abort_on &&
			    abort_enable != KIOCABORTALTERNATE)
				abort_sequence_enter((char *)NULL);

			freemsg(mp);
			UNLOCK_UNIT(lqi);
			return (1);
		} else {
			UNLOCK_UNIT(lqi);
		}
		break;
	default:
		break;
	}

	if (DB_TYPE(mp) >= QPCTL)
		return (0);

	LOCK_UNIT(lqi); /* lock out the upper half */
	if ((lqi->sm_uqflags & SM_UQVALID) && SM_RQ(lqi->sm_uqi)) {
		UNLOCK_UNIT(lqi);
		if (!canput(SM_RQ(lqi->sm_uqi))) {
			sm_dbg('I', ("sm_lrmsg_check: flow controlled.\n"));
			(void) putq(q, mp);
			return (1);
		}
	} else {
		UNLOCK_UNIT(lqi);
	}

	return (0);
}

/*
 * sm_sendup - deliver a message to the upper read side of the multiplexer
 */
static int
sm_sendup(queue_t *q, mblk_t *mp)
{
	sm_lqi_t	*lqi	= (sm_lqi_t *)q->q_ptr;

	if (sm_ssp == NULL) {
		freemsg(mp);
		return (0);
	}

	/*
	 * Check for CD status change messages from driver.
	 * (Remark: this is an se driver thread running at soft interupt
	 * priority and the waiters are in user context).
	 */
	switch (DB_TYPE(mp)) {
	case M_DATA:
	case M_BREAK:	/* we'll eventually see this as a flush */
		break;

	/* high priority messages */
	case M_IOCACK:
	case M_IOCNAK:
		if ((lqi->sm_flags & SM_IOCPENDING) && lqi->sm_piocid ==
		    ((struct iocblk *)mp->b_rptr)->ioc_id) {
			freemsg(mp);
			lqi->sm_flags &= ~SM_IOCPENDING;
			sm_issue_ioctl(lqi);
			return (0);
		}
		break;
	case M_UNHANGUP:
		/*
		 * If the driver can send an M_UNHANGUP it must be able to
		 * accept messages from above (ie clear WERROR_MODE if set).
		 */
		sm_dbg('E', ("lrput: M_UNHANGUP\n"));
		lqi->sm_mbits |= TIOCM_CD;
		lqi->sm_flags &= ~(WERROR_MODE|HANGUP_MODE);

		break;

	case M_HANGUP:
		sm_dbg('E', ("lrput: MHANGUP\n"));
		lqi->sm_mbits &= ~TIOCM_CD;
		lqi->sm_flags |= (WERROR_MODE|HANGUP_MODE);
		break;

	case M_ERROR:

		sm_dbg('E', ("lrput: MERROR\n"));
		/*
		 * Tell the driver to flush rd/wr queue if its read/write error.
		 * if its a read/write error flush rq/wq (type in first bytes).
		 */
		if ((mp->b_wptr - mp->b_rptr) == 2) {
			uchar_t	rw = 0;

			if (*mp->b_rptr == NOERROR) {
				/* not in error anymore */
				lqi->sm_flags &= ~ERROR_MODE;
				lqi->sm_flags |= WANT_CD;
			} else {
				if (*mp->b_rptr != 0) {
					/* read error */
					rw |= FLUSHR;
					lqi->sm_flags |= RERROR_MODE;
				}
				mp->b_rptr++;
				if (*mp->b_rptr != 0) {
					/* write error */
					rw |= FLUSHW;
					lqi->sm_flags |= WERROR_MODE;
				}

				mp->b_rptr--;
				/* has next driver done qprocsoff */
				if (rw && OTHERQ(q)->q_next != NULL) {
					(void) putnextctl1(OTHERQ(q), M_FLUSH,
					    rw);
				}
			}
		} else if (*mp->b_rptr != 0 && OTHERQ(q)->q_next != NULL) {
			sm_dbg('E', ("lrput: old style MERROR (?)\n"));

			lqi->sm_flags |= (RERROR_MODE | WERROR_MODE);
			(void) putnextctl1(OTHERQ(q), M_FLUSH, FLUSHRW);
		}
		break;

	case M_PCSIG:
	case M_SIG:
		break;
	case M_COPYOUT:
	case M_COPYIN:
		break;
	case M_FLUSH:
		/* flush the read queue and pass on up */
		flushq(q, FLUSHDATA);
		break;
	default:
		break;
	}

	LOCK_UNIT(lqi); /* lock out the upper half */
	if (lqi->sm_uqflags & SM_UQVALID && SM_RQ(lqi->sm_uqi)) {
		UNLOCK_UNIT(lqi);
		(void) putq(SM_RQ(lqi->sm_uqi), mp);
		return (0);
	} else {
		sm_dbg('I', ("sm_sendup: uq not valid\n"));
		freemsg(mp);
	}
	UNLOCK_UNIT(lqi);

	return (0);
}

/*
 * sm_lrput - put function for a lower STREAM read.
 */
static int
sm_lrput(queue_t *q, mblk_t *mp)
{
	if (sm_lrmsg_check(q, mp) == 0)
		(void) sm_sendup(q, mp);
	return (0);
}

/*
 * sm_lrsrv - service function for the lower read STREAM.
 */
static int
sm_lrsrv(queue_t *q)
{
	mblk_t	*mp;

	sm_dbg('I', ("sm_lrsrv: not controlled.\n"));
	while (mp = getq(q))
		(void) sm_sendup(q, mp);

	return (0);
}

/*
 * Check whether a thread is allowed to open the requested device.
 */
static int
sm_ok_to_open(sm_uqi_t *uqi, int protocol, cred_t *credp, int *abort_waiters)
{
	int rval = 0;
	int proto;

	*abort_waiters = 0;

	switch (protocol) {
		case ASYNC_DEVICE: /* Standard async protocol */
		if ((uqi->sm_protocol == NULL_PROTOCOL) ||
		    (uqi->sm_protocol == ASYN_PROTOCOL)) {
			/*
			 * Lock out other incompatible protocol requests.
			 */
			proto = ASYN_PROTOCOL;
			rval = 0;
		} else
			rval = EBUSY;
		break;

		case OUTLINE:	/* Outdial protocol */
		if ((uqi->sm_protocol == NULL_PROTOCOL) ||
		    (uqi->sm_protocol == OUTD_PROTOCOL)) {
			proto = OUTD_PROTOCOL;
			rval = 0;
		} else if (uqi->sm_protocol == ASYN_PROTOCOL) {
			/*
			 * check for dialout request on a line that is already
			 * open for dial in:
			 * kick off any thread that is waiting to fully open
			 */
			if (uqi->sm_flags & FULLY_OPEN)
				rval = EBUSY;
			else {
				proto = OUTD_PROTOCOL;
				*abort_waiters = 1;
			}
		} else
			rval = EBUSY;
		break;
		default:
			rval = ENOTSUP;
	}

	if (rval == 0 &&
	    (uqi->sm_ttycommon->t_flags & TS_XCLUDE) &&
	    secpolicy_excl_open(credp) != 0) {

		if (uqi->sm_flags & FULLY_OPEN) {
			rval = EBUSY; /* exclusive device already open */
		} else {
			/* NB TS_XCLUDE cant be set during open so NOTREACHED */
			/* force any waiters to yield TS_XCLUDE */
			*abort_waiters = 1;
		}
	}

	if (rval == 0)
		uqi->sm_protocol = proto;

	sm_dbg('A', ("ok_to_open (0x%p, %d) proto=%d rval %d (wabort=%d)",
	    uqi, protocol, uqi->sm_protocol, rval, *abort_waiters));

	return (rval);
}

/* wait for memory to become available whilst performing a qwait */
/*ARGSUSED*/
static void dummy_callback(void *arg)
{}

/* ARGSUSED */
static int
sm_dump_msg(queue_t *q, mblk_t *mp)
{
	freemsg(mp);
	return (0);
}

/*
 * Wait for a message to arrive - must be called with exclusive
 * access at the outer perimiter.
 */
static int
sm_qwait_sig(sm_uqi_t *uqi, queue_t *q)
{
	int err;

	sm_dbg('C', ("sm_qwait_sig: waiting.\n"));

	uqi->sm_waitq = q;
	uqi->sm_nwaiters++;	/* required by the close routine */
	err = qwait_sig(q);
	if (--uqi->sm_nwaiters == 0)
		uqi->sm_waitq = 0;

	if (err == 0)
		err = EINTR;
	else if (q->q_ptr == 0) /* can happen if there are multiple waiters */
		err = -1;
	else if (uqi->sm_flags & SM_CLOSE) {
		uqi->sm_flags &= ~SM_CLOSE;
		err = 1;	/* a different protocol has closed its stream */
	}
	else
		err = 0;	/* was worth waiting for */

	sm_dbg('C', ("sm_qwait_sig: rval %d\n", err));
	return (err);
}

/*
 * Defer the opening of one the drivers devices until the state of each
 * associated lower stream is known.
 */
static int
sm_defer_open(sm_uqi_t *uqi, queue_t *q)
{
	uint_t cmdflags = WANT_CDSTAT;
	int err, nqs;

	while ((nqs = sm_good_qs(uqi)) == 0) {
		sm_dbg('C', ("sm_defer_open: no good qs\n"));
		if (err = sm_qwait_sig(uqi, q))
			return (err);
	}

	while ((uqi->sm_flags & SM_CARON) == 0) {
		int iocmd;
		mblk_t *pioc;

		sm_dbg('C', ("sm_defer_open: flags 0x%x cmdflags 0x%x\n",
		    uqi->sm_flags, cmdflags));
		if (cmdflags == 0) {
			if (err = sm_qwait_sig(uqi, q))
				return (err);
			continue;	/* waiting for an M_UNHANGUP */
		} else if (cmdflags & WANT_SC) {
			cmdflags &= ~WANT_SC;
			iocmd = TIOCGSOFTCAR;
		} else if (cmdflags & WANT_CD) {
			cmdflags &= ~WANT_CD;
			iocmd = TIOCMGET;
		} else if (cmdflags & WANT_CL) {
			cmdflags &= ~WANT_CL;
			iocmd = TCGETS;
		}

		if (uqi->sm_piocdata.sm_iocid == 0) {
			while ((pioc = mkiocb(iocmd)) == 0) {
				bufcall_id_t id =
				    qbufcall(q, sizeof (struct iocblk),
				    BPRI_MED, dummy_callback, 0);
				if (err = sm_qwait_sig(uqi, q)) {
					/* wait for the bufcall */
					qunbufcall(q, id);
					return (err);
				}
				qunbufcall(q, id);
			}

			uqi->sm_flags |= SM_IOCPENDING;

			uqi->sm_piocdata.sm_iocid =
			    ((struct iocblk *)pioc->b_rptr)->ioc_id;
			uqi->sm_piocdata.sm_acked = 0;
			uqi->sm_piocdata.sm_nacks = nqs;
			uqi->sm_piocdata.sm_acnt = 0;
			uqi->sm_piocdata.sm_ackcnt = uqi->
			    sm_piocdata.sm_nakcnt = 0;
			uqi->sm_piocdata.sm_policy = uqi->sm_policy;
			uqi->sm_piocdata.sm_flags = SM_INTERNALIOC;
			if (sm_putqs(WR(q), pioc, sm_dump_msg) != 0) {
				uqi->sm_piocdata.sm_iocid = 0;
				sm_log("sm_defer_open: bad putqs\n");
				return (-1);
			}
		}

		sm_dbg('C', ("sm_defer_open: flags 0x%x\n", uqi->sm_flags));
		while ((uqi->sm_flags & SM_CARON) == 0 &&
		    (uqi->sm_flags & SM_IOCPENDING) != 0)
			if (err = sm_qwait_sig(uqi, q))
				return (err);

		sm_dbg('C', ("defer_open: uq flags 0x%x.\n", uqi->sm_flags));
	}
	sm_dbg('C', ("defer_open: return 0.\n"));
	return (0);
}

static int
sm_open(queue_t *rq, dev_t *devp, int flag, int sflag, cred_t *credp)
{
	int		ftstat;
	int		unit;
	int		protocol;
	sm_uqi_t		*uqi;
	int		abort_waiters;

	if (sm_ssp == NULL)
		return (ENXIO);
	/*
	 * sflag = 0 => streams device.
	 */
	if (sflag != 0 || DEV_TO_UNIT(*devp) >= NLUNITS) {
		sm_dbg('C', ("open: sflag=%d or bad dev_t.\n", sflag));
		return (ENXIO);
	}

	unit = DEV_TO_UNIT(*devp);
	protocol = DEV_TO_PROTOBITS(*devp);

	uqi = get_uqi(sm_ssp, unit);

	sm_dbg('C', ("open(0x%p, %d, 0x%x) :- unit=%d, proto=%d, uqi=0x%p\n",
	    rq, *devp, flag, unit, protocol, uqi));

	if (uqi == 0)
		return (ENXIO);

	if (sm_refuse_opens && unit > smctlunit && uqi->sm_nlqs == 0)
		return (ENXIO);

	if (uqi->sm_flags & EXCL_OPEN && (flag & FEXCL)) {
		return (EBUSY); /* device in use */
	}

	if ((flag & FEXCL)) {
		if (secpolicy_excl_open(credp) != 0)
			return (EPERM);

		if ((uqi->sm_flags & FULLY_OPEN) || uqi->sm_nwaiters > 0)
			return (EBUSY); /* device in use */

		uqi->sm_flags |= EXCL_OPEN;
	}

	if (uqi->sm_protocol == NULL_PROTOCOL) {
		struct termios *termiosp;
		int len;

		if (ddi_getlongprop(DDI_DEV_T_ANY, ddi_root_node(),
		    DDI_PROP_NOTPROM, "ttymodes", (caddr_t)&termiosp, &len)
		    == DDI_PROP_SUCCESS &&
		    (len == sizeof (struct termios))) {

			sm_dbg('C', ("open: c_cflag=0x%x\n",
				termiosp->c_cflag));

			uqi->sm_ttycommon->t_iflag = termiosp->c_iflag;
			uqi->sm_ttycommon->t_cflag = termiosp->c_cflag;
			uqi->sm_ttycommon->t_stopc = termiosp->c_cc[VSTOP];
			uqi->sm_ttycommon->t_startc = termiosp->c_cc[VSTART];

			/*
			 * IGNBRK,BRKINT,INPCK,IXON,IXANY,IXOFF - drivers
			 * PARMRK,IGNPAR,ISTRIP - how to report parity
			 * INLCR,IGNCR,ICRNL,IUCLC - ldterm (sophisticated I/O)
			 * IXON, IXANY, IXOFF - flow control input
			 * CBAUD,CSIZE,CS5-8,CSTOPB,PARENB,PARODD,HUPCL,
			 * RCV1EN,XMT1EN,LOBLK,XCLUDE,CRTSXOFF,CRTSCTS,
			 * CIBAUD,PAREXT,CBAUDEXT,CIBAUDEXT,CREAD,CLOCAL
			 */

			kmem_free(termiosp, len);
		}
		else
			bzero((caddr_t)uqi->sm_ttycommon,
			    sizeof (uqi->sm_ttycommon));

		if (*devp == rconsdev) {
			uqi->sm_cmask = sm_cmask;
			uqi->sm_ttycommon->t_flags |= TS_SOFTCAR;
		} else {
			uqi->sm_ttycommon->t_flags &= ~TS_SOFTCAR;
		}

		/*
		 * Clear the default CLOCAL and TS_SOFTCAR flags since
		 * they must correspond to the settings on the real devices.
		 */

		uqi->sm_ttycommon->t_cflag &= ~(uqi->sm_cmask|CLOCAL);
		uqi->sm_mbits = 0;
		uqi->sm_policy = FIRSTACK;
		if (unit == 0 && sm_ssp->sm_ms == 0)
			sm_ssp->sm_ms = (sm_mux_state_t *)
			    space_fetch(TTYMUXPTR);
		if (sm_ssp->sm_ms) {
			if (sm_ssp->sm_ms->sm_cons_stdin.sm_dev == *devp ||
			    sm_ssp->sm_ms->sm_cons_stdout.sm_dev == *devp)
				sm_ssp->sm_lconsole = uqi;
		}
	}

	/*
	 * Does this thread need to wait?
	 */

	sm_dbg('C', ("sm_open: %d %d 0x%p 0x%x\n",
	    !(flag & (FNDELAY|FNONBLOCK)), !(protocol == OUTLINE), uqi->sm_lqs,
	    uqi->sm_flags));

tryopen:

	abort_waiters = 0;
	if (ftstat = sm_ok_to_open(uqi, protocol, credp, &abort_waiters)) {
		sm_dbg('C', ("open failed stat=%d.\n", ftstat));

		if ((uqi->sm_flags & FULLY_OPEN) == 0 && uqi->sm_nwaiters == 0)
			uqi->sm_protocol = NULL_PROTOCOL;
		if (flag & FEXCL)
			uqi->sm_flags &= ~EXCL_OPEN;
		return (ftstat);
	}

	if (abort_waiters) {
		uqi->sm_dev = *devp;
		/* different device wants to use the unit */
		SM_RQ(uqi) = rq;
		SM_WQ(uqi) = WR(rq);
	}
	if (rq->q_ptr == 0) {
		sm_lqi_t *lqi;

		uqi->sm_dev = *devp;
		rq->q_ptr = WR(rq)->q_ptr = uqi;
		SM_RQ(uqi) = rq;
		SM_WQ(uqi) = WR(rq);
		qprocson(rq);
		for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) {
			LOCK_UNIT(lqi);
			lqi->sm_uqflags |= SM_UQVALID;
			UNLOCK_UNIT(lqi);
		}

		sm_dbg('C', ("sm_open: SM_UQVALID set on lqs.\n"));
	}

	if (*devp != rconsdev && BLOCKING(uqi, protocol, flag)) {

		uqi->sm_flags |= WANT_CDSTAT;

		do {
			/*
			 * Wait for notifications of changes in the CLOCAL
			 * and TS_SOFTCAR flags and a TIOCM_CD flag of a
			 * TIOCMGET request (come in on the write side queue).
			 */

			if ((ftstat = sm_defer_open(uqi, rq)) != EINTR) {
				if (ftstat) {
					goto tryopen;
				} else {
					continue;
				}
			}

			if (uqi->sm_nwaiters == 0) {	/* clean up */
				/*
				 * only opens on an asynchronous
				 * protocols reach here so checking
				 * nwaiters == 0 is sufficient to
				 * ensure that no other thread
				 * is waiting on this logical unit
				 */
				if ((uqi->sm_flags & FULLY_OPEN) == 0) {

					sm_lqi_t *lqi;

					uqi->sm_dev = NODEV;
					sm_dbg('C', ("sm_open FULLY_OPEN=0\n"));
					for (lqi = uqi->sm_lqs; lqi != 0;
					    lqi = lqi->sm_nlqi) {
						LOCK_UNIT(lqi);
						lqi->sm_uqflags &= ~SM_UQVALID;
						UNLOCK_UNIT(lqi);
					}

					qprocsoff(rq);
					rq->q_ptr = WR(rq)->q_ptr = 0;
					SM_RQ(uqi) = 0;
					SM_WQ(uqi) = 0;
				}
			}
			if ((uqi->sm_flags & FULLY_OPEN) == 0 &&
			    uqi->sm_nwaiters == 0)
				uqi->sm_protocol = NULL_PROTOCOL;
			if (flag & FEXCL)
				uqi->sm_flags &= ~EXCL_OPEN;
			sm_dbg('C', ("sm_open: done (ret %d).\n", ftstat));
			return (ftstat);
		} while (BLOCKING(uqi, protocol, flag));
	}

	uqi->sm_flags |= FULLY_OPEN;

	sm_dbg('C', ("sm_open done (ret %d).\n", ftstat));
	return (ftstat);
}

/*
 * Multiplexer device close routine.
 */
/*ARGSUSED*/
static int
sm_close(queue_t *rq, int flag, cred_t *credp)
{
	sm_uqi_t *uqi = (sm_uqi_t *)rq->q_ptr;
	sm_lqi_t *lqi;

	if (sm_ssp == NULL)
		return (ENXIO);

	if (uqi == NULL) {
		sm_dbg('C', ("close: WARN:- q 0x%p already closed.\n", rq));
		return (ENXIO);
	}

	sm_dbg('C', ("close: uqi=0x%p unit=%d q=0x%p)\n", uqi, uqi->sm_lunit,
	    rq));

	if (SM_RQ(uqi) != rq)
		sm_dbg('C', ("sm_close: rq != current uqi queue\n"));

	if (uqi->sm_ttybid) {
		qunbufcall(SM_RQ(uqi), uqi->sm_ttybid);
		uqi->sm_ttybid = 0;
	}

	/*
	 * Tell all the linked queues that the upper queue has gone
	 * Note close will never get called on a stream while there is a
	 * thread blocked trying to open the same stream.
	 * If there is a blocked open on a different stream but on
	 * the same logical unit it will reset the lower queue flags.
	 */
	for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) {
		LOCK_UNIT(lqi);
		lqi->sm_uqflags &= ~SM_UQVALID;
		UNLOCK_UNIT(lqi);
	}

	/*
	 * Turn off the STREAMs queue processing for this queue.
	 */
	qprocsoff(rq);

	/*
	 * Similarly we will never get here if there is thread trying to
	 * open ths stream.
	 */
	LOCK_UNIT(uqi);
	if (uqi->sm_waitq == 0)
		uqi->sm_flags = (uqi->sm_flags & SM_OBPCNDEV) ? SM_OBPCNDEV :
		    0U;

	uqi->sm_dev = NODEV;
	uqi->sm_protocol = NULL_PROTOCOL;
	ttycommon_close(uqi->sm_ttycommon);
	/* it just frees any pending ioctl */

	uqi->sm_ttycommon->t_cflag = 0;
	uqi->sm_ttycommon->t_flags = 0;

	/*
	 * Reset the queue pointers to NULL.
	 * If a thread is qwaiting in the open routine it will recheck
	 * the q_ptr.
	 */
	rq->q_ptr = NULL;
	WR(rq)->q_ptr = NULL;
	UNLOCK_UNIT(uqi);

	if (sm_ssp->sm_lconsole == uqi) {
		/* this will never be the outdial device closing */
		sm_ssp->sm_lconsole = 0;
	}
	/*
	 * If there is another thread waiting for this close then unblock
	 * the thread by putting a message on its read queue.
	 */
	if (uqi->sm_waitq) {
		sm_dbg('C', ("close(0x%p): doing putctl on 0x%p\n",
		    rq, uqi->sm_waitq));
		if (rq == uqi->sm_waitq)
			sm_log("close: waitq and closeq are same q\n");
		(void) putctl(uqi->sm_waitq, M_CTL);
	}

	uqi->sm_flags &= ~(EXCL_OPEN | FULLY_OPEN);
	sm_dbg('C', ("close: returning ok.\n"));
	return (0);
}

/*
 * Initialise the software abort sequence for use when one of the
 * driver's nodes provides the system console.
 */
static void
sm_set_abort()
{
	char ds[3] = { '\r', '~', CNTRL('b') };
	char as[SM_MAX_ABSLEN];
	int len = SM_MAX_ABSLEN;

	if (ddi_prop_op(DDI_DEV_T_ANY, sm_ssp->sm_dip, PROP_LEN_AND_VAL_BUF, 0,
	    "abort-str", as, &len) != DDI_PROP_SUCCESS ||
	    (len = strlen(as)) < SM_MIN_ABSLEN) {
		(void) strcpy(as, ds);
		len = strlen(as);
	} else {
		char *s;
		int i;

		for (s = as, i = 0; i < len-1; i++, s++) {
			if (as[i] == '^' && as[i+1] >= 'a' && as[i+1] <= 'z') {
				*s = as[i+1] - 'a' + 1;
				i++;
			} else {
				*s = as[i];
			}
		}
		*s++ = as[i];
		*s = '\0';
		len = strlen(as);
	}

	if (len < SM_MIN_ABSLEN)
		(void) strcpy(sm_ssp->sm_abs, ds);
	else
		(void) strcpy(sm_ssp->sm_abs, as);
}

/*
 *
 * sm_attach - initialisation routine per driver instance.
 */
static int
sm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int unit;
	char name[32];
	sm_uqi_t *uqi;
	sm_lqi_t *lqip;

	/*
	 * Is this an attach?
	 */
	if (cmd != DDI_ATTACH) {
		return (DDI_FAILURE);
	}

	/*
	 * Validate the instance number (sm is a single instance driver).
	 */
	if (sm_ssp) {	/* only one instance allowed */
		return (DDI_FAILURE);
	}

	sm_instance = ddi_get_instance(dip);

	/*
	 * Create the default minor node which will become the console.
	 * (create it with three different names).:
	 *	con which appears in the /dev filesystem;
	 *	input which matches the prom /multiplexer:input node;
	 *	output which matches the prom /multiplexer:input node
	 * Create a minor node for control operations.
	 */
	if (ddi_create_minor_node(dip, "con", S_IFCHR, 0,
	    DDI_PSEUDO, 0) != DDI_SUCCESS ||
	    ddi_create_minor_node(dip, "input", S_IFCHR, 0,
	    DDI_PSEUDO, 0) != DDI_SUCCESS ||
	    ddi_create_minor_node(dip, "output", S_IFCHR, 0,
	    DDI_PSEUDO, 0) != DDI_SUCCESS ||
	    ddi_create_minor_node(dip, "ctl", S_IFCHR, 1,
	    DDI_PSEUDO, 0) != DDI_SUCCESS) {

		cmn_err(CE_WARN, "sm_attach: create minors failed.\n");
		ddi_remove_minor_node(dip, NULL);
		return (DDI_FAILURE);
	}

	smctlunit = 1;

	/*
	 * Allocate private state for this instance.
	 */
	sm_ssp = (sm_ss_t *)kmem_zalloc(sizeof (sm_ss_t), KM_SLEEP);

	/*
	 * Initialise per instance data.
	 */
	sm_ssp->sm_dip = dip;

	/*
	 * Get required debug level.
	 */
	sm_ssp->sm_trflag = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "sm-trlv", sm_default_trflag);

	sm_max_units = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "sm-max-units", sm_max_units);
	sm_minor_cnt = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "sm-minor-cnt", 0);

	sm_refuse_opens = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "sm-refuse-opens", sm_refuse_opens);

	sm_ssp->sm_ctrla_abort_on = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "sm-ctrla-abort-on", 1);
	sm_ssp->sm_break_abort_on = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS, "sm-break-abort-on", 0);

	sm_set_abort();

	sm_ssp->sm_lqs = (sm_lqi_t *)kmem_zalloc(sizeof (sm_lqi_t) * MAX_LQS,
	    KM_SLEEP);
	sm_ssp->sm_uqs = (sm_uqi_t *)kmem_zalloc(sizeof (sm_uqi_t) * NLUNITS,
	    KM_SLEEP);

	for (unit = 2; unit < NLUNITS && unit < sm_minor_cnt + 2; unit++) {

		if (snprintf(name, sizeof (name), "sm%c", 'a' + unit-2) >
		    sizeof (name)) {
			cmn_err(CE_WARN,
			    "sm_attach: create device for unit %d failed.\n",
			    unit);
		} else if (ddi_create_minor_node(dip, name, S_IFCHR,
		    unit, DDI_NT_SERIAL, NULL) != DDI_SUCCESS) {
			ddi_remove_minor_node(dip, NULL);
			return (DDI_FAILURE);
		}

		if (snprintf(name, sizeof (name), "sm%c,cu", 'a' + unit-2) >
		    sizeof (name)) {
			cmn_err(CE_WARN,
			    "sm_attach: create cu device for unit %d failed.\n",
			    unit);
			continue;
		} else if (ddi_create_minor_node(dip, name, S_IFCHR,
		    unit|OUTLINE, DDI_NT_SERIAL_DO, NULL) != DDI_SUCCESS) {
			ddi_remove_minor_node(dip, NULL);
			return (DDI_FAILURE);
		}
	}

	for (unit = 0; unit < NLUNITS; unit++) {

		uqi = get_uqi(sm_ssp, unit);
		uqi->sm_lqs = 0;
		uqi->sm_dev = NODEV;
		uqi->sm_nlqs = 0;
		uqi->sm_lunit = unit;
		uqi->sm_protocol = NULL_PROTOCOL;
		mutex_init(uqi->sm_umutex, NULL, MUTEX_DRIVER, NULL);
		cv_init(uqi->sm_ucv, NULL, CV_DRIVER, NULL);
		mutex_init(&uqi->sm_ttycommon->t_excl, NULL,
		    MUTEX_DRIVER, NULL);
	}

	for (unit = 0; unit < MAX_LQS; unit++) {
		lqip = get_lqi(sm_ssp, unit);
		lqip->sm_unit = unit;
		lqip->sm_hadkadbchar = 0;
		lqip->sm_nachar = sm_ssp->sm_abs;
		lqip->sm_ioflag = FORIO;
		lqip->sm_ctrla_abort_on = sm_ssp->sm_ctrla_abort_on;
		lqip->sm_break_abort_on = sm_ssp->sm_break_abort_on;
		mutex_init(lqip->sm_umutex, NULL, MUTEX_DRIVER, NULL);
		cv_init(lqip->sm_ucv, NULL, CV_DRIVER, NULL);
		mutex_init(&lqip->sm_ttycommon->t_excl, NULL,
		    MUTEX_DRIVER, NULL);
	}

	return (DDI_SUCCESS);
}

/*
 *
 * sm_detach - detach routine per driver instance.
 */
static int
sm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	sm_uqi_t		*lu;
	sm_lqi_t		*pu;
	int		unit;

	/*
	 * Is this a detach request for instance 0 (single instance driver).
	 */
	if (cmd != DDI_DETACH)
		return (DDI_FAILURE);

	if (sm_ssp == NULL)
		return (DDI_FAILURE);

	sm_dbg('V', ("detach ..."));


	/*
	 * Check that all the upper and lower queues are closed.
	 */

	for (unit = 0; unit < NLUNITS; unit++) {
		lu = &sm_ssp->sm_uqs[unit];
		if (lu && lu->sm_protocol != NULL_PROTOCOL) {
			sm_dbg('V', ("detach: upper unit still open.\n"));
			return (DDI_FAILURE);
		}
	}
	for (unit = 0; unit < MAX_LQS; unit++) {
		pu = &sm_ssp->sm_lqs[unit];
		if (pu && pu->sm_linkid != 0) {
			sm_dbg('V', ("detach: lower unit still linked (%d)\n",
			    pu->sm_linkid));
			return (DDI_FAILURE);
		}
	}

	for (unit = 0; unit < NLUNITS; unit++) {
		lu = &sm_ssp->sm_uqs[unit];
		mutex_destroy(lu->sm_umutex);
		cv_destroy(lu->sm_ucv);
		mutex_destroy(&lu->sm_ttycommon->t_excl);
	}
	for (unit = 0; unit < MAX_LQS; unit++) {
		pu = &sm_ssp->sm_lqs[unit];
		mutex_destroy(pu->sm_umutex);
		cv_destroy(pu->sm_ucv);
		mutex_destroy(&pu->sm_ttycommon->t_excl);
	}

	/*
	 * Tidy up per instance state.
	 */
	kmem_free(sm_ssp->sm_lqs, sizeof (sm_lqi_t) * MAX_LQS);
	kmem_free(sm_ssp->sm_uqs, sizeof (sm_uqi_t) * NLUNITS);
	kmem_free(sm_ssp, sizeof (sm_ss_t));

	sm_ssp = 0;

	/*
	 * Remove all of the devices created in attach.
	 */
	ddi_remove_minor_node(dip, NULL);

	return (DDI_SUCCESS);
}

/*
 * SECTION
 * Driver interface to the OS.
 */

/*
 * The driver is responsible for managing the mapping between the file system
 * device types (major/minor pairs) and the corresponding instance of the driver
 * or device information pointer (dip).
 * sm_info - return the instance or dip corresponding to the dev_t.
 */
/*ARGSUSED*/
static int
sm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	int res = DDI_SUCCESS;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (sm_ssp == NULL)
			res = DDI_FAILURE;
		else
			*result = (void *)sm_ssp->sm_dip;
		break;

	case DDI_INFO_DEVT2INSTANCE:
		*result = (void*)0;	/* single instance driver */
		break;

	default:
		res = DDI_FAILURE;
		break;
	}

	return (res);
}

/*
 * End of driver implementation
 */

/*
 * Loadable module interface to the kernel
 */

/*
 * Firstly the Streams specific interface
 */

/*
 * Solaris driver/STREAM initialisation structures.
 */
static struct module_info uinfo =
{
	SM_MOD_ID,
	TTYMUX_DRVNAME,
	0,		/* min packet size */
	INFPSZ,		/* max packet size */
	2048,		/* high water mark */
	256,		/* low water mark */
};

/*
 * Use zero water marks becuase the lower queues are used only for flow control.
 */
static struct module_info linfo =
{
	SM_MOD_ID,
	TTYMUX_DRVNAME,
	0,		/* min packet size */
	INFPSZ,		/* max packet size */
	0,		/* high water mark */
	0		/* low water mark	*/
};


/*
 * Solaris upper read STREAM initialisation structure.
 */
static struct qinit urinit =
{
	sm_urput,	/* put */
	sm_ursrv,	/* service */
	sm_open,	/* open */
	sm_close,	/* close */
	NULL,		/* admin */
	&uinfo,		/* module info */
	NULL		/* stats */
};

/*
 * Solaris upper write STREAM initialisation structure.
 */
static struct qinit uwinit =
{
	sm_uwput,
	sm_uwsrv,
	NULL,
	NULL,
	NULL,
	&uinfo,
	NULL
};

/*
 * Solaris lower read STREAM initialisation structure.
 */
static struct qinit lrinit =
{
	sm_lrput,
	sm_lrsrv,
	NULL,
	NULL, NULL,
	&linfo,
	NULL
};

/*
 * Solaris lower write STREAM initialisation structure.
 */
static struct qinit lwinit =
{
	putq,
	sm_lwsrv,
	NULL,
	NULL,
	NULL,
	&linfo,
	NULL
};

/*
 * Multiplexing STREAM structure.
 */
struct streamtab sm_streamtab =
{
	&urinit,
	&uwinit,
	&lrinit,
	&lwinit
};

/*
 * Driver operations structure (struct cb_ops) and
 * driver dynamic loading functions (struct dev_ops).
 */

/*
 * Fold the Stream interface to the kernel into the driver interface
 * to the OS.
 */

DDI_DEFINE_STREAM_OPS(sm_ops, \
	nulldev, nulldev, \
	sm_attach, sm_detach, nodev, \
	sm_info, (D_NEW | D_MTQPAIR|D_MTOUTPERIM|D_MTOCEXCL | D_MP),
	&sm_streamtab, ddi_quiesce_not_supported);

/*
 * Driver module information.
 */
extern struct mod_ops mod_driverops;
static struct modldrv modldrv =
{
	&mod_driverops,
	"serial mux driver",
	&sm_ops
};

static struct modlinkage modlinkage =
{
	MODREV_1,
	&modldrv,
	NULL
};

/*
 * Define the body of our interface to the OS.
 */

/*
 * '_init' is called by Solaris to initialise any driver
 * specific state and to install the driver.
 */
int
_init(void)
{
	return (mod_install(&modlinkage));
}

/*
 * _info - return this drivers interface to the kernel.
 */
int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/*
 * _fini - the OS is finished with the services provided by the driver.
 * remove ourself and then remove any footprint that remains.
 */
int
_fini(void)
{
	return (mod_remove(&modlinkage));
}