/*
* 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
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* 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 <sys/ser_sync.h>
#include "ttymux_impl.h"
/*
* Extern declarations
*/
extern int nulldev();
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
*/
/*
* Variables defined here and visible only internally
*/
static int sm_instance = 0;
static int smctlunit;
/*
* Local definitions.
*/
/* force these flags to be unset on console devices */
/*
* SECTION
* Implementation Section:
*/
void
{
int sz;
if (sz < 0)
SL_TRACE, "vsnprintf parse error\n");
char *b;
if (sz > 0)
} else {
}
}
void
{
int sz;
if (sz < 0)
SL_TRACE, "vsnprintf parse error\n");
char *b;
if (sz > 0)
} else {
}
}
/*
* Should only be called if the caller can guarantee that the vnode
* 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
{
}
/*
* 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
{
int mu_owned;
/*
* 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 (mu_owned == 0)
}
}
/*
* Given a q return the associated lower queue data structure or NULL.
* Return the data locked.
*/
static sm_lqi_t *
{
int i;
for (i = 0; i < MAX_LQS; i++) {
if (flqi)
return (lqi);
}
else
}
return (flqi);
}
/*
* Given a streams link identifier return the associated lower queue data
* structure or NULL.
*/
sm_lqi_t *
{
int i;
if (linkid == 0)
return (NULL);
for (i = 0; i < MAX_LQS; i++) {
return (lqi);
}
return (NULL);
}
/*
* Given a dev_t for a lower stream return the associated lower queue data
* structure or NULL.
*/
sm_lqi_t *
{
int i;
return (NULL);
for (i = 0; i < MAX_LQS; i++) {
return (lqi);
}
return (NULL);
}
/*
* Determine whether the input flag is set on at least
* howmany queues.
*/
static int
{
if (howmany == 0)
return (0);
if (--howmany == 0)
return (1);
}
return (0);
}
/*
* How many usable queues are associated with a given upper stream
*/
static int
{
}
/*
* How many of the queues associated with a given upper stream
* - do not - have the given flags set.
*/
static int
{
int count = 0;
count++;
}
return (count);
}
/*
* How many of the queues associated with a given upper stream
* - do not - have the given flags set.
*/
static int
{
int count = 0;
count++;
}
return (count);
}
/*
* How many usable queues are associated with a given upper stream
*/
static int
{
}
static int
{
}
/*
* Send an ioctl downstream and remember that it was sent so that
* its response can be caught on the way back up.
*/
static void
{
} else {
return;
}
if (cmdflag == WANT_TCSET) {
BPRI_MED);
pioc = 0;
} else {
sizeof (struct termios));
sizeof (struct termios);
}
sizeof (struct termios);
}
else
}
else
if (pioc != 0) {
/* lqi->sm_flags |= cmdflag; */
} else {
}
}
else
}
/*
* Associate one of the drivers minor nodes with a serial device.
*/
int
{
int rval = 0;
/*
* Check the data is valid.
* Associate a lower queue with a logical unit.
*/
} else {
rval = 0;
} else {
}
} else {
if (*dp == '/')
else
#ifdef ADD2FRONT
#else
}
} else
#endif
(void) ttymux_device_init(plqi);
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.
*/
/*
* Bypass the lower half of the driver (hence
* no qwriter) and apply the current termio
* settings on the lower stream.
*/
}
/*
* Only set cflags on the lower q if we know
* the settings on any other lower queue.
*/
}
}
}
return (rval);
}
/*
* Break an association between one of the driver's minor nodes and
* a serial device.
*/
int
{
int rval = 0;
/*
* Check the data is valid.
* Disassociate a lower queue with a logical unit.
*/
} else {
sm_dbg('@',
("Invalid tag for TTYMUX_DISASSOC ioctl\n"));
} else {
(void) ttymux_device_fini(plqi);
/*
* Indicate that carrier status is no
* longer required and that the upper
* queue should not be used by plqi
*/
rval = 0;
}
}
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
{
noenable(q);
}
/*
* qbufcall routine to restart the queues when memory is available.
*/
static void
{
}
}
/*
* Place a message on the write queue of each stream associated with
* the given upper stream.
*/
static void
{
}
}
}
/*
* For each lower device that should receive a write message duplicate
* the message block.
*/
static int
{
continue;
}
continue;
}
/* must have been sm_copymsg */
}
}
}
}
return (0);
}
/*
* Return 1 if all associated lower devices have room for another message
* otherwise return 0.
*/
static int
{
return (0);
return (0);
}
return (1);
}
/*
* Put a message down all associated lower queues.
* Return 1 if the q function was called.
*/
static int
{
register int memreq;
int rval = 0;
/* a lower q is flow controlled */
rval = 1;
} else {
sm_log("sm_putqs: msg 0x%x - can't alloc %d bytes (pri %d).\n",
rval = 1;
}
return (rval);
}
/*
* Service a streams link and unlink requests.
*/
static void
{
int rval;
int cmd;
switch (cmd) {
case I_LINK:
case I_PLINK:
/*
* 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.
*/
} else {
(void) ttymux_device_init(plqi);
rval = 0;
}
break;
case I_UNLINK:
case I_PUNLINK:
} else {
int werrmode;
/*
* Mark the lower q as invalid.
*/
}
}
(void) ttymux_device_fini(plqi);
if (uqi)
? 1 : 0;
/* SM_RQ(plqi) = SM_WQ(plqi) = 0; */
plqi->sm_hadkadbchar = 0;
if (uqi &&
werrmode &&
sm_uwq_error(uqi) &&
sm_log("sm_link_req: putnextctl(M_HANGUP)"
" failed.\n");
}
rval = 0;
}
break;
default:
}
if (rval != 0)
else
}
static int
{
case M_COPYOUT:
break;
case M_COPYIN:
break;
case M_IOCACK:
/* the se driver has bug so we cannot use ioc_count */
break;
case M_IOCNAK:
break;
case M_IOCDATA:
break;
case M_IOCTL:
break;
default:
return (EINVAL);
}
return (0);
}
/*
* Record the termio settings that have been set on the upper stream
*/
static int
{
int err;
return (err);
case TIOCSPPS:
case TIOCGPPS:
case TIOCGPPSEV:
return (ENOTSUP);
case TIOCGWINSZ:
case TIOCSWINSZ:
break;
case TCSBRK:
case TIOCSBRK:
case TIOCCBRK:
break;
case TCSETSF:
/*FALLTHROUGH*/
case TCSETSW:
case TCSETS:
case TCGETS:
}
break;
case TCSETAF:
/*FALLTHROUGH*/
case TCSETAW:
case TCSETA:
case TCGETA:
}
break;
case TIOCSSOFTCAR:
case TIOCGSOFTCAR:
else
}
break;
case TIOCMSET:
case TIOCMGET:
break;
case TIOCMBIS:
break;
case TIOCMBIC:
break;
default:
return (EINVAL);
/* NOTREACHED */
} /* end switch cmd */
else
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
{
int rval = 0;
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:
*/
/* flush the upper write queue */
/*
* 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.
*/
}
}
break;
case M_STARTI:
}
break;
case M_STOPI:
}
break;
case M_STOP: /* must never be queued */
rval = 1;
break;
case M_START: /* never be queued */
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:
break;
} /* end switch on high pri message type */
return (rval);
}
static int
{
int err;
case TIOCEXCL:
case TIOCNXCL:
case TIOCSTI:
/*
* The three ioctl types we support do not require any
* additional allocation and should not return a pending
* ioctl state. For this reason it is safe for us to ignore
* the return value from ttycommon_ioctl().
* Additionally, we translate any error response from
* ttycommon_ioctl() into EINVAL.
*/
if (err < 0)
else
return (0);
default:
break;
}
return (0);
}
/*
* If uqi->sm_siocdata.sm_iocid just overwrite it since the stream
* head will have timed it out
*/
}
/*
*
* sm_uwput - put function for an upper STREAM write.
*/
static int
{
int cmd;
return (0);
}
case M_DATA:
case M_DELAY:
case M_BREAK:
default:
break;
case M_CTL:
}
break;
case M_IOCDATA: /* not handled as high pri because may need to putbq */
/*FALLTHROUGH*/
case M_IOCTL:
switch (cmd) {
case CONSGETABORTENABLE:
break;
case CONSSETABORTENABLE:
break;
case TTYMUX_SETABORT:
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:
break;
case I_LINK:
case I_PLINK:
case I_UNLINK:
case I_PUNLINK:
break;
case TCSETSW:
case TCSETSF:
case TCSETAW:
case TCSETAF:
case TCSBRK:
/* keep message order intact */
break;
}
/*FALLTHROUGH*/
default:
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
{
int msgtype;
/*
* Empty the queue unless explicitly stopped.
*/
if (sm_hp_uwput(q, mp)) {
break; /* indicates that the is disabled */
}
else
continue;
break;
}
/*
* Read any ttycommon data that may
* change (TS_SOFTCAR, CREAD, etc.).
*/
case M_IOCTL:
case M_IOCDATA:
return (0);
break;
default:
return (0);
}
}
return (0);
}
/*
* Lower write side service routine used for backenabling upstream
* flow control.
*/
static int
{
/*
* 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.
*/
} else {
}
return (0);
}
/*
* Upper read queue ioctl response handler for messages
* passed from the lower half of the driver.
*/
static int
{
return (err);
}
} else {
sm_log("Unexpected ioctl response\n");
/*
* 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;
}
case M_COPYOUT:
flag = SM_COPYOUT;
/*FALLTHRU*/
case M_COPYIN:
goto out;
break;
case M_IOCACK:
else
goto out;
} else {
} else
goto out;
}
break;
case M_IOCNAK:
break;
}
}
goto out;
default:
goto out;
}
/*
* Merge the tty settings each of the associated lower streams.
*/
}
} else {
return (0);
}
out:
return (0);
}
/*
* Transfer a message from the lower read side of the multiplexer onto
* the associated upper stream.
*/
static int
{
return (1);
}
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 */
} else {
/* there are still usable q's - don't send up */
}
break;
case M_ERROR:
if (sm_uwq_error(uqi)) {
/* there are no usable lower q's */
/* the error has cleared */
} else {
/* there are still usable q's - don't send up */
}
break;
case M_FLUSH:
break;
case M_CTL:
/* wrong direction - must have come from sm_close */
break;
case M_UNHANGUP:
/* just pass them all up - they're harmless */
/* FALLTHROUGH */
default:
break;
}
return (0);
}
/*
* sm_urput - put function for a lower STREAM read.
*/
static int
{
if (sm_ursendup(q, mp) != 0)
return (0);
}
/*
* Upper read side service routine.
* Read side needs to be fast so only check for duplicate M_IOCTL acks.
*/
static int
{
if (sm_ursendup(q, mp) != 0) {
break;
}
}
/*
* If the q service was called because it was no longer
* flow controled then enable each of the driver queues.
*/
}
}
return (0);
}
/*
* Check a message sent from a linked device for abort requests and
* for flow control.
*/
static int
{
case M_DATA:
/*
* check for abort - only allow abort on I/O consoles
* known to OBP -
* fix it when we do polled io
*/
return (1);
}
lqi->sm_ctrla_abort_on &&
rxc++)
(char *)NULL);
}
} else
sm_abs) ?
sm_ssp->
sm_abs + 1 :
if (aborted) {
return (1);
}
}
break;
case M_BREAK: /* we'll eventually see this as a flush */
/*
* 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_break_abort_on &&
abort_sequence_enter((char *)NULL);
return (1);
} else {
}
break;
default:
break;
}
return (0);
return (1);
}
} else {
}
return (0);
}
/*
* sm_sendup - deliver a message to the upper read side of the multiplexer
*/
static int
{
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).
*/
case M_DATA:
case M_BREAK: /* we'll eventually see this as a flush */
break;
/* high priority messages */
case M_IOCACK:
case M_IOCNAK:
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).
*/
break;
case M_HANGUP:
break;
case M_ERROR:
/*
*/
/* not in error anymore */
} else {
/* read error */
}
/* write error */
}
/* has next driver done qprocsoff */
rw);
}
}
}
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 */
break;
default:
break;
}
return (0);
} else {
}
return (0);
}
/*
* sm_lrput - put function for a lower STREAM read.
*/
static int
{
if (sm_lrmsg_check(q, mp) == 0)
return (0);
}
/*
* sm_lrsrv - service function for the lower read STREAM.
*/
static int
{
return (0);
}
/*
* Check whether a thread is allowed to open the requested device.
*/
static int
{
int rval = 0;
int proto;
*abort_waiters = 0;
switch (protocol) {
case ASYNC_DEVICE: /* Standard async protocol */
/*
* Lock out other incompatible protocol requests.
*/
rval = 0;
} else
break;
case OUTLINE: /* Outdial protocol */
rval = 0;
/*
* check for dialout request on a line that is already
* open for dial in:
* kick off any thread that is waiting to fully open
*/
else {
*abort_waiters = 1;
}
} else
break;
default:
}
if (rval == 0 &&
secpolicy_excl_open(credp) != 0) {
} else {
/* NB TS_XCLUDE cant be set during open so NOTREACHED */
/* force any waiters to yield TS_XCLUDE */
*abort_waiters = 1;
}
}
if (rval == 0)
return (rval);
}
/* wait for memory to become available whilst performing a qwait */
/*ARGSUSED*/
{}
/* ARGSUSED */
static int
{
return (0);
}
/*
* Wait for a message to arrive - must be called with exclusive
* access at the outer perimiter.
*/
static int
{
int err;
if (--uqi->sm_nwaiters == 0)
if (err == 0)
else if (q->q_ptr == 0) /* can happen if there are multiple waiters */
err = -1;
}
else
err = 0; /* was worth waiting for */
return (err);
}
/*
* Defer the opening of one the drivers devices until the state of each
* associated lower stream is known.
*/
static int
{
return (err);
}
int iocmd;
if (cmdflags == 0) {
return (err);
continue; /* waiting for an M_UNHANGUP */
}
BPRI_MED, dummy_callback, 0);
/* wait for the bufcall */
qunbufcall(q, id);
return (err);
}
qunbufcall(q, id);
}
sm_piocdata.sm_nakcnt = 0;
sm_log("sm_defer_open: bad putqs\n");
return (-1);
}
}
return (err);
}
return (0);
}
static int
{
int ftstat;
int unit;
int protocol;
int abort_waiters;
return (ENXIO);
/*
* sflag = 0 => streams device.
*/
return (ENXIO);
}
if (uqi == 0)
return (ENXIO);
return (ENXIO);
return (EBUSY); /* device in use */
}
if (secpolicy_excl_open(credp) != 0)
return (EPERM);
return (EBUSY); /* device in use */
}
int len;
== DDI_PROP_SUCCESS &&
/*
* 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
*/
}
else
sizeof (uqi->sm_ttycommon));
} else {
}
/*
* Clear the default CLOCAL and TS_SOFTCAR flags since
* they must correspond to the settings on the real devices.
*/
}
}
/*
* Does this thread need to wait?
*/
abort_waiters = 0;
return (ftstat);
}
if (abort_waiters) {
/* different device wants to use the unit */
}
}
}
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) {
goto tryopen;
} else {
continue;
}
}
/*
* 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
*/
}
}
}
uqi->sm_nwaiters == 0)
return (ftstat);
}
return (ftstat);
}
/*
* Multiplexer device close routine.
*/
/*ARGSUSED*/
static int
{
return (ENXIO);
return (ENXIO);
}
rq));
}
/*
* 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.
*/
}
/*
* Turn off the STREAMs queue processing for this queue.
*/
/*
* Similarly we will never get here if there is thread trying to
* open ths stream.
*/
0U;
/* it just frees any pending ioctl */
/*
* Reset the queue pointers to NULL.
* If a thread is qwaiting in the open routine it will recheck
* the q_ptr.
*/
/* 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.
*/
sm_log("close: waitq and closeq are same q\n");
}
return (0);
}
/*
* Initialise the software abort sequence for use when one of the
* driver's nodes provides the system console.
*/
static void
{
} else {
char *s;
int i;
i++;
} else {
*s = as[i];
}
}
*s++ = as[i];
*s = '\0';
}
if (len < SM_MIN_ABSLEN)
else
}
/*
*
* sm_attach - initialisation routine per driver instance.
*/
static int
{
int unit;
/*
* 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);
}
/*
* 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.
*/
DDI_PSEUDO, 0) != DDI_SUCCESS ||
DDI_PSEUDO, 0) != DDI_SUCCESS ||
DDI_PSEUDO, 0) != DDI_SUCCESS ||
DDI_PSEUDO, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
smctlunit = 1;
/*
* Allocate private state for this instance.
*/
/*
* Initialise per instance data.
*/
/*
* Get required debug level.
*/
DDI_PROP_DONTPASS, "sm-minor-cnt", 0);
DDI_PROP_DONTPASS, "sm-break-abort-on", 0);
sm_set_abort();
KM_SLEEP);
KM_SLEEP);
sizeof (name)) {
"sm_attach: create device for unit %d failed.\n",
unit);
return (DDI_FAILURE);
}
sizeof (name)) {
"sm_attach: create cu device for unit %d failed.\n",
unit);
continue;
return (DDI_FAILURE);
}
}
MUTEX_DRIVER, NULL);
}
lqip->sm_hadkadbchar = 0;
MUTEX_DRIVER, NULL);
}
return (DDI_SUCCESS);
}
/*
*
* sm_detach - detach routine per driver instance.
*/
static int
{
int unit;
/*
* Is this a detach request for instance 0 (single instance driver).
*/
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
return (DDI_FAILURE);
/*
* Check that all the upper and lower queues are closed.
*/
return (DDI_FAILURE);
}
}
return (DDI_FAILURE);
}
}
}
}
/*
* Tidy up per instance state.
*/
sm_ssp = 0;
/*
* Remove all of the devices created in attach.
*/
return (DDI_SUCCESS);
}
/*
* SECTION
* Driver interface to the OS.
*/
/*
* The driver is responsible for managing the mapping between the file system
* or device information pointer (dip).
* sm_info - return the instance or dip corresponding to the dev_t.
*/
/*ARGSUSED*/
static int
{
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
res = DDI_FAILURE;
else
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
*/
/*
*/
{
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.
*/
{
0, /* min packet size */
INFPSZ, /* max packet size */
0, /* high water mark */
0 /* low water mark */
};
/*
* Solaris upper read STREAM initialisation structure.
*/
{
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.
*/
{
NULL,
NULL,
NULL,
&uinfo,
};
/*
* Solaris lower read STREAM initialisation structure.
*/
{
NULL,
&linfo,
};
/*
* Solaris lower write STREAM initialisation structure.
*/
{
putq,
NULL,
NULL,
NULL,
&linfo,
};
/*
* Multiplexing STREAM structure.
*/
{
&urinit,
&uwinit,
&lrinit,
};
/*
* 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.
*/
/*
* Driver module information.
*/
extern struct mod_ops mod_driverops;
{
"serial mux driver",
};
{
&modldrv,
};
/*
* 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
{
}
/*
* _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));
}