root/usr/src/uts/sun4u/opl/io/oplmsu/oplmsu_ioctl_lrp.c
/*
 * 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
 */

#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/prom_plat.h>
#include <sys/oplmsu/oplmsu.h>
#include <sys/oplmsu/oplmsu_proto.h>

/*
 *      LOWER READ SERVICE PROCEDURE
 */

/* termios ioctl response received */
int
oplmsu_lrioctl_termios(queue_t *lrq, mblk_t *mp)
{
        upath_t         *upath, *altn_upath = NULL, *stp_upath = NULL;
        lpath_t         *lpath, *altn_lpath = NULL, *stp_lpath = NULL;
        struct iocblk   *iocp, *temp_iocp = NULL;
        mblk_t          *hndl_mp, *nmp = NULL, *fmp = NULL;
        queue_t         *dst_queue;
        int             term_ioctl, term_stat, sts;
        int             ack_flag, termio_flag, chkflag;
        ulong_t         trad_sts;

        rw_enter(&oplmsu_uinst->lock, RW_READER);
        iocp = (struct iocblk *)mp->b_rptr;

        mutex_enter(&oplmsu_uinst->u_lock);
        mutex_enter(&oplmsu_uinst->l_lock);
        lpath = (lpath_t *)lrq->q_ptr;
        hndl_mp = lpath->hndl_mp;

        upath = oplmsu_search_upath_info(lpath->path_no);
        trad_sts = upath->traditional_status;
        mutex_exit(&oplmsu_uinst->l_lock);
        mutex_exit(&oplmsu_uinst->u_lock);

        if (((iocp->ioc_cmd == TCSETS) && (trad_sts == MSU_WTCS_ACK)) ||
            ((iocp->ioc_cmd == TCSETSW) && (trad_sts == MSU_WTCS_ACK)) ||
            ((iocp->ioc_cmd == TCSETSF) && (trad_sts == MSU_WTCS_ACK)) ||
            ((iocp->ioc_cmd == TIOCMSET) && (trad_sts == MSU_WTMS_ACK)) ||
            ((iocp->ioc_cmd == TIOCSPPS) && (trad_sts == MSU_WPPS_ACK)) ||
            ((iocp->ioc_cmd == TIOCSWINSZ) && (trad_sts == MSU_WWSZ_ACK)) ||
            ((iocp->ioc_cmd == TIOCSSOFTCAR) && (trad_sts == MSU_WCAR_ACK))) {
                if (mp->b_datap->db_type == M_IOCACK) {
                        ack_flag = ACK_RES;
                } else {
                        ack_flag = NAK_RES;
                }
        } else {
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
                cmn_err(CE_WARN, "oplmsu: lr-termios: "
                    "Status of path is improper");
                return (SUCCESS);
        }

        switch (trad_sts) {
        case MSU_WTCS_ACK :
                termio_flag = MSU_TIOS_TCSETS;
                break;

        case MSU_WTMS_ACK :
                termio_flag = MSU_TIOS_MSET;
                break;

        case MSU_WPPS_ACK :
                termio_flag = MSU_TIOS_PPS;
                break;

        case MSU_WWSZ_ACK :
                termio_flag = MSU_TIOS_WINSZP;
                break;

        case MSU_WCAR_ACK :
                termio_flag = MSU_TIOS_SOFTCAR;
                break;

        default :
                termio_flag = MSU_TIOS_END;
                break;
        }

        if (hndl_mp == NULL) {
                switch (trad_sts) {
                case MSU_WTCS_ACK :     /* FALLTHRU */
                case MSU_WTMS_ACK :     /* FALLTHRU */
                case MSU_WPPS_ACK :     /* FALLTHRU */
                case MSU_WWSZ_ACK :     /* FALLTHRU */
                case MSU_WCAR_ACK :
                        chkflag = MSU_CMD_STOP;
                        break;

                default :
                        chkflag = FAILURE;
                        break;
                }
        } else {
                /* xoff/xon received */
                if (hndl_mp->b_datap->db_type == M_DATA) {
                        chkflag = MSU_CMD_ACTIVE;
                } else { /* Normal termios */
                        temp_iocp = (struct iocblk *)hndl_mp->b_rptr;
                        chkflag = temp_iocp->ioc_cmd;
                }
        }

        if ((chkflag == MSU_CMD_ACTIVE) || (chkflag == MSU_CMD_STOP)) {
                if (ack_flag == ACK_RES) { /* M_IOCACK received */
                        ctrl_t  *ctrl;

                        if (oplmsu_cmn_prechg_termio(lrq, mp, MSU_READ_SIDE,
                            termio_flag, &nmp, &term_stat) == FAILURE) {
                                rw_exit(&oplmsu_uinst->lock);
                                return (FAILURE);
                        }

                        OPLMSU_RWLOCK_UPGRADE();
                        mutex_enter(&oplmsu_uinst->u_lock);
                        if (term_stat != MSU_WPTH_CHG) {
                                upath->traditional_status = term_stat;
                                mutex_exit(&oplmsu_uinst->u_lock);
                                rw_exit(&oplmsu_uinst->lock);
                                freemsg(mp);

                                OPLMSU_TRACE(RD(lrq), nmp, MSU_TRC_LO);

                                /* Continue sending termios ioctls */
                                qreply(RD(lrq), nmp);
                                return (SUCCESS);
                        }
                        freemsg(mp);

                        /* Change status of new active path */
                        oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_ACTIVE,
                            upath->status, MSU_ACTIVE);

                        mutex_enter(&oplmsu_uinst->l_lock);
                        lpath->uinst = oplmsu_uinst;
                        dst_queue = lpath->hndl_uqueue;

                        ctrl = oplmsu_uinst->user_ctrl;
                        if ((chkflag == MSU_CMD_ACTIVE) && (hndl_mp != NULL)) {
                                /* Put a message(M_DATA) on a queue */
                                if (ctrl != NULL) {
                                        mutex_enter(&oplmsu_uinst->c_lock);
                                        (void) putq(RD(ctrl->queue), hndl_mp);
                                        mutex_exit(&oplmsu_uinst->c_lock);
                                }
                        }

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

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

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

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

                        /* Change status of stopped or old-active path */
                        if (chkflag == MSU_CMD_STOP) {
                                sts = MSU_PSTAT_STOP;
                                trad_sts = MSU_STOP;
                        } else { /* == MSU_CMD_ACTIVE */
                                sts = MSU_PSTAT_STANDBY;
                                trad_sts = MSU_STANDBY;
                        }
                        oplmsu_cmn_set_upath_sts(stp_upath, sts,
                            stp_upath->status, trad_sts);

                        /* Send XOFF to notify all standby paths */
                        oplmsu_cmn_putxoff_standby();
                        oplmsu_uinst->lower_queue = lrq;
                        oplmsu_uinst->inst_status = oplmsu_get_inst_status();
                        mutex_exit(&oplmsu_uinst->l_lock);
                        mutex_exit(&oplmsu_uinst->u_lock);

                        /* Change active path of user node */
                        if (ctrl != NULL) {
                                queue_t *temp_queue;

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

                                /* Reschedule a queue for service */
                                enableok(temp_queue);

                                oplmsu_queue_flag = 0;
                                oplmsu_wcmn_high_qenable(temp_queue, RW_WRITER);
                        }
                        rw_exit(&oplmsu_uinst->lock);

                        if (nmp != NULL) {
                                freemsg(nmp);
                        }

                        /* Wake up oplmsu_config_stop */
                        mutex_enter(&oplmsu_uinst->l_lock);
                        if (stp_lpath->sw_flag) {
                                stp_lpath->sw_flag = 0;
                                cv_signal(&stp_lpath->sw_cv);
                        }
                        mutex_exit(&oplmsu_uinst->l_lock);
                        return (SUCCESS);
                } else { /* M_IOCNAK received */
                        mutex_enter(&oplmsu_uinst->u_lock);
                        mutex_enter(&oplmsu_uinst->l_lock);
                        if ((chkflag == MSU_CMD_ACTIVE) &&
                            (lpath->hndl_uqueue == NULL)) {
                                oplmsu_clear_ioctl_path(lpath);
                                stp_upath = lpath->src_upath;
                                lpath->src_upath = NULL;
                                lpath->status = MSU_EXT_NOTUSED;
                                mutex_exit(&oplmsu_uinst->l_lock);

                                oplmsu_cmn_set_upath_sts(upath,
                                    MSU_PSTAT_STANDBY, upath->status,
                                    MSU_STANDBY);
                                mutex_exit(&oplmsu_uinst->u_lock);

                                if (hndl_mp != NULL) {
                                        freemsg(hndl_mp);
                                }

                                OPLMSU_RWLOCK_UPGRADE();
                                mutex_enter(&oplmsu_uinst->u_lock);
                                oplmsu_uinst->inst_status =
                                    oplmsu_get_inst_status();
                                mutex_exit(&oplmsu_uinst->u_lock);
                                rw_exit(&oplmsu_uinst->lock);
                                return (SUCCESS);
                        } else if ((chkflag == MSU_CMD_STOP) &&
                            (lpath->src_upath != NULL) &&
                            (lpath->src_upath->lpath->sw_flag)) {
                        /* MSU_CMD_STOP for active path */

                                dst_queue = RD(lpath->hndl_uqueue);
                                stp_upath = lpath->src_upath;

                                /* Search alternate path from standby paths */
                                altn_upath = oplmsu_search_standby();
                                if (altn_upath == NULL) {
                                        altn_upath = upath;
                                }

                                mutex_exit(&oplmsu_uinst->l_lock);
                                if (oplmsu_cmn_allocmb(lrq, mp, &fmp,
                                    sizeof (char), MSU_READ_SIDE) == FAILURE) {
                                        mutex_exit(&oplmsu_uinst->u_lock);
                                        rw_exit(&oplmsu_uinst->lock);
                                        return (FAILURE);
                                }

                                if (oplmsu_cmn_prechg(lrq, mp, MSU_READ_SIDE,
                                    &nmp, &term_ioctl, &term_stat) == FAILURE) {
                                        mutex_exit(&oplmsu_uinst->u_lock);
                                        rw_exit(&oplmsu_uinst->lock);
                                        freeb(fmp);
                                        return (FAILURE);
                                }

                                altn_upath->traditional_status = term_stat;
                                altn_lpath = altn_upath->lpath;

                                mutex_enter(&oplmsu_uinst->l_lock);
                                altn_lpath->hndl_mp = hndl_mp;
                                altn_lpath->hndl_uqueue = dst_queue;
                                altn_lpath->src_upath = stp_upath;
                                altn_lpath->status = MSU_EXT_VOID;
                                dst_queue = RD(altn_lpath->lower_queue);

                                oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_FAIL,
                                    upath->status, MSU_FAIL);

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

                                OPLMSU_RWLOCK_UPGRADE();
                                mutex_enter(&oplmsu_uinst->u_lock);
                                oplmsu_uinst->inst_status =
                                    oplmsu_get_inst_status();
                                mutex_exit(&oplmsu_uinst->u_lock);
                                rw_exit(&oplmsu_uinst->lock);
                                freemsg(mp);
                                oplmsu_cmn_set_mflush(fmp);

                                OPLMSU_TRACE(dst_queue, fmp, MSU_TRC_LO);
                                qreply(dst_queue, fmp);

                                OPLMSU_TRACE(dst_queue, nmp, MSU_TRC_LO);
                                qreply(dst_queue, nmp);
                                return (SUCCESS);
                        }
                }
        } else if ((chkflag == TCSETS) || (chkflag == TCSETSW) ||
            (chkflag == TCSETSF) || (chkflag == TIOCMSET) ||
            (chkflag == TIOCSPPS) || (chkflag == TIOCSWINSZ) ||
            (chkflag == TIOCSSOFTCAR)) {
                mutex_enter(&oplmsu_uinst->u_lock);
                mutex_enter(&oplmsu_uinst->l_lock);

                if ((ack_flag == ACK_RES) &&
                    (lpath->hndl_uqueue != NULL)) { /* M_IOCACK received */
                        mutex_exit(&oplmsu_uinst->l_lock);
                        mutex_exit(&oplmsu_uinst->u_lock);
                        if (oplmsu_cmn_copymb(lrq, mp, &nmp, hndl_mp,
                            MSU_READ_SIDE) == FAILURE) {
                                rw_exit(&oplmsu_uinst->lock);
                                return (FAILURE);
                        }

                        OPLMSU_RWLOCK_UPGRADE();
                        switch (chkflag) {
                        case TCSETS :   /* FALLTHRU */
                        case TCSETSW :  /* FALLTHRU */
                        case TCSETSF :
                                if (oplmsu_uinst->tcsets_p != NULL) {
                                        freemsg(oplmsu_uinst->tcsets_p);
                                }
                                oplmsu_uinst->tcsets_p = nmp;
                                break;

                        case TIOCMSET :
                                if (oplmsu_uinst->tiocmset_p != NULL) {
                                        freemsg(oplmsu_uinst->tiocmset_p);
                                }
                                oplmsu_uinst->tiocmset_p = nmp;
                                break;

                        case TIOCSPPS :
                                if (oplmsu_uinst->tiocspps_p != NULL) {
                                        freemsg(oplmsu_uinst->tiocspps_p);
                                }
                                oplmsu_uinst->tiocspps_p = nmp;
                                break;

                        case TIOCSWINSZ :
                                if (oplmsu_uinst->tiocswinsz_p != NULL) {
                                        freemsg(oplmsu_uinst->tiocswinsz_p);
                                }
                                oplmsu_uinst->tiocswinsz_p = nmp;
                                break;

                        case TIOCSSOFTCAR :
                                if (oplmsu_uinst->tiocssoftcar_p != NULL) {
                                        freemsg(oplmsu_uinst->tiocssoftcar_p);
                                }
                                oplmsu_uinst->tiocssoftcar_p = nmp;
                                break;
                        }

                        mutex_enter(&oplmsu_uinst->u_lock);
                        mutex_enter(&oplmsu_uinst->l_lock);
                        upath->traditional_status = lpath->status;
                        nmp = lpath->hndl_mp;
                        nmp->b_datap->db_type = M_IOCACK;
                        dst_queue = RD(lpath->hndl_uqueue);
                        bcopy(mp->b_rptr, nmp->b_rptr, sizeof (struct iocblk));

                        oplmsu_clear_ioctl_path(lpath);
                        lpath->src_upath = NULL;
                        lpath->status = MSU_EXT_NOTUSED;
                        mutex_exit(&oplmsu_uinst->l_lock);
                        mutex_exit(&oplmsu_uinst->u_lock);
                        freemsg(mp);
                        (void) putq(dst_queue, nmp);

                        /* Check sleep flag and wake up thread */
                        oplmsu_cmn_wakeup(dst_queue);
                        rw_exit(&oplmsu_uinst->lock);
                        return (SUCCESS);
                } else if ((ack_flag == NAK_RES) &&
                    (lpath->hndl_uqueue != NULL)) { /* M_IOCNAK received */
                        upath->traditional_status = lpath->status;

                        nmp = lpath->hndl_mp;
                        nmp->b_datap->db_type = M_IOCNAK;
                        dst_queue = RD(lpath->hndl_uqueue);

                        oplmsu_clear_ioctl_path(lpath);
                        lpath->src_upath = NULL;
                        lpath->status = MSU_EXT_NOTUSED;
                        mutex_exit(&oplmsu_uinst->l_lock);
                        mutex_exit(&oplmsu_uinst->u_lock);
                        freemsg(mp);
                        (void) putq(dst_queue, nmp);

                        /* Check sleep flag and wake up thread */
                        oplmsu_cmn_wakeup(dst_queue);
                        rw_exit(&oplmsu_uinst->lock);
                        return (SUCCESS);
                }
        }

        mutex_enter(&oplmsu_uinst->u_lock);
        switch (upath->status) {
        case MSU_PSTAT_FAIL :
                upath->traditional_status = MSU_FAIL;
                break;

        case MSU_PSTAT_STOP :
                upath->traditional_status = MSU_STOP;
                break;

        case MSU_PSTAT_STANDBY :
                upath->traditional_status = MSU_STANDBY;
                break;

        case MSU_PSTAT_ACTIVE :
                upath->traditional_status = MSU_ACTIVE;
                break;
        }

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

