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

/*
 * All Rights Reserved, Copyright (c) FUJITSU LIMITED 2006
 */

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


#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <sys/kmem.h>
#include <sys/ksynch.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/termio.h>
#include <sys/ddi.h>
#include <sys/file.h>
#include <sys/disp.h>
#include <sys/sunddi.h>
#include <sys/sunldi.h>
#include <sys/sunndi.h>
#include <sys/kbio.h>
#include <sys/prom_plat.h>
#include <sys/oplmsu/oplmsu.h>
#include <sys/oplmsu/oplmsu_proto.h>

extern int ddi_create_internal_pathname(dev_info_t *, char *, int, minor_t);

#define	MOD_ID		0xe145
#define	MOD_NAME	"oplmsu"

#define	META_NAME	"oplmsu"
#define	USER_NAME	"a"

struct module_info oplmsu_mod_info = {
	MOD_ID,
	MOD_NAME,
	0,
	16384,
	14336,
	2048
};

struct qinit oplmsu_urinit = {
	NULL,
	oplmsu_ursrv,
	oplmsu_open,
	oplmsu_close,
	NULL,
	&oplmsu_mod_info,
	NULL
};

struct qinit oplmsu_uwinit = {
	oplmsu_uwput,
	oplmsu_uwsrv,
	oplmsu_open,
	oplmsu_close,
	NULL,
	&oplmsu_mod_info,
	NULL
};

struct qinit oplmsu_lrinit = {
	oplmsu_lrput,
	oplmsu_lrsrv,
	oplmsu_open,
	oplmsu_close,
	NULL,
	&oplmsu_mod_info,
	NULL
};

struct qinit oplmsu_lwinit = {
	NULL,
	oplmsu_lwsrv,
	oplmsu_open,
	oplmsu_close,
	NULL,
	&oplmsu_mod_info,
	NULL
};

struct streamtab oplmsu_info = {
	&oplmsu_urinit,
	&oplmsu_uwinit,
	&oplmsu_lrinit,
	&oplmsu_lwinit
};

static struct cb_ops cb_oplmsu_ops = {
	nulldev,			/* cb_open */
	nulldev,			/* cb_close */
	nodev,				/* cb_strategy */
	nodev,				/* cb_print */
	nodev,				/* cb_dump */
	nodev,				/* cb_read */
	nodev,				/* cb_write */
	nodev,				/* cb_ioctl */
	nodev,				/* cb_devmap */
	nodev,				/* cb_mmap */
	nodev,				/* cb_segmap */
	nochpoll,			/* cb_chpoll */
	ddi_prop_op,			/* cb_prop_op */
	(&oplmsu_info),			/* cb_stream */
	(int)(D_NEW|D_MP|D_HOTPLUG)	/* cb_flag */
};

static struct dev_ops oplmsu_ops = {
	DEVO_REV,			/* devo_rev */
	0,				/* devo_refcnt */
	(oplmsu_getinfo),		/* devo_getinfo */
	(nulldev),			/* devo_identify */
	(nulldev),			/* devo_probe */
	(oplmsu_attach),		/* devo_attach */
	(oplmsu_detach),		/* devo_detach */
	(nodev),			/* devo_reset */
	&(cb_oplmsu_ops),		/* devo_cb_ops */
	(struct bus_ops *)NULL,		/* devo_bus_ops */
	NULL,				/* devo_power */
	ddi_quiesce_not_needed,			/* dev_quiesce */
};

struct modldrv modldrv = {
	&mod_driverops,
	"OPL serial mux driver",
	&oplmsu_ops
};

struct modlinkage modlinkage = {
	MODREV_1,
	(void *)&modldrv,
	NULL
};

uinst_t		oplmsu_uinst_local;	/* upper_instance_table structure */
uinst_t		*oplmsu_uinst = &oplmsu_uinst_local;
int		oplmsu_queue_flag;	/* Enable/disable queueing flag */
int		oplmsu_check_su;	/* Check super-user flag */

#ifdef DEBUG
int		oplmsu_debug_mode = 0;	/* Enable/disable debug mode */
int		oplmsu_trace_on;	/* Enable/disable trace */
uint_t		oplmsu_ltrc_size;	/* Trace buffer size */
msu_trc_t	*oplmsu_ltrc_top;	/* Top of trace data area */
msu_trc_t	*oplmsu_ltrc_tail;	/* Tail of trace data area */
msu_trc_t	*oplmsu_ltrc_cur;	/* Current pointer of trace data area */
ulong_t		oplmsu_ltrc_ccnt;	/* Current counter */
kmutex_t	oplmsu_ltrc_lock;	/* Lock table for trace mode */
#endif

/* oplmsu_conf_st */
#define	MSU_CONFIGURED		2
#define	MSU_CONFIGURING		1
#define	MSU_UNCONFIGURED	0

static kmutex_t		oplmsu_bthrd_excl;
static kthread_id_t	oplmsu_bthrd_id = NULL;
static int		oplmsu_conf_st = MSU_UNCONFIGURED;
static kcondvar_t	oplmsu_conf_cv;


/*
 * Locking hierarcy of oplmsu driver. This driver have 5 locks in uinst_t.
 *
 * Each mutex guards as follows.
 *
 *  uinst_t->lock: This mutex is read/write mutex.
 *     read lock : acquired if the member of uinst_t is refered only.
 *     write lock: acquired if the member of uinst_t is changed.
 *
 *  uinst_t->u_lock: This mutex is normal mutex.
 *   This mutex is acquired at reading/changing the member of all upath_t.
 *
 *  uinst_t->l_lock: This mutex is normal mutex.
 *   This mutex is acquired at reading/changing the member of all lpath_t.
 *
 *  uinst_t->c_lock: This mutex is normal mutex.
 *   This mutex is acquired at reading/changing the member of the ctrl_t.
 *
 *  oplmsu_bthrd_excl: This mutex is normal mutex.
 *   This mutex is used only to start/stop the configuring thread of the
 *   multiplexed STREAMS.
 *   This mutex is exclusively acquired with the above-mentioned 4 mutexes.
 *
 * To guard of the deadlock by cross locking, the base locking hierarcy
 * is as follows:
 *
 *     uisnt->lock ==> uinst->u_lock ==> uinst->l_lock ==> uinst->c_lock
 *
 */


int
_init(void)
{
	int	rval;

	/* Initialize R/W lock for uinst_t */
	rw_init(&oplmsu_uinst->lock, "uinst rwlock", RW_DRIVER, NULL);

	/* Initialize mutex for upath_t */
	mutex_init(&oplmsu_uinst->u_lock, "upath lock", MUTEX_DRIVER, NULL);

	/* Initialize mutex for lpath_t */
	mutex_init(&oplmsu_uinst->l_lock, "lpath lock", MUTEX_DRIVER, NULL);

	/* Initialize mutex for ctrl_t */
	mutex_init(&oplmsu_uinst->c_lock, "ctrl lock", MUTEX_DRIVER, NULL);

	/* Initialize mutex for protecting background thread */
	mutex_init(&oplmsu_bthrd_excl, NULL, MUTEX_DRIVER, NULL);

	/* Initialize condition variable */
	cv_init(&oplmsu_conf_cv, NULL, CV_DRIVER, NULL);

	rval = mod_install(&modlinkage);
	if (rval != DDI_SUCCESS) {
		cv_destroy(&oplmsu_conf_cv);
		mutex_destroy(&oplmsu_bthrd_excl);
		mutex_destroy(&oplmsu_uinst->c_lock);
		mutex_destroy(&oplmsu_uinst->l_lock);
		mutex_destroy(&oplmsu_uinst->u_lock);
		rw_destroy(&oplmsu_uinst->lock);
	}
	return (rval);
}

int
_fini(void)
{
	int	rval;

	rval = mod_remove(&modlinkage);
	if (rval == DDI_SUCCESS) {
		cv_destroy(&oplmsu_conf_cv);
		mutex_destroy(&oplmsu_bthrd_excl);
		mutex_destroy(&oplmsu_uinst->c_lock);
		mutex_destroy(&oplmsu_uinst->l_lock);
		mutex_destroy(&oplmsu_uinst->u_lock);
		rw_destroy(&oplmsu_uinst->lock);
	}
	return (rval);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/* ARGSUSED */
int
oplmsu_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
	dev_t	dev = (dev_t)arg;
	minor_t	inst;
	int	rval = DDI_SUCCESS;

	switch (cmd) {
	case DDI_INFO_DEVT2DEVINFO  :
		if (oplmsu_uinst->msu_dip == NULL) {
			rval = DDI_FAILURE;
		} else {
			*resultp = oplmsu_uinst->msu_dip;
		}
		break;

	case DDI_INFO_DEVT2INSTANCE :
		inst = getminor(dev) & ~(META_NODE_MASK|USER_NODE_MASK);
		*resultp = (void *)(uintptr_t)inst;
		break;

	default :
		rval = DDI_FAILURE;
		break;
	}
	return (rval);
}

int
oplmsu_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	minor_t	meta_minor, user_minor;
	int	rval = 0;
	int	instance;
