root/usr/src/uts/common/io/ptem.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */


/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
 */

/*
 * Description:
 *
 * The PTEM streams module is used as a pseudo driver emulator.  Its purpose
 * is to emulate the ioctl() functions of a terminal device driver.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/termio.h>
#include <sys/pcb.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/strtty.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/jioctl.h>
#include <sys/ptem.h>
#include <sys/ptms.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/conf.h>
#include <sys/modctl.h>

extern struct streamtab pteminfo;

static struct fmodsw fsw = {
        "ptem",
        &pteminfo,
        D_MTQPAIR | D_MP | _D_SINGLE_INSTANCE
};

static struct modlstrmod modlstrmod = {
        &mod_strmodops, "pty hardware emulator", &fsw
};

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

int
_init()
{
        return (mod_install(&modlinkage));
}

int
_fini()
{
        return (mod_remove(&modlinkage));
}

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

/*
 * stream data structure definitions
 */
static int ptemopen(queue_t *, dev_t  *, int, int, cred_t *);
static int ptemclose(queue_t *, int, cred_t *);
static int ptemrput(queue_t *, mblk_t *);
static int ptemwput(queue_t *, mblk_t *);
static int ptemwsrv(queue_t *);

static struct module_info ptem_info = {
        0xabcd,
        "ptem",
        0,
        _TTY_BUFSIZ,
        _TTY_BUFSIZ,
        128
};

static struct qinit ptemrinit = {
        ptemrput,
        NULL,
        ptemopen,
        ptemclose,
        NULL,
        &ptem_info,
        NULL
};

static struct qinit ptemwinit = {
        ptemwput,
        ptemwsrv,
        ptemopen,
        ptemclose,
        nulldev,
        &ptem_info,
        NULL
};

struct streamtab pteminfo = {
        &ptemrinit,
        &ptemwinit,
        NULL,
        NULL
};

static void     ptioc(queue_t *, mblk_t *, int);
static int      ptemwmsg(queue_t *, mblk_t *);

/*
 * ptemopen - open routine gets called when the module gets pushed onto the
 * stream.
 */
/* ARGSUSED */
static int
ptemopen(
        queue_t    *q,          /* pointer to the read side queue */
        dev_t   *devp,          /* pointer to stream tail's dev */
        int     oflag,          /* the user open(2) supplied flags */
        int     sflag,          /* open state flag */
        cred_t *credp)          /* credentials */
{
        struct ptem *ntp;       /* ptem entry for this PTEM module */
        mblk_t *mop;            /* an setopts mblk */
        struct stroptions *sop;
        struct termios *termiosp;
        int len;

        if (sflag != MODOPEN)
                return (EINVAL);

        if (q->q_ptr != NULL) {
                /* It's already attached. */
                return (0);
        }

        /*
         * Allocate state structure.
         */
        ntp = kmem_alloc(sizeof (*ntp), KM_SLEEP);

        /*
         * Allocate a message block, used to pass the zero length message for
         * "stty 0".
         *
         * NOTE: it's better to find out if such a message block can be
         *       allocated before it's needed than to not be able to
         *       deliver (for possible lack of buffers) when a hang-up
         *       occurs.
         */
        if ((ntp->dack_ptr = allocb(4, BPRI_MED)) == NULL) {
                kmem_free(ntp, sizeof (*ntp));
                return (EAGAIN);
        }

        /*
         * Initialize an M_SETOPTS message to set up hi/lo water marks on
         * stream head read queue and add controlling tty if not set.
         */
        mop = allocb(sizeof (struct stroptions), BPRI_MED);
        if (mop == NULL) {
                freemsg(ntp->dack_ptr);
                kmem_free(ntp, sizeof (*ntp));
                return (EAGAIN);
        }
        mop->b_datap->db_type = M_SETOPTS;
        mop->b_wptr += sizeof (struct stroptions);
        sop = (struct stroptions *)mop->b_rptr;
        sop->so_flags = SO_HIWAT | SO_LOWAT | SO_ISTTY;
        sop->so_hiwat = _TTY_BUFSIZ;
        sop->so_lowat = 256;

        /*
         * Cross-link.
         */
        ntp->q_ptr = q;
        q->q_ptr = ntp;
        WR(q)->q_ptr = ntp;

        /*
         * Get termios defaults.  These are stored as
         * a property in the "options" node.
         */
        if (ddi_getlongprop(DDI_DEV_T_ANY, ddi_root_node(), 0, "ttymodes",
            (caddr_t)&termiosp, &len) == DDI_PROP_SUCCESS &&
            len == sizeof (struct termios)) {

                ntp->cflags = termiosp->c_cflag;
                kmem_free(termiosp, len);
        } else {
                /*
                 * Gack!  Whine about it.
                 */
                cmn_err(CE_WARN, "ptem: Couldn't get ttymodes property!");
        }
        ntp->wsz.ws_row = 0;
        ntp->wsz.ws_col = 0;
        ntp->wsz.ws_xpixel = 0;
        ntp->wsz.ws_ypixel = 0;

        ntp->state = 0;

        /*
         * Commit to the open and send the M_SETOPTS off to the stream head.
         */
        qprocson(q);
        putnext(q, mop);

        return (0);
}


