/*
* 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/sysmacros.h>
#include <sys/processor.h>
#include <sys/starfire.h>
#include <vm/seg_kmem.h>
#include <sys/cpu_sgnblk_defs.h>
extern void prom_printf(char *fmt, ...);
static void cvc_reioctl(void *);
static void cvc_input_daemon(void);
static void cvc_putc(register int);
static void cvc_flush_buf(void *);
static void cvc_bbsram_ops(volatile uchar_t *);
static void cvc_iobuf_mapout(processorid_t);
void cvc_assign_iocpu(processorid_t);
/*
* Private copy of devinfo pointer; cvc_info uses it.
*/
/*
* This buffer is used to manage mapping in the I/O buffer that CVC
* uses when communicating with the SSP Client (netcon_server) via bbsram.
*/
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 */
};
/* The bbsram control reg is located at the end of the I/O buffers */
+ CVC_IN_SIZE + CVC_OUT_SIZE))
static int cvc_stopped = 0;
static int cvc_suspended = 0;
static int cvc_hangup_ok = 0;
static int stop_timeout = 0;
/*
* Module linkage information for the kernel.
*/
&mod_driverops, /* Type of module. This one is a pseudo driver */
"CVC driver 'cvc'",
&cvcops, /* driver ops */
};
&modldrv,
};
int
_init(void)
{
int status;
if (status == 0) {
}
return (status);
}
int
_fini(void)
{
return (EBUSY);
}
int
{
}
/*
* DDI glue routines.
*/
/* ARGSUSED */
static int
{
static char been_here = 0;
if (cmd == DDI_RESUME) {
cvc_suspended = 0;
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
{
register int err = 0;
static int input_daemon_started;
if (unit != 0)
return (ENXIO);
if (q->q_ptr)
return (0);
cp->cvc_wbufcid = 0;
qprocson(q);
input_ok = 1;
if (!input_daemon_started) {
extern cpu_sgnblk_t *cpu_sgnblkp[];
input_daemon_started = 1;
} else {
}
#ifdef lint
#endif
return (err);
}
/* ARGSUSED */
static int
{
register int err = 0;
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
* by cvcredir (if there is a cvcd running) or bbsram (if there
* isn't).
* Data is sent to the cvcredir via it's read q which is cvcoutput_q
* and was set in cvc_register().
*/
static int
{
int error = 0;
case M_IOCTL:
case M_CTL:
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:
(void *)mp);
/* FALLTHROUGH */
#ifdef lint
break;
#endif
case M_DATA:
break;
}
/*
* Send it up past cvcredir module.
*/
} else {
char *msgp, c;
int count;
while (count > 0) {
count--;
if ((c = *msgp++) != '\0') {
/* don't print NULs */
cvc_putc(c);
}
}
}
}
break;
}
return (error);
}
static int cvc_wsrv_count = 0;
static int
{
return (0);
}
/*
* Send it up past cvcredir module.
*/
} else {
char *msgp, c;
int count;
while (count > 0) {
count--;
if ((c = *msgp++) != '\0') {
/* don't print NULs */
cvc_putc(c);
}
}
}
}
}
return (0);
}
/*
* cvc_ioctl()
* handle normal console ioctls.
*/
static void
{
int datasize;
int error = 0;
}
if (datasize != 0) {
if (cp->cvc_wbufcid)
return;
}
if (error < 0) {
/*
* "ttycommon_ioctl" didn't do anything; we process it here.
*/
error = 0;
/*
* Set modem bit ioctls. These are NOPs for us, since we
* dont control any hardware.
*/
case TCSBRK:
case TIOCSBRK:
case TIOCCBRK:
case TIOCMSET:
case TIOCMBIS:
case TIOCMBIC:
} else {
}
/* qreply done below */
break;
/*
* Get modem bits, we return 0 in mblk.
*/
case TIOCMGET:
return;
}
else
/* qreply done below */
break;
default:
/*
* If we don't understand it, it's an error. NAK it.
*/
break;
}
}
if (error != 0) {
}
}
/*
* 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. Note that if `mp' is
* an M_IOCTL, then it may be reused by the caller to send back
* an M_IOCACK or M_IOCNAK.
*/
int
{
int error;
if (cvcinput_q == NULL) {
return (EINVAL);
}
return (0);
}
if (error != 0)
return (error);
} else
} else {
/*
* It must be a CVC_DISCONNECT, send hangup.
*/
if (cvc_hangup_ok)
}
return (0);
}
/*
* 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);
}
/*
* Unless "via_bbsram" is set, i/o will be going through cvcd, so
* stop flushing output to BBSRAM.
*/
stop_timeout = 1;
(void) untimeout(cvc_timeout_id);
cvc_hangup_ok = 1;
}
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;
}
/*
* i/o will not be going through cvcd, start flushing output to
* BBSRAM
*/
stop_timeout = 0;
}
}
/*
* 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_bbsram_ops()
* Process commands sent to cvc from netcon_server via BBSRAM
*/
static void
{
return;
switch (op) {
case CVC_BBSRAM_BREAK: /* A console break (L1-A) */
abort_sequence_enter((char *)NULL);
break;
case CVC_BBSRAM_DISCONNECT: /* Break connection, hang up */
if (cvcinput_q && cvc_hangup_ok)
break;
case CVC_BBSRAM_VIA_NET: /* console via network */
via_bbsram = 0;
/*
* stop periodic flushing of output to BBSRAM
*/
if (cvcoutput_q != NULL) {
stop_timeout = 1;
(void) untimeout(cvc_timeout_id);
}
}
break;
case CVC_BBSRAM_VIA_BBSRAM: /* console via bbsram */
via_bbsram = 1;
/* start periodic flushing of ouput to BBSRAM */
stop_timeout = 0;
}
break;
case CVC_BBSRAM_CLOSE_NET:
/*
* Send a hangup control message upstream to cvcd
* thru cvcredir. This is an attempt to close
* out any existing network connection(if any).
* cvcoutput_q should point to the cvcredir's read
* queue.
*/
if (cvcoutput_q != NULL) {
}
break;
default:
(unsigned int)op);
break;
}
*op_reg = 0;
}
/*
* cvc_putc()
* Put a single character out to BBSRAM if space available.
*/
static void
cvc_putc(register int c)
{
static int output_lost = 0;
if (c == '\n')
cvc_putc('\r');
/*
* Just exit if the buffer is already full.
* It will be up to cvc_flush_buf() to flush the buffer.
*/
if (cvc_output_count == MAX_XFER_OUTPUT) {
output_lost = 1;
return;
}
if (output_lost)
prom_printf("WARNING: overflow of cvc output buffer, "
"output lost!");
output_lost = 0;
cvc_output_buffer[cvc_output_count] = (unsigned char)c;
/* flush cvc's internal output buffer to BBSRAM */
/*
* Wait for the BBSRAM output buffer to be emptied.
* This may hang if netcon_server isn't running on the SSP
*/
while ((BBSRAM_OUTPUT_COUNT != 0) && --maxspin) {
if (stop_bbsram) {
return;
}
DELAY(1000);
}
cvc_output_count = 0;
}
}
/*
* cvc_flush_buf()
* Flush cvc's internal output buffer to BBSRAM at regular intervals.
* This should only be done if cvcd is not running or the user (via the cvc
* application on the SSP) has requested that i/o go through BBSRAM.
*/
/* ARGSUSED */
static void
{
if (stop_timeout)
return;
if (cvc_output_count != 0) {
/*
* Wait for the BBSRAM output buffer to be emptied.
* This may hang if netcon_server isn't running on the SSP.
*/
while ((BBSRAM_OUTPUT_COUNT != 0) && --maxspin) {
if (stop_bbsram)
goto exit;
DELAY(1000);
}
cvc_output_count = 0;
}
exit:
/* rw_enter(&cvclock, RW_WRITER); */
/* rw_exit(&cvclock); */
}
/*
* cvc_getstr()
* Poll BBSRAM for console input while available.
*/
static void
{
short count;
volatile char *lp;
/* Poll BBSRAM for input */
do {
if (stop_bbsram) {
return;
}
/*
* Use a smaller delay between checks of BBSRAM 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 BBSRAM
* to switch if the network is down or hung.
*/
else
} while (count == 0);
while (count--) {
}
*cp = '\0';
BBSRAM_INPUT_COUNT = 0;
}
/*
* cvc_input_daemon()
* this function runs as a separate kernel thread and polls BBSRAM 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_bbsram
* 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.
*/
static void
cvc_input_daemon(void)
{
char *cp;
int c;
int dropped_read = 0;
for (;;) {
if (!dropped_read) {
"cvc_input_daemon: "
"dropping BBSRAM reads\n");
}
dropped_read++;
continue;
}
if (dropped_read) {
"cvc_input_daemon: dropped %d BBSRAM reads\n",
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 {
}
} else {
}
}
/* NOTREACHED */
}
/*
* cvc_bbsram_stop()
* Prevents accesses to BBSRAM. used by cvc_assign_iocpu() when
* mapping in BBSRAM to a virtual address.
*/
static void
cvc_bbsram_stop(void)
{
stop_bbsram = 1;
}
/*
* cvc_bbsram_start()
* Allow accesses to BBSRAM, used by cvc_assign_iocpu() after
* BBSRAM has been mapped to a virtual address.
*/
static void
cvc_bbsram_start(void)
{
stop_bbsram = 0;
}
/*
* cvc_assign_iocpu()
* Map in BBSRAM to a virtual address
* This called by the kernel with the cpu id of cpu zero.
*/
void
{
return;
if (oldcpu != -1)
}
/*
* cvc_iobuf_mapin()
* Map in the cvc bbsram i/o buffer into kernel space.
*/
static caddr_t
{
extern cpu_sgnblk_t *cpu_sgnblkp[];
/*
* First construct the physical base address of the bbsram
* in Starfire PSI space associated with this cpu in question.
*/
/*
* Next add the cvc i/o buffer offset obtained from the
* sigblock to get cvc iobuf physical address
*/
/* Get the page frame number */
/* Calculate how many pages we need to map in */
& MMU_PAGEOFFSET) + sizeof (sigb_cvc_t)));
/*
* Map in the cvc iobuf
*/
& MMU_PAGEOFFSET)));
}
/*
* cvc_iobuf_mapout()
* Map out the cvc iobuf from kernel space
*/
static void
{
/* already unmapped - return */
return;
}
/* Calculate how many pages we need to map out */
sizeof (sigb_cvc_t)));
/* Get cvaddr to the start of the page boundary */
}