#define	CNTRL(c) ((c) & 037)
	char	abt_ch_seq[3] = { '\r', '~', CNTRL('b') };

	if (cmd == DDI_RESUME) {
		return (DDI_SUCCESS);
	}

	if (cmd != DDI_ATTACH) {
		return (DDI_FAILURE);
	}

	instance = ddi_get_instance(dip);
	if (instance != 0) {
		cmn_err(CE_WARN, "oplmsu: attach: "
		    "Invaild instance => %d", instance);
		return (DDI_FAILURE);
	}

	/* Create minor number for meta control node */
	meta_minor = instance | META_NODE_MASK;
	/* Create minor number for user access node */
	user_minor = instance | USER_NODE_MASK;

	/* Create minor node for user access */
	rval = ddi_create_minor_node(dip, USER_NAME, S_IFCHR, user_minor,
	    DDI_NT_SERIAL, 0);
	if (rval != DDI_SUCCESS) {
		cmn_err(CE_WARN, "oplmsu: attach: "
		    "ddi_create_minor_node failed. errno = %d", rval);
		ddi_remove_minor_node(dip, NULL);
		return (rval);
	}

	/* Create minor node for meta control */
	rval = ddi_create_internal_pathname(dip, META_NAME, S_IFCHR,
	    meta_minor);
	if (rval != DDI_SUCCESS) {
		cmn_err(CE_WARN, "oplmsu: attach: "
		    "ddi_create_internal_pathname failed. errno = %d", rval);
		ddi_remove_minor_node(dip, NULL);
		return (rval);
	}

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);

	/* Get each properties */
	oplmsu_check_su = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    (DDI_PROP_DONTPASS|DDI_PROP_NOTPROM), "check-superuser", 1);

	/*
	 * Initialize members of uinst_t
	 */

	oplmsu_uinst->inst_status = INST_STAT_UNCONFIGURED;
	oplmsu_uinst->path_num = UNDEFINED;
	oplmsu_uinst->msu_dip = dip;
	(void) strcpy(oplmsu_uinst->abts, abt_ch_seq);

#ifdef DEBUG
	oplmsu_trace_on = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    (DDI_PROP_DONTPASS|DDI_PROP_NOTPROM), "trace-mode", 1);
	oplmsu_ltrc_size = (uint_t)ddi_prop_get_int(DDI_DEV_T_ANY, dip,
	    (DDI_PROP_DONTPASS|DDI_PROP_NOTPROM), "trace-bufsize", 128);

	if (oplmsu_trace_on == MSU_TRACE_ON) {
		/* Initialize mutex for msu_trc_t */
		mutex_init(&oplmsu_ltrc_lock, "trc lock", MUTEX_DRIVER, NULL);

		mutex_enter(&oplmsu_ltrc_lock);
		oplmsu_ltrc_top = (msu_trc_t *)kmem_zalloc(
		    (sizeof (msu_trc_t) * oplmsu_ltrc_size), KM_SLEEP);
		oplmsu_ltrc_cur = (msu_trc_t *)(oplmsu_ltrc_top - 1);
		oplmsu_ltrc_tail =
		    (msu_trc_t *)(oplmsu_ltrc_top + (oplmsu_ltrc_size - 1));
		mutex_exit(&oplmsu_ltrc_lock);
	}
#endif
	rw_exit(&oplmsu_uinst->lock);
	ddi_report_dev(dip);
	return (rval);
}

int
oplmsu_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	lpath_t	*lpath, *next_lpath;

	if (cmd == DDI_SUSPEND) {
		return (DDI_SUCCESS);
	}

	if (cmd != DDI_DETACH) {
		return (DDI_FAILURE);
	}

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);

	/* Delete all upath_t */
	oplmsu_delete_upath_info();

	/* Delete all lpath_t */
	mutex_enter(&oplmsu_uinst->l_lock);
	lpath = oplmsu_uinst->first_lpath;
	oplmsu_uinst->first_lpath = NULL;
	oplmsu_uinst->last_lpath = NULL;
	mutex_exit(&oplmsu_uinst->l_lock);

#ifdef DEBUG
	if (oplmsu_trace_on == MSU_TRACE_ON) {
		mutex_enter(&oplmsu_ltrc_lock);
		if (oplmsu_ltrc_top != NULL) {
			kmem_free(oplmsu_ltrc_top,
			    (sizeof (msu_trc_t) * oplmsu_ltrc_size));
		}
		oplmsu_ltrc_top = NULL;
		oplmsu_ltrc_cur = NULL;
		oplmsu_ltrc_tail = NULL;
		mutex_exit(&oplmsu_ltrc_lock);

		mutex_destroy(&oplmsu_ltrc_lock);
	}
#endif
	rw_exit(&oplmsu_uinst->lock);

	while (lpath) {
		if (lpath->rbuf_id) {
			unbufcall(lpath->rbuf_id);
		}

		if (lpath->rtout_id) {
			untimeout(lpath->rtout_id);
		}

		if (lpath->rbuftbl) {
			kmem_free(lpath->rbuftbl, sizeof (struct buf_tbl));
		}

		cv_destroy(&lpath->sw_cv);
		next_lpath = lpath->l_next;
		kmem_free(lpath, sizeof (lpath_t));
		lpath = next_lpath;
	}
	ddi_remove_minor_node(dip, NULL);
	return (DDI_SUCCESS);
}

/* ARGSUSED */
int
oplmsu_open(queue_t *urq, dev_t *dev, int oflag, int sflag, cred_t *cred_p)
{
	ctrl_t	*ctrl;
	minor_t	mindev = 0;
	minor_t	qmindev = 0;
	major_t	majdev;
	ulong_t	node_flag;

	DBG_PRINT((CE_NOTE, "oplmsu: open: "
	    "devt = 0x%lx, sflag = 0x%x", *dev, sflag));

	if (sflag == CLONEOPEN) {
		return (EINVAL);
	}

	/* Get minor device number */
	qmindev = (minor_t)getminor(*dev);
	/* Get node type */
	node_flag = MSU_NODE_TYPE(qmindev);
	if ((node_flag != MSU_NODE_USER) && (node_flag != MSU_NODE_META)) {
		return (EINVAL);
	}

	mutex_enter(&oplmsu_bthrd_excl);
	if ((node_flag == MSU_NODE_USER) &&
	    (oplmsu_conf_st != MSU_CONFIGURED)) { /* User access & First open */
		int	cv_rval;

		DBG_PRINT((CE_NOTE, "oplmsu: open: "
		    "oplmsu_conf_st = %x", oplmsu_conf_st));

		if (oplmsu_conf_st == MSU_UNCONFIGURED) {
			oplmsu_conf_st = MSU_CONFIGURING;

			/* Start up background thread */
			oplmsu_bthrd_id = thread_create(NULL, 2 * DEFAULTSTKSZ,
			    oplmsu_setup, (void *)oplmsu_uinst, 0, &p0, TS_RUN,
			    minclsyspri);
		}

		/*
		 * Wait with cv_wait_sig() until background thread is
		 * completed.
		 */
		while (oplmsu_conf_st == MSU_CONFIGURING) {
			cv_rval =
			    cv_wait_sig(&oplmsu_conf_cv, &oplmsu_bthrd_excl);
			if (cv_rval == 0) {
				mutex_exit(&oplmsu_bthrd_excl);
				return (EINTR);
			}
		}
	}
	mutex_exit(&oplmsu_bthrd_excl);

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);

	/*
	 *  If the node which will open is meta-control-node or
	 * user-access-node, and q_ptr, this is queue_t queue
	 * table member, is not NULL, then oplmsu returns
	 * SUCCESS immidiately.
	 *  This process is used to protect dual open.
	 */

	if ((urq != NULL) && (urq->q_ptr != NULL)) {
		rw_exit(&oplmsu_uinst->lock);
		return (SUCCESS);
	}

	/*
	 *  If the node which will open is User-Access-Node, and instance
	 * status of oplmsu is no ONLINE, then oplmsu_open process fails
	 * with return value 'EIO'.
	 */

	if ((node_flag == MSU_NODE_USER) &&
	    (oplmsu_uinst->inst_status != INST_STAT_ONLINE)) {
		rw_exit(&oplmsu_uinst->lock);
		return (EIO);
	}

	mindev |= qmindev;			/* Create minor device number */
	majdev = getmajor(*dev);		/* Get major device number */
	*dev = makedevice(majdev, mindev);	/* Make device number */

	/* Allocate kernel memory for ctrl_t */
	ctrl = (ctrl_t *)kmem_zalloc(sizeof (ctrl_t), KM_SLEEP);

	/*
	 * Initialize members of ctrl_t
	 */
	ctrl->minor = (minor_t)mindev;
	ctrl->queue = urq;
	ctrl->sleep_flag = CV_WAKEUP;
	ctrl->node_type = node_flag;
	ctrl->wbuftbl =
	    (struct buf_tbl *)kmem_zalloc(sizeof (struct buf_tbl), KM_SLEEP);
	cv_init(&ctrl->cvp, "oplmsu ctrl_tbl condvar", CV_DRIVER, NULL);

	mutex_enter(&oplmsu_uinst->c_lock);

	if (node_flag == MSU_NODE_USER) {	/* User access node */

		oplmsu_uinst->user_ctrl = ctrl;
		oplmsu_queue_flag = 0;

	} else {	/* Meta control node */

		oplmsu_uinst->meta_ctrl = ctrl;
	}

	RD(urq)->q_ptr = ctrl;
	WR(urq)->q_ptr = ctrl;

	mutex_exit(&oplmsu_uinst->c_lock);
	rw_exit(&oplmsu_uinst->lock);

	OPLMSU_TRACE(urq, (mblk_t *)node_flag, MSU_TRC_OPN);

	qprocson(urq);	/* Enable put and service routine */
	return (SUCCESS);
}

