/* * 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 2008 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 #include #include #include #include #include #include extern void prom_printf(char *fmt, ...); 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_ack(mblk_t *, mblk_t *, uint_t); 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 caddr_t cvc_iobuf_mapin(processorid_t); static void cvc_iobuf_mapout(processorid_t); void cvc_assign_iocpu(processorid_t); /* * Private copy of devinfo pointer; cvc_info uses it. */ static dev_info_t *cvcdip; /* * 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. */ static caddr_t cvc_iobufp[NCPU]; 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 */ }; #define TIMEOUT_DELAY 100000 #define BBSRAM_INPUT_BUF ((volatile char *)(cvc_iobufp[cvc_iocpu] \ + BBSRAM_INPUT_COUNT_OFF)) #define BBSRAM_OUTPUT_BUF ((volatile char *)(cvc_iobufp[cvc_iocpu] \ + BBSRAM_OUTPUT_COUNT_OFF)) #define BBSRAM_INPUT_COUNT (*((volatile short *)BBSRAM_INPUT_BUF)) #define BBSRAM_OUTPUT_COUNT (*((volatile short *)BBSRAM_OUTPUT_BUF)) #define CVC_OUT_MAXSPIN 1024 /* The bbsram control reg is located at the end of the I/O buffers */ #define BBSRAM_CONTROL_REG ((volatile uchar_t *)(cvc_iobufp[cvc_iocpu] \ + CVC_IN_SIZE + CVC_OUT_SIZE)) 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; static int cvc_hangup_ok = 0; static kthread_id_t cvc_input_daemon_thread; static kmutex_t cvcmutex; /* protects input */ static kmutex_t cvc_buf_mutex; /* protects internal output buffer */ static kmutex_t cvc_bbsram_input_mutex; /* protects BBSRAM inp buff */ static int input_ok = 0; /* true when stream is valid */ static int stop_bbsram = 1; /* true when BBSRAM is not usable */ static int stop_timeout = 0; static uchar_t cvc_output_buffer[MAX_XFER_OUTPUT]; /* output buffer */ static ushort_t cvc_output_count = 0; static int via_bbsram = 0; /* toggle switch */ static timeout_id_t cvc_timeout_id = (timeout_id_t)-1; static processorid_t cvc_iocpu = -1; /* cpu id of cpu zero */ /* * Module linkage information for the kernel. */ DDI_DEFINE_STREAM_OPS(cvcops, nulldev, nulldev, cvc_attach, cvc_detach, nodev, cvc_info, (D_MTPERQ | D_MP), &cvcinfo, ddi_quiesce_not_supported); 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(void) { int status; status = mod_install(&modlinkage); if (status == 0) { mutex_init(&cvcmutex, NULL, MUTEX_DEFAULT, NULL); } return (status); } int _fini(void) { 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; return (DDI_SUCCESS); } mutex_enter(&cvcmutex); if (!been_here) { been_here = 1; mutex_init(&cvc_buf_mutex, NULL, MUTEX_DEFAULT, NULL); mutex_init(&cvc_bbsram_input_mutex, NULL, MUTEX_DEFAULT, NULL); rw_init(&cvclock, NULL, RW_DRIVER, NULL); rw_enter(&cvclock, RW_WRITER); cvc_timeout_id = timeout(cvc_flush_buf, NULL, drv_usectohz(TIMEOUT_DELAY)); rw_exit(&cvclock); 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 */ return (DDI_SUCCESS); } mutex_exit(&cvcmutex); if (ddi_create_minor_node(devi, "cvc", S_IFCHR, 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (-1); } cvcdip = devi; cvcinput_q = NULL; cvcoutput_q = NULL; 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); } } 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 = 0; tty_common_t *tty; cvc_t *cp; static int input_daemon_started; 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; if (!input_daemon_started) { extern struct cpu *SIGBCPU; /* bugid4141050 */ extern cpu_sgnblk_t *cpu_sgnblkp[]; input_daemon_started = 1; mutex_exit(&cvcmutex); ASSERT(cpu_sgnblkp[SIGBCPU->cpu_id] != NULL); cvc_assign_iocpu(SIGBCPU->cpu_id); cvc_input_daemon_thread = thread_create(NULL, 0, cvc_input_daemon, NULL, 0, &p0, TS_RUN, minclsyspri); } else { mutex_exit(&cvcmutex); } #ifdef lint cvc_input_daemon_thread = cvc_input_daemon_thread; #endif return (err); } /* ARGSUSED */ static int cvc_close(queue_t *q, int flag, cred_t *crp) { register int err = 0; 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); 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 cvc_wput(register queue_t *q, register mblk_t *mp) { int error = 0; rw_enter(&cvclock, RW_READER); switch (mp->b_datap->db_type) { case M_IOCTL: case M_CTL: 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: illegal mblk = 0x%p", mp); cmn_err(CE_WARN, "cvc_wput: type = 0x%x", mp->b_datap->db_type); /* FALLTHROUGH */ #ifdef lint break; #endif case M_DATA: if (cvc_stopped == 1 || cvc_suspended == 1) { (void) putq(q, mp); break; } if (cvcoutput_q != NULL && !via_bbsram) { /* * Send it up past cvcredir module. */ putnext(cvcoutput_q, mp); } else { char *msgp, c; mblk_t *mp2 = mp; int count; while (mp2 != NULL) { count = mp2->b_wptr - mp2->b_rptr; msgp = (char *)mp2->b_rptr; while (count > 0) { count--; if ((c = *msgp++) != '\0') { /* don't print NULs */ cvc_putc(c); } } mp2 = mp2->b_cont; } freemsg(mp); } break; } rw_exit(&cvclock); return (error); } static int cvc_wsrv_count = 0; static int cvc_wsrv(queue_t *q) { register mblk_t *mp; cvc_wsrv_count++; if (cvc_stopped == 1 || cvc_suspended == 1) { return (0); } rw_enter(&cvclock, RW_READER); while ((mp = getq(q)) != NULL) { if (cvcoutput_q != NULL && !via_bbsram) { /* * Send it up past cvcredir module. */ putnext(cvcoutput_q, mp); } else { char *msgp, c; mblk_t *mp2 = mp; int count; while (mp2 != NULL) { count = mp2->b_wptr - mp2->b_rptr; msgp = (char *)mp2->b_rptr; while (count > 0) { count--; if ((c = *msgp++) != '\0') { /* don't print NULs */ cvc_putc(c); } } mp2 = mp2->b_cont; } freemsg(mp); } } rw_exit(&cvclock); return (0); } /* * cvc_ioctl() * handle normal console ioctls. */ static void cvc_ioctl(register queue_t *q, register mblk_t *mp) { register struct iocblk *iocp; register tty_common_t *tty; register cvc_t *cp; int datasize; int error = 0; mblk_t *tmp; cp = q->q_ptr; tty = &cp->cvc_tty; if (tty->t_iocpending != NULL) { freemsg(tty->t_iocpending); tty->t_iocpending = NULL; } datasize = ttycommon_ioctl(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; } if (error < 0) { iocp = (struct iocblk *)mp->b_rptr; /* * "ttycommon_ioctl" didn't do anything; we process it here. */ error = 0; switch (iocp->ioc_cmd) { /* * 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: if (iocp->ioc_count != TRANSPARENT) { mioc2ack(mp, NULL, 0, 0); } else { mcopyin(mp, NULL, sizeof (int), NULL); } /* qreply done below */ break; /* * Get modem bits, we return 0 in mblk. */ case TIOCMGET: tmp = allocb(sizeof (int), BPRI_MED); if (tmp == NULL) { miocnak(q, mp, 0, EAGAIN); return; } *(int *)tmp->b_rptr = 0; if (iocp->ioc_count != TRANSPARENT) mioc2ack(mp, tmp, sizeof (int), 0); else mcopyout(mp, NULL, sizeof (int), NULL, tmp); /* qreply done below */ break; default: /* * If we don't understand it, it's an error. NAK it. */ error = EINVAL; break; } } if (error != 0) { iocp->ioc_error = error; mp->b_datap->db_type = M_IOCNAK; } 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. 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 cvc_redir(mblk_t *mp) { register struct iocblk *iocp; register tty_common_t *tty; register cvc_t *cp; struct winsize *ws; int error; if (cvcinput_q == NULL) { cmn_err(CE_WARN, "cvc_redir: cvcinput_q NULL!"); return (EINVAL); } if (DB_TYPE(mp) != M_IOCTL) { putnext(cvcinput_q, mp); return (0); } iocp = (struct iocblk *)mp->b_rptr; if (iocp->ioc_cmd == TIOCSWINSZ) { error = miocpullup(mp, sizeof (struct winsize)); if (error != 0) return (error); ws = (struct winsize *)mp->b_cont->b_rptr; cp = cvcinput_q->q_ptr; tty = &cp->cvc_tty; mutex_enter(&tty->t_excl); if (bcmp(&tty->t_size, ws, sizeof (struct winsize)) != 0) { tty->t_size = *ws; mutex_exit(&tty->t_excl); (void) putnextctl1(cvcinput_q, M_PCSIG, SIGWINCH); } else mutex_exit(&tty->t_excl); } else { /* * It must be a CVC_DISCONNECT, send hangup. */ ASSERT(iocp->ioc_cmd == CVC_DISCONNECT); if (cvc_hangup_ok) (void) putnextctl(cvcinput_q, M_HANGUP); } 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 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", q); return (error); } /* * Unless "via_bbsram" is set, i/o will be going through cvcd, so * stop flushing output to BBSRAM. */ if ((cvc_timeout_id != (timeout_id_t)-1) && (!via_bbsram)) { stop_timeout = 1; (void) untimeout(cvc_timeout_id); cvc_timeout_id = (timeout_id_t)-1; cvc_hangup_ok = 1; } 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", q); return; } /* * i/o will not be going through cvcd, start flushing output to * BBSRAM */ if (cvc_timeout_id == (timeout_id_t)-1) { stop_timeout = 0; cvc_timeout_id = timeout(cvc_flush_buf, NULL, drv_usectohz(TIMEOUT_DELAY)); } 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_bbsram_ops() * Process commands sent to cvc from netcon_server via BBSRAM */ static void cvc_bbsram_ops(volatile unsigned char *op_reg) { uchar_t op; if ((op = *op_reg) == 0) return; ASSERT(MUTEX_HELD(&cvc_bbsram_input_mutex)); 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) (void) putnextctl(cvcinput_q, M_HANGUP); break; case CVC_BBSRAM_VIA_NET: /* console via network */ via_bbsram = 0; /* * stop periodic flushing of output to BBSRAM * only if cvcredir/cvcd are present */ rw_enter(&cvclock, RW_WRITER); if (cvcoutput_q != NULL) { stop_timeout = 1; if (cvc_timeout_id != (timeout_id_t)-1) { (void) untimeout(cvc_timeout_id); cvc_timeout_id = (timeout_id_t)-1; } } rw_exit(&cvclock); break; case CVC_BBSRAM_VIA_BBSRAM: /* console via bbsram */ via_bbsram = 1; /* start periodic flushing of ouput to BBSRAM */ rw_enter(&cvclock, RW_WRITER); if (cvc_timeout_id == (timeout_id_t)-1) { stop_timeout = 0; cvc_timeout_id = timeout(cvc_flush_buf, NULL, drv_usectohz(TIMEOUT_DELAY)); } rw_exit(&cvclock); 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. */ rw_enter(&cvclock, RW_READER); if (cvcoutput_q != NULL) { (void) putnextctl(cvcoutput_q, M_HANGUP); } rw_exit(&cvclock); break; default: cmn_err(CE_WARN, "cvc: unknown BBSRAM opcode %d\n", (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'); mutex_enter(&cvc_buf_mutex); /* * 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; mutex_exit(&cvc_buf_mutex); 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; cvc_output_count++; if ((cvc_output_count == MAX_XFER_OUTPUT) || (c == '\n')) { /* 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 */ int maxspin = CVC_OUT_MAXSPIN; while ((BBSRAM_OUTPUT_COUNT != 0) && --maxspin) { if (stop_bbsram) { mutex_exit(&cvc_buf_mutex); return; } DELAY(1000); } bcopy((caddr_t)cvc_output_buffer, (caddr_t)(BBSRAM_OUTPUT_BUF - cvc_output_count), cvc_output_count); BBSRAM_OUTPUT_COUNT = cvc_output_count; cvc_output_count = 0; } mutex_exit(&cvc_buf_mutex); } /* * 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 cvc_flush_buf(void *notused) { if (stop_timeout) return; mutex_enter(&cvc_buf_mutex); 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. */ int maxspin = CVC_OUT_MAXSPIN; while ((BBSRAM_OUTPUT_COUNT != 0) && --maxspin) { if (stop_bbsram) goto exit; DELAY(1000); } bcopy((caddr_t)cvc_output_buffer, (caddr_t)BBSRAM_OUTPUT_BUF - cvc_output_count, cvc_output_count); BBSRAM_OUTPUT_COUNT = cvc_output_count; cvc_output_count = 0; } exit: mutex_exit(&cvc_buf_mutex); /* rw_enter(&cvclock, RW_WRITER); */ cvc_timeout_id = timeout(cvc_flush_buf, NULL, drv_usectohz(TIMEOUT_DELAY)); /* rw_exit(&cvclock); */ } /* * cvc_getstr() * Poll BBSRAM for console input while available. */ static void cvc_getstr(char *cp) { short count; volatile char *lp; mutex_enter(&cvc_bbsram_input_mutex); /* Poll BBSRAM for input */ do { if (stop_bbsram) { *cp = '\0'; /* set string to zero-length */ mutex_exit(&cvc_bbsram_input_mutex); return; } /* * Use a smaller delay between checks of BBSRAM for input * when cvcd/cvcredir are not running or "via_bbsram" 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 BBSRAM * to switch if the network is down or hung. */ if ((cvcoutput_q == NULL) || (via_bbsram)) delay(drv_usectohz(100000)); else delay(drv_usectohz(1000000)); cvc_bbsram_ops(BBSRAM_CONTROL_REG); count = BBSRAM_INPUT_COUNT; } while (count == 0); lp = BBSRAM_INPUT_BUF - count; while (count--) { *cp++ = *lp++; } *cp = '\0'; BBSRAM_INPUT_COUNT = 0; mutex_exit(&cvc_bbsram_input_mutex); } /* * 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 linebuf[MAX_XFER_INPUT]; 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 BBSRAM reads\n"); } dropped_read++; continue; } if (dropped_read) { cmn_err(CE_WARN, "cvc_input_daemon: dropped %d BBSRAM reads\n", 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 { putnext(cvcinput_q, mbp); } } else { freemsg(mbp); } mutex_exit(&cvcmutex); } /* 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; mutex_enter(&cvc_bbsram_input_mutex); mutex_enter(&cvc_buf_mutex); } /* * 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; mutex_exit(&cvc_buf_mutex); mutex_exit(&cvc_bbsram_input_mutex); } /* * cvc_assign_iocpu() * Map in BBSRAM to a virtual address * This called by the kernel with the cpu id of cpu zero. */ void cvc_assign_iocpu(processorid_t newcpu) { processorid_t oldcpu = cvc_iocpu; if (newcpu == oldcpu) return; cvc_iobufp[newcpu] = cvc_iobuf_mapin(newcpu); cvc_bbsram_stop(); cvc_iocpu = newcpu; cvc_bbsram_start(); if (oldcpu != -1) cvc_iobuf_mapout(oldcpu); } /* * cvc_iobuf_mapin() * Map in the cvc bbsram i/o buffer into kernel space. */ static caddr_t cvc_iobuf_mapin(processorid_t cpu_id) { caddr_t cvaddr; uint64_t cvc_iobuf_physaddr; pfn_t pfn; uint_t num_pages; extern cpu_sgnblk_t *cpu_sgnblkp[]; ASSERT(cpu_sgnblkp[cpu_id] != NULL); /* * First construct the physical base address of the bbsram * in Starfire PSI space associated with this cpu in question. */ cvc_iobuf_physaddr = STARFIRE_UPAID2UPS(cpu_id) | STARFIRE_PSI_BASE; /* * Next add the cvc i/o buffer offset obtained from the * sigblock to get cvc iobuf physical address */ cvc_iobuf_physaddr += cpu_sgnblkp[cpu_id]->sigb_cvc_off; /* Get the page frame number */ pfn = (cvc_iobuf_physaddr >> MMU_PAGESHIFT); /* Calculate how many pages we need to map in */ num_pages = mmu_btopr(((uint_t)(cvc_iobuf_physaddr & MMU_PAGEOFFSET) + sizeof (sigb_cvc_t))); /* * Map in the cvc iobuf */ cvaddr = vmem_alloc(heap_arena, ptob(num_pages), VM_SLEEP); hat_devload(kas.a_hat, cvaddr, mmu_ptob(num_pages), pfn, PROT_READ | PROT_WRITE, HAT_LOAD_LOCK); return ((caddr_t)(cvaddr + (uint_t)(cvc_iobuf_physaddr & MMU_PAGEOFFSET))); } /* * cvc_iobuf_mapout() * Map out the cvc iobuf from kernel space */ static void cvc_iobuf_mapout(processorid_t cpu_id) { caddr_t cvaddr; size_t num_pages; if ((cvaddr = cvc_iobufp[cpu_id]) == 0) { /* already unmapped - return */ return; } /* Calculate how many pages we need to map out */ num_pages = mmu_btopr(((size_t)((uint64_t)cvaddr & MMU_PAGEOFFSET) + sizeof (sigb_cvc_t))); /* Get cvaddr to the start of the page boundary */ cvaddr = (caddr_t)(((uint64_t)cvaddr & MMU_PAGEMASK)); hat_unload(kas.a_hat, cvaddr, mmu_ptob(num_pages), HAT_UNLOAD_UNLOCK); vmem_free(heap_arena, cvaddr, ptob(num_pages)); cvc_iobufp[cpu_id] = NULL; }