| /* |
| * 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 (c) 1982, 2010, Oracle and/or its affiliates. All rights reserved. |
| */ |
| |
| /* |
| * Indirect console driver for Sun. |
| * |
| * Redirects all I/O to the device designated as the underlying "hardware" |
| * console, as given by the value of rconsvp. The implementation assumes that |
| * rconsvp denotes a STREAMS device; the assumption is justified since |
| * consoles must be capable of effecting tty semantics. |
| * |
| * rconsvp is set in autoconf.c:consconfig(), based on information obtained |
| * from the EEPROM. |
| * |
| * XXX: The driver still needs to be converted to use ANSI C consistently |
| * throughout. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/open.h> |
| #include <sys/param.h> |
| #include <sys/systm.h> |
| #include <sys/signal.h> |
| #include <sys/cred.h> |
| #include <sys/user.h> |
| #include <sys/proc.h> |
| #include <sys/disp.h> |
| #include <sys/file.h> |
| #include <sys/taskq.h> |
| #include <sys/log.h> |
| #include <sys/vnode.h> |
| #include <sys/uio.h> |
| #include <sys/stat.h> |
| |
| #include <sys/console.h> |
| #include <sys/consdev.h> |
| |
| #include <sys/stream.h> |
| #include <sys/strsubr.h> |
| #include <sys/poll.h> |
| |
| #include <sys/debug.h> |
| |
| #include <sys/conf.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/vt.h> |
| |
| static int cnopen(dev_t *, int, int, struct cred *); |
| static int cnclose(dev_t, int, int, struct cred *); |
| static int cnread(dev_t, struct uio *, struct cred *); |
| static int cnwrite(dev_t, struct uio *, struct cred *); |
| static int cnioctl(dev_t, int, intptr_t, int, struct cred *, int *); |
| static int cnpoll(dev_t, short, int, short *, struct pollhead **); |
| static int cn_info(dev_info_t *, ddi_info_cmd_t, void *, void **); |
| static int cn_attach(dev_info_t *, ddi_attach_cmd_t); |
| static int cn_detach(dev_info_t *, ddi_detach_cmd_t); |
| |
| static dev_info_t *cn_dip; /* private copy of devinfo pointer */ |
| |
| static struct cb_ops cn_cb_ops = { |
| |
| cnopen, /* open */ |
| cnclose, /* close */ |
| nodev, /* strategy */ |
| nodev, /* print */ |
| nodev, /* dump */ |
| cnread, /* read */ |
| cnwrite, /* write */ |
| cnioctl, /* ioctl */ |
| nodev, /* devmap */ |
| nodev, /* mmap */ |
| nodev, /* segmap */ |
| cnpoll, /* poll */ |
| ddi_prop_op, /* cb_prop_op */ |
| 0, /* streamtab */ |
| D_NEW | D_MP /* Driver compatibility flag */ |
| |
| }; |
| |
| static struct dev_ops cn_ops = { |
| |
| DEVO_REV, /* devo_rev, */ |
| 0, /* refcnt */ |
| cn_info, /* info */ |
| nulldev, /* identify */ |
| nulldev, /* probe */ |
| cn_attach, /* attach */ |
| cn_detach, /* detach */ |
| nodev, /* reset */ |
| &cn_cb_ops, /* driver operations */ |
| (struct bus_ops *)0, /* bus operations */ |
| NULL, /* power */ |
| ddi_quiesce_not_needed, /* quiesce */ |
| |
| }; |
| |
| /* |
| * Global variables associated with the console device: |
| * |
| * XXX: There are too many of these! |
| * moved to space.c to become resident in the kernel so that cons |
| * can be loadable. |
| */ |
| |
| extern dev_t rconsdev; /* "hardware" console */ |
| extern vnode_t *rconsvp; /* pointer to vnode for that device */ |
| |
| /* |
| * XXX: consulted in prsubr.c, for /proc entry point for obtaining ps info. |
| */ |
| extern dev_t uconsdev; /* What the user thinks is the console device */ |
| |
| /* |
| * Private driver state: |
| */ |
| |
| /* |
| * The underlying console device potentially can be opened through (at least) |
| * two paths: through this driver and through the underlying device's driver. |
| * To ensure that reference counts are meaningful and therefore that close |
| * routines are called at the right time, it's important to make sure that |
| * rconsvp's s_count field (i.e., the count on the underlying device) never |
| * has a contribution of more than one through this driver, regardless of how |
| * many times this driver's been opened. rconsopen keeps track of the |
| * necessary information to ensure this property. |
| */ |
| static uint_t rconsopen; |
| |
| |
| #include <sys/types.h> |
| #include <sys/conf.h> |
| #include <sys/param.h> |
| #include <sys/systm.h> |
| #include <sys/errno.h> |
| #include <sys/modctl.h> |
| |
| |
| extern int nodev(), nulldev(); |
| extern int dseekneg_flag; |
| extern struct mod_ops mod_driverops; |
| extern struct dev_ops cn_ops; |
| |
| /* |
| * Module linkage information for the kernel. |
| */ |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, /* Type of module. This one is a pseudo driver */ |
| "Console redirection driver", |
| &cn_ops, /* driver ops */ |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, |
| &modldrv, |
| NULL |
| }; |
| |
| int |
| _init(void) |
| { |
| return (mod_install(&modlinkage)); |
| } |
| |
| int |
| _fini(void) |
| { |
| return (EBUSY); |
| } |
| |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| /* |
| * DDI glue routines |
| */ |
| static int |
| cn_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) |
| { |
| if (cmd != DDI_ATTACH) |
| return (DDI_FAILURE); |
| |
| if (ddi_create_minor_node(devi, "syscon", S_IFCHR, |
| 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { |
| return (DDI_FAILURE); |
| } |
| if (ddi_create_minor_node(devi, "systty", S_IFCHR, |
| 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { |
| ddi_remove_minor_node(devi, NULL); |
| return (DDI_FAILURE); |
| } |
| if (ddi_create_minor_node(devi, "console", S_IFCHR, |
| 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { |
| ddi_remove_minor_node(devi, NULL); |
| return (DDI_FAILURE); |
| } |
| |
| cn_dip = devi; |
| return (DDI_SUCCESS); |
| } |
| |
| static int |
| cn_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) |
| { |
| if (cmd != DDI_DETACH) |
| return (DDI_FAILURE); |
| ddi_remove_minor_node(devi, NULL); |
| uconsdev = NODEV; |
| return (DDI_SUCCESS); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cn_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) |
| { |
| int error = DDI_FAILURE; |
| |
| switch (infocmd) { |
| case DDI_INFO_DEVT2DEVINFO: |
| if (getminor((dev_t)arg) == 0 && cn_dip != NULL) { |
| *result = (void *) cn_dip; |
| error = DDI_SUCCESS; |
| } |
| break; |
| |
| case DDI_INFO_DEVT2INSTANCE: |
| if (getminor((dev_t)arg) == 0) { |
| *result = (void *)0; |
| error = DDI_SUCCESS; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| return (error); |
| } |
| |
| /* |
| * XXX Caution: before allowing more than 256 minor devices on the |
| * console, make sure you understand the 'compatibility' hack |
| * in ufs_iget() that translates old dev_t's to new dev_t's. |
| * See bugid 1098104 for the sordid details. |
| */ |
| |
| /* ARGSUSED */ |
| static int |
| cnopen(dev_t *dev, int flag, int state, struct cred *cred) |
| { |
| int err; |
| static int been_here; |
| vnode_t *vp = rconsvp; |
| |
| ASSERT(cred != NULL); |
| |
| if (rconsvp == NULL) |
| return (0); |
| |
| /* |
| * Enable virtual console I/O for console logging if needed. |
| */ |
| if (vsconsvp != NULL && vsconsvp->v_stream == NULL) { |
| if (VOP_OPEN(&vsconsvp, FREAD | FWRITE, cred, NULL) != 0) { |
| cmn_err(CE_WARN, "cnopen: failed to open vsconsvp " |
| "for virtual console logging"); |
| } |
| } |
| |
| /* |
| * XXX: Clean up inactive PIDs from previous opens if any. |
| * These would have been created as a result of an I_SETSIG |
| * issued against console. This is a workaround, and |
| * console driver must be correctly redesigned not to need |
| * this hook. |
| */ |
| if (vp->v_stream) { |
| str_cn_clean(vp); |
| } |
| |
| /* |
| * XXX: Set hook to tell /proc about underlying console. (There's |
| * gotta be a better way...) |
| */ |
| if (state != OTYP_CHR || getminor(*dev) != 0) |
| return (ENXIO); |
| if (been_here == 0) { |
| uconsdev = *dev; |
| been_here = 1; |
| if (vn_open("/dev/console", UIO_SYSSPACE, FWRITE | FNOCTTY, |
| 0, &console_vnode, 0, 0) == 0) |
| console_taskq = taskq_create("console_taskq", |
| 1, maxclsyspri - 1, LOG_LOWAT / LOG_MSGSIZE, |
| LOG_HIWAT / LOG_MSGSIZE, TASKQ_PREPOPULATE); |
| } |
| |
| if ((err = VOP_OPEN(&vp, flag, cred, NULL)) != 0) |
| return (err); |
| |
| /* |
| * The underlying driver is not allowed to have cloned itself |
| * for this open. |
| */ |
| if (vp != rconsvp) { |
| /* |
| * It might happen that someone set rconsvp to NULL |
| * whilst we were in the middle of the open. |
| */ |
| if (rconsvp == NULL) { |
| (void) VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL); |
| return (0); |
| } |
| cmn_err(CE_PANIC, "cnopen: cloned open"); |
| } |
| |
| rconsopen++; |
| |
| return (0); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cnclose(dev_t dev, int flag, int state, struct cred *cred) |
| { |
| int err = 0; |
| vnode_t *vp; |
| |
| /* |
| * Since this is the _last_ close, it's our last chance to close the |
| * underlying device. (Note that if someone else has the underlying |
| * hardware console device open, we won't get here, since spec_close |
| * will see s_count > 1.) |
| */ |
| if (state != OTYP_CHR) |
| return (ENXIO); |
| |
| if (rconsvp == NULL) |
| return (0); |
| |
| while ((rconsopen != 0) && ((vp = rconsvp) != NULL)) { |
| err = VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL); |
| if (!err) { |
| rconsopen--; |
| } |
| } |
| return (err); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cnread(dev_t dev, struct uio *uio, struct cred *cred) |
| { |
| kcondvar_t sleep_forever; |
| kmutex_t sleep_forever_mutex; |
| |
| if (rconsvp == NULL) { |
| /* |
| * Go to sleep forever. This seems like the least |
| * harmful thing to do if there's no console. |
| * EOF might be better if we're ending up single-user |
| * mode. |
| */ |
| cv_init(&sleep_forever, NULL, CV_DRIVER, NULL); |
| mutex_init(&sleep_forever_mutex, NULL, MUTEX_DRIVER, NULL); |
| mutex_enter(&sleep_forever_mutex); |
| (void) cv_wait_sig(&sleep_forever, &sleep_forever_mutex); |
| mutex_exit(&sleep_forever_mutex); |
| return (EIO); |
| } |
| |
| if (rconsvp->v_stream != NULL) |
| return (strread(rconsvp, uio, cred)); |
| else |
| return (cdev_read(rconsdev, uio, cred)); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cnwrite(dev_t dev, struct uio *uio, struct cred *cred) |
| { |
| if (rconsvp == NULL) { |
| uio->uio_resid = 0; |
| return (0); |
| } |
| |
| /* |
| * Output to virtual console for logging if enabled. |
| */ |
| if (vsconsvp != NULL && vsconsvp->v_stream != NULL) { |
| struiod_t uiod; |
| |
| /* |
| * strwrite modifies uio so need to make copy. |
| */ |
| (void) uiodup(uio, &uiod.d_uio, uiod.d_iov, |
| sizeof (uiod.d_iov) / sizeof (*uiod.d_iov)); |
| |
| (void) strwrite(vsconsvp, &uiod.d_uio, cred); |
| } |
| |
| if (rconsvp->v_stream != NULL) |
| return (strwrite(rconsvp, uio, cred)); |
| else |
| return (cdev_write(rconsdev, uio, cred)); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cnprivateioc(dev_t dev, int cmd, intptr_t arg, int flag, struct cred *cred, |
| int *rvalp) |
| { |
| |
| /* currently we only support one ioctl */ |
| if (cmd != CONS_GETTERM) |
| return (EINVAL); |
| |
| /* Confirm iwscn is immediate target of cn redirection */ |
| if (rconsvp != wsconsvp) |
| return (ENODEV); |
| |
| /* |
| * If the redirection client is not wc, it should return |
| * error upon receiving the CONS_GETTERM ioctl. |
| * |
| * if it is wc, we know that the target supports the CONS_GETTERM |
| * ioctl, which very conviently has the exact same data |
| * format as this ioctl... so let's just pass it on. |
| */ |
| return (cdev_ioctl(rconsdev, CONS_GETTERM, arg, flag, cred, rvalp)); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cnioctl(dev_t dev, int cmd, intptr_t arg, int flag, struct cred *cred, |
| int *rvalp) |
| { |
| if (rconsvp == NULL) |
| return (0); |
| |
| /* |
| * In wc, VT_SET_CONSUSER which comes from minor node 0 |
| * has two sources -- either /dev/console or /dev/vt/0 . |
| * We need a way to differentiate them, so here we |
| * change VT_SET_CONSUSER to a private VT_RESET_CONSUSER |
| * ioctl. |
| */ |
| if (cmd == VT_SET_CONSUSER) |
| cmd = VT_RESET_CONSUSER; |
| |
| if ((cmd & _CNIOC_MASK) == _CNIOC) |
| return (cnprivateioc(dev, cmd, arg, flag, cred, rvalp)); |
| |
| if (rconsvp->v_stream != NULL) |
| return (strioctl(rconsvp, cmd, arg, flag, U_TO_K, |
| cred, rvalp)); |
| |
| return (cdev_ioctl(rconsdev, cmd, arg, flag, cred, rvalp)); |
| } |
| |
| /* ARGSUSED */ |
| static int |
| cnpoll(dev_t dev, short events, int anyyet, short *reventsp, |
| struct pollhead **phpp) |
| { |
| if (rconsvp == NULL) |
| return (nochpoll(dev, events, anyyet, reventsp, phpp)); |
| |
| if (rconsvp->v_stream != NULL) |
| return (strpoll(rconsvp->v_stream, events, anyyet, reventsp, |
| phpp)); |
| else |
| return (cdev_poll(rconsdev, events, anyyet, reventsp, phpp)); |
| } |