/*
 * ptemclose - This routine gets called when the module gets popped off of the
 * stream.
 */
/* ARGSUSED */
static int
ptemclose(queue_t *q, int flag, cred_t *credp)
{
        struct ptem *ntp;       /* ptem entry for this PTEM module */

        qprocsoff(q);
        ntp = (struct ptem *)q->q_ptr;
        freemsg(ntp->dack_ptr);
        kmem_free(ntp, sizeof (*ntp));
        q->q_ptr = WR(q)->q_ptr = NULL;
        return (0);
}


/*
 * ptemrput - Module read queue put procedure.
 *
 * This is called from the module or driver downstream.
 */
static int
ptemrput(queue_t *q, mblk_t *mp)
{
        struct iocblk *iocp;    /* M_IOCTL data */
        struct copyresp *resp;  /* transparent ioctl response struct */
        int error;

        switch (mp->b_datap->db_type) {
        case M_DELAY:
        case M_READ:
                freemsg(mp);
                break;

        case M_IOCTL:
                iocp = (struct iocblk *)mp->b_rptr;

                switch (iocp->ioc_cmd) {
                case TCSBRK:
                        /*
                         * Send a break message upstream.
                         *
                         * XXX: Shouldn't the argument come into play in
                         *      determining whether or not so send an M_BREAK?
                         *      It certainly does in the write-side direction.
                         */
                        error = miocpullup(mp, sizeof (int));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                break;
                        }
                        if (!(*(int *)mp->b_cont->b_rptr)) {
                                if (!putnextctl(q, M_BREAK)) {
                                        /*
                                         * Send an NAK reply back
                                         */
                                        miocnak(q, mp, 0, EAGAIN);
                                        break;
                                }
                        }
                        /*
                         * ACK it.
                         */
                        mioc2ack(mp, NULL, 0, 0);
                        qreply(q, mp);
                        break;

                case JWINSIZE:
                case TIOCGWINSZ:
                case TIOCSWINSZ:
                        ptioc(q, mp, RDSIDE);
                        break;

                case TIOCSIGNAL:
                        /*
                         * The following subtle logic is due to the fact that
                         * `mp' may be in any one of three distinct formats:
                         *
                         *      1. A transparent M_IOCTL with an intptr_t-sized
                         *         payload containing the signal number.
                         *
                         *      2. An I_STR M_IOCTL with an int-sized payload
                         *         containing the signal number.
                         *
                         *      3. An M_IOCDATA with an int-sized payload
                         *         containing the signal number.
                         */
                        if (iocp->ioc_count == TRANSPARENT) {
                                intptr_t sig = *(intptr_t *)mp->b_cont->b_rptr;

                                if (sig < 1 || sig >= NSIG) {
                                        /*
                                         * it's transparent with pointer
                                         * to the arg
                                         */
                                        mcopyin(mp, NULL, sizeof (int), NULL);
                                        qreply(q, mp);
                                        break;
                                }
                        }
                        ptioc(q, mp, RDSIDE);
                        break;

                case TIOCREMOTE:
                        if (iocp->ioc_count != TRANSPARENT)
                                ptioc(q, mp, RDSIDE);
                        else {
                                mcopyin(mp, NULL, sizeof (int), NULL);
                                qreply(q, mp);
                        }
                        break;

                default:
                        putnext(q, mp);
                        break;
                }
                break;

        case M_IOCDATA:
                resp = (struct copyresp *)mp->b_rptr;
                if (resp->cp_rval) {
                        /*
                         * Just free message on failure.
                         */
                        freemsg(mp);
                        break;
                }

                /*
                 * Only need to copy data for the SET case.
                 */
                switch (resp->cp_cmd) {

                case TIOCSWINSZ:
                case TIOCSIGNAL:
                case TIOCREMOTE:
                        ptioc(q, mp, RDSIDE);
                        break;

                case JWINSIZE:
                case TIOCGWINSZ:
                        mp->b_datap->db_type = M_IOCACK;
                        mioc2ack(mp, NULL, 0, 0);
                        qreply(q, mp);
                        break;

                default:
                        freemsg(mp);
                        break;
        }
        break;

        case M_IOCACK:
        case M_IOCNAK:
                /*
                 * We only pass write-side ioctls through to the manager that
                 * we've already ACKed or NAKed to the stream head.  Thus, we
                 * discard ones arriving from below, since they're redundant
                 * from the point of view of modules above us.
                 */
                freemsg(mp);
                break;

        case M_HANGUP:
                /*
                 * clear blocked state.
                 */
                {
                        struct ptem *ntp = (struct ptem *)q->q_ptr;
                        if (ntp->state & OFLOW_CTL) {
                                ntp->state &= ~OFLOW_CTL;
                                qenable(WR(q));
                        }
                }
                /* FALLTHROUGH */
        default:
                putnext(q, mp);
                break;
        }
        return (0);
}


