/* * 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. */ /* * "Workstation console" multiplexor driver for Sun. * * Sends output to the primary frame buffer using the PROM monitor; * gets input from a stream linked below us that is the "keyboard * driver", below which is linked the primary keyboard. */ /* * Locking Policy: * This module has a D_MTPERMOD inner perimeter which means STREAMS * only allows one thread to enter this module through STREAMS entry * points each time -- open() close() put() srv() qtimeout(). * So for the most time we do not need locking in this module, but with * the following exceptions: * * - wc shares three global variables (wc_dip, vc_active_consle, vc_avl_root) * with virtual console devname part (fs/dev/sdev_vtops.c) which get * compiled into genunix. * * - wc_modechg_cb() is a callback function which will triggered when * framebuffer display mode is changed. * * - vt_send_hotkeys() is triggered by timeout() which is not STREAMS MT * safe. * * Based on the fact that virtual console devname part and wc_modechg_cb() * only do read access to the above mentioned shared three global variables, * It is safe to do locking this way: * 1) all read access to the three global variables in THIS WC MODULE do not * need locking; * 2) all write access to the three global variables in THIS WC MODULE must * hold vc_lock; * 3) any access to the three global variables in either DEVNAME PART or the * CALLBACK must hold vc_lock; * 4) other global variables which are only shared in this wc module and only * accessible through STREAMS entry points such as "vc_last_console", * "vc_inuse_max_minor", "vc_target_console" and "vc_waitactive_list" * do not need explict locking. * * wc_modechg_cb() does read access to vc_state_t::vc_flags, * vc_state_t::vc_state_lock is used to protect concurrently accesses to * vc_state_t::vc_flags which may happen from both through STREAMS entry * points and wc_modechg_cb(). * Since wc_modechg_cb() only does read access to vc_state_t::vc_flags, * The other parts of wc module (except wc_modechg_cb()) only has to hold * vc_state_t::vc_flags when writing to vc_state_t::vc_flags. * * vt_send_hotkeys() could access vt_pending_vtno at the same time with * the rest of wc module, vt_pending_vtno_lock is used to protect * vt_pending_vtno. * * Lock order: vc_lock -> vc_state_t::vc_state_lock. * No overlap between vc_lock and vt_pending_vtno_lock. */ #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 #include #include #include #include #include #include #include #include #include #include #include #include /* streams stuff */ _NOTE(SCHEME_PROTECTS_DATA("Unshared data", copyreq)) _NOTE(SCHEME_PROTECTS_DATA("Unshared data", copyresp)) _NOTE(SCHEME_PROTECTS_DATA("Unshared data", datab)) _NOTE(SCHEME_PROTECTS_DATA("Unshared data", iocblk)) _NOTE(SCHEME_PROTECTS_DATA("Unshared data", msgb)) _NOTE(SCHEME_PROTECTS_DATA("Unshared data", queue)) #define MINLINES 10 #define MAXLINES 48 #define LOSCREENLINES 34 #define HISCREENLINES 48 #define MINCOLS 10 #define MAXCOLS 120 #define LOSCREENCOLS 80 #define HISCREENCOLS 120 struct wscons_state { dev_t wc_dev; /* major/minor for this device */ #ifdef _HAVE_TEM_FIRMWARE int wc_defer_output; /* set if output device is "slow" */ #endif /* _HAVE_TEM_FIRMWARE */ queue_t *wc_kbdqueue; /* "console keyboard" device queue */ /* below us */ cons_polledio_t wc_polledio; /* polled I/O function pointers */ cons_polledio_t *wc_kb_polledio; /* keyboard's polledio */ unsigned int wc_kb_getpolledio_id; /* id for kb CONSOPENPOLLEDIO */ queue_t *wc_pending_wq; mblk_t *wc_pending_link; /* I_PLINK pending for kb polledio */ } wscons; /* * This module has a D_MTPERMOD inner perimeter, so we don't need to protect * the variables only shared within this module */ _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", wscons)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", wscons_state)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vt_stat)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vc_waitactive_msg)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", tty_common)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vt_mode)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vt_dispinfo)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", winsize)) _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vc_last_console)) #ifdef _HAVE_TEM_FIRMWARE ssize_t wc_cons_wrtvec(promif_redir_arg_t arg, uchar_t *s, size_t n); #endif /* _HAVE_TEM_FIRMWARE */ static int wcopen(queue_t *, dev_t *, int, int, cred_t *); static int wcclose(queue_t *, int, cred_t *); static int wcuwput(queue_t *, mblk_t *); static int wclrput(queue_t *, mblk_t *); static struct module_info wcm_info = { 0, "wc", 0, INFPSZ, 2048, 128 }; static struct qinit wcurinit = { putq, NULL, wcopen, wcclose, NULL, &wcm_info, NULL }; static struct qinit wcuwinit = { wcuwput, NULL, wcopen, wcclose, NULL, &wcm_info, NULL }; static struct qinit wclrinit = { wclrput, NULL, NULL, NULL, NULL, &wcm_info, NULL }; /* * We always putnext directly to the underlying queue. */ static struct qinit wclwinit = { NULL, NULL, NULL, NULL, NULL, &wcm_info, NULL }; static struct streamtab wcinfo = { &wcurinit, &wcuwinit, &wclrinit, &wclwinit, }; static int wc_info(dev_info_t *, ddi_info_cmd_t, void *, void **result); static int wc_attach(dev_info_t *, ddi_attach_cmd_t); DDI_DEFINE_STREAM_OPS(wc_ops, nulldev, nulldev, wc_attach, nodev, nodev, wc_info, D_MTPERMOD | D_MP, &wcinfo, ddi_quiesce_not_supported); static void wcreioctl(void *); static void wcioctl(queue_t *, mblk_t *); #ifdef _HAVE_TEM_FIRMWARE static void wcopoll(void *); static void wconsout(void *); #endif /* _HAVE_TEM_FIRMWARE */ static void wcrstrt(void *); static void wcstart(void *); static void wc_open_kb_polledio(struct wscons_state *wc, queue_t *q, mblk_t *mp); static void wc_close_kb_polledio(struct wscons_state *wc, queue_t *q, mblk_t *mp); static void wc_polled_putchar(cons_polledio_arg_t arg, unsigned char c); static boolean_t wc_polled_ischar(cons_polledio_arg_t arg); static int wc_polled_getchar(cons_polledio_arg_t arg); static void wc_polled_enter(cons_polledio_arg_t arg); static void wc_polled_exit(cons_polledio_arg_t arg); void wc_get_size(vc_state_t *pvc); static void wc_modechg_cb(tem_modechg_cb_arg_t arg); static struct dev_ops wc_ops; /* * Debug printing */ #ifndef DPRINTF #ifdef DEBUG /*PRINTFLIKE1*/ static void wc_dprintf(const char *fmt, ...) __KPRINTFLIKE(1); #define DPRINTF(l, m, args) \ (((l) >= wc_errlevel) && ((m) & wc_errmask) ? \ wc_dprintf args : \ (void) 0) /* * Severity levels for printing */ #define PRINT_L0 0 /* print every message */ #define PRINT_L1 1 /* debug */ #define PRINT_L2 2 /* quiet */ /* * Masks */ #define PRINT_MASK_ALL 0xFFFFFFFFU uint_t wc_errmask = PRINT_MASK_ALL; uint_t wc_errlevel = PRINT_L2; #else #define DPRINTF(l, m, args) /* NOTHING */ #endif #endif /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a pseudo driver */ "Workstation multiplexer Driver 'wc'", &wc_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { int rc; if ((rc = mod_install(&modlinkage)) == 0) vt_init(); return (rc); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /*ARGSUSED*/ static int wc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { /* create minor node for workstation hard console */ if (ddi_create_minor_node(devi, "wscons", S_IFCHR, 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { ddi_remove_minor_node(devi, NULL); return (DDI_FAILURE); } mutex_enter(&vc_lock); wc_dip = devi; bzero(&(wscons.wc_polledio), sizeof (wscons.wc_polledio)); vt_resize(VC_DEFAULT_COUNT); mutex_exit(&vc_lock); return (DDI_SUCCESS); } /* ARGSUSED */ static int wc_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int error; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: if (wc_dip == NULL) { error = DDI_FAILURE; } else { *result = (void *) wc_dip; error = DDI_SUCCESS; } break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)0; error = DDI_SUCCESS; break; default: error = DDI_FAILURE; } return (error); } #ifdef _HAVE_TEM_FIRMWARE /* * Output buffer. Protected by the per-module inner perimeter. */ #define MAXHIWAT 2000 static char obuf[MAXHIWAT]; #endif /* _HAVE_TEM_FIRMWARE */ static void wc_init_polledio(void) { static boolean_t polledio_inited = B_FALSE; _NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", polledio_inited)) if (polledio_inited) return; polledio_inited = B_TRUE; /* * Initialize the parts of the polled I/O struct that * are common to both input and output modes, but which * don't flag to the upper layers, which if any of the * two modes are available. We don't know at this point * if system is configured CONS_KFB, but we will when * consconfig_dacf asks us with CONSOPENPOLLED I/O. */ bzero(&(wscons.wc_polledio), sizeof (wscons.wc_polledio)); wscons.wc_polledio.cons_polledio_version = CONSPOLLEDIO_V0; wscons.wc_polledio.cons_polledio_argument = (cons_polledio_arg_t)&wscons; wscons.wc_polledio.cons_polledio_enter = wc_polled_enter; wscons.wc_polledio.cons_polledio_exit = wc_polled_exit; #ifdef _HAVE_TEM_FIRMWARE /* * If we're talking directly to a framebuffer, we assume * that it's a "slow" device, so that rendering should * be deferred to a timeout or softcall so that we write * a bunch of characters at once. */ wscons.wc_defer_output = prom_stdout_is_framebuffer(); #endif /* _HAVE_TEM_FIRMWARE */ } /*ARGSUSED*/ static int wcopen(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *crp) { int minor; wc_init_polledio(); minor = (int)getminor(*devp); return (vt_open(minor, q, crp)); } /*ARGSUSED*/ static int wcclose(queue_t *q, int flag, cred_t *crp) { vc_state_t *pvc = (vc_state_t *)q->q_ptr; qprocsoff(q); mutex_enter(&vc_lock); if (pvc->vc_minor == 0 || pvc->vc_minor == vc_active_console) { /* * If we lose the system console, * no any other active consoles. */ if (pvc->vc_minor == 0 && pvc->vc_minor == vc_active_console) { vc_active_console = VT_MINOR_INVALID; vc_last_console = VT_MINOR_INVALID; } /* * just clean for our primary console * and active console */ mutex_enter(&pvc->vc_state_lock); vt_clean(q, pvc); mutex_exit(&pvc->vc_state_lock); mutex_exit(&vc_lock); return (0); } vt_close(q, pvc, crp); mutex_exit(&vc_lock); return (0); } /* * Put procedure for upper write queue. * Respond to M_STOP, M_START, M_IOCTL, and M_FLUSH messages here; * queue up M_BREAK, M_DELAY, and M_DATA messages for processing by * the start routine, and then call the start routine; discard * everything else. */ static int wcuwput(queue_t *q, mblk_t *mp) { vc_state_t *pvc = (vc_state_t *)q->q_ptr; switch (mp->b_datap->db_type) { case M_STOP: mutex_enter(&pvc->vc_state_lock); pvc->vc_flags |= WCS_STOPPED; mutex_exit(&pvc->vc_state_lock); freemsg(mp); break; case M_START: mutex_enter(&pvc->vc_state_lock); pvc->vc_flags &= ~WCS_STOPPED; mutex_exit(&pvc->vc_state_lock); wcstart(pvc); freemsg(mp); break; case M_IOCTL: { struct iocblk *iocp; struct linkblk *linkp; iocp = (struct iocblk *)(void *)mp->b_rptr; switch (iocp->ioc_cmd) { case I_LINK: /* stupid, but permitted */ case I_PLINK: if (wscons.wc_kbdqueue != NULL) { /* somebody already linked */ miocnak(q, mp, 0, EINVAL); return (0); } linkp = (struct linkblk *)(void *)mp->b_cont->b_rptr; wscons.wc_kbdqueue = WR(linkp->l_qbot); mp->b_datap->db_type = M_IOCACK; iocp->ioc_count = 0; wc_open_kb_polledio(&wscons, q, mp); break; case I_UNLINK: /* stupid, but permitted */ case I_PUNLINK: linkp = (struct linkblk *)(void *)mp->b_cont->b_rptr; if (wscons.wc_kbdqueue != WR(linkp->l_qbot)) { /* not us */ miocnak(q, mp, 0, EINVAL); return (0); } mp->b_datap->db_type = M_IOCACK; iocp->ioc_count = 0; wc_close_kb_polledio(&wscons, q, mp); break; case TCSETSW: case TCSETSF: case TCSETAW: case TCSETAF: case TCSBRK: /* * The changes do not take effect until all * output queued before them is drained. * Put this message on the queue, so that * "wcstart" will see it when it's done * with the output before it. Poke the * start routine, just in case. */ (void) putq(q, mp); wcstart(pvc); break; case CONSSETABORTENABLE: case CONSGETABORTENABLE: case KIOCSDIRECT: if (wscons.wc_kbdqueue != NULL) { wscons.wc_pending_wq = q; (void) putnext(wscons.wc_kbdqueue, mp); break; } /* fall through */ default: /* * Do it now. */ wcioctl(q, mp); break; } break; } case M_FLUSH: if (*mp->b_rptr & FLUSHW) { /* * Flush our write queue. */ flushq(q, FLUSHDATA); /* XXX doesn't flush M_DELAY */ *mp->b_rptr &= ~FLUSHW; /* it has been flushed */ } if (*mp->b_rptr & FLUSHR) { flushq(RD(q), FLUSHDATA); qreply(q, mp); /* give the read queues a crack at it */ } else freemsg(mp); break; case M_BREAK: /* * Ignore these, as they make no sense. */ freemsg(mp); break; case M_DELAY: case M_DATA: /* * Queue the message up to be transmitted, * and poke the start routine. */ (void) putq(q, mp); wcstart(pvc); break; case M_IOCDATA: vt_miocdata(q, mp); break; default: /* * "No, I don't want a subscription to Chain Store Age, * thank you anyway." */ freemsg(mp); break; } return (0); } /* * Retry an "ioctl", now that "qbufcall" claims we may be able to allocate * the buffer we need. */ /*ARGSUSED*/ static void wcreioctl(void *arg) { vc_state_t *pvc = (vc_state_t *)arg; queue_t *q; mblk_t *mp; pvc->vc_bufcallid = 0; q = pvc->vc_ttycommon.t_writeq; if ((mp = pvc->vc_ttycommon.t_iocpending) != NULL) { /* not pending any more */ pvc->vc_ttycommon.t_iocpending = NULL; wcioctl(q, mp); } } static int wc_getterm(mblk_t *mp) { char *term; intptr_t arg; int flag = ((struct iocblk *)(void *)mp->b_rptr)->ioc_flag; STRUCT_DECL(cons_getterm, wcterm); STRUCT_INIT(wcterm, flag); arg = *((intptr_t *)(void *)mp->b_cont->b_rptr); if (ddi_copyin((void *)arg, STRUCT_BUF(wcterm), STRUCT_SIZE(wcterm), flag) != 0) { return (EFAULT); } if (consmode == CONS_FW) { /* PROM terminal emulator */ term = "sun"; } else { /* Kernel terminal emulator */ ASSERT(consmode == CONS_KFB); term = "sun-color"; } if (STRUCT_FGET(wcterm, cn_term_len) < strlen(term) + 1) { return (EOVERFLOW); } if (ddi_copyout(term, STRUCT_FGETP(wcterm, cn_term_type), strlen(term) + 1, flag) != 0) { return (EFAULT); } return (0); } /* * Process an "ioctl" message sent down to us. */ static void wcioctl(queue_t *q, mblk_t *mp) { vc_state_t *pvc = (vc_state_t *)q->q_ptr; struct iocblk *iocp; size_t datasize; int error; long len; iocp = (struct iocblk *)(void *)mp->b_rptr; if ((iocp->ioc_cmd & VTIOC) == VTIOC || (iocp->ioc_cmd & KDIOC) == KDIOC) { vt_ioctl(q, mp); return; } switch (iocp->ioc_cmd) { case TIOCSWINSZ: /* * Ignore all attempts to set the screen size; the * value in the EEPROM is guaranteed (modulo PROM bugs) * to be the value used by the PROM monitor code, so it * is by definition correct. Many programs (e.g., * "login" and "tset") will attempt to reset the size * to (0, 0) or (34, 80), neither of which is * necessarily correct. * We just ACK the message, so as not to disturb * programs that set the sizes. */ iocp->ioc_count = 0; /* no data returned */ mp->b_datap->db_type = M_IOCACK; qreply(q, mp); return; case CONSOPENPOLLEDIO: DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wcioctl: CONSOPENPOLLEDIO\n")); error = miocpullup(mp, sizeof (struct cons_polledio *)); if (error != 0) { miocnak(q, mp, 0, error); return; } /* * We are given an appropriate-sized data block, * and return a pointer to our structure in it. */ if (consmode == CONS_KFB) wscons.wc_polledio.cons_polledio_putchar = wc_polled_putchar; *(struct cons_polledio **)(void *)mp->b_cont->b_rptr = &wscons.wc_polledio; mp->b_datap->db_type = M_IOCACK; qreply(q, mp); break; case CONS_GETTERM: if ((error = wc_getterm(mp)) != 0) miocnak(q, mp, 0, error); else miocack(q, mp, 0, 0); return; case WC_OPEN_FB: /* * Start out pessimistic, so that we can just jump to * the reply to bail out. */ mp->b_datap->db_type = M_IOCNAK; /* * First test: really, this should be done only from * inside the kernel. Unfortunately, that information * doesn't seem to be available in a streams ioctl, * so restrict it to root only. (Perhaps we could check * for ioc_cr == kcred.) */ if ((iocp->ioc_error = secpolicy_console(iocp->ioc_cr)) != 0) goto open_fail; /* * Some miscellaneous checks... */ iocp->ioc_error = EINVAL; /* * If we don't have exactly one continuation block, fail. */ if (mp->b_cont == NULL || mp->b_cont->b_cont != NULL) goto open_fail; /* * If there's no null terminator in the string, fail. */ /* LINTED E_PTRDIFF_OVERFLOW */ len = mp->b_cont->b_wptr - mp->b_cont->b_rptr; if (memchr(mp->b_cont->b_rptr, 0, len) == NULL) goto open_fail; /* * NOTE: should eventually get default * dimensions from a property, e.g. screen-#rows. */ iocp->ioc_error = tem_info_init((char *)mp->b_cont->b_rptr, iocp->ioc_cr); /* * Of course, if the terminal emulator initialization * failed, fail. */ if (iocp->ioc_error != 0) goto open_fail; #ifdef _HAVE_TEM_FIRMWARE if (prom_stdout_is_framebuffer()) { /* * Drivers in the console stream may emit additional * messages before we are ready. This causes text * overwrite on the screen. So we set the redirection * here. It is safe because the ioctl in consconfig_dacf * will succeed and consmode will be set to CONS_KFB. */ prom_set_stdout_redirect(wc_cons_wrtvec, (promif_redir_arg_t)NULL); } #endif /* _HAVE_TEM_FIRMWARE */ tem_register_modechg_cb(wc_modechg_cb, (tem_modechg_cb_arg_t)&wscons); /* * ... and succeed. */ mp->b_datap->db_type = M_IOCACK; open_fail: qreply(q, mp); break; case WC_CLOSE_FB: /* * There's nothing that can call this, so it's not * really implemented. */ mp->b_datap->db_type = M_IOCNAK; /* * However, if it were implemented, it would clearly * be root-only. */ if ((iocp->ioc_error = secpolicy_console(iocp->ioc_cr)) != 0) goto close_fail; iocp->ioc_error = EINVAL; close_fail: qreply(q, mp); break; default: /* * The only way in which "ttycommon_ioctl" can fail is * if the "ioctl" requires a response containing data * to be returned to the user, and no mblk could be * allocated for the data. No such "ioctl" alters our * state. Thus, we always go ahead and do any * state-changes the "ioctl" calls for. If we couldn't * allocate the data, "ttycommon_ioctl" has stashed the * "ioctl" away safely, so we just call "qbufcall" to * request that we be called back when we stand a * better chance of allocating the data. */ datasize = ttycommon_ioctl(&pvc->vc_ttycommon, q, mp, &error); if (datasize != 0) { if (pvc->vc_bufcallid != 0) qunbufcall(q, pvc->vc_bufcallid); pvc->vc_bufcallid = qbufcall(q, datasize, BPRI_HI, wcreioctl, pvc); return; } if (error < 0) { if (iocp->ioc_cmd == TCSBRK) error = 0; else error = EINVAL; } if (error != 0) { iocp->ioc_error = error; mp->b_datap->db_type = M_IOCNAK; } qreply(q, mp); break; } } /* * This function gets the polled I/O structures from the lower * keyboard driver. If any initialization or resource allocation * needs to be done by the lower driver, it will be done when * the lower driver services this message. */ static void wc_open_kb_polledio(struct wscons_state *wscons, queue_t *q, mblk_t *mp) { mblk_t *mp2; struct iocblk *iocp; DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wc_open_kb_polledio: sending CONSOPENPOLLEDIO\n")); mp2 = mkiocb(CONSOPENPOLLEDIO); if (mp2 == NULL) { /* * If we can't get an mblk, then wait for it. */ goto nomem; } mp2->b_cont = allocb(sizeof (struct cons_polledio *), BPRI_HI); if (mp2->b_cont == NULL) { /* * If we can't get an mblk, then wait for it, and release * the mblk that we have already allocated. */ freemsg(mp2); goto nomem; } iocp = (struct iocblk *)(void *)mp2->b_rptr; iocp->ioc_count = sizeof (struct cons_polledio *); mp2->b_cont->b_wptr = mp2->b_cont->b_rptr + sizeof (struct cons_polledio *); wscons->wc_pending_wq = q; wscons->wc_pending_link = mp; wscons->wc_kb_getpolledio_id = iocp->ioc_id; putnext(wscons->wc_kbdqueue, mp2); return; nomem: iocp = (struct iocblk *)(void *)mp->b_rptr; iocp->ioc_error = ENOMEM; mp->b_datap->db_type = M_IOCNAK; qreply(q, mp); } /* * This function releases the polled I/O structures from the lower * keyboard driver. If any de-initialization needs to be done, or * any resources need to be released, it will be done when the lower * driver services this message. */ static void wc_close_kb_polledio(struct wscons_state *wscons, queue_t *q, mblk_t *mp) { mblk_t *mp2; struct iocblk *iocp; DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wc_close_kb_polledio: sending CONSCLOSEPOLLEDIO\n")); mp2 = mkiocb(CONSCLOSEPOLLEDIO); if (mp2 == NULL) { /* * If we can't get an mblk, then wait for it. */ goto nomem; } mp2->b_cont = allocb(sizeof (struct cons_polledio *), BPRI_HI); if (mp2->b_cont == NULL) { /* * If we can't get an mblk, then wait for it, and release * the mblk that we have already allocated. */ freemsg(mp2); goto nomem; } iocp = (struct iocblk *)(void *)mp2->b_rptr; iocp->ioc_count = 0; wscons->wc_pending_wq = q; wscons->wc_pending_link = mp; wscons->wc_kb_getpolledio_id = iocp->ioc_id; putnext(wscons->wc_kbdqueue, mp2); return; nomem: iocp = (struct iocblk *)(void *)mp->b_rptr; iocp->ioc_error = ENOMEM; mp->b_datap->db_type = M_IOCNAK; qreply(q, mp); } #ifdef _HAVE_TEM_FIRMWARE /* ARGSUSED */ static void wcopoll(void *arg) { vc_state_t *pvc = (vc_state_t *)arg; queue_t *q; q = pvc->vc_ttycommon.t_writeq; pvc->vc_timeoutid = 0; mutex_enter(&pvc->vc_state_lock); /* See if we can continue output */ if ((pvc->vc_flags & WCS_BUSY) && pvc->vc_pendc != -1) { if (prom_mayput((char)pvc->vc_pendc) == 0) { pvc->vc_pendc = -1; pvc->vc_flags &= ~WCS_BUSY; if (!(pvc->vc_flags&(WCS_DELAY|WCS_STOPPED))) wcstart(pvc); } else pvc->vc_timeoutid = qtimeout(q, wcopoll, pvc, 1); } mutex_exit(&pvc->vc_state_lock); } #endif /* _HAVE_TEM_FIRMWARE */ /* * Restart output on the console after a timeout. */ /* ARGSUSED */ static void wcrstrt(void *arg) { vc_state_t *pvc = (vc_state_t *)arg; ASSERT(pvc->vc_ttycommon.t_writeq != NULL); mutex_enter(&pvc->vc_state_lock); pvc->vc_flags &= ~WCS_DELAY; mutex_exit(&pvc->vc_state_lock); wcstart(pvc); } /* * get screen terminal for current output */ static tem_vt_state_t wc_get_screen_tem(vc_state_t *pvc) { if (!tem_initialized(pvc->vc_tem) || tem_get_fbmode(pvc->vc_tem) != KD_TEXT) return (NULL); return (pvc->vc_tem); } /* * Start console output */ static void wcstart(void *arg) { vc_state_t *pvc = (vc_state_t *)arg; tem_vt_state_t ptem = NULL; #ifdef _HAVE_TEM_FIRMWARE int c; ssize_t cc; #endif /* _HAVE_TEM_FIRMWARE */ queue_t *q; mblk_t *bp; mblk_t *nbp; /* * If we're waiting for something to happen (delay timeout to * expire, current transmission to finish, output to be * restarted, output to finish draining), don't grab anything * new. */ if (pvc->vc_flags & (WCS_DELAY|WCS_BUSY|WCS_STOPPED)) return; q = pvc->vc_ttycommon.t_writeq; /* * assumes that we have been called by whoever holds the * exclusionary lock on the write-side queue (protects * vc_flags and vc_pendc). */ for (;;) { if ((bp = getq(q)) == NULL) return; /* nothing to transmit */ /* * We have a new message to work on. * Check whether it's a delay or an ioctl (the latter * occurs if the ioctl in question was waiting for the output * to drain). If it's one of those, process it immediately. */ switch (bp->b_datap->db_type) { case M_DELAY: /* * Arrange for "wcrstrt" to be called when the * delay expires; it will turn WCS_DELAY off, * and call "wcstart" to grab the next message. */ if (pvc->vc_timeoutid != 0) (void) quntimeout(q, pvc->vc_timeoutid); pvc->vc_timeoutid = qtimeout(q, wcrstrt, pvc, (clock_t)(*(unsigned char *)bp->b_rptr + 6)); mutex_enter(&pvc->vc_state_lock); pvc->vc_flags |= WCS_DELAY; mutex_exit(&pvc->vc_state_lock); freemsg(bp); return; /* wait for this to finish */ case M_IOCTL: /* * This ioctl was waiting for the output ahead of * it to drain; obviously, it has. Do it, and * then grab the next message after it. */ wcioctl(q, bp); continue; } #ifdef _HAVE_TEM_FIRMWARE if (consmode == CONS_KFB) { #endif /* _HAVE_TEM_FIRMWARE */ if ((ptem = wc_get_screen_tem(pvc)) != NULL) { for (nbp = bp; nbp != NULL; nbp = nbp->b_cont) { if (nbp->b_wptr > nbp->b_rptr) { (void) tem_write(ptem, nbp->b_rptr, /* LINTED */ nbp->b_wptr - nbp->b_rptr, kcred); } } } freemsg(bp); #ifdef _HAVE_TEM_FIRMWARE continue; } /* consmode = CONS_FW */ if (pvc->vc_minor != 0) { freemsg(bp); continue; } /* LINTED E_PTRDIFF_OVERFLOW */ if ((cc = bp->b_wptr - bp->b_rptr) == 0) { freemsg(bp); continue; } /* * Direct output to the frame buffer if this device * is not the "hardware" console. */ if (wscons.wc_defer_output) { /* * Never do output here; * it takes forever. */ mutex_enter(&pvc->vc_state_lock); pvc->vc_flags |= WCS_BUSY; mutex_exit(&pvc->vc_state_lock); pvc->vc_pendc = -1; (void) putbq(q, bp); if (q->q_count > 128) { /* do it soon */ softcall(wconsout, pvc); } else { /* wait a bit */ if (pvc->vc_timeoutid != 0) (void) quntimeout(q, pvc->vc_timeoutid); pvc->vc_timeoutid = qtimeout(q, wconsout, pvc, hz / 30); } return; } for (;;) { c = *bp->b_rptr++; cc--; if (prom_mayput((char)c) != 0) { mutex_enter(&pvc->vc_state_lock); pvc->vc_flags |= WCS_BUSY; mutex_exit(&pvc->vc_state_lock); pvc->vc_pendc = c; if (pvc->vc_timeoutid != 0) (void) quntimeout(q, pvc->vc_timeoutid); pvc->vc_timeoutid = qtimeout(q, wcopoll, pvc, 1); if (bp != NULL) /* not done with this message yet */ (void) putbq(q, bp); return; } while (cc <= 0) { nbp = bp; bp = bp->b_cont; freeb(nbp); if (bp == NULL) return; /* LINTED E_PTRDIFF_OVERFLOW */ cc = bp->b_wptr - bp->b_rptr; } } #endif /* _HAVE_TEM_FIRMWARE */ } } #ifdef _HAVE_TEM_FIRMWARE /* * Output to frame buffer console. * It takes a long time to scroll. */ /* ARGSUSED */ static void wconsout(void *arg) { vc_state_t *pvc = (vc_state_t *)arg; uchar_t *cp; ssize_t cc; queue_t *q; mblk_t *bp; mblk_t *nbp; char *current_position; ssize_t bytes_left; if ((q = pvc->vc_ttycommon.t_writeq) == NULL) { return; /* not attached to a stream */ } /* * Set up to copy up to MAXHIWAT bytes. */ current_position = &obuf[0]; bytes_left = MAXHIWAT; while ((bp = getq(q)) != NULL) { if (bp->b_datap->db_type == M_IOCTL) { /* * This ioctl was waiting for the output ahead of * it to drain; obviously, it has. Put it back * so that "wcstart" can handle it, and transmit * what we've got. */ (void) putbq(q, bp); goto transmit; } do { cp = bp->b_rptr; /* LINTED E_PTRDIFF_OVERFLOW */ cc = bp->b_wptr - cp; while (cc != 0) { if (bytes_left == 0) { /* * Out of buffer space; put this * buffer back on the queue, and * transmit what we have. */ bp->b_rptr = cp; (void) putbq(q, bp); goto transmit; } *current_position++ = *cp++; cc--; bytes_left--; } nbp = bp; bp = bp->b_cont; freeb(nbp); } while (bp != NULL); } transmit: if ((cc = MAXHIWAT - bytes_left) != 0) console_puts(obuf, cc); mutex_enter(&pvc->vc_state_lock); pvc->vc_flags &= ~WCS_BUSY; mutex_exit(&pvc->vc_state_lock); wcstart(pvc); } #endif /* _HAVE_TEM_FIRMWARE */ /* * Put procedure for lower read queue. * Pass everything up to queue above "upper half". */ static int wclrput(queue_t *q, mblk_t *mp) { vc_state_t *pvc; queue_t *upq; struct iocblk *iocp; pvc = vt_minor2vc(VT_ACTIVE); DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wclrput: wclrput type = 0x%x\n", mp->b_datap->db_type)); switch (mp->b_datap->db_type) { case M_FLUSH: if (*mp->b_rptr == FLUSHW || *mp->b_rptr == FLUSHRW) { /* * Flush our write queue. */ /* XXX doesn't flush M_DELAY */ flushq(WR(q), FLUSHDATA); *mp->b_rptr = FLUSHR; /* it has been flushed */ } if (*mp->b_rptr == FLUSHR || *mp->b_rptr == FLUSHRW) { flushq(q, FLUSHDATA); *mp->b_rptr = FLUSHW; /* it has been flushed */ qreply(q, mp); /* give the read queues a crack at it */ } else freemsg(mp); break; case M_DATA: if (consmode == CONS_KFB && vt_check_hotkeys(mp)) { freemsg(mp); break; } if ((upq = pvc->vc_ttycommon.t_readq) != NULL) { if (!canput(upq->q_next)) { ttycommon_qfull(&pvc->vc_ttycommon, upq); wcstart(pvc); freemsg(mp); } else { putnext(upq, mp); } } else freemsg(mp); break; case M_IOCACK: case M_IOCNAK: iocp = (struct iocblk *)(void *)mp->b_rptr; if (wscons.wc_pending_link != NULL && iocp->ioc_id == wscons.wc_kb_getpolledio_id) { switch (mp->b_datap->db_type) { case M_IOCACK: switch (iocp->ioc_cmd) { case CONSOPENPOLLEDIO: DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wclrput: " "ACK CONSOPENPOLLEDIO\n")); wscons.wc_kb_polledio = *(struct cons_polledio **) (void *)mp->b_cont->b_rptr; wscons.wc_polledio. cons_polledio_getchar = wc_polled_getchar; wscons.wc_polledio. cons_polledio_ischar = wc_polled_ischar; break; case CONSCLOSEPOLLEDIO: DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wclrput: " "ACK CONSCLOSEPOLLEDIO\n")); wscons.wc_kb_polledio = NULL; wscons.wc_kbdqueue = NULL; wscons.wc_polledio. cons_polledio_getchar = NULL; wscons.wc_polledio. cons_polledio_ischar = NULL; break; default: DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wclrput: " "ACK UNKNOWN\n")); } break; case M_IOCNAK: /* * Keyboard may or may not support polled I/O. * This ioctl may have been rejected because * we only have the wc->conskbd chain built, * and the keyboard driver has not been linked * underneath conskbd yet. */ DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wclrput: NAK\n")); switch (iocp->ioc_cmd) { case CONSCLOSEPOLLEDIO: wscons.wc_kb_polledio = NULL; wscons.wc_kbdqueue = NULL; wscons.wc_polledio. cons_polledio_getchar = NULL; wscons.wc_polledio. cons_polledio_ischar = NULL; break; } break; } /* * Discard the response, replace it with the * pending response to the I_PLINK, then let it * flow upward. */ freemsg(mp); mp = wscons.wc_pending_link; wscons.wc_pending_link = NULL; wscons.wc_kb_getpolledio_id = 0; } /* FALLTHROUGH */ default: /* inc M_ERROR, M_HANGUP, M_IOCACK, M_IOCNAK, ... */ if (wscons.wc_pending_wq != NULL) { qreply(wscons.wc_pending_wq, mp); wscons.wc_pending_wq = NULL; break; } if ((upq = pvc->vc_ttycommon.t_readq) != NULL) { putnext(upq, mp); } else { DPRINTF(PRINT_L1, PRINT_MASK_ALL, ("wclrput: Message DISCARDED\n")); freemsg(mp); } break; } return (0); } #ifdef _HAVE_TEM_FIRMWARE /* * This routine exists so that prom_write() can redirect writes * to the framebuffer through the kernel terminal emulator, if * that configuration is selected during consconfig. * When the kernel terminal emulator is enabled, consconfig_dacf * sets up the PROM output redirect vector to enter this function. * During panic the console will already be powered up as part of * calling into the prom_*() layer. */ /* ARGSUSED */ ssize_t wc_cons_wrtvec(promif_redir_arg_t arg, uchar_t *s, size_t n) { vc_state_t *pvc; pvc = vt_minor2vc(VT_ACTIVE); if (pvc->vc_tem == NULL) return (0); ASSERT(consmode == CONS_KFB); if (panicstr) polled_io_cons_write(s, n); else (void) tem_write(pvc->vc_tem, s, n, kcred); return (n); } #endif /* _HAVE_TEM_FIRMWARE */ /* * These are for systems without OBP, and for devices that cannot be * shared between Solaris and the OBP. */ static void wc_polled_putchar(cons_polledio_arg_t arg, unsigned char c) { vc_state_t *pvc; pvc = vt_minor2vc(VT_ACTIVE); if (c == '\n') wc_polled_putchar(arg, '\r'); if (pvc->vc_tem == NULL) { /* * We have no terminal emulator configured. We have no * recourse but to drop the output on the floor. */ return; } tem_safe_polled_write(pvc->vc_tem, &c, 1); } /* * These are for systems without OBP, and for devices that cannot be * shared between Solaris and the OBP. */ static int wc_polled_getchar(cons_polledio_arg_t arg) { struct wscons_state *wscons = (struct wscons_state *)arg; if (wscons->wc_kb_polledio == NULL) { prom_printf("wscons: getchar with no keyboard support"); prom_printf("Halted..."); for (;;) /* HANG FOREVER */; } return (wscons->wc_kb_polledio->cons_polledio_getchar( wscons->wc_kb_polledio->cons_polledio_argument)); } static boolean_t wc_polled_ischar(cons_polledio_arg_t arg) { struct wscons_state *wscons = (struct wscons_state *)arg; if (wscons->wc_kb_polledio == NULL) return (B_FALSE); return (wscons->wc_kb_polledio->cons_polledio_ischar( wscons->wc_kb_polledio->cons_polledio_argument)); } static void wc_polled_enter(cons_polledio_arg_t arg) { struct wscons_state *wscons = (struct wscons_state *)arg; if (wscons->wc_kb_polledio == NULL) return; if (wscons->wc_kb_polledio->cons_polledio_enter != NULL) { wscons->wc_kb_polledio->cons_polledio_enter( wscons->wc_kb_polledio->cons_polledio_argument); } } static void wc_polled_exit(cons_polledio_arg_t arg) { struct wscons_state *wscons = (struct wscons_state *)arg; if (wscons->wc_kb_polledio == NULL) return; if (wscons->wc_kb_polledio->cons_polledio_exit != NULL) { wscons->wc_kb_polledio->cons_polledio_exit( wscons->wc_kb_polledio->cons_polledio_argument); } } #ifdef DEBUG static void wc_dprintf(const char *fmt, ...) { char buf[256]; va_list ap; va_start(ap, fmt); (void) vsprintf(buf, fmt, ap); va_end(ap); cmn_err(CE_WARN, "wc: %s", buf); } #endif /*ARGSUSED*/ static void update_property(vc_state_t *pvc, char *name, ushort_t value) { char data[8]; (void) snprintf(data, sizeof (data), "%u", value); (void) ddi_prop_update_string(wscons.wc_dev, wc_dip, name, data); } /* * Gets the number of text rows and columns and the * width and height (in pixels) of the console. */ void wc_get_size(vc_state_t *pvc) { struct winsize *t = &pvc->vc_ttycommon.t_size; ushort_t r = LOSCREENLINES, c = LOSCREENCOLS, x = 0, y = 0; if (pvc->vc_tem != NULL) tem_get_size(&r, &c, &x, &y); #ifdef _HAVE_TEM_FIRMWARE else console_get_size(&r, &c, &x, &y); #endif /* _HAVE_TEM_FIRMWARE */ mutex_enter(&pvc->vc_ttycommon.t_excl); t->ws_col = c; t->ws_row = r; t->ws_xpixel = x; t->ws_ypixel = y; mutex_exit(&pvc->vc_ttycommon.t_excl); if (pvc->vc_minor != 0) return; /* only for the wscons:0 */ update_property(pvc, "screen-#cols", c); update_property(pvc, "screen-#rows", r); update_property(pvc, "screen-width", x); update_property(pvc, "screen-height", y); } /*ARGSUSED*/ static void wc_modechg_cb(tem_modechg_cb_arg_t arg) { minor_t index; vc_state_t *pvc; mutex_enter(&vc_lock); for (index = 0; index < VC_INSTANCES_COUNT; index++) { pvc = vt_minor2vc(index); mutex_enter(&pvc->vc_state_lock); if ((pvc->vc_flags & WCS_ISOPEN) && (pvc->vc_flags & WCS_INIT)) wc_get_size(pvc); mutex_exit(&pvc->vc_state_lock); } mutex_exit(&vc_lock); }