/* ARGSUSED */
int
oplmsu_close(queue_t *urq, int flag, cred_t *cred_p)
{
	ctrl_t		*ctrl;
	minor_t		qmindev = 0;
	lpath_t		*lpath;
	ulong_t		node_flag;
	bufcall_id_t	wbuf_id;
	timeout_id_t	wtout_id;

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	mutex_enter(&oplmsu_uinst->l_lock);
	mutex_enter(&oplmsu_uinst->c_lock);
	if ((ctrl = urq->q_ptr) == NULL) {
		mutex_exit(&oplmsu_uinst->c_lock);
		mutex_exit(&oplmsu_uinst->l_lock);
		rw_exit(&oplmsu_uinst->lock);

		DBG_PRINT((CE_NOTE, "oplmsu: close: "
		    "close has already been completed"));
		return (FAILURE);
	}
	qmindev = ctrl->minor;

	DBG_PRINT((CE_NOTE, "oplmsu: close: ctrl->minor = 0x%x", qmindev));

	node_flag = MSU_NODE_TYPE(qmindev);
	if (node_flag > MSU_NODE_META) {
		mutex_exit(&oplmsu_uinst->c_lock);
		mutex_exit(&oplmsu_uinst->l_lock);
		rw_exit(&oplmsu_uinst->lock);
		return (EINVAL);
	}

	/*
	 *  Check that queue which is waiting for response from lower stream
	 * exist. If queue exists, oplmsu sets CV_SLEEP to sleep_flag.
	 */

	for (lpath = oplmsu_uinst->first_lpath; lpath; ) {
		if (((RD(urq) == lpath->hndl_uqueue) ||
		    (WR(urq) == lpath->hndl_uqueue)) &&
		    (lpath->hndl_mp != NULL)) {
			ctrl->sleep_flag = CV_SLEEP;
			break;
		}

		lpath = lpath->l_next;
	}
	mutex_exit(&oplmsu_uinst->l_lock);
	rw_exit(&oplmsu_uinst->lock);

	/* If sleep_flag is not CV_SLEEP, oplmsu calls cv_wait. */
	if (lpath) {
		while (ctrl->sleep_flag != CV_WAKEUP) {
			cv_wait(&ctrl->cvp, &oplmsu_uinst->c_lock);
		}
	}

	flushq(RD(urq), FLUSHALL);
	flushq(WR(urq), FLUSHALL);
	mutex_exit(&oplmsu_uinst->c_lock);
	qprocsoff(urq);		/* Disable queuing of queue */

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);
	switch (node_flag) {
	case MSU_NODE_USER :	/* User access node */
		oplmsu_uinst->user_ctrl = NULL;
		oplmsu_queue_flag = 0;
		break;

	case MSU_NODE_META :	/* Meta control node */
		oplmsu_uinst->meta_ctrl = NULL;
		break;

	default :
		cmn_err(CE_WARN, "oplmsu: close: node_flag = 0x%lx", node_flag);
	}

	ctrl->minor = NULL;
	ctrl->queue = NULL;
	wbuf_id = ctrl->wbuf_id;
	wtout_id = ctrl->wtout_id;
	ctrl->wbuf_id = 0;
	ctrl->wtout_id = 0;

	cv_destroy(&ctrl->cvp);
	kmem_free(ctrl->wbuftbl, sizeof (struct buf_tbl));
	ctrl->wbuftbl = NULL;

	RD(urq)->q_ptr = NULL;
	WR(urq)->q_ptr = NULL;
	rw_exit(&oplmsu_uinst->lock);

	if (wbuf_id != 0) {
		unbufcall(wbuf_id);
	}

	if (wtout_id != 0) {
		untimeout(wtout_id);
	}

	/* Free kernel memory for ctrl_t */
	kmem_free(ctrl, sizeof (ctrl_t));

	OPLMSU_TRACE(urq, (mblk_t *)node_flag, MSU_TRC_CLS);
	return (SUCCESS);
}

/*
 * Upper write put procedure
 */
int
oplmsu_uwput(queue_t *uwq, mblk_t *mp)
{

	if (mp == NULL) {
		return (SUCCESS);
	}

	if ((uwq == NULL) || (uwq->q_ptr == NULL)) {
		freemsg(mp);
		return (SUCCESS);
	}

	OPLMSU_TRACE(uwq, mp, MSU_TRC_UI);

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	if (mp->b_datap->db_type == M_FLUSH) {
		oplmsu_wcmn_flush_hndl(uwq, mp, RW_READER);
	} else if (mp->b_datap->db_type >= QPCTL) {
		ctrl_t	*ctrl;

		mutex_enter(&oplmsu_uinst->c_lock);
		ctrl = (ctrl_t *)uwq->q_ptr;

		/* Link high priority message to local queue */
		oplmsu_link_high_primsg(&ctrl->first_upri_hi,
		    &ctrl->last_upri_hi, mp);

		mutex_exit(&oplmsu_uinst->c_lock);
		oplmsu_wcmn_high_qenable(WR(uwq), RW_READER);
	} else {
		putq(WR(uwq), mp);
	}
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}

/*
 * Upper write service procedure
 */
int
oplmsu_uwsrv(queue_t *uwq)
{
	struct iocblk	*iocp = NULL;
	mblk_t		*mp = NULL;
	int		rval;

	if ((uwq == NULL) || (uwq->q_ptr == NULL)) {
		return (FAILURE);
	}

	rw_enter(&oplmsu_uinst->lock, RW_READER);

	/* Handle high priority message */
	while (mp = oplmsu_wcmn_high_getq(uwq)) {
		if (mp->b_datap->db_type == M_FLUSH) {
			oplmsu_wcmn_flush_hndl(uwq, mp, RW_READER);
			continue;
		}

		if (oplmsu_wcmn_through_hndl(uwq, mp, MSU_HIGH, RW_READER) ==
		    FAILURE) {
			rw_exit(&oplmsu_uinst->lock);
			return (SUCCESS);
		}
	}
	rw_exit(&oplmsu_uinst->lock);

	/* Handle normal priority message */
	while (mp = getq(uwq)) {
		rval = SUCCESS;
		switch (mp->b_datap->db_type) {
		case M_IOCTL :
			iocp = (struct iocblk *)mp->b_rptr;
			switch (iocp->ioc_cmd) {
			case I_PLINK :
				if (oplmsu_cmn_pullup_msg(uwq, mp) != FAILURE) {
					rval = oplmsu_uwioctl_iplink(uwq, mp);
				}
				break;

			case I_PUNLINK :
				if (oplmsu_cmn_pullup_msg(uwq, mp) != FAILURE) {
					rval = oplmsu_uwioctl_ipunlink(uwq, mp);
				}
				break;

			case TCSETS :		/* FALLTHRU */
			case TCSETSW :		/* FALLTHRU */
			case TCSETSF :		/* FALLTHRU */
			case TIOCMSET :		/* FALLTHRU */
			case TIOCSPPS :		/* FALLTHRU */
			case TIOCSWINSZ :	/* FALLTHRU */
			case TIOCSSOFTCAR :
				rval = oplmsu_uwioctl_termios(uwq, mp);
				break;

			default :
				rw_enter(&oplmsu_uinst->lock, RW_READER);
				rval = oplmsu_wcmn_through_hndl(uwq, mp,
				    MSU_NORM, RW_READER);
				rw_exit(&oplmsu_uinst->lock);
				break;
			}
			break;

		default :
			rw_enter(&oplmsu_uinst->lock, RW_READER);
			rval = oplmsu_wcmn_through_hndl(uwq, mp, MSU_NORM,
			    RW_READER);
			rw_exit(&oplmsu_uinst->lock);
			break;
		}

		if (rval == FAILURE) {
			break;
		}
	}
	return (SUCCESS);
}

/*
 * Lower write service procedure
 */