/*
 * ptemwput - Module write queue put procedure.
 *
 * This is called from the module or stream head upstream.
 *
 * XXX: This routine is quite lazy about handling allocation failures,
 *      basically just giving up and reporting failure.  It really ought to
 *      set up bufcalls and only fail when it's absolutely necessary.
 */
static int
ptemwput(queue_t *q, mblk_t *mp)
{
        struct ptem *ntp = (struct ptem *)q->q_ptr;
        struct iocblk *iocp;    /* outgoing ioctl structure */
        struct copyresp *resp;
        unsigned char type = mp->b_datap->db_type;

        if (type >= QPCTL) {
                switch (type) {

                case M_IOCDATA:
                        resp = (struct copyresp *)mp->b_rptr;
                        if (resp->cp_rval) {
                                /*
                                 * Just free message on failure.
                                 */
                                freemsg(mp);
                                break;
                        }

                        /*
                         * Only need to copy data for the SET case.
                         */
                        switch (resp->cp_cmd) {

                                case TIOCSWINSZ:
                                        ptioc(q, mp, WRSIDE);
                                        break;

                                case JWINSIZE:
                                case TIOCGWINSZ:
                                        mioc2ack(mp, NULL, 0, 0);
                                        qreply(q, mp);
                                        break;

                                default:
                                        freemsg(mp);
                        }
                        break;

                case M_FLUSH:
                        if (*mp->b_rptr & FLUSHW) {
                                if ((ntp->state & IS_PTSTTY) &&
                                    (*mp->b_rptr & FLUSHBAND))
                                        flushband(q, *(mp->b_rptr + 1),
                                            FLUSHDATA);
                                else
                                        flushq(q, FLUSHDATA);
                        }
                        putnext(q, mp);
                        break;

                case M_READ:
                        freemsg(mp);
                        break;

                case M_STOP:
                        /*
                         * Set the output flow control state.
                         */
                        ntp->state |= OFLOW_CTL;
                        putnext(q, mp);
                        break;

                case M_START:
                        /*
                         * Relieve the output flow control state.
                         */
                        ntp->state &= ~OFLOW_CTL;
                        putnext(q, mp);
                        qenable(q);
                        break;
                default:
                        putnext(q, mp);
                        break;
                }
                return (0);
        }
        /*
         * If our queue is nonempty or flow control persists
         * downstream or module in stopped state, queue this message.
         */
        if (q->q_first != NULL || !bcanputnext(q, mp->b_band)) {
                /*
                 * Exception: ioctls, except for those defined to
                 * take effect after output has drained, should be
                 * processed immediately.
                 */
                switch (type) {

                case M_IOCTL:
                        iocp = (struct iocblk *)mp->b_rptr;
                        switch (iocp->ioc_cmd) {
                        /*
                         * Queue these.
                         */
                        case TCSETSW:
                        case TCSETSF:
                        case TCSETAW:
                        case TCSETAF:
                        case TCSBRK:
                                break;

                        /*
                         * Handle all others immediately.
                         */
                        default:
                                (void) ptemwmsg(q, mp);
                                return (0);
                        }
                        break;

                case M_DELAY: /* tty delays not supported */
                        freemsg(mp);
                        return (0);

                case M_DATA:
                        if ((mp->b_wptr - mp->b_rptr) < 0) {
                                /*
                                 * Free all bad length messages.
                                 */
                                freemsg(mp);
                                return (0);
                        } else if ((mp->b_wptr - mp->b_rptr) == 0) {
                                if (!(ntp->state & IS_PTSTTY)) {
                                        freemsg(mp);
                                        return (0);
                                }
                        }
                }
                (void) putq(q, mp);
                return (0);
        }
        /*
         * fast path into ptemwmsg to dispose of mp.
         */
        if (!ptemwmsg(q, mp))
                (void) putq(q, mp);
        return (0);
}

