/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* MT STREAMS Virtual Console Device Driver
*/
#include <sys/sc_cvcio.h>
#include <sys/iosramio.h>
static void cvc_reioctl(void *);
static void cvc_input_daemon(void);
static void cvc_flush_queue(void *);
static void cvc_iosram_ops(uint8_t);
static void cvc_getstr(char *cp);
static void cvc_win_resize(int clear_flag);
#define ESUCCESS 0
#ifndef TRUE
#define FALSE 0
#endif
/*
* Private copy of devinfo pointer; cvc_info uses it.
*/
/*
* This structure reflects the layout of data in CONI and CONO. If you are
* going to add fields that don't get written into those chunks, be sure to
* place them _after_ the buffer field.
*/
typedef struct cvc_buf {
} cvc_buf_t;
typedef struct cvc_s {
} cvc_t;
1313, /* mi_idnum Bad luck number ;-) */
"cvc", /* mi_idname */
0, /* mi_minpsz */
INFPSZ, /* mi_maxpsz */
2048, /* mi_hiwat */
2048 /* mi_lowat */
};
NULL, /* qi_putp */
NULL, /* qi_srvp */
cvc_open, /* qi_qopen */
cvc_close, /* qi_qclose */
NULL, /* qi_qadmin */
&cvcm_info, /* qi_minfo */
NULL /* qi_mstat */
};
cvc_wput, /* qi_putp */
cvc_wsrv, /* qi_srvp */
cvc_open, /* qi_qopen */
cvc_close, /* qi_qclose */
NULL, /* qi_qadmin */
&cvcm_info, /* qi_minfo */
NULL /* qi_mstat */
};
&cvcrinit, /* st_rdinit */
&cvcwinit, /* st_wrinit */
NULL, /* st_muxrinit */
NULL /* st_muxwrinit */
};
static int cvc_stopped = 0;
static int cvc_suspended = 0;
static int input_daemon_started = 0;
/* debugging functions */
#ifdef DEBUG
#endif
/*
* Module linkage information for the kernel.
*/
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a pseudo driver */
"CVC driver 'cvc'",
&cvcops, /* driver ops */
};
&modldrv,
};
int
_init()
{
int status;
if (status == 0) {
}
return (status);
}
int
_fini()
{
return (EBUSY);
}
int
{
}
/*
* DDI glue routines.
*/
/* ARGSUSED */
static int
{
static char been_here = 0;
if (cmd == DDI_RESUME) {
cvc_suspended = 0;
if (cvcinput_q != NULL) {
}
return (DDI_SUCCESS);
}
if (!been_here) {
been_here = 1;
} else {
#if defined(DEBUG)
"cvc_attach: called multiple times!! (instance = %d)",
#endif /* DEBUG */
return (DDI_SUCCESS);
}
return (-1);
}
cvcinput_q = NULL;
cvcoutput_q = NULL;
return (DDI_SUCCESS);
}
static int
{
if (cmd == DDI_SUSPEND) {
cvc_suspended = 1;
} else {
if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
/*
* XXX this doesn't even begin to address the detach
* issues - it doesn't terminate the outstanding thread,
* it doesn't clean up mutexes, kill the timeout routine
* etc.
*/
}
}
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
{
register int error;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_FAILURE;
} else {
error = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)0;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
/* ARGSUSED */
static int
{
if (unit != 0)
return (ENXIO);
if (q->q_ptr)
return (0);
cp->cvc_wbufcid = 0;
qprocson(q);
input_ok = 1;
/*
* Start the thread that handles input polling if it hasn't been started
* previously.
*/
if (!input_daemon_started) {
input_daemon_started = 1;
} else {
}
/*
* Set the console window size.
*/
return (err);
}
/* ARGSUSED */
static int
{
input_ok = 0;
if (cp->cvc_wbufcid != 0) {
}
cvcinput_q = NULL;
qprocsoff(q);
return (err);
}
/*
* cvc_wput()
* cn driver does a strwrite of console output data to rconsvp which has
* been set by consconfig. The data enters the cvc stream at the streamhead
* and flows thru ttycompat and ldterm which have been pushed on the
* stream. Console output data gets sent out either to cvcredir, if the
* network path is available and selected, or to IOSRAM otherwise. Data is
* sent to cvcredir via its read queue (cvcoutput_q, which gets set in
* cvc_register()). If the IOSRAM path is selected, or if previous mblks
* are currently queued up for processing, the new mblk will be queued
* and handled later on by cvc_wsrv.
*/
static int
{
int error = 0;
case M_IOCTL:
case M_CTL: {
/*
* These ioctls are only supposed to be
* processed after everything else that is
* already queued awaiting processing, so throw
* them on the queue and let cvc_wsrv handle
* them.
*/
case TCSETSW:
case TCSETSF:
case TCSETAW:
case TCSETAF:
case TCSBRK:
break;
default:
}
break;
}
case M_FLUSH:
/*
* Flush our write queue.
*/
}
} else
break;
case M_STOP:
cvc_stopped = 1;
break;
case M_START:
cvc_stopped = 0;
qenable(q); /* Start up delayed messages */
break;
case M_READ:
/*
*/
break;
default:
" 0x%p, type = 0x%x", (void *)mp,
break;
case M_DATA:
/*
* If there are other mblks queued up for transmission,
* or we're using IOSRAM either because cvcredir hasn't
* registered yet or because we were configured that
* way, or cvc has been stopped or suspended, place this
* mblk on the input queue for future processing.
* Otherwise, hand it off to cvcredir for transmission
* via the network.
*/
cvc_suspended == 1) {
} else {
/*
* XXX - should canputnext be called here?
* Starfire's cvc doesn't do that, and it
* appears to work anyway.
*/
}
break;
}
return (error);
}
/*
* cvc_wsrv()
* cvc_wsrv handles mblks that have been queued by cvc_wput either because
* the IOSRAM path was selected or the queue contained preceding mblks. To
* optimize processing (particularly if the IOSRAM path is selected), all
* mblks are pulled off of the queue and chained together. Then, if there
* are any mblks on the chain, they are either forwarded to cvcredir or
* sent for IOSRAM processing as appropriate given current circumstances.
* IOSRAM processing may not be able to handle all of the data in the
* chain, in which case the remaining data is placed back on the queue and
* a timeout routine is registered to reschedule cvc_wsrv in the future.
* Automatic scheduling of the queue is disabled (noenable(q)) while
* cvc_wsrv is running to avoid superfluous calls.
*/
static int
{
return (0);
}
noenable(q);
/*
* If there's already a timeout registered for scheduling this routine
* in the future, it's a safe bet that we don't want to run right now.
*/
enableok(q);
return (0);
}
/*
* Start by linking all of the queued M_DATA mblks into a single chain
* so we can flush as much as possible to IOSRAM (if we choose that
* route).
*/
/*
* Technically, certain IOCTLs are supposed to be processed only
* after all preceding data has completely "drained". In an
* attempt to support that, we delay processing of those IOCTLs
* until this point. It is still possible that an IOCTL will be
* processed before all preceding data is drained, for instance
* in the case where not all of the preceding data would fit
* into IOSRAM and we have to place it back on the queue.
* However, since none of these IOCTLs really appear to have any
* relevance for cvc, and we weren't supporting delayed
* processing at _all_ previously, this partial implementation
* should suffice. (Fully implementing the delayed IOCTL
* processing would be unjustifiably difficult given the nature
* of the underlying IOSRAM console protocol.)
*/
continue;
}
/*
* We know that only M_IOCTL and M_DATA blocks are placed on our
* queue. Since this block isn't an M_IOCTL, it must be M_DATA.
*/
} else {
}
}
/*
* Do we actually have anything to do?
*/
enableok(q);
return (0);
}
/*
* Yes, we do, so send the data to either cvcredir or IOSRAM as
* appropriate. In the latter case, we might not be able to transmit
* everything right now, so re-queue the remainder.
*/
/*
* XXX - should canputnext be called here? Starfire's cvc
* doesn't do that, and it appears to work anyway.
*/
} else {
}
}
/*
* If there is still data queued at this point, make sure the queue
* gets scheduled again after an appropriate delay (which has been
* somewhat arbitrarily selected as half of the SC's input polling
* frequency).
*/
enableok(q);
}
}
return (0);
}
/*
* cvc_ioctl()
* handle normal console ioctls.
*/
static void
{
int datasize;
int error = 0;
/*
* Let ttycommon_ioctl take the first shot at processing the ioctl. If
* it fails because it can't allocate memory, schedule processing of the
* ioctl later when a proper buffer is available. The mblk that
* couldn't be processed will have been stored in the tty structure by
* ttycommon_ioctl.
*/
if (datasize != 0) {
if (cp->cvc_wbufcid) {
}
return;
}
/*
* ttycommon_ioctl didn't do anything, but there's nothing we really
* support either with the exception of TCSBRK, which is supported
* only to appear a bit more like a serial device for software that
* expects TCSBRK to work.
*/
if (error != 0) {
} else {
}
} else {
}
}
/*
* cvc_redir()
* called from cvcredir:cvcr_wput() to handle console input
* data. This routine puts the cvcredir write (downstream) data
* onto the cvc read (upstream) queues.
*/
int
{
/*
* This function shouldn't be called if cvcredir hasn't registered yet.
*/
if (cvcinput_q == NULL) {
/*
* Need to let caller know that it may be necessary for them to
* free the message buffer, so return 0.
*/
return (0);
}
/*
* XXX - should canputnext be called here? Starfire's cvc
* doesn't do that, and it appears to work anyway.
*/
/*
* The cvcredir driver filters out ioctl mblks we wouldn't
* understand, so we don't have to check for every conceivable
* ioc_cmd. However, additional ioctls may be supported (again)
* some day, so the code is structured to check the value even
* though there's only one that is currently supported.
*/
}
} else {
/*
* Since we don't know what this mblk is, we're not going to
* process it.
*/
rv = 0;
}
return (rv);
}
/*
* cvc_register()
* called from cvcredir to register it's queues. cvc
* receives data from cn via the streamhead and sends it to cvcredir
* via pointers to cvcredir's queues.
*/
int
{
if (cvcinput_q == NULL)
if (cvcoutput_q == NULL) {
error = 0;
} else {
/*
* cmn_err will call us, so release lock.
*/
if (cvcoutput_q == q)
else
(void *)q);
return (error);
}
return (error);
}
/*
* cvc_unregister()
* called from cvcredir to clear pointers to its queues.
* cvcredir no longer wants to send or receive data.
*/
void
{
if (q == cvcoutput_q) {
cvcoutput_q = NULL;
} else {
(void *)q);
return;
}
}
/*
* cvc_reioctl()
* Retry an "ioctl", now that "bufcall" claims we may be able
* to allocate the buffer we need.
*/
static void
{
register queue_t *q;
/*
* The bufcall is no longer pending.
*/
if (!cp->cvc_wbufcid) {
return;
}
cp->cvc_wbufcid = 0;
return;
}
/* not pending any more */
}
}
/*
* cvc_iosram_ops()
* Process commands sent to cvc from netcon_server via IOSRAM
*/
static void
{
/*
* If this is a repeated notice of a command that was previously
* processed but couldn't be cleared due to EAGAIN (tunnel switch in
* progress), just clear the data_valid flag and return.
*/
IOSRAM_INT_NONE) == 0) {
stale_op = 0;
}
return;
}
stale_op = 0;
switch (op) {
case CVC_IOSRAM_BREAK: /* A console break (L1-A) */
abort_sequence_enter((char *)NULL);
break;
case CVC_IOSRAM_DISCONNECT: /* Break connection, hang up */
if (cvcinput_q)
break;
case CVC_IOSRAM_VIA_NET: /* console via network */
via_iosram = 0;
break;
case CVC_IOSRAM_VIA_IOSRAM: /* console via iosram */
via_iosram = 1;
/*
* Tell cvcd to close any network connection it has.
*/
if (cvcoutput_q != NULL) {
}
break;
case CVC_IOSRAM_WIN_RESIZE: /* console window size data */
/*
* In the case of window resizing, we don't want to
* record a stale_op value because we should always use
* the most recent winsize info, which could change
* between the time that we fail to clear the flag and
* the next time we try to process the command. So,
* we'll just let cvc_win_resize clear the data_valid
* flag itself (hence the TRUE parameter) and not worry
* about whether or not it succeeds.
*/
return;
/* NOTREACHED */
default:
break;
}
/*
* Clear CONC's data_valid flag to indicate that the chunk is available
* for further communications. If the flag can't be cleared due to an
* error, record the op value so we'll know to ignore it when we see it
* on the next poll.
*/
if (rval != 0) {
"cvc_iosram_ops: set flag for cntlbuf ret %d",
rval);
}
}
}
/*
* cvc_send_to_iosram()
* Flush as much data as possible to the CONO chunk. If successful, free
* any mblks that were completely transmitted, update the b_rptr field in
* the first remaining mblk if it was partially transmitted, and update the
* caller's pointer to the new head of the mblk chain. Since the software
* that will be pulling this data out of IOSRAM (dxs on the SC) is just
* polling at some frequency, we avoid attempts to flush data to IOSRAM any
* faster than a large divisor of that polling frequency.
*
* Note that "cvc_buf_t out" is only declared "static" to keep it from
* being allocated on the stack. Allocating 1K+ structures on the stack
* seems rather antisocial.
*/
static void
{
int rval;
/*
* We _do_ have something to do, right?
*/
return;
}
/*
* We can actually increase throughput by throttling back on attempts to
* flush data to IOSRAM, since trying to write every little bit of data
* as it shows up will actually generate more delays waiting for the SC
* to pick up each of those bits. Instead, we'll avoid attempting to
* write data to IOSRAM any faster than half of the polling frequency we
* expect the SC to be using.
*/
if (ddi_get_lbolt() - last_flush <
return;
}
/*
* If IOSRAM is inaccessible or the CONO chunk still holds data that
* hasn't been picked up by the SC, there's nothing we can do right now.
*/
rval);
}
return;
}
/*
* Copy up to MAX_XFER_COUTPUT chars from the mblk chain into a buffer.
* Don't change any of the mblks just yet, since we can't be certain
* that we'll be successful in writing data to the CONO chunk.
*/
/*
* Process as many of the characters in the current mblk as
* possible.
*/
}
/*
* Did we process that entire mblk? If so, move on to the next
* one. If not, we're done filling the buffer even if there's
* space left, because apparently there wasn't room to process
* the next character.
*/
break;
}
/*
* When this loop terminates, last_empty_mp will point to the
* last mblk that was completely processed, mp will point to the
* following mblk (or NULL if no more mblks exist), and cp will
* point to the first untransmitted character in the mblk
* pointed to by mp. We'll need this data to update the mblk
* chain if all of the data is successfully transmitted.
*/
last_empty_mp = mp;
}
/*
* If we succeeded in preparing some data, try to transmit it through
* IOSRAM. First write the count and the data, which can be done in a
* single operation thanks to the buffer structure we use, then set the
* data_valid flag if the first step succeeded.
*/
}
/* if the data write succeeded, set the data_valid flag */
if (rval == 0) {
"cvc_putc: set flags for outbuf ret %d",
rval);
}
}
/*
* If we successfully transmitted any data, modify the caller's
* mblk chain to remove the data that was transmitted, freeing
* all mblks that were completely processed.
*/
if (rval == 0) {
last_flush = ddi_get_lbolt();
/*
* If any data is left over, update the b_rptr field of
* the first remaining mblk in case some of its data was
* processed.
*/
}
/*
* If any mblks have been emptied, unlink them from the
* residual chain, free them, and update the caller's
* mblk pointer.
*/
if (last_empty_mp != NULL) {
}
}
}
}
/*
* cvc_flush_queue()
* Tell the STREAMS subsystem to schedule cvc_wsrv to process the queue we
* use to gather console output.
*/
/* ARGSUSED */
static void
{
if (cvcinput_q != NULL) {
}
}
/*
* cvc_getstr()
* Poll IOSRAM for console input while available.
*/
static void
{
short count;
while (dvalid == IOSRAM_DATA_INVALID) {
/*
* Check the CONC data_valid flag to see if a control message is
* available.
*/
"cvc_getstr: get flag for cntl ret %d", rval);
}
/*
* If a control message is available, try to read and process
* it.
*/
/* read the control reg offset */
"cvc_getstr: read for command ret %d",
rval);
}
/* process the cntl msg and clear the data_valid flag */
if (rval == 0) {
}
}
/*
* Check the CONI data_valid flag to see if console input data
* is available.
*/
"cvc_getstr: get flag for inbuf ret %d",
rval);
}
goto retry;
}
/*
* Try to read the count.
*/
if (rval != 0) {
"cvc_getstr: read for count ret %d", rval);
}
goto retry;
}
/*
* If there is data to be read, try to read it.
*/
if (count != 0) {
if (rval != 0) {
"cvc_getstr: read for count ret %d",
rval);
}
goto retry;
}
}
/*
* Try to clear the data_valid flag to indicate that whatever
* was in CONI was read successfully. If successful, and some
* data was read, break out of the loop to return to the caller.
*/
if (rval != 0) {
"cvc_getstr: set flag for inbuf ret %d",
rval);
}
} else if (count != 0) {
break;
}
/*
* Use a smaller delay between checks of IOSRAM for input
* been set.
* We don't go away completely when i/o is going through the
* network via cvcd since a command may be sent via IOSRAM
* to switch if the network is down or hung.
*/
else
}
}
/*
* cvc_input_daemon()
* this function runs as a separate kernel thread and polls IOSRAM for
* input, and possibly put it on read stream for the console.
* There are two poll rates (implemented in cvc_getstr):
* 100 000 uS (10 Hz) - no cvcd communications || via_iosram
* 1000 000 uS ( 1 Hz) - cvcd communications
* This continues to run even if there are network console communications
* in order to handle out-of-band signaling.
*/
/* ARGSUSED */
static void
cvc_input_daemon(void)
{
char *cp;
int c;
int dropped_read = 0;
for (;;) {
if (!dropped_read) {
"dropping IOSRAM reads");
}
dropped_read++;
continue;
}
if (dropped_read) {
"cvc_input_daemon: dropped %d IOSRAM reads",
dropped_read = 0;
}
c = (int)*cp;
if (c == '\r')
c = '\n';
c &= 0177;
}
if (input_ok) {
if (cvcinput_q == NULL) {
"cvc_input_daemon: cvcinput_q is NULL!");
} else {
/*
* XXX - should canputnext be called here?
* Starfire's cvc doesn't do that, and it
* appears to work anyway.
*/
}
} else {
}
}
/* NOTREACHED */
}
/*
* cvc_win_resize()
* cvc_win_resize will read winsize data from the CONC IOSRAM chunk and set
* the console window size accordingly. If indicated by the caller, CONC's
* data_valid flag will also be cleared. The flag isn't cleared in all
* cases because we need to process winsize data at startup without waiting
* for a command.
*/
static void
{
int rval;
/*
* Start by reading the new window size out of the CONC chunk and, if
* requested, clearing CONC's data_valid flag. If any of that fails,
* return immediately. (Note that the rather bulky condition in the
* two "if" statements takes advantage of C's short-circuit logic
* evaluation)
*/
"cvc_win_resize: read for ctlbuf ret %d", rval);
}
return;
}
IOSRAM_DATA_INVALID, IOSRAM_INT_NONE)) != 0)) {
"cvc_win_resize: set_flag for ctlbuf ret %d", rval);
}
return;
}
/*
* Copy the parameters from IOSRAM to a winsize struct.
*/
/*
* This code was taken from Starfire, and it appears to work correctly.
* However, since the original developer felt it necessary to add the
* following comment, it's probably worth preserving:
*
* XXX I hope this is safe...
*/
sizeof (struct winsize))) {
SIGWINCH);
} else {
}
}
#ifdef DEBUG
void
{
char *s = NULL;
switch (flag) {
case CVC_DBG_ATTACH:
s = "attach";
break;
case CVC_DBG_DETACH:
s = "detach";
break;
case CVC_DBG_OPEN:
s = "open";
break;
case CVC_DBG_CLOSE:
s = "close";
break;
case CVC_DBG_IOCTL:
s = "ioctl";
break;
case CVC_DBG_REDIR:
s = "redir";
break;
case CVC_DBG_WPUT:
s = "wput";
break;
case CVC_DBG_WSRV:
s = "wsrv";
break;
case CVC_DBG_IOSRAM_WR:
s = "iosram_wr";
break;
case CVC_DBG_IOSRAM_RD:
s = "iosram_rd";
break;
case CVC_DBG_NETWORK_WR:
s = "network_wr";
break;
case CVC_DBG_NETWORK_RD:
s = "network_rd";
break;
case CVC_DBG_IOSRAM_CNTL:
s = "iosram_cntlmsg";
break;
default:
s = "Unknown debug flag";
break;
}
s, cvc_instance, fmt);
}
}
#endif /* DEBUG */