int
oplmsu_lwsrv(queue_t *lwq)
{
	mblk_t		*mp;
	queue_t		*dst_queue;
	lpath_t		*lpath;

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	while (mp = getq(lwq)) {
		if (mp->b_datap->db_type >= QPCTL) {
			rw_exit(&oplmsu_uinst->lock);
			OPLMSU_TRACE(WR(lwq), mp, MSU_TRC_LO);
			putnext(WR(lwq), mp);
			rw_enter(&oplmsu_uinst->lock, RW_READER);
			continue;
		}

		dst_queue = WR(lwq);
		if (canputnext(dst_queue)) {
			rw_exit(&oplmsu_uinst->lock);
			OPLMSU_TRACE(dst_queue, mp, MSU_TRC_LO);
			putnext(dst_queue, mp);
			rw_enter(&oplmsu_uinst->lock, RW_READER);
		} else {
			putbq(WR(lwq), mp);
			break;
		}
	}

	mutex_enter(&oplmsu_uinst->l_lock);
	lpath = (lpath_t *)lwq->q_ptr;
	if (lpath->uwq_flag != 0) {
		qenable(WR(lpath->uwq_queue));
		lpath->uwq_flag = 0;
		lpath->uwq_queue = NULL;
	}
	mutex_exit(&oplmsu_uinst->l_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}

/*
 * Lower read put procedure
 */
int
oplmsu_lrput(queue_t *lrq, mblk_t *mp)
{

	if (mp == NULL) {
		return (SUCCESS);
	}

	if ((lrq == NULL) || (lrq->q_ptr == NULL)) {
		freemsg(mp);
		return (SUCCESS);
	}

	OPLMSU_TRACE(lrq, mp, MSU_TRC_LI);

	if (mp->b_datap->db_type == M_FLUSH) {
		rw_enter(&oplmsu_uinst->lock, RW_READER);
		oplmsu_rcmn_flush_hndl(lrq, mp);
		rw_exit(&oplmsu_uinst->lock);
	} else if (mp->b_datap->db_type >= QPCTL) {
		lpath_t	*lpath;

		rw_enter(&oplmsu_uinst->lock, RW_READER);
		mutex_enter(&oplmsu_uinst->l_lock);
		lpath = lrq->q_ptr;

		/* Link high priority message to local queue */
		oplmsu_link_high_primsg(&lpath->first_lpri_hi,
		    &lpath->last_lpri_hi, mp);

		mutex_exit(&oplmsu_uinst->l_lock);
		rw_exit(&oplmsu_uinst->lock);
		oplmsu_rcmn_high_qenable(lrq);
	} else {
		putq(lrq, mp);
	}
	return (SUCCESS);
}

/*
 * Lower read service procedure
 */
int
oplmsu_lrsrv(queue_t *lrq)
{
	mblk_t		*mp;
	boolean_t	aborted;
	int		rval;

	if ((lrq == NULL) || (lrq->q_ptr == NULL)) {
		return (FAILURE);
	}

	/* Handle normal priority message */
	while (mp = getq(lrq)) {
		if (mp->b_datap->db_type >= QPCTL) {
			cmn_err(CE_WARN, "oplmsu: lr-srv: "
			    "Invalid db_type => %x", mp->b_datap->db_type);
		}

		switch (mp->b_datap->db_type) {
		case M_DATA :
			aborted = B_FALSE;
			rw_enter(&oplmsu_uinst->lock, RW_READER);
			if ((abort_enable == KIOCABORTALTERNATE) &&
			    (RD(oplmsu_uinst->lower_queue) == lrq)) {
				uchar_t	*rx_char = mp->b_rptr;
				lpath_t	*lpath;

				mutex_enter(&oplmsu_uinst->l_lock);
				lpath = lrq->q_ptr;
				while (rx_char != mp->b_wptr) {
					if (*rx_char == *lpath->abt_char) {
					lpath->abt_char++;
					if (*lpath->abt_char == '\0') {
						abort_sequence_enter((char *)
						    NULL);
						lpath->abt_char
						    = oplmsu_uinst->abts;
						aborted = B_TRUE;
						break;
					}
					} else {
					lpath->abt_char = (*rx_char ==
					    *oplmsu_uinst->abts) ?
					    oplmsu_uinst->abts + 1 :
					    oplmsu_uinst->abts;
					}
					rx_char++;
				}
				mutex_exit(&oplmsu_uinst->l_lock);
			}
			rw_exit(&oplmsu_uinst->lock);

			if (aborted) {
				freemsg(mp);
				continue;
			}

			/*
			 * When 1st byte of the received M_DATA is XON or,
			 * 1st byte is XOFF and 2nd byte is XON.
			 */

			if ((*(mp->b_rptr) == MSU_XON) ||
			    (((mp->b_wptr - mp->b_rptr) == 2) &&
			    ((*(mp->b_rptr) == MSU_XOFF) &&
			    (*(mp->b_rptr + 1) == MSU_XON)))) {
				/* Path switching by XOFF/XON */
				if (oplmsu_lrdata_xoffxon(lrq, mp) == FAILURE) {
					return (SUCCESS);
				}
			} else {
				rw_enter(&oplmsu_uinst->lock, RW_READER);
				rval =
				    oplmsu_rcmn_through_hndl(lrq, mp, MSU_NORM);
				rw_exit(&oplmsu_uinst->lock);

				if (rval == FAILURE) {
					return (SUCCESS);
				}
			}
			break;

		case M_BREAK :
			if ((mp->b_wptr - mp->b_rptr) == 0 && msgdsize(mp)
			    == 0) {
				rw_enter(&oplmsu_uinst->lock, RW_READER);
				if ((abort_enable != KIOCABORTALTERNATE) &&
				    (RD(oplmsu_uinst->lower_queue) == lrq)) {
					abort_sequence_enter((char *)NULL);
				}
				rw_exit(&oplmsu_uinst->lock);
				freemsg(mp);
				break;
			}
			/* FALLTHRU */

		default :
			rw_enter(&oplmsu_uinst->lock, RW_READER);
			(void) oplmsu_rcmn_through_hndl(lrq, mp, MSU_NORM);
			rw_exit(&oplmsu_uinst->lock);
			break;
		}
	}
	return (SUCCESS);
}

/*
 * Upper read service procedure
 */
int
oplmsu_ursrv(queue_t *urq)
{
	mblk_t	*mp;
	queue_t	*dst_queue;
	lpath_t	*lpath;
	ctrl_t	*ctrl;
	int	res_chk = 0;

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	while (mp = getq(urq)) {
		if (mp->b_datap->db_type >= QPCTL) {
			if ((mp->b_datap->db_type == M_IOCACK) ||
			    (mp->b_datap->db_type == M_IOCNAK)) {
				res_chk = 1;
			}
			rw_exit(&oplmsu_uinst->lock);
			OPLMSU_TRACE(RD(urq), mp, MSU_TRC_UO);
			putnext(RD(urq), mp);

			rw_enter(&oplmsu_uinst->lock, RW_READER);
			mutex_enter(&oplmsu_uinst->l_lock);
			lpath = oplmsu_uinst->first_lpath;
			while (lpath) {
				qenable(RD(lpath->lower_queue));
				lpath = lpath->l_next;
			}
			mutex_exit(&oplmsu_uinst->l_lock);

			if (res_chk == 1) {
				mutex_enter(&oplmsu_uinst->c_lock);
				ctrl = (ctrl_t *)urq->q_ptr;
				if (ctrl != NULL) {
					if (ctrl->wait_queue != NULL) {
						qenable(WR(ctrl->wait_queue));
						ctrl->wait_queue = NULL;
					}
				}
				mutex_exit(&oplmsu_uinst->c_lock);
				res_chk = 0;
			}
			continue;
		}

		dst_queue = RD(urq);
		if (canputnext(dst_queue)) {
			rw_exit(&oplmsu_uinst->lock);
			OPLMSU_TRACE(dst_queue, mp, MSU_TRC_UO);
			putnext(dst_queue, mp);
			rw_enter(&oplmsu_uinst->lock, RW_READER);
		} else {
			putbq(urq, mp);
			break;
		}
	}

	mutex_enter(&oplmsu_uinst->c_lock);
	ctrl = urq->q_ptr;
	if (ctrl->lrq_flag != 0) {
		qenable(ctrl->lrq_queue);
		ctrl->lrq_flag = 0;
		ctrl->lrq_queue = NULL;
	}
	mutex_exit(&oplmsu_uinst->c_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}

int
oplmsu_open_msu(dev_info_t *dip, ldi_ident_t *lip, ldi_handle_t *lhp)
{
	dev_t	devt;
	int	rval;

	/* Allocate LDI identifier */
	rval = ldi_ident_from_dip(dip, lip);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: open-msu: "
		    "ldi_ident_from_dip failed. errno = %d", rval);
		return (rval);
	}

	/* Open oplmsu(meta ctrl node) */
	devt = makedevice(ddi_driver_major(dip), META_NODE_MASK);
	rval =
	    ldi_open_by_dev(&devt, OTYP_CHR, (FREAD|FWRITE), kcred, lhp, *lip);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: open-msu: "
		    "ldi_open_by_dev failed. errno = %d", rval);
		ldi_ident_release(*lip);
	}
	return (rval);
}

int
oplmsu_plink_serial(dev_info_t *dip, ldi_handle_t msu_lh, int *id)
{
	ldi_ident_t	li = NULL;
	ldi_handle_t	lh = NULL;
	int		param;
	int		rval;
	char		pathname[MSU_PATHNAME_SIZE];
	char		wrkbuf[MSU_PATHNAME_SIZE];

	/* Create physical path-name for serial */
	ddi_pathname(dip, wrkbuf);
	*(wrkbuf + strlen(wrkbuf)) = '\0';
	sprintf(pathname, "/devices%s:%c", wrkbuf, 'a'+ ddi_get_instance(dip));

	/* Allocate LDI identifier */
	rval = ldi_ident_from_dip(dip, &li);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: plink-serial: "
		    "%s ldi_ident_from_dip failed. errno = %d", pathname, rval);
		return (rval);
	}

	/* Open serial */
	rval = ldi_open_by_name(pathname, (FREAD|FWRITE|FEXCL), kcred, &lh, li);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: plink-serial: "
		    "%s open failed. errno = %d", pathname, rval);
		ldi_ident_release(li);
		return (rval);
	}

	/* Try to remove the top module from the stream */
	param = 0;
	while ((ldi_ioctl(lh, I_POP, (intptr_t)0, FKIOCTL, kcred, &param))
	    == 0) {
		continue;
	}

	/* Issue ioctl(I_PLINK) */
	param = 0;
	rval = ldi_ioctl(msu_lh, I_PLINK, (intptr_t)lh, FKIOCTL, kcred, &param);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: plink-serial: "
		    "%s ioctl(I_PLINK) failed. errno = %d", pathname, rval);
	}

	(void) ldi_close(lh, (FREAD|FWRITE|FEXCL), kcred);
	ldi_ident_release(li);

	*id = param;	/* Save link-id */
	return (rval);
}

int
oplmsu_set_lpathnum(int lnk_id, int instance)
{
	lpath_t	*lpath;
	int	rval = SUCCESS;

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	mutex_enter(&oplmsu_uinst->l_lock);
	lpath = oplmsu_uinst->first_lpath;
	while (lpath) {
		if ((lpath->path_no == UNDEFINED) &&
		    (lpath->link_id == lnk_id)) {
			lpath->path_no = instance; /* Set instance number */
			lpath->src_upath = NULL;
			lpath->status = MSU_SETID_NU;
			break;
		}
		lpath = lpath->l_next;
	}
	mutex_exit(&oplmsu_uinst->l_lock);
	rw_exit(&oplmsu_uinst->lock);

	if (lpath == NULL) {
		rval = EINVAL;
	}
	return (rval);
}