/*
 * ptem write queue service procedure.
 */
static int
ptemwsrv(queue_t *q)
{
        mblk_t *mp;

        while ((mp = getq(q)) != NULL) {
                if (!bcanputnext(q, mp->b_band) || !ptemwmsg(q, mp)) {
                        (void) putbq(q, mp);
                        break;
                }
        }
        return (0);
}


/*
 * This routine is called from both ptemwput and ptemwsrv to do the
 * actual work of dealing with mp.  ptmewput will have already
 * dealt with high priority messages.
 *
 * Return 1 if the message was processed completely and 0 if not.
 */
static int
ptemwmsg(queue_t *q, mblk_t *mp)
{
        struct ptem *ntp = (struct ptem *)q->q_ptr;
        struct iocblk *iocp;    /* outgoing ioctl structure */
        struct termio *termiop;
        struct termios *termiosp;
        mblk_t *dack_ptr;               /* disconnect message ACK block */
        mblk_t *pckt_msgp;              /* message sent to the PCKT module */
        mblk_t *dp;                     /* ioctl reply data */
        tcflag_t cflags;
        int error;

        switch (mp->b_datap->db_type) {

        case M_IOCTL:
                /*
                 * Note:  for each "set" type operation a copy
                 * of the M_IOCTL message is made and passed
                 * downstream.  Eventually the PCKT module, if
                 * it has been pushed, should pick up this message.
                 * If the PCKT module has not been pushed the manager
                 * side stream head will free it.
                 */
                iocp = (struct iocblk *)mp->b_rptr;
                switch (iocp->ioc_cmd) {

                case TCSETAF:
                case TCSETSF:
                        /*
                         * Flush the read queue.
                         */
                        if (putnextctl1(q, M_FLUSH, FLUSHR) == 0) {
                                miocnak(q, mp, 0, EAGAIN);
                                break;
                        }
                        /* FALLTHROUGH */

                case TCSETA:
                case TCSETAW:
                case TCSETS:
                case TCSETSW:

                        switch (iocp->ioc_cmd) {
                        case TCSETAF:
                        case TCSETA:
                        case TCSETAW:
                                error = miocpullup(mp, sizeof (struct termio));
                                if (error != 0) {
                                        miocnak(q, mp, 0, error);
                                        goto out;
                                }
                                cflags = ((struct termio *)
                                    mp->b_cont->b_rptr)->c_cflag;
                                ntp->cflags =
                                    (ntp->cflags & 0xffff0000 | cflags);
                                break;

                        case TCSETSF:
                        case TCSETS:
                        case TCSETSW:
                                error = miocpullup(mp, sizeof (struct termios));
                                if (error != 0) {
                                        miocnak(q, mp, 0, error);
                                        goto out;
                                }
                                cflags = ((struct termios *)
                                    mp->b_cont->b_rptr)->c_cflag;
                                ntp->cflags = cflags;
                                break;
                        }

                        if ((cflags & CBAUD) == B0) {
                                /*
                                 * Hang-up: Send a zero length message.
                                 */
                                dack_ptr = ntp->dack_ptr;

                                if (dack_ptr) {
                                        ntp->dack_ptr = NULL;
                                        /*
                                         * Send a zero length message
                                         * downstream.
                                         */
                                        putnext(q, dack_ptr);
                                }
                        } else {
                                /*
                                 * Make a copy of this message and pass it on
                                 * to the PCKT module.
                                 */
                                if ((pckt_msgp = copymsg(mp)) == NULL) {
                                        miocnak(q, mp, 0, EAGAIN);
                                        break;
                                }
                                putnext(q, pckt_msgp);
                        }
                        /*
                         * Send ACK upstream.
                         */
                        mioc2ack(mp, NULL, 0, 0);
                        qreply(q, mp);
out:
                        break;

                case TCGETA:
                        dp = allocb(sizeof (struct termio), BPRI_MED);
                        if (dp == NULL) {
                                miocnak(q, mp, 0, EAGAIN);
                                break;
                        }
                        termiop = (struct termio *)dp->b_rptr;
                        termiop->c_cflag = (ushort_t)ntp->cflags;
                        mioc2ack(mp, dp, sizeof (struct termio), 0);
                        qreply(q, mp);
                        break;

                case TCGETS:
                        dp = allocb(sizeof (struct termios), BPRI_MED);
                        if (dp == NULL) {
                                miocnak(q, mp, 0, EAGAIN);
                                break;
                        }
                        termiosp = (struct termios *)dp->b_rptr;
                        termiosp->c_cflag = ntp->cflags;
                        mioc2ack(mp, dp, sizeof (struct termios), 0);
                        qreply(q, mp);
                        break;

                case TCSBRK:
                        error = miocpullup(mp, sizeof (int));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                break;
                        }

                        /*
                         * Need a copy of this message to pass it on to
                         * the PCKT module.
                         */
                        if ((pckt_msgp = copymsg(mp)) == NULL) {
                                miocnak(q, mp, 0, EAGAIN);
                                break;
                        }
                        /*
                         * Send a copy of the M_IOCTL to the PCKT module.
                         */
                        putnext(q, pckt_msgp);

                        /*
                         * TCSBRK meaningful if data part of message is 0
                         * cf. termio(4I).
                         */
                        if (!(*(int *)mp->b_cont->b_rptr))
                                (void) putnextctl(q, M_BREAK);
                        /*
                         * ACK the ioctl.
                         */
                        mioc2ack(mp, NULL, 0, 0);
                        qreply(q, mp);
                        break;

                case JWINSIZE:
                case TIOCGWINSZ:
                case TIOCSWINSZ:
                        ptioc(q, mp, WRSIDE);
                        break;

                case TIOCSTI:
                        /*
                         * Simulate typing of a character at the terminal.  In
                         * all cases, we acknowledge the ioctl and pass a copy
                         * of it along for the PCKT module to encapsulate.  If
                         * not in remote mode, we also process the ioctl
                         * itself, looping the character given as its argument
                         * back around to the read side.
                         */

                        /*
                         * Need a copy of this message to pass on to the PCKT
                         * module.
                         */
                        if ((pckt_msgp = copymsg(mp)) == NULL) {
                                miocnak(q, mp, 0, EAGAIN);
                                break;
                        }
                        if ((ntp->state & REMOTEMODE) == 0) {
                                mblk_t *bp;

                                error = miocpullup(mp, sizeof (char));
                                if (error != 0) {
                                        freemsg(pckt_msgp);
                                        miocnak(q, mp, 0, error);
                                        break;
                                }

                                /*
                                 * The permission checking has already been
                                 * done at the stream head, since it has to be
                                 * done in the context of the process doing
                                 * the call.
                                 */
                                if ((bp = allocb(1, BPRI_MED)) == NULL) {
                                        freemsg(pckt_msgp);
                                        miocnak(q, mp, 0, EAGAIN);
                                        break;
                                }
                                /*
                                 * XXX: Is EAGAIN really the right response to
                                 *      flow control blockage?
                                 */
                                if (!bcanputnext(RD(q), mp->b_band)) {
                                        freemsg(bp);
                                        freemsg(pckt_msgp);
                                        miocnak(q, mp, 0, EAGAIN);
                                        break;
                                }
                                *bp->b_wptr++ = *mp->b_cont->b_rptr;
                                qreply(q, bp);
                        }

                        putnext(q, pckt_msgp);
                        mioc2ack(mp, NULL, 0, 0);
                        qreply(q, mp);
                        break;

                case PTSSTTY:
                        if (ntp->state & IS_PTSTTY) {
                                miocnak(q, mp, 0, EEXIST);
                        } else {
                                ntp->state |= IS_PTSTTY;
                                mioc2ack(mp, NULL, 0, 0);
                                qreply(q, mp);
                        }
                        break;

                default:
                        /*
                         * End of the line.  The subsidiary driver doesn't see
                         * any ioctls that we don't explicitly pass along to
                         * it.
                         */
                        miocnak(q, mp, 0, EINVAL);
                        break;
                }
                break;

        case M_DELAY: /* tty delays not supported */
                freemsg(mp);
                break;

        case M_DATA:
                if ((mp->b_wptr - mp->b_rptr) < 0) {
                        /*
                         * Free all bad length messages.
                         */
                        freemsg(mp);
                        break;
                } else if ((mp->b_wptr - mp->b_rptr) == 0) {
                        if (!(ntp->state & IS_PTSTTY)) {
                                freemsg(mp);
                                break;
                        }
                }
                if (ntp->state & OFLOW_CTL)
                        return (0);
                /* FALLTHROUGH */

        default:
                putnext(q, mp);
                break;

        }

        return (1);
}

