/* * 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 2001-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * DESCRIPTION * * ttymux - Multiplexer driver for multiplexing termio compliant streams onto * a single upper stream. * * ADD2FRONT macro can be used to specify the order in which a console * device is put in the queue of multiplexed physical serial devices, * during the association and disassociation of a console interface. * When this macro is defined, the device is placed in front of the queue, * otherwise by default it is placed at the end. * Console I/O happens to each of the physical devices in the order of * their position in this queue. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ttymux_impl.h" /* * Extern declarations */ extern mblk_t *mkiocb(uint_t); extern int nulldev(); extern uintptr_t space_fetch(char *key); extern int sm_ioctl_cmd(sm_uqi_t *, mblk_t *); extern int ttymux_abort_ioctl(mblk_t *); extern int ttymux_device_fini(sm_lqi_t *); extern int ttymux_device_init(sm_lqi_t *); /* * Exported interfaces */ int sm_disassociate(int, sm_lqi_t *, ulong_t); int sm_associate(int, sm_lqi_t *, ulong_t, uint_t, char *); /* * Variables defined here and visible only internally */ sm_ss_t *sm_ssp = 0; static int sm_instance = 0; static int smctlunit; static uint_t sm_default_trflag = 0; uint_t sm_max_units = 6; uint_t sm_minor_cnt = 0; static uint_t sm_refuse_opens = 0; /* * Local definitions. */ /* force these flags to be unset on console devices */ static ulong_t sm_cmask = (ulong_t)(CRTSXOFF|CRTSCTS); /* * SECTION * Implementation Section: */ void sm_debug(char *msg, ...) { va_list args; char buf[256]; int sz; va_start(args, msg); sz = vsnprintf(buf, sizeof (buf), msg, args); va_end(args); if (sz < 0) (void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1, SL_TRACE, "vsnprintf parse error\n"); else if (sz > sizeof (buf)) { char *b; size_t len = sz + 1; b = kmem_alloc(len, KM_SLEEP); va_start(args, msg); sz = vsnprintf(b, len, msg, args); va_end(args); if (sz > 0) (void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1, SL_TRACE, b); kmem_free(b, len); } else { (void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1, SL_TRACE, buf); } } void sm_log(char *msg, ...) { va_list args; char buf[128]; int sz; va_start(args, msg); sz = vsnprintf(buf, sizeof (buf), msg, args); va_end(args); if (sz < 0) (void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1, SL_TRACE, "vsnprintf parse error\n"); else if (sz > sizeof (buf)) { char *b; size_t len = sz + 1; b = kmem_alloc(len, KM_SLEEP); va_start(args, msg); sz = vsnprintf(b, len, msg, args); va_end(args); if (sz > 0) (void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1, SL_NOTE, b); kmem_free(b, len); } else { (void) strlog(ddi_driver_major(sm_ssp->sm_dip), sm_instance, 1, SL_NOTE, buf); } } /* * Should only be called if the caller can guarantee that the vnode * and/or the stream won't disappear while finding the dip. * This routine is only called during an I_PLINK request so it's safe. * The routine obtains the dev_t for a linked se stream. */ static void sm_setdip(queue_t *q, sm_lqi_t *lqi) { lqi->sm_dev = q && STREAM(q) ? STREAM(q)->sd_vnode->v_rdev : NODEV; } /* * Called from driver close, state change reports and I_PUNLINK ioctl. * A lower stream has been unlinked - clean up the state associated with it. */ void sm_lqifree(sm_lqi_t *lqi) { int mu_owned; sm_lqi_t **pplqi; ASSERT(mutex_owned(lqi->sm_umutex)); ASSERT(SM_RQ(lqi) != 0); /* * Clear all state associated with this lower queue except * the identity of the queues themselves and the link id which * can only be cleared by issuing a streams I_PUNLINK ioctl. * * The association of a lower queue is a two step process: * 1. initialise the lower q data structure on I_PLINK * 2. associate an upper q with the lower q on SM_CMD_ASSOCIATE. * * If step 2 has ocurred then * remove this lower queue info from the logical unit. */ if (lqi->sm_uqi) { sm_dbg('Y', ("lqifree unit %d, ", lqi->sm_uqi->sm_lunit)); if ((mu_owned = mutex_owned(lqi->sm_uqi->sm_umutex)) == 0) LOCK_UNIT(lqi->sm_uqi); pplqi = &lqi->sm_uqi->sm_lqs; while (*pplqi != lqi) { ASSERT(*pplqi); pplqi = &((*pplqi)->sm_nlqi); } *pplqi = lqi->sm_nlqi; lqi->sm_uqi->sm_nlqs--; if (mu_owned == 0) UNLOCK_UNIT(lqi->sm_uqi); lqi->sm_uqi = 0; } } /* * Given a q return the associated lower queue data structure or NULL. * Return the data locked. */ static sm_lqi_t * get_lqi_byq(queue_t *q) { int i; sm_lqi_t *lqi, *flqi = 0; for (i = 0; i < MAX_LQS; i++) { lqi = &sm_ssp->sm_lqs[i]; LOCK_UNIT(lqi); if (flqi == 0 && lqi->sm_linkid == 0) /* assumes muxids != 0 */ flqi = lqi; else if (SM_RQ(lqi) == q || SM_WQ(lqi) == q) { if (flqi) UNLOCK_UNIT(flqi); return (lqi); } else UNLOCK_UNIT(lqi); } return (flqi); } /* * Given a streams link identifier return the associated lower queue data * structure or NULL. */ sm_lqi_t * get_lqi_byid(int linkid) { int i; sm_lqi_t *lqi; if (linkid == 0) return (NULL); for (i = 0; i < MAX_LQS; i++) { lqi = &sm_ssp->sm_lqs[i]; if (lqi->sm_linkid == linkid) return (lqi); } return (NULL); } /* * Given a dev_t for a lower stream return the associated lower queue data * structure or NULL. */ sm_lqi_t * get_lqi_bydevt(dev_t dev) { int i; sm_lqi_t *lqi; if (dev == NODEV) return (NULL); for (i = 0; i < MAX_LQS; i++) { lqi = &sm_ssp->sm_lqs[i]; if (lqi->sm_dev == dev) return (lqi); } return (NULL); } /* * Determine whether the input flag is set on at least * howmany queues. */ static int sm_is_flag_set(sm_uqi_t *uqi, uint_t flag, uint_t howmany) { sm_lqi_t *lqi; if (howmany == 0) return (0); for (lqi = uqi->sm_lqs; lqi; lqi = lqi->sm_nlqi) { if (lqi->sm_flags & flag) if (--howmany == 0) return (1); } return (0); } /* * How many usable queues are associated with a given upper stream */ static int sm_uwq_error(sm_uqi_t *uqi) { return (sm_is_flag_set(uqi, (WERROR_MODE|HANGUP_MODE), uqi->sm_nlqs)); } /* * How many of the queues associated with a given upper stream * - do not - have the given flags set. */ static int sm_q_count(sm_uqi_t *uqi, uint_t flag) { sm_lqi_t *lqi; int count = 0; for (lqi = uqi->sm_lqs; lqi; lqi = lqi->sm_nlqi) { if ((lqi->sm_flags & flag) == 0) count++; } return (count); } /* * How many of the queues associated with a given upper stream * - do not - have the given flags set. */ static int sm_qs_without(sm_uqi_t *uqi, uint_t flag, uint_t ioflag) { sm_lqi_t *lqi; int count = 0; for (lqi = uqi->sm_lqs; lqi; lqi = lqi->sm_nlqi) { if ((lqi->sm_flags & flag) == 0 && (lqi->sm_ioflag & ioflag) == 0) count++; } return (count); } /* * How many usable queues are associated with a given upper stream */ static int sm_good_qs(sm_uqi_t *uqi) { return (sm_q_count(uqi, (WERROR_MODE|HANGUP_MODE))); } static int sm_cnt_oqs(sm_uqi_t *uqi) { return (sm_qs_without(uqi, (WERROR_MODE|HANGUP_MODE), (uint_t)FOROUTPUT)); } /* * Send an ioctl downstream and remember that it was sent so that * its response can be caught on the way back up. */ static void sm_issue_ioctl(void *arg) { sm_lqi_t *lqi = arg; uint_t cmdflag = 0; queue_t *q = SM_WQ(lqi); int iocmd, size; LOCK_UNIT(lqi); lqi->sm_bid = 0; if ((lqi->sm_flags & (WERROR_MODE|HANGUP_MODE)) == 0 && (lqi->sm_flags & (WANT_CDSTAT|WANT_TCSET))) { mblk_t *pioc; if (lqi->sm_flags & WANT_TCSET) { lqi->sm_flags &= ~WANT_TCSET; iocmd = TCSETS; cmdflag = WANT_TCSET; } else if (lqi->sm_flags & WANT_SC) { lqi->sm_flags &= ~WANT_SC; iocmd = TIOCGSOFTCAR; cmdflag = WANT_SC; } else if (lqi->sm_flags & WANT_CD) { lqi->sm_flags &= ~WANT_CD; iocmd = TIOCMGET; } else if (lqi->sm_flags & WANT_CL) { lqi->sm_flags &= ~WANT_CL; iocmd = TCGETS; cmdflag = WANT_CL; } else { UNLOCK_UNIT(lqi); return; } if (pioc = mkiocb(iocmd)) { if (cmdflag == WANT_TCSET) { pioc->b_cont = sm_allocb(sizeof (struct termios), BPRI_MED); if (pioc->b_cont == 0) { freemsg(pioc); pioc = 0; } else { struct termios *tc = (struct termios *) pioc->b_cont->b_wptr; bzero((caddr_t)tc, sizeof (struct termios)); tc->c_cflag = lqi->sm_ttycommon-> t_cflag; pioc->b_cont->b_rptr = pioc->b_cont->b_wptr; pioc->b_cont->b_wptr += sizeof (struct termios); } size = sizeof (struct iocblk) + sizeof (struct termios); } else size = sizeof (struct iocblk); } else size = sizeof (struct iocblk); if (pioc != 0) { lqi->sm_piocid = ((struct iocblk *)pioc->b_rptr)-> ioc_id; lqi->sm_flags |= SM_IOCPENDING; /* lqi->sm_flags |= cmdflag; */ UNLOCK_UNIT(lqi); (void) putq(q, pioc); } else { UNLOCK_UNIT(lqi); lqi->sm_bid = qbufcall(WR(q), size, BPRI_MED, sm_issue_ioctl, lqi); } } else UNLOCK_UNIT(lqi); } /* * Associate one of the drivers minor nodes with a serial device. */ int sm_associate(int unit, sm_lqi_t *plqi, ulong_t tag, uint_t ioflag, char *dp) { sm_uqi_t *uqi; int rval = 0; sm_dbg('Y', ("sm_associate(%d, %d, %d): ", (plqi) ? plqi->sm_linkid : 0, unit, ioflag)); /* * Check the data is valid. * Associate a lower queue with a logical unit. */ if (unit < 0 || unit >= NLUNITS || plqi == 0 || (uqi = get_uqi(sm_ssp, unit)) == 0) { sm_dbg('@', (" invalid: lqi=0x%p lui=0x%p:", plqi, uqi)); rval = EINVAL; } else { if ((ioflag & FORIO) == 0) ioflag = FORIO; LOCK_UNIT(plqi); if (plqi->sm_uqi) { if (plqi->sm_uqi->sm_lunit == unit) { if ((ioflag & (uint_t)FORIO) != 0) plqi->sm_ioflag = (ioflag & (uint_t)FORIO); rval = 0; } else { sm_dbg('@', ("already associated with unit %d:", plqi->sm_uqi->sm_lunit)); rval = EINVAL; } } else { LOCK_UNIT(uqi); if ((ioflag & (uint_t)FORIO) != 0) plqi->sm_ioflag = (ioflag & (uint_t)FORIO); plqi->sm_ttycommon->t_cflag = uqi->sm_ttycommon-> t_cflag; plqi->sm_ttycommon->t_flags = uqi->sm_ttycommon-> t_flags; plqi->sm_uqi = uqi; plqi->sm_mbits = 0; plqi->sm_tag = tag; if (*dp == '/') (void) strncpy(plqi->sm_path, dp, MAXPATHLEN); else *(plqi->sm_path) = '\0'; plqi->sm_flags |= WANT_TCSET; #ifdef ADD2FRONT plqi->sm_nlqi = uqi->sm_lqs; uqi->sm_lqs = plqi; #else plqi->sm_nlqi = 0; if (uqi->sm_lqs) { sm_lqi_t *lq; for (lq = uqi->sm_lqs; lq->sm_nlqi; lq = lq->sm_nlqi) { } lq->sm_nlqi = plqi; } else uqi->sm_lqs = plqi; #endif uqi->sm_nlqs++; (void) ttymux_device_init(plqi); UNLOCK_UNIT(uqi); rval = 0; /* * Everything looks good so it's now ok to enable lower * queue processing. * Note the lower queue should be enabled as soon as * I_PLINK returns (used in sm_get_ttymodes etc). * Schedule ioctls to obtain the terminal settings. */ if ((uqi->sm_flags & FULLY_OPEN) || uqi->sm_waitq) plqi->sm_uqflags |= SM_UQVALID; qenable(SM_RQ(plqi)); if (plqi->sm_flags & (WANT_CDSTAT|WANT_TCSET)) { /* * Bypass the lower half of the driver (hence * no qwriter) and apply the current termio * settings on the lower stream. */ UNLOCK_UNIT(plqi); if (plqi->sm_bid) { qunbufcall(SM_WQ(plqi), plqi->sm_bid); plqi->sm_bid = 0; } /* * Only set cflags on the lower q if we know * the settings on any other lower queue. */ sm_issue_ioctl(plqi); LOCK_UNIT(plqi); } } UNLOCK_UNIT(plqi); } sm_dbg('Y', ("sm_associate: rval=%d.\n", rval)); return (rval); } /* * Break an association between one of the driver's minor nodes and * a serial device. */ int sm_disassociate(int unit, sm_lqi_t *plqi, ulong_t tag) { sm_uqi_t *uqi; int rval = 0; sm_dbg('Y', ("sm_disassociate: link %d, unit %d: ", (plqi) ? plqi->sm_linkid : 0, unit)); /* * Check the data is valid. * Disassociate a lower queue with a logical unit. */ if (unit < 0 || unit >= NLUNITS || plqi == 0 || (uqi = get_uqi(sm_ssp, unit)) == 0) { sm_dbg('@', ("invalid: lqi=0x%p lui=0x%p", plqi, uqi)); rval = EINVAL; } else { LOCK_UNIT(plqi); if (plqi->sm_uqi == NULL) { sm_dbg('@', ("unit not associated")); rval = EINVAL; } else if (plqi->sm_uqi->sm_lunit != unit) { sm_dbg('@', ("unit and linkid not related", plqi->sm_uqi->sm_lunit)); rval = EINVAL; } else if (plqi->sm_tag != tag) { sm_dbg('@', ("Invalid tag for TTYMUX_DISASSOC ioctl\n")); rval = EPERM; } else { sm_dbg('Y', ("disassociating ")); (void) ttymux_device_fini(plqi); /* * Indicate that carrier status is no * longer required and that the upper * queue should not be used by plqi */ plqi->sm_flags &= ~(WANT_CDSTAT|WANT_TCSET); plqi->sm_uqflags &= ~(SM_UQVALID|SM_OBPCNDEV); plqi->sm_ioflag = 0u; sm_lqifree(plqi); rval = 0; } UNLOCK_UNIT(plqi); } sm_dbg('Y', (" rval=%d.\n", rval)); return (rval); } /* * Streams helper routines; */ /* * Schedule a qbufcall for an upper queue. * Must be called within the perimiter of the parameter q. * fn must reenable the q. * Called: * whenever a message must be placed on multiple queues and allocb fails; */ static void sm_sched_uqcb(queue_t *q, int memreq, int pri, void (*fn)()) { sm_uqi_t *uqi = q->q_ptr; if (uqi->sm_ttybid != 0) qunbufcall(q, uqi->sm_ttybid); noenable(q); uqi->sm_ttybid = qbufcall(q, memreq, pri, fn, uqi); } /* * qbufcall routine to restart the queues when memory is available. */ static void sm_reenable_q(sm_uqi_t *uqi) { queue_t *wq = SM_WQ(uqi); if ((uqi->sm_flags & SM_STOPPED) == 0) { enableok(wq); qenable(wq); } } /* * Place a message on the write queue of each stream associated with * the given upper stream. */ static void sm_senddown(sm_uqi_t *uqi) { sm_lqi_t *lqi; for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { if (lqi->sm_mp != 0) { putnext(SM_WQ(lqi), lqi->sm_mp); lqi->sm_mp = 0; } } } /* * For each lower device that should receive a write message duplicate * the message block. */ static int sm_dupmsg(sm_uqi_t *uqi, mblk_t *mp) { sm_lqi_t *lqi; mblk_t *origmp = mp; for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { lqi->sm_mp = 0; if (lqi->sm_flags & WERROR_MODE) { continue; } if ((lqi->sm_ioflag & (uint_t)FOROUTPUT) == 0) { if (DB_TYPE(mp) == M_DATA) continue; } if (lqi->sm_nlqi == 0) { lqi->sm_mp = mp; origmp = NULL; } else if ((lqi->sm_mp = sm_copymsg(mp)) == 0) { sm_lqi_t *flqi; for (flqi = uqi->sm_lqs; flqi != lqi; flqi = flqi->sm_nlqi) { if (lqi->sm_mp) { /* must have been sm_copymsg */ sm_freemsg(lqi->sm_mp); lqi->sm_mp = 0; } } return (sm_cnt_oqs(uqi) * msgdsize(mp)); } } if (origmp != NULL) freemsg(origmp); return (0); } /* * Return 1 if all associated lower devices have room for another message * otherwise return 0. */ static int sm_cansenddown(sm_uqi_t *uqi) { register sm_lqi_t *lqi; if (uqi->sm_lqs == 0) return (0); for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { if ((lqi->sm_flags & WERROR_MODE) == 0 && canputnext(SM_WQ(lqi)) == 0) return (0); } return (1); } /* * Put a message down all associated lower queues. * Return 1 if the q function was called. */ static int sm_putqs(queue_t *q, mblk_t *mp, int (*qfn)()) { register sm_uqi_t *uqi = (sm_uqi_t *)q->q_ptr; register int memreq; int pri = (DB_TYPE(mp) < QPCTL) ? BPRI_MED : BPRI_HI; int rval = 0; if (uqi->sm_lqs == 0 || (uqi->sm_flags & WERROR_MODE)) { sm_dbg('Q', ("sm_putqs: freeing (0x%p 0x%p).\n", uqi->sm_lqs, uqi->sm_flags)); freemsg(mp); } else if (pri != BPRI_HI && sm_cansenddown(uqi) == 0) { /* a lower q is flow controlled */ (void) qfn(q, mp); rval = 1; } else if ((memreq = sm_dupmsg(uqi, mp)) == 0) { sm_senddown(uqi); } else { sm_log("sm_putqs: msg 0x%x - can't alloc %d bytes (pri %d).\n", DB_TYPE(mp), memreq, pri); sm_sched_uqcb(q, memreq, pri, sm_reenable_q); (void) qfn(q, mp); rval = 1; } return (rval); } /* * sm_reply - send an ioctl reply back up the queue. */ static void sm_reply(queue_t *q, mblk_t *mp, uchar_t type, int error) { struct iocblk *iocbp; iocbp = (struct iocblk *)mp->b_rptr; DB_TYPE(mp) = type; iocbp->ioc_count = 0; iocbp->ioc_error = error; qreply(q, mp); } /* * Service a streams link and unlink requests. */ static void sm_link_req(queue_t *wq, mblk_t *mp) { struct linkblk *linkp; int rval; int cmd; sm_lqi_t *plqi; ASSERT(DB_TYPE(mp) == M_IOCTL); cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd; switch (cmd) { case I_LINK: case I_PLINK: sm_dbg('G', ("sm_link_req: M_IOCTL %x (I_PLINK).\n", cmd)); linkp = (struct linkblk *)mp->b_cont->b_rptr; /* * 1. Sanity check the link block. * 2. Validate that the queue is not already linked * (and resources available). * 3. Validate that the lower queue is not associated with * a logical unit. * 4. Remember that this lower queue is linked to the driver. */ if ((linkp == NULL) || (MBLKL(mp) < sizeof (*linkp)) || linkp->l_qbot == NULL) { sm_dbg('I', ("sm_link_req: invalid link block.\n")); rval = EINVAL; } else if ((plqi = get_lqi_byq(linkp->l_qbot)) == 0) { sm_dbg('I', ("sm_link_req: out of resources.\n")); rval = EBUSY; /* out of resources */ } else if (plqi->sm_uqi) { UNLOCK_UNIT(plqi); /* was aquired by get_lqi_byq */ sm_dbg('I', ("sm_link_req: already associated.\n")); rval = EBUSY; /* already linked */ } else { SM_WQ(plqi) = linkp->l_qbot; SM_RQ(plqi) = OTHERQ(linkp->l_qbot); linkp->l_qbot->q_ptr = OTHERQ(linkp->l_qbot)->q_ptr = plqi; plqi->sm_linkid = linkp->l_index; UNLOCK_UNIT(plqi); /* was aquired by get_lqi_byq */ sm_dbg('H', ("sm_link_req: linkid = %d.\n", linkp->l_index)); sm_setdip(linkp->l_qbot, plqi); plqi->sm_ttycommon->t_flags = 0; plqi->sm_ttycommon->t_cflag = 0; plqi->sm_mbits = 0; (void) ttymux_device_init(plqi); rval = 0; } break; case I_UNLINK: case I_PUNLINK: sm_dbg('G', ("sm_link_req: M_IOCTL (I_PUNLINK).\n")); linkp = (struct linkblk *)mp->b_cont->b_rptr; if ((linkp == NULL) || (MBLKL(mp) < sizeof (*linkp)) || linkp->l_qbot == NULL) { rval = EINVAL; } else if ((plqi = get_lqi_byid(linkp->l_index)) == 0) { rval = EINVAL; } else { sm_uqi_t *uqi; int werrmode; /* * Mark the lower q as invalid. */ sm_dbg('G', ("I_PUNLINK: freeing link %d\n", linkp->l_index)); if (plqi->sm_bid) { qunbufcall(SM_RQ(plqi), plqi->sm_bid); plqi->sm_bid = 0; } if (plqi->sm_ttybid) { qunbufcall(SM_RQ(plqi), plqi->sm_ttybid); plqi->sm_ttybid = 0; } uqi = plqi->sm_uqi; (void) ttymux_device_fini(plqi); if (uqi) (void) sm_disassociate(uqi->sm_lunit, plqi, plqi->sm_tag); LOCK_UNIT(plqi); plqi->sm_piocid = 0; werrmode = (plqi->sm_flags & (WERROR_MODE|HANGUP_MODE)) ? 1 : 0; plqi->sm_mbits = 0; plqi->sm_flags = 0; ttycommon_close(plqi->sm_ttycommon); /* SM_RQ(plqi) = SM_WQ(plqi) = 0; */ plqi->sm_ttycommon->t_flags = 0; plqi->sm_ttycommon->t_cflag = 0; plqi->sm_ttycommon->t_iflag = 0; plqi->sm_linkid = 0; plqi->sm_dev = NODEV; plqi->sm_hadkadbchar = 0; plqi->sm_nachar = sm_ssp->sm_abs; UNLOCK_UNIT(plqi); if (uqi && werrmode && (uqi->sm_flags & FULLY_OPEN) && sm_uwq_error(uqi) && putnextctl(SM_RQ(uqi), M_HANGUP) == 0) { sm_log("sm_link_req: putnextctl(M_HANGUP)" " failed.\n"); } rval = 0; } break; default: rval = EINVAL; } sm_reply(wq, mp, (rval) ? M_IOCNAK : M_IOCACK, rval); } static int sm_getiocinfo(mblk_t *mp, struct sm_iocinfo *info) { switch (DB_TYPE(mp)) { case M_COPYOUT: info->sm_id = ((struct copyreq *)mp->b_rptr)->cq_id; info->sm_cmd = ((struct copyreq *)mp->b_rptr)->cq_cmd; info->sm_data = (((struct copyreq *)mp->b_rptr)->cq_size && mp->b_cont) ? (void *)mp->b_cont->b_rptr : 0; break; case M_COPYIN: info->sm_id = ((struct copyresp *)mp->b_rptr)->cp_id; info->sm_cmd = ((struct copyresp *)mp->b_rptr)->cp_cmd; info->sm_data = 0; break; case M_IOCACK: info->sm_id = ((struct iocblk *)mp->b_rptr)->ioc_id; info->sm_cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd; /* the se driver has bug so we cannot use ioc_count */ info->sm_data = (((struct iocblk *)mp->b_rptr)-> ioc_error == 0 && mp->b_cont) ? (void *)mp->b_cont->b_rptr : 0; break; case M_IOCNAK: info->sm_id = ((struct iocblk *)mp->b_rptr)->ioc_id; info->sm_cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd; info->sm_data = 0; break; case M_IOCDATA: info->sm_id = ((struct copyresp *)mp->b_rptr)->cp_id; info->sm_cmd = ((struct copyresp *)mp->b_rptr)->cp_cmd; info->sm_data = (((struct copyresp *)mp->b_rptr)-> cp_rval == 0 && mp->b_cont) ? (void *)mp->b_cont->b_rptr : 0; break; case M_IOCTL: info->sm_id = ((struct iocblk *)mp->b_rptr)->ioc_id; info->sm_cmd = ((struct iocblk *)mp->b_rptr)->ioc_cmd; info->sm_data = 0; break; default: return (EINVAL); } return (0); } /* * Record the termio settings that have been set on the upper stream */ static int sm_update_ttyinfo(mblk_t *mp, sm_uqi_t *uqi) { int err; struct sm_iocinfo info; if ((err = sm_getiocinfo(mp, &info)) != 0) return (err); switch (info.sm_cmd) { case TIOCSPPS: case TIOCGPPS: case TIOCGPPSEV: return (ENOTSUP); case TIOCGWINSZ: case TIOCSWINSZ: break; case TCSBRK: case TIOCSBRK: case TIOCCBRK: break; case TCSETSF: uqi->sm_flags |= FLUSHR_PEND; sm_dbg('I', ("TCSETSF: FLUSH is pending\n")); /*FALLTHROUGH*/ case TCSETSW: case TCSETS: case TCGETS: if (info.sm_data != 0) { ((struct termios *)info.sm_data)->c_cflag &= (tcflag_t)(~uqi->sm_cmask); uqi->sm_ttycommon->t_cflag = ((struct termios *)info.sm_data)->c_cflag; } break; case TCSETAF: sm_dbg('I', ("TCSETAF: FLUSH is pending\n")); uqi->sm_flags |= FLUSHR_PEND; /*FALLTHROUGH*/ case TCSETAW: case TCSETA: case TCGETA: if (info.sm_data != 0) { ((struct termio *)info.sm_data)->c_cflag &= (tcflag_t)(~uqi->sm_cmask); uqi->sm_ttycommon->t_cflag = (tcflag_t)((struct termio *)info.sm_data)->c_cflag; } break; case TIOCSSOFTCAR: case TIOCGSOFTCAR: if (info.sm_data != 0) { if (*(int *)info.sm_data == 1) uqi->sm_ttycommon->t_flags |= TS_SOFTCAR; else uqi->sm_ttycommon->t_flags &= ~TS_SOFTCAR; } break; case TIOCMSET: case TIOCMGET: if (info.sm_data != 0) uqi->sm_mbits = *(int *)info.sm_data; break; case TIOCMBIS: if (info.sm_data != 0) uqi->sm_mbits |= *(int *)info.sm_data; break; case TIOCMBIC: if (info.sm_data != 0) uqi->sm_mbits &= ~(*(int *)info.sm_data); break; default: return (EINVAL); /* NOTREACHED */ } /* end switch cmd */ if ((uqi->sm_mbits & TIOCM_CD) || (uqi->sm_ttycommon->t_flags & TS_SOFTCAR) || (uqi->sm_ttycommon->t_cflag & CLOCAL)) uqi->sm_flags |= SM_CARON; else uqi->sm_flags &= ~SM_CARON; return (0); } /* * SECTION * STREAM's interface to the OS. * Routines directly callable from the OS. */ /* * Processes high priority messages comming from modules above the * multiplexor. * Return 1 if the queue was disabled. */ static int sm_hp_uwput(queue_t *wq, mblk_t *mp) { sm_uqi_t *uqi = (sm_uqi_t *)(wq->q_ptr); int rval = 0; sm_lqi_t *plqi; int msgtype = DB_TYPE(mp); switch (msgtype) { case M_FLUSH: /* * How to flush the bottom half: * putctl1(SM_WQ(plqi), *mp->b_rptr) * will work on the bottom half but if FLUSHR is set * when is the right time to flush the upper read queue. * * Could set uqi->sm_flags & WANT_FLUSH but then what happens * if FLUSHR is set and the driver sends up a FLUSHR * before it handles the current FLUSHR request * (if only there was an id for the message that could * be matched when it returns back from the drivers. * * Thus I'm going by the book - the bottom half acts like * a stream head and turns around FLUSHW back down to * the driver (see lrput). The upper half acts like a * driver and turns around FLUSHR: */ sm_dbg('I', ("sm_hp_uwput: FLUSH request 0x%x\n", *mp->b_rptr)); /* flush the upper write queue */ if (*mp->b_rptr & FLUSHW) flushq(wq, FLUSHDATA); /* * flush each associated lower write queue * and pass down the driver (ignore the FLUSHR and deal with * it when it comes back up the read side. */ for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) { if ((plqi->sm_flags & WERROR_MODE) == 0 && SM_WQ(plqi)) { sm_dbg('I', ("flush lq 0x%p\n", SM_WQ(plqi))); if (*mp->b_rptr & FLUSHW) flushq(SM_WQ(plqi), FLUSHDATA); (void) putnextctl1(SM_WQ(plqi), M_FLUSH, *mp->b_rptr); } } break; case M_STARTI: for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) { plqi->sm_flags &= ~SM_ISTOPPED; if ((plqi->sm_flags & WERROR_MODE) == 0) (void) putnextctl(SM_WQ(plqi), msgtype); } break; case M_STOPI: for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) { plqi->sm_flags |= SM_ISTOPPED; if ((plqi->sm_flags & WERROR_MODE) == 0) (void) putnextctl(SM_WQ(plqi), msgtype); } break; case M_STOP: /* must never be queued */ uqi->sm_flags |= SM_STOPPED; noenable(wq); for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) if ((plqi->sm_flags & WERROR_MODE) == 0) (void) putnextctl(SM_WQ(plqi), msgtype); rval = 1; break; case M_START: /* never be queued */ uqi->sm_flags &= ~SM_STOPPED; enableok(wq); qenable(wq); for (plqi = uqi->sm_lqs; plqi != 0; plqi = plqi->sm_nlqi) if ((plqi->sm_flags & WERROR_MODE) == 0) (void) putnextctl(SM_WQ(plqi), msgtype); break; case M_PCSIG: case M_COPYOUT: case M_COPYIN: case M_IOCACK: case M_IOCNAK: /* Wrong direction for message */ break; case M_READ: break; case M_PCPROTO: case M_PCRSE: default: sm_dbg('I', ("sm_hp_uwput: default case %d.\n", msgtype)); break; } /* end switch on high pri message type */ freemsg(mp); return (rval); } static int sm_default_uwioctl(queue_t *wq, mblk_t *mp, int (*qfn)()) { int err; struct iocblk *iobp; sm_uqi_t *uqi; uqi = (sm_uqi_t *)(wq->q_ptr); iobp = (struct iocblk *)mp->b_rptr; switch (iobp->ioc_cmd) { case TIOCEXCL: case TIOCNXCL: case TIOCSTI: (void) ttycommon_ioctl(uqi->sm_ttycommon, wq, mp, &err); sm_reply(wq, mp, err ? M_IOCACK : M_IOCNAK, err); return (0); default: break; } err = sm_update_ttyinfo(mp, uqi); if (err) { iobp->ioc_error = err; mp->b_datap->db_type = M_IOCNAK; qreply(wq, mp); return (0); } /* * If uqi->sm_siocdata.sm_iocid just overwrite it since the stream * head will have timed it out */ uqi->sm_siocdata.sm_iocid = iobp->ioc_id; uqi->sm_siocdata.sm_acked = 0; uqi->sm_siocdata.sm_nacks = sm_good_qs(uqi); uqi->sm_siocdata.sm_acnt = 0; uqi->sm_siocdata.sm_policy = uqi->sm_policy; uqi->sm_siocdata.sm_flags = 0; sm_dbg('Z', (" want %d acks for id %d.\n", uqi->sm_siocdata.sm_nacks, iobp->ioc_id)); return (sm_putqs(wq, mp, qfn)); } /* * * sm_uwput - put function for an upper STREAM write. */ static int sm_uwput(queue_t *wq, mblk_t *mp) { sm_uqi_t *uqi; uchar_t msgtype; int cmd; struct iocblk *iobp; uqi = (sm_uqi_t *)(wq->q_ptr); msgtype = DB_TYPE(mp); ASSERT(uqi != 0 && sm_ssp != 0); if (msgtype >= QPCTL && msgtype != M_IOCDATA) { (void) sm_hp_uwput(wq, mp); return (0); } switch (DB_TYPE(mp)) { case M_DATA: case M_DELAY: case M_BREAK: default: (void) sm_putqs(wq, mp, putq); break; case M_CTL: if (((struct iocblk *)mp->b_rptr)->ioc_cmd == MC_CANONQUERY) { (void) putnextctl1(OTHERQ(wq), M_CTL, MC_NOCANON); } freemsg(mp); break; case M_IOCDATA: /* not handled as high pri because may need to putbq */ sm_dbg('M', ("sm_uwput(M_IOCDATA)\n")); /*FALLTHROUGH*/ case M_IOCTL: cmd = (msgtype == M_IOCDATA) ? ((struct copyresp *)mp->b_rptr)->cp_cmd : ((struct iocblk *)mp->b_rptr)->ioc_cmd; iobp = (struct iocblk *)mp->b_rptr; iobp->ioc_rval = 0; sm_dbg('M', ("sm_uwput(M_IOCTL:%d)\n", cmd)); switch (cmd) { case CONSGETABORTENABLE: iobp->ioc_error = ttymux_abort_ioctl(mp); DB_TYPE(mp) = iobp->ioc_error ? M_IOCNAK : M_IOCACK; qreply(wq, mp); break; case CONSSETABORTENABLE: iobp->ioc_error = secpolicy_sys_config(iobp->ioc_cr, B_FALSE) != 0 ? EPERM : ttymux_abort_ioctl(mp); DB_TYPE(mp) = iobp->ioc_error ? M_IOCNAK : M_IOCACK; qreply(wq, mp); break; case TTYMUX_SETABORT: if (secpolicy_sys_config(iobp->ioc_cr, B_FALSE) != 0) { iobp->ioc_error = EPERM; DB_TYPE(mp) = M_IOCNAK; qreply(wq, mp); break; } /*FALLTHROUGH*/ case TTYMUX_GETABORT: case TTYMUX_GETABORTSTR: case TTYMUX_ASSOC: case TTYMUX_DISASSOC: case TTYMUX_SETCTL: case TTYMUX_GETLINK: case TTYMUX_CONSDEV: case TTYMUX_GETCTL: case TTYMUX_LIST: (void) sm_ioctl_cmd(uqi, mp); qreply(wq, mp); break; case I_LINK: case I_PLINK: case I_UNLINK: case I_PUNLINK: qwriter(wq, mp, sm_link_req, PERIM_OUTER); break; case TCSETSW: case TCSETSF: case TCSETAW: case TCSETAF: case TCSBRK: if (wq->q_first) { sm_dbg('A', ("sm_uwput: TCSET-> on srv q.\n")); /* keep message order intact */ (void) putq(wq, mp); break; } /*FALLTHROUGH*/ default: (void) sm_default_uwioctl(wq, mp, putq); break; } break; /* M_IOCTL */ } /* end switch on message type */ return (0); } /* * sm_uwsrv - service function for an upper STREAM write. * 'sm_uwsrv' takes a q parameter. The q parameter specifies the queue * which is to be serviced. This function reads the messages which are on * this service queue and passes them to the appropriate lower driver queue. */ static int sm_uwsrv(queue_t *q) { mblk_t *mp; sm_uqi_t *uqi = (sm_uqi_t *)(q->q_ptr); int msgtype; ASSERT(q == SM_WQ(uqi)); /* * Empty the queue unless explicitly stopped. */ while (mp = getq(q)) { msgtype = DB_TYPE(mp); if (msgtype >= QPCTL && msgtype != M_IOCDATA) if (sm_hp_uwput(q, mp)) { sm_dbg('T', ("sm_uwsrv: flowcontrolled.\n")); break; /* indicates that the is disabled */ } else continue; if (uqi->sm_flags & SM_STOPPED) { (void) putbq(q, mp); sm_dbg('T', ("sm_uwsrv: SM_STOPPED.\n")); break; } /* * Read any ttycommon data that may * change (TS_SOFTCAR, CREAD, etc.). */ switch (DB_TYPE(mp)) { case M_IOCTL: case M_IOCDATA: if (sm_default_uwioctl(q, mp, putbq)) return (0); break; default: if (sm_putqs(q, mp, putbq)) return (0); } } return (0); } /* * Lower write side service routine used for backenabling upstream * flow control. */ static int sm_lwsrv(queue_t *q) { sm_lqi_t *lqi = (sm_lqi_t *)q->q_ptr; queue_t *uwq; LOCK_UNIT(lqi); if (lqi->sm_uqflags & SM_UQVALID) { /* * It's safe to lock uqi since lwsrv runs asynchronously * with the upper write routines so this cannot be an * upper half thread. While holding the lqi lock and * if SM_UQVALID is set we are guaranteed that * lqi->sm_uqi will be valid. */ sm_dbg('I', ("sm_lwsrv: re-enabling upper queue.\n")); uwq = SM_WQ(lqi->sm_uqi); UNLOCK_UNIT(lqi); qenable(uwq); } else { UNLOCK_UNIT(lqi); } return (0); } /* * Upper read queue ioctl response handler for messages * passed from the lower half of the driver. */ static int sm_uriocack(queue_t *rq, mblk_t *mp) { sm_uqi_t *uqi = (sm_uqi_t *)rq->q_ptr; int err, flag; sm_iocdata_t *iodp; struct sm_iocinfo info; if ((err = sm_getiocinfo(mp, &info)) != 0) { sm_dbg('I', ("Unknown ioctl response\n")); return (err); } if (info.sm_id == uqi->sm_piocdata.sm_iocid) { iodp = &uqi->sm_piocdata; } else if (info.sm_id == uqi->sm_siocdata.sm_iocid) { iodp = &uqi->sm_siocdata; } else { sm_log("Unexpected ioctl response\n"); sm_dbg('I', ("Unexpected ioctl response (id %d)\n", info.sm_id)); /* * If the response is sent up it will result in * duplicate ioctl responses. The ioctl has probably been * timed out by the stream head so dispose of the response * (since it has arrived too late. */ goto out; } flag = SM_COPYIN; switch (DB_TYPE(mp)) { case M_COPYOUT: flag = SM_COPYOUT; /*FALLTHRU*/ case M_COPYIN: if (iodp->sm_flags & flag) goto out; iodp->sm_flags |= flag; break; case M_IOCACK: iodp->sm_ackcnt += 1; iodp->sm_acnt += 1; if (iodp->sm_policy == FIRSTACK) { if (iodp->sm_acnt == iodp->sm_nacks) iodp->sm_iocid = 0; if (iodp->sm_acnt == 1) iodp->sm_acked = 1; else goto out; } else { if (iodp->sm_acnt == iodp->sm_nacks) { iodp->sm_iocid = 0; iodp->sm_acked = 1; } else goto out; } break; case M_IOCNAK: iodp->sm_nakcnt += 1; iodp->sm_acnt += 1; if (iodp->sm_acnt == iodp->sm_nacks) { iodp->sm_iocid = 0; if (iodp->sm_acked == 0) { iodp->sm_acked = 1; break; } } goto out; default: goto out; } /* * Merge the tty settings each of the associated lower streams. */ if (info.sm_data) (void) sm_update_ttyinfo(mp, uqi); if (iodp == &uqi->sm_piocdata) { if (iodp->sm_iocid == 0) { uqi->sm_flags &= ~SM_IOCPENDING; } } else { sm_dbg('I', ("sm_uriocack: forwarding response for %d.\n", info.sm_id)); putnext(rq, mp); return (0); } out: sm_dbg('I', ("sm_uriocack: freeing response for %d.\n", info.sm_id)); freemsg(mp); return (0); } /* * Transfer a message from the lower read side of the multiplexer onto * the associated upper stream. */ static int sm_ursendup(queue_t *q, mblk_t *mp) { sm_uqi_t *uqi = (sm_uqi_t *)q->q_ptr; if (!canputnext(q) && DB_TYPE(mp) < QPCTL) { sm_dbg('I', ("sm_ursendup: flow controlled.\n")); return (1); } switch (DB_TYPE(mp)) { case M_COPYIN: case M_COPYOUT: case M_IOCACK: case M_IOCNAK: (void) sm_uriocack(q, mp); break; case M_HANGUP: if (sm_uwq_error(uqi)) { /* there are no usable lower q's */ uqi->sm_flags &= ~SM_CARON; putnext(q, mp); } else { /* there are still usable q's - don't send up */ freemsg(mp); } break; case M_ERROR: if (sm_uwq_error(uqi)) { /* there are no usable lower q's */ uqi->sm_flags &= ~SM_CARON; putnext(q, mp); } else if (*mp->b_rptr == NOERROR) { /* the error has cleared */ uqi->sm_flags &= ~ERROR_MODE; putnext(q, mp); } else { /* there are still usable q's - don't send up */ freemsg(mp); } break; case M_FLUSH: flushq(q, FLUSHDATA); putnext(q, mp); /* time to use FLUSHR_PEND flag */ break; case M_CTL: /* wrong direction - must have come from sm_close */ uqi->sm_flags |= SM_CLOSE; sm_dbg('I', ("sm_ursrv: had SM_CLOSE.\n")); freemsg(mp); break; case M_UNHANGUP: /* just pass them all up - they're harmless */ uqi->sm_flags |= SM_CARON; /* FALLTHROUGH */ default: putnext(q, mp); break; } return (0); } /* * sm_urput - put function for a lower STREAM read. */ static int sm_urput(queue_t *q, mblk_t *mp) { if (sm_ursendup(q, mp) != 0) (void) putq(q, mp); return (0); } /* * Upper read side service routine. * Read side needs to be fast so only check for duplicate M_IOCTL acks. */ static int sm_ursrv(queue_t *q) { sm_uqi_t *uqi = (sm_uqi_t *)q->q_ptr; mblk_t *mp; int flags = uqi->sm_flags; while ((mp = getq(q))) { if (sm_ursendup(q, mp) != 0) { sm_dbg('I', ("sm_ursrv: flow controlled.\n")); (void) putbq(q, mp); uqi->sm_flags |= WANT_RENB; break; } } /* * If the q service was called because it was no longer * flow controled then enable each of the driver queues. */ if ((flags & WANT_RENB) && !(uqi->sm_flags & WANT_RENB)) { sm_lqi_t *lqi; queue_t *drq; /* read q of linked driver */ uqi->sm_flags &= ~WANT_RENB; for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { drq = SM_RQ(lqi)->q_next; if (drq && drq->q_first != 0) qenable(drq); } } return (0); } /* * Check a message sent from a linked device for abort requests and * for flow control. */ static int sm_lrmsg_check(queue_t *q, mblk_t *mp) { sm_lqi_t *lqi = (sm_lqi_t *)q->q_ptr; switch (DB_TYPE(mp)) { case M_DATA: LOCK_UNIT(lqi); /* * check for abort - only allow abort on I/O consoles * known to OBP - * fix it when we do polled io */ if ((lqi->sm_ioflag & (uint_t)FORINPUT) == 0) { freemsg(mp); UNLOCK_UNIT(lqi); return (1); } if ((lqi->sm_uqflags & SM_OBPCNDEV) && lqi->sm_ctrla_abort_on && abort_enable == KIOCABORTALTERNATE) { uchar_t *rxc; boolean_t aborted = B_FALSE; for (rxc = mp->b_rptr; rxc != mp->b_wptr; rxc++) if (*rxc == *lqi->sm_nachar) { lqi->sm_nachar++; if (*lqi->sm_nachar == '\0') { abort_sequence_enter( (char *)NULL); lqi->sm_nachar = sm_ssp->sm_abs; aborted = B_TRUE; } } else lqi->sm_nachar = (*rxc == *sm_ssp-> sm_abs) ? sm_ssp-> sm_abs + 1 : sm_ssp->sm_abs; if (aborted) { freemsg(mp); UNLOCK_UNIT(lqi); return (1); } } UNLOCK_UNIT(lqi); break; case M_BREAK: /* we'll eventually see this as a flush */ LOCK_UNIT(lqi); /* * Only allow abort on OBP devices. When polled I/O is * supported allow abort on any console device. * Parity errors are reported upstream as breaks so * ensure that there is no data in the message before * deciding whether to abort. */ if ((lqi->sm_uqflags & SM_OBPCNDEV) && /* console stream */ (mp->b_wptr - mp->b_rptr == 0 && msgdsize(mp) == 0)) { /* not due to parity */ if (lqi->sm_break_abort_on && abort_enable != KIOCABORTALTERNATE) abort_sequence_enter((char *)NULL); freemsg(mp); UNLOCK_UNIT(lqi); return (1); } else { UNLOCK_UNIT(lqi); } break; default: break; } if (DB_TYPE(mp) >= QPCTL) return (0); LOCK_UNIT(lqi); /* lock out the upper half */ if ((lqi->sm_uqflags & SM_UQVALID) && SM_RQ(lqi->sm_uqi)) { UNLOCK_UNIT(lqi); if (!canput(SM_RQ(lqi->sm_uqi))) { sm_dbg('I', ("sm_lrmsg_check: flow controlled.\n")); (void) putq(q, mp); return (1); } } else { UNLOCK_UNIT(lqi); } return (0); } /* * sm_sendup - deliver a message to the upper read side of the multiplexer */ static int sm_sendup(queue_t *q, mblk_t *mp) { sm_lqi_t *lqi = (sm_lqi_t *)q->q_ptr; if (sm_ssp == NULL) { freemsg(mp); return (0); } /* * Check for CD status change messages from driver. * (Remark: this is an se driver thread running at soft interupt * priority and the waiters are in user context). */ switch (DB_TYPE(mp)) { case M_DATA: case M_BREAK: /* we'll eventually see this as a flush */ break; /* high priority messages */ case M_IOCACK: case M_IOCNAK: if ((lqi->sm_flags & SM_IOCPENDING) && lqi->sm_piocid == ((struct iocblk *)mp->b_rptr)->ioc_id) { freemsg(mp); lqi->sm_flags &= ~SM_IOCPENDING; sm_issue_ioctl(lqi); return (0); } break; case M_UNHANGUP: /* * If the driver can send an M_UNHANGUP it must be able to * accept messages from above (ie clear WERROR_MODE if set). */ sm_dbg('E', ("lrput: M_UNHANGUP\n")); lqi->sm_mbits |= TIOCM_CD; lqi->sm_flags &= ~(WERROR_MODE|HANGUP_MODE); break; case M_HANGUP: sm_dbg('E', ("lrput: MHANGUP\n")); lqi->sm_mbits &= ~TIOCM_CD; lqi->sm_flags |= (WERROR_MODE|HANGUP_MODE); break; case M_ERROR: sm_dbg('E', ("lrput: MERROR\n")); /* * Tell the driver to flush rd/wr queue if its read/write error. * if its a read/write error flush rq/wq (type in first bytes). */ if ((mp->b_wptr - mp->b_rptr) == 2) { uchar_t rw = 0; if (*mp->b_rptr == NOERROR) { /* not in error anymore */ lqi->sm_flags &= ~ERROR_MODE; lqi->sm_flags |= WANT_CD; } else { if (*mp->b_rptr != 0) { /* read error */ rw |= FLUSHR; lqi->sm_flags |= RERROR_MODE; } mp->b_rptr++; if (*mp->b_rptr != 0) { /* write error */ rw |= FLUSHW; lqi->sm_flags |= WERROR_MODE; } mp->b_rptr--; /* has next driver done qprocsoff */ if (rw && OTHERQ(q)->q_next != NULL) { (void) putnextctl1(OTHERQ(q), M_FLUSH, rw); } } } else if (*mp->b_rptr != 0 && OTHERQ(q)->q_next != NULL) { sm_dbg('E', ("lrput: old style MERROR (?)\n")); lqi->sm_flags |= (RERROR_MODE | WERROR_MODE); (void) putnextctl1(OTHERQ(q), M_FLUSH, FLUSHRW); } break; case M_PCSIG: case M_SIG: break; case M_COPYOUT: case M_COPYIN: break; case M_FLUSH: /* flush the read queue and pass on up */ flushq(q, FLUSHDATA); break; default: break; } LOCK_UNIT(lqi); /* lock out the upper half */ if (lqi->sm_uqflags & SM_UQVALID && SM_RQ(lqi->sm_uqi)) { UNLOCK_UNIT(lqi); (void) putq(SM_RQ(lqi->sm_uqi), mp); return (0); } else { sm_dbg('I', ("sm_sendup: uq not valid\n")); freemsg(mp); } UNLOCK_UNIT(lqi); return (0); } /* * sm_lrput - put function for a lower STREAM read. */ static int sm_lrput(queue_t *q, mblk_t *mp) { if (sm_lrmsg_check(q, mp) == 0) (void) sm_sendup(q, mp); return (0); } /* * sm_lrsrv - service function for the lower read STREAM. */ static int sm_lrsrv(queue_t *q) { mblk_t *mp; sm_dbg('I', ("sm_lrsrv: not controlled.\n")); while (mp = getq(q)) (void) sm_sendup(q, mp); return (0); } /* * Check whether a thread is allowed to open the requested device. */ static int sm_ok_to_open(sm_uqi_t *uqi, int protocol, cred_t *credp, int *abort_waiters) { int rval = 0; int proto; *abort_waiters = 0; switch (protocol) { case ASYNC_DEVICE: /* Standard async protocol */ if ((uqi->sm_protocol == NULL_PROTOCOL) || (uqi->sm_protocol == ASYN_PROTOCOL)) { /* * Lock out other incompatible protocol requests. */ proto = ASYN_PROTOCOL; rval = 0; } else rval = EBUSY; break; case OUTLINE: /* Outdial protocol */ if ((uqi->sm_protocol == NULL_PROTOCOL) || (uqi->sm_protocol == OUTD_PROTOCOL)) { proto = OUTD_PROTOCOL; rval = 0; } else if (uqi->sm_protocol == ASYN_PROTOCOL) { /* * check for dialout request on a line that is already * open for dial in: * kick off any thread that is waiting to fully open */ if (uqi->sm_flags & FULLY_OPEN) rval = EBUSY; else { proto = OUTD_PROTOCOL; *abort_waiters = 1; } } else rval = EBUSY; break; default: rval = ENOTSUP; } if (rval == 0 && (uqi->sm_ttycommon->t_flags & TS_XCLUDE) && secpolicy_excl_open(credp) != 0) { if (uqi->sm_flags & FULLY_OPEN) { rval = EBUSY; /* exclusive device already open */ } else { /* NB TS_XCLUDE cant be set during open so NOTREACHED */ /* force any waiters to yield TS_XCLUDE */ *abort_waiters = 1; } } if (rval == 0) uqi->sm_protocol = proto; sm_dbg('A', ("ok_to_open (0x%p, %d) proto=%d rval %d (wabort=%d)", uqi, protocol, uqi->sm_protocol, rval, *abort_waiters)); return (rval); } /* wait for memory to become available whilst performing a qwait */ /*ARGSUSED*/ static void dummy_callback(void *arg) {} /* ARGSUSED */ static int sm_dump_msg(queue_t *q, mblk_t *mp) { freemsg(mp); return (0); } /* * Wait for a message to arrive - must be called with exclusive * access at the outer perimiter. */ static int sm_qwait_sig(sm_uqi_t *uqi, queue_t *q) { int err; sm_dbg('C', ("sm_qwait_sig: waiting.\n")); uqi->sm_waitq = q; uqi->sm_nwaiters++; /* required by the close routine */ err = qwait_sig(q); if (--uqi->sm_nwaiters == 0) uqi->sm_waitq = 0; if (err == 0) err = EINTR; else if (q->q_ptr == 0) /* can happen if there are multiple waiters */ err = -1; else if (uqi->sm_flags & SM_CLOSE) { uqi->sm_flags &= ~SM_CLOSE; err = 1; /* a different protocol has closed its stream */ } else err = 0; /* was worth waiting for */ sm_dbg('C', ("sm_qwait_sig: rval %d\n", err)); return (err); } /* * Defer the opening of one the drivers devices until the state of each * associated lower stream is known. */ static int sm_defer_open(sm_uqi_t *uqi, queue_t *q) { uint_t cmdflags = WANT_CDSTAT; int err, nqs; while ((nqs = sm_good_qs(uqi)) == 0) { sm_dbg('C', ("sm_defer_open: no good qs\n")); if (err = sm_qwait_sig(uqi, q)) return (err); } while ((uqi->sm_flags & SM_CARON) == 0) { int iocmd; mblk_t *pioc; sm_dbg('C', ("sm_defer_open: flags 0x%x cmdflags 0x%x\n", uqi->sm_flags, cmdflags)); if (cmdflags == 0) { if (err = sm_qwait_sig(uqi, q)) return (err); continue; /* waiting for an M_UNHANGUP */ } else if (cmdflags & WANT_SC) { cmdflags &= ~WANT_SC; iocmd = TIOCGSOFTCAR; } else if (cmdflags & WANT_CD) { cmdflags &= ~WANT_CD; iocmd = TIOCMGET; } else if (cmdflags & WANT_CL) { cmdflags &= ~WANT_CL; iocmd = TCGETS; } if (uqi->sm_piocdata.sm_iocid == 0) { while ((pioc = mkiocb(iocmd)) == 0) { bufcall_id_t id = qbufcall(q, sizeof (struct iocblk), BPRI_MED, dummy_callback, 0); if (err = sm_qwait_sig(uqi, q)) { /* wait for the bufcall */ qunbufcall(q, id); return (err); } qunbufcall(q, id); } uqi->sm_flags |= SM_IOCPENDING; uqi->sm_piocdata.sm_iocid = ((struct iocblk *)pioc->b_rptr)->ioc_id; uqi->sm_piocdata.sm_acked = 0; uqi->sm_piocdata.sm_nacks = nqs; uqi->sm_piocdata.sm_acnt = 0; uqi->sm_piocdata.sm_ackcnt = uqi-> sm_piocdata.sm_nakcnt = 0; uqi->sm_piocdata.sm_policy = uqi->sm_policy; uqi->sm_piocdata.sm_flags = SM_INTERNALIOC; if (sm_putqs(WR(q), pioc, sm_dump_msg) != 0) { uqi->sm_piocdata.sm_iocid = 0; sm_log("sm_defer_open: bad putqs\n"); return (-1); } } sm_dbg('C', ("sm_defer_open: flags 0x%x\n", uqi->sm_flags)); while ((uqi->sm_flags & SM_CARON) == 0 && (uqi->sm_flags & SM_IOCPENDING) != 0) if (err = sm_qwait_sig(uqi, q)) return (err); sm_dbg('C', ("defer_open: uq flags 0x%x.\n", uqi->sm_flags)); } sm_dbg('C', ("defer_open: return 0.\n")); return (0); } static int sm_open(queue_t *rq, dev_t *devp, int flag, int sflag, cred_t *credp) { int ftstat; int unit; int protocol; sm_uqi_t *uqi; int abort_waiters; if (sm_ssp == NULL) return (ENXIO); /* * sflag = 0 => streams device. */ if (sflag != 0 || DEV_TO_UNIT(*devp) >= NLUNITS) { sm_dbg('C', ("open: sflag=%d or bad dev_t.\n", sflag)); return (ENXIO); } unit = DEV_TO_UNIT(*devp); protocol = DEV_TO_PROTOBITS(*devp); uqi = get_uqi(sm_ssp, unit); sm_dbg('C', ("open(0x%p, %d, 0x%x) :- unit=%d, proto=%d, uqi=0x%p\n", rq, *devp, flag, unit, protocol, uqi)); if (uqi == 0) return (ENXIO); if (sm_refuse_opens && unit > smctlunit && uqi->sm_nlqs == 0) return (ENXIO); if (uqi->sm_flags & EXCL_OPEN && (flag & FEXCL)) { return (EBUSY); /* device in use */ } if ((flag & FEXCL)) { if (secpolicy_excl_open(credp) != 0) return (EPERM); if ((uqi->sm_flags & FULLY_OPEN) || uqi->sm_nwaiters > 0) return (EBUSY); /* device in use */ uqi->sm_flags |= EXCL_OPEN; } if (uqi->sm_protocol == NULL_PROTOCOL) { struct termios *termiosp; int len; if (ddi_getlongprop(DDI_DEV_T_ANY, ddi_root_node(), DDI_PROP_NOTPROM, "ttymodes", (caddr_t)&termiosp, &len) == DDI_PROP_SUCCESS && (len == sizeof (struct termios))) { sm_dbg('C', ("open: c_cflag=0x%x\n", termiosp->c_cflag)); uqi->sm_ttycommon->t_iflag = termiosp->c_iflag; uqi->sm_ttycommon->t_cflag = termiosp->c_cflag; uqi->sm_ttycommon->t_stopc = termiosp->c_cc[VSTOP]; uqi->sm_ttycommon->t_startc = termiosp->c_cc[VSTART]; /* * IGNBRK,BRKINT,INPCK,IXON,IXANY,IXOFF - drivers * PARMRK,IGNPAR,ISTRIP - how to report parity * INLCR,IGNCR,ICRNL,IUCLC - ldterm (sophisticated I/O) * IXON, IXANY, IXOFF - flow control input * CBAUD,CSIZE,CS5-8,CSTOPB,PARENB,PARODD,HUPCL, * RCV1EN,XMT1EN,LOBLK,XCLUDE,CRTSXOFF,CRTSCTS, * CIBAUD,PAREXT,CBAUDEXT,CIBAUDEXT,CREAD,CLOCAL */ kmem_free(termiosp, len); } else bzero((caddr_t)uqi->sm_ttycommon, sizeof (uqi->sm_ttycommon)); if (*devp == rconsdev) { uqi->sm_cmask = sm_cmask; uqi->sm_ttycommon->t_flags |= TS_SOFTCAR; } else { uqi->sm_ttycommon->t_flags &= ~TS_SOFTCAR; } /* * Clear the default CLOCAL and TS_SOFTCAR flags since * they must correspond to the settings on the real devices. */ uqi->sm_ttycommon->t_cflag &= ~(uqi->sm_cmask|CLOCAL); uqi->sm_mbits = 0; uqi->sm_policy = FIRSTACK; if (unit == 0 && sm_ssp->sm_ms == 0) sm_ssp->sm_ms = (sm_mux_state_t *) space_fetch(TTYMUXPTR); if (sm_ssp->sm_ms) { if (sm_ssp->sm_ms->sm_cons_stdin.sm_dev == *devp || sm_ssp->sm_ms->sm_cons_stdout.sm_dev == *devp) sm_ssp->sm_lconsole = uqi; } } /* * Does this thread need to wait? */ sm_dbg('C', ("sm_open: %d %d 0x%p 0x%x\n", !(flag & (FNDELAY|FNONBLOCK)), !(protocol == OUTLINE), uqi->sm_lqs, uqi->sm_flags)); tryopen: abort_waiters = 0; if (ftstat = sm_ok_to_open(uqi, protocol, credp, &abort_waiters)) { sm_dbg('C', ("open failed stat=%d.\n", ftstat)); if ((uqi->sm_flags & FULLY_OPEN) == 0 && uqi->sm_nwaiters == 0) uqi->sm_protocol = NULL_PROTOCOL; if (flag & FEXCL) uqi->sm_flags &= ~EXCL_OPEN; return (ftstat); } if (abort_waiters) { uqi->sm_dev = *devp; /* different device wants to use the unit */ SM_RQ(uqi) = rq; SM_WQ(uqi) = WR(rq); } if (rq->q_ptr == 0) { sm_lqi_t *lqi; uqi->sm_dev = *devp; rq->q_ptr = WR(rq)->q_ptr = uqi; SM_RQ(uqi) = rq; SM_WQ(uqi) = WR(rq); qprocson(rq); for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { LOCK_UNIT(lqi); lqi->sm_uqflags |= SM_UQVALID; UNLOCK_UNIT(lqi); } sm_dbg('C', ("sm_open: SM_UQVALID set on lqs.\n")); } if (*devp != rconsdev && BLOCKING(uqi, protocol, flag)) { uqi->sm_flags |= WANT_CDSTAT; do { /* * Wait for notifications of changes in the CLOCAL * and TS_SOFTCAR flags and a TIOCM_CD flag of a * TIOCMGET request (come in on the write side queue). */ if ((ftstat = sm_defer_open(uqi, rq)) != EINTR) { if (ftstat) { goto tryopen; } else { continue; } } if (uqi->sm_nwaiters == 0) { /* clean up */ /* * only opens on an asynchronous * protocols reach here so checking * nwaiters == 0 is sufficient to * ensure that no other thread * is waiting on this logical unit */ if ((uqi->sm_flags & FULLY_OPEN) == 0) { sm_lqi_t *lqi; uqi->sm_dev = NODEV; sm_dbg('C', ("sm_open FULLY_OPEN=0\n")); for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { LOCK_UNIT(lqi); lqi->sm_uqflags &= ~SM_UQVALID; UNLOCK_UNIT(lqi); } qprocsoff(rq); rq->q_ptr = WR(rq)->q_ptr = 0; SM_RQ(uqi) = 0; SM_WQ(uqi) = 0; } } if ((uqi->sm_flags & FULLY_OPEN) == 0 && uqi->sm_nwaiters == 0) uqi->sm_protocol = NULL_PROTOCOL; if (flag & FEXCL) uqi->sm_flags &= ~EXCL_OPEN; sm_dbg('C', ("sm_open: done (ret %d).\n", ftstat)); return (ftstat); } while (BLOCKING(uqi, protocol, flag)); } uqi->sm_flags |= FULLY_OPEN; sm_dbg('C', ("sm_open done (ret %d).\n", ftstat)); return (ftstat); } /* * Multiplexer device close routine. */ /*ARGSUSED*/ static int sm_close(queue_t *rq, int flag, cred_t *credp) { sm_uqi_t *uqi = (sm_uqi_t *)rq->q_ptr; sm_lqi_t *lqi; if (sm_ssp == NULL) return (ENXIO); if (uqi == NULL) { sm_dbg('C', ("close: WARN:- q 0x%p already closed.\n", rq)); return (ENXIO); } sm_dbg('C', ("close: uqi=0x%p unit=%d q=0x%p)\n", uqi, uqi->sm_lunit, rq)); if (SM_RQ(uqi) != rq) sm_dbg('C', ("sm_close: rq != current uqi queue\n")); if (uqi->sm_ttybid) { qunbufcall(SM_RQ(uqi), uqi->sm_ttybid); uqi->sm_ttybid = 0; } /* * Tell all the linked queues that the upper queue has gone * Note close will never get called on a stream while there is a * thread blocked trying to open the same stream. * If there is a blocked open on a different stream but on * the same logical unit it will reset the lower queue flags. */ for (lqi = uqi->sm_lqs; lqi != 0; lqi = lqi->sm_nlqi) { LOCK_UNIT(lqi); lqi->sm_uqflags &= ~SM_UQVALID; UNLOCK_UNIT(lqi); } /* * Turn off the STREAMs queue processing for this queue. */ qprocsoff(rq); /* * Similarly we will never get here if there is thread trying to * open ths stream. */ LOCK_UNIT(uqi); if (uqi->sm_waitq == 0) uqi->sm_flags = (uqi->sm_flags & SM_OBPCNDEV) ? SM_OBPCNDEV : 0U; uqi->sm_dev = NODEV; uqi->sm_protocol = NULL_PROTOCOL; ttycommon_close(uqi->sm_ttycommon); /* it just frees any pending ioctl */ uqi->sm_ttycommon->t_cflag = 0; uqi->sm_ttycommon->t_flags = 0; /* * Reset the queue pointers to NULL. * If a thread is qwaiting in the open routine it will recheck * the q_ptr. */ rq->q_ptr = NULL; WR(rq)->q_ptr = NULL; UNLOCK_UNIT(uqi); if (sm_ssp->sm_lconsole == uqi) { /* this will never be the outdial device closing */ sm_ssp->sm_lconsole = 0; } /* * If there is another thread waiting for this close then unblock * the thread by putting a message on its read queue. */ if (uqi->sm_waitq) { sm_dbg('C', ("close(0x%p): doing putctl on 0x%p\n", rq, uqi->sm_waitq)); if (rq == uqi->sm_waitq) sm_log("close: waitq and closeq are same q\n"); (void) putctl(uqi->sm_waitq, M_CTL); } uqi->sm_flags &= ~(EXCL_OPEN | FULLY_OPEN); sm_dbg('C', ("close: returning ok.\n")); return (0); } /* * Initialise the software abort sequence for use when one of the * driver's nodes provides the system console. */ static void sm_set_abort() { char ds[3] = { '\r', '~', CNTRL('b') }; char as[SM_MAX_ABSLEN]; int len = SM_MAX_ABSLEN; if (ddi_prop_op(DDI_DEV_T_ANY, sm_ssp->sm_dip, PROP_LEN_AND_VAL_BUF, 0, "abort-str", as, &len) != DDI_PROP_SUCCESS || (len = strlen(as)) < SM_MIN_ABSLEN) { (void) strcpy(as, ds); len = strlen(as); } else { char *s; int i; for (s = as, i = 0; i < len-1; i++, s++) { if (as[i] == '^' && as[i+1] >= 'a' && as[i+1] <= 'z') { *s = as[i+1] - 'a' + 1; i++; } else { *s = as[i]; } } *s++ = as[i]; *s = '\0'; len = strlen(as); } if (len < SM_MIN_ABSLEN) (void) strcpy(sm_ssp->sm_abs, ds); else (void) strcpy(sm_ssp->sm_abs, as); } /* * * sm_attach - initialisation routine per driver instance. */ static int sm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int unit; char name[32]; sm_uqi_t *uqi; sm_lqi_t *lqip; /* * Is this an attach? */ if (cmd != DDI_ATTACH) { return (DDI_FAILURE); } /* * Validate the instance number (sm is a single instance driver). */ if (sm_ssp) { /* only one instance allowed */ return (DDI_FAILURE); } sm_instance = ddi_get_instance(dip); /* * Create the default minor node which will become the console. * (create it with three different names).: * con which appears in the /dev filesystem; * input which matches the prom /multiplexer:input node; * output which matches the prom /multiplexer:input node * Create a minor node for control operations. */ if (ddi_create_minor_node(dip, "con", S_IFCHR, 0, DDI_PSEUDO, 0) != DDI_SUCCESS || ddi_create_minor_node(dip, "input", S_IFCHR, 0, DDI_PSEUDO, 0) != DDI_SUCCESS || ddi_create_minor_node(dip, "output", S_IFCHR, 0, DDI_PSEUDO, 0) != DDI_SUCCESS || ddi_create_minor_node(dip, "ctl", S_IFCHR, 1, DDI_PSEUDO, 0) != DDI_SUCCESS) { cmn_err(CE_WARN, "sm_attach: create minors failed.\n"); ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } smctlunit = 1; /* * Allocate private state for this instance. */ sm_ssp = (sm_ss_t *)kmem_zalloc(sizeof (sm_ss_t), KM_SLEEP); /* * Initialise per instance data. */ sm_ssp->sm_dip = dip; /* * Get required debug level. */ sm_ssp->sm_trflag = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sm-trlv", sm_default_trflag); sm_max_units = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sm-max-units", sm_max_units); sm_minor_cnt = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sm-minor-cnt", 0); sm_refuse_opens = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sm-refuse-opens", sm_refuse_opens); sm_ssp->sm_ctrla_abort_on = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sm-ctrla-abort-on", 1); sm_ssp->sm_break_abort_on = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sm-break-abort-on", 0); sm_set_abort(); sm_ssp->sm_lqs = (sm_lqi_t *)kmem_zalloc(sizeof (sm_lqi_t) * MAX_LQS, KM_SLEEP); sm_ssp->sm_uqs = (sm_uqi_t *)kmem_zalloc(sizeof (sm_uqi_t) * NLUNITS, KM_SLEEP); for (unit = 2; unit < NLUNITS && unit < sm_minor_cnt + 2; unit++) { if (snprintf(name, sizeof (name), "sm%c", 'a' + unit-2) > sizeof (name)) { cmn_err(CE_WARN, "sm_attach: create device for unit %d failed.\n", unit); } else if (ddi_create_minor_node(dip, name, S_IFCHR, unit, DDI_NT_SERIAL, NULL) != DDI_SUCCESS) { ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } if (snprintf(name, sizeof (name), "sm%c,cu", 'a' + unit-2) > sizeof (name)) { cmn_err(CE_WARN, "sm_attach: create cu device for unit %d failed.\n", unit); continue; } else if (ddi_create_minor_node(dip, name, S_IFCHR, unit|OUTLINE, DDI_NT_SERIAL_DO, NULL) != DDI_SUCCESS) { ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } } for (unit = 0; unit < NLUNITS; unit++) { uqi = get_uqi(sm_ssp, unit); uqi->sm_lqs = 0; uqi->sm_dev = NODEV; uqi->sm_nlqs = 0; uqi->sm_lunit = unit; uqi->sm_protocol = NULL_PROTOCOL; mutex_init(uqi->sm_umutex, NULL, MUTEX_DRIVER, NULL); cv_init(uqi->sm_ucv, NULL, CV_DRIVER, NULL); mutex_init(&uqi->sm_ttycommon->t_excl, NULL, MUTEX_DRIVER, NULL); } for (unit = 0; unit < MAX_LQS; unit++) { lqip = get_lqi(sm_ssp, unit); lqip->sm_unit = unit; lqip->sm_hadkadbchar = 0; lqip->sm_nachar = sm_ssp->sm_abs; lqip->sm_ioflag = FORIO; lqip->sm_ctrla_abort_on = sm_ssp->sm_ctrla_abort_on; lqip->sm_break_abort_on = sm_ssp->sm_break_abort_on; mutex_init(lqip->sm_umutex, NULL, MUTEX_DRIVER, NULL); cv_init(lqip->sm_ucv, NULL, CV_DRIVER, NULL); mutex_init(&lqip->sm_ttycommon->t_excl, NULL, MUTEX_DRIVER, NULL); } return (DDI_SUCCESS); } /* * * sm_detach - detach routine per driver instance. */ static int sm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { sm_uqi_t *lu; sm_lqi_t *pu; int unit; /* * Is this a detach request for instance 0 (single instance driver). */ if (cmd != DDI_DETACH) return (DDI_FAILURE); if (sm_ssp == NULL) return (DDI_FAILURE); sm_dbg('V', ("detach ...")); /* * Check that all the upper and lower queues are closed. */ for (unit = 0; unit < NLUNITS; unit++) { lu = &sm_ssp->sm_uqs[unit]; if (lu && lu->sm_protocol != NULL_PROTOCOL) { sm_dbg('V', ("detach: upper unit still open.\n")); return (DDI_FAILURE); } } for (unit = 0; unit < MAX_LQS; unit++) { pu = &sm_ssp->sm_lqs[unit]; if (pu && pu->sm_linkid != 0) { sm_dbg('V', ("detach: lower unit still linked (%d)\n", pu->sm_linkid)); return (DDI_FAILURE); } } for (unit = 0; unit < NLUNITS; unit++) { lu = &sm_ssp->sm_uqs[unit]; mutex_destroy(lu->sm_umutex); cv_destroy(lu->sm_ucv); mutex_destroy(&lu->sm_ttycommon->t_excl); } for (unit = 0; unit < MAX_LQS; unit++) { pu = &sm_ssp->sm_lqs[unit]; mutex_destroy(pu->sm_umutex); cv_destroy(pu->sm_ucv); mutex_destroy(&pu->sm_ttycommon->t_excl); } /* * Tidy up per instance state. */ kmem_free(sm_ssp->sm_lqs, sizeof (sm_lqi_t) * MAX_LQS); kmem_free(sm_ssp->sm_uqs, sizeof (sm_uqi_t) * NLUNITS); kmem_free(sm_ssp, sizeof (sm_ss_t)); sm_ssp = 0; /* * Remove all of the devices created in attach. */ ddi_remove_minor_node(dip, NULL); return (DDI_SUCCESS); } /* * SECTION * Driver interface to the OS. */ /* * The driver is responsible for managing the mapping between the file system * device types (major/minor pairs) and the corresponding instance of the driver * or device information pointer (dip). * sm_info - return the instance or dip corresponding to the dev_t. */ /*ARGSUSED*/ static int sm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int res = DDI_SUCCESS; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (sm_ssp == NULL) res = DDI_FAILURE; else *result = (void *)sm_ssp->sm_dip; break; case DDI_INFO_DEVT2INSTANCE: *result = (void*)0; /* single instance driver */ break; default: res = DDI_FAILURE; break; } return (res); } /* * End of driver implementation */ /* * Loadable module interface to the kernel */ /* * Firstly the Streams specific interface */ /* * Solaris driver/STREAM initialisation structures. */ static struct module_info uinfo = { SM_MOD_ID, TTYMUX_DRVNAME, 0, /* min packet size */ INFPSZ, /* max packet size */ 2048, /* high water mark */ 256, /* low water mark */ }; /* * Use zero water marks becuase the lower queues are used only for flow control. */ static struct module_info linfo = { SM_MOD_ID, TTYMUX_DRVNAME, 0, /* min packet size */ INFPSZ, /* max packet size */ 0, /* high water mark */ 0 /* low water mark */ }; /* * Solaris upper read STREAM initialisation structure. */ static struct qinit urinit = { sm_urput, /* put */ sm_ursrv, /* service */ sm_open, /* open */ sm_close, /* close */ NULL, /* admin */ &uinfo, /* module info */ NULL /* stats */ }; /* * Solaris upper write STREAM initialisation structure. */ static struct qinit uwinit = { sm_uwput, sm_uwsrv, NULL, NULL, NULL, &uinfo, NULL }; /* * Solaris lower read STREAM initialisation structure. */ static struct qinit lrinit = { sm_lrput, sm_lrsrv, NULL, NULL, NULL, &linfo, NULL }; /* * Solaris lower write STREAM initialisation structure. */ static struct qinit lwinit = { putq, sm_lwsrv, NULL, NULL, NULL, &linfo, NULL }; /* * Multiplexing STREAM structure. */ struct streamtab sm_streamtab = { &urinit, &uwinit, &lrinit, &lwinit }; /* * Driver operations structure (struct cb_ops) and * driver dynamic loading functions (struct dev_ops). */ /* * Fold the Stream interface to the kernel into the driver interface * to the OS. */ DDI_DEFINE_STREAM_OPS(sm_ops, \ nulldev, nulldev, \ sm_attach, sm_detach, nodev, \ sm_info, (D_NEW | D_MTQPAIR|D_MTOUTPERIM|D_MTOCEXCL | D_MP), &sm_streamtab); /* * Driver module information. */ extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, "serial mux driver %I%", &sm_ops }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; /* * Define the body of our interface to the OS. */ /* * '_init' is called by Solaris to initialise any driver * specific state and to install the driver. */ int _init(void) { return (mod_install(&modlinkage)); } /* * _info - return this drivers interface to the kernel. */ int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * _fini - the OS is finished with the services provided by the driver. * remove ourself and then remove any footprint that remains. */ int _fini(void) { return (mod_remove(&modlinkage)); }