int
oplmsu_dr_attach(dev_info_t *dip)
{
	ldi_ident_t	msu_li = NULL;
	ldi_handle_t	msu_lh = NULL;
	upath_t		*upath;
	int		len;
	int		instance;
	int		lnk_id = 0;
	int		param = 0;
	int		rval;

	/* Get instance for serial */
	instance = ddi_get_instance(dip);

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);
	mutex_enter(&oplmsu_uinst->u_lock);

	/* Get current number of paths */
	oplmsu_uinst->path_num = oplmsu_get_pathnum();

	/* Check specified upath_t */
	upath = oplmsu_uinst->first_upath;
	while (upath) {
		if (instance == upath->path_no) {
			break;
		}
		upath = upath->u_next;
	}
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);

	if (upath != NULL) {
		cmn_err(CE_WARN, "oplmsu: attach(dr): "
		    "Instance %d already exist", instance);
		return (EINVAL);
	}

	/* Open oplmsu */
	rval = oplmsu_open_msu(oplmsu_uinst->msu_dip, &msu_li, &msu_lh);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: attach(dr): "
		    "msu open failed. errno = %d", rval);
		return (rval);
	}

	/* Connect two streams */
	rval = oplmsu_plink_serial(dip, msu_lh, &lnk_id);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: attach(dr): "
		    "i_plink failed. errno = %d", rval);
		(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
		ldi_ident_release(msu_li);
		return (rval);
	}

	rval = oplmsu_set_lpathnum(lnk_id, instance);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: attach(dr): "
		    "Link id %d is not found", lnk_id);
		/* Issue ioctl(I_PUNLINK) */
		(void) ldi_ioctl(msu_lh, I_PUNLINK, (intptr_t)lnk_id, FKIOCTL,
		    kcred, &param);
		(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
		ldi_ident_release(msu_li);
		return (rval);
	}

	/* Add the path */
	rval = oplmsu_config_add(dip);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: attach(dr): "
		    "Failed to add the path. errno = %d", rval);
		/* Issue ioctl(I_PUNLINK) */
		(void) ldi_ioctl(msu_lh, I_PUNLINK, (intptr_t)lnk_id, FKIOCTL,
		    kcred, &param);

		(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
		ldi_ident_release(msu_li);
		return (rval);
	}

	/* Start to use the path */
	rval = oplmsu_config_start(instance);
	if (rval != 0) {
		struct msu_path	*mpath;
		struct msu_dev	*mdev;

		cmn_err(CE_WARN, "oplmsu: attach(dr): "
		    "Failed to start the path. errno = %d", rval);

		len = sizeof (struct msu_path) + sizeof (struct msu_dev);
		mpath = (struct msu_path *)kmem_zalloc((size_t)len, KM_SLEEP);
		mpath->num = 1;
		mdev = (struct msu_dev *)(mpath + 1);
		mdev->dip = dip;

		/* Delete the path */
		if ((oplmsu_config_del(mpath)) == 0) {
			/* Issue ioctl(I_PUNLINK) */
			(void) ldi_ioctl(msu_lh, I_PUNLINK, (intptr_t)lnk_id,
			    FKIOCTL, kcred, &param);
		}
		kmem_free(mpath, (size_t)len);
	}

	/* Close oplmsu */
	(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
	ldi_ident_release(msu_li);
	return (rval);
}

int
oplmsu_dr_detach(dev_info_t *dip)
{
	ldi_ident_t	msu_li = NULL;
	ldi_handle_t	msu_lh = NULL;
	struct msu_path	*mpath;
	struct msu_dev	*mdev;
	upath_t		*upath;
	lpath_t		*lpath;
	int		len;
	int		instance;
	int		count = 0;
	int		param = 0;
	int		status;
	int		rval;

	/* Get instance for serial */
	instance = ddi_get_instance(dip);

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);
	mutex_enter(&oplmsu_uinst->u_lock);

	/* Get current number of paths */
	oplmsu_uinst->path_num = oplmsu_get_pathnum();

	rval = FAILURE;

	/* Check specified upath_t */
	upath = oplmsu_uinst->first_upath;
	while (upath) {
		if (instance == upath->path_no) {
			/* Save status of specified path */
			status = upath->status;
			rval = SUCCESS;
		}
		upath = upath->u_next;
		count += 1;
	}
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);

	if (rval == FAILURE) {
		if (count <= 1) {
			cmn_err(CE_WARN, "oplmsu: detach(dr): "
			    "Instance %d is last path", instance);
		} else {
			cmn_err(CE_WARN, "oplmsu: detach(dr): "
			    "Instance %d doesn't find", instance);
		}
		return (EINVAL);
	}

	/* Check status of specified path */
	if ((status == MSU_PSTAT_ACTIVE) || (status == MSU_PSTAT_STANDBY)) {
		/* Stop to use the path */
		rval = oplmsu_config_stop(instance);
		if (rval != 0) {
			cmn_err(CE_WARN, "oplmsu: detach(dr): "
			    "Failed to stop the path. errno = %d", rval);
			return (rval);
		}
	}

	/* Prepare to unlink the path */
	rval = oplmsu_config_disc(instance);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: detach(dr): "
		    "Failed to disconnect the path. errno = %d", rval);
		return (rval);
	}

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	mutex_enter(&oplmsu_uinst->l_lock);
	lpath = oplmsu_uinst->first_lpath;
	while (lpath) {
		if (lpath->path_no == instance) { /* Get link ID */
			break;
		}
		lpath = lpath->l_next;
	}
	mutex_exit(&oplmsu_uinst->l_lock);
	rw_exit(&oplmsu_uinst->lock);

	if (lpath == NULL) {
		cmn_err(CE_WARN, "oplmsu: detach(dr): Can not find link ID");
		return (EINVAL);
	}

	/* Open oplmsu */
	rval = oplmsu_open_msu(oplmsu_uinst->msu_dip, &msu_li, &msu_lh);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: detach(dr): "
		    "msu open failed. errno = %d", rval);
		return (rval);
	}

	/* Issue ioctl(I_PUNLINK) */
	rval = ldi_ioctl(msu_lh, I_PUNLINK, (intptr_t)lpath->link_id, FKIOCTL,
	    kcred, &param);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: detach(dr): "
		    "ioctl(I_PUNLINK) failed. errno = %d", rval);
		(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
		ldi_ident_release(msu_li);
		return (rval);
	}

	/* Close oplmsu(meta node) */
	(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
	ldi_ident_release(msu_li);

	len = sizeof (struct msu_path) + sizeof (struct msu_dev);
	mpath = (struct msu_path *)kmem_zalloc((size_t)len, KM_SLEEP);
	mpath->num = 1;
	mdev = (struct msu_dev *)(mpath + 1);
	mdev->dip = dip;

	/* Delete the path */
	rval = oplmsu_config_del(mpath);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: detach(dr): "
		    "Failed to delete the path. errno = %d", rval);
	}

	kmem_free(mpath, (size_t)len);
	return (rval);
}

/*
 * The ebus and the serial device path under a given CMU_CH chip
 * is expected to be always at the same address. So, it is safe
 * to hard-code the pathnames as below.
 */
#define	EBUS_PATH		"ebus@1"
#define	SERIAL_PATH		"serial@14,400000"
#define	EBUS_SERIAL_PATH	("/" EBUS_PATH "/" SERIAL_PATH)

/*
 * Given the CMU_CH dip, find the serial device dip.
 */
dev_info_t *
oplmsu_find_ser_dip(dev_info_t *cmuch_dip)
{
	int		circ1, circ2;
	dev_info_t	*ebus_dip;
	dev_info_t	*ser_dip = NULL;

	ndi_devi_enter(cmuch_dip, &circ1);
	ebus_dip = ndi_devi_findchild(cmuch_dip, EBUS_PATH);

	DBG_PRINT((CE_NOTE, "oplmsu: find-serial-dip: "
	    "ebus_dip = %p", ebus_dip));

	if (ebus_dip != NULL) {
		ndi_devi_enter(ebus_dip, &circ2);
		ser_dip = ndi_devi_findchild(ebus_dip, SERIAL_PATH);

		DBG_PRINT((CE_NOTE, "oplmsu: find-serial-dip: "
		    "ser_dip = %p", ser_dip));
		ndi_devi_exit(ebus_dip, circ2);
	}
	ndi_devi_exit(cmuch_dip, circ1);
	return (ser_dip);
}

/*
 * Find all console related serial devices.
 */
int
oplmsu_find_serial(ser_devl_t **ser_dl)
{
	dev_info_t	*root_dip;
	dev_info_t	*cmuch_dip;
	dev_info_t	*dip;
	ser_devl_t	*wrk_ser_dl;
	int		circ;
	int		count = 0;
	char		pathname[MSU_PATHNAME_SIZE];
	dev_t		devt;
	char		*namep;

	root_dip = ddi_root_node();
	ndi_devi_enter(root_dip, &circ);
	cmuch_dip = ddi_get_child(root_dip);

	while (cmuch_dip != NULL) {
		namep = ddi_binding_name(cmuch_dip);	/* Get binding name */
		if (namep == NULL) {
			cmuch_dip = ddi_get_next_sibling(cmuch_dip);
			continue;
		}

		DBG_PRINT((CE_NOTE, "oplmsu: find-serial: name => %s", namep));

		if ((strcmp(namep, MSU_CMUCH_FF) != 0) &&
		    (strcmp(namep, MSU_CMUCH_DC) != 0)) {
#ifdef DEBUG
			if (strcmp(namep, MSU_CMUCH_DBG) != 0) {
				cmuch_dip = ddi_get_next_sibling(cmuch_dip);
				continue;
			}
#else
			cmuch_dip = ddi_get_next_sibling(cmuch_dip);
			continue;
#endif
		}

		/*
		 * Online the cmuch_dip so that its in the right state
		 * to get the complete path, that is both name and address.
		 */
		(void) ndi_devi_online(cmuch_dip, 0);
		(void) ddi_pathname(cmuch_dip, pathname);
		DBG_PRINT((CE_NOTE,
		    "oplmsu: find-serial: cmu-ch path => %s", pathname));
		(void) strcat(pathname, EBUS_SERIAL_PATH);

		/*
		 * Call ddi_pathname_to_dev_t to forceload and attach
		 * the required drivers.
		 */
		devt = ddi_pathname_to_dev_t(pathname);
		DBG_PRINT((CE_NOTE, "oplmsu: find-serial: serial device "
		    "dev_t = %lx", devt));
		if ((devt != NODEV) &&
		    ((dip = oplmsu_find_ser_dip(cmuch_dip)) != NULL)) {
			wrk_ser_dl = (ser_devl_t *)
			    kmem_zalloc(sizeof (ser_devl_t), KM_SLEEP);
			wrk_ser_dl->dip = dip;
			count += 1;

			if (*ser_dl != NULL) {
				wrk_ser_dl->next = *ser_dl;
			}
			*ser_dl = wrk_ser_dl;
		}
		cmuch_dip = ddi_get_next_sibling(cmuch_dip);
	}
	ndi_devi_exit(root_dip, circ);
	return (count);
}