/*
 * Message must be of type M_IOCTL or M_IOCDATA for this routine to be called.
 */
static void
ptioc(queue_t *q, mblk_t *mp, int qside)
{
        struct ptem *tp;
        struct iocblk *iocp;
        struct winsize *wb;
        struct jwinsize *jwb;
        mblk_t *tmp;
        mblk_t *pckt_msgp;      /* message sent to the PCKT module */
        int error;

        iocp = (struct iocblk *)mp->b_rptr;
        tp = (struct ptem *)q->q_ptr;

        switch (iocp->ioc_cmd) {

        case JWINSIZE:
                /*
                 * For compatibility:  If all zeros, NAK the message for dumb
                 * terminals.
                 */
                if ((tp->wsz.ws_row == 0) && (tp->wsz.ws_col == 0) &&
                    (tp->wsz.ws_xpixel == 0) && (tp->wsz.ws_ypixel == 0)) {
                        miocnak(q, mp, 0, EINVAL);
                        return;
                }

                tmp = allocb(sizeof (struct jwinsize), BPRI_MED);
                if (tmp == NULL) {
                        miocnak(q, mp, 0, EAGAIN);
                        return;
                }

                if (iocp->ioc_count == TRANSPARENT)
                        mcopyout(mp, NULL, sizeof (struct jwinsize), NULL, tmp);
                else
                        mioc2ack(mp, tmp, sizeof (struct jwinsize), 0);

                jwb = (struct jwinsize *)mp->b_cont->b_rptr;
                jwb->bytesx = tp->wsz.ws_col;
                jwb->bytesy = tp->wsz.ws_row;
                jwb->bitsx = tp->wsz.ws_xpixel;
                jwb->bitsy = tp->wsz.ws_ypixel;

                qreply(q, mp);
                return;

        case TIOCGWINSZ:
                tmp = allocb(sizeof (struct winsize), BPRI_MED);
                if (tmp == NULL) {
                        miocnak(q, mp, 0, EAGAIN);
                        return;
                }

                mioc2ack(mp, tmp, sizeof (struct winsize), 0);

                wb = (struct winsize *)mp->b_cont->b_rptr;
                wb->ws_row = tp->wsz.ws_row;
                wb->ws_col = tp->wsz.ws_col;
                wb->ws_xpixel = tp->wsz.ws_xpixel;
                wb->ws_ypixel = tp->wsz.ws_ypixel;

                qreply(q, mp);
                return;

        case TIOCSWINSZ:
                error = miocpullup(mp, sizeof (struct winsize));
                if (error != 0) {
                        miocnak(q, mp, 0, error);
                        return;
                }

                wb = (struct winsize *)mp->b_cont->b_rptr;
                /*
                 * Send a SIGWINCH signal if the row/col information has
                 * changed.
                 */
                if ((tp->wsz.ws_row != wb->ws_row) ||
                    (tp->wsz.ws_col != wb->ws_col) ||
                    (tp->wsz.ws_xpixel != wb->ws_xpixel) ||
                    (tp->wsz.ws_ypixel != wb->ws_xpixel)) {
                        /*
                         * SIGWINCH is always sent upstream.
                         */
                        if (qside == WRSIDE)
                                (void) putnextctl1(RD(q), M_SIG, SIGWINCH);
                        else if (qside == RDSIDE)
                                (void) putnextctl1(q, M_SIG, SIGWINCH);
                        /*
                         * Message may have come in as an M_IOCDATA; pass it
                         * to the manager side as an M_IOCTL.
                         */
                        mp->b_datap->db_type = M_IOCTL;
                        if (qside == WRSIDE) {
                                /*
                                 * Need a copy of this message to pass on to
                                 * the PCKT module, only if the M_IOCTL
                                 * orginated from the subsidiary side.
                                 */
                                if ((pckt_msgp = copymsg(mp)) == NULL) {
                                        miocnak(q, mp, 0, EAGAIN);
                                        return;
                                }
                                putnext(q, pckt_msgp);
                        }
                        tp->wsz.ws_row = wb->ws_row;
                        tp->wsz.ws_col = wb->ws_col;
                        tp->wsz.ws_xpixel = wb->ws_xpixel;
                        tp->wsz.ws_ypixel = wb->ws_ypixel;
                }

                mioc2ack(mp, NULL, 0, 0);
                qreply(q, mp);
                return;

        case TIOCSIGNAL: {
                /*
                 * This ioctl can emanate from the manager side in remote
                 * mode only.
                 */
                int     sig;

                if (DB_TYPE(mp) == M_IOCTL && iocp->ioc_count != TRANSPARENT) {
                        error = miocpullup(mp, sizeof (int));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                return;
                        }
                }

                if (DB_TYPE(mp) == M_IOCDATA || iocp->ioc_count != TRANSPARENT)
                        sig = *(int *)mp->b_cont->b_rptr;
                else
                        sig = (int)*(intptr_t *)mp->b_cont->b_rptr;

                if (sig < 1 || sig >= NSIG) {
                        miocnak(q, mp, 0, EINVAL);
                        return;
                }

                /*
                 * Send an M_PCSIG message up the subsidiary's read side and
                 * respond back to the manager with an ACK or NAK as
                 * appropriate.
                 */
                if (putnextctl1(q, M_PCSIG, sig) == 0) {
                        miocnak(q, mp, 0, EAGAIN);
                        return;
                }

                mioc2ack(mp, NULL, 0, 0);
                qreply(q, mp);
                return;
        }

        case TIOCREMOTE: {
                int     onoff;
                mblk_t  *mctlp;

                if (DB_TYPE(mp) == M_IOCTL) {
                        error = miocpullup(mp, sizeof (int));
                        if (error != 0) {
                                miocnak(q, mp, 0, error);
                                return;
                        }
                }

                onoff = *(int *)mp->b_cont->b_rptr;

                /*
                 * Send M_CTL up using the iocblk format.
                 */
                mctlp = mkiocb(onoff ? MC_NO_CANON : MC_DO_CANON);
                if (mctlp == NULL) {
                        miocnak(q, mp, 0, EAGAIN);
                        return;
                }
                mctlp->b_datap->db_type = M_CTL;
                putnext(q, mctlp);

                /*
                 * ACK the ioctl.
                 */
                mioc2ack(mp, NULL, 0, 0);
                qreply(q, mp);

                /*
                 * Record state change.
                 */
                if (onoff)
                        tp->state |= REMOTEMODE;
                else
                        tp->state &= ~REMOTEMODE;
                return;
        }

        default:
                putnext(q, mp);
                return;
        }
}