/* M_ERROR or M_HANGUP response received */
int
oplmsu_lrmsg_error(queue_t *lrq, mblk_t *mp)
{
        upath_t *upath, *altn_upath = NULL;
        lpath_t *lpath, *altn_lpath = NULL;
        mblk_t  *nmp = NULL, *fmp = NULL;
        queue_t *dst_queue = NULL;
        ctrl_t  *ctrl;
        int     term_stat, term_ioctl;

        rw_enter(&oplmsu_uinst->lock, RW_READER);
        mutex_enter(&oplmsu_uinst->c_lock);
        ctrl = oplmsu_uinst->user_ctrl;
        if (ctrl != NULL) {
                dst_queue = RD(ctrl->queue);
        }
        mutex_exit(&oplmsu_uinst->c_lock);

        mutex_enter(&oplmsu_uinst->u_lock);
        mutex_enter(&oplmsu_uinst->l_lock);
        lpath = (lpath_t *)lrq->q_ptr;
        upath = oplmsu_search_upath_info(lpath->path_no);

        if (upath == NULL) {
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
                return (SUCCESS);
        }

        if ((lpath->status == MSU_LINK_NU) ||
            (lpath->status == MSU_SETID_NU) ||
            (upath->traditional_status == MSU_WSTR_ACK) ||
            (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) ||
            (upath->traditional_status == MSU_WSTP_ACK) ||
            (upath->traditional_status == MSU_WPTH_CHG)) {
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
        } else if ((upath->traditional_status == MSU_MAKE_INST) ||
            (upath->traditional_status == MSU_STOP) ||
            (upath->traditional_status == MSU_STANDBY) ||
            (upath->traditional_status == MSU_SETID) ||
            (upath->traditional_status == MSU_LINK)) {
                oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_FAIL, upath->status,
                    MSU_FAIL);
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
        } else if (upath->traditional_status == MSU_FAIL) {
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
        } else if (upath->traditional_status == MSU_ACTIVE) {
                altn_upath = oplmsu_search_standby();
                if (altn_upath == NULL) {
                        oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_FAIL,
                            upath->status, MSU_FAIL);

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

                        OPLMSU_RWLOCK_UPGRADE();
                        oplmsu_uinst->lower_queue = NULL;
                        rw_exit(&oplmsu_uinst->lock);
                        freemsg(mp);
                        return (SUCCESS);
                }

                mutex_exit(&oplmsu_uinst->l_lock);
                if (oplmsu_cmn_allocmb(lrq, mp, &fmp, sizeof (char),
                    MSU_READ_SIDE) == FAILURE) {
                        mutex_exit(&oplmsu_uinst->u_lock);
                        rw_exit(&oplmsu_uinst->lock);
                        return (FAILURE);
                }

                if (oplmsu_cmn_prechg(lrq, mp, MSU_READ_SIDE, &nmp, &term_ioctl,
                    &term_stat) == FAILURE) {
                        mutex_exit(&oplmsu_uinst->u_lock);
                        rw_exit(&oplmsu_uinst->lock);
                        freeb(fmp);
                        return (FAILURE);
                }

                oplmsu_cmn_set_upath_sts(upath, MSU_PSTAT_FAIL,
                    upath->status, MSU_FAIL);

                mutex_enter(&oplmsu_uinst->l_lock);
                lpath->uinst = NULL;

                altn_upath->traditional_status = term_stat;
                altn_lpath = altn_upath->lpath;

                altn_lpath->hndl_mp = NULL;
                altn_lpath->hndl_uqueue = NULL;
                altn_lpath->src_upath = upath;
                altn_lpath->status = MSU_EXT_VOID;
                dst_queue = RD(altn_lpath->lower_queue);
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);

                OPLMSU_RWLOCK_UPGRADE();
                oplmsu_uinst->lower_queue = NULL;
                oplmsu_cmn_set_mflush(fmp);

                if (ctrl != NULL) {
                        mutex_enter(&oplmsu_uinst->c_lock);
                        noenable(WR(ctrl->queue));
                        mutex_exit(&oplmsu_uinst->c_lock);

                        oplmsu_queue_flag = 1;
                }

                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);

                OPLMSU_TRACE(dst_queue, fmp, MSU_TRC_LO);
                qreply(dst_queue, fmp);
                OPLMSU_TRACE(dst_queue, nmp, MSU_TRC_LO);
                qreply(dst_queue, nmp);
        }
        return (SUCCESS);
}