/* Configure STREAM */
void
oplmsu_conf_stream(uinst_t *msu_uinst)
{
	ldi_ident_t	msu_li = NULL;
	ldi_handle_t	msu_lh = NULL;
	struct msu_path	*mpath;
	struct msu_dev	*mdev;
	ser_devl_t	*ser_dl = NULL, *next_ser_dl;
	int		*plink_id;
	int		size;
	int		i;
	int		param;
	int		connected = 0;
	int		devcnt = 0;
	int		rval;

	DBG_PRINT((CE_NOTE,
	    "oplmsu: conf-stream: stream configuration start!"));

	/* Find serial devices */
	devcnt = oplmsu_find_serial(&ser_dl);
	if ((devcnt == 0) || (ser_dl == NULL)) {
		cmn_err(CE_WARN, "oplmsu: conf-stream: "
		    "Discovered serial device = %d", devcnt);
		return;
	}

	/* Open oplmsu */
	rval = oplmsu_open_msu(msu_uinst->msu_dip, &msu_li, &msu_lh);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: conf-stream: "
		    "msu open failed. errno = %d", rval);
		return;
	}

	size = (sizeof (struct msu_path) + (sizeof (struct msu_dev) * devcnt));
	mpath = (struct msu_path *)kmem_zalloc((size_t)size, KM_SLEEP);
	plink_id = (int *)kmem_zalloc((sizeof (int) * devcnt), KM_SLEEP);

	mdev = (struct msu_dev *)(mpath + 1);
	for (i = 0; i < devcnt; i++) {
		/* Connect two streams */
		rval = oplmsu_plink_serial(ser_dl->dip, msu_lh, &plink_id[i]);
		if (rval != 0) {
			cmn_err(CE_WARN, "oplmsu: conf-stream: "
			    "i_plink failed. errno = %d", rval);
			next_ser_dl = ser_dl->next;
			kmem_free(ser_dl, sizeof (ser_devl_t));
			ser_dl = next_ser_dl;
			continue;
		}

		rval = oplmsu_set_lpathnum(plink_id[i],
		    ddi_get_instance(ser_dl->dip));
		if (rval != 0) {
			cmn_err(CE_WARN, "oplmsu: conf-stream: "
			    "Link id %d is not found", plink_id[i]);
			/* Issue ioctl(I_PUNLINK) */
			(void) ldi_ioctl(msu_lh, I_PUNLINK,
			    (intptr_t)plink_id[i], FKIOCTL, kcred, &param);
			next_ser_dl = ser_dl->next;
			kmem_free(ser_dl, sizeof (ser_devl_t));
			ser_dl = next_ser_dl;
			continue;
		}

		mdev->dip = ser_dl->dip;
		next_ser_dl = ser_dl->next;
		kmem_free(ser_dl, sizeof (ser_devl_t));
		ser_dl = next_ser_dl;

		mdev++;
		connected++;
	}

	if (connected == 0) {
		cmn_err(CE_WARN, "oplmsu: conf-stream: "
		    "Connected paths = %d", connected);
		(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
		ldi_ident_release(msu_li);
		kmem_free(plink_id, (sizeof (int) * devcnt));
		kmem_free(mpath, size);
		return;
	}

	/* Setup all structure */
	mpath->num = connected;
	rval = oplmsu_config_new(mpath);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: conf-stream: "
		    "Failed to create all paths. errno = %d", rval);
		oplmsu_unlinks(msu_lh, plink_id, devcnt);
		(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
		ldi_ident_release(msu_li);
		kmem_free(plink_id, (sizeof (int) * devcnt));
		kmem_free(mpath, size);
		return;
	}

	/* Start to use all paths */
	rval = oplmsu_config_start(MSU_PATH_ALL);
	if (rval != 0) {
		cmn_err(CE_WARN, "oplmsu: conf-stream: "
		    "Failed to start all paths. errno = %d", rval);

		/* Delete the path */
		rval = oplmsu_config_del(mpath);
		if (rval == 0) {
			oplmsu_unlinks(msu_lh, plink_id, devcnt);
		}
	}

	(void) ldi_close(msu_lh, (FREAD|FWRITE), kcred);
	ldi_ident_release(msu_li);
	kmem_free(plink_id, (sizeof (int) * devcnt));
	kmem_free(mpath, size);

	DBG_PRINT((CE_NOTE, "oplmsu: conf-stream: stream configuration end!"));
}

void
oplmsu_unlinks(ldi_handle_t msu_lh, int *plink_id, int devcnt)
{
	int	i;
	int	param = 0;

	for (i = 0; i < devcnt; i++) {
		if (plink_id[i] == 0) {
			continue;
		}

		/* Issue ioctl(I_PUNLINK) */
		(void) ldi_ioctl(msu_lh, I_PUNLINK, (intptr_t)plink_id[i],
		    FKIOCTL, kcred, &param);
	}
}

void
oplmsu_setup(uinst_t *msu_uinst)
{

	DBG_PRINT((CE_NOTE, "oplmsu: setup: Background thread start!"));

	mutex_enter(&oplmsu_bthrd_excl);
	if (oplmsu_conf_st == MSU_CONFIGURING) {
		mutex_exit(&oplmsu_bthrd_excl);
		oplmsu_conf_stream(msu_uinst);	/* Configure stream */
		mutex_enter(&oplmsu_bthrd_excl);
		oplmsu_conf_st = MSU_CONFIGURED;
		cv_broadcast(&oplmsu_conf_cv);	/* Wake up from cv_wait_sig() */
	}

	if (oplmsu_bthrd_id != NULL) {
		oplmsu_bthrd_id = NULL;
	}
	mutex_exit(&oplmsu_bthrd_excl);

	DBG_PRINT((CE_NOTE, "oplmsu: setup: Background thread end!"));

	thread_exit();
}

int
oplmsu_create_upath(dev_info_t *dip)
{
	upath_t		*upath;
	lpath_t		*lpath;
	dev_info_t	*cmuch_dip;
	int		instance;
	int		lsb;

	cmuch_dip = ddi_get_parent(ddi_get_parent(dip));
	lsb = ddi_prop_get_int(DDI_DEV_T_ANY, cmuch_dip, 0, MSU_BOARD_PROP,
	    FAILURE);
	if (lsb == FAILURE) {
		return (lsb);
	}

	instance = ddi_get_instance(dip);

	mutex_enter(&oplmsu_uinst->l_lock);
	lpath = oplmsu_uinst->first_lpath;
	while (lpath) {
		if (lpath->path_no == instance) {
			break;
		}
		lpath = lpath->l_next;
	}

	if (lpath == NULL) {
		mutex_exit(&oplmsu_uinst->l_lock);
		return (ENODEV);
	}

	upath = (upath_t *)kmem_zalloc(sizeof (upath_t), KM_SLEEP);

	/*
	 * Initialize members of upath_t
	 */

	upath->path_no = instance;
	upath->lpath = lpath;
	upath->ser_devcb.dip = dip;
	upath->ser_devcb.lsb = lsb;
	oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_STOP, MSU_PSTAT_EMPTY,
	    MSU_STOP);

	lpath->src_upath = NULL;
	lpath->status = MSU_EXT_NOTUSED;
	mutex_exit(&oplmsu_uinst->l_lock);

	oplmsu_link_upath(upath);
	return (SUCCESS);
}

/* Setup new upper instance structure */
int
oplmsu_config_new(struct msu_path *mpath)
{
	struct msu_dev	*mdev;
	int		i;
	int		rval = SUCCESS;

	DBG_PRINT((CE_NOTE, "oplmsu: conf-new: config_new() called"));
	ASSERT(mpath);

	if (mpath->num == 0) {
		cmn_err(CE_WARN, "oplmsu: conf-new: "
		    "Number of paths = %d", mpath->num);
		return (EINVAL);
	}

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);

	mutex_enter(&oplmsu_uinst->l_lock);
	rval = oplmsu_check_lpath_usable();
	mutex_exit(&oplmsu_uinst->l_lock);

	if (rval == BUSY) { /* Check whether Lower path is usable */
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-new: "
		    "Other processing is using this device");
		return (EBUSY);
	}

	/*
	 * Because the OPLMSU instance already exists when the upper path
	 * table exists, the configure_new processing cannot be done.
	 */

	mutex_enter(&oplmsu_uinst->u_lock);

	if ((oplmsu_uinst->first_upath != NULL) ||
	    (oplmsu_uinst->last_upath != NULL)) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-new: upath_t already exist");
		return (EINVAL);
	}

	/*
	 * Because the config_new processing has already been done
	 * if oplmsu_uinst->path_num isn't -1, this processing cannot be
	 * continued.
	 */

	if (oplmsu_uinst->path_num != UNDEFINED) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-new: "
		    "conf-new processing has already been completed");
		return (EINVAL);
	}

	/*
	 * Only the number of specified paths makes the upper path
	 * information tables.
	 */

	mdev = (struct msu_dev *)(mpath + 1);
	for (i = 0; i < mpath->num; i++) {
		/*
		 * Associate upper path information table with lower path
		 * information table.
		 *
		 * If the upper path information table and the lower path
		 * information table cannot be associated, the link list of
		 * the upper path information table is released.
		 */
		rval = oplmsu_create_upath(mdev->dip);
		if (rval != SUCCESS) {
			oplmsu_delete_upath_info();
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			cmn_err(CE_WARN, "oplmsu: conf-new: "
			    "Failed to create upath %d", rval);
			return (rval);
		}

		mdev++;
	}

	/*
	 * Setup members of uinst_t
	 */

	oplmsu_uinst->inst_status = oplmsu_get_inst_status();
	oplmsu_uinst->path_num = mpath->num;
	oplmsu_uinst->lower_queue = NULL;
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}

