From c2c324d37976496417e7195d8daae3975be8eaa1 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 1 Nov 2008 08:35:28 +0000 Subject: Reimplement the /dev/console device node. One of the pieces of code that I had left alone during the development of the MPSAFE TTY layer, was tty_cons.c. This file actually has two different functions: - It contains low-level console input/output routines (cnputc(), etc). - It creates /dev/console and wraps all its cdevsw calls to the appropriate TTY. This commit reimplements the second set of functions by moving it directly into the TTY layer. /dev/console is now a character device node that's basically a regular TTY, but does a lookup of `si_drv1' each time you open it. d_write has also been changed to call log_console(). d_close() is not present, because we must make sure we don't revoke the TTY after writing a log message to it. Even though I'm not convinced this is in line with the future directions of our console code, it is a good move for now. It removes recursive locking from the top half of the TTY layer. The previous implementation called into the TTY layer with Giant held. I'm renaming tty_cons.c to kern_cons.c now. The code hardly contains any TTY related bits, so we'd better give it a less misleading name. Tested by: Andrzej Tobola , Carlos A.M. dos Santos , Eygene Ryabinkin --- sys/conf/files | 2 +- sys/kern/kern_cons.c | 566 ++++++++++++++++++++++++++++++++++++ sys/kern/tty.c | 96 ++++++- sys/kern/tty_cons.c | 796 --------------------------------------------------- sys/sys/tty.h | 3 + 5 files changed, 657 insertions(+), 806 deletions(-) create mode 100644 sys/kern/kern_cons.c delete mode 100644 sys/kern/tty_cons.c diff --git a/sys/conf/files b/sys/conf/files index fba04aa..ad5a19a 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1596,6 +1596,7 @@ kern/kern_alq.c optional alq kern/kern_clock.c standard kern/kern_condvar.c standard kern/kern_conf.c standard +kern/kern_cons.c standard kern/kern_cpu.c standard kern/kern_cpuset.c standard kern/kern_context.c standard @@ -1708,7 +1709,6 @@ kern/sysv_sem.c optional sysvsem kern/sysv_shm.c optional sysvshm kern/tty.c standard kern/tty_compat.c optional compat_43tty -kern/tty_cons.c standard kern/tty_info.c standard kern/tty_inq.c standard kern/tty_outq.c standard diff --git a/sys/kern/kern_cons.c b/sys/kern/kern_cons.c new file mode 100644 index 0000000..3930f59 --- /dev/null +++ b/sys/kern/kern_cons.c @@ -0,0 +1,566 @@ +/*- + * Copyright (c) 1988 University of Utah. + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * the Systems Programming Group of the University of Utah Computer + * Science Department. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)cons.c 7.2 (Berkeley) 5/9/91 + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_ddb.h" + +#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 MALLOC_DEFINE(M_TTYCONS, "tty console", "tty console handling"); + +struct cn_device { + STAILQ_ENTRY(cn_device) cnd_next; + struct consdev *cnd_cn; +}; + +#define CNDEVPATHMAX 32 +#define CNDEVTAB_SIZE 4 +static struct cn_device cn_devtab[CNDEVTAB_SIZE]; +static STAILQ_HEAD(, cn_device) cn_devlist = + STAILQ_HEAD_INITIALIZER(cn_devlist); + +int cons_avail_mask = 0; /* Bit mask. Each registered low level console + * which is currently unavailable for inpit + * (i.e., if it is in graphics mode) will have + * this bit cleared. + */ +static int cn_mute; +static char *consbuf; /* buffer used by `consmsgbuf' */ +static struct callout conscallout; /* callout for outputting to constty */ +struct msgbuf consmsgbuf; /* message buffer for console tty */ +static u_char console_pausing; /* pause after each line during probe */ +static char *console_pausestr= +""; +struct tty *constty; /* pointer to console "window" tty */ +static struct mtx cnputs_mtx; /* Mutex for cnputs(). */ +static int use_cnputs_mtx = 0; /* != 0 if cnputs_mtx locking reqd. */ + +static void constty_timeout(void *arg); + +static struct consdev cons_consdev; +DATA_SET(cons_set, cons_consdev); +SET_DECLARE(cons_set, struct consdev); + +void +cninit(void) +{ + struct consdev *best_cn, *cn, **list; + + /* + * Check if we should mute the console (for security reasons perhaps) + * It can be changes dynamically using sysctl kern.consmute + * once we are up and going. + * + */ + cn_mute = ((boothowto & (RB_MUTE + |RB_SINGLE + |RB_VERBOSE + |RB_ASKNAME)) == RB_MUTE); + + /* + * Find the first console with the highest priority. + */ + best_cn = NULL; + SET_FOREACH(list, cons_set) { + cn = *list; + cnremove(cn); + if (cn->cn_probe == NULL) + continue; + cn->cn_probe(cn); + if (cn->cn_pri == CN_DEAD) + continue; + if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri) + best_cn = cn; + if (boothowto & RB_MULTIPLE) { + /* + * Initialize console, and attach to it. + */ + cn->cn_init(cn); + cnadd(cn); + } + } + if (best_cn == NULL) + return; + if ((boothowto & RB_MULTIPLE) == 0) { + best_cn->cn_init(best_cn); + cnadd(best_cn); + } + if (boothowto & RB_PAUSE) + console_pausing = 1; + /* + * Make the best console the preferred console. + */ + cnselect(best_cn); +} + +void +cninit_finish() +{ + console_pausing = 0; +} + +/* add a new physical console to back the virtual console */ +int +cnadd(struct consdev *cn) +{ + struct cn_device *cnd; + int i; + + STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) + if (cnd->cnd_cn == cn) + return (0); + for (i = 0; i < CNDEVTAB_SIZE; i++) { + cnd = &cn_devtab[i]; + if (cnd->cnd_cn == NULL) + break; + } + if (cnd->cnd_cn != NULL) + return (ENOMEM); + cnd->cnd_cn = cn; + if (cn->cn_name[0] == '\0') { + /* XXX: it is unclear if/where this print might output */ + printf("WARNING: console at %p has no name\n", cn); + } + STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next); + if (STAILQ_FIRST(&cn_devlist) == cnd) + ttyconsdev_select(cnd->cnd_cn->cn_name); + + /* Add device to the active mask. */ + cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0); + + return (0); +} + +void +cnremove(struct consdev *cn) +{ + struct cn_device *cnd; + int i; + + STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { + if (cnd->cnd_cn != cn) + continue; + if (STAILQ_FIRST(&cn_devlist) == cnd) + ttyconsdev_select(NULL); + STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); + cnd->cnd_cn = NULL; + + /* Remove this device from available mask. */ + for (i = 0; i < CNDEVTAB_SIZE; i++) + if (cnd == &cn_devtab[i]) { + cons_avail_mask &= ~(1 << i); + break; + } +#if 0 + /* + * XXX + * syscons gets really confused if console resources are + * freed after the system has initialized. + */ + if (cn->cn_term != NULL) + cn->cn_term(cn); +#endif + return; + } +} + +void +cnselect(struct consdev *cn) +{ + struct cn_device *cnd; + + STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { + if (cnd->cnd_cn != cn) + continue; + if (cnd == STAILQ_FIRST(&cn_devlist)) + return; + STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); + STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next); + ttyconsdev_select(cnd->cnd_cn->cn_name); + return; + } +} + +void +cnavailable(struct consdev *cn, int available) +{ + int i; + + for (i = 0; i < CNDEVTAB_SIZE; i++) { + if (cn_devtab[i].cnd_cn == cn) + break; + } + if (available) { + if (i < CNDEVTAB_SIZE) + cons_avail_mask |= (1 << i); + cn->cn_flags &= ~CN_FLAG_NOAVAIL; + } else { + if (i < CNDEVTAB_SIZE) + cons_avail_mask &= ~(1 << i); + cn->cn_flags |= CN_FLAG_NOAVAIL; + } +} + +int +cnunavailable(void) +{ + + return (cons_avail_mask == 0); +} + +/* + * sysctl_kern_console() provides output parseable in conscontrol(1). + */ +static int +sysctl_kern_console(SYSCTL_HANDLER_ARGS) +{ + struct cn_device *cnd; + struct consdev *cp, **list; + char *p; + int delete, error; + struct sbuf *sb; + + sb = sbuf_new(NULL, NULL, CNDEVPATHMAX * 2, SBUF_AUTOEXTEND); + if (sb == NULL) + return (ENOMEM); + sbuf_clear(sb); + STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) + sbuf_printf(sb, "%s,", cnd->cnd_cn->cn_name); + sbuf_printf(sb, "/"); + SET_FOREACH(list, cons_set) { + cp = *list; + if (cp->cn_name[0] != '\0') + sbuf_printf(sb, "%s,", cp->cn_name); + } + sbuf_finish(sb); + error = sysctl_handle_string(oidp, sbuf_data(sb), sbuf_len(sb), req); + if (error == 0 && req->newptr != NULL) { + p = sbuf_data(sb); + error = ENXIO; + delete = 0; + if (*p == '-') { + delete = 1; + p++; + } + SET_FOREACH(list, cons_set) { + cp = *list; + if (strcmp(p, cp->cn_name) != 0) + continue; + if (delete) { + cnremove(cp); + error = 0; + } else { + error = cnadd(cp); + if (error == 0) + cnselect(cp); + } + break; + } + } + sbuf_delete(sb); + return (error); +} + +SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW, + 0, 0, sysctl_kern_console, "A", "Console device control"); + +/* + * User has changed the state of the console muting. + * This may require us to open or close the device in question. + */ +static int +sysctl_kern_consmute(SYSCTL_HANDLER_ARGS) +{ + int error; + int ocn_mute; + + ocn_mute = cn_mute; + error = sysctl_handle_int(oidp, &cn_mute, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + return (error); +} + +SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW, + 0, sizeof(cn_mute), sysctl_kern_consmute, "I", ""); + +/* + * Low level console routines. + */ +int +cngetc(void) +{ + int c; + + if (cn_mute) + return (-1); + while ((c = cncheckc()) == -1) + ; + if (c == '\r') + c = '\n'; /* console input is always ICRNL */ + return (c); +} + +int +cncheckc(void) +{ + struct cn_device *cnd; + struct consdev *cn; + int c; + + if (cn_mute) + return (-1); + STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { + cn = cnd->cnd_cn; + if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { + if (cn->cn_checkc != NULL) + c = cn->cn_checkc(cn); + else + c = cn->cn_getc(cn); + if (c != -1) { + return (c); + } + } + } + return (-1); +} + +void +cnputc(int c) +{ + struct cn_device *cnd; + struct consdev *cn; + char *cp; + + if (cn_mute || c == '\0') + return; + STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { + cn = cnd->cnd_cn; + if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { + if (c == '\n') + cn->cn_putc(cn, '\r'); + cn->cn_putc(cn, c); + } + } + if (console_pausing && c == '\n' && !kdb_active) { + for (cp = console_pausestr; *cp != '\0'; cp++) + cnputc(*cp); + if (cngetc() == '.') + console_pausing = 0; + cnputc('\r'); + for (cp = console_pausestr; *cp != '\0'; cp++) + cnputc(' '); + cnputc('\r'); + } +} + +void +cnputs(char *p) +{ + int c; + int unlock_reqd = 0; + + if (use_cnputs_mtx) { + mtx_lock_spin(&cnputs_mtx); + unlock_reqd = 1; + } + + while ((c = *p++) != '\0') + cnputc(c); + + if (unlock_reqd) + mtx_unlock_spin(&cnputs_mtx); +} + +static int consmsgbuf_size = 8192; +SYSCTL_INT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RW, &consmsgbuf_size, 0, + ""); + +/* + * Redirect console output to a tty. + */ +void +constty_set(struct tty *tp) +{ + int size; + + KASSERT(tp != NULL, ("constty_set: NULL tp")); + if (consbuf == NULL) { + size = consmsgbuf_size; + consbuf = malloc(size, M_TTYCONS, M_WAITOK); + msgbuf_init(&consmsgbuf, consbuf, size); + callout_init(&conscallout, 0); + } + constty = tp; + constty_timeout(NULL); +} + +/* + * Disable console redirection to a tty. + */ +void +constty_clear(void) +{ + int c; + + constty = NULL; + if (consbuf == NULL) + return; + callout_stop(&conscallout); + while ((c = msgbuf_getchar(&consmsgbuf)) != -1) + cnputc(c); + free(consbuf, M_TTYCONS); + consbuf = NULL; +} + +/* Times per second to check for pending console tty messages. */ +static int constty_wakeups_per_second = 5; +SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW, + &constty_wakeups_per_second, 0, ""); + +static void +constty_timeout(void *arg) +{ + int c; + + if (constty != NULL) { + tty_lock(constty); + while ((c = msgbuf_getchar(&consmsgbuf)) != -1) { + if (tty_putchar(constty, c) < 0) { + tty_unlock(constty); + constty = NULL; + break; + } + } + + if (constty != NULL) + tty_unlock(constty); + } + if (constty != NULL) { + callout_reset(&conscallout, hz / constty_wakeups_per_second, + constty_timeout, NULL); + } else { + /* Deallocate the constty buffer memory. */ + constty_clear(); + } +} + +static void +cn_drvinit(void *unused) +{ + + mtx_init(&cnputs_mtx, "cnputs_mtx", NULL, MTX_SPIN | MTX_NOWITNESS); + use_cnputs_mtx = 1; +} + +SYSINIT(cndev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, cn_drvinit, NULL); + +/* + * Sysbeep(), if we have hardware for it + */ + +#ifdef HAS_TIMER_SPKR + +static int beeping; + +static void +sysbeepstop(void *chan) +{ + + timer_spkr_release(); + beeping = 0; +} + +int +sysbeep(int pitch, int period) +{ + + if (timer_spkr_acquire()) { + if (!beeping) { + /* Something else owns it. */ + return (EBUSY); + } + } + timer_spkr_setfreq(pitch); + if (!beeping) { + beeping = period; + timeout(sysbeepstop, (void *)NULL, period); + } + return (0); +} + +#else + +/* + * No hardware, no sound + */ + +int +sysbeep(int pitch __unused, int period __unused) +{ + + return (ENODEV); +} + +#endif + diff --git a/sys/kern/tty.c b/sys/kern/tty.c index 16fda40..8f6f9b2 100644 --- a/sys/kern/tty.c +++ b/sys/kern/tty.c @@ -73,6 +73,10 @@ static struct sx tty_list_sx; SX_SYSINIT(tty_list, &tty_list_sx, "tty list"); static unsigned int tty_list_count = 0; +/* Character device of /dev/console. */ +static struct cdev *dev_console; +static const char *dev_console_filename; + /* * Flags that are supported and stored by this implementation. */ @@ -86,7 +90,7 @@ static unsigned int tty_list_count = 0; HUPCL|CLOCAL|CCTS_OFLOW|CRTS_IFLOW|CDTR_IFLOW|\ CDSR_OFLOW|CCAR_OFLOW) -#define TTY_CALLOUT(tp,d) ((tp)->t_dev != (d)) +#define TTY_CALLOUT(tp,d) ((d) != (tp)->t_dev && (d) != dev_console) /* * Set TTY buffer sizes. @@ -1189,11 +1193,7 @@ tty_wait(struct tty *tp, struct cv *cv) int error; int revokecnt = tp->t_revokecnt; -#if 0 - /* XXX: /dev/console also picks up Giant. */ tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); -#endif - tty_lock_assert(tp, MA_OWNED); MPASS(!tty_gone(tp)); error = cv_wait_sig(cv, tp->t_mtx); @@ -1215,11 +1215,7 @@ tty_timedwait(struct tty *tp, struct cv *cv, int hz) int error; int revokecnt = tp->t_revokecnt; -#if 0 - /* XXX: /dev/console also picks up Giant. */ tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); -#endif - tty_lock_assert(tp, MA_OWNED); MPASS(!tty_gone(tp)); error = cv_timedwait_sig(cv, tp->t_mtx, hz); @@ -1662,6 +1658,10 @@ tty_hiwat_in_unblock(struct tty *tp) ttydevsw_inwakeup(tp); } +/* + * TTY hooks interface. + */ + static int ttyhook_defrint(struct tty *tp, char c, int flags) { @@ -1745,6 +1745,84 @@ ttyhook_unregister(struct tty *tp) tty_rel_free(tp); } +/* + * /dev/console handling. + */ + +static int +ttyconsdev_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + struct tty *tp; + + /* System has no console device. */ + if (dev_console_filename == NULL) + return (ENXIO); + + /* Look up corresponding TTY by device name. */ + sx_slock(&tty_list_sx); + TAILQ_FOREACH(tp, &tty_list, t_list) { + if (strcmp(dev_console_filename, tty_devname(tp)) == 0) { + dev_console->si_drv1 = tp; + break; + } + } + sx_sunlock(&tty_list_sx); + + /* System console has no TTY associated. */ + if (dev_console->si_drv1 == NULL) + return (ENXIO); + + return (ttydev_open(dev, oflags, devtype, td)); +} + +static int +ttyconsdev_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + + log_console(uio); + + return (ttydev_write(dev, uio, ioflag)); +} + +/* + * /dev/console is a little different than normal TTY's. Unlike regular + * TTY device nodes, this device node will not revoke the entire TTY + * upon closure and all data written to it will be logged. + */ +static struct cdevsw ttyconsdev_cdevsw = { + .d_version = D_VERSION, + .d_open = ttyconsdev_open, + .d_read = ttydev_read, + .d_write = ttyconsdev_write, + .d_ioctl = ttydev_ioctl, + .d_kqfilter = ttydev_kqfilter, + .d_poll = ttydev_poll, + .d_mmap = ttydev_mmap, + .d_name = "ttyconsdev", + .d_flags = D_TTY, +}; + +static void +ttyconsdev_init(void *unused) +{ + + dev_console = make_dev(&ttyconsdev_cdevsw, 0, UID_ROOT, GID_WHEEL, + 0600, "console"); +} + +SYSINIT(tty, SI_SUB_DRIVERS, SI_ORDER_FIRST, ttyconsdev_init, NULL); + +void +ttyconsdev_select(const char *name) +{ + + dev_console_filename = name; +} + +/* + * Debugging routines. + */ + #include "opt_ddb.h" #ifdef DDB #include diff --git a/sys/kern/tty_cons.c b/sys/kern/tty_cons.c deleted file mode 100644 index 81e1708..0000000 --- a/sys/kern/tty_cons.c +++ /dev/null @@ -1,796 +0,0 @@ -/*- - * Copyright (c) 1988 University of Utah. - * Copyright (c) 1991 The Regents of the University of California. - * All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * the Systems Programming Group of the University of Utah Computer - * Science Department. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * from: @(#)cons.c 7.2 (Berkeley) 5/9/91 - */ - -#include -__FBSDID("$FreeBSD$"); - -#include "opt_ddb.h" - -#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 MALLOC_DEFINE(M_TTYCONS, "tty console", "tty console handling"); - -static d_open_t cnopen; -static d_close_t cnclose; -static d_read_t cnread; -static d_write_t cnwrite; -static d_ioctl_t cnioctl; -static d_poll_t cnpoll; -static d_kqfilter_t cnkqfilter; - -static struct cdevsw cn_cdevsw = { - .d_version = D_VERSION, - .d_open = cnopen, - .d_close = cnclose, - .d_read = cnread, - .d_write = cnwrite, - .d_ioctl = cnioctl, - .d_poll = cnpoll, - .d_name = "console", - .d_flags = D_TTY | D_NEEDGIANT, - .d_kqfilter = cnkqfilter, -}; - -struct cn_device { - STAILQ_ENTRY(cn_device) cnd_next; - struct vnode *cnd_vp; - struct consdev *cnd_cn; -}; - -#define CNDEVPATHMAX 32 -#define CNDEVTAB_SIZE 4 -static struct cn_device cn_devtab[CNDEVTAB_SIZE]; -static STAILQ_HEAD(, cn_device) cn_devlist = - STAILQ_HEAD_INITIALIZER(cn_devlist); - -#define CND_INVALID(cnd, td) \ - (cnd == NULL || cnd->cnd_vp == NULL || \ - (cnd->cnd_vp->v_type == VBAD && !cn_devopen(cnd, td, 1))) - -static dev_t cn_udev_t; -SYSCTL_OPAQUE(_machdep, OID_AUTO, consdev, CTLFLAG_RD, - &cn_udev_t, sizeof cn_udev_t, "T,struct cdev *", ""); - -int cons_avail_mask = 0; /* Bit mask. Each registered low level console - * which is currently unavailable for inpit - * (i.e., if it is in graphics mode) will have - * this bit cleared. - */ -static int cn_mute; -static int openflag; /* how /dev/console was opened */ -static int cn_is_open; -static char *consbuf; /* buffer used by `consmsgbuf' */ -static struct callout conscallout; /* callout for outputting to constty */ -struct msgbuf consmsgbuf; /* message buffer for console tty */ -static u_char console_pausing; /* pause after each line during probe */ -static char *console_pausestr= -""; -struct tty *constty; /* pointer to console "window" tty */ -static struct mtx cnputs_mtx; /* Mutex for cnputs(). */ -static int use_cnputs_mtx = 0; /* != 0 if cnputs_mtx locking reqd. */ - -static void constty_timeout(void *arg); - -static struct consdev cons_consdev; -DATA_SET(cons_set, cons_consdev); -SET_DECLARE(cons_set, struct consdev); - -void -cninit(void) -{ - struct consdev *best_cn, *cn, **list; - - /* - * Check if we should mute the console (for security reasons perhaps) - * It can be changes dynamically using sysctl kern.consmute - * once we are up and going. - * - */ - cn_mute = ((boothowto & (RB_MUTE - |RB_SINGLE - |RB_VERBOSE - |RB_ASKNAME)) == RB_MUTE); - - /* - * Find the first console with the highest priority. - */ - best_cn = NULL; - SET_FOREACH(list, cons_set) { - cn = *list; - cnremove(cn); - if (cn->cn_probe == NULL) - continue; - cn->cn_probe(cn); - if (cn->cn_pri == CN_DEAD) - continue; - if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri) - best_cn = cn; - if (boothowto & RB_MULTIPLE) { - /* - * Initialize console, and attach to it. - */ - cn->cn_init(cn); - cnadd(cn); - } - } - if (best_cn == NULL) - return; - if ((boothowto & RB_MULTIPLE) == 0) { - best_cn->cn_init(best_cn); - cnadd(best_cn); - } - if (boothowto & RB_PAUSE) - console_pausing = 1; - /* - * Make the best console the preferred console. - */ - cnselect(best_cn); -} - -void -cninit_finish() -{ - console_pausing = 0; -} - -/* add a new physical console to back the virtual console */ -int -cnadd(struct consdev *cn) -{ - struct cn_device *cnd; - int i; - - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) - if (cnd->cnd_cn == cn) - return (0); - for (i = 0; i < CNDEVTAB_SIZE; i++) { - cnd = &cn_devtab[i]; - if (cnd->cnd_cn == NULL) - break; - } - if (cnd->cnd_cn != NULL) - return (ENOMEM); - cnd->cnd_cn = cn; - if (cn->cn_name[0] == '\0') { - /* XXX: it is unclear if/where this print might output */ - printf("WARNING: console at %p has no name\n", cn); - } - STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next); - - /* Add device to the active mask. */ - cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0); - - return (0); -} - -void -cnremove(struct consdev *cn) -{ - struct cn_device *cnd; - int i; - - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { - if (cnd->cnd_cn != cn) - continue; - STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); - if (cnd->cnd_vp != NULL) - vn_close(cnd->cnd_vp, openflag, NOCRED, NULL); - cnd->cnd_vp = NULL; - cnd->cnd_cn = NULL; - - /* Remove this device from available mask. */ - for (i = 0; i < CNDEVTAB_SIZE; i++) - if (cnd == &cn_devtab[i]) { - cons_avail_mask &= ~(1 << i); - break; - } -#if 0 - /* - * XXX - * syscons gets really confused if console resources are - * freed after the system has initialized. - */ - if (cn->cn_term != NULL) - cn->cn_term(cn); -#endif - return; - } -} - -void -cnselect(struct consdev *cn) -{ - struct cn_device *cnd; - - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { - if (cnd->cnd_cn != cn) - continue; - if (cnd == STAILQ_FIRST(&cn_devlist)) - return; - STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next); - STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next); - return; - } -} - -void -cnavailable(struct consdev *cn, int available) -{ - int i; - - for (i = 0; i < CNDEVTAB_SIZE; i++) { - if (cn_devtab[i].cnd_cn == cn) - break; - } - if (available) { - if (i < CNDEVTAB_SIZE) - cons_avail_mask |= (1 << i); - cn->cn_flags &= ~CN_FLAG_NOAVAIL; - } else { - if (i < CNDEVTAB_SIZE) - cons_avail_mask &= ~(1 << i); - cn->cn_flags |= CN_FLAG_NOAVAIL; - } -} - -int -cnunavailable(void) -{ - - return (cons_avail_mask == 0); -} - -/* - * sysctl_kern_console() provides output parseable in conscontrol(1). - */ -static int -sysctl_kern_console(SYSCTL_HANDLER_ARGS) -{ - struct cn_device *cnd; - struct consdev *cp, **list; - char *p; - int delete, error; - struct sbuf *sb; - - sb = sbuf_new(NULL, NULL, CNDEVPATHMAX * 2, SBUF_AUTOEXTEND); - if (sb == NULL) - return (ENOMEM); - sbuf_clear(sb); - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) - sbuf_printf(sb, "%s,", cnd->cnd_cn->cn_name); - sbuf_printf(sb, "/"); - SET_FOREACH(list, cons_set) { - cp = *list; - if (cp->cn_name[0] != '\0') - sbuf_printf(sb, "%s,", cp->cn_name); - } - sbuf_finish(sb); - error = sysctl_handle_string(oidp, sbuf_data(sb), sbuf_len(sb), req); - if (error == 0 && req->newptr != NULL) { - p = sbuf_data(sb); - error = ENXIO; - delete = 0; - if (*p == '-') { - delete = 1; - p++; - } - SET_FOREACH(list, cons_set) { - cp = *list; - if (strcmp(p, cp->cn_name) != 0) - continue; - if (delete) { - cnremove(cp); - error = 0; - } else { - error = cnadd(cp); - if (error == 0) - cnselect(cp); - } - break; - } - } - sbuf_delete(sb); - return (error); -} - -SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW, - 0, 0, sysctl_kern_console, "A", "Console device control"); - -/* - * User has changed the state of the console muting. - * This may require us to open or close the device in question. - */ -static int -sysctl_kern_consmute(SYSCTL_HANDLER_ARGS) -{ - int error; - int ocn_mute; - - ocn_mute = cn_mute; - error = sysctl_handle_int(oidp, &cn_mute, 0, req); - if (error != 0 || req->newptr == NULL) - return (error); - if (ocn_mute && !cn_mute && cn_is_open) - error = cnopen(NULL, openflag, 0, curthread); - else if (!ocn_mute && cn_mute && cn_is_open) { - error = cnclose(NULL, openflag, 0, curthread); - cn_is_open = 1; /* XXX hack */ - } - return (error); -} - -SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW, - 0, sizeof(cn_mute), sysctl_kern_consmute, "I", ""); - -static int -cn_devopen(struct cn_device *cnd, struct thread *td, int forceopen) -{ - char path[CNDEVPATHMAX]; - struct nameidata nd; - struct vnode *vp; - struct cdev *dev; - struct cdevsw *csw; - int error; - - if ((vp = cnd->cnd_vp) != NULL) { - if (!forceopen && vp->v_type != VBAD) { - dev = vp->v_rdev; - csw = dev_refthread(dev); - if (csw == NULL) - return (ENXIO); - error = (*csw->d_open)(dev, openflag, 0, td); - dev_relthread(dev); - return (error); - } - cnd->cnd_vp = NULL; - vn_close(vp, openflag, td->td_ucred, td); - } - snprintf(path, sizeof(path), "/dev/%s", cnd->cnd_cn->cn_name); - NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, path, td); - error = vn_open(&nd, &openflag, 0, NULL); - if (error == 0) { - NDFREE(&nd, NDF_ONLY_PNBUF); - VOP_UNLOCK(nd.ni_vp, 0); - if (nd.ni_vp->v_type == VCHR) - cnd->cnd_vp = nd.ni_vp; - else - vn_close(nd.ni_vp, openflag, td->td_ucred, td); - } - return (cnd->cnd_vp != NULL); -} - -static int -cnopen(struct cdev *dev, int flag, int mode, struct thread *td) -{ - struct cn_device *cnd; - - openflag = flag | FWRITE; /* XXX */ - cn_is_open = 1; /* console is logically open */ - if (cn_mute) - return (0); - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) - cn_devopen(cnd, td, 0); - return (0); -} - -static int -cnclose(struct cdev *dev, int flag, int mode, struct thread *td) -{ - struct cn_device *cnd; - struct vnode *vp; - - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { - if ((vp = cnd->cnd_vp) == NULL) - continue; - cnd->cnd_vp = NULL; - vn_close(vp, openflag, td->td_ucred, td); - } - cn_is_open = 0; - return (0); -} - -static int -cnread(struct cdev *dev, struct uio *uio, int flag) -{ - struct cn_device *cnd; - struct cdevsw *csw; - int error; - - cnd = STAILQ_FIRST(&cn_devlist); - if (cn_mute || CND_INVALID(cnd, curthread)) - return (0); - dev = cnd->cnd_vp->v_rdev; - csw = dev_refthread(dev); - if (csw == NULL) - return (ENXIO); - error = (csw->d_read)(dev, uio, flag); - dev_relthread(dev); - return (error); -} - -static int -cnwrite(struct cdev *dev, struct uio *uio, int flag) -{ - struct cn_device *cnd; - struct cdevsw *csw; - int error; - - cnd = STAILQ_FIRST(&cn_devlist); - if (cn_mute || CND_INVALID(cnd, curthread)) - goto done; - if (constty) - dev = constty->t_dev; - else - dev = cnd->cnd_vp->v_rdev; - if (dev != NULL) { - log_console(uio); - csw = dev_refthread(dev); - if (csw == NULL) - return (ENXIO); - error = (csw->d_write)(dev, uio, flag); - dev_relthread(dev); - return (error); - } -done: - uio->uio_resid = 0; /* dump the data */ - return (0); -} - -static int -cnioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) -{ - struct cn_device *cnd; - struct cdevsw *csw; - int error; - - cnd = STAILQ_FIRST(&cn_devlist); - if (cn_mute || CND_INVALID(cnd, td)) - return (0); - /* - * Superuser can always use this to wrest control of console - * output from the "virtual" console. - */ - if (cmd == TIOCCONS && constty) { - error = priv_check(td, PRIV_TTY_CONSOLE); - if (error) - return (error); - constty = NULL; - return (0); - } - dev = cnd->cnd_vp->v_rdev; - if (dev == NULL) - return (0); /* XXX : ENOTTY ? */ - csw = dev_refthread(dev); - if (csw == NULL) - return (ENXIO); - error = (csw->d_ioctl)(dev, cmd, data, flag, td); - dev_relthread(dev); - return (error); -} - -/* - * XXX - * poll/kqfilter do not appear to be correct - */ -static int -cnpoll(struct cdev *dev, int events, struct thread *td) -{ - struct cn_device *cnd; - struct cdevsw *csw; - int error; - - cnd = STAILQ_FIRST(&cn_devlist); - if (cn_mute || CND_INVALID(cnd, td)) - return (0); - dev = cnd->cnd_vp->v_rdev; - if (dev == NULL) - return (0); - csw = dev_refthread(dev); - if (csw == NULL) - return (ENXIO); - error = (csw->d_poll)(dev, events, td); - dev_relthread(dev); - return (error); -} - -static int -cnkqfilter(struct cdev *dev, struct knote *kn) -{ - struct cn_device *cnd; - struct cdevsw *csw; - int error; - - cnd = STAILQ_FIRST(&cn_devlist); - if (cn_mute || CND_INVALID(cnd, curthread)) - return (EINVAL); - dev = cnd->cnd_vp->v_rdev; - if (dev == NULL) - return (ENXIO); - csw = dev_refthread(dev); - if (csw == NULL) - return (ENXIO); - error = (csw->d_kqfilter)(dev, kn); - dev_relthread(dev); - return (error); -} - -/* - * Low level console routines. - */ -int -cngetc(void) -{ - int c; - - if (cn_mute) - return (-1); - while ((c = cncheckc()) == -1) - ; - if (c == '\r') - c = '\n'; /* console input is always ICRNL */ - return (c); -} - -int -cncheckc(void) -{ - struct cn_device *cnd; - struct consdev *cn; - int c; - - if (cn_mute) - return (-1); - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { - cn = cnd->cnd_cn; - if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { - if (cn->cn_checkc != NULL) - c = cn->cn_checkc(cn); - else - c = cn->cn_getc(cn); - if (c != -1) { - return (c); - } - } - } - return (-1); -} - -void -cnputc(int c) -{ - struct cn_device *cnd; - struct consdev *cn; - char *cp; - - if (cn_mute || c == '\0') - return; - STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) { - cn = cnd->cnd_cn; - if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) { - if (c == '\n') - cn->cn_putc(cn, '\r'); - cn->cn_putc(cn, c); - } - } - if (console_pausing && c == '\n' && !kdb_active) { - for (cp = console_pausestr; *cp != '\0'; cp++) - cnputc(*cp); - if (cngetc() == '.') - console_pausing = 0; - cnputc('\r'); - for (cp = console_pausestr; *cp != '\0'; cp++) - cnputc(' '); - cnputc('\r'); - } -} - -void -cnputs(char *p) -{ - int c; - int unlock_reqd = 0; - - if (use_cnputs_mtx) { - mtx_lock_spin(&cnputs_mtx); - unlock_reqd = 1; - } - - while ((c = *p++) != '\0') - cnputc(c); - - if (unlock_reqd) - mtx_unlock_spin(&cnputs_mtx); -} - -static int consmsgbuf_size = 8192; -SYSCTL_INT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RW, &consmsgbuf_size, 0, - ""); - -/* - * Redirect console output to a tty. - */ -void -constty_set(struct tty *tp) -{ - int size; - - KASSERT(tp != NULL, ("constty_set: NULL tp")); - if (consbuf == NULL) { - size = consmsgbuf_size; - consbuf = malloc(size, M_TTYCONS, M_WAITOK); - msgbuf_init(&consmsgbuf, consbuf, size); - callout_init(&conscallout, 0); - } - constty = tp; - constty_timeout(NULL); -} - -/* - * Disable console redirection to a tty. - */ -void -constty_clear(void) -{ - int c; - - constty = NULL; - if (consbuf == NULL) - return; - callout_stop(&conscallout); - while ((c = msgbuf_getchar(&consmsgbuf)) != -1) - cnputc(c); - free(consbuf, M_TTYCONS); - consbuf = NULL; -} - -/* Times per second to check for pending console tty messages. */ -static int constty_wakeups_per_second = 5; -SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW, - &constty_wakeups_per_second, 0, ""); - -static void -constty_timeout(void *arg) -{ - int c; - - if (constty != NULL) { - tty_lock(constty); - while ((c = msgbuf_getchar(&consmsgbuf)) != -1) { - if (tty_putchar(constty, c) < 0) { - tty_unlock(constty); - constty = NULL; - break; - } - } - - if (constty != NULL) - tty_unlock(constty); - } - if (constty != NULL) { - callout_reset(&conscallout, hz / constty_wakeups_per_second, - constty_timeout, NULL); - } else { - /* Deallocate the constty buffer memory. */ - constty_clear(); - } -} - -static void -cn_drvinit(void *unused) -{ - - make_dev(&cn_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "console"); - - mtx_init(&cnputs_mtx, "cnputs_mtx", NULL, MTX_SPIN | MTX_NOWITNESS); - use_cnputs_mtx = 1; -} - -SYSINIT(cndev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, cn_drvinit, NULL); - -/* - * Sysbeep(), if we have hardware for it - */ - -#ifdef HAS_TIMER_SPKR - -static int beeping; - -static void -sysbeepstop(void *chan) -{ - - timer_spkr_release(); - beeping = 0; -} - -int -sysbeep(int pitch, int period) -{ - - if (timer_spkr_acquire()) { - if (!beeping) { - /* Something else owns it. */ - return (EBUSY); - } - } - timer_spkr_setfreq(pitch); - if (!beeping) { - beeping = period; - timeout(sysbeepstop, (void *)NULL, period); - } - return (0); -} - -#else - -/* - * No hardware, no sound - */ - -int -sysbeep(int pitch __unused, int period __unused) -{ - - return (ENODEV); -} - -#endif - diff --git a/sys/sys/tty.h b/sys/sys/tty.h index 9c4e956..7505562 100644 --- a/sys/sys/tty.h +++ b/sys/sys/tty.h @@ -192,6 +192,9 @@ dev_t tty_udev(struct tty *tp); /* Status line printing. */ void tty_info(struct tty *tp); +/* /dev/console selection. */ +void ttyconsdev_select(const char *name); + /* Pseudo-terminal hooks. */ int pts_alloc_external(int fd, struct thread *td, struct file *fp, struct cdev *dev, const char *name); -- cgit v1.1