/* M_DATA[xoff/xon] was received from serial port */
int
oplmsu_lrdata_xoffxon(queue_t *lrq, mblk_t *mp)
{
        upath_t *upath, *stp_upath = NULL;
        lpath_t *lpath, *stp_lpath = NULL;
        mblk_t  *nmp = NULL, *fmp = NULL;
        ctrl_t  *ctrl;
        int     term_stat, term_ioctl;

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

        if (oplmsu_uinst->lower_queue != NULL) {
                /* Get lower path of active status */
                stp_lpath = (lpath_t *)oplmsu_uinst->lower_queue->q_ptr;
                if (stp_lpath != NULL) {
                        stp_upath =
                            oplmsu_search_upath_info(stp_lpath->path_no);
                }
        }

        lpath = (lpath_t *)lrq->q_ptr;
        upath = oplmsu_search_upath_info(lpath->path_no);

        if (upath == NULL) {
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
                return (SUCCESS);
        }

        if ((stp_upath != NULL) && (stp_upath != upath)) {
                if ((stp_upath->status != MSU_PSTAT_ACTIVE) ||
                    (stp_upath->traditional_status != MSU_ACTIVE)) {
                        mutex_exit(&oplmsu_uinst->l_lock);
                        mutex_exit(&oplmsu_uinst->u_lock);
                        rw_exit(&oplmsu_uinst->lock);
                        (void) putbq(lrq, mp);
                        return (FAILURE);
                }
        }

        if ((upath->status == MSU_PSTAT_ACTIVE) &&
            ((upath->traditional_status == MSU_ACTIVE) ||
            (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))) {
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                (void) oplmsu_rcmn_through_hndl(lrq, mp, MSU_NORM);
                rw_exit(&oplmsu_uinst->lock);
                return (SUCCESS);
        } else if ((upath->status != MSU_PSTAT_STANDBY) ||
            (upath->traditional_status != MSU_STANDBY)) {
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freemsg(mp);
                cmn_err(CE_WARN, "oplmsu: lr-xoffxon: "
                    "Can't change to specified path");
                return (SUCCESS);
        }
        mutex_exit(&oplmsu_uinst->l_lock);

        if (oplmsu_cmn_allocmb(lrq, mp, &fmp, sizeof (char), MSU_READ_SIDE) ==
            FAILURE) {
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                return (FAILURE);
        }

        if (oplmsu_cmn_prechg(lrq, mp, MSU_READ_SIDE, &nmp, &term_ioctl,
            &term_stat) == FAILURE) {
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);
                freeb(fmp);
                return (FAILURE);
        }

        oplmsu_cmn_set_mflush(fmp);
        upath->traditional_status = term_stat;

        mutex_enter(&oplmsu_uinst->l_lock);
        lpath->hndl_mp = mp;
        lpath->hndl_uqueue = NULL;
        lpath->src_upath = stp_upath;
        lpath->status = MSU_EXT_VOID;

        mutex_enter(&oplmsu_uinst->c_lock);
        ctrl = oplmsu_uinst->user_ctrl;
        if (term_stat != MSU_WPTH_CHG) {
                /*
                 * Send termios to new active path and wait response
                 */
                if (ctrl != NULL) {
                        noenable(WR(ctrl->queue));
                }
                mutex_exit(&oplmsu_uinst->c_lock);
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);
                rw_exit(&oplmsu_uinst->lock);

                OPLMSU_TRACE(RD(lrq), fmp, MSU_TRC_LO);
                qreply(RD(lrq), fmp);
                OPLMSU_TRACE(RD(lrq), nmp, MSU_TRC_LO);
                qreply(RD(lrq), nmp);
        } else {
                /*
                 * No termios messages are received. Change active path.
                 */

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

                lpath->uinst = oplmsu_uinst;
                lpath->src_upath = NULL;
                lpath->status = MSU_EXT_NOTUSED;

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

                (void) putq(WR(lrq), fmp);

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

                if (lpath->hndl_mp != NULL) {
                        /* Put a message(M_DATA) on a queue */
                        if (ctrl != NULL) {
                                (void) putq(RD(ctrl->queue), lpath->hndl_mp);
                        }
                }

                oplmsu_clear_ioctl_path(lpath);

                if (ctrl != NULL) {
                        noenable(WR(ctrl->queue));
                }

                if ((stp_upath != NULL) && (stp_lpath != NULL)) {
                        /* Change the status of stop path */
                        oplmsu_cmn_set_upath_sts(stp_upath, MSU_PSTAT_STANDBY,
                            stp_upath->status, MSU_STANDBY);

                        oplmsu_clear_ioctl_path(stp_lpath);
                        stp_lpath->uinst = NULL;
                        stp_lpath->src_upath = NULL;
                        stp_lpath->status = MSU_EXT_NOTUSED;
                }
#ifdef DEBUG
                oplmsu_cmn_prt_pathname(upath->ser_devcb.dip);
#endif
                /* Send XOFF to notify all standby paths */
                oplmsu_cmn_putxoff_standby();
                mutex_exit(&oplmsu_uinst->c_lock);
                mutex_exit(&oplmsu_uinst->l_lock);
                mutex_exit(&oplmsu_uinst->u_lock);

                OPLMSU_RWLOCK_UPGRADE();
                mutex_enter(&oplmsu_uinst->u_lock);
                oplmsu_uinst->lower_queue = lrq;
                oplmsu_uinst->inst_status = oplmsu_get_inst_status();
                mutex_exit(&oplmsu_uinst->u_lock);

                if (ctrl != NULL) {
                        queue_t *temp_queue;

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

                        /* Reschedule a queue for service */
                        enableok(temp_queue);

                        oplmsu_queue_flag = 0;
                        oplmsu_wcmn_high_qenable(temp_queue, RW_WRITER);
                }
                rw_exit(&oplmsu_uinst->lock);
        }
        return (SUCCESS);
}