/* Add path information */
int
oplmsu_config_add(dev_info_t *dip)
{
	upath_t	*upath;
	int	instance;
	int	rval = SUCCESS;

	DBG_PRINT((CE_NOTE, "oplmsu: conf-add: config_add() called"));
	ASSERT(dip);

	instance = ddi_get_instance(dip);
	rw_enter(&oplmsu_uinst->lock, RW_WRITER);

	if (oplmsu_uinst->path_num == UNDEFINED) {
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-add: "
		    "conf-new processing has not been completed yet");
		return (EINVAL);
	}

	mutex_enter(&oplmsu_uinst->u_lock);
	upath = oplmsu_search_upath_info(instance);
	if (upath != NULL) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-add: "
		    "Proper upath_t doesn't find");
		return (EINVAL);
	}

	rval = oplmsu_create_upath(dip);
	if (rval != SUCCESS) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-add: "
		    "Failed to create upath %d", rval);
		return (rval);
	}

	oplmsu_uinst->inst_status = oplmsu_get_inst_status();
	oplmsu_uinst->path_num = oplmsu_get_pathnum();
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}

/* Delete each path information */
int
oplmsu_config_del(struct msu_path *mpath)
{
	struct msu_dev	*mdev;
	upath_t		*upath;
	lpath_t		*lpath;
	int		rval = SUCCESS;
	int		use_flag;
	int		i;

	DBG_PRINT((CE_NOTE, "oplmsu: conf-del: config_del() called"));
	ASSERT(mpath);

	mdev = (struct msu_dev *)(mpath + 1);

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);
	mutex_enter(&oplmsu_uinst->u_lock);
	for (i = 0; i < mpath->num; i++) {
		upath = oplmsu_search_upath_info(ddi_get_instance(mdev->dip));
		if (upath == NULL) {
			cmn_err(CE_WARN, "oplmsu: conf-del: "
			    "Proper upath_t doesn't find");
			rval = ENODEV;
			mdev++;
			continue;
		}

		lpath = upath->lpath;
		if (lpath == NULL) {
			if ((upath->traditional_status == MSU_WSTP_ACK) ||
			    (upath->traditional_status == MSU_WSTR_ACK) ||
			    (upath->traditional_status == MSU_WPTH_CHG) ||
			    (upath->traditional_status == MSU_WTCS_ACK) ||
			    (upath->traditional_status == MSU_WTMS_ACK) ||
			    (upath->traditional_status == MSU_WPPS_ACK) ||
			    (upath->traditional_status == MSU_WWSZ_ACK) ||
			    (upath->traditional_status == MSU_WCAR_ACK)) {
				cmn_err(CE_WARN, "oplmsu: conf-del: "
				    "Other processing is using this device");
				rval = EBUSY;
				mdev++;
				continue;
			}

			if ((upath->status != MSU_PSTAT_DISCON) ||
			    (upath->traditional_status != MSU_DISCON)) {
				cmn_err(CE_WARN, "oplmsu: conf-del: "
				    "Status of path is improper");
				rval = EINVAL;
				mdev++;
				continue;
			}
		} else {
			mutex_enter(&oplmsu_uinst->l_lock);
			use_flag = oplmsu_set_ioctl_path(lpath, NULL, NULL);
			if (use_flag == BUSY) {
				mutex_exit(&oplmsu_uinst->l_lock);
				cmn_err(CE_WARN, "oplmsu: conf-del: "
				    "Other processing is using lower path");
				rval = EBUSY;
				mdev++;
				continue;
			}

			if (((upath->status != MSU_PSTAT_STOP) ||
			    (upath->traditional_status != MSU_STOP)) &&
			    ((upath->status != MSU_PSTAT_FAIL) ||
			    (upath->traditional_status != MSU_FAIL))) {
				oplmsu_clear_ioctl_path(lpath);
				mutex_exit(&oplmsu_uinst->l_lock);
				cmn_err(CE_WARN, "oplmsu: conf-del: "
				    "Status of path isn't 'Offline:stop/fail'");
				rval = EINVAL;
				mdev++;
				continue;
			}
			lpath->src_upath = NULL;
			lpath->status = MSU_SETID_NU;
			oplmsu_clear_ioctl_path(lpath);
			mutex_exit(&oplmsu_uinst->l_lock);
		}
		oplmsu_unlink_upath(upath);	/* Unlink upath_t */
		kmem_free(upath, sizeof (upath_t));
		mdev++;
	}

	oplmsu_uinst->inst_status = oplmsu_get_inst_status();
	oplmsu_uinst->path_num = oplmsu_get_pathnum();
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (rval);
}

/* Stop to use the path */
int
oplmsu_config_stop(int pathnum)
{
	upath_t	*upath, *altn_upath;
	lpath_t	*lpath, *altn_lpath;
	queue_t	*stp_queue = NULL;
	queue_t	*dst_queue = NULL;
	mblk_t	*nmp = NULL, *fmp = NULL;
	ctrl_t	*ctrl;
	int	term_ioctl, term_stat;
	int	use_flag;

	DBG_PRINT((CE_NOTE,
	    "oplmsu: conf-stop: config_stop(%d) called", pathnum));

	if (pathnum == MSU_PATH_ALL) {
		cmn_err(CE_WARN, "oplmsu: conf-stop: "
		    "All path can't be transferred to the status of "
		    "'Offline:stop'");
		return (EINVAL);
	}

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);
	mutex_enter(&oplmsu_uinst->u_lock);

	upath = oplmsu_search_upath_info(pathnum);	/* Search upath_t */
	if (upath == NULL) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-stop: "
		    "Proper upath_t doesn't find");
		return (ENODEV);
	}

	lpath = upath->lpath;
	if (lpath == NULL) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-stop: "
		    "Proper lpath_t doesn't exist");
		return (ENODEV);
	}

	mutex_enter(&oplmsu_uinst->l_lock);

	/* Check status of lpath_t */
	use_flag = oplmsu_set_ioctl_path(lpath, NULL, NULL);
	if (use_flag == BUSY) {
		mutex_exit(&oplmsu_uinst->l_lock);
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-stop: "
		    "Other processing is using lower path");
		return (EBUSY);
	}

	if (upath->status == MSU_PSTAT_FAIL) {
		oplmsu_clear_ioctl_path(lpath);
		mutex_exit(&oplmsu_uinst->l_lock);
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		return (EIO);
	} else if ((upath->status == MSU_PSTAT_STOP) &&
	    (upath->traditional_status == MSU_STOP)) {
		oplmsu_clear_ioctl_path(lpath);
		mutex_exit(&oplmsu_uinst->l_lock);
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		return (SUCCESS);
	} else if ((upath->status == MSU_PSTAT_STANDBY) &&
	    (upath->traditional_status == MSU_STANDBY)) {
		oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_STOP,
		    upath->status, MSU_STOP);
		oplmsu_clear_ioctl_path(lpath);
		lpath->src_upath = NULL;
		lpath->status = MSU_EXT_NOTUSED;

		oplmsu_uinst->inst_status = oplmsu_get_inst_status();
		mutex_exit(&oplmsu_uinst->l_lock);
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		return (SUCCESS);
	} else if ((upath->status == MSU_PSTAT_ACTIVE) &&
	    (upath->traditional_status == MSU_ACTIVE)) {
		altn_upath = oplmsu_search_standby();
		if (altn_upath == NULL) { /* Alternate path doesn't exist */
			DBG_PRINT((CE_NOTE, "oplmsu: conf-stop: "
			    "Alternate upper path doesn't find"));
			oplmsu_clear_ioctl_path(lpath);
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			return (EINVAL);
		}

		if ((fmp = allocb(sizeof (char), BPRI_LO)) == NULL) {
			oplmsu_clear_ioctl_path(lpath);
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			return (ENOSR);
		}

		if (oplmsu_stop_prechg(&nmp, &term_ioctl, &term_stat) !=
		    SUCCESS) {
			oplmsu_clear_ioctl_path(lpath);
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			freeb(fmp);
			return (ENOSR);
		}

		altn_lpath = altn_upath->lpath;
		use_flag = oplmsu_set_ioctl_path(altn_lpath, NULL, NULL);
		if (use_flag == BUSY) {
			oplmsu_clear_ioctl_path(lpath);
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);

			cmn_err(CE_WARN, "oplmsu: conf-stop: "
			    "Other processing is using alternate lower path");
			freeb(fmp);
			freemsg(nmp);
			return (EBUSY);
		}

		dst_queue = WR(altn_lpath->lower_queue);

		/* termios is not held. Change alternate path to MSU_ACTIVE */
		if (nmp == NULL) {
			altn_upath->traditional_status = term_stat;
			altn_lpath->src_upath = upath;
			altn_lpath->status = MSU_EXT_VOID;

			oplmsu_uinst->lower_queue = NULL;

			ctrl = oplmsu_uinst->user_ctrl;
			if (ctrl != NULL) {
				mutex_enter(&oplmsu_uinst->c_lock);
				stp_queue = WR(ctrl->queue);
				mutex_exit(&oplmsu_uinst->c_lock);
				noenable(stp_queue);
				oplmsu_queue_flag = 1;
			}

			/* Make M_FLUSH and send to alternate path */
			oplmsu_cmn_set_mflush(fmp);
			putq(dst_queue, fmp);

			/* Change status of alternate path */
			oplmsu_cmn_set_upath_sts(altn_upath, MSU_PSTAT_ACTIVE,
			    altn_upath->status, MSU_ACTIVE);

			oplmsu_clear_ioctl_path(altn_lpath);
			altn_lpath->uinst = oplmsu_uinst;
			altn_lpath->src_upath = NULL;
			altn_lpath->status = MSU_EXT_NOTUSED;

			/* Notify of the active path changing */
			prom_opl_switch_console(altn_upath->ser_devcb.lsb);

			/* Send XON to notify active path */
			(void) oplmsu_cmn_put_xoffxon(dst_queue, MSU_XON_4);

			/* Send XOFF to notify all standby paths */
			oplmsu_cmn_putxoff_standby();

			oplmsu_uinst->lower_queue = RD(dst_queue);
			ctrl = oplmsu_uinst->user_ctrl;

			/* Switch active path of oplmsu */
			if (ctrl != NULL) {
				queue_t	*altn_queue;

				mutex_enter(&oplmsu_uinst->c_lock);
				altn_queue = WR(ctrl->queue);
				mutex_exit(&oplmsu_uinst->c_lock);

				/* Restart queuing of user access node */
				enableok(altn_queue);

				oplmsu_queue_flag = 0;
				mutex_exit(&oplmsu_uinst->l_lock);
				mutex_exit(&oplmsu_uinst->u_lock);
				oplmsu_wcmn_high_qenable(altn_queue, RW_WRITER);
				mutex_enter(&oplmsu_uinst->u_lock);
				mutex_enter(&oplmsu_uinst->l_lock);
			}

			/* Stop previous active path */
			oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_STOP,
			    upath->status, MSU_STOP);

			lpath->uinst = NULL;
			lpath->src_upath = NULL;
			lpath->status = MSU_EXT_NOTUSED;
			oplmsu_clear_ioctl_path(lpath);

			oplmsu_uinst->inst_status = oplmsu_get_inst_status();
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			return (SUCCESS);
		}

		/* Send termios information to alternate path */
		if (canput(dst_queue)) {
			altn_upath->traditional_status = term_stat;
			altn_lpath->src_upath = upath;
			altn_lpath->status = MSU_EXT_VOID;

			upath->traditional_status = MSU_WSTP_ACK;
			lpath->uinst = NULL;

			oplmsu_uinst->lower_queue = NULL;

			ctrl = oplmsu_uinst->user_ctrl;
			if (ctrl != NULL) {
				mutex_enter(&oplmsu_uinst->c_lock);
				stp_queue = WR(ctrl->queue);
				mutex_exit(&oplmsu_uinst->c_lock);
				noenable(stp_queue);
				oplmsu_queue_flag = 1;
			}

			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			oplmsu_cmn_set_mflush(fmp);
			putq(dst_queue, fmp);
			putq(dst_queue, nmp);

			mutex_enter(&oplmsu_uinst->l_lock);
			lpath->sw_flag = 1;
			while (lpath->sw_flag != 0) {
				/* Wait for the completion of path switching */
				cv_wait(&lpath->sw_cv, &oplmsu_uinst->l_lock);
			}
			mutex_exit(&oplmsu_uinst->l_lock);
			return (SUCCESS);
		} else {
			oplmsu_clear_ioctl_path(altn_lpath);
			oplmsu_clear_ioctl_path(lpath);
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			freeb(fmp);
			freemsg(nmp);
			return (FAILURE);
		}
		/* NOTREACHED */
	} else {
		oplmsu_clear_ioctl_path(lpath);
		mutex_exit(&oplmsu_uinst->l_lock);
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);

		cmn_err(CE_WARN, "oplmsu: conf-stop: "
		    "Status of path is improper");
		return (EINVAL);
	}
	/* NOTREACHED */
}

