/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * MT STREAMS Virtual Console Device Driver */ #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 #include #include #include #include #include #include static int cvc_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static int cvc_attach(dev_info_t *, ddi_attach_cmd_t); static int cvc_detach(dev_info_t *, ddi_detach_cmd_t); static int cvc_open(register queue_t *, dev_t *, int, int, cred_t *); static int cvc_close(queue_t *, int, cred_t *); static int cvc_wput(queue_t *, mblk_t *); static int cvc_wsrv(queue_t *); static void cvc_ioctl(queue_t *, mblk_t *); static void cvc_reioctl(void *); static void cvc_input_daemon(void); static void cvc_send_to_iosram(mblk_t **chainpp); 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 TRUE 1 #define FALSE 0 #endif /* * Private copy of devinfo pointer; cvc_info uses it. */ static dev_info_t *cvcdip; /* * 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 { ushort_t count; uchar_t buffer[MAX_XFER_COUTPUT]; } cvc_buf_t; typedef struct cvc_s { bufcall_id_t cvc_wbufcid; tty_common_t cvc_tty; } cvc_t; cvc_t cvc_common_tty; static struct module_info cvcm_info = { 1313, /* mi_idnum Bad luck number ;-) */ "cvc", /* mi_idname */ 0, /* mi_minpsz */ INFPSZ, /* mi_maxpsz */ 2048, /* mi_hiwat */ 2048 /* mi_lowat */ }; static struct qinit cvcrinit = { NULL, /* qi_putp */ NULL, /* qi_srvp */ cvc_open, /* qi_qopen */ cvc_close, /* qi_qclose */ NULL, /* qi_qadmin */ &cvcm_info, /* qi_minfo */ NULL /* qi_mstat */ }; static struct qinit cvcwinit = { 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 */ }; struct streamtab cvcinfo = { &cvcrinit, /* st_rdinit */ &cvcwinit, /* st_wrinit */ NULL, /* st_muxrinit */ NULL /* st_muxwrinit */ }; static krwlock_t cvclock; /* lock protecting everything here */ static queue_t *cvcinput_q; /* queue for console input */ static queue_t *cvcoutput_q; /* queue for console output */ static int cvc_instance = -1; static int cvc_stopped = 0; static int cvc_suspended = 0; kthread_id_t cvc_input_daemon_thread; /* just to aid debugging */ static kmutex_t cvcmutex; /* protects input */ static kmutex_t cvc_iosram_input_mutex; /* protects IOSRAM inp buff */ static int input_ok = 0; /* true when stream is valid */ static int via_iosram = 0; /* toggle switch */ static timeout_id_t cvc_timeout_id = (timeout_id_t)-1; static int input_daemon_started = 0; /* debugging functions */ #ifdef DEBUG uint32_t cvc_dbg_flags = 0x0; static void cvc_dbg(uint32_t flag, char *fmt, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5); #endif /* * Module linkage information for the kernel. */ DDI_DEFINE_STREAM_OPS(cvcops, nulldev, nulldev, cvc_attach, cvc_detach, nodev, cvc_info, (D_NEW|D_MTPERQ|D_MP), &cvcinfo, ddi_quiesce_not_supported); extern int nodev(), nulldev(); extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a pseudo driver */ "CVC driver 'cvc'", &cvcops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init() { int status; status = mod_install(&modlinkage); if (status == 0) { mutex_init(&cvcmutex, NULL, MUTEX_DEFAULT, NULL); } return (status); } int _fini() { return (EBUSY); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * DDI glue routines. */ /* ARGSUSED */ static int cvc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { static char been_here = 0; if (cmd == DDI_RESUME) { cvc_suspended = 0; if (cvcinput_q != NULL) { qenable(WR(cvcinput_q)); } return (DDI_SUCCESS); } mutex_enter(&cvcmutex); if (!been_here) { been_here = 1; mutex_init(&cvc_iosram_input_mutex, NULL, MUTEX_DEFAULT, NULL); rw_init(&cvclock, NULL, RW_DRIVER, NULL); cvc_instance = ddi_get_instance(devi); } else { #if defined(DEBUG) cmn_err(CE_NOTE, "cvc_attach: called multiple times!! (instance = %d)", ddi_get_instance(devi)); #endif /* DEBUG */ mutex_exit(&cvcmutex); return (DDI_SUCCESS); } mutex_exit(&cvcmutex); if (ddi_create_minor_node(devi, "cvc", S_IFCHR, 0, NULL, NULL) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (-1); } cvcdip = devi; cvcinput_q = NULL; cvcoutput_q = NULL; CVC_DBG0(CVC_DBG_ATTACH, "Attached"); return (DDI_SUCCESS); } static int cvc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { 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. */ if (cvc_instance == ddi_get_instance(dip)) { ddi_remove_minor_node(dip, NULL); } } CVC_DBG0(CVC_DBG_DETACH, "Detached"); return (DDI_SUCCESS); } /* ARGSUSED */ static int cvc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { register int error; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (cvcdip == NULL) { error = DDI_FAILURE; } else { *result = (void *)cvcdip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)0; error = DDI_SUCCESS; break; default: error = DDI_FAILURE; } return (error); } /* ARGSUSED */ static int cvc_open(register queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp) { register int unit = getminor(*devp); register int err = DDI_SUCCESS; tty_common_t *tty; cvc_t *cp; if (unit != 0) return (ENXIO); if (q->q_ptr) return (0); cp = (cvc_t *)&cvc_common_tty; bzero((caddr_t)cp, sizeof (cvc_t)); cp->cvc_wbufcid = 0; tty = &cp->cvc_tty; tty->t_readq = q; tty->t_writeq = WR(q); WR(q)->q_ptr = q->q_ptr = (caddr_t)cp; cvcinput_q = RD(q); /* save for cvc_redir */ qprocson(q); mutex_enter(&cvcmutex); 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; mutex_exit(&cvcmutex); cvc_input_daemon_thread = thread_create(NULL, 0, cvc_input_daemon, NULL, 0, &p0, TS_RUN, minclsyspri); CVC_DBG0(CVC_DBG_IOSRAM_RD, "Started input daemon"); } else { mutex_exit(&cvcmutex); } /* * Set the console window size. */ mutex_enter(&cvc_iosram_input_mutex); cvc_win_resize(FALSE); mutex_exit(&cvc_iosram_input_mutex); CVC_DBG0(CVC_DBG_OPEN, "Plumbed successfully"); return (err); } /* ARGSUSED */ static int cvc_close(queue_t *q, int flag, cred_t *crp) { register int err = DDI_SUCCESS; register cvc_t *cp; mutex_enter(&cvcmutex); input_ok = 0; mutex_exit(&cvcmutex); cp = q->q_ptr; if (cp->cvc_wbufcid != 0) { unbufcall(cp->cvc_wbufcid); } ttycommon_close(&cp->cvc_tty); WR(q)->q_ptr = q->q_ptr = NULL; cvcinput_q = NULL; bzero((caddr_t)cp, sizeof (cvc_t)); qprocsoff(q); CVC_DBG0(CVC_DBG_CLOSE, "Un-plumbed successfully"); 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 cvc_wput(queue_t *q, mblk_t *mp) { int error = 0; rw_enter(&cvclock, RW_READER); CVC_DBG2(CVC_DBG_WPUT, "mp 0x%x db_type 0x%x", mp, mp->b_datap->db_type); switch (mp->b_datap->db_type) { case M_IOCTL: case M_CTL: { struct iocblk *iocp = (struct iocblk *)mp->b_rptr; switch (iocp->ioc_cmd) { /* * 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: (void) putq(q, mp); break; default: cvc_ioctl(q, mp); } break; } case M_FLUSH: if (*mp->b_rptr & FLUSHW) { /* * Flush our write queue. */ flushq(q, FLUSHDATA); *mp->b_rptr &= ~FLUSHW; } if (*mp->b_rptr & FLUSHR) { flushq(RD(q), FLUSHDATA); qreply(q, mp); } else freemsg(mp); break; case M_STOP: cvc_stopped = 1; freemsg(mp); break; case M_START: cvc_stopped = 0; freemsg(mp); qenable(q); /* Start up delayed messages */ break; case M_READ: /* * ldterm handles this (VMIN/VTIME processing). */ freemsg(mp); break; default: cmn_err(CE_WARN, "cvc_wput: unexpected mblk type - mp =" " 0x%p, type = 0x%x", (void *)mp, mp->b_datap->db_type); freemsg(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. */ if (q->q_first != NULL || cvcoutput_q == NULL || via_iosram || cvc_stopped == 1 || cvc_suspended == 1) { (void) putq(q, mp); } else { /* * XXX - should canputnext be called here? * Starfire's cvc doesn't do that, and it * appears to work anyway. */ (void) putnext(cvcoutput_q, mp); } break; } rw_exit(&cvclock); 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 cvc_wsrv(queue_t *q) { mblk_t *total_mp = NULL; mblk_t *mp; if (cvc_stopped == 1 || cvc_suspended == 1) { return (0); } rw_enter(&cvclock, RW_READER); 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. */ if (cvc_timeout_id != (timeout_id_t)-1) { enableok(q); rw_exit(&cvclock); 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). */ while ((mp = getq(q)) != NULL) { /* * 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.) */ if (mp->b_datap->db_type == M_IOCTL) { cvc_ioctl(q, mp); 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. */ if (total_mp != NULL) { linkb(total_mp, mp); } else { total_mp = mp; } } /* * Do we actually have anything to do? */ if (total_mp == NULL) { enableok(q); rw_exit(&cvclock); 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. */ if (cvcoutput_q != NULL && !via_iosram) { CVC_DBG0(CVC_DBG_NETWORK_WR, "Sending to cvcredir."); /* * XXX - should canputnext be called here? Starfire's cvc * doesn't do that, and it appears to work anyway. */ (void) putnext(cvcoutput_q, total_mp); } else { CVC_DBG0(CVC_DBG_IOSRAM_WR, "Send to IOSRAM."); cvc_send_to_iosram(&total_mp); if (total_mp != NULL) { (void) putbq(q, total_mp); } } /* * 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); if (q->q_first != NULL) { if (cvc_timeout_id == (timeout_id_t)-1) { cvc_timeout_id = timeout(cvc_flush_queue, NULL, drv_usectohz(CVC_IOSRAM_POLL_USECS / 2)); } } rw_exit(&cvclock); return (0); } /* * cvc_ioctl() * handle normal console ioctls. */ static void cvc_ioctl(register queue_t *q, register mblk_t *mp) { register cvc_t *cp = q->q_ptr; 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. */ datasize = ttycommon_ioctl(&cp->cvc_tty, q, mp, &error); if (datasize != 0) { if (cp->cvc_wbufcid) { unbufcall(cp->cvc_wbufcid); } cp->cvc_wbufcid = bufcall(datasize, BPRI_HI, cvc_reioctl, cp); 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) { struct iocblk *iocp = (struct iocblk *)mp->b_rptr; if (iocp->ioc_cmd == TCSBRK) { miocack(q, mp, 0, 0); } else { miocnak(q, mp, 0, EINVAL); } } else { qreply(q, mp); } } /* * 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 cvc_redir(mblk_t *mp) { register struct iocblk *iocp; int rv = 1; /* * 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. */ CVC_DBG0(CVC_DBG_REDIR, "redirection not enabled"); cmn_err(CE_WARN, "cvc_redir: cvcinput_q NULL!"); return (0); } CVC_DBG1(CVC_DBG_REDIR, "type 0x%x", mp->b_datap->db_type); if (mp->b_datap->db_type == M_DATA) { /* * XXX - should canputnext be called here? Starfire's cvc * doesn't do that, and it appears to work anyway. */ CVC_DBG1(CVC_DBG_NETWORK_RD, "Sending mp 0x%x", mp); (void) putnext(cvcinput_q, mp); } else if (mp->b_datap->db_type == M_IOCTL) { /* * 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. */ iocp = (struct iocblk *)mp->b_rptr; if (iocp->ioc_cmd == CVC_DISCONNECT) { (void) putnextctl(cvcinput_q, M_HANGUP); } } else { /* * Since we don't know what this mblk is, we're not going to * process it. */ CVC_DBG1(CVC_DBG_REDIR, "unrecognized mblk type: %d", mp->b_datap->db_type); 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 cvc_register(queue_t *q) { int error = -1; if (cvcinput_q == NULL) cmn_err(CE_WARN, "cvc_register: register w/ no console open!"); rw_enter(&cvclock, RW_WRITER); if (cvcoutput_q == NULL) { cvcoutput_q = RD(q); /* Make sure its the upstream q */ qprocson(cvcoutput_q); /* must be done within cvclock */ error = 0; } else { /* * cmn_err will call us, so release lock. */ rw_exit(&cvclock); if (cvcoutput_q == q) cmn_err(CE_WARN, "cvc_register: duplicate q!"); else cmn_err(CE_WARN, "cvc_register: nondup q = 0x%p", (void *)q); return (error); } rw_exit(&cvclock); return (error); } /* * cvc_unregister() * called from cvcredir to clear pointers to its queues. * cvcredir no longer wants to send or receive data. */ void cvc_unregister(queue_t *q) { rw_enter(&cvclock, RW_WRITER); if (q == cvcoutput_q) { qprocsoff(cvcoutput_q); /* must be done within cvclock */ cvcoutput_q = NULL; } else { rw_exit(&cvclock); cmn_err(CE_WARN, "cvc_unregister: q = 0x%p not registered", (void *)q); return; } rw_exit(&cvclock); } /* * cvc_reioctl() * Retry an "ioctl", now that "bufcall" claims we may be able * to allocate the buffer we need. */ static void cvc_reioctl(void *unit) { register queue_t *q; register mblk_t *mp; register cvc_t *cp = (cvc_t *)unit; /* * The bufcall is no longer pending. */ if (!cp->cvc_wbufcid) { return; } cp->cvc_wbufcid = 0; if ((q = cp->cvc_tty.t_writeq) == NULL) { return; } if ((mp = cp->cvc_tty.t_iocpending) != NULL) { /* not pending any more */ cp->cvc_tty.t_iocpending = NULL; cvc_ioctl(q, mp); } } /* * cvc_iosram_ops() * Process commands sent to cvc from netcon_server via IOSRAM */ static void cvc_iosram_ops(uint8_t op) { int rval = ESUCCESS; static uint8_t stale_op = 0; ASSERT(MUTEX_HELD(&cvc_iosram_input_mutex)); CVC_DBG1(CVC_DBG_IOSRAM_CNTL, "cntl msg 0x%x", op); /* * 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. */ if (op == stale_op) { if (iosram_set_flag(IOSRAM_KEY_CONC, IOSRAM_DATA_INVALID, 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) (void) putnextctl(cvcinput_q, M_HANGUP); 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. */ rw_enter(&cvclock, RW_READER); if (cvcoutput_q != NULL) { (void) putnextctl(cvcoutput_q, M_HANGUP); } rw_exit(&cvclock); 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. */ cvc_win_resize(TRUE); return; /* NOTREACHED */ default: cmn_err(CE_WARN, "cvc: unknown IOSRAM opcode %d", op); 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. */ rval = iosram_set_flag(IOSRAM_KEY_CONC, IOSRAM_DATA_INVALID, IOSRAM_INT_NONE); if (rval != 0) { stale_op = op; if (rval != EAGAIN) { cmn_err(CE_WARN, "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 cvc_send_to_iosram(mblk_t **chainpp) { int rval; uint8_t dvalid; uchar_t *cp; mblk_t *mp; mblk_t *last_empty_mp; static clock_t last_flush = (clock_t)-1; static cvc_buf_t out; /* see note above about static */ ASSERT(chainpp != NULL); /* * We _do_ have something to do, right? */ if (*chainpp == NULL) { 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 < drv_usectohz(CVC_IOSRAM_POLL_USECS / 2)) { 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 = iosram_get_flag(IOSRAM_KEY_CONO, &dvalid, NULL); if ((rval != 0) || (dvalid == IOSRAM_DATA_VALID)) { if ((rval != 0) && (rval != EAGAIN)) { cmn_err(CE_WARN, "cvc_send_to_iosram: get_flag ret %d", 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. */ out.count = 0; mp = *chainpp; cp = mp->b_rptr; last_empty_mp = NULL; while ((mp != NULL) && (out.count < MAX_XFER_COUTPUT)) { /* * Process as many of the characters in the current mblk as * possible. */ while ((cp != mp->b_wptr) && (out.count < MAX_XFER_COUTPUT)) { out.buffer[out.count++] = *cp++; } /* * 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. */ if (cp != mp->b_wptr) { 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; mp = mp->b_cont; cp = (mp != NULL) ? mp->b_rptr : NULL; } /* * 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 (out.count != 0) { rval = iosram_wr(IOSRAM_KEY_CONO, COUNT_OFFSET, CONSBUF_COUNT_SIZE + out.count, (caddr_t)&out); if ((rval != 0) && (rval != EAGAIN)) { cmn_err(CE_WARN, "cvc_putc: write ret %d", rval); } /* if the data write succeeded, set the data_valid flag */ if (rval == 0) { rval = iosram_set_flag(IOSRAM_KEY_CONO, IOSRAM_DATA_VALID, IOSRAM_INT_NONE); if ((rval != 0) && (rval != EAGAIN)) { cmn_err(CE_WARN, "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 (mp != NULL) { mp->b_rptr = cp; } /* * 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) { last_empty_mp->b_cont = NULL; freemsg(*chainpp); *chainpp = mp; } } } } /* * cvc_flush_queue() * Tell the STREAMS subsystem to schedule cvc_wsrv to process the queue we * use to gather console output. */ /* ARGSUSED */ static void cvc_flush_queue(void *notused) { rw_enter(&cvclock, RW_WRITER); if (cvcinput_q != NULL) { qenable(WR(cvcinput_q)); } cvc_timeout_id = (timeout_id_t)-1; rw_exit(&cvclock); } /* * cvc_getstr() * Poll IOSRAM for console input while available. */ static void cvc_getstr(char *cp) { short count; uint8_t command = 0; int rval = ESUCCESS; uint8_t dvalid = IOSRAM_DATA_INVALID; uint8_t intrpending = 0; mutex_enter(&cvc_iosram_input_mutex); while (dvalid == IOSRAM_DATA_INVALID) { /* * Check the CONC data_valid flag to see if a control message is * available. */ rval = iosram_get_flag(IOSRAM_KEY_CONC, &dvalid, &intrpending); if ((rval != 0) && (rval != EAGAIN)) { cmn_err(CE_WARN, "cvc_getstr: get flag for cntl ret %d", rval); } /* * If a control message is available, try to read and process * it. */ if ((dvalid == IOSRAM_DATA_VALID) && (rval == 0)) { /* read the control reg offset */ rval = iosram_rd(IOSRAM_KEY_CONC, CVC_CTL_OFFSET(command), CVC_CTL_SIZE(command), (caddr_t)&command); if ((rval != 0) && (rval != EAGAIN)) { cmn_err(CE_WARN, "cvc_getstr: read for command ret %d", rval); } /* process the cntl msg and clear the data_valid flag */ if (rval == 0) { cvc_iosram_ops(command); } } /* * Check the CONI data_valid flag to see if console input data * is available. */ rval = iosram_get_flag(IOSRAM_KEY_CONI, &dvalid, &intrpending); if ((rval != 0) && (rval != EAGAIN)) { cmn_err(CE_WARN, "cvc_getstr: get flag for inbuf ret %d", rval); } if ((rval != 0) || (dvalid != IOSRAM_DATA_VALID)) { goto retry; } /* * Try to read the count. */ rval = iosram_rd(IOSRAM_KEY_CONI, COUNT_OFFSET, CONSBUF_COUNT_SIZE, (caddr_t)&count); if (rval != 0) { if (rval != EAGAIN) { cmn_err(CE_WARN, "cvc_getstr: read for count ret %d", rval); } goto retry; } /* * If there is data to be read, try to read it. */ if (count != 0) { rval = iosram_rd(IOSRAM_KEY_CONI, DATA_OFFSET, count, (caddr_t)cp); if (rval != 0) { if (rval != EAGAIN) { cmn_err(CE_WARN, "cvc_getstr: read for count ret %d", rval); } goto retry; } cp[count] = '\0'; } /* * 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. */ rval = iosram_set_flag(IOSRAM_KEY_CONI, IOSRAM_DATA_INVALID, IOSRAM_INT_NONE); if (rval != 0) { if (rval != EAGAIN) { cmn_err(CE_WARN, "cvc_getstr: set flag for inbuf ret %d", rval); } } else if (count != 0) { CVC_DBG1(CVC_DBG_IOSRAM_RD, "Read 0x%x", count); break; } /* * Use a smaller delay between checks of IOSRAM for input * when cvcd/cvcredir are not running or "via_iosram" has * 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. */ retry: if ((cvcoutput_q == NULL) || (via_iosram)) delay(drv_usectohz(CVC_IOSRAM_POLL_USECS)); else delay(drv_usectohz(CVC_IOSRAM_POLL_USECS * 10)); } mutex_exit(&cvc_iosram_input_mutex); } /* * 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 linebuf[MAX_XFER_CINPUT + 1]; char *cp; mblk_t *mbp; int c; int dropped_read = 0; for (;;) { cvc_getstr(linebuf); mbp = allocb(strlen(linebuf), BPRI_MED); if (mbp == NULL) { /* drop it & go on if no buffer */ if (!dropped_read) { cmn_err(CE_WARN, "cvc_input_daemon: " "dropping IOSRAM reads"); } dropped_read++; continue; } if (dropped_read) { cmn_err(CE_WARN, "cvc_input_daemon: dropped %d IOSRAM reads", dropped_read); dropped_read = 0; } for (cp = linebuf; *cp != '\0'; cp++) { c = (int)*cp; if (c == '\r') c = '\n'; c &= 0177; *mbp->b_wptr = (char)c; mbp->b_wptr++; } mutex_enter(&cvcmutex); if (input_ok) { if (cvcinput_q == NULL) { cmn_err(CE_WARN, "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. */ (void) putnext(cvcinput_q, mbp); } } else { freemsg(mbp); } mutex_exit(&cvcmutex); } /* 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 cvc_win_resize(int clear_flag) { int rval; uint16_t rows; uint16_t cols; uint16_t xpixels; uint16_t ypixels; tty_common_t *tty; cvc_t *cp; struct winsize ws; /* * 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) */ if (((rval = iosram_rd(IOSRAM_KEY_CONC, CVC_CTL_OFFSET(winsize_rows), CVC_CTL_SIZE(winsize_rows), (caddr_t)&rows)) != 0) || ((rval = iosram_rd(IOSRAM_KEY_CONC, CVC_CTL_OFFSET(winsize_cols), CVC_CTL_SIZE(winsize_cols), (caddr_t)&cols)) != 0) || ((rval = iosram_rd(IOSRAM_KEY_CONC, CVC_CTL_OFFSET(winsize_xpixels), CVC_CTL_SIZE(winsize_xpixels), (caddr_t)&xpixels)) != 0) || ((rval = iosram_rd(IOSRAM_KEY_CONC, CVC_CTL_OFFSET(winsize_ypixels), CVC_CTL_SIZE(winsize_ypixels), (caddr_t)&ypixels)) != 0)) { if (rval != EAGAIN) { cmn_err(CE_WARN, "cvc_win_resize: read for ctlbuf ret %d", rval); } return; } if (clear_flag && ((rval = iosram_set_flag(IOSRAM_KEY_CONC, IOSRAM_DATA_INVALID, IOSRAM_INT_NONE)) != 0)) { if (rval != EAGAIN) { cmn_err(CE_WARN, "cvc_win_resize: set_flag for ctlbuf ret %d", rval); } return; } /* * Copy the parameters from IOSRAM to a winsize struct. */ ws.ws_row = rows; ws.ws_col = cols; ws.ws_xpixel = xpixels; ws.ws_ypixel = ypixels; /* * 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... */ cp = cvcinput_q->q_ptr; tty = &cp->cvc_tty; mutex_enter(&tty->t_excl); if (bcmp((caddr_t)&tty->t_size, (caddr_t)&ws, sizeof (struct winsize))) { tty->t_size = ws; mutex_exit(&tty->t_excl); (void) putnextctl1(cvcinput_q, M_PCSIG, SIGWINCH); } else { mutex_exit(&tty->t_excl); } } #ifdef DEBUG void cvc_dbg(uint32_t flag, char *fmt, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5) { char *s = NULL; char buf[256]; if (cvc_dbg_flags && ((cvc_dbg_flags & flag) == flag)) { 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; } (void) sprintf(buf, "!%s_%s(%d): %s", ddi_driver_name(cvcdip), s, cvc_instance, fmt); cmn_err(CE_NOTE, buf, a1, a2, a3, a4, a5); } } #endif /* DEBUG */