/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
/*
* Module to intercept old V7 and 4BSD "ioctl" calls.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <sys/termios.h>
#include <sys/ttold.h>
#include <sys/cmn_err.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/ttcompat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/policy.h>
/*
* This is the loadable module wrapper.
*/
#include <sys/conf.h>
#include <sys/modctl.h>
/* See os/streamio.c */
extern int sgttyb_handling;
static struct streamtab ttcoinfo;
static struct fmodsw fsw = {
"ttcompat",
&ttcoinfo,
D_MTQPAIR | D_MP
};
/*
* Module linkage information for the kernel.
*/
static struct modlstrmod modlstrmod = {
&mod_strmodops,
"alt ioctl calls",
&fsw
};
static struct modlinkage modlinkage = {
MODREV_1, &modlstrmod, NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int ttcompatopen(queue_t *, dev_t *, int, int, cred_t *);
static int ttcompatclose(queue_t *, int, cred_t *);
static void ttcompatrput(queue_t *, mblk_t *);
static void ttcompatwput(queue_t *, mblk_t *);
static struct module_info ttycompatmiinfo = {
0,
"ttcompat",
0,
INFPSZ,
2048,
128
};
static struct qinit ttycompatrinit = {
(int (*)())ttcompatrput,
NULL,
ttcompatopen,
ttcompatclose,
NULL,
&ttycompatmiinfo
};
static struct module_info ttycompatmoinfo = {
42,
"ttcompat",
0,
INFPSZ,
300,
200
};
static struct qinit ttycompatwinit = {
(int (*)())ttcompatwput,
NULL,
ttcompatopen,
ttcompatclose,
NULL,
&ttycompatmoinfo
};
static struct streamtab ttcoinfo = {
&ttycompatrinit,
&ttycompatwinit,
NULL,
NULL
};
/*
* This is the termios structure that is used to reset terminal settings
* when the underlying device is an instance of zcons. It came from
* cmd/init/init.c and should be kept in-sync with dflt_termios found therein.
*/
static const struct termios base_termios = {
BRKINT|ICRNL|IXON|IMAXBEL, /* iflag */
OPOST|ONLCR|TAB3, /* oflag */
CS8|CREAD|B9600, /* cflag */
ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN, /* lflag */
CINTR, CQUIT, CERASE, CKILL, CEOF, 0, 0, 0, 0, 0, 0, 0, /* c_cc vals */
0, 0, 0, 0, 0, 0, 0
};
static void ttcompat_do_ioctl(ttcompat_state_t *, queue_t *, mblk_t *);
static void ttcompat_ioctl_ack(queue_t *, mblk_t *);
static void ttcopyout(queue_t *, mblk_t *);
static void ttcompat_ioctl_nak(queue_t *, mblk_t *);
static void from_compat(compat_state_t *, struct termios *);
static void to_compat(struct termios *, compat_state_t *);
/*
* Open - get the current modes and translate them to the V7/4BSD equivalent.
*/
/*ARGSUSED*/
static int
ttcompatopen(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *crp)
{
ttcompat_state_t *tp;
mblk_t *mp;
mblk_t *datamp;
struct iocblk *iocb;
int error;
if (q->q_ptr != NULL) {
tp = (ttcompat_state_t *)q->q_ptr;
/* fail open if TIOCEXCL was done and its not privileged */
if ((tp->t_new_lflags & XCLUDE) &&
secpolicy_excl_open(crp) != 0) {
return (EBUSY);
}
return (0); /* already attached */
}
tp = kmem_zalloc(sizeof (ttcompat_state_t), KM_SLEEP);
tp->t_iocpending = NULL;
tp->t_state = 0;
tp->t_iocid = 0;
tp->t_ioccmd = 0;
tp->t_new_lflags = 0;
tp->t_curstate.t_flags = 0;
tp->t_curstate.t_ispeed = B0;
tp->t_curstate.t_ospeed = B0;
tp->t_curstate.t_erase = '\0';
tp->t_curstate.t_kill = '\0';
tp->t_curstate.t_intrc = '\0';
tp->t_curstate.t_quitc = '\0';
tp->t_curstate.t_startc = '\0';
tp->t_curstate.t_stopc = '\0';
tp->t_curstate.t_eofc = '\0';
tp->t_curstate.t_brkc = '\0';
tp->t_curstate.t_suspc = '\0';
tp->t_curstate.t_dsuspc = '\0';
tp->t_curstate.t_rprntc = '\0';
tp->t_curstate.t_flushc = '\0';
tp->t_curstate.t_werasc = '\0';
tp->t_curstate.t_lnextc = '\0';
tp->t_curstate.t_xflags = 0;
tp->t_bufcallid = 0;
tp->t_arg = 0;
q->q_ptr = tp;
WR(q)->q_ptr = tp;
qprocson(q);
/*
* Determine if the underlying device is a zcons instance. If so,
* then issue a termios ioctl to reset the terminal settings.
*/
if (getmajor(q->q_stream->sd_vnode->v_rdev) !=
ddi_name_to_major("zcons"))
return (0);
/*
* Create the ioctl message.
*/
if ((mp = mkiocb(TCSETSF)) == NULL) {
error = ENOMEM;
goto common_error;
}
if ((datamp = allocb(sizeof (struct termios), BPRI_HI)) == NULL) {
freemsg(mp);
error = ENOMEM;
goto common_error;
}
iocb = (struct iocblk *)mp->b_rptr;
iocb->ioc_count = sizeof (struct termios);
bcopy(&base_termios, datamp->b_rptr, sizeof (struct termios));
datamp->b_wptr += sizeof (struct termios);
mp->b_cont = datamp;
/*
* Send the ioctl message on its merry way toward the driver.
* Set some state beforehand so we can properly wait for
* an acknowledgement.
*/
tp->t_state |= TS_IOCWAIT | TS_TIOCNAK;
tp->t_iocid = iocb->ioc_id;
tp->t_ioccmd = TCSETSF;
putnext(WR(q), mp);
/*
* Wait for an acknowledgement. A NAK is treated as an error.
* The presence of the TS_TIOCNAK flag indicates that a NAK was
* received.
*/
while (tp->t_state & TS_IOCWAIT) {
if (qwait_sig(q) == 0) {
error = EINTR;
goto common_error;
}
}
if (!(tp->t_state & TS_TIOCNAK))
return (0);
error = ENOTTY;
common_error:
qprocsoff(q);
kmem_free(tp, sizeof (ttcompat_state_t));
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
return (error);
}
/* ARGSUSED1 */
static int
ttcompatclose(queue_t *q, int flag, cred_t *crp)
{
ttcompat_state_t *tp = (ttcompat_state_t *)q->q_ptr;
mblk_t *mp;
/* Dump the state structure, then unlink it */
qprocsoff(q);
if (tp->t_bufcallid != 0) {
qunbufcall(q, tp->t_bufcallid);
tp->t_bufcallid = 0;
}
if ((mp = tp->t_iocpending) != NULL)
freemsg(mp);
kmem_free(tp, sizeof (ttcompat_state_t));
q->q_ptr = NULL;
return (0);
}
/*
* Put procedure for input from driver end of stream (read queue).
* Most messages just get passed to the next guy up; we intercept
* "ioctl" replies, and if it's an "ioctl" whose reply we plan to do
* something with, we do it.
*/
static void
ttcompatrput(queue_t *q, mblk_t *mp)
{
switch (mp->b_datap->db_type) {
case M_IOCACK:
ttcompat_ioctl_ack(q, mp);
break;
case M_IOCNAK:
ttcompat_ioctl_nak(q, mp);
break;
default:
putnext(q, mp);
break;
}
}
/*
* Line discipline output queue put procedure: speeds M_IOCTL
* messages.
*/
static void
ttcompatwput(queue_t *q, mblk_t *mp)
{
ttcompat_state_t *tp;
struct copyreq *cqp;
struct copyresp *csp;
struct iocblk *iocbp;
tp = (ttcompat_state_t *)q->q_ptr;
/*
* Process some M_IOCTL messages here; pass everything else down.
*/
switch (mp->b_datap->db_type) {
default:
putnext(q, mp);
return;
case M_IOCTL:
iocbp = (struct iocblk *)mp->b_rptr;
switch (iocbp->ioc_cmd) {
default:
/* these are ioctls with no arguments or are known to stream head */
/* process them right away */
ttcompat_do_ioctl(tp, q, mp);
return;
case TIOCSETN:
case TIOCSLTC:
case TIOCSETC:
case TIOCLBIS:
case TIOCLBIC:
case TIOCLSET:
case TIOCFLUSH:
if (iocbp->ioc_count != TRANSPARENT) {
putnext(q, mp);
return;
}
mp->b_datap->db_type = M_COPYIN;
cqp = (struct copyreq *)mp->b_rptr;
cqp->cq_addr = (caddr_t)*(intptr_t *)mp->b_cont->b_rptr;
switch (iocbp->ioc_cmd) {
case TIOCSETN:
cqp->cq_size = sizeof (struct sgttyb);
break;
case TIOCSLTC:
cqp->cq_size = sizeof (struct ltchars);
break;
case TIOCSETC:
cqp->cq_size = sizeof (struct tchars);
break;
case TIOCLBIS:
case TIOCLBIC:
case TIOCLSET:
case TIOCFLUSH:
cqp->cq_size = sizeof (int);
break;
default:
break;
}
cqp->cq_flag = 0;
cqp->cq_private = NULL;
freemsg(mp->b_cont);
mp->b_cont = NULL;
mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);
tp->t_ioccmd = iocbp->ioc_cmd;
tp->t_state |= TS_W_IN;
qreply(q, mp);
return;
} /* switch ioc_cmd */
case M_IOCDATA:
csp = (struct copyresp *)mp->b_rptr;
switch (csp->cp_cmd) {
default:
putnext(q, mp);
return;
case TIOCSETN:
case TIOCSLTC:
case TIOCSETC:
case TIOCLBIS:
case TIOCLBIC:
case TIOCLSET:
case TIOCFLUSH:
tp->t_state &= ~TS_W_IN;
if (csp->cp_rval != 0) { /* failure */
freemsg(mp);
return;
}
/* make it look like an ioctl */
mp->b_datap->db_type = M_IOCTL;
mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
iocbp = (struct iocblk *)mp->b_rptr;
iocbp->ioc_count = MBLKL(mp->b_cont);
iocbp->ioc_error = 0;
iocbp->ioc_rval = 0;
ttcompat_do_ioctl(tp, q, mp);
return;
case TIOCGLTC:
case TIOCLGET:
case TIOCGETC:
tp->t_state &= ~TS_W_OUT;
if (csp->cp_rval != 0) { /* failure */
freemsg(mp);
return;
}
iocbp = (struct iocblk *)mp->b_rptr;
iocbp->ioc_count = 0;
iocbp->ioc_error = 0;
iocbp->ioc_rval = 0;
mp->b_datap->db_type = M_IOCACK;
qreply(q, mp);
return;
} /* switch cp_cmd */
} /* end message switch */
}
/*
* Retry an "ioctl", now that "bufcall" claims we may be able to allocate
* the buffer we need.
*/
static void
ttcompat_reioctl(void *arg)
{
queue_t *q = arg;
ttcompat_state_t *tp;
mblk_t *mp;
tp = (ttcompat_state_t *)q->q_ptr;
tp->t_bufcallid = 0;
if ((mp = tp->t_iocpending) != NULL) {
tp->t_iocpending = NULL; /* not pending any more */
ttcompat_do_ioctl(tp, q, mp);
}
}
/*
* Handle old-style "ioctl" messages; pass the rest down unmolested.
*/
static void
ttcompat_do_ioctl(ttcompat_state_t *tp, queue_t *q, mblk_t *mp)
{
struct iocblk *iocp;
int error;
/*
* Most of the miocpullup()'s below aren't needed because the
* ioctls in question are actually transparent M_IOCDATA messages
* dummied to look like M_IOCTL messages. However, for clarity and
* robustness against future changes, we've included them anyway.
*/
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
/*
* "get"-style calls that get translated data from the "termios"
* structure. Save the existing code and pass it down as a TCGETS.
*/
case TIOCGETC:
case TIOCLGET:
case TIOCGLTC:
if (iocp->ioc_count != TRANSPARENT) {
miocnak(q, mp, 0, EINVAL);
return;
}
/*
* We can get here with t_arg != 0, iff the stream head
* has for some reason given up on the ioctl in progress.
* The most likely cause is an interrupted ioctl syscall.
* We will behave robustly because (given our perimeter)
* the ttcompat_state_t will get set up for the new ioctl,
* and when the response we were waiting for appears it
* will be passed on to the stream head which will discard
* it as non-current.
*/
ASSERT(mp->b_cont != NULL);
tp->t_arg = *(intptr_t *)mp->b_cont->b_rptr;
/* free the data buffer - it might not be sufficient */
/* driver will allocate one for termios size */
freemsg(mp->b_cont);
mp->b_cont = NULL;
iocp->ioc_count = 0;
/* FALLTHRU */
case TIOCGETP:
goto dogets;
/*
* "set"-style calls that set translated data into a "termios"
* structure. Set our idea of the new state from the value
* given to us. We then have to get the current state, so we
* turn this guy into a TCGETS and pass it down. When the
* ACK comes back, we modify the state we got back and shove it
* back down as the appropriate type of TCSETS.
*/
case TIOCSETP:
case TIOCSETN:
error = miocpullup(mp, sizeof (struct sgttyb));
if (error != 0) {
miocnak(q, mp, 0, error);
return;
}
tp->t_new_sgttyb = *((struct sgttyb *)mp->b_cont->b_rptr);
goto dogets;
case TIOCSETC:
error = miocpullup(mp, sizeof (struct tchars));
if (error != 0) {
miocnak(q, mp, 0, error);
return;
}
tp->t_new_tchars = *((struct tchars *)mp->b_cont->b_rptr);
goto dogets;
case TIOCSLTC:
error = miocpullup(mp, sizeof (struct ltchars));
if (error != 0) {
miocnak(q, mp, 0, error);
return;
}
tp->t_new_ltchars = *((struct ltchars *)mp->b_cont->b_rptr);
goto dogets;
case TIOCLBIS:
case TIOCLBIC:
case TIOCLSET:
error = miocpullup(mp, sizeof (int));
if (error != 0) {
miocnak(q, mp, 0, error);
return;
}
tp->t_new_lflags = *(int *)mp->b_cont->b_rptr;
goto dogets;
/*
* "set"-style call that sets a particular bit in a "termios"
* structure. We then have to get the current state, so we
* turn this guy into a TCGETS and pass it down. When the
* ACK comes back, we modify the state we got back and shove it
* back down as the appropriate type of TCSETS.
*/
case TIOCHPCL:
dogets:
tp->t_ioccmd = iocp->ioc_cmd;
tp->t_iocid = iocp->ioc_id;
tp->t_state |= TS_IOCWAIT;
iocp->ioc_cmd = TCGETS;
iocp->ioc_count = 0; /* no data returned unless we say so */
break;
/*
* "set"-style call that sets DTR. Pretend that it was a TIOCMBIS
* with TIOCM_DTR set.
*/
case TIOCSDTR: {
mblk_t *datap;
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)
goto allocfailure;
*(int *)datap->b_wptr = TIOCM_DTR;
datap->b_wptr += sizeof (int);
iocp->ioc_cmd = TIOCMBIS; /* turn it into a TIOCMBIS */
if (mp->b_cont != NULL)
freemsg(mp->b_cont);
mp->b_cont = datap; /* attach the data */
iocp->ioc_count = sizeof (int); /* in case driver checks */
break;
}
/*
* "set"-style call that clears DTR. Pretend that it was a TIOCMBIC
* with TIOCM_DTR set.
*/
case TIOCCDTR: {
mblk_t *datap;
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)
goto allocfailure;
*(int *)datap->b_wptr = TIOCM_DTR;
datap->b_wptr += sizeof (int);
iocp->ioc_cmd = TIOCMBIC; /* turn it into a TIOCMBIC */
if (mp->b_cont != NULL)
freemsg(mp->b_cont);
mp->b_cont = datap; /* attach the data */
iocp->ioc_count = sizeof (int); /* in case driver checks */
break;
}
/*
* Translate into the S5 form of TCFLSH.
*/
case TIOCFLUSH: {
int flags;
error = miocpullup(mp, sizeof (int));
if (error != 0) {
miocnak(q, mp, 0, error);
return;
}
flags = *(int *)mp->b_cont->b_rptr;
switch (flags&(FREAD|FWRITE)) {
case 0:
case FREAD|FWRITE:
flags = 2; /* flush 'em both */
break;
case FREAD:
flags = 0; /* flush read */
break;
case FWRITE:
flags = 1; /* flush write */
break;
}
iocp->ioc_cmd = TCFLSH; /* turn it into a TCFLSH */
*(int *)mp->b_cont->b_rptr = flags; /* fiddle the arg */
break;
}
/*
* Turn into a TCXONC.
*/
case TIOCSTOP: {
mblk_t *datap;
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)
goto allocfailure;
*(int *)datap->b_wptr = 0; /* stop */
datap->b_wptr += sizeof (int);
iocp->ioc_cmd = TCXONC; /* turn it into a XONC */
iocp->ioc_count = sizeof (int);
if (mp->b_cont != NULL)
freemsg(mp->b_cont);
mp->b_cont = datap; /* attach the data */
break;
}
case TIOCSTART: {
mblk_t *datap;
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL)
goto allocfailure;
*(int *)datap->b_wptr = 1; /* start */
datap->b_wptr += sizeof (int);
iocp->ioc_cmd = TCXONC; /* turn it into a XONC */
iocp->ioc_count = sizeof (int);
if (mp->b_cont != NULL)
freemsg(mp->b_cont);
mp->b_cont = datap; /* attach the data */
break;
}
case TIOCSETD:
case TIOCGETD:
case DIOCSETP:
case DIOCGETP:
case LDOPEN:
case LDCLOSE:
case LDCHG:
case LDSETT:
case LDGETT:
/*
* All of these ioctls are just ACK'd, except for
* TIOCSETD, which must be for line discipline zero.
*/
mp->b_datap->db_type = M_IOCACK;
if (iocp->ioc_cmd == TIOCSETD) {
iocp->ioc_error = miocpullup(mp, sizeof (uchar_t));
if (iocp->ioc_error == 0 && (*mp->b_cont->b_rptr != 0))
mp->b_datap->db_type = M_IOCNAK;
}
iocp->ioc_error = 0;
iocp->ioc_count = 0;
iocp->ioc_rval = 0;
qreply(q, mp);
return;
case IOCTYPE:
mp->b_datap->db_type = M_IOCACK;
iocp->ioc_error = 0;
iocp->ioc_count = 0;
iocp->ioc_rval = TIOC;
qreply(q, mp);
return;
case TIOCEXCL:
/* check for binary value of XCLUDE flag ???? */
tp->t_new_lflags |= XCLUDE;
mp->b_datap->db_type = M_IOCACK;
iocp->ioc_error = 0;
iocp->ioc_count = 0;
iocp->ioc_rval = 0;
qreply(q, mp);
return;
case TIOCNXCL:
tp->t_new_lflags &= ~XCLUDE;
mp->b_datap->db_type = M_IOCACK;
iocp->ioc_error = 0;
iocp->ioc_count = 0;
iocp->ioc_rval = 0;
qreply(q, mp);
return;
}
/*
* We don't reply to most calls, we just pass them down,
* possibly after modifying the arguments.
*/
putnext(q, mp);
return;
allocfailure:
/*
* We needed to allocate something to handle this "ioctl", but
* couldn't; save this "ioctl" and arrange to get called back when
* it's more likely that we can get what we need.
* If there's already one being saved, throw it out, since it
* must have timed out.
*/
if (tp->t_iocpending != NULL)
freemsg(tp->t_iocpending);
tp->t_iocpending = mp; /* hold this ioctl */
if (tp->t_bufcallid != 0)
qunbufcall(q, tp->t_bufcallid);
tp->t_bufcallid = qbufcall(q, sizeof (struct iocblk), BPRI_HI,
ttcompat_reioctl, q);
}
/*
* Called when an M_IOCACK message is seen on the read queue; if this
* is the response we were waiting for, we either:
* modify the data going up (if the "ioctl" read data); since in all
* cases, the old-style returned information is smaller than or the same
* size as the new-style returned information, we just overwrite the old
* stuff with the new stuff (beware of changing structure sizes, in case
* you invalidate this)
* or
* take this data, modify it appropriately, and send it back down (if
* the "ioctl" wrote data).
* In either case, we cancel the "wait"; the final response to a "write"
* ioctl goes back up to the user.
* If this wasn't the response we were waiting for, just pass it up.
*/
static void
ttcompat_ioctl_ack(queue_t *q, mblk_t *mp)
{
ttcompat_state_t *tp;
struct iocblk *iocp;
mblk_t *datap;
tp = (ttcompat_state_t *)q->q_ptr;
iocp = (struct iocblk *)mp->b_rptr;
if (!(tp->t_state&TS_IOCWAIT) || iocp->ioc_id != tp->t_iocid) {
/*
* This isn't the reply we're looking for. Move along.
*/
putnext(q, mp);
return;
}
datap = mp->b_cont; /* mblk containing data going up */
switch (tp->t_ioccmd) {
case TIOCGETP: {
struct sgttyb *cb;
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
datap->b_rptr = datap->b_wptr = datap->b_datap->db_base;
/* recycle the reply's buffer */
cb = (struct sgttyb *)datap->b_wptr;
/*
* This is used for TIOCGETP handling of sg_ispeed and
* sg_ospeed. If the current speed is over 38400 (the
* sgttyb limit), then we report 38400. Note that
* when "compatibility with old releases" is enabled
* (sgttyb_handling == 0), then t_[io]speed will have
* garbled nonsense, as in prior releases. (See
* to_compat() below).
*/
cb->sg_ispeed = tp->t_curstate.t_ispeed > B38400 ? B38400 :
tp->t_curstate.t_ispeed;
cb->sg_ospeed = tp->t_curstate.t_ospeed > B38400 ? B38400 :
tp->t_curstate.t_ospeed;
cb->sg_erase = tp->t_curstate.t_erase;
cb->sg_kill = tp->t_curstate.t_kill;
cb->sg_flags = tp->t_curstate.t_flags;
datap->b_wptr += sizeof (struct sgttyb);
iocp->ioc_count = sizeof (struct sgttyb);
/* you are lucky - stream head knows how to copy you out */
tp->t_state &= ~TS_IOCWAIT; /* we got what we wanted */
iocp->ioc_rval = 0;
iocp->ioc_cmd = tp->t_ioccmd;
putnext(q, mp);
return;
}
case TIOCGETC:
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
datap->b_rptr = datap->b_wptr = datap->b_datap->db_base;
/* recycle the reply's buffer */
bcopy(&tp->t_curstate.t_intrc, datap->b_wptr,
sizeof (struct tchars));
datap->b_wptr += sizeof (struct tchars);
break;
case TIOCGLTC:
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
datap->b_rptr = datap->b_wptr = datap->b_datap->db_base;
/* recycle the reply's buffer */
bcopy(&tp->t_curstate.t_suspc, datap->b_wptr,
sizeof (struct ltchars));
datap->b_wptr += sizeof (struct ltchars);
break;
case TIOCLGET:
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
datap->b_rptr = datap->b_wptr = datap->b_datap->db_base;
/* recycle the reply's buffer */
*(int *)datap->b_wptr =
((unsigned)tp->t_curstate.t_flags) >> 16;
datap->b_wptr += sizeof (int);
break;
case TIOCSETP:
case TIOCSETN:
/*
* Get the current state from the GETS data, and
* update it.
*/
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
tp->t_curstate.t_erase = tp->t_new_sgttyb.sg_erase;
tp->t_curstate.t_kill = tp->t_new_sgttyb.sg_kill;
/*
* For new-style handling, we ignore requests to set
* B38400 when the current speed is over B38400. This
* means that we change the speed as requested if:
* old style (sgttyb_handling == 0) is requested
* the requested new speed isn't B38400
* the current speed is at or below B38400
* Note that when old style is requested, both speeds
* in t_curstate are set to <= B38400 by to_compat, so
* the first test isn't needed here.
* Also note that we silently allow the user to set
* speeds above B38400 through this interface,
* regardless of the style setting. This allows
* greater compatibility with current BSD releases.
*/
if (tp->t_new_sgttyb.sg_ispeed != B38400 ||
tp->t_curstate.t_ispeed <= B38400)
tp->t_curstate.t_ispeed = tp->t_new_sgttyb.sg_ispeed;
if (tp->t_new_sgttyb.sg_ospeed != B38400 ||
tp->t_curstate.t_ospeed <= B38400)
tp->t_curstate.t_ospeed = tp->t_new_sgttyb.sg_ospeed;
tp->t_curstate.t_flags =
(tp->t_curstate.t_flags & 0xffff0000) |
(tp->t_new_sgttyb.sg_flags & 0xffff);
/*
* Replace the data that came up with the updated data.
*/
from_compat(&tp->t_curstate, (struct termios *)datap->b_rptr);
/*
* Send it back down as a TCSETS or TCSETSF.
*/
iocp->ioc_cmd = (tp->t_ioccmd == TIOCSETP) ? TCSETSF : TCSETS;
goto senddown;
case TIOCSETC:
/*
* Get the current state from the GETS data, and
* update it.
*/
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
bcopy(&tp->t_new_tchars,
&tp->t_curstate.t_intrc, sizeof (struct tchars));
/*
* Replace the data that came up with the updated data.
*/
from_compat(&tp->t_curstate, (struct termios *)datap->b_rptr);
/*
* Send it back down as a TCSETS.
*/
iocp->ioc_cmd = TCSETS;
goto senddown;
case TIOCSLTC:
/*
* Get the current state from the GETS data, and
* update it.
*/
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
bcopy(&tp->t_new_ltchars,
&tp->t_curstate.t_suspc, sizeof (struct ltchars));
/*
* Replace the data that came up with the updated data.
*/
from_compat(&tp->t_curstate, (struct termios *)datap->b_rptr);
/*
* Send it back down as a TCSETS.
*/
iocp->ioc_cmd = TCSETS;
goto senddown;
case TIOCLBIS:
/*
* Get the current state from the GETS data, and
* update it.
*/
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
tp->t_curstate.t_flags |= (tp->t_new_lflags << 16);
/*
* Replace the data that came up with the updated data.
*/
from_compat(&tp->t_curstate, (struct termios *)datap->b_rptr);
/*
* Send it back down as a TCSETS.
*/
iocp->ioc_cmd = TCSETS;
goto senddown;
case TIOCLBIC:
/*
* Get the current state from the GETS data, and
* update it.
*/
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
tp->t_curstate.t_flags &= ~(tp->t_new_lflags << 16);
/*
* Replace the data that came up with the updated data.
*/
from_compat(&tp->t_curstate, (struct termios *)datap->b_rptr);
/*
* Send it back down as a TCSETS.
*/
iocp->ioc_cmd = TCSETS;
goto senddown;
case TIOCLSET:
/*
* Get the current state from the GETS data, and
* update it.
*/
to_compat((struct termios *)datap->b_rptr, &tp->t_curstate);
tp->t_curstate.t_flags &= 0xffff;
tp->t_curstate.t_flags |= (tp->t_new_lflags << 16);
/*
* Replace the data that came up with the updated data.
*/
from_compat(&tp->t_curstate, (struct termios *)datap->b_rptr);
/*
* Send it back down as a TCSETS.
*/
iocp->ioc_cmd = TCSETS;
goto senddown;
case TIOCHPCL:
/*
* Replace the data that came up with the updated data.
*/
((struct termios *)datap->b_rptr)->c_cflag |= HUPCL;
/*
* Send it back down as a TCSETS.
*/
iocp->ioc_cmd = TCSETS;
goto senddown;
case TCSETSF:
/*
* We're acknowledging the terminal reset ioctl that we sent
* when the module was opened.
*/
tp->t_state &= ~(TS_IOCWAIT | TS_TIOCNAK);
freemsg(mp);
return;
default:
cmn_err(CE_WARN, "ttcompat: Unexpected ioctl acknowledgment\n");
}
/*
* All the calls that return something return 0.
*/
tp->t_state &= ~TS_IOCWAIT; /* we got what we wanted */
iocp->ioc_rval = 0;
/* copy out the data - ioctl transparency */
iocp->ioc_cmd = tp->t_ioccmd;
ttcopyout(q, mp);
return;
senddown:
/*
* Send a "get state" reply back down, with suitably-modified
* state, as a "set state" "ioctl".
*/
tp->t_state &= ~TS_IOCWAIT;
mp->b_datap->db_type = M_IOCTL;
mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
putnext(WR(q), mp);
}
/* Called from ttcompatrput M_IOCACK processing. */
/* Copies out the data using M_COPYOUT messages */
static void
ttcopyout(queue_t *q, mblk_t *mp)
{
struct copyreq *cqp;
ttcompat_state_t *tp;
tp = (ttcompat_state_t *)q->q_ptr;
mp->b_datap->db_type = M_COPYOUT;
cqp = (struct copyreq *)mp->b_rptr;
cqp->cq_addr = (caddr_t)tp->t_arg; /* retrieve the 3rd argument */
tp->t_arg = 0; /* clear it since we don't need it anymore */
switch (tp->t_ioccmd) {
case TIOCGLTC:
cqp->cq_size = sizeof (struct ltchars);
break;
case TIOCGETC:
cqp->cq_size = sizeof (struct tchars);
break;
case TIOCLGET:
cqp->cq_size = sizeof (int);
break;
default:
cmn_err(CE_WARN,
"ttcompat: Unknown ioctl to copyout\n");
break;
}
cqp->cq_flag = 0;
cqp->cq_private = NULL;
tp->t_state |= TS_W_OUT;
putnext(q, mp);
}
/*
* Called when an M_IOCNAK message is seen on the read queue; if this is
* the response we were waiting for, cancel the wait. Pass the reply up;
* if we were waiting for this response, we can't complete the "ioctl" and
* the NAK will tell that to the guy above us.
* If this wasn't the response we were waiting for, just pass it up.
*/
static void
ttcompat_ioctl_nak(queue_t *q, mblk_t *mp)
{
ttcompat_state_t *tp;
struct iocblk *iocp;
iocp = (struct iocblk *)mp->b_rptr;
tp = (ttcompat_state_t *)q->q_ptr;
if (tp->t_state&TS_IOCWAIT && iocp->ioc_id == tp->t_iocid) {
tp->t_state &= ~TS_IOCWAIT; /* this call isn't going through */
tp->t_arg = 0; /* we may have stashed the 3rd argument */
}
putnext(q, mp);
}
#define FROM_COMPAT_CHAR(to, from) { if ((to = from) == 0377) to = 0; }
static void
from_compat(compat_state_t *csp, struct termios *termiosp)
{
termiosp->c_iflag = 0;
termiosp->c_oflag &= (ONLRET|ONOCR);
termiosp->c_cflag = (termiosp->c_cflag &
(CRTSCTS|CRTSXOFF|PAREXT|LOBLK|HUPCL)) | CREAD;
if (csp->t_ospeed > CBAUD) {
termiosp->c_cflag |= ((csp->t_ospeed - CBAUD - 1) & CBAUD) |
CBAUDEXT;
} else {
termiosp->c_cflag |= csp->t_ospeed & CBAUD;
}
if (csp->t_ospeed != csp->t_ispeed) {
if (csp->t_ispeed > (CIBAUD >> IBSHIFT)) {
termiosp->c_cflag |= CIBAUDEXT |
(((csp->t_ispeed - (CIBAUD >> IBSHIFT) - 1) <<
IBSHIFT) & CIBAUD);
} else {
termiosp->c_cflag |= (csp->t_ispeed << IBSHIFT) &
CIBAUD;
}
/* hang up if ispeed=0 */
if (csp->t_ispeed == 0)
termiosp->c_cflag &= ~CBAUD & ~CBAUDEXT;
}
if (csp->t_ispeed == B110 || csp->t_xflags & STOPB)
termiosp->c_cflag |= CSTOPB;
termiosp->c_lflag = ECHOK;
FROM_COMPAT_CHAR(termiosp->c_cc[VERASE], csp->t_erase);
FROM_COMPAT_CHAR(termiosp->c_cc[VKILL], csp->t_kill);
FROM_COMPAT_CHAR(termiosp->c_cc[VINTR], csp->t_intrc);
FROM_COMPAT_CHAR(termiosp->c_cc[VQUIT], csp->t_quitc);
FROM_COMPAT_CHAR(termiosp->c_cc[VSTART], csp->t_startc);
FROM_COMPAT_CHAR(termiosp->c_cc[VSTOP], csp->t_stopc);
termiosp->c_cc[VEOL2] = 0;
FROM_COMPAT_CHAR(termiosp->c_cc[VSUSP], csp->t_suspc);
/* is this useful? */
FROM_COMPAT_CHAR(termiosp->c_cc[VDSUSP], csp->t_dsuspc);
FROM_COMPAT_CHAR(termiosp->c_cc[VREPRINT], csp->t_rprntc);
FROM_COMPAT_CHAR(termiosp->c_cc[VDISCARD], csp->t_flushc);
FROM_COMPAT_CHAR(termiosp->c_cc[VWERASE], csp->t_werasc);
FROM_COMPAT_CHAR(termiosp->c_cc[VLNEXT], csp->t_lnextc);
termiosp->c_cc[VSTATUS] = 0;
if (csp->t_flags & O_TANDEM)
termiosp->c_iflag |= IXOFF;
if (csp->t_flags & O_LCASE) {
termiosp->c_iflag |= IUCLC;
termiosp->c_oflag |= OLCUC;
termiosp->c_lflag |= XCASE;
}
if (csp->t_flags & O_ECHO)
termiosp->c_lflag |= ECHO;
if (csp->t_flags & O_CRMOD) {
termiosp->c_iflag |= ICRNL;
termiosp->c_oflag |= ONLCR;
switch (csp->t_flags & O_CRDELAY) {
case O_CR1:
termiosp->c_oflag |= CR2;
break;
case O_CR2:
termiosp->c_oflag |= CR3;
break;
}
} else {
if ((csp->t_flags & O_NLDELAY) == O_NL1)
termiosp->c_oflag |= ONLRET|CR1; /* tty37 */
}
if ((csp->t_flags & O_NLDELAY) == O_NL2)
termiosp->c_oflag |= NL1;
/*
* When going into RAW mode, the special characters controlled by the
* POSIX IEXTEN bit no longer apply; when leaving, they do.
*/
if (csp->t_flags & O_RAW) {
termiosp->c_cflag |= CS8;
termiosp->c_iflag &= ~(ICRNL|IUCLC);
termiosp->c_lflag &= ~(XCASE|IEXTEN);
} else {
termiosp->c_iflag |= IMAXBEL|BRKINT|IGNPAR;
if (termiosp->c_cc[VSTOP] != 0 && termiosp->c_cc[VSTART] != 0)
termiosp->c_iflag |= IXON;
if (csp->t_flags & O_LITOUT)
termiosp->c_cflag |= CS8;
else {
if (csp->t_flags & O_PASS8)
termiosp->c_cflag |= CS8;
/* XXX - what about 8 bits plus parity? */
else {
switch (csp->t_flags & (O_EVENP|O_ODDP)) {
case 0:
termiosp->c_iflag |= ISTRIP;
termiosp->c_cflag |= CS8;
break;
case O_EVENP:
termiosp->c_iflag |= INPCK|ISTRIP;
termiosp->c_cflag |= CS7|PARENB;
break;
case O_ODDP:
termiosp->c_iflag |= INPCK|ISTRIP;
termiosp->c_cflag |= CS7|PARENB|PARODD;
break;
case O_EVENP|O_ODDP:
termiosp->c_iflag |= ISTRIP;
termiosp->c_cflag |= CS7|PARENB;
break;
}
}
if (!(csp->t_xflags & NOPOST))
termiosp->c_oflag |= OPOST;
}
termiosp->c_lflag |= IEXTEN;
if (!(csp->t_xflags & NOISIG))
termiosp->c_lflag |= ISIG;
if (!(csp->t_flags & O_CBREAK))
termiosp->c_lflag |= ICANON;
if (csp->t_flags & O_CTLECH)
termiosp->c_lflag |= ECHOCTL;
}
switch (csp->t_flags & O_TBDELAY) {
case O_TAB1:
termiosp->c_oflag |= TAB1;
break;
case O_TAB2:
termiosp->c_oflag |= TAB2;
break;
case O_XTABS:
termiosp->c_oflag |= TAB3;
break;
}
if (csp->t_flags & O_VTDELAY)
termiosp->c_oflag |= FFDLY;
if (csp->t_flags & O_BSDELAY)
termiosp->c_oflag |= BSDLY;
if (csp->t_flags & O_PRTERA)
termiosp->c_lflag |= ECHOPRT;
if (csp->t_flags & O_CRTERA)
termiosp->c_lflag |= ECHOE;
if (csp->t_flags & O_TOSTOP)
termiosp->c_lflag |= TOSTOP;
if (csp->t_flags & O_FLUSHO)
termiosp->c_lflag |= FLUSHO;
if (csp->t_flags & O_NOHANG)
termiosp->c_cflag |= CLOCAL;
if (csp->t_flags & O_CRTKIL)
termiosp->c_lflag |= ECHOKE;
if (csp->t_flags & O_PENDIN)
termiosp->c_lflag |= PENDIN;
if (!(csp->t_flags & O_DECCTQ))
termiosp->c_iflag |= IXANY;
if (csp->t_flags & O_NOFLSH)
termiosp->c_lflag |= NOFLSH;
if (termiosp->c_lflag & ICANON) {
FROM_COMPAT_CHAR(termiosp->c_cc[VEOF], csp->t_eofc);
FROM_COMPAT_CHAR(termiosp->c_cc[VEOL], csp->t_brkc);
} else {
termiosp->c_cc[VMIN] = 1;
termiosp->c_cc[VTIME] = 0;
}
}
#define TO_COMPAT_CHAR(to, from) { if ((to = from) == 0) to = (uchar_t)0377; }
static void
to_compat(struct termios *termiosp, compat_state_t *csp)
{
csp->t_xflags &= (NOISIG|NOPOST);
csp->t_ospeed = termiosp->c_cflag & CBAUD;
csp->t_ispeed = (termiosp->c_cflag & CIBAUD) >> IBSHIFT;
if (sgttyb_handling > 0) {
if (termiosp->c_cflag & CBAUDEXT)
csp->t_ospeed += CBAUD + 1;
if (termiosp->c_cflag & CIBAUDEXT)
csp->t_ispeed += (CIBAUD >> IBSHIFT) + 1;
}
if (csp->t_ispeed == 0)
csp->t_ispeed = csp->t_ospeed;
if ((termiosp->c_cflag & CSTOPB) && csp->t_ispeed != B110)
csp->t_xflags |= STOPB;
TO_COMPAT_CHAR(csp->t_erase, termiosp->c_cc[VERASE]);
TO_COMPAT_CHAR(csp->t_kill, termiosp->c_cc[VKILL]);
TO_COMPAT_CHAR(csp->t_intrc, termiosp->c_cc[VINTR]);
TO_COMPAT_CHAR(csp->t_quitc, termiosp->c_cc[VQUIT]);
TO_COMPAT_CHAR(csp->t_startc, termiosp->c_cc[VSTART]);
TO_COMPAT_CHAR(csp->t_stopc, termiosp->c_cc[VSTOP]);
TO_COMPAT_CHAR(csp->t_suspc, termiosp->c_cc[VSUSP]);
TO_COMPAT_CHAR(csp->t_dsuspc, termiosp->c_cc[VDSUSP]);
TO_COMPAT_CHAR(csp->t_rprntc, termiosp->c_cc[VREPRINT]);
TO_COMPAT_CHAR(csp->t_flushc, termiosp->c_cc[VDISCARD]);
TO_COMPAT_CHAR(csp->t_werasc, termiosp->c_cc[VWERASE]);
TO_COMPAT_CHAR(csp->t_lnextc, termiosp->c_cc[VLNEXT]);
csp->t_flags &= (O_CTLECH|O_LITOUT|O_PASS8|O_ODDP|O_EVENP);
if (termiosp->c_iflag & IXOFF)
csp->t_flags |= O_TANDEM;
if (!(termiosp->c_iflag &
(IMAXBEL|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|
INLCR|IGNCR|ICRNL|IUCLC|IXON)) &&
!(termiosp->c_oflag & OPOST) &&
(termiosp->c_cflag & (CSIZE|PARENB)) == CS8 &&
!(termiosp->c_lflag & (ISIG|ICANON|XCASE|IEXTEN)))
csp->t_flags |= O_RAW;
else {
if (!(termiosp->c_iflag & IXON)) {
csp->t_startc = (uchar_t)0377;
csp->t_stopc = (uchar_t)0377;
}
if ((termiosp->c_cflag & (CSIZE|PARENB)) == CS8 &&
!(termiosp->c_oflag & OPOST))
csp->t_flags |= O_LITOUT;
else {
csp->t_flags &= ~O_LITOUT;
if ((termiosp->c_cflag & (CSIZE|PARENB)) == CS8) {
if (!(termiosp->c_iflag & ISTRIP))
csp->t_flags |= O_PASS8;
} else {
csp->t_flags &= ~(O_ODDP|O_EVENP|O_PASS8);
if (termiosp->c_cflag & PARODD)
csp->t_flags |= O_ODDP;
else if (termiosp->c_iflag & INPCK)
csp->t_flags |= O_EVENP;
else
csp->t_flags |= O_ODDP|O_EVENP;
}
if (!(termiosp->c_oflag & OPOST))
csp->t_xflags |= NOPOST;
else
csp->t_xflags &= ~NOPOST;
}
if (!(termiosp->c_lflag & ISIG))
csp->t_xflags |= NOISIG;
else
csp->t_xflags &= ~NOISIG;
if (!(termiosp->c_lflag & ICANON))
csp->t_flags |= O_CBREAK;
if (termiosp->c_lflag & ECHOCTL)
csp->t_flags |= O_CTLECH;
else
csp->t_flags &= ~O_CTLECH;
}
if (termiosp->c_oflag & OLCUC)
csp->t_flags |= O_LCASE;
if (termiosp->c_lflag&ECHO)
csp->t_flags |= O_ECHO;
if (termiosp->c_oflag & ONLCR) {
csp->t_flags |= O_CRMOD;
switch (termiosp->c_oflag & CRDLY) {
case CR2:
csp->t_flags |= O_CR1;
break;
case CR3:
csp->t_flags |= O_CR2;
break;
}
} else {
if ((termiosp->c_oflag & CR1) &&
(termiosp->c_oflag & ONLRET))
csp->t_flags |= O_NL1; /* tty37 */
}
if ((termiosp->c_oflag & ONLRET) && (termiosp->c_oflag & NL1))
csp->t_flags |= O_NL2;
switch (termiosp->c_oflag & TABDLY) {
case TAB1:
csp->t_flags |= O_TAB1;
break;
case TAB2:
csp->t_flags |= O_TAB2;
break;
case XTABS:
csp->t_flags |= O_XTABS;
break;
}
if (termiosp->c_oflag & FFDLY)
csp->t_flags |= O_VTDELAY;
if (termiosp->c_oflag & BSDLY)
csp->t_flags |= O_BSDELAY;
if (termiosp->c_lflag & ECHOPRT)
csp->t_flags |= O_PRTERA;
if (termiosp->c_lflag & ECHOE)
csp->t_flags |= (O_CRTERA|O_CRTBS);
if (termiosp->c_lflag & TOSTOP)
csp->t_flags |= O_TOSTOP;
if (termiosp->c_lflag & FLUSHO)
csp->t_flags |= O_FLUSHO;
if (termiosp->c_cflag & CLOCAL)
csp->t_flags |= O_NOHANG;
if (termiosp->c_lflag & ECHOKE)
csp->t_flags |= O_CRTKIL;
if (termiosp->c_lflag & PENDIN)
csp->t_flags |= O_PENDIN;
if (!(termiosp->c_iflag & IXANY))
csp->t_flags |= O_DECCTQ;
if (termiosp->c_lflag & NOFLSH)
csp->t_flags |= O_NOFLSH;
if (termiosp->c_lflag & ICANON) {
TO_COMPAT_CHAR(csp->t_eofc, termiosp->c_cc[VEOF]);
TO_COMPAT_CHAR(csp->t_brkc, termiosp->c_cc[VEOL]);
} else {
termiosp->c_cc[VMIN] = 1;
termiosp->c_cc[VTIME] = 0;
}
}