/* Start to use path */
int
oplmsu_config_start(int pathnum)
{
	upath_t	*upath = NULL;
	lpath_t	*lpath = NULL;
	queue_t	*dst_queue, *main_rq = NULL;
	int	msu_tty_port;

	DBG_PRINT((CE_NOTE,
	    "oplmsu: conf-start: config_start(%d) called", pathnum));

	rw_enter(&oplmsu_uinst->lock, RW_WRITER);
	mutex_enter(&oplmsu_uinst->u_lock);

	if (oplmsu_get_inst_status() == INST_STAT_BUSY) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		return (EBUSY);
	}

	if (pathnum == MSU_PATH_ALL) {
		(void) oplmsu_search_min_stop_path();
	}

	for (upath = oplmsu_uinst->first_upath; upath; ) {
		if ((pathnum != MSU_PATH_ALL) && (upath->path_no != pathnum)) {
			upath = upath->u_next;
			continue;
		}

		if (upath->path_no == pathnum) {
			lpath = upath->lpath;
			if (lpath == NULL) {
				mutex_exit(&oplmsu_uinst->u_lock);
				rw_exit(&oplmsu_uinst->lock);
				cmn_err(CE_WARN, "oplmsu: conf-start: "
				    "Proper lpath_t doesn't exist");
				return (EINVAL);
			}

			oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_STANDBY,
			    upath->status, MSU_STANDBY);

			mutex_enter(&oplmsu_uinst->l_lock);
			lpath->src_upath = NULL;
			lpath->status = MSU_EXT_NOTUSED;
			mutex_exit(&oplmsu_uinst->l_lock);
			mutex_exit(&oplmsu_uinst->u_lock);
			rw_exit(&oplmsu_uinst->lock);
			return (SUCCESS);
		}

		/*
		 * with PATH_ALL
		 */
		lpath = upath->lpath;
		if (lpath == NULL) {
			upath = upath->u_next;

			DBG_PRINT((CE_WARN, "oplmsu: conf-start: "
			    "Proper lpath_t doesn't exist"));
			continue;
		}

		msu_tty_port = ddi_prop_get_int(DDI_DEV_T_ANY,
		    oplmsu_uinst->msu_dip, 0, MSU_TTY_PORT_PROP, -1);

		if (upath->ser_devcb.lsb == msu_tty_port) {
			/* Notify of the active path changing */
			prom_opl_switch_console(upath->ser_devcb.lsb);

			oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_ACTIVE,
			    upath->status, MSU_ACTIVE);

			mutex_enter(&oplmsu_uinst->l_lock);
			main_rq = RD(lpath->lower_queue);
			dst_queue = WR(lpath->lower_queue);
			lpath->src_upath = NULL;
			lpath->status = MSU_EXT_NOTUSED;
			lpath->uinst = oplmsu_uinst;
			mutex_exit(&oplmsu_uinst->l_lock);

			/* Send XON to notify active path */
			(void) oplmsu_cmn_put_xoffxon(dst_queue, MSU_XON_4);
		} else {
			oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_STANDBY,
			    upath->status, MSU_STANDBY);

			mutex_enter(&oplmsu_uinst->l_lock);
			lpath->src_upath = NULL;
			lpath->status = MSU_EXT_NOTUSED;
			mutex_exit(&oplmsu_uinst->l_lock);
		}
		upath = upath->u_next;
	}

	if (main_rq == NULL) {
		upath_t	*altn_upath;
		lpath_t	*altn_lpath;

		altn_upath = oplmsu_search_standby();
		if (altn_upath) {
			oplmsu_cmn_set_upath_sts(altn_upath, MSU_PSTAT_ACTIVE,
			    altn_upath->status, MSU_ACTIVE);

			/* Notify of the active path changing */
			prom_opl_switch_console(altn_upath->ser_devcb.lsb);

			altn_lpath = altn_upath->lpath;
			if (altn_lpath) {
				mutex_enter(&oplmsu_uinst->l_lock);
				main_rq = RD(altn_lpath->lower_queue);
				dst_queue = WR(altn_lpath->lower_queue);
				altn_lpath->src_upath = NULL;
				altn_lpath->status = MSU_EXT_NOTUSED;
				altn_lpath->uinst = oplmsu_uinst;
				mutex_exit(&oplmsu_uinst->l_lock);

				/* Send XON to notify active path */
				(void) oplmsu_cmn_put_xoffxon(dst_queue,
				    MSU_XON_4);
			} else {
				cmn_err(CE_WARN, "oplmsu: conf-start: "
				    "Proper alternate lpath_t doesn't exist");
			}
		} else {
			cmn_err(CE_WARN, "oplmsu: conf-start: "
			    "Proper alternate upath_t doesn't exist");
		}
	}

	mutex_enter(&oplmsu_uinst->l_lock);

	/* Send XOFF to notify all standby paths */
	oplmsu_cmn_putxoff_standby();

	/* Change active path of oplmsu */
	oplmsu_uinst->lower_queue = main_rq;
	oplmsu_uinst->inst_status = oplmsu_get_inst_status();
	mutex_exit(&oplmsu_uinst->l_lock);
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}

/* Prepare of unlink path */
int
oplmsu_config_disc(int pathnum)
{
	upath_t	*upath;
	lpath_t	*lpath;
	int	use_flag;

	DBG_PRINT((CE_NOTE,
	    "oplmsu: conf-disc: config_disc(%d) called", pathnum));

	rw_enter(&oplmsu_uinst->lock, RW_READER);
	mutex_enter(&oplmsu_uinst->u_lock);

	upath = oplmsu_search_upath_info(pathnum);
	if (upath == NULL) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-disc: "
		    "Proper upath_t doesn't find");
		return (EINVAL);
	}

	if ((upath->status == MSU_PSTAT_DISCON) ||
	    (upath->traditional_status == MSU_DISCON)) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		return (SUCCESS);
	} else if (((upath->status != MSU_PSTAT_STOP) ||
	    (upath->traditional_status != MSU_STOP)) &&
	    ((upath->status != MSU_PSTAT_FAIL) ||
	    (upath->traditional_status != MSU_FAIL))) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-disc: "
		    "Status of path is improper");
		return (EINVAL);
	}

	lpath = upath->lpath;
	if (lpath == NULL) {
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-disc: "
		    "Proper lpath_t doesn't exist");
		return (ENODEV);
	}

	mutex_enter(&oplmsu_uinst->l_lock);

	/* Check lower path status */
	use_flag = oplmsu_set_ioctl_path(lpath, NULL, NULL);
	if (use_flag == BUSY) {
		mutex_exit(&oplmsu_uinst->l_lock);
		mutex_exit(&oplmsu_uinst->u_lock);
		rw_exit(&oplmsu_uinst->lock);
		cmn_err(CE_WARN, "oplmsu: conf-disc: "
		    "Other processing is using lower path");
		return (EBUSY);
	}

	upath->status = MSU_PSTAT_STOP;
	upath->traditional_status = MSU_SETID;

	oplmsu_clear_ioctl_path(lpath);
	mutex_exit(&oplmsu_uinst->l_lock);
	mutex_exit(&oplmsu_uinst->u_lock);
	rw_exit(&oplmsu_uinst->lock);
	return (SUCCESS);
}