Index: head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c =================================================================== --- head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c (revision 360050) +++ head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c (revision 360051) @@ -1,564 +1,564 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2011-2012, 2016 Robert N. M. Watson * All rights reserved. * * This software was developed by SRI International and the University of * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) * ("CTSRD"), as part of the DARPA CRASH research programme. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * If one of the Altera JTAG UARTs is currently the system console, register * it here. */ static struct altera_jtag_uart_softc *aju_cons_sc; static tsw_outwakeup_t aju_outwakeup; static void aju_ac_callout(void *); static void aju_io_callout(void *); static struct ttydevsw aju_ttydevsw = { .tsw_flags = TF_NOPREFIX, .tsw_outwakeup = aju_outwakeup, }; /* * When polling for the AC bit, the number of times we have to not see it * before assuming JTAG has disappeared on us. By default, four seconds. */ #define AJU_JTAG_MAXMISS 20 /* * Polling intervals for input/output and JTAG connection events. */ #define AJU_IO_POLLINTERVAL (hz/100) #define AJU_AC_POLLINTERVAL (hz/5) /* * Statistics on JTAG removal events when sending, for debugging purposes * only. */ static u_int aju_jtag_vanished; SYSCTL_UINT(_debug, OID_AUTO, aju_jtag_vanished, CTLFLAG_RW, &aju_jtag_vanished, 0, "Number of times JTAG has vanished"); static u_int aju_jtag_appeared; SYSCTL_UINT(_debug, OID_AUTO, aju_jtag_appeared, CTLFLAG_RW, &aju_jtag_appeared, 0, "Number of times JTAG has appeared"); SYSCTL_INT(_debug, OID_AUTO, aju_cons_jtag_present, CTLFLAG_RW, &aju_cons_jtag_present, 0, "JTAG console present flag"); SYSCTL_UINT(_debug, OID_AUTO, aju_cons_jtag_missed, CTLFLAG_RW, &aju_cons_jtag_missed, 0, "JTAG console missed counter"); /* * Interrupt-related statistics. */ static u_int aju_intr_readable_enabled; SYSCTL_UINT(_debug, OID_AUTO, aju_intr_readable_enabled, CTLFLAG_RW, &aju_intr_readable_enabled, 0, "Number of times read interrupt enabled"); static u_int aju_intr_writable_disabled; SYSCTL_UINT(_debug, OID_AUTO, aju_intr_writable_disabled, CTLFLAG_RW, &aju_intr_writable_disabled, 0, "Number of times write interrupt disabled"); static u_int aju_intr_writable_enabled; SYSCTL_UINT(_debug, OID_AUTO, aju_intr_writable_enabled, CTLFLAG_RW, &aju_intr_writable_enabled, 0, "Number of times write interrupt enabled"); static u_int aju_intr_disabled; SYSCTL_UINT(_debug, OID_AUTO, aju_intr_disabled, CTLFLAG_RW, &aju_intr_disabled, 0, "Number of times write interrupt disabled"); static u_int aju_intr_read_count; SYSCTL_UINT(_debug, OID_AUTO, aju_intr_read_count, CTLFLAG_RW, &aju_intr_read_count, 0, "Number of times read interrupt fired"); static u_int aju_intr_write_count; SYSCTL_UINT(_debug, OID_AUTO, aju_intr_write_count, CTLFLAG_RW, &aju_intr_write_count, 0, "Number of times write interrupt fired"); /* * Low-level read and write register routines; the Altera UART is little * endian, so we byte swap 32-bit reads and writes. */ static inline uint32_t aju_data_read(struct altera_jtag_uart_softc *sc) { return (le32toh(bus_read_4(sc->ajus_mem_res, ALTERA_JTAG_UART_DATA_OFF))); } static inline void aju_data_write(struct altera_jtag_uart_softc *sc, uint32_t v) { bus_write_4(sc->ajus_mem_res, ALTERA_JTAG_UART_DATA_OFF, htole32(v)); } static inline uint32_t aju_control_read(struct altera_jtag_uart_softc *sc) { return (le32toh(bus_read_4(sc->ajus_mem_res, ALTERA_JTAG_UART_CONTROL_OFF))); } static inline void aju_control_write(struct altera_jtag_uart_softc *sc, uint32_t v) { bus_write_4(sc->ajus_mem_res, ALTERA_JTAG_UART_CONTROL_OFF, htole32(v)); } /* * Slightly higher-level routines aware of buffering and flow control. */ static inline int aju_writable(struct altera_jtag_uart_softc *sc) { return ((aju_control_read(sc) & ALTERA_JTAG_UART_CONTROL_WSPACE) != 0); } static inline int aju_readable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); if (*sc->ajus_buffer_validp) return (1); v = aju_data_read(sc); if ((v & ALTERA_JTAG_UART_DATA_RVALID) != 0) { *sc->ajus_buffer_validp = 1; *sc->ajus_buffer_datap = (v & ALTERA_JTAG_UART_DATA_DATA); return (1); } return (0); } static char aju_read(struct altera_jtag_uart_softc *sc) { AJU_LOCK_ASSERT(sc); while (!aju_readable(sc)); *sc->ajus_buffer_validp = 0; return (*sc->ajus_buffer_datap); } /* * Routines for enabling and disabling interrupts for read and write. */ static void aju_intr_readable_enable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); atomic_add_int(&aju_intr_readable_enabled, 1); v = aju_control_read(sc); v |= ALTERA_JTAG_UART_CONTROL_RE; aju_control_write(sc, v); } static void aju_intr_writable_enable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); atomic_add_int(&aju_intr_writable_enabled, 1); v = aju_control_read(sc); v |= ALTERA_JTAG_UART_CONTROL_WE; aju_control_write(sc, v); } static void aju_intr_writable_disable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); atomic_add_int(&aju_intr_writable_disabled, 1); v = aju_control_read(sc); v &= ~ALTERA_JTAG_UART_CONTROL_WE; aju_control_write(sc, v); } static void aju_intr_disable(struct altera_jtag_uart_softc *sc) { uint32_t v; AJU_LOCK_ASSERT(sc); atomic_add_int(&aju_intr_disabled, 1); v = aju_control_read(sc); v &= ~(ALTERA_JTAG_UART_CONTROL_RE | ALTERA_JTAG_UART_CONTROL_WE); aju_control_write(sc, v); } /* * The actual work of checking for, and handling, available reads. This is * used in both polled and interrupt-driven modes, as JTAG UARTs may be hooked * up with, or without, IRQs allocated. */ static void aju_handle_input(struct altera_jtag_uart_softc *sc, struct tty *tp) { int c; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); AJU_LOCK_ASSERT(sc); while (aju_readable(sc)) { c = aju_read(sc); AJU_UNLOCK(sc); #ifdef KDB if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) kdb_alt_break(c, &sc->ajus_alt_break_state); #endif ttydisc_rint(tp, c, 0); AJU_LOCK(sc); } AJU_UNLOCK(sc); ttydisc_rint_done(tp); AJU_LOCK(sc); } /* * Send output to the UART until either there's none left to send, or we run * out of room and need to await an interrupt so that we can start sending * again. * * XXXRW: It would be nice to query WSPACE at the beginning and write to the * FIFO in bugger chunks. */ static void aju_handle_output(struct altera_jtag_uart_softc *sc, struct tty *tp) { uint32_t v; uint8_t ch; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); AJU_LOCK_ASSERT(sc); AJU_UNLOCK(sc); while (ttydisc_getc_poll(tp) != 0) { AJU_LOCK(sc); if (*sc->ajus_jtag_presentp == 0) { /* * If JTAG is not present, then we will drop this * character instead of perhaps scheduling an * interrupt to let us know when there is buffer * space. Otherwise we might get a write interrupt * later even though we aren't interested in sending * anymore. Loop to drain TTY-layer buffer. */ AJU_UNLOCK(sc); if (ttydisc_getc(tp, &ch, sizeof(ch)) != sizeof(ch)) panic("%s: ttydisc_getc", __func__); continue; } v = aju_control_read(sc); if ((v & ALTERA_JTAG_UART_CONTROL_WSPACE) == 0) { if (sc->ajus_irq_res != NULL && (v & ALTERA_JTAG_UART_CONTROL_WE) == 0) aju_intr_writable_enable(sc); return; } AJU_UNLOCK(sc); if (ttydisc_getc(tp, &ch, sizeof(ch)) != sizeof(ch)) panic("%s: ttydisc_getc 2", __func__); AJU_LOCK(sc); /* * XXXRW: There is a slight race here in which we test for * writability, drop the lock, get the character from the tty * layer, re-acquire the lock, and then write. It's possible * for other code -- specifically, the low-level console -- to * have* written in the mean time, which might mean that there * is no longer space. The BERI memory bus will cause this * write to block, wedging the processor until space is * available -- which could be a while if JTAG is not * attached! * * The 'easy' fix is to drop the character if WSPACE has * become unset. Not sure what the 'hard' fix is. */ aju_data_write(sc, ch); AJU_UNLOCK(sc); } AJU_LOCK(sc); /* * If interrupts are configured, and there's no data to write, but we * had previously enabled write interrupts, disable them now. */ v = aju_control_read(sc); if (sc->ajus_irq_res != NULL && (v & ALTERA_JTAG_UART_CONTROL_WE) != 0) aju_intr_writable_disable(sc); } static void aju_outwakeup(struct tty *tp) { struct altera_jtag_uart_softc *sc = tty_softc(tp); - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); AJU_LOCK(sc); aju_handle_output(sc, tp); AJU_UNLOCK(sc); } static void aju_io_callout(void *arg) { struct altera_jtag_uart_softc *sc = arg; struct tty *tp = sc->ajus_ttyp; tty_lock(tp); AJU_LOCK(sc); /* * It would be convenient if we could share code with aju_intr() here * by testing the control register for ALTERA_JTAG_UART_CONTROL_RI and * ALTERA_JTAG_UART_CONTROL_WI. Unfortunately, it's not clear that * this is supported, so do all the work to poll for both input and * output. */ aju_handle_input(sc, tp); aju_handle_output(sc, tp); /* * Reschedule next poll attempt. There's some argument that we should * do adaptive polling based on the expectation of I/O: is something * pending in the output buffer, or have we recently had input, but we * don't. */ callout_reset(&sc->ajus_io_callout, AJU_IO_POLLINTERVAL, aju_io_callout, sc); AJU_UNLOCK(sc); tty_unlock(tp); } static void aju_ac_callout(void *arg) { struct altera_jtag_uart_softc *sc = arg; struct tty *tp = sc->ajus_ttyp; uint32_t v; tty_lock(tp); AJU_LOCK(sc); v = aju_control_read(sc); if (v & ALTERA_JTAG_UART_CONTROL_AC) { v &= ~ALTERA_JTAG_UART_CONTROL_AC; aju_control_write(sc, v); if (*sc->ajus_jtag_presentp == 0) { *sc->ajus_jtag_presentp = 1; atomic_add_int(&aju_jtag_appeared, 1); aju_handle_output(sc, tp); } /* Any hit eliminates all recent misses. */ *sc->ajus_jtag_missedp = 0; } else if (*sc->ajus_jtag_presentp != 0) { /* * If we've exceeded our tolerance for misses, mark JTAG as * disconnected and drain output. Otherwise, bump the miss * counter. */ if (*sc->ajus_jtag_missedp > AJU_JTAG_MAXMISS) { *sc->ajus_jtag_presentp = 0; atomic_add_int(&aju_jtag_vanished, 1); aju_handle_output(sc, tp); } else (*sc->ajus_jtag_missedp)++; } callout_reset(&sc->ajus_ac_callout, AJU_AC_POLLINTERVAL, aju_ac_callout, sc); AJU_UNLOCK(sc); tty_unlock(tp); } static void aju_intr(void *arg) { struct altera_jtag_uart_softc *sc = arg; struct tty *tp = sc->ajus_ttyp; uint32_t v; tty_lock(tp); AJU_LOCK(sc); v = aju_control_read(sc); if (v & ALTERA_JTAG_UART_CONTROL_RI) { atomic_add_int(&aju_intr_read_count, 1); aju_handle_input(sc, tp); } if (v & ALTERA_JTAG_UART_CONTROL_WI) { atomic_add_int(&aju_intr_write_count, 1); aju_handle_output(sc, tp); } AJU_UNLOCK(sc); tty_unlock(tp); } int altera_jtag_uart_attach(struct altera_jtag_uart_softc *sc) { struct tty *tp; int error; AJU_LOCK_INIT(sc); /* * XXXRW: Currently, we detect the console solely based on it using a * reserved address, and borrow console-level locks and buffer if so. * Is there a better way? */ if (rman_get_start(sc->ajus_mem_res) == BERI_UART_BASE) { sc->ajus_lockp = &aju_cons_lock; sc->ajus_buffer_validp = &aju_cons_buffer_valid; sc->ajus_buffer_datap = &aju_cons_buffer_data; sc->ajus_jtag_presentp = &aju_cons_jtag_present; sc->ajus_jtag_missedp = &aju_cons_jtag_missed; sc->ajus_flags |= ALTERA_JTAG_UART_FLAG_CONSOLE; } else { sc->ajus_lockp = &sc->ajus_lock; sc->ajus_buffer_validp = &sc->ajus_buffer_valid; sc->ajus_buffer_datap = &sc->ajus_buffer_data; sc->ajus_jtag_presentp = &sc->ajus_jtag_present; sc->ajus_jtag_missedp = &sc->ajus_jtag_missed; } /* * Disable interrupts regardless of whether or not we plan to use * them. We will register an interrupt handler now if they will be * used, but not re-enable intil later once the remainder of the tty * layer is properly initialised, as we're not ready for input yet. */ AJU_LOCK(sc); aju_intr_disable(sc); AJU_UNLOCK(sc); if (sc->ajus_irq_res != NULL) { error = bus_setup_intr(sc->ajus_dev, sc->ajus_irq_res, INTR_ENTROPY | INTR_TYPE_TTY | INTR_MPSAFE, NULL, aju_intr, sc, &sc->ajus_irq_cookie); if (error) { device_printf(sc->ajus_dev, "could not activate interrupt\n"); AJU_LOCK_DESTROY(sc); return (error); } } tp = sc->ajus_ttyp = tty_alloc(&aju_ttydevsw, sc); if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) { aju_cons_sc = sc; tty_init_console(tp, 0); } tty_makedev(tp, NULL, "%s%d", AJU_TTYNAME, sc->ajus_unit); /* * If we will be using interrupts, enable them now; otherwise, start * polling. From this point onwards, input can arrive. */ if (sc->ajus_irq_res != NULL) { AJU_LOCK(sc); aju_intr_readable_enable(sc); AJU_UNLOCK(sc); } else { callout_init(&sc->ajus_io_callout, 1); callout_reset(&sc->ajus_io_callout, AJU_IO_POLLINTERVAL, aju_io_callout, sc); } callout_init(&sc->ajus_ac_callout, 1); callout_reset(&sc->ajus_ac_callout, AJU_AC_POLLINTERVAL, aju_ac_callout, sc); return (0); } void altera_jtag_uart_detach(struct altera_jtag_uart_softc *sc) { struct tty *tp = sc->ajus_ttyp; /* * If we're using interrupts, disable and release the interrupt * handler now. Otherwise drain the polling timeout. */ if (sc->ajus_irq_res != NULL) { AJU_LOCK(sc); aju_intr_disable(sc); AJU_UNLOCK(sc); bus_teardown_intr(sc->ajus_dev, sc->ajus_irq_res, sc->ajus_irq_cookie); } else callout_drain(&sc->ajus_io_callout); callout_drain(&sc->ajus_ac_callout); if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) aju_cons_sc = NULL; tty_lock(tp); tty_rel_gone(tp); AJU_LOCK_DESTROY(sc); } Index: head/sys/dev/bvm/bvm_console.c =================================================================== --- head/sys/dev/bvm/bvm_console.c (revision 360050) +++ head/sys/dev/bvm/bvm_console.c (revision 360051) @@ -1,239 +1,239 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``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 NETAPP, INC 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #ifndef BVMCONS_POLL_HZ #define BVMCONS_POLL_HZ 4 #endif #define BVMBURSTLEN 16 /* max number of bytes to write in one chunk */ static tsw_open_t bvm_tty_open; static tsw_close_t bvm_tty_close; static tsw_outwakeup_t bvm_tty_outwakeup; static struct ttydevsw bvm_ttydevsw = { .tsw_flags = TF_NOPREFIX, .tsw_open = bvm_tty_open, .tsw_close = bvm_tty_close, .tsw_outwakeup = bvm_tty_outwakeup, }; static int polltime; static struct callout bvm_timer; #if defined(KDB) static int alt_break_state; #endif #define BVM_CONS_PORT 0x220 static int bvm_cons_port = BVM_CONS_PORT; #define BVM_CONS_SIG ('b' << 8 | 'v') static void bvm_timeout(void *); static cn_probe_t bvm_cnprobe; static cn_init_t bvm_cninit; static cn_term_t bvm_cnterm; static cn_getc_t bvm_cngetc; static cn_putc_t bvm_cnputc; static cn_grab_t bvm_cngrab; static cn_ungrab_t bvm_cnungrab; CONSOLE_DRIVER(bvm); static int bvm_rcons(u_char *ch) { int c; c = inl(bvm_cons_port); if (c != -1) { *ch = (u_char)c; return (0); } else return (-1); } static void bvm_wcons(u_char ch) { outl(bvm_cons_port, ch); } static void cn_drvinit(void *unused) { struct tty *tp; if (bvm_consdev.cn_pri != CN_DEAD) { tp = tty_alloc(&bvm_ttydevsw, NULL); callout_init_mtx(&bvm_timer, tty_getlock(tp), 0); tty_makedev(tp, NULL, "bvmcons"); } } static int bvm_tty_open(struct tty *tp) { polltime = hz / BVMCONS_POLL_HZ; if (polltime < 1) polltime = 1; callout_reset(&bvm_timer, polltime, bvm_timeout, tp); return (0); } static void bvm_tty_close(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); callout_stop(&bvm_timer); } static void bvm_tty_outwakeup(struct tty *tp) { int len, written; u_char buf[BVMBURSTLEN]; for (;;) { len = ttydisc_getc(tp, buf, sizeof(buf)); if (len == 0) break; written = 0; while (written < len) bvm_wcons(buf[written++]); } } static void bvm_timeout(void *v) { struct tty *tp; int c; tp = (struct tty *)v; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); while ((c = bvm_cngetc(NULL)) != -1) ttydisc_rint(tp, c, 0); ttydisc_rint_done(tp); callout_reset(&bvm_timer, polltime, bvm_timeout, tp); } static void bvm_cnprobe(struct consdev *cp) { int disabled, port; disabled = 0; cp->cn_pri = CN_DEAD; strcpy(cp->cn_name, "bvmcons"); resource_int_value("bvmconsole", 0, "disabled", &disabled); if (!disabled) { if (resource_int_value("bvmconsole", 0, "port", &port) == 0) bvm_cons_port = port; if (inw(bvm_cons_port) == BVM_CONS_SIG) cp->cn_pri = CN_REMOTE; } } static void bvm_cninit(struct consdev *cp) { int i; const char *bootmsg = "Using bvm console.\n"; if (boothowto & RB_VERBOSE) { for (i = 0; i < strlen(bootmsg); i++) bvm_cnputc(cp, bootmsg[i]); } } static void bvm_cnterm(struct consdev *cp) { } static int bvm_cngetc(struct consdev *cp) { unsigned char ch; if (bvm_rcons(&ch) == 0) { #if defined(KDB) kdb_alt_break(ch, &alt_break_state); #endif return (ch); } return (-1); } static void bvm_cnputc(struct consdev *cp, int c) { bvm_wcons(c); } static void bvm_cngrab(struct consdev *cp) { } static void bvm_cnungrab(struct consdev *cp) { } SYSINIT(cndev, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, cn_drvinit, NULL); Index: head/sys/dev/cfe/cfe_console.c =================================================================== --- head/sys/dev/cfe/cfe_console.c (revision 360050) +++ head/sys/dev/cfe/cfe_console.c (revision 360051) @@ -1,228 +1,228 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2007 Bruce M. Simpson. * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef CFECONS_POLL_HZ #define CFECONS_POLL_HZ 4 #endif #define CFEBURSTLEN 128 /* max number of bytes to write in one chunk */ static tsw_open_t cfe_tty_open; static tsw_close_t cfe_tty_close; static tsw_outwakeup_t cfe_tty_outwakeup; static struct ttydevsw cfe_ttydevsw = { .tsw_flags = TF_NOPREFIX, .tsw_open = cfe_tty_open, .tsw_close = cfe_tty_close, .tsw_outwakeup = cfe_tty_outwakeup, }; static int conhandle = -1; /* XXX does cfe have to poll? */ static int polltime; static struct callout cfe_timer; #if defined(KDB) static int alt_break_state; #endif static void cfe_timeout(void *); static cn_probe_t cfe_cnprobe; static cn_init_t cfe_cninit; static cn_term_t cfe_cnterm; static cn_getc_t cfe_cngetc; static cn_putc_t cfe_cnputc; static cn_grab_t cfe_cngrab; static cn_ungrab_t cfe_cnungrab; CONSOLE_DRIVER(cfe); static void cn_drvinit(void *unused) { struct tty *tp; if (cfe_consdev.cn_pri != CN_DEAD && cfe_consdev.cn_name[0] != '\0') { tp = tty_alloc(&cfe_ttydevsw, NULL); callout_init_mtx(&cfe_timer, tty_getlock(tp), 0); tty_makedev(tp, NULL, "cfecons"); } } static int cfe_tty_open(struct tty *tp) { polltime = hz / CFECONS_POLL_HZ; if (polltime < 1) polltime = 1; callout_reset(&cfe_timer, polltime, cfe_timeout, tp); return (0); } static void cfe_tty_close(struct tty *tp) { callout_stop(&cfe_timer); } static void cfe_tty_outwakeup(struct tty *tp) { int len, written, rc; u_char buf[CFEBURSTLEN]; for (;;) { len = ttydisc_getc(tp, buf, sizeof buf); if (len == 0) break; written = 0; while (written < len) { rc = cfe_write(conhandle, &buf[written], len - written); if (rc < 0) break; written += rc; } } } static void cfe_timeout(void *v) { struct tty *tp; int c; tp = (struct tty *)v; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); while ((c = cfe_cngetc(NULL)) != -1) ttydisc_rint(tp, c, 0); ttydisc_rint_done(tp); callout_reset(&cfe_timer, polltime, cfe_timeout, tp); } static void cfe_cnprobe(struct consdev *cp) { conhandle = cfe_getstdhandle(CFE_STDHANDLE_CONSOLE); if (conhandle < 0) { cp->cn_pri = CN_DEAD; return; } /* XXX */ if (bootverbose) { char *bootmsg = "Using CFE firmware console.\n"; int i; for (i = 0; i < strlen(bootmsg); i++) cfe_cnputc(cp, bootmsg[i]); } cp->cn_pri = CN_LOW; } static void cfe_cninit(struct consdev *cp) { strcpy(cp->cn_name, "cfecons"); } static void cfe_cnterm(struct consdev *cp) { } static void cfe_cngrab(struct consdev *cp) { } static void cfe_cnungrab(struct consdev *cp) { } static int cfe_cngetc(struct consdev *cp) { unsigned char ch; if (cfe_read(conhandle, &ch, 1) == 1) { #if defined(KDB) kdb_alt_break(ch, &alt_break_state); #endif return (ch); } return (-1); } static void cfe_cnputc(struct consdev *cp, int c) { char cbuf; if (c == '\n') cfe_cnputc(cp, '\r'); cbuf = c; while (cfe_write(conhandle, &cbuf, 1) == 0) continue; } SYSINIT(cndev, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, cn_drvinit, NULL); Index: head/sys/dev/ofw/ofw_console.c =================================================================== --- head/sys/dev/ofw/ofw_console.c (revision 360050) +++ head/sys/dev/ofw/ofw_console.c (revision 360051) @@ -1,236 +1,236 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 2001 Benno Rice. * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY Benno Rice ``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 TOOLS GMBH 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_ofw.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef OFWCONS_POLL_HZ #define OFWCONS_POLL_HZ 4 /* 50-100 works best on Ultra2 */ #endif #define OFBURSTLEN 128 /* max number of bytes to write in one chunk */ static tsw_open_t ofwtty_open; static tsw_close_t ofwtty_close; static tsw_outwakeup_t ofwtty_outwakeup; static struct ttydevsw ofw_ttydevsw = { .tsw_flags = TF_NOPREFIX, .tsw_open = ofwtty_open, .tsw_close = ofwtty_close, .tsw_outwakeup = ofwtty_outwakeup, }; static int polltime; static struct callout ofw_timer; #if defined(KDB) static int alt_break_state; #endif static void ofw_timeout(void *); static cn_probe_t ofw_cnprobe; static cn_init_t ofw_cninit; static cn_term_t ofw_cnterm; static cn_getc_t ofw_cngetc; static cn_putc_t ofw_cnputc; static cn_grab_t ofw_cngrab; static cn_ungrab_t ofw_cnungrab; CONSOLE_DRIVER(ofw); static void cn_drvinit(void *unused) { phandle_t options; char output[32]; struct tty *tp; if (ofw_consdev.cn_pri != CN_DEAD && ofw_consdev.cn_name[0] != '\0') { tp = tty_alloc(&ofw_ttydevsw, NULL); tty_makedev(tp, NULL, "%s", "ofwcons"); /* * XXX: This is a hack and it may result in two /dev/ttya * XXX: devices on platforms where the sab driver works. */ if ((options = OF_finddevice("/options")) == -1 || OF_getprop(options, "output-device", output, sizeof(output)) == -1) return; if (strlen(output) > 0) tty_makealias(tp, "%s", output); callout_init_mtx(&ofw_timer, tty_getlock(tp), 0); } } SYSINIT(cndev, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, cn_drvinit, NULL); static pcell_t stdin; static pcell_t stdout; static int ofwtty_open(struct tty *tp) { polltime = hz / OFWCONS_POLL_HZ; if (polltime < 1) polltime = 1; callout_reset(&ofw_timer, polltime, ofw_timeout, tp); return (0); } static void ofwtty_close(struct tty *tp) { callout_stop(&ofw_timer); } static void ofwtty_outwakeup(struct tty *tp) { int len; u_char buf[OFBURSTLEN]; for (;;) { len = ttydisc_getc(tp, buf, sizeof buf); if (len == 0) break; OF_write(stdout, buf, len); } } static void ofw_timeout(void *v) { struct tty *tp; int c; tp = (struct tty *)v; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); while ((c = ofw_cngetc(NULL)) != -1) ttydisc_rint(tp, c, 0); ttydisc_rint_done(tp); callout_schedule(&ofw_timer, polltime); } static void ofw_cnprobe(struct consdev *cp) { int chosen; if ((chosen = OF_finddevice("/chosen")) == -1) { cp->cn_pri = CN_DEAD; return; } if (OF_getencprop(chosen, "stdin", &stdin, sizeof(stdin)) == -1) { cp->cn_pri = CN_DEAD; return; } if (OF_getencprop(chosen, "stdout", &stdout, sizeof(stdout)) == -1) { cp->cn_pri = CN_DEAD; return; } cp->cn_pri = CN_LOW; } static void ofw_cninit(struct consdev *cp) { /* XXX: This is the alias, but that should be good enough */ strcpy(cp->cn_name, "ofwcons"); } static void ofw_cnterm(struct consdev *cp) { } static void ofw_cngrab(struct consdev *cp) { } static void ofw_cnungrab(struct consdev *cp) { } static int ofw_cngetc(struct consdev *cp) { unsigned char ch; if (OF_read(stdin, &ch, 1) > 0) { #if defined(KDB) kdb_alt_break(ch, &alt_break_state); #endif return (ch); } return (-1); } static void ofw_cnputc(struct consdev *cp, int c) { char cbuf; if (c == '\n') { cbuf = '\r'; OF_write(stdout, &cbuf, 1); } cbuf = c; OF_write(stdout, &cbuf, 1); } Index: head/sys/dev/rp/rp.c =================================================================== --- head/sys/dev/rp/rp.c (revision 360050) +++ head/sys/dev/rp/rp.c (revision 360051) @@ -1,1113 +1,1113 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) Comtrol Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted prodived that the follwoing conditions * are met. * 1. Redistributions of source code must retain the above copyright * notive, this list of conditions and the following disclainer. * 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 prodided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Comtrol Corporation. * 4. The name of Comtrol Corporation may not be used to endorse or * promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY COMTROL CORPORATION ``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 COMTROL CORPORATION 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, LIFE 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. * */ #include __FBSDID("$FreeBSD$"); /* * rp.c - for RocketPort FreeBSD */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define ROCKET_C #include #include static const char RocketPortVersion[] = "3.02"; static Byte_t RData[RDATASIZE] = { 0x00, 0x09, 0xf6, 0x82, 0x02, 0x09, 0x86, 0xfb, 0x04, 0x09, 0x00, 0x0a, 0x06, 0x09, 0x01, 0x0a, 0x08, 0x09, 0x8a, 0x13, 0x0a, 0x09, 0xc5, 0x11, 0x0c, 0x09, 0x86, 0x85, 0x0e, 0x09, 0x20, 0x0a, 0x10, 0x09, 0x21, 0x0a, 0x12, 0x09, 0x41, 0xff, 0x14, 0x09, 0x82, 0x00, 0x16, 0x09, 0x82, 0x7b, 0x18, 0x09, 0x8a, 0x7d, 0x1a, 0x09, 0x88, 0x81, 0x1c, 0x09, 0x86, 0x7a, 0x1e, 0x09, 0x84, 0x81, 0x20, 0x09, 0x82, 0x7c, 0x22, 0x09, 0x0a, 0x0a }; static Byte_t RRegData[RREGDATASIZE]= { 0x00, 0x09, 0xf6, 0x82, /* 00: Stop Rx processor */ 0x08, 0x09, 0x8a, 0x13, /* 04: Tx software flow control */ 0x0a, 0x09, 0xc5, 0x11, /* 08: XON char */ 0x0c, 0x09, 0x86, 0x85, /* 0c: XANY */ 0x12, 0x09, 0x41, 0xff, /* 10: Rx mask char */ 0x14, 0x09, 0x82, 0x00, /* 14: Compare/Ignore #0 */ 0x16, 0x09, 0x82, 0x7b, /* 18: Compare #1 */ 0x18, 0x09, 0x8a, 0x7d, /* 1c: Compare #2 */ 0x1a, 0x09, 0x88, 0x81, /* 20: Interrupt #1 */ 0x1c, 0x09, 0x86, 0x7a, /* 24: Ignore/Replace #1 */ 0x1e, 0x09, 0x84, 0x81, /* 28: Interrupt #2 */ 0x20, 0x09, 0x82, 0x7c, /* 2c: Ignore/Replace #2 */ 0x22, 0x09, 0x0a, 0x0a /* 30: Rx FIFO Enable */ }; #if 0 /* IRQ number to MUDBAC register 2 mapping */ Byte_t sIRQMap[16] = { 0,0,0,0x10,0x20,0x30,0,0,0,0x40,0x50,0x60,0x70,0,0,0x80 }; #endif Byte_t rp_sBitMapClrTbl[8] = { 0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f }; Byte_t rp_sBitMapSetTbl[8] = { 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 }; static void rpfree(void *); /*************************************************************************** Function: sReadAiopID Purpose: Read the AIOP idenfication number directly from an AIOP. Call: sReadAiopID(CtlP, aiop) CONTROLLER_T *CtlP; Ptr to controller structure int aiop: AIOP index Return: int: Flag AIOPID_XXXX if a valid AIOP is found, where X is replace by an identifying number. Flag AIOPID_NULL if no valid AIOP is found Warnings: No context switches are allowed while executing this function. */ int sReadAiopID(CONTROLLER_T *CtlP, int aiop) { Byte_t AiopID; /* ID byte from AIOP */ rp_writeaiop1(CtlP, aiop, _CMD_REG, RESET_ALL); /* reset AIOP */ rp_writeaiop1(CtlP, aiop, _CMD_REG, 0x0); AiopID = rp_readaiop1(CtlP, aiop, _CHN_STAT0) & 0x07; if(AiopID == 0x06) return(1); else /* AIOP does not exist */ return(-1); } /*************************************************************************** Function: sReadAiopNumChan Purpose: Read the number of channels available in an AIOP directly from an AIOP. Call: sReadAiopNumChan(CtlP, aiop) CONTROLLER_T *CtlP; Ptr to controller structure int aiop: AIOP index Return: int: The number of channels available Comments: The number of channels is determined by write/reads from identical offsets within the SRAM address spaces for channels 0 and 4. If the channel 4 space is mirrored to channel 0 it is a 4 channel AIOP, otherwise it is an 8 channel. Warnings: No context switches are allowed while executing this function. */ int sReadAiopNumChan(CONTROLLER_T *CtlP, int aiop) { Word_t x, y; rp_writeaiop4(CtlP, aiop, _INDX_ADDR,0x12340000L); /* write to chan 0 SRAM */ rp_writeaiop2(CtlP, aiop, _INDX_ADDR,0); /* read from SRAM, chan 0 */ x = rp_readaiop2(CtlP, aiop, _INDX_DATA); rp_writeaiop2(CtlP, aiop, _INDX_ADDR,0x4000); /* read from SRAM, chan 4 */ y = rp_readaiop2(CtlP, aiop, _INDX_DATA); if(x != y) /* if different must be 8 chan */ return(8); else return(4); } /*************************************************************************** Function: sInitChan Purpose: Initialization of a channel and channel structure Call: sInitChan(CtlP,ChP,AiopNum,ChanNum) CONTROLLER_T *CtlP; Ptr to controller structure CHANNEL_T *ChP; Ptr to channel structure int AiopNum; AIOP number within controller int ChanNum; Channel number within AIOP Return: int: TRUE if initialization succeeded, FALSE if it fails because channel number exceeds number of channels available in AIOP. Comments: This function must be called before a channel can be used. Warnings: No range checking on any of the parameters is done. No context switches are allowed while executing this function. */ int sInitChan( CONTROLLER_T *CtlP, CHANNEL_T *ChP, int AiopNum, int ChanNum) { int i, ChOff; Byte_t *ChR; static Byte_t R[4]; if(ChanNum >= CtlP->AiopNumChan[AiopNum]) return(FALSE); /* exceeds num chans in AIOP */ /* Channel, AIOP, and controller identifiers */ ChP->CtlP = CtlP; ChP->ChanID = CtlP->AiopID[AiopNum]; ChP->AiopNum = AiopNum; ChP->ChanNum = ChanNum; /* Initialize the channel from the RData array */ for(i=0; i < RDATASIZE; i+=4) { R[0] = RData[i]; R[1] = RData[i+1] + 0x10 * ChanNum; R[2] = RData[i+2]; R[3] = RData[i+3]; rp_writech4(ChP,_INDX_ADDR,le32dec(R)); } ChR = ChP->R; for(i=0; i < RREGDATASIZE; i+=4) { ChR[i] = RRegData[i]; ChR[i+1] = RRegData[i+1] + 0x10 * ChanNum; ChR[i+2] = RRegData[i+2]; ChR[i+3] = RRegData[i+3]; } /* Indexed registers */ ChOff = (Word_t)ChanNum * 0x1000; ChP->BaudDiv[0] = (Byte_t)(ChOff + _BAUD); ChP->BaudDiv[1] = (Byte_t)((ChOff + _BAUD) >> 8); ChP->BaudDiv[2] = (Byte_t)BRD9600; ChP->BaudDiv[3] = (Byte_t)(BRD9600 >> 8); rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->BaudDiv)); ChP->TxControl[0] = (Byte_t)(ChOff + _TX_CTRL); ChP->TxControl[1] = (Byte_t)((ChOff + _TX_CTRL) >> 8); ChP->TxControl[2] = 0; ChP->TxControl[3] = 0; rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxControl)); ChP->RxControl[0] = (Byte_t)(ChOff + _RX_CTRL); ChP->RxControl[1] = (Byte_t)((ChOff + _RX_CTRL) >> 8); ChP->RxControl[2] = 0; ChP->RxControl[3] = 0; rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->RxControl)); ChP->TxEnables[0] = (Byte_t)(ChOff + _TX_ENBLS); ChP->TxEnables[1] = (Byte_t)((ChOff + _TX_ENBLS) >> 8); ChP->TxEnables[2] = 0; ChP->TxEnables[3] = 0; rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxEnables)); ChP->TxCompare[0] = (Byte_t)(ChOff + _TXCMP1); ChP->TxCompare[1] = (Byte_t)((ChOff + _TXCMP1) >> 8); ChP->TxCompare[2] = 0; ChP->TxCompare[3] = 0; rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxCompare)); ChP->TxReplace1[0] = (Byte_t)(ChOff + _TXREP1B1); ChP->TxReplace1[1] = (Byte_t)((ChOff + _TXREP1B1) >> 8); ChP->TxReplace1[2] = 0; ChP->TxReplace1[3] = 0; rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxReplace1)); ChP->TxReplace2[0] = (Byte_t)(ChOff + _TXREP2); ChP->TxReplace2[1] = (Byte_t)((ChOff + _TXREP2) >> 8); ChP->TxReplace2[2] = 0; ChP->TxReplace2[3] = 0; rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxReplace2)); ChP->TxFIFOPtrs = ChOff + _TXF_OUTP; ChP->TxFIFO = ChOff + _TX_FIFO; rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum | RESTXFCNT); /* apply reset Tx FIFO count */ rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum); /* remove reset Tx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->TxFIFOPtrs); /* clear Tx in/out ptrs */ rp_writech2(ChP,_INDX_DATA,0); ChP->RxFIFOPtrs = ChOff + _RXF_OUTP; ChP->RxFIFO = ChOff + _RX_FIFO; rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum | RESRXFCNT); /* apply reset Rx FIFO count */ rp_writech1(ChP,_CMD_REG,(Byte_t)ChanNum); /* remove reset Rx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs); /* clear Rx out ptr */ rp_writech2(ChP,_INDX_DATA,0); rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs + 2); /* clear Rx in ptr */ rp_writech2(ChP,_INDX_DATA,0); ChP->TxPrioCnt = ChOff + _TXP_CNT; rp_writech2(ChP,_INDX_ADDR,ChP->TxPrioCnt); rp_writech1(ChP,_INDX_DATA,0); ChP->TxPrioPtr = ChOff + _TXP_PNTR; rp_writech2(ChP,_INDX_ADDR,ChP->TxPrioPtr); rp_writech1(ChP,_INDX_DATA,0); ChP->TxPrioBuf = ChOff + _TXP_BUF; sEnRxProcessor(ChP); /* start the Rx processor */ return(TRUE); } /*************************************************************************** Function: sStopRxProcessor Purpose: Stop the receive processor from processing a channel. Call: sStopRxProcessor(ChP) CHANNEL_T *ChP; Ptr to channel structure Comments: The receive processor can be started again with sStartRxProcessor(). This function causes the receive processor to skip over the stopped channel. It does not stop it from processing other channels. Warnings: No context switches are allowed while executing this function. Do not leave the receive processor stopped for more than one character time. After calling this function a delay of 4 uS is required to ensure that the receive processor is no longer processing this channel. */ void sStopRxProcessor(CHANNEL_T *ChP) { Byte_t R[4]; R[0] = ChP->R[0]; R[1] = ChP->R[1]; R[2] = 0x0a; R[3] = ChP->R[3]; rp_writech4(ChP,_INDX_ADDR,le32dec(R)); } /*************************************************************************** Function: sFlushRxFIFO Purpose: Flush the Rx FIFO Call: sFlushRxFIFO(ChP) CHANNEL_T *ChP; Ptr to channel structure Return: void Comments: To prevent data from being enqueued or dequeued in the Tx FIFO while it is being flushed the receive processor is stopped and the transmitter is disabled. After these operations a 4 uS delay is done before clearing the pointers to allow the receive processor to stop. These items are handled inside this function. Warnings: No context switches are allowed while executing this function. */ void sFlushRxFIFO(CHANNEL_T *ChP) { int i; Byte_t Ch; /* channel number within AIOP */ int RxFIFOEnabled; /* TRUE if Rx FIFO enabled */ if(sGetRxCnt(ChP) == 0) /* Rx FIFO empty */ return; /* don't need to flush */ RxFIFOEnabled = FALSE; if(ChP->R[0x32] == 0x08) /* Rx FIFO is enabled */ { RxFIFOEnabled = TRUE; sDisRxFIFO(ChP); /* disable it */ for(i=0; i < 2000/200; i++) /* delay 2 uS to allow proc to disable FIFO*/ rp_readch1(ChP,_INT_CHAN); /* depends on bus i/o timing */ } sGetChanStatus(ChP); /* clear any pending Rx errors in chan stat */ Ch = (Byte_t)sGetChanNum(ChP); rp_writech1(ChP,_CMD_REG,Ch | RESRXFCNT); /* apply reset Rx FIFO count */ rp_writech1(ChP,_CMD_REG,Ch); /* remove reset Rx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs); /* clear Rx out ptr */ rp_writech2(ChP,_INDX_DATA,0); rp_writech2(ChP,_INDX_ADDR,ChP->RxFIFOPtrs + 2); /* clear Rx in ptr */ rp_writech2(ChP,_INDX_DATA,0); if(RxFIFOEnabled) sEnRxFIFO(ChP); /* enable Rx FIFO */ } /*************************************************************************** Function: sFlushTxFIFO Purpose: Flush the Tx FIFO Call: sFlushTxFIFO(ChP) CHANNEL_T *ChP; Ptr to channel structure Return: void Comments: To prevent data from being enqueued or dequeued in the Tx FIFO while it is being flushed the receive processor is stopped and the transmitter is disabled. After these operations a 4 uS delay is done before clearing the pointers to allow the receive processor to stop. These items are handled inside this function. Warnings: No context switches are allowed while executing this function. */ void sFlushTxFIFO(CHANNEL_T *ChP) { int i; Byte_t Ch; /* channel number within AIOP */ int TxEnabled; /* TRUE if transmitter enabled */ if(sGetTxCnt(ChP) == 0) /* Tx FIFO empty */ return; /* don't need to flush */ TxEnabled = FALSE; if(ChP->TxControl[3] & TX_ENABLE) { TxEnabled = TRUE; sDisTransmit(ChP); /* disable transmitter */ } sStopRxProcessor(ChP); /* stop Rx processor */ for(i = 0; i < 4000/200; i++) /* delay 4 uS to allow proc to stop */ rp_readch1(ChP,_INT_CHAN); /* depends on bus i/o timing */ Ch = (Byte_t)sGetChanNum(ChP); rp_writech1(ChP,_CMD_REG,Ch | RESTXFCNT); /* apply reset Tx FIFO count */ rp_writech1(ChP,_CMD_REG,Ch); /* remove reset Tx FIFO count */ rp_writech2(ChP,_INDX_ADDR,ChP->TxFIFOPtrs); /* clear Tx in/out ptrs */ rp_writech2(ChP,_INDX_DATA,0); if(TxEnabled) sEnTransmit(ChP); /* enable transmitter */ sStartRxProcessor(ChP); /* restart Rx processor */ } /*************************************************************************** Function: sWriteTxPrioByte Purpose: Write a byte of priority transmit data to a channel Call: sWriteTxPrioByte(ChP,Data) CHANNEL_T *ChP; Ptr to channel structure Byte_t Data; The transmit data byte Return: int: 1 if the bytes is successfully written, otherwise 0. Comments: The priority byte is transmitted before any data in the Tx FIFO. Warnings: No context switches are allowed while executing this function. */ int sWriteTxPrioByte(CHANNEL_T *ChP, Byte_t Data) { Byte_t DWBuf[4]; /* buffer for double word writes */ if(sGetTxCnt(ChP) > 1) /* write it to Tx priority buffer */ { rp_writech2(ChP,_INDX_ADDR,ChP->TxPrioCnt); /* get priority buffer status */ if(rp_readch1(ChP,_INDX_DATA) & PRI_PEND) /* priority buffer busy */ return(0); /* nothing sent */ le16enc(DWBuf,ChP->TxPrioBuf); /* data byte address */ DWBuf[2] = Data; /* data byte value */ DWBuf[3] = 0; /* priority buffer pointer */ rp_writech4(ChP,_INDX_ADDR,le32dec(DWBuf)); /* write it out */ le16enc(DWBuf,ChP->TxPrioCnt); /* Tx priority count address */ DWBuf[2] = PRI_PEND + 1; /* indicate 1 byte pending */ DWBuf[3] = 0; /* priority buffer pointer */ rp_writech4(ChP,_INDX_ADDR,le32dec(DWBuf)); /* write it out */ } else /* write it to Tx FIFO */ { sWriteTxByte(ChP,sGetTxRxDataIO(ChP),Data); } return(1); /* 1 byte sent */ } /*************************************************************************** Function: sEnInterrupts Purpose: Enable one or more interrupts for a channel Call: sEnInterrupts(ChP,Flags) CHANNEL_T *ChP; Ptr to channel structure Word_t Flags: Interrupt enable flags, can be any combination of the following flags: TXINT_EN: Interrupt on Tx FIFO empty RXINT_EN: Interrupt on Rx FIFO at trigger level (see sSetRxTrigger()) SRCINT_EN: Interrupt on SRC (Special Rx Condition) MCINT_EN: Interrupt on modem input change CHANINT_EN: Allow channel interrupt signal to the AIOP's Interrupt Channel Register. Return: void Comments: If an interrupt enable flag is set in Flags, that interrupt will be enabled. If an interrupt enable flag is not set in Flags, that interrupt will not be changed. Interrupts can be disabled with function sDisInterrupts(). This function sets the appropriate bit for the channel in the AIOP's Interrupt Mask Register if the CHANINT_EN flag is set. This allows this channel's bit to be set in the AIOP's Interrupt Channel Register. Interrupts must also be globally enabled before channel interrupts will be passed on to the host. This is done with function sEnGlobalInt(). In some cases it may be desirable to disable interrupts globally but enable channel interrupts. This would allow the global interrupt status register to be used to determine which AIOPs need service. */ void sEnInterrupts(CHANNEL_T *ChP,Word_t Flags) { Byte_t Mask; /* Interrupt Mask Register */ ChP->RxControl[2] |= ((Byte_t)Flags & (RXINT_EN | SRCINT_EN | MCINT_EN)); rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->RxControl)); ChP->TxControl[2] |= ((Byte_t)Flags & TXINT_EN); rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxControl)); if(Flags & CHANINT_EN) { Mask = rp_readch1(ChP,_INT_MASK) | rp_sBitMapSetTbl[ChP->ChanNum]; rp_writech1(ChP,_INT_MASK,Mask); } } /*************************************************************************** Function: sDisInterrupts Purpose: Disable one or more interrupts for a channel Call: sDisInterrupts(ChP,Flags) CHANNEL_T *ChP; Ptr to channel structure Word_t Flags: Interrupt flags, can be any combination of the following flags: TXINT_EN: Interrupt on Tx FIFO empty RXINT_EN: Interrupt on Rx FIFO at trigger level (see sSetRxTrigger()) SRCINT_EN: Interrupt on SRC (Special Rx Condition) MCINT_EN: Interrupt on modem input change CHANINT_EN: Disable channel interrupt signal to the AIOP's Interrupt Channel Register. Return: void Comments: If an interrupt flag is set in Flags, that interrupt will be disabled. If an interrupt flag is not set in Flags, that interrupt will not be changed. Interrupts can be enabled with function sEnInterrupts(). This function clears the appropriate bit for the channel in the AIOP's Interrupt Mask Register if the CHANINT_EN flag is set. This blocks this channel's bit from being set in the AIOP's Interrupt Channel Register. */ void sDisInterrupts(CHANNEL_T *ChP,Word_t Flags) { Byte_t Mask; /* Interrupt Mask Register */ ChP->RxControl[2] &= ~((Byte_t)Flags & (RXINT_EN | SRCINT_EN | MCINT_EN)); rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->RxControl)); ChP->TxControl[2] &= ~((Byte_t)Flags & TXINT_EN); rp_writech4(ChP,_INDX_ADDR,le32dec(ChP->TxControl)); if(Flags & CHANINT_EN) { Mask = rp_readch1(ChP,_INT_MASK) & rp_sBitMapClrTbl[ChP->ChanNum]; rp_writech1(ChP,_INT_MASK,Mask); } } /********************************************************************* Begin FreeBsd-specific driver code **********************************************************************/ #define POLL_INTERVAL (hz / 100) #define RP_ISMULTIPORT(dev) ((dev)->id_flags & 0x1) #define RP_MPMASTER(dev) (((dev)->id_flags >> 8) & 0xff) #define RP_NOTAST4(dev) ((dev)->id_flags & 0x04) /* * The top-level routines begin here */ static void rpclose(struct tty *tp); static void rphardclose(struct tty *tp); static int rpmodem(struct tty *, int, int); static int rpparam(struct tty *, struct termios *); static void rpstart(struct tty *); static int rpioctl(struct tty *, u_long, caddr_t, struct thread *); static int rpopen(struct tty *); static void rp_do_receive(struct rp_port *rp, struct tty *tp, CHANNEL_t *cp, unsigned int ChanStatus) { unsigned int CharNStat; int ToRecv, ch, err = 0; ToRecv = sGetRxCnt(cp); if(ToRecv == 0) return; /* If status indicates there are errored characters in the FIFO, then enter status mode (a word in FIFO holds characters and status) */ if(ChanStatus & (RXFOVERFL | RXBREAK | RXFRAME | RXPARITY)) { if(!(ChanStatus & STATMODE)) { ChanStatus |= STATMODE; sEnRxStatusMode(cp); } } /* if we previously entered status mode then read down the FIFO one word at a time, pulling apart the character and the status. Update error counters depending on status. */ tty_lock(tp); if(ChanStatus & STATMODE) { while(ToRecv) { CharNStat = rp_readch2(cp,sGetTxRxDataIO(cp)); ch = CharNStat & 0xff; if((CharNStat & STMBREAK) || (CharNStat & STMFRAMEH)) err |= TRE_FRAMING; else if (CharNStat & STMPARITYH) err |= TRE_PARITY; else if (CharNStat & STMRCVROVRH) { rp->rp_overflows++; err |= TRE_OVERRUN; } ttydisc_rint(tp, ch, err); ToRecv--; } /* After emtying FIFO in status mode, turn off status mode */ if(sGetRxCnt(cp) == 0) { sDisRxStatusMode(cp); } } else { ToRecv = sGetRxCnt(cp); while (ToRecv) { ch = rp_readch1(cp,sGetTxRxDataIO(cp)); ttydisc_rint(tp, ch & 0xff, err); ToRecv--; } } ttydisc_rint_done(tp); tty_unlock(tp); } static void rp_handle_port(struct rp_port *rp) { CHANNEL_t *cp; struct tty *tp; unsigned int IntMask, ChanStatus; if(!rp) return; cp = &rp->rp_channel; tp = rp->rp_tty; IntMask = sGetChanIntID(cp); IntMask = IntMask & rp->rp_intmask; ChanStatus = sGetChanStatus(cp); if(IntMask & RXF_TRIG) rp_do_receive(rp, tp, cp, ChanStatus); if(IntMask & DELTA_CD) { if(ChanStatus & CD_ACT) { (void)ttydisc_modem(tp, 1); } else { (void)ttydisc_modem(tp, 0); } } /* oldcts = rp->rp_cts; rp->rp_cts = ((ChanStatus & CTS_ACT) != 0); if(oldcts != rp->rp_cts) { printf("CTS change (now %s)... on port %d\n", rp->rp_cts ? "on" : "off", rp->rp_port); } */ } static void rp_do_poll(void *arg) { CONTROLLER_t *ctl; struct rp_port *rp; struct tty *tp; int count; unsigned char CtlMask, AiopMask; rp = arg; tp = rp->rp_tty; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); ctl = rp->rp_ctlp; CtlMask = ctl->ctlmask(ctl); if (CtlMask & (1 << rp->rp_aiop)) { AiopMask = sGetAiopIntStatus(ctl, rp->rp_aiop); if (AiopMask & (1 << rp->rp_chan)) { rp_handle_port(rp); } } count = sGetTxCnt(&rp->rp_channel); if (count >= 0 && (count <= rp->rp_restart)) { rpstart(tp); } callout_schedule(&rp->rp_timer, POLL_INTERVAL); } static struct ttydevsw rp_tty_class = { .tsw_flags = TF_INITLOCK|TF_CALLOUT, .tsw_open = rpopen, .tsw_close = rpclose, .tsw_outwakeup = rpstart, .tsw_ioctl = rpioctl, .tsw_param = rpparam, .tsw_modem = rpmodem, .tsw_free = rpfree, }; static void rpfree(void *softc) { struct rp_port *rp = softc; CONTROLLER_t *ctlp = rp->rp_ctlp; atomic_subtract_32(&ctlp->free, 1); } int rp_attachcommon(CONTROLLER_T *ctlp, int num_aiops, int num_ports) { int unit; int num_chan; int aiop, chan, port; int ChanStatus; int retval; struct rp_port *rp; struct tty *tp; unit = device_get_unit(ctlp->dev); printf("RocketPort%d (Version %s) %d ports.\n", unit, RocketPortVersion, num_ports); ctlp->num_ports = num_ports; ctlp->rp = rp = (struct rp_port *) malloc(sizeof(struct rp_port) * num_ports, M_DEVBUF, M_NOWAIT | M_ZERO); if (rp == NULL) { device_printf(ctlp->dev, "rp_attachcommon: Could not malloc rp_ports structures.\n"); retval = ENOMEM; goto nogo; } port = 0; for(aiop=0; aiop < num_aiops; aiop++) { num_chan = sGetAiopNumChan(ctlp, aiop); for(chan=0; chan < num_chan; chan++, port++, rp++) { rp->rp_tty = tp = tty_alloc(&rp_tty_class, rp); callout_init_mtx(&rp->rp_timer, tty_getlock(tp), 0); rp->rp_port = port; rp->rp_ctlp = ctlp; rp->rp_unit = unit; rp->rp_chan = chan; rp->rp_aiop = aiop; rp->rp_intmask = RXF_TRIG | TXFIFO_MT | SRC_INT | DELTA_CD | DELTA_CTS | DELTA_DSR; #ifdef notdef ChanStatus = sGetChanStatus(&rp->rp_channel); #endif /* notdef */ if(sInitChan(ctlp, &rp->rp_channel, aiop, chan) == 0) { device_printf(ctlp->dev, "RocketPort sInitChan(%d, %d, %d) failed.\n", unit, aiop, chan); retval = ENXIO; goto nogo; } ChanStatus = sGetChanStatus(&rp->rp_channel); rp->rp_cts = (ChanStatus & CTS_ACT) != 0; tty_makedev(tp, NULL, "R%r%r", unit, port); } } mtx_init(&ctlp->hwmtx, "rp_hwmtx", NULL, MTX_DEF); ctlp->hwmtx_init = 1; return (0); nogo: rp_releaseresource(ctlp); return (retval); } void rp_releaseresource(CONTROLLER_t *ctlp) { struct rp_port *rp; int i; if (ctlp->rp != NULL) { for (i = 0; i < ctlp->num_ports; i++) { rp = ctlp->rp + i; atomic_add_32(&ctlp->free, 1); tty_lock(rp->rp_tty); tty_rel_gone(rp->rp_tty); } free(ctlp->rp, M_DEVBUF); ctlp->rp = NULL; } while (ctlp->free != 0) { pause("rpwt", hz / 10); } if (ctlp->hwmtx_init) mtx_destroy(&ctlp->hwmtx); } static int rpopen(struct tty *tp) { struct rp_port *rp; int flags; unsigned int IntMask, ChanStatus; rp = tty_softc(tp); flags = 0; flags |= SET_RTS; flags |= SET_DTR; rp->rp_channel.TxControl[3] = ((rp->rp_channel.TxControl[3] & ~(SET_RTS | SET_DTR)) | flags); rp_writech4(&rp->rp_channel,_INDX_ADDR, le32dec(rp->rp_channel.TxControl)); sSetRxTrigger(&rp->rp_channel, TRIG_1); sDisRxStatusMode(&rp->rp_channel); sFlushRxFIFO(&rp->rp_channel); sFlushTxFIFO(&rp->rp_channel); sEnInterrupts(&rp->rp_channel, (TXINT_EN|MCINT_EN|RXINT_EN|SRCINT_EN|CHANINT_EN)); sSetRxTrigger(&rp->rp_channel, TRIG_1); sDisRxStatusMode(&rp->rp_channel); sClrTxXOFF(&rp->rp_channel); /* sDisRTSFlowCtl(&rp->rp_channel); sDisCTSFlowCtl(&rp->rp_channel); */ sDisTxSoftFlowCtl(&rp->rp_channel); sStartRxProcessor(&rp->rp_channel); sEnRxFIFO(&rp->rp_channel); sEnTransmit(&rp->rp_channel); /* sSetDTR(&rp->rp_channel); sSetRTS(&rp->rp_channel); */ IntMask = sGetChanIntID(&rp->rp_channel); IntMask = IntMask & rp->rp_intmask; ChanStatus = sGetChanStatus(&rp->rp_channel); callout_reset(&rp->rp_timer, POLL_INTERVAL, rp_do_poll, rp); device_busy(rp->rp_ctlp->dev); return(0); } static void rpclose(struct tty *tp) { struct rp_port *rp; rp = tty_softc(tp); callout_stop(&rp->rp_timer); rphardclose(tp); device_unbusy(rp->rp_ctlp->dev); } static void rphardclose(struct tty *tp) { struct rp_port *rp; CHANNEL_t *cp; rp = tty_softc(tp); cp = &rp->rp_channel; sFlushRxFIFO(cp); sFlushTxFIFO(cp); sDisTransmit(cp); sDisInterrupts(cp, TXINT_EN|MCINT_EN|RXINT_EN|SRCINT_EN|CHANINT_EN); sDisRTSFlowCtl(cp); sDisCTSFlowCtl(cp); sDisTxSoftFlowCtl(cp); sClrTxXOFF(cp); #ifdef DJA if(tp->t_cflag&HUPCL || !(tp->t_state&TS_ISOPEN) || !tp->t_actout) { sClrDTR(cp); } if(ISCALLOUT(tp->t_dev)) { sClrDTR(cp); } tp->t_actout = FALSE; wakeup(&tp->t_actout); wakeup(TSA_CARR_ON(tp)); #endif /* DJA */ } static int rpioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { struct rp_port *rp; rp = tty_softc(tp); switch (cmd) { case TIOCSBRK: sSendBreak(&rp->rp_channel); return (0); case TIOCCBRK: sClrBreak(&rp->rp_channel); return (0); default: return ENOIOCTL; } } static int rpmodem(struct tty *tp, int sigon, int sigoff) { struct rp_port *rp; int i, j, k; rp = tty_softc(tp); if (sigon != 0 || sigoff != 0) { i = j = 0; if (sigon & SER_DTR) i = SET_DTR; if (sigoff & SER_DTR) j = SET_DTR; if (sigon & SER_RTS) i = SET_RTS; if (sigoff & SER_RTS) j = SET_RTS; rp->rp_channel.TxControl[3] &= ~i; rp->rp_channel.TxControl[3] |= j; rp_writech4(&rp->rp_channel,_INDX_ADDR, le32dec(rp->rp_channel.TxControl)); } else { i = sGetChanStatusLo(&rp->rp_channel); j = rp->rp_channel.TxControl[3]; k = 0; if (j & SET_DTR) k |= SER_DTR; if (j & SET_RTS) k |= SER_RTS; if (i & CD_ACT) k |= SER_DCD; if (i & DSR_ACT) k |= SER_DSR; if (i & CTS_ACT) k |= SER_CTS; return(k); } return (0); } static struct { int baud; int conversion; } baud_table[] = { {B0, 0}, {B50, BRD50}, {B75, BRD75}, {B110, BRD110}, {B134, BRD134}, {B150, BRD150}, {B200, BRD200}, {B300, BRD300}, {B600, BRD600}, {B1200, BRD1200}, {B1800, BRD1800}, {B2400, BRD2400}, {B4800, BRD4800}, {B9600, BRD9600}, {B19200, BRD19200}, {B38400, BRD38400}, {B7200, BRD7200}, {B14400, BRD14400}, {B57600, BRD57600}, {B76800, BRD76800}, {B115200, BRD115200}, {B230400, BRD230400}, {-1, -1} }; static int rp_convert_baud(int baud) { int i; for (i = 0; baud_table[i].baud >= 0; i++) { if (baud_table[i].baud == baud) break; } return baud_table[i].conversion; } static int rpparam(tp, t) struct tty *tp; struct termios *t; { struct rp_port *rp; CHANNEL_t *cp; int cflag, iflag, oflag, lflag; int ospeed; #ifdef RPCLOCAL int devshift; #endif rp = tty_softc(tp); cp = &rp->rp_channel; cflag = t->c_cflag; #ifdef RPCLOCAL devshift = umynor / 32; devshift = 1 << devshift; if ( devshift & RPCLOCAL ) { cflag |= CLOCAL; } #endif iflag = t->c_iflag; oflag = t->c_oflag; lflag = t->c_lflag; ospeed = rp_convert_baud(t->c_ispeed); if(ospeed < 0 || t->c_ispeed != t->c_ospeed) return(EINVAL); if(t->c_ospeed == 0) { sClrDTR(cp); return(0); } rp->rp_fifo_lw = ((t->c_ospeed*2) / 1000) +1; /* Set baud rate ----- we only pay attention to ispeed */ sSetDTR(cp); sSetRTS(cp); sSetBaud(cp, ospeed); if(cflag & CSTOPB) { sSetStop2(cp); } else { sSetStop1(cp); } if(cflag & PARENB) { sEnParity(cp); if(cflag & PARODD) { sSetOddParity(cp); } else { sSetEvenParity(cp); } } else { sDisParity(cp); } if((cflag & CSIZE) == CS8) { sSetData8(cp); rp->rp_imask = 0xFF; } else { sSetData7(cp); rp->rp_imask = 0x7F; } if(iflag & ISTRIP) { rp->rp_imask &= 0x7F; } if(cflag & CLOCAL) { rp->rp_intmask &= ~DELTA_CD; } else { rp->rp_intmask |= DELTA_CD; } /* Put flow control stuff here */ if(cflag & CCTS_OFLOW) { sEnCTSFlowCtl(cp); } else { sDisCTSFlowCtl(cp); } if(cflag & CRTS_IFLOW) { rp->rp_rts_iflow = 1; } else { rp->rp_rts_iflow = 0; } if(cflag & CRTS_IFLOW) { sEnRTSFlowCtl(cp); } else { sDisRTSFlowCtl(cp); } return(0); } static void rpstart(struct tty *tp) { struct rp_port *rp; CHANNEL_t *cp; char flags; int xmit_fifo_room; int i, count, wcount; rp = tty_softc(tp); cp = &rp->rp_channel; flags = rp->rp_channel.TxControl[3]; if(rp->rp_xmit_stopped) { sEnTransmit(cp); rp->rp_xmit_stopped = 0; } xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp); count = ttydisc_getc(tp, &rp->TxBuf, xmit_fifo_room); if(xmit_fifo_room > 0) { for( i = 0, wcount = count >> 1; wcount > 0; i += 2, wcount-- ) { rp_writech2(cp, sGetTxRxDataIO(cp), le16dec(&rp->TxBuf[i])); } if ( count & 1 ) { rp_writech1(cp, sGetTxRxDataIO(cp), rp->TxBuf[(count-1)]); } } } Index: head/sys/dev/xen/console/xen_console.c =================================================================== --- head/sys/dev/xen/console/xen_console.c (revision 360050) +++ head/sys/dev/xen/console/xen_console.c (revision 360051) @@ -1,791 +1,791 @@ /* * Copyright (c) 2015 Julien Grall * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #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 "opt_ddb.h" #include "opt_printf.h" #ifdef DDB #include #endif static char driver_name[] = "xc"; struct xencons_priv; typedef void xencons_early_init_t(struct xencons_priv *cons); typedef int xencons_init_t(device_t dev, struct tty *tp, driver_intr_t intr_handler); typedef int xencons_read_t(struct xencons_priv *cons, char *buffer, unsigned int size); typedef int xencons_write_t(struct xencons_priv *cons, const char *buffer, unsigned int size); struct xencons_ops { /* * Called by the low-level driver during early boot. * Only the minimal set up to get a console should be done here. */ xencons_early_init_t *early_init; /* Prepare the console to be fully use */ xencons_init_t *init; /* Read/write helpers */ xencons_read_t *read; xencons_write_t *write; }; struct xencons_priv { /* Mutex to protect the shared ring and the internal buffers */ struct mtx mtx; /* Interrupt handler used for notify the backend */ xen_intr_handle_t intr_handle; /* KDB internal state */ #ifdef KDB int altbrk; #endif /* Status of the tty */ bool opened; /* Callout used when the write buffer is full */ struct callout callout; /* Internal buffers must be used with mtx locked */ #define WBUF_SIZE 4096 #define WBUF_MASK(_i) ((_i)&(WBUF_SIZE-1)) char wbuf[WBUF_SIZE]; unsigned int wc, wp; /* Consumer/producer wbuf */ #define RBUF_SIZE 1024 #define RBUF_MASK(_i) ((_i)&(RBUF_SIZE-1)) char rbuf[RBUF_SIZE]; unsigned int rc, rp; /* Consumer/producer rbuf */ /* Pointer to the console operations */ const struct xencons_ops *ops; /* * Ring specific fields * XXX: make an union? */ /* Event channel number for early notification (PV only) */ uint32_t evtchn; /* Console shared page */ struct xencons_interface *intf; }; /* * Data for the main console * Necessary to support low-level console driver */ static struct xencons_priv main_cons; #define XC_POLLTIME (hz/10) /*----------------------------- Debug function ------------------------------*/ struct putchar_arg { char *buf; size_t size; size_t n_next; }; static void putchar(int c, void *arg) { struct putchar_arg *pca; pca = (struct putchar_arg *)arg; if (pca->buf == NULL) { /* * We have no buffer, output directly to the * console char by char. */ HYPERVISOR_console_write((char *)&c, 1); } else { pca->buf[pca->n_next++] = c; if ((pca->size == pca->n_next) || (c = '\0')) { /* Flush the buffer */ HYPERVISOR_console_write(pca->buf, pca->n_next); pca->n_next = 0; } } } void xc_printf(const char *fmt, ...) { va_list ap; struct putchar_arg pca; #ifdef PRINTF_BUFR_SIZE char buf[PRINTF_BUFR_SIZE]; pca.buf = buf; pca.size = sizeof(buf); pca.n_next = 0; #else pca.buf = NULL; pca.size = 0; #endif KASSERT((xen_domain()), ("call to xc_printf from non Xen guest")); va_start(ap, fmt); kvprintf(fmt, putchar, &pca, 10, ap); va_end(ap); #ifdef PRINTF_BUFR_SIZE if (pca.n_next != 0) HYPERVISOR_console_write(buf, pca.n_next); #endif } /*---------------------- Helpers for the console lock -----------------------*/ /* * The lock is not used when the kernel is panicing as it will never recover * and we want to output no matter what it costs. */ static inline void xencons_lock(struct xencons_priv *cons) { if (!KERNEL_PANICKED()) mtx_lock_spin(&cons->mtx); } static inline void xencons_unlock(struct xencons_priv *cons) { if (!KERNEL_PANICKED()) mtx_unlock_spin(&cons->mtx); } #define xencons_lock_assert(cons) mtx_assert(&(cons)->mtx, MA_OWNED) /*------------------ Helpers for the hypervisor console ---------------------*/ static void xencons_early_init_hypervisor(struct xencons_priv *cons) { /* * Nothing to setup for the low-level console when using * the hypervisor console. */ } static int xencons_init_hypervisor(device_t dev, struct tty *tp, driver_intr_t intr_handler) { struct xencons_priv *cons; int err; cons = tty_softc(tp); err = xen_intr_bind_virq(dev, VIRQ_CONSOLE, 0, NULL, intr_handler, tp, INTR_TYPE_TTY | INTR_MPSAFE, &cons->intr_handle); if (err != 0) device_printf(dev, "Can't register console interrupt\n"); return (err); } static int xencons_write_hypervisor(struct xencons_priv *cons, const char *buffer, unsigned int size) { HYPERVISOR_console_io(CONSOLEIO_write, size, buffer); return (size); } static int xencons_read_hypervisor(struct xencons_priv *cons, char *buffer, unsigned int size) { xencons_lock_assert(cons); return (HYPERVISOR_console_io(CONSOLEIO_read, size, buffer)); } static const struct xencons_ops xencons_hypervisor_ops = { .early_init = xencons_early_init_hypervisor, .init = xencons_init_hypervisor, .read = xencons_read_hypervisor, .write = xencons_write_hypervisor, }; /*------------------ Helpers for the ring console ---------------------------*/ static void xencons_early_init_ring(struct xencons_priv *cons) { cons->intf = pmap_mapdev_attr(ptoa(xen_get_console_mfn()), PAGE_SIZE, PAT_WRITE_BACK); cons->evtchn = xen_get_console_evtchn(); } static int xencons_init_ring(device_t dev, struct tty *tp, driver_intr_t intr_handler) { struct xencons_priv *cons; int err; cons = tty_softc(tp); if (cons->evtchn == 0) return (ENODEV); err = xen_intr_bind_local_port(dev, cons->evtchn, NULL, intr_handler, tp, INTR_TYPE_TTY | INTR_MPSAFE, &cons->intr_handle); if (err != 0) return (err); return (0); } static void xencons_notify_ring(struct xencons_priv *cons) { /* * The console may be used before the ring interrupt is properly * initialized. * If so, fallback to directly use the event channel hypercall. */ if (__predict_true(cons->intr_handle != NULL)) xen_intr_signal(cons->intr_handle); else { struct evtchn_send send = { .port = cons->evtchn }; HYPERVISOR_event_channel_op(EVTCHNOP_send, &send); } } static int xencons_write_ring(struct xencons_priv *cons, const char *buffer, unsigned int size) { struct xencons_interface *intf; XENCONS_RING_IDX wcons, wprod; int sent; intf = cons->intf; xencons_lock_assert(cons); wcons = intf->out_cons; wprod = intf->out_prod; mb(); KASSERT((wprod - wcons) <= sizeof(intf->out), ("console send ring inconsistent")); for (sent = 0; sent < size; sent++, wprod++) { if ((wprod - wcons) >= sizeof(intf->out)) break; intf->out[MASK_XENCONS_IDX(wprod, intf->out)] = buffer[sent]; } wmb(); intf->out_prod = wprod; xencons_notify_ring(cons); return (sent); } static int xencons_read_ring(struct xencons_priv *cons, char *buffer, unsigned int size) { struct xencons_interface *intf; XENCONS_RING_IDX rcons, rprod; unsigned int rsz; intf = cons->intf; xencons_lock_assert(cons); rcons = intf->in_cons; rprod = intf->in_prod; rmb(); for (rsz = 0; rsz < size; rsz++, rcons++) { if (rprod == rcons) break; buffer[rsz] = intf->in[MASK_XENCONS_IDX(rcons, intf->in)]; } wmb(); intf->in_cons = rcons; /* No need to notify the backend if nothing has been read */ if (rsz != 0) xencons_notify_ring(cons); return (rsz); } static const struct xencons_ops xencons_ring_ops = { .early_init = xencons_early_init_ring, .init = xencons_init_ring, .read = xencons_read_ring, .write = xencons_write_ring, }; /*------------------ Common implementation of the console -------------------*/ /* * Called by the low-level driver during early boot to initialize the * main console driver. * Only the minimal set up to get a console should be done here. */ static void xencons_early_init(void) { mtx_init(&main_cons.mtx, "XCONS LOCK", NULL, MTX_SPIN); if (xen_get_console_evtchn() == 0) main_cons.ops = &xencons_hypervisor_ops; else main_cons.ops = &xencons_ring_ops; main_cons.ops->early_init(&main_cons); } /* * Receive character from the console and put them in the internal buffer * XXX: Handle overflow of the internal buffer */ static void xencons_rx(struct xencons_priv *cons) { char buf[16]; int sz; xencons_lock(cons); while ((sz = cons->ops->read(cons, buf, sizeof(buf))) > 0) { int i; for (i = 0; i < sz; i++) cons->rbuf[RBUF_MASK(cons->rp++)] = buf[i]; } xencons_unlock(cons); } /* Return true if the write buffer is full */ static bool xencons_tx_full(struct xencons_priv *cons) { unsigned int used; xencons_lock(cons); used = cons->wp - cons->wc; xencons_unlock(cons); return (used >= WBUF_SIZE); } static void xencons_tx_flush(struct xencons_priv *cons, int force) { int sz; xencons_lock(cons); while (cons->wc != cons->wp) { int sent; sz = cons->wp - cons->wc; if (sz > (WBUF_SIZE - WBUF_MASK(cons->wc))) sz = WBUF_SIZE - WBUF_MASK(cons->wc); sent = cons->ops->write(cons, &cons->wbuf[WBUF_MASK(cons->wc)], sz); /* * The other end may not have been initialized. Ignore * the force. */ if (__predict_false(sent < 0)) break; /* * If force is set, spin until the console data is * flushed through the domain controller. */ if (sent == 0 && __predict_true(!force)) break; cons->wc += sent; } xencons_unlock(cons); } static bool xencons_putc(struct xencons_priv *cons, int c, bool force_flush) { xencons_lock(cons); if ((cons->wp - cons->wc) < WBUF_SIZE) cons->wbuf[WBUF_MASK(cons->wp++)] = c; xencons_unlock(cons); xencons_tx_flush(cons, force_flush); return (xencons_tx_full(cons)); } static int xencons_getc(struct xencons_priv *cons) { int ret; xencons_lock(cons); if (cons->rp != cons->rc) { /* We need to return only one char */ ret = (int)cons->rbuf[RBUF_MASK(cons->rc)]; cons->rc++; } else { ret = -1; } xencons_unlock(cons); return (ret); } static bool xencons_tx(struct tty *tp) { bool cons_full; char c; struct xencons_priv *cons; cons = tty_softc(tp); - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); /* * Don't transmit any character if the buffer is full. Otherwise, * characters may be lost */ if (xencons_tx_full(cons)) return (false); cons_full = false; while (!cons_full && ttydisc_getc(tp, &c, 1) == 1) cons_full = xencons_putc(cons, c, false); return (!cons_full); } static void xencons_intr(void *arg) { struct tty *tp; struct xencons_priv *cons; int ret; tp = arg; cons = tty_softc(tp); /* * The input will be used by the low-level console when KDB is active */ if (kdb_active) return; /* * It's not necessary to retrieve input when the tty is not opened */ if (!cons->opened) return; xencons_rx(cons); tty_lock(tp); while ((ret = xencons_getc(cons)) != -1) { #ifdef KDB kdb_alt_break(ret, &cons->altbrk); #endif ttydisc_rint(tp, ret, 0); } ttydisc_rint_done(tp); tty_unlock(tp); /* Try to flush remaining characters if necessary */ xencons_tx_flush(cons, 0); } /* * Helpers to call while shutting down: * - Force flush all output */ static void xencons_shutdown(void *arg, int howto) { struct tty *tp; tp = arg; xencons_tx_flush(tty_softc(tp), 1); } /*---------------------- Low-level console driver ---------------------------*/ static void xencons_cnprobe(struct consdev *cp) { if (!xen_domain()) return; cp->cn_pri = (boothowto & RB_SERIAL) ? CN_REMOTE : CN_NORMAL; sprintf(cp->cn_name, "%s0", driver_name); } static void xencons_cninit(struct consdev *cp) { xencons_early_init(); } static void xencons_cnterm(struct consdev *cp) { } static void xencons_cngrab(struct consdev *cp) { } static void xencons_cnungrab(struct consdev *cp) { } static int xencons_cngetc(struct consdev *dev) { xencons_rx(&main_cons); return (xencons_getc(&main_cons)); } static void xencons_cnputc(struct consdev *dev, int c) { /* * The low-level console is used by KDB and panic. We have to ensure * that any character sent will be seen by the backend. */ xencons_putc(&main_cons, c, true); } CONSOLE_DRIVER(xencons); /*----------------------------- TTY driver ---------------------------------*/ static int xencons_tty_open(struct tty *tp) { struct xencons_priv *cons; cons = tty_softc(tp); cons->opened = true; return (0); } static void xencons_tty_close(struct tty *tp) { struct xencons_priv *cons; cons = tty_softc(tp); cons->opened = false; } static void xencons_timeout(void *v) { struct tty *tp; struct xencons_priv *cons; tp = v; cons = tty_softc(tp); if (!xencons_tx(tp)) callout_reset(&cons->callout, XC_POLLTIME, xencons_timeout, tp); } static void xencons_tty_outwakeup(struct tty *tp) { struct xencons_priv *cons; cons = tty_softc(tp); callout_stop(&cons->callout); if (!xencons_tx(tp)) callout_reset(&cons->callout, XC_POLLTIME, xencons_timeout, tp); } static struct ttydevsw xencons_ttydevsw = { .tsw_flags = TF_NOPREFIX, .tsw_open = xencons_tty_open, .tsw_close = xencons_tty_close, .tsw_outwakeup = xencons_tty_outwakeup, }; /*------------------------ Main console driver ------------------------------*/ static void xencons_identify(driver_t *driver, device_t parent) { device_t child; if (main_cons.ops == NULL) return; child = BUS_ADD_CHILD(parent, 0, driver_name, 0); } static int xencons_probe(device_t dev) { device_set_desc(dev, "Xen Console"); return (BUS_PROBE_NOWILDCARD); } static int xencons_attach(device_t dev) { struct tty *tp; /* * The main console is already allocated statically in order to * support low-level console */ struct xencons_priv *cons; int err; cons = &main_cons; tp = tty_alloc(&xencons_ttydevsw, cons); tty_makedev(tp, NULL, "%s%r", driver_name, 0); device_set_softc(dev, tp); callout_init_mtx(&cons->callout, tty_getlock(tp), 0); err = cons->ops->init(dev, tp, xencons_intr); if (err != 0) { device_printf(dev, "Unable to initialize the console (%d)\n", err); return (err); } /* register handler to flush console on shutdown */ if ((EVENTHANDLER_REGISTER(shutdown_post_sync, xencons_shutdown, tp, SHUTDOWN_PRI_DEFAULT)) == NULL) device_printf(dev, "shutdown event registration failed!\n"); return (0); } static int xencons_resume(device_t dev) { struct xencons_priv *cons; struct tty *tp; int err; tp = device_get_softc(dev); cons = tty_softc(tp); xen_intr_unbind(&cons->intr_handle); err = cons->ops->init(dev, tp, xencons_intr); if (err != 0) { device_printf(dev, "Unable to resume the console (%d)\n", err); return (err); } return (0); } static devclass_t xencons_devclass; static device_method_t xencons_methods[] = { DEVMETHOD(device_identify, xencons_identify), DEVMETHOD(device_probe, xencons_probe), DEVMETHOD(device_attach, xencons_attach), DEVMETHOD(device_resume, xencons_resume), DEVMETHOD_END }; static driver_t xencons_driver = { driver_name, xencons_methods, 0, }; DRIVER_MODULE(xc, xenpv, xencons_driver, xencons_devclass, 0, 0); Index: head/sys/kern/tty.c =================================================================== --- head/sys/kern/tty.c (revision 360050) +++ head/sys/kern/tty.c (revision 360051) @@ -1,2426 +1,2426 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * Portions of this software were developed under sponsorship from Snow * B.V., the Netherlands. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_capsicum.h" #include "opt_printf.h" #include #include #include #include #include #include #include #include #ifdef COMPAT_43TTY #include #endif /* COMPAT_43TTY */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TTYDEFCHARS #include #undef TTYDEFCHARS #include #include #include static MALLOC_DEFINE(M_TTY, "tty", "tty device"); static void tty_rel_free(struct tty *tp); static TAILQ_HEAD(, tty) tty_list = TAILQ_HEAD_INITIALIZER(tty_list); 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. */ #define TTYSUP_IFLAG (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|ISTRIP|\ INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY|IMAXBEL) #define TTYSUP_OFLAG (OPOST|ONLCR|TAB3|ONOEOT|OCRNL|ONOCR|ONLRET) #define TTYSUP_LFLAG (ECHOKE|ECHOE|ECHOK|ECHO|ECHONL|ECHOPRT|\ ECHOCTL|ISIG|ICANON|ALTWERASE|IEXTEN|TOSTOP|\ FLUSHO|NOKERNINFO|NOFLSH) #define TTYSUP_CFLAG (CIGNORE|CSIZE|CSTOPB|CREAD|PARENB|PARODD|\ HUPCL|CLOCAL|CCTS_OFLOW|CRTS_IFLOW|CDTR_IFLOW|\ CDSR_OFLOW|CCAR_OFLOW|CNO_RTSDTR) #define TTY_CALLOUT(tp,d) (dev2unit(d) & TTYUNIT_CALLOUT) static int tty_drainwait = 5 * 60; SYSCTL_INT(_kern, OID_AUTO, tty_drainwait, CTLFLAG_RWTUN, &tty_drainwait, 0, "Default output drain timeout in seconds"); /* * Set TTY buffer sizes. */ #define TTYBUF_MAX 65536 #ifdef PRINTF_BUFR_SIZE #define TTY_PRBUF_SIZE PRINTF_BUFR_SIZE #else #define TTY_PRBUF_SIZE 256 #endif /* * Allocate buffer space if necessary, and set low watermarks, based on speed. * Note that the ttyxxxq_setsize() functions may drop and then reacquire the tty * lock during memory allocation. They will return ENXIO if the tty disappears * while unlocked. */ static int tty_watermarks(struct tty *tp) { size_t bs = 0; int error; /* Provide an input buffer for 2 seconds of data. */ if (tp->t_termios.c_cflag & CREAD) bs = MIN(tp->t_termios.c_ispeed / 5, TTYBUF_MAX); error = ttyinq_setsize(&tp->t_inq, tp, bs); if (error != 0) return (error); /* Set low watermark at 10% (when 90% is available). */ tp->t_inlow = (ttyinq_getallocatedsize(&tp->t_inq) * 9) / 10; /* Provide an output buffer for 2 seconds of data. */ bs = MIN(tp->t_termios.c_ospeed / 5, TTYBUF_MAX); error = ttyoutq_setsize(&tp->t_outq, tp, bs); if (error != 0) return (error); /* Set low watermark at 10% (when 90% is available). */ tp->t_outlow = (ttyoutq_getallocatedsize(&tp->t_outq) * 9) / 10; return (0); } static int tty_drain(struct tty *tp, int leaving) { sbintime_t timeout_at; size_t bytes; int error; if (ttyhook_hashook(tp, getc_inject)) /* buffer is inaccessible */ return (0); /* * For close(), use the recent historic timeout of "1 second without * making progress". For tcdrain(), use t_drainwait as the timeout, * with zero meaning "no timeout" which gives POSIX behavior. */ if (leaving) timeout_at = getsbinuptime() + SBT_1S; else if (tp->t_drainwait != 0) timeout_at = getsbinuptime() + SBT_1S * tp->t_drainwait; else timeout_at = 0; /* * Poll the output buffer and the hardware for completion, at 10 Hz. * Polling is required for devices which are not able to signal an * interrupt when the transmitter becomes idle (most USB serial devs). * The unusual structure of this loop ensures we check for busy one more * time after tty_timedwait() returns EWOULDBLOCK, so that success has * higher priority than timeout if the IO completed in the last 100mS. */ error = 0; bytes = ttyoutq_bytesused(&tp->t_outq); for (;;) { if (ttyoutq_bytesused(&tp->t_outq) == 0 && !ttydevsw_busy(tp)) return (0); if (error != 0) return (error); ttydevsw_outwakeup(tp); error = tty_timedwait(tp, &tp->t_outwait, hz / 10); if (error != 0 && error != EWOULDBLOCK) return (error); else if (timeout_at == 0 || getsbinuptime() < timeout_at) error = 0; else if (leaving && ttyoutq_bytesused(&tp->t_outq) < bytes) { /* In close, making progress, grant an extra second. */ error = 0; timeout_at += SBT_1S; bytes = ttyoutq_bytesused(&tp->t_outq); } } } /* * Though ttydev_enter() and ttydev_leave() seem to be related, they * don't have to be used together. ttydev_enter() is used by the cdev * operations to prevent an actual operation from being processed when * the TTY has been abandoned. ttydev_leave() is used by ttydev_open() * and ttydev_close() to determine whether per-TTY data should be * deallocated. */ static __inline int ttydev_enter(struct tty *tp) { tty_lock(tp); if (tty_gone(tp) || !tty_opened(tp)) { /* Device is already gone. */ tty_unlock(tp); return (ENXIO); } return (0); } static void ttydev_leave(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tty_opened(tp) || tp->t_flags & TF_OPENCLOSE) { /* Device is still opened somewhere. */ tty_unlock(tp); return; } tp->t_flags |= TF_OPENCLOSE; /* Remove console TTY. */ if (constty == tp) constty_clear(); /* Drain any output. */ if (!tty_gone(tp)) tty_drain(tp, 1); ttydisc_close(tp); /* Free i/o queues now since they might be large. */ ttyinq_free(&tp->t_inq); tp->t_inlow = 0; ttyoutq_free(&tp->t_outq); tp->t_outlow = 0; knlist_clear(&tp->t_inpoll.si_note, 1); knlist_clear(&tp->t_outpoll.si_note, 1); if (!tty_gone(tp)) ttydevsw_close(tp); tp->t_flags &= ~TF_OPENCLOSE; cv_broadcast(&tp->t_dcdwait); tty_rel_free(tp); } /* * Operations that are exposed through the character device in /dev. */ static int ttydev_open(struct cdev *dev, int oflags, int devtype __unused, struct thread *td) { struct tty *tp; int error; tp = dev->si_drv1; error = 0; tty_lock(tp); if (tty_gone(tp)) { /* Device is already gone. */ tty_unlock(tp); return (ENXIO); } /* * Block when other processes are currently opening or closing * the TTY. */ while (tp->t_flags & TF_OPENCLOSE) { error = tty_wait(tp, &tp->t_dcdwait); if (error != 0) { tty_unlock(tp); return (error); } } tp->t_flags |= TF_OPENCLOSE; /* * Make sure the "tty" and "cua" device cannot be opened at the * same time. The console is a "tty" device. */ if (TTY_CALLOUT(tp, dev)) { if (tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) { error = EBUSY; goto done; } } else { if (tp->t_flags & TF_OPENED_OUT) { error = EBUSY; goto done; } } if (tp->t_flags & TF_EXCLUDE && priv_check(td, PRIV_TTY_EXCLUSIVE)) { error = EBUSY; goto done; } if (!tty_opened(tp)) { /* Set proper termios flags. */ if (TTY_CALLOUT(tp, dev)) tp->t_termios = tp->t_termios_init_out; else tp->t_termios = tp->t_termios_init_in; ttydevsw_param(tp, &tp->t_termios); /* Prevent modem control on callout devices and /dev/console. */ if (TTY_CALLOUT(tp, dev) || dev == dev_console) tp->t_termios.c_cflag |= CLOCAL; if ((tp->t_termios.c_cflag & CNO_RTSDTR) == 0) ttydevsw_modem(tp, SER_DTR|SER_RTS, 0); error = ttydevsw_open(tp); if (error != 0) goto done; ttydisc_open(tp); error = tty_watermarks(tp); if (error != 0) goto done; } /* Wait for Carrier Detect. */ if ((oflags & O_NONBLOCK) == 0 && (tp->t_termios.c_cflag & CLOCAL) == 0) { while ((ttydevsw_modem(tp, 0, 0) & SER_DCD) == 0) { error = tty_wait(tp, &tp->t_dcdwait); if (error != 0) goto done; } } if (dev == dev_console) tp->t_flags |= TF_OPENED_CONS; else if (TTY_CALLOUT(tp, dev)) tp->t_flags |= TF_OPENED_OUT; else tp->t_flags |= TF_OPENED_IN; MPASS((tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) == 0 || (tp->t_flags & TF_OPENED_OUT) == 0); done: tp->t_flags &= ~TF_OPENCLOSE; cv_broadcast(&tp->t_dcdwait); ttydev_leave(tp); return (error); } static int ttydev_close(struct cdev *dev, int fflag, int devtype __unused, struct thread *td __unused) { struct tty *tp = dev->si_drv1; tty_lock(tp); /* * Don't actually close the device if it is being used as the * console. */ MPASS((tp->t_flags & (TF_OPENED_CONS | TF_OPENED_IN)) == 0 || (tp->t_flags & TF_OPENED_OUT) == 0); if (dev == dev_console) tp->t_flags &= ~TF_OPENED_CONS; else tp->t_flags &= ~(TF_OPENED_IN|TF_OPENED_OUT); if (tp->t_flags & TF_OPENED) { tty_unlock(tp); return (0); } /* If revoking, flush output now to avoid draining it later. */ if (fflag & FREVOKE) tty_flush(tp, FWRITE); tp->t_flags &= ~TF_EXCLUDE; /* Properly wake up threads that are stuck - revoke(). */ tp->t_revokecnt++; tty_wakeup(tp, FREAD|FWRITE); cv_broadcast(&tp->t_bgwait); cv_broadcast(&tp->t_dcdwait); ttydev_leave(tp); return (0); } static __inline int tty_is_ctty(struct tty *tp, struct proc *p) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); return (p->p_session == tp->t_session && p->p_flag & P_CONTROLT); } int tty_wait_background(struct tty *tp, struct thread *td, int sig) { struct proc *p = td->td_proc; struct pgrp *pg; ksiginfo_t ksi; int error; MPASS(sig == SIGTTIN || sig == SIGTTOU); - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); for (;;) { PROC_LOCK(p); /* * The process should only sleep, when: * - This terminal is the controlling terminal * - Its process group is not the foreground process * group * - The parent process isn't waiting for the child to * exit * - the signal to send to the process isn't masked */ if (!tty_is_ctty(tp, p) || p->p_pgrp == tp->t_pgrp) { /* Allow the action to happen. */ PROC_UNLOCK(p); return (0); } if (SIGISMEMBER(p->p_sigacts->ps_sigignore, sig) || SIGISMEMBER(td->td_sigmask, sig)) { /* Only allow them in write()/ioctl(). */ PROC_UNLOCK(p); return (sig == SIGTTOU ? 0 : EIO); } pg = p->p_pgrp; if (p->p_flag & P_PPWAIT || pg->pg_jobc == 0) { /* Don't allow the action to happen. */ PROC_UNLOCK(p); return (EIO); } PROC_UNLOCK(p); /* * Send the signal and sleep until we're the new * foreground process group. */ if (sig != 0) { ksiginfo_init(&ksi); ksi.ksi_code = SI_KERNEL; ksi.ksi_signo = sig; sig = 0; } PGRP_LOCK(pg); pgsignal(pg, ksi.ksi_signo, 1, &ksi); PGRP_UNLOCK(pg); error = tty_wait(tp, &tp->t_bgwait); if (error) return (error); } } static int ttydev_read(struct cdev *dev, struct uio *uio, int ioflag) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) goto done; error = ttydisc_read(tp, uio, ioflag); tty_unlock(tp); /* * The read() call should not throw an error when the device is * being destroyed. Silently convert it to an EOF. */ done: if (error == ENXIO) error = 0; return (error); } static int ttydev_write(struct cdev *dev, struct uio *uio, int ioflag) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) return (error); if (tp->t_termios.c_lflag & TOSTOP) { error = tty_wait_background(tp, curthread, SIGTTOU); if (error) goto done; } if (ioflag & IO_NDELAY && tp->t_flags & TF_BUSY_OUT) { /* Allow non-blocking writes to bypass serialization. */ error = ttydisc_write(tp, uio, ioflag); } else { /* Serialize write() calls. */ while (tp->t_flags & TF_BUSY_OUT) { error = tty_wait(tp, &tp->t_outserwait); if (error) goto done; } tp->t_flags |= TF_BUSY_OUT; error = ttydisc_write(tp, uio, ioflag); tp->t_flags &= ~TF_BUSY_OUT; cv_signal(&tp->t_outserwait); } done: tty_unlock(tp); return (error); } static int ttydev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) return (error); switch (cmd) { case TIOCCBRK: case TIOCCONS: case TIOCDRAIN: case TIOCEXCL: case TIOCFLUSH: case TIOCNXCL: case TIOCSBRK: case TIOCSCTTY: case TIOCSETA: case TIOCSETAF: case TIOCSETAW: case TIOCSPGRP: case TIOCSTART: case TIOCSTAT: case TIOCSTI: case TIOCSTOP: case TIOCSWINSZ: #if 0 case TIOCSDRAINWAIT: case TIOCSETD: #endif #ifdef COMPAT_43TTY case TIOCLBIC: case TIOCLBIS: case TIOCLSET: case TIOCSETC: case OTIOCSETD: case TIOCSETN: case TIOCSETP: case TIOCSLTC: #endif /* COMPAT_43TTY */ /* * If the ioctl() causes the TTY to be modified, let it * wait in the background. */ error = tty_wait_background(tp, curthread, SIGTTOU); if (error) goto done; } if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { struct termios *old = &tp->t_termios; struct termios *new = (struct termios *)data; struct termios *lock = TTY_CALLOUT(tp, dev) ? &tp->t_termios_lock_out : &tp->t_termios_lock_in; int cc; /* * Lock state devices. Just overwrite the values of the * commands that are currently in use. */ new->c_iflag = (old->c_iflag & lock->c_iflag) | (new->c_iflag & ~lock->c_iflag); new->c_oflag = (old->c_oflag & lock->c_oflag) | (new->c_oflag & ~lock->c_oflag); new->c_cflag = (old->c_cflag & lock->c_cflag) | (new->c_cflag & ~lock->c_cflag); new->c_lflag = (old->c_lflag & lock->c_lflag) | (new->c_lflag & ~lock->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lock->c_cc[cc]) new->c_cc[cc] = old->c_cc[cc]; if (lock->c_ispeed) new->c_ispeed = old->c_ispeed; if (lock->c_ospeed) new->c_ospeed = old->c_ospeed; } error = tty_ioctl(tp, cmd, data, fflag, td); done: tty_unlock(tp); return (error); } static int ttydev_poll(struct cdev *dev, int events, struct thread *td) { struct tty *tp = dev->si_drv1; int error, revents = 0; error = ttydev_enter(tp); if (error) return ((events & (POLLIN|POLLRDNORM)) | POLLHUP); if (events & (POLLIN|POLLRDNORM)) { /* See if we can read something. */ if (ttydisc_read_poll(tp) > 0) revents |= events & (POLLIN|POLLRDNORM); } if (tp->t_flags & TF_ZOMBIE) { /* Hangup flag on zombie state. */ revents |= POLLHUP; } else if (events & (POLLOUT|POLLWRNORM)) { /* See if we can write something. */ if (ttydisc_write_poll(tp) > 0) revents |= events & (POLLOUT|POLLWRNORM); } if (revents == 0) { if (events & (POLLIN|POLLRDNORM)) selrecord(td, &tp->t_inpoll); if (events & (POLLOUT|POLLWRNORM)) selrecord(td, &tp->t_outpoll); } tty_unlock(tp); return (revents); } static int ttydev_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { struct tty *tp = dev->si_drv1; int error; /* Handle mmap() through the driver. */ error = ttydev_enter(tp); if (error) return (-1); error = ttydevsw_mmap(tp, offset, paddr, nprot, memattr); tty_unlock(tp); return (error); } /* * kqueue support. */ static void tty_kqops_read_detach(struct knote *kn) { struct tty *tp = kn->kn_hook; knlist_remove(&tp->t_inpoll.si_note, kn, 0); } static int tty_kqops_read_event(struct knote *kn, long hint __unused) { struct tty *tp = kn->kn_hook; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tty_gone(tp) || tp->t_flags & TF_ZOMBIE) { kn->kn_flags |= EV_EOF; return (1); } else { kn->kn_data = ttydisc_read_poll(tp); return (kn->kn_data > 0); } } static void tty_kqops_write_detach(struct knote *kn) { struct tty *tp = kn->kn_hook; knlist_remove(&tp->t_outpoll.si_note, kn, 0); } static int tty_kqops_write_event(struct knote *kn, long hint __unused) { struct tty *tp = kn->kn_hook; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tty_gone(tp)) { kn->kn_flags |= EV_EOF; return (1); } else { kn->kn_data = ttydisc_write_poll(tp); return (kn->kn_data > 0); } } static struct filterops tty_kqops_read = { .f_isfd = 1, .f_detach = tty_kqops_read_detach, .f_event = tty_kqops_read_event, }; static struct filterops tty_kqops_write = { .f_isfd = 1, .f_detach = tty_kqops_write_detach, .f_event = tty_kqops_write_event, }; static int ttydev_kqfilter(struct cdev *dev, struct knote *kn) { struct tty *tp = dev->si_drv1; int error; error = ttydev_enter(tp); if (error) return (error); switch (kn->kn_filter) { case EVFILT_READ: kn->kn_hook = tp; kn->kn_fop = &tty_kqops_read; knlist_add(&tp->t_inpoll.si_note, kn, 1); break; case EVFILT_WRITE: kn->kn_hook = tp; kn->kn_fop = &tty_kqops_write; knlist_add(&tp->t_outpoll.si_note, kn, 1); break; default: error = EINVAL; break; } tty_unlock(tp); return (error); } static struct cdevsw ttydev_cdevsw = { .d_version = D_VERSION, .d_open = ttydev_open, .d_close = ttydev_close, .d_read = ttydev_read, .d_write = ttydev_write, .d_ioctl = ttydev_ioctl, .d_kqfilter = ttydev_kqfilter, .d_poll = ttydev_poll, .d_mmap = ttydev_mmap, .d_name = "ttydev", .d_flags = D_TTY, }; /* * Init/lock-state devices */ static int ttyil_open(struct cdev *dev, int oflags __unused, int devtype __unused, struct thread *td) { struct tty *tp; int error; tp = dev->si_drv1; error = 0; tty_lock(tp); if (tty_gone(tp)) error = ENODEV; tty_unlock(tp); return (error); } static int ttyil_close(struct cdev *dev __unused, int flag __unused, int mode __unused, struct thread *td __unused) { return (0); } static int ttyil_rdwr(struct cdev *dev __unused, struct uio *uio __unused, int ioflag __unused) { return (ENODEV); } static int ttyil_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct tty *tp = dev->si_drv1; int error; tty_lock(tp); if (tty_gone(tp)) { error = ENODEV; goto done; } error = ttydevsw_cioctl(tp, dev2unit(dev), cmd, data, td); if (error != ENOIOCTL) goto done; error = 0; switch (cmd) { case TIOCGETA: /* Obtain terminal flags through tcgetattr(). */ *(struct termios*)data = *(struct termios*)dev->si_drv2; break; case TIOCSETA: /* Set terminal flags through tcsetattr(). */ error = priv_check(td, PRIV_TTY_SETA); if (error) break; *(struct termios*)dev->si_drv2 = *(struct termios*)data; break; case TIOCGETD: *(int *)data = TTYDISC; break; case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); break; default: error = ENOTTY; } done: tty_unlock(tp); return (error); } static struct cdevsw ttyil_cdevsw = { .d_version = D_VERSION, .d_open = ttyil_open, .d_close = ttyil_close, .d_read = ttyil_rdwr, .d_write = ttyil_rdwr, .d_ioctl = ttyil_ioctl, .d_name = "ttyil", .d_flags = D_TTY, }; static void tty_init_termios(struct tty *tp) { struct termios *t = &tp->t_termios_init_in; t->c_cflag = TTYDEF_CFLAG; t->c_iflag = TTYDEF_IFLAG; t->c_lflag = TTYDEF_LFLAG; t->c_oflag = TTYDEF_OFLAG; t->c_ispeed = TTYDEF_SPEED; t->c_ospeed = TTYDEF_SPEED; memcpy(&t->c_cc, ttydefchars, sizeof ttydefchars); tp->t_termios_init_out = *t; } void tty_init_console(struct tty *tp, speed_t s) { struct termios *ti = &tp->t_termios_init_in; struct termios *to = &tp->t_termios_init_out; if (s != 0) { ti->c_ispeed = ti->c_ospeed = s; to->c_ispeed = to->c_ospeed = s; } ti->c_cflag |= CLOCAL; to->c_cflag |= CLOCAL; } /* * Standard device routine implementations, mostly meant for * pseudo-terminal device drivers. When a driver creates a new terminal * device class, missing routines are patched. */ static int ttydevsw_defopen(struct tty *tp __unused) { return (0); } static void ttydevsw_defclose(struct tty *tp __unused) { } static void ttydevsw_defoutwakeup(struct tty *tp __unused) { panic("Terminal device has output, while not implemented"); } static void ttydevsw_definwakeup(struct tty *tp __unused) { } static int ttydevsw_defioctl(struct tty *tp __unused, u_long cmd __unused, caddr_t data __unused, struct thread *td __unused) { return (ENOIOCTL); } static int ttydevsw_defcioctl(struct tty *tp __unused, int unit __unused, u_long cmd __unused, caddr_t data __unused, struct thread *td __unused) { return (ENOIOCTL); } static int ttydevsw_defparam(struct tty *tp __unused, struct termios *t) { /* * Allow the baud rate to be adjusted for pseudo-devices, but at * least restrict it to 115200 to prevent excessive buffer * usage. Also disallow 0, to prevent foot shooting. */ if (t->c_ispeed < B50) t->c_ispeed = B50; else if (t->c_ispeed > B115200) t->c_ispeed = B115200; if (t->c_ospeed < B50) t->c_ospeed = B50; else if (t->c_ospeed > B115200) t->c_ospeed = B115200; t->c_cflag |= CREAD; return (0); } static int ttydevsw_defmodem(struct tty *tp __unused, int sigon __unused, int sigoff __unused) { /* Simulate a carrier to make the TTY layer happy. */ return (SER_DCD); } static int ttydevsw_defmmap(struct tty *tp __unused, vm_ooffset_t offset __unused, vm_paddr_t *paddr __unused, int nprot __unused, vm_memattr_t *memattr __unused) { return (-1); } static void ttydevsw_defpktnotify(struct tty *tp __unused, char event __unused) { } static void ttydevsw_deffree(void *softc __unused) { panic("Terminal device freed without a free-handler"); } static bool ttydevsw_defbusy(struct tty *tp __unused) { return (FALSE); } /* * TTY allocation and deallocation. TTY devices can be deallocated when * the driver doesn't use it anymore, when the TTY isn't a session's * controlling TTY and when the device node isn't opened through devfs. */ struct tty * tty_alloc(struct ttydevsw *tsw, void *sc) { return (tty_alloc_mutex(tsw, sc, NULL)); } struct tty * tty_alloc_mutex(struct ttydevsw *tsw, void *sc, struct mtx *mutex) { struct tty *tp; /* Make sure the driver defines all routines. */ #define PATCH_FUNC(x) do { \ if (tsw->tsw_ ## x == NULL) \ tsw->tsw_ ## x = ttydevsw_def ## x; \ } while (0) PATCH_FUNC(open); PATCH_FUNC(close); PATCH_FUNC(outwakeup); PATCH_FUNC(inwakeup); PATCH_FUNC(ioctl); PATCH_FUNC(cioctl); PATCH_FUNC(param); PATCH_FUNC(modem); PATCH_FUNC(mmap); PATCH_FUNC(pktnotify); PATCH_FUNC(free); PATCH_FUNC(busy); #undef PATCH_FUNC tp = malloc(sizeof(struct tty) + TTY_PRBUF_SIZE, M_TTY, M_WAITOK | M_ZERO); tp->t_prbufsz = TTY_PRBUF_SIZE; tp->t_devsw = tsw; tp->t_devswsoftc = sc; tp->t_flags = tsw->tsw_flags; tp->t_drainwait = tty_drainwait; tty_init_termios(tp); cv_init(&tp->t_inwait, "ttyin"); cv_init(&tp->t_outwait, "ttyout"); cv_init(&tp->t_outserwait, "ttyosr"); cv_init(&tp->t_bgwait, "ttybg"); cv_init(&tp->t_dcdwait, "ttydcd"); /* Allow drivers to use a custom mutex to lock the TTY. */ if (mutex != NULL) { tp->t_mtx = mutex; } else { tp->t_mtx = &tp->t_mtxobj; mtx_init(&tp->t_mtxobj, "ttymtx", NULL, MTX_DEF); } knlist_init_mtx(&tp->t_inpoll.si_note, tp->t_mtx); knlist_init_mtx(&tp->t_outpoll.si_note, tp->t_mtx); return (tp); } static void tty_dealloc(void *arg) { struct tty *tp = arg; /* * ttyydev_leave() usually frees the i/o queues earlier, but it is * not always called between queue allocation and here. The queues * may be allocated by ioctls on a pty control device without the * corresponding pty slave device ever being open, or after it is * closed. */ ttyinq_free(&tp->t_inq); ttyoutq_free(&tp->t_outq); seldrain(&tp->t_inpoll); seldrain(&tp->t_outpoll); knlist_destroy(&tp->t_inpoll.si_note); knlist_destroy(&tp->t_outpoll.si_note); cv_destroy(&tp->t_inwait); cv_destroy(&tp->t_outwait); cv_destroy(&tp->t_bgwait); cv_destroy(&tp->t_dcdwait); cv_destroy(&tp->t_outserwait); if (tp->t_mtx == &tp->t_mtxobj) mtx_destroy(&tp->t_mtxobj); ttydevsw_free(tp); free(tp, M_TTY); } static void tty_rel_free(struct tty *tp) { struct cdev *dev; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); #define TF_ACTIVITY (TF_GONE|TF_OPENED|TF_HOOK|TF_OPENCLOSE) if (tp->t_sessioncnt != 0 || (tp->t_flags & TF_ACTIVITY) != TF_GONE) { /* TTY is still in use. */ tty_unlock(tp); return; } /* Stop asynchronous I/O. */ funsetown(&tp->t_sigio); /* TTY can be deallocated. */ dev = tp->t_dev; tp->t_dev = NULL; tty_unlock(tp); if (dev != NULL) { sx_xlock(&tty_list_sx); TAILQ_REMOVE(&tty_list, tp, t_list); tty_list_count--; sx_xunlock(&tty_list_sx); destroy_dev_sched_cb(dev, tty_dealloc, tp); } } void tty_rel_pgrp(struct tty *tp, struct pgrp *pg) { MPASS(tp->t_sessioncnt > 0); - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tp->t_pgrp == pg) tp->t_pgrp = NULL; tty_unlock(tp); } void tty_rel_sess(struct tty *tp, struct session *sess) { MPASS(tp->t_sessioncnt > 0); /* Current session has left. */ if (tp->t_session == sess) { tp->t_session = NULL; MPASS(tp->t_pgrp == NULL); } tp->t_sessioncnt--; tty_rel_free(tp); } void tty_rel_gone(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); /* Simulate carrier removal. */ ttydisc_modem(tp, 0); /* Wake up all blocked threads. */ tty_wakeup(tp, FREAD|FWRITE); cv_broadcast(&tp->t_bgwait); cv_broadcast(&tp->t_dcdwait); tp->t_flags |= TF_GONE; tty_rel_free(tp); } static int tty_drop_ctty(struct tty *tp, struct proc *p) { struct session *session; struct vnode *vp; /* * This looks terrible, but it's generally safe as long as the tty * hasn't gone away while we had the lock dropped. All of our sanity * checking that this operation is OK happens after we've picked it back * up, so other state changes are generally not fatal and the potential * for this particular operation to happen out-of-order in a * multithreaded scenario is likely a non-issue. */ tty_unlock(tp); sx_xlock(&proctree_lock); tty_lock(tp); if (tty_gone(tp)) { sx_xunlock(&proctree_lock); return (ENODEV); } /* * If the session doesn't have a controlling TTY, or if we weren't * invoked on the controlling TTY, we'll return ENOIOCTL as we've * historically done. */ session = p->p_session; if (session->s_ttyp == NULL || session->s_ttyp != tp) { sx_xunlock(&proctree_lock); return (ENOTTY); } if (!SESS_LEADER(p)) { sx_xunlock(&proctree_lock); return (EPERM); } PROC_LOCK(p); SESS_LOCK(session); vp = session->s_ttyvp; session->s_ttyp = NULL; session->s_ttyvp = NULL; session->s_ttydp = NULL; SESS_UNLOCK(session); tp->t_sessioncnt--; p->p_flag &= ~P_CONTROLT; PROC_UNLOCK(p); sx_xunlock(&proctree_lock); /* * If we did have a vnode, release our reference. Ordinarily we manage * these at the devfs layer, but we can't necessarily know that we were * invoked on the vnode referenced in the session (i.e. the vnode we * hold a reference to). We explicitly don't check VBAD/VIRF_DOOMED here * to avoid a vnode leak -- in circumstances elsewhere where we'd hit a * VIRF_DOOMED vnode, release has been deferred until the controlling TTY * is either changed or released. */ if (vp != NULL) vrele(vp); return (0); } /* * Exposing information about current TTY's through sysctl */ static void tty_to_xtty(struct tty *tp, struct xtty *xt) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); xt->xt_size = sizeof(struct xtty); xt->xt_insize = ttyinq_getsize(&tp->t_inq); xt->xt_incc = ttyinq_bytescanonicalized(&tp->t_inq); xt->xt_inlc = ttyinq_bytesline(&tp->t_inq); xt->xt_inlow = tp->t_inlow; xt->xt_outsize = ttyoutq_getsize(&tp->t_outq); xt->xt_outcc = ttyoutq_bytesused(&tp->t_outq); xt->xt_outlow = tp->t_outlow; xt->xt_column = tp->t_column; xt->xt_pgid = tp->t_pgrp ? tp->t_pgrp->pg_id : 0; xt->xt_sid = tp->t_session ? tp->t_session->s_sid : 0; xt->xt_flags = tp->t_flags; xt->xt_dev = tp->t_dev ? dev2udev(tp->t_dev) : (uint32_t)NODEV; } static int sysctl_kern_ttys(SYSCTL_HANDLER_ARGS) { unsigned long lsize; struct xtty *xtlist, *xt; struct tty *tp; int error; sx_slock(&tty_list_sx); lsize = tty_list_count * sizeof(struct xtty); if (lsize == 0) { sx_sunlock(&tty_list_sx); return (0); } xtlist = xt = malloc(lsize, M_TTY, M_WAITOK); TAILQ_FOREACH(tp, &tty_list, t_list) { tty_lock(tp); tty_to_xtty(tp, xt); tty_unlock(tp); xt++; } sx_sunlock(&tty_list_sx); error = SYSCTL_OUT(req, xtlist, lsize); free(xtlist, M_TTY); return (error); } SYSCTL_PROC(_kern, OID_AUTO, ttys, CTLTYPE_OPAQUE|CTLFLAG_RD|CTLFLAG_MPSAFE, 0, 0, sysctl_kern_ttys, "S,xtty", "List of TTYs"); /* * Device node creation. Device has been set up, now we can expose it to * the user. */ int tty_makedevf(struct tty *tp, struct ucred *cred, int flags, const char *fmt, ...) { va_list ap; struct make_dev_args args; struct cdev *dev, *init, *lock, *cua, *cinit, *clock; const char *prefix = "tty"; char name[SPECNAMELEN - 3]; /* for "tty" and "cua". */ uid_t uid; gid_t gid; mode_t mode; int error; /* Remove "tty" prefix from devices like PTY's. */ if (tp->t_flags & TF_NOPREFIX) prefix = ""; va_start(ap, fmt); vsnrprintf(name, sizeof name, 32, fmt, ap); va_end(ap); if (cred == NULL) { /* System device. */ uid = UID_ROOT; gid = GID_WHEEL; mode = S_IRUSR|S_IWUSR; } else { /* User device. */ uid = cred->cr_ruid; gid = GID_TTY; mode = S_IRUSR|S_IWUSR|S_IWGRP; } flags = flags & TTYMK_CLONING ? MAKEDEV_REF : 0; flags |= MAKEDEV_CHECKNAME; /* Master call-in device. */ make_dev_args_init(&args); args.mda_flags = flags; args.mda_devsw = &ttydev_cdevsw; args.mda_cr = cred; args.mda_uid = uid; args.mda_gid = gid; args.mda_mode = mode; args.mda_si_drv1 = tp; error = make_dev_s(&args, &dev, "%s%s", prefix, name); if (error != 0) return (error); tp->t_dev = dev; init = lock = cua = cinit = clock = NULL; /* Slave call-in devices. */ if (tp->t_flags & TF_INITLOCK) { args.mda_devsw = &ttyil_cdevsw; args.mda_unit = TTYUNIT_INIT; args.mda_si_drv1 = tp; args.mda_si_drv2 = &tp->t_termios_init_in; error = make_dev_s(&args, &init, "%s%s.init", prefix, name); if (error != 0) goto fail; dev_depends(dev, init); args.mda_unit = TTYUNIT_LOCK; args.mda_si_drv2 = &tp->t_termios_lock_in; error = make_dev_s(&args, &lock, "%s%s.lock", prefix, name); if (error != 0) goto fail; dev_depends(dev, lock); } /* Call-out devices. */ if (tp->t_flags & TF_CALLOUT) { make_dev_args_init(&args); args.mda_flags = flags; args.mda_devsw = &ttydev_cdevsw; args.mda_cr = cred; args.mda_uid = UID_UUCP; args.mda_gid = GID_DIALER; args.mda_mode = 0660; args.mda_unit = TTYUNIT_CALLOUT; args.mda_si_drv1 = tp; error = make_dev_s(&args, &cua, "cua%s", name); if (error != 0) goto fail; dev_depends(dev, cua); /* Slave call-out devices. */ if (tp->t_flags & TF_INITLOCK) { args.mda_devsw = &ttyil_cdevsw; args.mda_unit = TTYUNIT_CALLOUT | TTYUNIT_INIT; args.mda_si_drv2 = &tp->t_termios_init_out; error = make_dev_s(&args, &cinit, "cua%s.init", name); if (error != 0) goto fail; dev_depends(dev, cinit); args.mda_unit = TTYUNIT_CALLOUT | TTYUNIT_LOCK; args.mda_si_drv2 = &tp->t_termios_lock_out; error = make_dev_s(&args, &clock, "cua%s.lock", name); if (error != 0) goto fail; dev_depends(dev, clock); } } sx_xlock(&tty_list_sx); TAILQ_INSERT_TAIL(&tty_list, tp, t_list); tty_list_count++; sx_xunlock(&tty_list_sx); return (0); fail: destroy_dev(dev); if (init) destroy_dev(init); if (lock) destroy_dev(lock); if (cinit) destroy_dev(cinit); if (clock) destroy_dev(clock); return (error); } /* * Signalling processes. */ void tty_signal_sessleader(struct tty *tp, int sig) { struct proc *p; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(sig >= 1 && sig < NSIG); /* Make signals start output again. */ tp->t_flags &= ~TF_STOPPED; if (tp->t_session != NULL && tp->t_session->s_leader != NULL) { p = tp->t_session->s_leader; PROC_LOCK(p); kern_psignal(p, sig); PROC_UNLOCK(p); } } void tty_signal_pgrp(struct tty *tp, int sig) { ksiginfo_t ksi; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(sig >= 1 && sig < NSIG); /* Make signals start output again. */ tp->t_flags &= ~TF_STOPPED; if (sig == SIGINFO && !(tp->t_termios.c_lflag & NOKERNINFO)) tty_info(tp); if (tp->t_pgrp != NULL) { ksiginfo_init(&ksi); ksi.ksi_signo = sig; ksi.ksi_code = SI_KERNEL; PGRP_LOCK(tp->t_pgrp); pgsignal(tp->t_pgrp, sig, 1, &ksi); PGRP_UNLOCK(tp->t_pgrp); } } void tty_wakeup(struct tty *tp, int flags) { if (tp->t_flags & TF_ASYNC && tp->t_sigio != NULL) pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); if (flags & FWRITE) { cv_broadcast(&tp->t_outwait); selwakeup(&tp->t_outpoll); KNOTE_LOCKED(&tp->t_outpoll.si_note, 0); } if (flags & FREAD) { cv_broadcast(&tp->t_inwait); selwakeup(&tp->t_inpoll); KNOTE_LOCKED(&tp->t_inpoll.si_note, 0); } } int tty_wait(struct tty *tp, struct cv *cv) { int error; int revokecnt = tp->t_revokecnt; tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); MPASS(!tty_gone(tp)); error = cv_wait_sig(cv, tp->t_mtx); /* Bail out when the device slipped away. */ if (tty_gone(tp)) return (ENXIO); /* Restart the system call when we may have been revoked. */ if (tp->t_revokecnt != revokecnt) return (ERESTART); return (error); } int tty_timedwait(struct tty *tp, struct cv *cv, int hz) { int error; int revokecnt = tp->t_revokecnt; tty_lock_assert(tp, MA_OWNED|MA_NOTRECURSED); MPASS(!tty_gone(tp)); error = cv_timedwait_sig(cv, tp->t_mtx, hz); /* Bail out when the device slipped away. */ if (tty_gone(tp)) return (ENXIO); /* Restart the system call when we may have been revoked. */ if (tp->t_revokecnt != revokecnt) return (ERESTART); return (error); } void tty_flush(struct tty *tp, int flags) { if (flags & FWRITE) { tp->t_flags &= ~TF_HIWAT_OUT; ttyoutq_flush(&tp->t_outq); tty_wakeup(tp, FWRITE); if (!tty_gone(tp)) { ttydevsw_outwakeup(tp); ttydevsw_pktnotify(tp, TIOCPKT_FLUSHWRITE); } } if (flags & FREAD) { tty_hiwat_in_unblock(tp); ttyinq_flush(&tp->t_inq); tty_wakeup(tp, FREAD); if (!tty_gone(tp)) { ttydevsw_inwakeup(tp); ttydevsw_pktnotify(tp, TIOCPKT_FLUSHREAD); } } } void tty_set_winsize(struct tty *tp, const struct winsize *wsz) { if (memcmp(&tp->t_winsize, wsz, sizeof(*wsz)) == 0) return; tp->t_winsize = *wsz; tty_signal_pgrp(tp, SIGWINCH); } static int tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, struct thread *td) { int error; switch (cmd) { /* * Modem commands. * The SER_* and TIOCM_* flags are the same, but one bit * shifted. I don't know why. */ case TIOCSDTR: ttydevsw_modem(tp, SER_DTR, 0); return (0); case TIOCCDTR: ttydevsw_modem(tp, 0, SER_DTR); return (0); case TIOCMSET: { int bits = *(int *)data; ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, ((~bits) & (TIOCM_DTR | TIOCM_RTS)) >> 1); return (0); } case TIOCMBIS: { int bits = *(int *)data; ttydevsw_modem(tp, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1, 0); return (0); } case TIOCMBIC: { int bits = *(int *)data; ttydevsw_modem(tp, 0, (bits & (TIOCM_DTR | TIOCM_RTS)) >> 1); return (0); } case TIOCMGET: *(int *)data = TIOCM_LE + (ttydevsw_modem(tp, 0, 0) << 1); return (0); case FIOASYNC: if (*(int *)data) tp->t_flags |= TF_ASYNC; else tp->t_flags &= ~TF_ASYNC; return (0); case FIONBIO: /* This device supports non-blocking operation. */ return (0); case FIONREAD: *(int *)data = ttyinq_bytescanonicalized(&tp->t_inq); return (0); case FIONWRITE: case TIOCOUTQ: *(int *)data = ttyoutq_bytesused(&tp->t_outq); return (0); case FIOSETOWN: if (tp->t_session != NULL && !tty_is_ctty(tp, td->td_proc)) /* Not allowed to set ownership. */ return (ENOTTY); /* Temporarily unlock the TTY to set ownership. */ tty_unlock(tp); error = fsetown(*(int *)data, &tp->t_sigio); tty_lock(tp); return (error); case FIOGETOWN: if (tp->t_session != NULL && !tty_is_ctty(tp, td->td_proc)) /* Not allowed to set ownership. */ return (ENOTTY); /* Get ownership. */ *(int *)data = fgetown(&tp->t_sigio); return (0); case TIOCGETA: /* Obtain terminal flags through tcgetattr(). */ *(struct termios*)data = tp->t_termios; return (0); case TIOCSETA: case TIOCSETAW: case TIOCSETAF: { struct termios *t = data; /* * Who makes up these funny rules? According to POSIX, * input baud rate is set equal to the output baud rate * when zero. */ if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; /* Discard any unsupported bits. */ t->c_iflag &= TTYSUP_IFLAG; t->c_oflag &= TTYSUP_OFLAG; t->c_lflag &= TTYSUP_LFLAG; t->c_cflag &= TTYSUP_CFLAG; /* Set terminal flags through tcsetattr(). */ if (cmd == TIOCSETAW || cmd == TIOCSETAF) { error = tty_drain(tp, 0); if (error) return (error); if (cmd == TIOCSETAF) tty_flush(tp, FREAD); } /* * Only call param() when the flags really change. */ if ((t->c_cflag & CIGNORE) == 0 && (tp->t_termios.c_cflag != t->c_cflag || ((tp->t_termios.c_iflag ^ t->c_iflag) & (IXON|IXOFF|IXANY)) || tp->t_termios.c_ispeed != t->c_ispeed || tp->t_termios.c_ospeed != t->c_ospeed)) { error = ttydevsw_param(tp, t); if (error) return (error); /* XXX: CLOCAL? */ tp->t_termios.c_cflag = t->c_cflag & ~CIGNORE; tp->t_termios.c_ispeed = t->c_ispeed; tp->t_termios.c_ospeed = t->c_ospeed; /* Baud rate has changed - update watermarks. */ error = tty_watermarks(tp); if (error) return (error); } /* Copy new non-device driver parameters. */ tp->t_termios.c_iflag = t->c_iflag; tp->t_termios.c_oflag = t->c_oflag; tp->t_termios.c_lflag = t->c_lflag; memcpy(&tp->t_termios.c_cc, t->c_cc, sizeof t->c_cc); ttydisc_optimize(tp); if ((t->c_lflag & ICANON) == 0) { /* * When in non-canonical mode, wake up all * readers. Canonicalize any partial input. VMIN * and VTIME could also be adjusted. */ ttyinq_canonicalize(&tp->t_inq); tty_wakeup(tp, FREAD); } /* * For packet mode: notify the PTY consumer that VSTOP * and VSTART may have been changed. */ if (tp->t_termios.c_iflag & IXON && tp->t_termios.c_cc[VSTOP] == CTRL('S') && tp->t_termios.c_cc[VSTART] == CTRL('Q')) ttydevsw_pktnotify(tp, TIOCPKT_DOSTOP); else ttydevsw_pktnotify(tp, TIOCPKT_NOSTOP); return (0); } case TIOCGETD: /* For compatibility - we only support TTYDISC. */ *(int *)data = TTYDISC; return (0); case TIOCGPGRP: if (!tty_is_ctty(tp, td->td_proc)) return (ENOTTY); if (tp->t_pgrp != NULL) *(int *)data = tp->t_pgrp->pg_id; else *(int *)data = NO_PID; return (0); case TIOCGSID: if (!tty_is_ctty(tp, td->td_proc)) return (ENOTTY); MPASS(tp->t_session); *(int *)data = tp->t_session->s_sid; return (0); case TIOCNOTTY: return (tty_drop_ctty(tp, td->td_proc)); case TIOCSCTTY: { struct proc *p = td->td_proc; /* XXX: This looks awful. */ tty_unlock(tp); sx_xlock(&proctree_lock); tty_lock(tp); if (!SESS_LEADER(p)) { /* Only the session leader may do this. */ sx_xunlock(&proctree_lock); return (EPERM); } if (tp->t_session != NULL && tp->t_session == p->p_session) { /* This is already our controlling TTY. */ sx_xunlock(&proctree_lock); return (0); } if (p->p_session->s_ttyp != NULL || (tp->t_session != NULL && tp->t_session->s_ttyvp != NULL && tp->t_session->s_ttyvp->v_type != VBAD)) { /* * There is already a relation between a TTY and * a session, or the caller is not the session * leader. * * Allow the TTY to be stolen when the vnode is * invalid, but the reference to the TTY is * still active. This allows immediate reuse of * TTYs of which the session leader has been * killed or the TTY revoked. */ sx_xunlock(&proctree_lock); return (EPERM); } /* Connect the session to the TTY. */ tp->t_session = p->p_session; tp->t_session->s_ttyp = tp; tp->t_sessioncnt++; sx_xunlock(&proctree_lock); /* Assign foreground process group. */ tp->t_pgrp = p->p_pgrp; PROC_LOCK(p); p->p_flag |= P_CONTROLT; PROC_UNLOCK(p); return (0); } case TIOCSPGRP: { struct pgrp *pg; /* * XXX: Temporarily unlock the TTY to locate the process * group. This code would be lot nicer if we would ever * decompose proctree_lock. */ tty_unlock(tp); sx_slock(&proctree_lock); pg = pgfind(*(int *)data); if (pg != NULL) PGRP_UNLOCK(pg); if (pg == NULL || pg->pg_session != td->td_proc->p_session) { sx_sunlock(&proctree_lock); tty_lock(tp); return (EPERM); } tty_lock(tp); /* * Determine if this TTY is the controlling TTY after * relocking the TTY. */ if (!tty_is_ctty(tp, td->td_proc)) { sx_sunlock(&proctree_lock); return (ENOTTY); } tp->t_pgrp = pg; sx_sunlock(&proctree_lock); /* Wake up the background process groups. */ cv_broadcast(&tp->t_bgwait); return (0); } case TIOCFLUSH: { int flags = *(int *)data; if (flags == 0) flags = (FREAD|FWRITE); else flags &= (FREAD|FWRITE); tty_flush(tp, flags); return (0); } case TIOCDRAIN: /* Drain TTY output. */ return tty_drain(tp, 0); case TIOCGDRAINWAIT: *(int *)data = tp->t_drainwait; return (0); case TIOCSDRAINWAIT: error = priv_check(td, PRIV_TTY_DRAINWAIT); if (error == 0) tp->t_drainwait = *(int *)data; return (error); case TIOCCONS: /* Set terminal as console TTY. */ if (*(int *)data) { error = priv_check(td, PRIV_TTY_CONSOLE); if (error) return (error); /* * XXX: constty should really need to be locked! * XXX: allow disconnected constty's to be stolen! */ if (constty == tp) return (0); if (constty != NULL) return (EBUSY); tty_unlock(tp); constty_set(tp); tty_lock(tp); } else if (constty == tp) { constty_clear(); } return (0); case TIOCGWINSZ: /* Obtain window size. */ *(struct winsize*)data = tp->t_winsize; return (0); case TIOCSWINSZ: /* Set window size. */ tty_set_winsize(tp, data); return (0); case TIOCEXCL: tp->t_flags |= TF_EXCLUDE; return (0); case TIOCNXCL: tp->t_flags &= ~TF_EXCLUDE; return (0); case TIOCSTOP: tp->t_flags |= TF_STOPPED; ttydevsw_pktnotify(tp, TIOCPKT_STOP); return (0); case TIOCSTART: tp->t_flags &= ~TF_STOPPED; ttydevsw_outwakeup(tp); ttydevsw_pktnotify(tp, TIOCPKT_START); return (0); case TIOCSTAT: tty_info(tp); return (0); case TIOCSTI: if ((fflag & FREAD) == 0 && priv_check(td, PRIV_TTY_STI)) return (EPERM); if (!tty_is_ctty(tp, td->td_proc) && priv_check(td, PRIV_TTY_STI)) return (EACCES); ttydisc_rint(tp, *(char *)data, 0); ttydisc_rint_done(tp); return (0); } #ifdef COMPAT_43TTY return tty_ioctl_compat(tp, cmd, data, fflag, td); #else /* !COMPAT_43TTY */ return (ENOIOCTL); #endif /* COMPAT_43TTY */ } int tty_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, struct thread *td) { int error; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tty_gone(tp)) return (ENXIO); error = ttydevsw_ioctl(tp, cmd, data, td); if (error == ENOIOCTL) error = tty_generic_ioctl(tp, cmd, data, fflag, td); return (error); } dev_t tty_udev(struct tty *tp) { if (tp->t_dev) return (dev2udev(tp->t_dev)); else return (NODEV); } int tty_checkoutq(struct tty *tp) { /* 256 bytes should be enough to print a log message. */ return (ttyoutq_bytesleft(&tp->t_outq) >= 256); } void tty_hiwat_in_block(struct tty *tp) { if ((tp->t_flags & TF_HIWAT_IN) == 0 && tp->t_termios.c_iflag & IXOFF && tp->t_termios.c_cc[VSTOP] != _POSIX_VDISABLE) { /* * Input flow control. Only enter the high watermark when we * can successfully store the VSTOP character. */ if (ttyoutq_write_nofrag(&tp->t_outq, &tp->t_termios.c_cc[VSTOP], 1) == 0) tp->t_flags |= TF_HIWAT_IN; } else { /* No input flow control. */ tp->t_flags |= TF_HIWAT_IN; } } void tty_hiwat_in_unblock(struct tty *tp) { if (tp->t_flags & TF_HIWAT_IN && tp->t_termios.c_iflag & IXOFF && tp->t_termios.c_cc[VSTART] != _POSIX_VDISABLE) { /* * Input flow control. Only leave the high watermark when we * can successfully store the VSTART character. */ if (ttyoutq_write_nofrag(&tp->t_outq, &tp->t_termios.c_cc[VSTART], 1) == 0) tp->t_flags &= ~TF_HIWAT_IN; } else { /* No input flow control. */ tp->t_flags &= ~TF_HIWAT_IN; } if (!tty_gone(tp)) ttydevsw_inwakeup(tp); } /* * TTY hooks interface. */ static int ttyhook_defrint(struct tty *tp, char c, int flags) { if (ttyhook_rint_bypass(tp, &c, 1) != 1) return (-1); return (0); } int ttyhook_register(struct tty **rtp, struct proc *p, int fd, struct ttyhook *th, void *softc) { struct tty *tp; struct file *fp; struct cdev *dev; struct cdevsw *cdp; struct filedesc *fdp; cap_rights_t rights; int error, ref; /* Validate the file descriptor. */ fdp = p->p_fd; error = fget_unlocked(fdp, fd, cap_rights_init(&rights, CAP_TTYHOOK), &fp); if (error != 0) return (error); if (fp->f_ops == &badfileops) { error = EBADF; goto done1; } /* * Make sure the vnode is bound to a character device. * Unlocked check for the vnode type is ok there, because we * only shall prevent calling devvn_refthread on the file that * never has been opened over a character device. */ if (fp->f_type != DTYPE_VNODE || fp->f_vnode->v_type != VCHR) { error = EINVAL; goto done1; } /* Make sure it is a TTY. */ cdp = devvn_refthread(fp->f_vnode, &dev, &ref); if (cdp == NULL) { error = ENXIO; goto done1; } if (dev != fp->f_data) { error = ENXIO; goto done2; } if (cdp != &ttydev_cdevsw) { error = ENOTTY; goto done2; } tp = dev->si_drv1; /* Try to attach the hook to the TTY. */ error = EBUSY; tty_lock(tp); MPASS((tp->t_hook == NULL) == ((tp->t_flags & TF_HOOK) == 0)); if (tp->t_flags & TF_HOOK) goto done3; tp->t_flags |= TF_HOOK; tp->t_hook = th; tp->t_hooksoftc = softc; *rtp = tp; error = 0; /* Maybe we can switch into bypass mode now. */ ttydisc_optimize(tp); /* Silently convert rint() calls to rint_bypass() when possible. */ if (!ttyhook_hashook(tp, rint) && ttyhook_hashook(tp, rint_bypass)) th->th_rint = ttyhook_defrint; done3: tty_unlock(tp); done2: dev_relthread(dev, ref); done1: fdrop(fp, curthread); return (error); } void ttyhook_unregister(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(tp->t_flags & TF_HOOK); /* Disconnect the hook. */ tp->t_flags &= ~TF_HOOK; tp->t_hook = NULL; /* Maybe we need to leave bypass mode. */ ttydisc_optimize(tp); /* Maybe deallocate the TTY as well. */ 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. When opened, * it determines which TTY to use. When data gets written to it, it * will be logged in the kernel message buffer. */ static struct cdevsw ttyconsdev_cdevsw = { .d_version = D_VERSION, .d_open = ttyconsdev_open, .d_close = ttydev_close, .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 __unused) { dev_console = make_dev_credf(MAKEDEV_ETERNAL, &ttyconsdev_cdevsw, 0, NULL, 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 #include static const struct { int flag; char val; } ttystates[] = { #if 0 { TF_NOPREFIX, 'N' }, #endif { TF_INITLOCK, 'I' }, { TF_CALLOUT, 'C' }, /* Keep these together -> 'Oi' and 'Oo'. */ { TF_OPENED, 'O' }, { TF_OPENED_IN, 'i' }, { TF_OPENED_OUT, 'o' }, { TF_OPENED_CONS, 'c' }, { TF_GONE, 'G' }, { TF_OPENCLOSE, 'B' }, { TF_ASYNC, 'Y' }, { TF_LITERAL, 'L' }, /* Keep these together -> 'Hi' and 'Ho'. */ { TF_HIWAT, 'H' }, { TF_HIWAT_IN, 'i' }, { TF_HIWAT_OUT, 'o' }, { TF_STOPPED, 'S' }, { TF_EXCLUDE, 'X' }, { TF_BYPASS, 'l' }, { TF_ZOMBIE, 'Z' }, { TF_HOOK, 's' }, /* Keep these together -> 'bi' and 'bo'. */ { TF_BUSY, 'b' }, { TF_BUSY_IN, 'i' }, { TF_BUSY_OUT, 'o' }, { 0, '\0'}, }; #define TTY_FLAG_BITS \ "\20\1NOPREFIX\2INITLOCK\3CALLOUT\4OPENED_IN" \ "\5OPENED_OUT\6OPENED_CONS\7GONE\10OPENCLOSE" \ "\11ASYNC\12LITERAL\13HIWAT_IN\14HIWAT_OUT" \ "\15STOPPED\16EXCLUDE\17BYPASS\20ZOMBIE" \ "\21HOOK\22BUSY_IN\23BUSY_OUT" #define DB_PRINTSYM(name, addr) \ db_printf("%s " #name ": ", sep); \ db_printsym((db_addr_t) addr, DB_STGY_ANY); \ db_printf("\n"); static void _db_show_devsw(const char *sep, const struct ttydevsw *tsw) { db_printf("%sdevsw: ", sep); db_printsym((db_addr_t)tsw, DB_STGY_ANY); db_printf(" (%p)\n", tsw); DB_PRINTSYM(open, tsw->tsw_open); DB_PRINTSYM(close, tsw->tsw_close); DB_PRINTSYM(outwakeup, tsw->tsw_outwakeup); DB_PRINTSYM(inwakeup, tsw->tsw_inwakeup); DB_PRINTSYM(ioctl, tsw->tsw_ioctl); DB_PRINTSYM(param, tsw->tsw_param); DB_PRINTSYM(modem, tsw->tsw_modem); DB_PRINTSYM(mmap, tsw->tsw_mmap); DB_PRINTSYM(pktnotify, tsw->tsw_pktnotify); DB_PRINTSYM(free, tsw->tsw_free); } static void _db_show_hooks(const char *sep, const struct ttyhook *th) { db_printf("%shook: ", sep); db_printsym((db_addr_t)th, DB_STGY_ANY); db_printf(" (%p)\n", th); if (th == NULL) return; DB_PRINTSYM(rint, th->th_rint); DB_PRINTSYM(rint_bypass, th->th_rint_bypass); DB_PRINTSYM(rint_done, th->th_rint_done); DB_PRINTSYM(rint_poll, th->th_rint_poll); DB_PRINTSYM(getc_inject, th->th_getc_inject); DB_PRINTSYM(getc_capture, th->th_getc_capture); DB_PRINTSYM(getc_poll, th->th_getc_poll); DB_PRINTSYM(close, th->th_close); } static void _db_show_termios(const char *name, const struct termios *t) { db_printf("%s: iflag 0x%x oflag 0x%x cflag 0x%x " "lflag 0x%x ispeed %u ospeed %u\n", name, t->c_iflag, t->c_oflag, t->c_cflag, t->c_lflag, t->c_ispeed, t->c_ospeed); } /* DDB command to show TTY statistics. */ DB_SHOW_COMMAND(tty, db_show_tty) { struct tty *tp; if (!have_addr) { db_printf("usage: show tty \n"); return; } tp = (struct tty *)addr; db_printf("%p: %s\n", tp, tty_devname(tp)); db_printf("\tmtx: %p\n", tp->t_mtx); db_printf("\tflags: 0x%b\n", tp->t_flags, TTY_FLAG_BITS); db_printf("\trevokecnt: %u\n", tp->t_revokecnt); /* Buffering mechanisms. */ db_printf("\tinq: %p begin %u linestart %u reprint %u end %u " "nblocks %u quota %u\n", &tp->t_inq, tp->t_inq.ti_begin, tp->t_inq.ti_linestart, tp->t_inq.ti_reprint, tp->t_inq.ti_end, tp->t_inq.ti_nblocks, tp->t_inq.ti_quota); db_printf("\toutq: %p begin %u end %u nblocks %u quota %u\n", &tp->t_outq, tp->t_outq.to_begin, tp->t_outq.to_end, tp->t_outq.to_nblocks, tp->t_outq.to_quota); db_printf("\tinlow: %zu\n", tp->t_inlow); db_printf("\toutlow: %zu\n", tp->t_outlow); _db_show_termios("\ttermios", &tp->t_termios); db_printf("\twinsize: row %u col %u xpixel %u ypixel %u\n", tp->t_winsize.ws_row, tp->t_winsize.ws_col, tp->t_winsize.ws_xpixel, tp->t_winsize.ws_ypixel); db_printf("\tcolumn: %u\n", tp->t_column); db_printf("\twritepos: %u\n", tp->t_writepos); db_printf("\tcompatflags: 0x%x\n", tp->t_compatflags); /* Init/lock-state devices. */ _db_show_termios("\ttermios_init_in", &tp->t_termios_init_in); _db_show_termios("\ttermios_init_out", &tp->t_termios_init_out); _db_show_termios("\ttermios_lock_in", &tp->t_termios_lock_in); _db_show_termios("\ttermios_lock_out", &tp->t_termios_lock_out); /* Hooks */ _db_show_devsw("\t", tp->t_devsw); _db_show_hooks("\t", tp->t_hook); /* Process info. */ db_printf("\tpgrp: %p gid %d jobc %d\n", tp->t_pgrp, tp->t_pgrp ? tp->t_pgrp->pg_id : 0, tp->t_pgrp ? tp->t_pgrp->pg_jobc : 0); db_printf("\tsession: %p", tp->t_session); if (tp->t_session != NULL) db_printf(" count %u leader %p tty %p sid %d login %s", tp->t_session->s_count, tp->t_session->s_leader, tp->t_session->s_ttyp, tp->t_session->s_sid, tp->t_session->s_login); db_printf("\n"); db_printf("\tsessioncnt: %u\n", tp->t_sessioncnt); db_printf("\tdevswsoftc: %p\n", tp->t_devswsoftc); db_printf("\thooksoftc: %p\n", tp->t_hooksoftc); db_printf("\tdev: %p\n", tp->t_dev); } /* DDB command to list TTYs. */ DB_SHOW_ALL_COMMAND(ttys, db_show_all_ttys) { struct tty *tp; size_t isiz, osiz; int i, j; /* Make the output look like `pstat -t'. */ db_printf("PTR "); #if defined(__LP64__) db_printf(" "); #endif db_printf(" LINE INQ CAN LIN LOW OUTQ USE LOW " "COL SESS PGID STATE\n"); TAILQ_FOREACH(tp, &tty_list, t_list) { isiz = tp->t_inq.ti_nblocks * TTYINQ_DATASIZE; osiz = tp->t_outq.to_nblocks * TTYOUTQ_DATASIZE; db_printf("%p %10s %5zu %4u %4u %4zu %5zu %4u %4zu %5u %5d " "%5d ", tp, tty_devname(tp), isiz, tp->t_inq.ti_linestart - tp->t_inq.ti_begin, tp->t_inq.ti_end - tp->t_inq.ti_linestart, isiz - tp->t_inlow, osiz, tp->t_outq.to_end - tp->t_outq.to_begin, osiz - tp->t_outlow, MIN(tp->t_column, 99999), tp->t_session ? tp->t_session->s_sid : 0, tp->t_pgrp ? tp->t_pgrp->pg_id : 0); /* Flag bits. */ for (i = j = 0; ttystates[i].flag; i++) if (tp->t_flags & ttystates[i].flag) { db_printf("%c", ttystates[i].val); j++; } if (j == 0) db_printf("-"); db_printf("\n"); } } #endif /* DDB */ Index: head/sys/kern/tty_info.c =================================================================== --- head/sys/kern/tty_info.c (revision 360050) +++ head/sys/kern/tty_info.c (revision 360051) @@ -1,376 +1,376 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1982, 1986, 1990, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Copyright (c) 2002 Networks Associates Technologies, Inc. * All rights reserved. * * Portions of this software were developed for the FreeBSD Project by * ThinkSec AS and NAI Labs, the Security Research Division of Network * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 * ("CBOSS"), as part of the DARPA CHATS research program. * * 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. * 3. 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_stack.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Returns 1 if p2 is "better" than p1 * * The algorithm for picking the "interesting" process is thus: * * 1) Only foreground processes are eligible - implied. * 2) Runnable processes are favored over anything else. The runner * with the highest cpu utilization is picked (p_estcpu). Ties are * broken by picking the highest pid. * 3) The sleeper with the shortest sleep time is next. With ties, * we pick out just "short-term" sleepers (P_SINTR == 0). * 4) Further ties are broken by picking the highest pid. */ #define TESTAB(a, b) ((a)<<1 | (b)) #define ONLYA 2 #define ONLYB 1 #define BOTH 3 static int proc_sum(struct proc *p, fixpt_t *estcpup) { struct thread *td; int estcpu; int val; val = 0; estcpu = 0; FOREACH_THREAD_IN_PROC(p, td) { thread_lock(td); if (TD_ON_RUNQ(td) || TD_IS_RUNNING(td)) val = 1; estcpu += sched_pctcpu(td); thread_unlock(td); } *estcpup = estcpu; return (val); } static int thread_compare(struct thread *td, struct thread *td2) { int runa, runb; int slpa, slpb; fixpt_t esta, estb; if (td == NULL) return (1); /* * Fetch running stats, pctcpu usage, and interruptable flag. */ thread_lock(td); runa = TD_IS_RUNNING(td) | TD_ON_RUNQ(td); slpa = td->td_flags & TDF_SINTR; esta = sched_pctcpu(td); thread_unlock(td); thread_lock(td2); runb = TD_IS_RUNNING(td2) | TD_ON_RUNQ(td2); estb = sched_pctcpu(td2); slpb = td2->td_flags & TDF_SINTR; thread_unlock(td2); /* * see if at least one of them is runnable */ switch (TESTAB(runa, runb)) { case ONLYA: return (0); case ONLYB: return (1); case BOTH: break; } /* * favor one with highest recent cpu utilization */ if (estb > esta) return (1); if (esta > estb) return (0); /* * favor one sleeping in a non-interruptible sleep */ switch (TESTAB(slpa, slpb)) { case ONLYA: return (0); case ONLYB: return (1); case BOTH: break; } return (td < td2); } static int proc_compare(struct proc *p1, struct proc *p2) { int runa, runb; fixpt_t esta, estb; if (p1 == NULL) return (1); /* * Fetch various stats about these processes. After we drop the * lock the information could be stale but the race is unimportant. */ PROC_LOCK(p1); runa = proc_sum(p1, &esta); PROC_UNLOCK(p1); PROC_LOCK(p2); runb = proc_sum(p2, &estb); PROC_UNLOCK(p2); /* * see if at least one of them is runnable */ switch (TESTAB(runa, runb)) { case ONLYA: return (0); case ONLYB: return (1); case BOTH: break; } /* * favor one with highest recent cpu utilization */ if (estb > esta) return (1); if (esta > estb) return (0); /* * weed out zombies */ switch (TESTAB(p1->p_state == PRS_ZOMBIE, p2->p_state == PRS_ZOMBIE)) { case ONLYA: return (1); case ONLYB: return (0); case BOTH: break; } return (p2->p_pid > p1->p_pid); /* tie - return highest pid */ } static int sbuf_tty_drain(void *a, const char *d, int len) { struct tty *tp; int rc; tp = a; if (kdb_active) { cnputsn(d, len); return (len); } if (tp != NULL && !KERNEL_PANICKED()) { rc = tty_putstrn(tp, d, len); if (rc != 0) return (-ENXIO); return (len); } return (-ENXIO); } #ifdef STACK static bool tty_info_kstacks = false; SYSCTL_BOOL(_kern, OID_AUTO, tty_info_kstacks, CTLFLAG_RWTUN, &tty_info_kstacks, 0, "Enable printing kernel stack(9) traces on ^T (tty info)"); #endif /* * Report on state of foreground process group. */ void tty_info(struct tty *tp) { struct timeval rtime, utime, stime; #ifdef STACK struct stack stack; int sterr; #endif struct proc *p, *ppick; struct thread *td, *tdpick; const char *stateprefix, *state; struct sbuf sb; long rss; int load, pctcpu; pid_t pid; char comm[MAXCOMLEN + 1]; struct rusage ru; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tty_checkoutq(tp) == 0) return; (void)sbuf_new(&sb, tp->t_prbuf, tp->t_prbufsz, SBUF_FIXEDLEN); sbuf_set_drain(&sb, sbuf_tty_drain, tp); /* Print load average. */ load = (averunnable.ldavg[0] * 100 + FSCALE / 2) >> FSHIFT; sbuf_printf(&sb, "%sload: %d.%02d ", tp->t_column == 0 ? "" : "\n", load / 100, load % 100); if (tp->t_session == NULL) { sbuf_printf(&sb, "not a controlling terminal\n"); goto out; } if (tp->t_pgrp == NULL) { sbuf_printf(&sb, "no foreground process group\n"); goto out; } PGRP_LOCK(tp->t_pgrp); if (LIST_EMPTY(&tp->t_pgrp->pg_members)) { PGRP_UNLOCK(tp->t_pgrp); sbuf_printf(&sb, "empty foreground process group\n"); goto out; } /* * Pick the most interesting process and copy some of its * state for printing later. This operation could rely on stale * data as we can't hold the proc slock or thread locks over the * whole list. However, we're guaranteed not to reference an exited * thread or proc since we hold the tty locked. */ p = NULL; LIST_FOREACH(ppick, &tp->t_pgrp->pg_members, p_pglist) if (proc_compare(p, ppick)) p = ppick; PROC_LOCK(p); PGRP_UNLOCK(tp->t_pgrp); td = NULL; FOREACH_THREAD_IN_PROC(p, tdpick) if (thread_compare(td, tdpick)) td = tdpick; stateprefix = ""; thread_lock(td); if (TD_IS_RUNNING(td)) state = "running"; else if (TD_ON_RUNQ(td) || TD_CAN_RUN(td)) state = "runnable"; else if (TD_IS_SLEEPING(td)) { /* XXX: If we're sleeping, are we ever not in a queue? */ if (TD_ON_SLEEPQ(td)) state = td->td_wmesg; else state = "sleeping without queue"; } else if (TD_ON_LOCK(td)) { state = td->td_lockname; stateprefix = "*"; } else if (TD_IS_SUSPENDED(td)) state = "suspended"; else if (TD_AWAITING_INTR(td)) state = "intrwait"; else if (p->p_state == PRS_ZOMBIE) state = "zombie"; else state = "unknown"; pctcpu = (sched_pctcpu(td) * 10000 + FSCALE / 2) >> FSHIFT; #ifdef STACK if (tty_info_kstacks) { if (TD_IS_SWAPPED(td)) sterr = ENOENT; else sterr = stack_save_td(&stack, td); } #endif thread_unlock(td); if (p->p_state == PRS_NEW || p->p_state == PRS_ZOMBIE) rss = 0; else rss = pgtok(vmspace_resident_count(p->p_vmspace)); microuptime(&rtime); timevalsub(&rtime, &p->p_stats->p_start); rufetchcalc(p, &ru, &utime, &stime); pid = p->p_pid; strlcpy(comm, p->p_comm, sizeof comm); PROC_UNLOCK(p); /* Print command, pid, state, rtime, utime, stime, %cpu, and rss. */ sbuf_printf(&sb, " cmd: %s %d [%s%s] %ld.%02ldr %ld.%02ldu %ld.%02lds %d%% %ldk\n", comm, pid, stateprefix, state, (long)rtime.tv_sec, rtime.tv_usec / 10000, (long)utime.tv_sec, utime.tv_usec / 10000, (long)stime.tv_sec, stime.tv_usec / 10000, pctcpu / 100, rss); #ifdef STACK if (tty_info_kstacks && sterr == 0) stack_sbuf_print_flags(&sb, &stack, M_NOWAIT); #endif out: sbuf_finish(&sb); sbuf_delete(&sb); } Index: head/sys/kern/tty_ttydisc.c =================================================================== --- head/sys/kern/tty_ttydisc.c (revision 360050) +++ head/sys/kern/tty_ttydisc.c (revision 360051) @@ -1,1277 +1,1277 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * Portions of this software were developed under sponsorship from Snow * B.V., the Netherlands. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include /* * Standard TTYDISC `termios' line discipline. */ /* Statistics. */ static unsigned long tty_nin = 0; SYSCTL_ULONG(_kern, OID_AUTO, tty_nin, CTLFLAG_RD, &tty_nin, 0, "Total amount of bytes received"); static unsigned long tty_nout = 0; SYSCTL_ULONG(_kern, OID_AUTO, tty_nout, CTLFLAG_RD, &tty_nout, 0, "Total amount of bytes transmitted"); /* termios comparison macro's. */ #define CMP_CC(v,c) (tp->t_termios.c_cc[v] != _POSIX_VDISABLE && \ tp->t_termios.c_cc[v] == (c)) #define CMP_FLAG(field,opt) (tp->t_termios.c_ ## field ## flag & (opt)) /* Characters that cannot be modified through c_cc. */ #define CTAB '\t' #define CNL '\n' #define CCR '\r' /* Character is a control character. */ #define CTL_VALID(c) ((c) == 0x7f || (unsigned char)(c) < 0x20) /* Control character should be processed on echo. */ #define CTL_ECHO(c,q) (!(q) && ((c) == CERASE2 || (c) == CTAB || \ (c) == CNL || (c) == CCR)) /* Control character should be printed using ^X notation. */ #define CTL_PRINT(c,q) ((c) == 0x7f || ((unsigned char)(c) < 0x20 && \ ((q) || ((c) != CTAB && (c) != CNL)))) /* Character is whitespace. */ #define CTL_WHITE(c) ((c) == ' ' || (c) == CTAB) /* Character is alphanumeric. */ #define CTL_ALNUM(c) (((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) #define TTY_STACKBUF 256 void ttydisc_open(struct tty *tp) { ttydisc_optimize(tp); } void ttydisc_close(struct tty *tp) { /* Clean up our flags when leaving the discipline. */ tp->t_flags &= ~(TF_STOPPED|TF_HIWAT|TF_ZOMBIE); /* * POSIX states that we must drain output and flush input on * last close. Draining has already been done if possible. */ tty_flush(tp, FREAD | FWRITE); if (ttyhook_hashook(tp, close)) ttyhook_close(tp); } static int ttydisc_read_canonical(struct tty *tp, struct uio *uio, int ioflag) { char breakc[4] = { CNL }; /* enough to hold \n, VEOF and VEOL. */ int error; size_t clen, flen = 0, n = 1; unsigned char lastc = _POSIX_VDISABLE; #define BREAK_ADD(c) do { \ if (tp->t_termios.c_cc[c] != _POSIX_VDISABLE) \ breakc[n++] = tp->t_termios.c_cc[c]; \ } while (0) /* Determine which characters we should trigger on. */ BREAK_ADD(VEOF); BREAK_ADD(VEOL); #undef BREAK_ADD breakc[n] = '\0'; do { error = tty_wait_background(tp, curthread, SIGTTIN); if (error) return (error); /* * Quite a tricky case: unlike the old TTY * implementation, this implementation copies data back * to userspace in large chunks. Unfortunately, we can't * calculate the line length on beforehand if it crosses * ttyinq_block boundaries, because multiple reads could * then make this code read beyond the newline. * * This is why we limit the read to: * - The size the user has requested * - The blocksize (done in tty_inq.c) * - The amount of bytes until the newline * * This causes the line length to be recalculated after * each block has been copied to userspace. This will * cause the TTY layer to return data in chunks using * the blocksize (except the first and last blocks). */ clen = ttyinq_findchar(&tp->t_inq, breakc, uio->uio_resid, &lastc); /* No more data. */ if (clen == 0) { if (tp->t_flags & TF_ZOMBIE) return (0); else if (ioflag & IO_NDELAY) return (EWOULDBLOCK); error = tty_wait(tp, &tp->t_inwait); if (error) return (error); continue; } /* Don't send the EOF char back to userspace. */ if (CMP_CC(VEOF, lastc)) flen = 1; MPASS(flen <= clen); /* Read and throw away the EOF character. */ error = ttyinq_read_uio(&tp->t_inq, tp, uio, clen, flen); if (error) return (error); } while (uio->uio_resid > 0 && lastc == _POSIX_VDISABLE); return (0); } static int ttydisc_read_raw_no_timer(struct tty *tp, struct uio *uio, int ioflag) { size_t vmin = tp->t_termios.c_cc[VMIN]; ssize_t oresid = uio->uio_resid; int error; MPASS(tp->t_termios.c_cc[VTIME] == 0); /* * This routine implements the easy cases of read()s while in * non-canonical mode, namely case B and D, where we don't have * any timers at all. */ for (;;) { error = tty_wait_background(tp, curthread, SIGTTIN); if (error) return (error); error = ttyinq_read_uio(&tp->t_inq, tp, uio, uio->uio_resid, 0); if (error) return (error); if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) return (0); /* We have to wait for more. */ if (tp->t_flags & TF_ZOMBIE) return (0); else if (ioflag & IO_NDELAY) return (EWOULDBLOCK); error = tty_wait(tp, &tp->t_inwait); if (error) return (error); } } static int ttydisc_read_raw_read_timer(struct tty *tp, struct uio *uio, int ioflag, int oresid) { size_t vmin = MAX(tp->t_termios.c_cc[VMIN], 1); unsigned int vtime = tp->t_termios.c_cc[VTIME]; struct timeval end, now, left; int error, hz; MPASS(tp->t_termios.c_cc[VTIME] != 0); /* Determine when the read should be expired. */ end.tv_sec = vtime / 10; end.tv_usec = (vtime % 10) * 100000; getmicrotime(&now); timevaladd(&end, &now); for (;;) { error = tty_wait_background(tp, curthread, SIGTTIN); if (error) return (error); error = ttyinq_read_uio(&tp->t_inq, tp, uio, uio->uio_resid, 0); if (error) return (error); if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) return (0); /* Calculate how long we should wait. */ getmicrotime(&now); if (timevalcmp(&now, &end, >)) return (0); left = end; timevalsub(&left, &now); hz = tvtohz(&left); /* * We have to wait for more. If the timer expires, we * should return a 0-byte read. */ if (tp->t_flags & TF_ZOMBIE) return (0); else if (ioflag & IO_NDELAY) return (EWOULDBLOCK); error = tty_timedwait(tp, &tp->t_inwait, hz); if (error) return (error == EWOULDBLOCK ? 0 : error); } return (0); } static int ttydisc_read_raw_interbyte_timer(struct tty *tp, struct uio *uio, int ioflag) { size_t vmin = tp->t_termios.c_cc[VMIN]; ssize_t oresid = uio->uio_resid; int error; MPASS(tp->t_termios.c_cc[VMIN] != 0); MPASS(tp->t_termios.c_cc[VTIME] != 0); /* * When using the interbyte timer, the timer should be started * after the first byte has been received. We just call into the * generic read timer code after we've received the first byte. */ for (;;) { error = tty_wait_background(tp, curthread, SIGTTIN); if (error) return (error); error = ttyinq_read_uio(&tp->t_inq, tp, uio, uio->uio_resid, 0); if (error) return (error); if (uio->uio_resid == 0 || (oresid - uio->uio_resid) >= vmin) return (0); /* * Not enough data, but we did receive some, which means * we'll now start using the interbyte timer. */ if (oresid != uio->uio_resid) break; /* We have to wait for more. */ if (tp->t_flags & TF_ZOMBIE) return (0); else if (ioflag & IO_NDELAY) return (EWOULDBLOCK); error = tty_wait(tp, &tp->t_inwait); if (error) return (error); } return ttydisc_read_raw_read_timer(tp, uio, ioflag, oresid); } int ttydisc_read(struct tty *tp, struct uio *uio, int ioflag) { int error; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (uio->uio_resid == 0) return (0); if (CMP_FLAG(l, ICANON)) error = ttydisc_read_canonical(tp, uio, ioflag); else if (tp->t_termios.c_cc[VTIME] == 0) error = ttydisc_read_raw_no_timer(tp, uio, ioflag); else if (tp->t_termios.c_cc[VMIN] == 0) error = ttydisc_read_raw_read_timer(tp, uio, ioflag, uio->uio_resid); else error = ttydisc_read_raw_interbyte_timer(tp, uio, ioflag); if (ttyinq_bytesleft(&tp->t_inq) >= tp->t_inlow || ttyinq_bytescanonicalized(&tp->t_inq) == 0) { /* Unset the input watermark when we've got enough space. */ tty_hiwat_in_unblock(tp); } return (error); } static __inline unsigned int ttydisc_findchar(const char *obstart, unsigned int oblen) { const char *c = obstart; while (oblen--) { if (CTL_VALID(*c)) break; c++; } return (c - obstart); } static int ttydisc_write_oproc(struct tty *tp, char c) { unsigned int scnt, error; MPASS(CMP_FLAG(o, OPOST)); MPASS(CTL_VALID(c)); #define PRINT_NORMAL() ttyoutq_write_nofrag(&tp->t_outq, &c, 1) switch (c) { case CEOF: /* End-of-text dropping. */ if (CMP_FLAG(o, ONOEOT)) return (0); return PRINT_NORMAL(); case CERASE2: /* Handle backspace to fix tab expansion. */ if (PRINT_NORMAL() != 0) return (-1); if (tp->t_column > 0) tp->t_column--; return (0); case CTAB: /* Tab expansion. */ scnt = 8 - (tp->t_column & 7); if (CMP_FLAG(o, TAB3)) { error = ttyoutq_write_nofrag(&tp->t_outq, " ", scnt); } else { error = PRINT_NORMAL(); } if (error) return (-1); tp->t_column += scnt; MPASS((tp->t_column % 8) == 0); return (0); case CNL: /* Newline conversion. */ if (CMP_FLAG(o, ONLCR)) { /* Convert \n to \r\n. */ error = ttyoutq_write_nofrag(&tp->t_outq, "\r\n", 2); } else { error = PRINT_NORMAL(); } if (error) return (-1); if (CMP_FLAG(o, ONLCR|ONLRET)) { tp->t_column = tp->t_writepos = 0; ttyinq_reprintpos_set(&tp->t_inq); } return (0); case CCR: /* Carriage return to newline conversion. */ if (CMP_FLAG(o, OCRNL)) c = CNL; /* Omit carriage returns on column 0. */ if (CMP_FLAG(o, ONOCR) && tp->t_column == 0) return (0); if (PRINT_NORMAL() != 0) return (-1); tp->t_column = tp->t_writepos = 0; ttyinq_reprintpos_set(&tp->t_inq); return (0); } /* * Invisible control character. Print it, but don't * increase the column count. */ return PRINT_NORMAL(); #undef PRINT_NORMAL } /* * Just like the old TTY implementation, we need to copy data in chunks * into a temporary buffer. One of the reasons why we need to do this, * is because output processing (only TAB3 though) may allow the buffer * to grow eight times. */ int ttydisc_write(struct tty *tp, struct uio *uio, int ioflag) { char ob[TTY_STACKBUF]; char *obstart; int error = 0; unsigned int oblen = 0; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tp->t_flags & TF_ZOMBIE) return (EIO); /* * We don't need to check whether the process is the foreground * process group or if we have a carrier. This is already done * in ttydev_write(). */ while (uio->uio_resid > 0) { unsigned int nlen; MPASS(oblen == 0); /* Step 1: read data. */ obstart = ob; nlen = MIN(uio->uio_resid, sizeof ob); tty_unlock(tp); error = uiomove(ob, nlen, uio); tty_lock(tp); if (error != 0) break; oblen = nlen; if (tty_gone(tp)) { error = ENXIO; break; } MPASS(oblen > 0); /* Step 2: process data. */ do { unsigned int plen, wlen; /* Search for special characters for post processing. */ if (CMP_FLAG(o, OPOST)) { plen = ttydisc_findchar(obstart, oblen); } else { plen = oblen; } if (plen == 0) { /* * We're going to process a character * that needs processing */ if (ttydisc_write_oproc(tp, *obstart) == 0) { obstart++; oblen--; tp->t_writepos = tp->t_column; ttyinq_reprintpos_set(&tp->t_inq); continue; } } else { /* We're going to write regular data. */ wlen = ttyoutq_write(&tp->t_outq, obstart, plen); obstart += wlen; oblen -= wlen; tp->t_column += wlen; tp->t_writepos = tp->t_column; ttyinq_reprintpos_set(&tp->t_inq); if (wlen == plen) continue; } /* Watermark reached. Try to sleep. */ tp->t_flags |= TF_HIWAT_OUT; if (ioflag & IO_NDELAY) { error = EWOULDBLOCK; goto done; } /* * The driver may write back the data * synchronously. Be sure to check the high * water mark before going to sleep. */ ttydevsw_outwakeup(tp); if ((tp->t_flags & TF_HIWAT_OUT) == 0) continue; error = tty_wait(tp, &tp->t_outwait); if (error) goto done; if (tp->t_flags & TF_ZOMBIE) { error = EIO; goto done; } } while (oblen > 0); } done: if (!tty_gone(tp)) ttydevsw_outwakeup(tp); /* * Add the amount of bytes that we didn't process back to the * uio counters. We need to do this to make sure write() doesn't * count the bytes we didn't store in the queue. */ uio->uio_resid += oblen; return (error); } void ttydisc_optimize(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (ttyhook_hashook(tp, rint_bypass)) { tp->t_flags |= TF_BYPASS; } else if (ttyhook_hashook(tp, rint)) { tp->t_flags &= ~TF_BYPASS; } else if (!CMP_FLAG(i, ICRNL|IGNCR|IMAXBEL|INLCR|ISTRIP|IXON) && (!CMP_FLAG(i, BRKINT) || CMP_FLAG(i, IGNBRK)) && (!CMP_FLAG(i, PARMRK) || CMP_FLAG(i, IGNPAR|IGNBRK) == (IGNPAR|IGNBRK)) && !CMP_FLAG(l, ECHO|ICANON|IEXTEN|ISIG|PENDIN)) { tp->t_flags |= TF_BYPASS; } else { tp->t_flags &= ~TF_BYPASS; } } void ttydisc_modem(struct tty *tp, int open) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (open) cv_broadcast(&tp->t_dcdwait); /* * Ignore modem status lines when CLOCAL is turned on, but don't * enter the zombie state when the TTY isn't opened, because * that would cause the TTY to be in zombie state after being * opened. */ if (!tty_opened(tp) || CMP_FLAG(c, CLOCAL)) return; if (open == 0) { /* * Lost carrier. */ tp->t_flags |= TF_ZOMBIE; tty_signal_sessleader(tp, SIGHUP); tty_flush(tp, FREAD|FWRITE); } else { /* * Carrier is back again. */ /* XXX: what should we do here? */ } } static int ttydisc_echo_force(struct tty *tp, char c, int quote) { if (CMP_FLAG(o, OPOST) && CTL_ECHO(c, quote)) { /* * Only perform postprocessing when OPOST is turned on * and the character is an unquoted BS/TB/NL/CR. */ return ttydisc_write_oproc(tp, c); } else if (CMP_FLAG(l, ECHOCTL) && CTL_PRINT(c, quote)) { /* * Only use ^X notation when ECHOCTL is turned on and * we've got an quoted control character. * * Print backspaces when echoing an end-of-file. */ char ob[4] = "^?\b\b"; /* Print ^X notation. */ if (c != 0x7f) ob[1] = c + 'A' - 1; if (!quote && CMP_CC(VEOF, c)) { return ttyoutq_write_nofrag(&tp->t_outq, ob, 4); } else { tp->t_column += 2; return ttyoutq_write_nofrag(&tp->t_outq, ob, 2); } } else { /* Can just be printed. */ tp->t_column++; return ttyoutq_write_nofrag(&tp->t_outq, &c, 1); } } static int ttydisc_echo(struct tty *tp, char c, int quote) { /* * Only echo characters when ECHO is turned on, or ECHONL when * the character is an unquoted newline. */ if (!CMP_FLAG(l, ECHO) && (!CMP_FLAG(l, ECHONL) || c != CNL || quote)) return (0); return ttydisc_echo_force(tp, c, quote); } static void ttydisc_reprint_char(void *d, char c, int quote) { struct tty *tp = d; ttydisc_echo(tp, c, quote); } static void ttydisc_reprint(struct tty *tp) { cc_t c; /* Print ^R\n, followed by the line. */ c = tp->t_termios.c_cc[VREPRINT]; if (c != _POSIX_VDISABLE) ttydisc_echo(tp, c, 0); ttydisc_echo(tp, CNL, 0); ttyinq_reprintpos_reset(&tp->t_inq); ttyinq_line_iterate_from_linestart(&tp->t_inq, ttydisc_reprint_char, tp); } struct ttydisc_recalc_length { struct tty *tp; unsigned int curlen; }; static void ttydisc_recalc_charlength(void *d, char c, int quote) { struct ttydisc_recalc_length *data = d; struct tty *tp = data->tp; if (CTL_PRINT(c, quote)) { if (CMP_FLAG(l, ECHOCTL)) data->curlen += 2; } else if (c == CTAB) { data->curlen += 8 - (data->curlen & 7); } else { data->curlen++; } } static unsigned int ttydisc_recalc_linelength(struct tty *tp) { struct ttydisc_recalc_length data = { tp, tp->t_writepos }; ttyinq_line_iterate_from_reprintpos(&tp->t_inq, ttydisc_recalc_charlength, &data); return (data.curlen); } static int ttydisc_rubchar(struct tty *tp) { char c; int quote; unsigned int prevpos, tablen; if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) return (-1); ttyinq_unputchar(&tp->t_inq); if (CMP_FLAG(l, ECHO)) { /* * Remove the character from the screen. This is even * safe for characters that span multiple characters * (tabs, quoted, etc). */ if (tp->t_writepos >= tp->t_column) { /* Retype the sentence. */ ttydisc_reprint(tp); } else if (CMP_FLAG(l, ECHOE)) { if (CTL_PRINT(c, quote)) { /* Remove ^X formatted chars. */ if (CMP_FLAG(l, ECHOCTL)) { tp->t_column -= 2; ttyoutq_write_nofrag(&tp->t_outq, "\b\b \b\b", 6); } } else if (c == ' ') { /* Space character needs no rubbing. */ tp->t_column -= 1; ttyoutq_write_nofrag(&tp->t_outq, "\b", 1); } else if (c == CTAB) { /* * Making backspace work with tabs is * quite hard. Recalculate the length of * this character and remove it. * * Because terminal settings could be * changed while the line is being * inserted, the calculations don't have * to be correct. Make sure we keep the * tab length within proper bounds. */ prevpos = ttydisc_recalc_linelength(tp); if (prevpos >= tp->t_column) tablen = 1; else tablen = tp->t_column - prevpos; if (tablen > 8) tablen = 8; tp->t_column = prevpos; ttyoutq_write_nofrag(&tp->t_outq, "\b\b\b\b\b\b\b\b", tablen); return (0); } else { /* * Remove a regular character by * punching a space over it. */ tp->t_column -= 1; ttyoutq_write_nofrag(&tp->t_outq, "\b \b", 3); } } else { /* Don't print spaces. */ ttydisc_echo(tp, tp->t_termios.c_cc[VERASE], 0); } } return (0); } static void ttydisc_rubword(struct tty *tp) { char c; int quote, alnum; /* Strip whitespace first. */ for (;;) { if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) return; if (!CTL_WHITE(c)) break; ttydisc_rubchar(tp); } /* * Record whether the last character from the previous iteration * was alphanumeric or not. We need this to implement ALTWERASE. */ alnum = CTL_ALNUM(c); for (;;) { ttydisc_rubchar(tp); if (ttyinq_peekchar(&tp->t_inq, &c, "e) != 0) return; if (CTL_WHITE(c)) return; if (CMP_FLAG(l, ALTWERASE) && CTL_ALNUM(c) != alnum) return; } } int ttydisc_rint(struct tty *tp, char c, int flags) { int signal, quote = 0; char ob[3] = { 0xff, 0x00 }; size_t ol; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); atomic_add_long(&tty_nin, 1); if (ttyhook_hashook(tp, rint)) return ttyhook_rint(tp, c, flags); if (tp->t_flags & TF_BYPASS) goto processed; if (flags) { if (flags & TRE_BREAK) { if (CMP_FLAG(i, IGNBRK)) { /* Ignore break characters. */ return (0); } else if (CMP_FLAG(i, BRKINT)) { /* Generate SIGINT on break. */ tty_flush(tp, FREAD|FWRITE); tty_signal_pgrp(tp, SIGINT); return (0); } else { /* Just print it. */ goto parmrk; } } else if (flags & TRE_FRAMING || (flags & TRE_PARITY && CMP_FLAG(i, INPCK))) { if (CMP_FLAG(i, IGNPAR)) { /* Ignore bad characters. */ return (0); } else { /* Just print it. */ goto parmrk; } } } /* Allow any character to perform a wakeup. */ if (CMP_FLAG(i, IXANY)) tp->t_flags &= ~TF_STOPPED; /* Remove the top bit. */ if (CMP_FLAG(i, ISTRIP)) c &= ~0x80; /* Skip input processing when we want to print it literally. */ if (tp->t_flags & TF_LITERAL) { tp->t_flags &= ~TF_LITERAL; quote = 1; goto processed; } /* Special control characters that are implementation dependent. */ if (CMP_FLAG(l, IEXTEN)) { /* Accept the next character as literal. */ if (CMP_CC(VLNEXT, c)) { if (CMP_FLAG(l, ECHO)) { if (CMP_FLAG(l, ECHOE)) ttyoutq_write_nofrag(&tp->t_outq, "^\b", 2); else ttydisc_echo(tp, c, 0); } tp->t_flags |= TF_LITERAL; return (0); } } /* * Handle signal processing. */ if (CMP_FLAG(l, ISIG)) { if (CMP_FLAG(l, ICANON|IEXTEN) == (ICANON|IEXTEN)) { if (CMP_CC(VSTATUS, c)) { tty_signal_pgrp(tp, SIGINFO); return (0); } } /* * When compared to the old implementation, this * implementation also flushes the output queue. POSIX * is really brief about this, but does makes us assume * we have to do so. */ signal = 0; if (CMP_CC(VINTR, c)) { signal = SIGINT; } else if (CMP_CC(VQUIT, c)) { signal = SIGQUIT; } else if (CMP_CC(VSUSP, c)) { signal = SIGTSTP; } if (signal != 0) { /* * Echo the character before signalling the * processes. */ if (!CMP_FLAG(l, NOFLSH)) tty_flush(tp, FREAD|FWRITE); ttydisc_echo(tp, c, 0); tty_signal_pgrp(tp, signal); return (0); } } /* * Handle start/stop characters. */ if (CMP_FLAG(i, IXON)) { if (CMP_CC(VSTOP, c)) { /* Stop it if we aren't stopped yet. */ if ((tp->t_flags & TF_STOPPED) == 0) { tp->t_flags |= TF_STOPPED; return (0); } /* * Fallthrough: * When VSTART == VSTOP, we should make this key * toggle it. */ if (!CMP_CC(VSTART, c)) return (0); } if (CMP_CC(VSTART, c)) { tp->t_flags &= ~TF_STOPPED; return (0); } } /* Conversion of CR and NL. */ switch (c) { case CCR: if (CMP_FLAG(i, IGNCR)) return (0); if (CMP_FLAG(i, ICRNL)) c = CNL; break; case CNL: if (CMP_FLAG(i, INLCR)) c = CCR; break; } /* Canonical line editing. */ if (CMP_FLAG(l, ICANON)) { if (CMP_CC(VERASE, c) || CMP_CC(VERASE2, c)) { ttydisc_rubchar(tp); return (0); } else if (CMP_CC(VKILL, c)) { while (ttydisc_rubchar(tp) == 0); return (0); } else if (CMP_FLAG(l, IEXTEN)) { if (CMP_CC(VWERASE, c)) { ttydisc_rubword(tp); return (0); } else if (CMP_CC(VREPRINT, c)) { ttydisc_reprint(tp); return (0); } } } processed: if (CMP_FLAG(i, PARMRK) && (unsigned char)c == 0xff) { /* Print 0xff 0xff. */ ob[1] = 0xff; ol = 2; quote = 1; } else { ob[0] = c; ol = 1; } goto print; parmrk: if (CMP_FLAG(i, PARMRK)) { /* Prepend 0xff 0x00 0x.. */ ob[2] = c; ol = 3; quote = 1; } else { ob[0] = c; ol = 1; } print: /* See if we can store this on the input queue. */ if (ttyinq_write_nofrag(&tp->t_inq, ob, ol, quote) != 0) { if (CMP_FLAG(i, IMAXBEL)) ttyoutq_write_nofrag(&tp->t_outq, "\a", 1); /* * Prevent a deadlock here. It may be possible that a * user has entered so much data, there is no data * available to read(), but the buffers are full anyway. * * Only enter the high watermark if the device driver * can actually transmit something. */ if (ttyinq_bytescanonicalized(&tp->t_inq) == 0) return (0); tty_hiwat_in_block(tp); return (-1); } /* * In raw mode, we canonicalize after receiving a single * character. Otherwise, we canonicalize when we receive a * newline, VEOL or VEOF, but only when it isn't quoted. */ if (!CMP_FLAG(l, ICANON) || (!quote && (c == CNL || CMP_CC(VEOL, c) || CMP_CC(VEOF, c)))) { ttyinq_canonicalize(&tp->t_inq); } ttydisc_echo(tp, c, quote); return (0); } size_t ttydisc_rint_simple(struct tty *tp, const void *buf, size_t len) { const char *cbuf; if (ttydisc_can_bypass(tp)) return (ttydisc_rint_bypass(tp, buf, len)); for (cbuf = buf; len-- > 0; cbuf++) { if (ttydisc_rint(tp, *cbuf, 0) != 0) break; } return (cbuf - (const char *)buf); } size_t ttydisc_rint_bypass(struct tty *tp, const void *buf, size_t len) { size_t ret; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(tp->t_flags & TF_BYPASS); atomic_add_long(&tty_nin, len); if (ttyhook_hashook(tp, rint_bypass)) return ttyhook_rint_bypass(tp, buf, len); ret = ttyinq_write(&tp->t_inq, buf, len, 0); ttyinq_canonicalize(&tp->t_inq); if (ret < len) tty_hiwat_in_block(tp); return (ret); } void ttydisc_rint_done(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (ttyhook_hashook(tp, rint_done)) ttyhook_rint_done(tp); /* Wake up readers. */ tty_wakeup(tp, FREAD); /* Wake up driver for echo. */ ttydevsw_outwakeup(tp); } size_t ttydisc_rint_poll(struct tty *tp) { size_t l; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (ttyhook_hashook(tp, rint_poll)) return ttyhook_rint_poll(tp); /* * XXX: Still allow character input when there's no space in the * buffers, but we haven't entered the high watermark. This is * to allow backspace characters to be inserted when in * canonical mode. */ l = ttyinq_bytesleft(&tp->t_inq); if (l == 0 && (tp->t_flags & TF_HIWAT_IN) == 0) return (1); return (l); } static void ttydisc_wakeup_watermark(struct tty *tp) { size_t c; c = ttyoutq_bytesleft(&tp->t_outq); if (tp->t_flags & TF_HIWAT_OUT) { /* Only allow us to run when we're below the watermark. */ if (c < tp->t_outlow) return; /* Reset the watermark. */ tp->t_flags &= ~TF_HIWAT_OUT; } else { /* Only run when we have data at all. */ if (c == 0) return; } tty_wakeup(tp, FWRITE); } size_t ttydisc_getc(struct tty *tp, void *buf, size_t len) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tp->t_flags & TF_STOPPED) return (0); if (ttyhook_hashook(tp, getc_inject)) return ttyhook_getc_inject(tp, buf, len); len = ttyoutq_read(&tp->t_outq, buf, len); if (ttyhook_hashook(tp, getc_capture)) ttyhook_getc_capture(tp, buf, len); ttydisc_wakeup_watermark(tp); atomic_add_long(&tty_nout, len); return (len); } int ttydisc_getc_uio(struct tty *tp, struct uio *uio) { int error = 0; ssize_t obytes = uio->uio_resid; size_t len; char buf[TTY_STACKBUF]; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tp->t_flags & TF_STOPPED) return (0); /* * When a TTY hook is attached, we cannot perform unbuffered * copying to userspace. Just call ttydisc_getc() and * temporarily store data in a shadow buffer. */ if (ttyhook_hashook(tp, getc_capture) || ttyhook_hashook(tp, getc_inject)) { while (uio->uio_resid > 0) { /* Read to shadow buffer. */ len = ttydisc_getc(tp, buf, MIN(uio->uio_resid, sizeof buf)); if (len == 0) break; /* Copy to userspace. */ tty_unlock(tp); error = uiomove(buf, len, uio); tty_lock(tp); if (error != 0) break; } } else { error = ttyoutq_read_uio(&tp->t_outq, tp, uio); ttydisc_wakeup_watermark(tp); atomic_add_long(&tty_nout, obytes - uio->uio_resid); } return (error); } size_t ttydisc_getc_poll(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tp->t_flags & TF_STOPPED) return (0); if (ttyhook_hashook(tp, getc_poll)) return ttyhook_getc_poll(tp); return ttyoutq_bytesused(&tp->t_outq); } /* * XXX: not really related to the TTYDISC, but we'd better put * tty_putchar() here, because we need to perform proper output * processing. */ int tty_putstrn(struct tty *tp, const char *p, size_t n) { size_t i; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (tty_gone(tp)) return (-1); for (i = 0; i < n; i++) ttydisc_echo_force(tp, p[i], 0); tp->t_writepos = tp->t_column; ttyinq_reprintpos_set(&tp->t_inq); ttydevsw_outwakeup(tp); return (0); } int tty_putchar(struct tty *tp, char c) { return (tty_putstrn(tp, &c, 1)); } Index: head/sys/netgraph/ng_tty.c =================================================================== --- head/sys/netgraph/ng_tty.c (revision 360050) +++ head/sys/netgraph/ng_tty.c (revision 360051) @@ -1,510 +1,510 @@ /* * ng_tty.c */ /*- * Copyright (c) 1996-1999 Whistle Communications, Inc. * All rights reserved. * * Subject to the following obligations and disclaimer of warranty, use and * redistribution of this software, in source or object code forms, with or * without modifications are expressly permitted by Whistle Communications; * provided, however, that: * 1. Any and all reproductions of the source or object code must include the * copyright notice above and the following disclaimer of warranties; and * 2. No rights are granted, in any manner or form, to use Whistle * Communications, Inc. trademarks, including the mark "WHISTLE * COMMUNICATIONS" on advertising, endorsements, or otherwise except as * such appears in the above copyright notice or in the software. * * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * Author: Archie Cobbs * * Updated by Andrew Thompson for MPSAFE TTY. * * $FreeBSD$ * $Whistle: ng_tty.c,v 1.21 1999/11/01 09:24:52 julian Exp $ */ /* * This file implements TTY hooks to link in to the netgraph system. The node * is created and then passed the callers opened TTY file descriptor number to * NGM_TTY_SET_TTY, this will hook the tty via ttyhook_register(). * * Incoming data is delivered directly to ng_tty via the TTY bypass hook as a * buffer pointer and length, this is converted to a mbuf and passed to the * peer. * * If the TTY device does not support bypass then incoming characters are * delivered to the hook one at a time, each in its own mbuf. You may * optionally define a ``hotchar,'' which causes incoming characters to be * buffered up until either the hotchar is seen or the mbuf is full (MHLEN * bytes). Then all buffered characters are immediately delivered. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Per-node private info */ struct ngt_softc { struct tty *tp; /* Terminal device */ node_p node; /* Netgraph node */ hook_p hook; /* Netgraph hook */ struct ifqueue outq; /* Queue of outgoing data */ size_t outqlen; /* Number of bytes in outq */ struct mbuf *m; /* Incoming non-bypass data buffer */ short hotchar; /* Hotchar, or -1 if none */ u_int flags; /* Flags */ }; typedef struct ngt_softc *sc_p; /* Flags */ #define FLG_DEBUG 0x0002 /* Netgraph methods */ static ng_constructor_t ngt_constructor; static ng_rcvmsg_t ngt_rcvmsg; static ng_shutdown_t ngt_shutdown; static ng_newhook_t ngt_newhook; static ng_connect_t ngt_connect; static ng_rcvdata_t ngt_rcvdata; static ng_disconnect_t ngt_disconnect; #define ERROUT(x) do { error = (x); goto done; } while (0) static th_getc_inject_t ngt_getc_inject; static th_getc_poll_t ngt_getc_poll; static th_rint_t ngt_rint; static th_rint_bypass_t ngt_rint_bypass; static th_rint_poll_t ngt_rint_poll; static struct ttyhook ngt_hook = { .th_getc_inject = ngt_getc_inject, .th_getc_poll = ngt_getc_poll, .th_rint = ngt_rint, .th_rint_bypass = ngt_rint_bypass, .th_rint_poll = ngt_rint_poll, }; /* Netgraph node type descriptor */ static struct ng_type typestruct = { .version = NG_ABI_VERSION, .name = NG_TTY_NODE_TYPE, .constructor = ngt_constructor, .rcvmsg = ngt_rcvmsg, .shutdown = ngt_shutdown, .newhook = ngt_newhook, .connect = ngt_connect, .rcvdata = ngt_rcvdata, .disconnect = ngt_disconnect, }; NETGRAPH_INIT(tty, &typestruct); #define NGTLOCK(sc) IF_LOCK(&sc->outq) #define NGTUNLOCK(sc) IF_UNLOCK(&sc->outq) /****************************************************************** NETGRAPH NODE METHODS ******************************************************************/ /* * Initialize a new node of this type. * * We only allow nodes to be created as a result of setting * the line discipline on a tty, so always return an error if not. */ static int ngt_constructor(node_p node) { sc_p sc; /* Allocate private structure */ sc = malloc(sizeof(*sc), M_NETGRAPH, M_WAITOK | M_ZERO); NG_NODE_SET_PRIVATE(node, sc); sc->node = node; mtx_init(&sc->outq.ifq_mtx, "ng_tty node+queue", NULL, MTX_DEF); IFQ_SET_MAXLEN(&sc->outq, ifqmaxlen); return (0); } /* * Add a new hook. There can only be one. */ static int ngt_newhook(node_p node, hook_p hook, const char *name) { const sc_p sc = NG_NODE_PRIVATE(node); if (strcmp(name, NG_TTY_HOOK)) return (EINVAL); if (sc->hook) return (EISCONN); NGTLOCK(sc); sc->hook = hook; NGTUNLOCK(sc); return (0); } /* * Set the hook into queueing mode (for outgoing packets), * so that we wont deliver mbuf through the whole graph holding * tty locks. */ static int ngt_connect(hook_p hook) { NG_HOOK_FORCE_QUEUE(hook); return (0); } /* * Disconnect the hook */ static int ngt_disconnect(hook_p hook) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); if (hook != sc->hook) panic("%s", __func__); NGTLOCK(sc); sc->hook = NULL; NGTUNLOCK(sc); return (0); } /* * Remove this node. The does the netgraph portion of the shutdown. */ static int ngt_shutdown(node_p node) { const sc_p sc = NG_NODE_PRIVATE(node); struct tty *tp; tp = sc->tp; if (tp != NULL) { tty_lock(tp); ttyhook_unregister(tp); } /* Free resources */ IF_DRAIN(&sc->outq); mtx_destroy(&(sc)->outq.ifq_mtx); NG_NODE_UNREF(sc->node); free(sc, M_NETGRAPH); return (0); } /* * Receive control message */ static int ngt_rcvmsg(node_p node, item_p item, hook_p lasthook) { struct proc *p; const sc_p sc = NG_NODE_PRIVATE(node); struct ng_mesg *msg, *resp = NULL; int error = 0; NGI_GET_MSG(item, msg); switch (msg->header.typecookie) { case NGM_TTY_COOKIE: switch (msg->header.cmd) { case NGM_TTY_SET_TTY: if (sc->tp != NULL) return (EBUSY); p = pfind(((int *)msg->data)[0]); if (p == NULL || (p->p_flag & P_WEXIT)) return (ESRCH); _PHOLD(p); PROC_UNLOCK(p); error = ttyhook_register(&sc->tp, p, ((int *)msg->data)[1], &ngt_hook, sc); PRELE(p); if (error != 0) return (error); break; case NGM_TTY_SET_HOTCHAR: { int hotchar; if (msg->header.arglen != sizeof(int)) ERROUT(EINVAL); hotchar = *((int *) msg->data); if (hotchar != (u_char) hotchar && hotchar != -1) ERROUT(EINVAL); sc->hotchar = hotchar; /* race condition is OK */ break; } case NGM_TTY_GET_HOTCHAR: NG_MKRESPONSE(resp, msg, sizeof(int), M_NOWAIT); if (!resp) ERROUT(ENOMEM); /* Race condition here is OK */ *((int *) resp->data) = sc->hotchar; break; default: ERROUT(EINVAL); } break; default: ERROUT(EINVAL); } done: NG_RESPOND_MSG(error, node, item, resp); NG_FREE_MSG(msg); return (error); } /* * Receive incoming data from netgraph system. Put it on our * output queue and start output if necessary. */ static int ngt_rcvdata(hook_p hook, item_p item) { const sc_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); struct tty *tp = sc->tp; struct mbuf *m; if (hook != sc->hook) panic("%s", __func__); NGI_GET_M(item, m); NG_FREE_ITEM(item); if (tp == NULL) { NG_FREE_M(m); return (ENXIO); } IF_LOCK(&sc->outq); if (_IF_QFULL(&sc->outq)) { IF_UNLOCK(&sc->outq); NG_FREE_M(m); return (ENOBUFS); } _IF_ENQUEUE(&sc->outq, m); sc->outqlen += m->m_pkthdr.len; IF_UNLOCK(&sc->outq); /* notify the TTY that data is ready */ tty_lock(tp); if (!tty_gone(tp)) ttydevsw_outwakeup(tp); tty_unlock(tp); return (0); } static size_t ngt_getc_inject(struct tty *tp, void *buf, size_t len) { sc_p sc = ttyhook_softc(tp); size_t total = 0; int length; while (len) { struct mbuf *m; /* Remove first mbuf from queue */ IF_DEQUEUE(&sc->outq, m); if (m == NULL) break; /* Send as much of it as possible */ while (m != NULL) { length = min(m->m_len, len); memcpy((char *)buf + total, mtod(m, char *), length); m->m_data += length; m->m_len -= length; total += length; len -= length; if (m->m_len > 0) break; /* device can't take any more */ m = m_free(m); } /* Put remainder of mbuf chain (if any) back on queue */ if (m != NULL) { IF_PREPEND(&sc->outq, m); break; } } IF_LOCK(&sc->outq); sc->outqlen -= total; IF_UNLOCK(&sc->outq); MPASS(sc->outqlen >= 0); return (total); } static size_t ngt_getc_poll(struct tty *tp) { sc_p sc = ttyhook_softc(tp); return (sc->outqlen); } /* * Optimised TTY input. * * We get a buffer pointer to hopefully a complete data frame. Do not check for * the hotchar, just pass it on. */ static size_t ngt_rint_bypass(struct tty *tp, const void *buf, size_t len) { sc_p sc = ttyhook_softc(tp); node_p node = sc->node; struct mbuf *m, *mb; size_t total = 0; int error = 0, length; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (sc->hook == NULL) return (0); m = m_getm2(NULL, len, M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { if (sc->flags & FLG_DEBUG) log(LOG_ERR, "%s: can't get mbuf\n", NG_NODE_NAME(node)); return (0); } m->m_pkthdr.rcvif = NULL; for (mb = m; mb != NULL; mb = mb->m_next) { length = min(M_TRAILINGSPACE(mb), len - total); memcpy(mtod(m, char *), (const char *)buf + total, length); mb->m_len = length; total += length; m->m_pkthdr.len += length; } if (sc->m != NULL) { /* * Odd, we have changed from non-bypass to bypass. It is * unlikely but not impossible, flush the data first. */ NG_SEND_DATA_ONLY(error, sc->hook, sc->m); sc->m = NULL; } NG_SEND_DATA_ONLY(error, sc->hook, m); return (total); } /* * Receive data coming from the device one char at a time, when it is not in * bypass mode. */ static int ngt_rint(struct tty *tp, char c, int flags) { sc_p sc = ttyhook_softc(tp); node_p node = sc->node; struct mbuf *m; int error = 0; - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); if (sc->hook == NULL) return (0); if (flags != 0) { /* framing error or overrun on this char */ if (sc->flags & FLG_DEBUG) log(LOG_DEBUG, "%s: line error %x\n", NG_NODE_NAME(node), flags); return (0); } /* Get a new header mbuf if we need one */ if (!(m = sc->m)) { MGETHDR(m, M_NOWAIT, MT_DATA); if (!m) { if (sc->flags & FLG_DEBUG) log(LOG_ERR, "%s: can't get mbuf\n", NG_NODE_NAME(node)); return (ENOBUFS); } m->m_len = m->m_pkthdr.len = 0; m->m_pkthdr.rcvif = NULL; sc->m = m; } /* Add char to mbuf */ *mtod(m, u_char *) = c; m->m_data++; m->m_len++; m->m_pkthdr.len++; /* Ship off mbuf if it's time */ if (sc->hotchar == -1 || c == sc->hotchar || m->m_len >= MHLEN) { sc->m = NULL; NG_SEND_DATA_ONLY(error, sc->hook, m); /* Will queue */ } return (error); } static size_t ngt_rint_poll(struct tty *tp) { /* We can always accept input */ return (1); } Index: head/sys/sys/tty.h =================================================================== --- head/sys/sys/tty.h (revision 360050) +++ head/sys/sys/tty.h (revision 360051) @@ -1,234 +1,237 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * Portions of this software were developed under sponsorship from Snow * B.V., the Netherlands. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _SYS_TTY_H_ #define _SYS_TTY_H_ #include #include #include #include #include #include #include #include #include struct cdev; struct file; struct pgrp; struct session; struct ucred; struct ttydevsw; /* * Per-TTY structure, containing buffers, etc. * * List of locks * (t) locked by t_mtx * (l) locked by tty_list_sx * (c) const until freeing */ struct tty { struct mtx *t_mtx; /* TTY lock. */ struct mtx t_mtxobj; /* Per-TTY lock (when not borrowing). */ TAILQ_ENTRY(tty) t_list; /* (l) TTY list entry. */ int t_drainwait; /* (t) TIOCDRAIN timeout seconds. */ unsigned int t_flags; /* (t) Terminal option flags. */ /* Keep flags in sync with db_show_tty and pstat(8). */ #define TF_NOPREFIX 0x00001 /* Don't prepend "tty" to device name. */ #define TF_INITLOCK 0x00002 /* Create init/lock state devices. */ #define TF_CALLOUT 0x00004 /* Create "cua" devices. */ #define TF_OPENED_IN 0x00008 /* "tty" node is in use. */ #define TF_OPENED_OUT 0x00010 /* "cua" node is in use. */ #define TF_OPENED_CONS 0x00020 /* Device in use as console. */ #define TF_OPENED (TF_OPENED_IN|TF_OPENED_OUT|TF_OPENED_CONS) #define TF_GONE 0x00040 /* Device node is gone. */ #define TF_OPENCLOSE 0x00080 /* Device is in open()/close(). */ #define TF_ASYNC 0x00100 /* Asynchronous I/O enabled. */ #define TF_LITERAL 0x00200 /* Accept the next character literally. */ #define TF_HIWAT_IN 0x00400 /* We've reached the input watermark. */ #define TF_HIWAT_OUT 0x00800 /* We've reached the output watermark. */ #define TF_HIWAT (TF_HIWAT_IN|TF_HIWAT_OUT) #define TF_STOPPED 0x01000 /* Output flow control - stopped. */ #define TF_EXCLUDE 0x02000 /* Exclusive access. */ #define TF_BYPASS 0x04000 /* Optimized input path. */ #define TF_ZOMBIE 0x08000 /* Modem disconnect received. */ #define TF_HOOK 0x10000 /* TTY has hook attached. */ #define TF_BUSY_IN 0x20000 /* Process busy in read() -- not supported. */ #define TF_BUSY_OUT 0x40000 /* Process busy in write(). */ #define TF_BUSY (TF_BUSY_IN|TF_BUSY_OUT) unsigned int t_revokecnt; /* (t) revoke() count. */ /* Buffering mechanisms. */ struct ttyinq t_inq; /* (t) Input queue. */ size_t t_inlow; /* (t) Input low watermark. */ struct ttyoutq t_outq; /* (t) Output queue. */ size_t t_outlow; /* (t) Output low watermark. */ /* Sleeping mechanisms. */ struct cv t_inwait; /* (t) Input wait queue. */ struct cv t_outwait; /* (t) Output wait queue. */ struct cv t_outserwait; /* (t) Serial output wait queue. */ struct cv t_bgwait; /* (t) Background wait queue. */ struct cv t_dcdwait; /* (t) Carrier Detect wait queue. */ /* Polling mechanisms. */ struct selinfo t_inpoll; /* (t) Input poll queue. */ struct selinfo t_outpoll; /* (t) Output poll queue. */ struct sigio *t_sigio; /* (t) Asynchronous I/O. */ struct termios t_termios; /* (t) I/O processing flags. */ struct winsize t_winsize; /* (t) Window size. */ unsigned int t_column; /* (t) Current cursor position. */ unsigned int t_writepos; /* (t) Where input was interrupted. */ int t_compatflags; /* (t) COMPAT_43TTY flags. */ /* Init/lock-state devices. */ struct termios t_termios_init_in; /* tty%s.init. */ struct termios t_termios_lock_in; /* tty%s.lock. */ struct termios t_termios_init_out; /* cua%s.init. */ struct termios t_termios_lock_out; /* cua%s.lock. */ struct ttydevsw *t_devsw; /* (c) Driver hooks. */ struct ttyhook *t_hook; /* (t) Capture/inject hook. */ /* Process signal delivery. */ struct pgrp *t_pgrp; /* (t) Foreground process group. */ struct session *t_session; /* (t) Associated session. */ unsigned int t_sessioncnt; /* (t) Backpointing sessions. */ void *t_devswsoftc; /* (c) Soft config, for drivers. */ void *t_hooksoftc; /* (t) Soft config, for hooks. */ struct cdev *t_dev; /* (c) Primary character device. */ size_t t_prbufsz; /* (t) SIGINFO buffer size. */ char t_prbuf[]; /* (t) SIGINFO buffer. */ }; /* * Userland version of struct tty, for sysctl kern.ttys */ struct xtty { size_t xt_size; /* Structure size. */ size_t xt_insize; /* Input queue size. */ size_t xt_incc; /* Canonicalized characters. */ size_t xt_inlc; /* Input line charaters. */ size_t xt_inlow; /* Input low watermark. */ size_t xt_outsize; /* Output queue size. */ size_t xt_outcc; /* Output queue usage. */ size_t xt_outlow; /* Output low watermark. */ unsigned int xt_column; /* Current column position. */ pid_t xt_pgid; /* Foreground process group. */ pid_t xt_sid; /* Session. */ unsigned int xt_flags; /* Terminal option flags. */ uint32_t xt_dev; /* Userland device. XXXKIB truncated */ }; #ifdef _KERNEL /* Used to distinguish between normal, callout, lock and init devices. */ #define TTYUNIT_INIT 0x1 #define TTYUNIT_LOCK 0x2 #define TTYUNIT_CALLOUT 0x4 /* Allocation and deallocation. */ struct tty *tty_alloc(struct ttydevsw *tsw, void *softc); struct tty *tty_alloc_mutex(struct ttydevsw *tsw, void *softc, struct mtx *mtx); void tty_rel_pgrp(struct tty *tp, struct pgrp *pgrp); void tty_rel_sess(struct tty *tp, struct session *sess); void tty_rel_gone(struct tty *tp); #define tty_lock(tp) mtx_lock((tp)->t_mtx) #define tty_unlock(tp) mtx_unlock((tp)->t_mtx) #define tty_lock_owned(tp) mtx_owned((tp)->t_mtx) -#define tty_lock_assert(tp,ma) mtx_assert((tp)->t_mtx, (ma)) +#define tty_assert_locked(tp) mtx_assert((tp)->t_mtx, MA_OWNED) #define tty_getlock(tp) ((tp)->t_mtx) + +/* XXX Should migrate users to tty_assert_locked! */ +#define tty_lock_assert(tp, ma) mtx_assert((tp)->t_mtx, (ma)) /* Device node creation. */ int tty_makedevf(struct tty *tp, struct ucred *cred, int flags, const char *fmt, ...) __printflike(4, 5); #define TTYMK_CLONING 0x1 #define tty_makedev(tp, cred, fmt, ...) \ (void )tty_makedevf((tp), (cred), 0, (fmt), ## __VA_ARGS__) #define tty_makealias(tp,fmt,...) \ make_dev_alias((tp)->t_dev, fmt, ## __VA_ARGS__) /* Signalling processes. */ void tty_signal_sessleader(struct tty *tp, int signal); void tty_signal_pgrp(struct tty *tp, int signal); /* Waking up readers/writers. */ int tty_wait(struct tty *tp, struct cv *cv); int tty_wait_background(struct tty *tp, struct thread *td, int sig); int tty_timedwait(struct tty *tp, struct cv *cv, int timo); void tty_wakeup(struct tty *tp, int flags); /* System messages. */ int tty_checkoutq(struct tty *tp); int tty_putchar(struct tty *tp, char c); int tty_putstrn(struct tty *tp, const char *p, size_t n); int tty_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, struct thread *td); int tty_ioctl_compat(struct tty *tp, u_long cmd, caddr_t data, int fflag, struct thread *td); void tty_set_winsize(struct tty *tp, const struct winsize *wsz); void tty_init_console(struct tty *tp, speed_t speed); void tty_flush(struct tty *tp, int flags); void tty_hiwat_in_block(struct tty *tp); void tty_hiwat_in_unblock(struct tty *tp); dev_t tty_udev(struct tty *tp); #define tty_opened(tp) ((tp)->t_flags & TF_OPENED) #define tty_gone(tp) ((tp)->t_flags & TF_GONE) #define tty_softc(tp) ((tp)->t_devswsoftc) #define tty_devname(tp) devtoname((tp)->t_dev) /* Status line printing. */ void tty_info(struct tty *tp); /* /dev/console selection. */ void ttyconsdev_select(const char *name); /* Pseudo-terminal hooks. */ int pts_alloc(int fflags, struct thread *td, struct file *fp); int pts_alloc_external(int fd, struct thread *td, struct file *fp, struct cdev *dev, const char *name); /* Drivers and line disciplines also need to call these. */ #include #include #include #endif /* _KERNEL */ #endif /* !_SYS_TTY_H_ */ Index: head/sys/sys/ttydevsw.h =================================================================== --- head/sys/sys/ttydevsw.h (revision 360050) +++ head/sys/sys/ttydevsw.h (revision 360051) @@ -1,211 +1,211 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * Portions of this software were developed under sponsorship from Snow * B.V., the Netherlands. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _SYS_TTYDEVSW_H_ #define _SYS_TTYDEVSW_H_ #ifndef _SYS_TTY_H_ #error "can only be included through " #endif /* !_SYS_TTY_H_ */ /* * Driver routines that are called from the line discipline to adjust * hardware parameters and such. */ typedef int tsw_open_t(struct tty *tp); typedef void tsw_close_t(struct tty *tp); typedef void tsw_outwakeup_t(struct tty *tp); typedef void tsw_inwakeup_t(struct tty *tp); typedef int tsw_ioctl_t(struct tty *tp, u_long cmd, caddr_t data, struct thread *td); typedef int tsw_cioctl_t(struct tty *tp, int unit, u_long cmd, caddr_t data, struct thread *td); typedef int tsw_param_t(struct tty *tp, struct termios *t); typedef int tsw_modem_t(struct tty *tp, int sigon, int sigoff); typedef int tsw_mmap_t(struct tty *tp, vm_ooffset_t offset, vm_paddr_t * paddr, int nprot, vm_memattr_t *memattr); typedef void tsw_pktnotify_t(struct tty *tp, char event); typedef void tsw_free_t(void *softc); typedef bool tsw_busy_t(struct tty *tp); struct ttydevsw { unsigned int tsw_flags; /* Default TTY flags. */ tsw_open_t *tsw_open; /* Device opening. */ tsw_close_t *tsw_close; /* Device closure. */ tsw_outwakeup_t *tsw_outwakeup; /* Output available. */ tsw_inwakeup_t *tsw_inwakeup; /* Input can be stored again. */ tsw_ioctl_t *tsw_ioctl; /* ioctl() hooks. */ tsw_cioctl_t *tsw_cioctl; /* ioctl() on control devices. */ tsw_param_t *tsw_param; /* TIOCSETA device parameter setting. */ tsw_modem_t *tsw_modem; /* Modem sigon/sigoff. */ tsw_mmap_t *tsw_mmap; /* mmap() hooks. */ tsw_pktnotify_t *tsw_pktnotify; /* TIOCPKT events. */ tsw_free_t *tsw_free; /* Destructor. */ tsw_busy_t *tsw_busy; /* Draining output. */ void *tsw_spare[3]; /* For future use. */ }; static __inline int ttydevsw_open(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_open(tp)); } static __inline void ttydevsw_close(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); tp->t_devsw->tsw_close(tp); } static __inline void ttydevsw_outwakeup(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); /* Prevent spurious wakeups. */ if (ttydisc_getc_poll(tp) == 0) return; tp->t_devsw->tsw_outwakeup(tp); } static __inline void ttydevsw_inwakeup(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); /* Prevent spurious wakeups. */ if (tp->t_flags & TF_HIWAT_IN) return; tp->t_devsw->tsw_inwakeup(tp); } static __inline int ttydevsw_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_ioctl(tp, cmd, data, td)); } static __inline int ttydevsw_cioctl(struct tty *tp, int unit, u_long cmd, caddr_t data, struct thread *td) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_cioctl(tp, unit, cmd, data, td)); } static __inline int ttydevsw_param(struct tty *tp, struct termios *t) { MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_param(tp, t)); } static __inline int ttydevsw_modem(struct tty *tp, int sigon, int sigoff) { MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_modem(tp, sigon, sigoff)); } static __inline int ttydevsw_mmap(struct tty *tp, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_mmap(tp, offset, paddr, nprot, memattr)); } static __inline void ttydevsw_pktnotify(struct tty *tp, char event) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); tp->t_devsw->tsw_pktnotify(tp, event); } static __inline void ttydevsw_free(struct tty *tp) { MPASS(tty_gone(tp)); tp->t_devsw->tsw_free(tty_softc(tp)); } static __inline bool ttydevsw_busy(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return (tp->t_devsw->tsw_busy(tp)); } #endif /* !_SYS_TTYDEVSW_H_ */ Index: head/sys/sys/ttydisc.h =================================================================== --- head/sys/sys/ttydisc.h (revision 360050) +++ head/sys/sys/ttydisc.h (revision 360051) @@ -1,89 +1,89 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * Portions of this software were developed under sponsorship from Snow * B.V., the Netherlands. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _SYS_TTYDISC_H_ #define _SYS_TTYDISC_H_ #ifndef _SYS_TTY_H_ #error "can only be included through " #endif /* !_SYS_TTY_H_ */ struct cv; struct thread; struct tty; struct uio; /* Top half routines. */ void ttydisc_open(struct tty *tp); void ttydisc_close(struct tty *tp); int ttydisc_read(struct tty *tp, struct uio *uio, int ioflag); int ttydisc_write(struct tty *tp, struct uio *uio, int ioflag); void ttydisc_optimize(struct tty *tp); /* Bottom half routines. */ void ttydisc_modem(struct tty *tp, int open); #define ttydisc_can_bypass(tp) ((tp)->t_flags & TF_BYPASS) int ttydisc_rint(struct tty *tp, char c, int flags); size_t ttydisc_rint_simple(struct tty *tp, const void *buf, size_t len); size_t ttydisc_rint_bypass(struct tty *tp, const void *buf, size_t len); void ttydisc_rint_done(struct tty *tp); size_t ttydisc_rint_poll(struct tty *tp); size_t ttydisc_getc(struct tty *tp, void *buf, size_t len); int ttydisc_getc_uio(struct tty *tp, struct uio *uio); size_t ttydisc_getc_poll(struct tty *tp); /* Error codes for ttydisc_rint(). */ #define TRE_FRAMING 0x01 #define TRE_PARITY 0x02 #define TRE_OVERRUN 0x04 #define TRE_BREAK 0x08 static __inline size_t ttydisc_read_poll(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); return ttyinq_bytescanonicalized(&tp->t_inq); } static __inline size_t ttydisc_write_poll(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); return ttyoutq_bytesleft(&tp->t_outq); } #endif /* !_SYS_TTYDISC_H_ */ Index: head/sys/sys/ttyhook.h =================================================================== --- head/sys/sys/ttyhook.h (revision 360050) +++ head/sys/sys/ttyhook.h (revision 360051) @@ -1,149 +1,149 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Ed Schouten * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD$ */ #ifndef _SYS_TTYHOOK_H_ #define _SYS_TTYHOOK_H_ #ifndef _SYS_TTY_H_ #error "can only be included through " #endif /* !_SYS_TTY_H_ */ struct tty; /* * Hooks interface, which allows to capture and inject traffic into the * input and output paths of a TTY. */ typedef int th_rint_t(struct tty *tp, char c, int flags); typedef size_t th_rint_bypass_t(struct tty *tp, const void *buf, size_t len); typedef void th_rint_done_t(struct tty *tp); typedef size_t th_rint_poll_t(struct tty *tp); typedef size_t th_getc_inject_t(struct tty *tp, void *buf, size_t len); typedef void th_getc_capture_t(struct tty *tp, const void *buf, size_t len); typedef size_t th_getc_poll_t(struct tty *tp); typedef void th_close_t(struct tty *tp); struct ttyhook { /* Character input. */ th_rint_t *th_rint; th_rint_bypass_t *th_rint_bypass; th_rint_done_t *th_rint_done; th_rint_poll_t *th_rint_poll; /* Character output. */ th_getc_inject_t *th_getc_inject; th_getc_capture_t *th_getc_capture; th_getc_poll_t *th_getc_poll; th_close_t *th_close; }; int ttyhook_register(struct tty **, struct proc *, int, struct ttyhook *, void *); void ttyhook_unregister(struct tty *); #define ttyhook_softc(tp) ((tp)->t_hooksoftc) #define ttyhook_hashook(tp,hook) ((tp)->t_hook != NULL && \ (tp)->t_hook->th_ ## hook != NULL) static __inline int ttyhook_rint(struct tty *tp, char c, int flags) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return tp->t_hook->th_rint(tp, c, flags); } static __inline size_t ttyhook_rint_bypass(struct tty *tp, const void *buf, size_t len) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return tp->t_hook->th_rint_bypass(tp, buf, len); } static __inline void ttyhook_rint_done(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); tp->t_hook->th_rint_done(tp); } static __inline size_t ttyhook_rint_poll(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return tp->t_hook->th_rint_poll(tp); } static __inline size_t ttyhook_getc_inject(struct tty *tp, void *buf, size_t len) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return tp->t_hook->th_getc_inject(tp, buf, len); } static __inline void ttyhook_getc_capture(struct tty *tp, const void *buf, size_t len) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); tp->t_hook->th_getc_capture(tp, buf, len); } static __inline size_t ttyhook_getc_poll(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); MPASS(!tty_gone(tp)); return tp->t_hook->th_getc_poll(tp); } static __inline void ttyhook_close(struct tty *tp) { - tty_lock_assert(tp, MA_OWNED); + tty_assert_locked(tp); tp->t_hook->th_close(tp); } #endif /* !_SYS_TTYHOOK_H_ */