1448 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			1448 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|  | /*
 | ||
|  |  * Copyright (c) 2006-2023, RT-Thread Development Team | ||
|  |  * | ||
|  |  * SPDX-License-Identifier: Apache-2.0 | ||
|  |  * | ||
|  |  * Change Logs: | ||
|  |  * Date           Author       Notes | ||
|  |  * 2023-11-13     Shell        init ver. | ||
|  |  */ | ||
|  | #include "../bsd_porting.h"
 | ||
|  | #include "../tty_config.h"
 | ||
|  | #include "../terminal.h"
 | ||
|  | #include "../tty_internal.h"
 | ||
|  | 
 | ||
|  | #include <rtdef.h>
 | ||
|  | #include <sys/ioctl.h>
 | ||
|  | 
 | ||
|  | /*-
 | ||
|  |  * SPDX-License-Identifier: BSD-2-Clause | ||
|  |  * | ||
|  |  * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> | ||
|  |  * 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. | ||
|  |  */ | ||
|  | 
 | ||
|  | static void tty_rel_free(struct lwp_tty *tp); | ||
|  | 
 | ||
|  | /* Character device of /dev/console. */ | ||
|  | static struct rt_device *dev_console; | ||
|  | #ifdef USING_BSD_CONSOLE_NAME
 | ||
|  | static const char *dev_console_filename; | ||
|  | #endif
 | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Flags that are supported and stored by this implementation. | ||
|  |  */ | ||
|  | #ifndef ALTWERASE
 | ||
|  | #define ALTWERASE 0
 | ||
|  | #endif
 | ||
|  | #ifndef NOKERNINFO
 | ||
|  | #define NOKERNINFO 0
 | ||
|  | #endif
 | ||
|  | #define TTYSUP_IFLAG                                                      \
 | ||
|  |     (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | \ | ||
|  |      ICRNL | IXON | IXOFF | IXANY | IMAXBEL | IUTF8) | ||
|  | #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) | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * 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
 | ||
|  | 
 | ||
|  | /* Note: access to struct cdev:si_drv0.
 | ||
|  |     Since pull-in, pull-out is not provided, this field | ||
|  |     is constant value 0 in smart system */ | ||
|  | #define dev2unit(d)        (0)
 | ||
|  | #define TTY_CALLOUT(tp, d) (dev2unit(d) & TTYUNIT_CALLOUT)
 | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * 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 lwp_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(bsd_speed_to_integer(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(bsd_speed_to_integer(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; | ||
|  | } | ||
|  | 
 | ||
|  | /**
 | ||
|  |  * Drain outq | ||
|  |  */ | ||
|  | static int tty_drain(struct lwp_tty *tp, int leaving) | ||
|  | { | ||
|  |     rt_tick_t timeout_tick; | ||
|  |     size_t bytes; | ||
|  |     int error; | ||
|  | 
 | ||
|  | #ifdef USING_BSD_HOOK
 | ||
|  |     if (ttyhook_hashook(tp, getc_inject)) | ||
|  |         /* buffer is inaccessible */ | ||
|  |         return 0; | ||
|  | #endif /* USING_BSD_HOOK */
 | ||
|  | 
 | ||
|  |     /*
 | ||
|  |      * 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_tick = rt_tick_get() + RT_TICK_PER_SECOND; | ||
|  |     else if (tp->t_drainwait != 0) | ||
|  |         timeout_tick = rt_tick_get() + RT_TICK_PER_SECOND * tp->t_drainwait; | ||
|  |     else | ||
|  |         timeout_tick = 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, RT_TICK_PER_SECOND / 10); | ||
|  |         if (error != 0 && error != EWOULDBLOCK) | ||
|  |             return error; | ||
|  |         else if (timeout_tick == 0 || rt_tick_get() < timeout_tick) | ||
|  |             error = 0; | ||
|  |         else if (leaving && ttyoutq_bytesused(&tp->t_outq) < bytes) | ||
|  |         { | ||
|  |             /* In close, making progress, grant an extra second. */ | ||
|  |             error = 0; | ||
|  |             timeout_tick += RT_TICK_PER_SECOND; | ||
|  |             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. | ||
|  |  */ | ||
|  | 
 | ||
|  | rt_inline int ttydev_enter(struct lwp_tty *tp) | ||
|  | { | ||
|  |     rt_err_t error = tty_lock(tp); | ||
|  |     if (error) | ||
|  |         RT_ASSERT(0); | ||
|  | 
 | ||
|  |     if (tty_gone(tp) || !tty_opened(tp)) | ||
|  |     { | ||
|  |         /* Device is already gone. */ | ||
|  |         tty_unlock(tp); | ||
|  |         return -ENXIO; | ||
|  |     } | ||
|  | 
 | ||
|  |     return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void ttydev_leave(struct lwp_tty *tp) | ||
|  | { | ||
|  |     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. */ | ||
|  |     constty_clear(tp); | ||
|  | 
 | ||
|  |     /* 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; | ||
|  | 
 | ||
|  | #ifdef USING_BSD_KNOTE
 | ||
|  |     knlist_clear(&tp->t_inpoll.si_note, 1); | ||
|  |     knlist_clear(&tp->t_outpoll.si_note, 1); | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     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 lwp_tty *tp, int oflags, int devtype, | ||
|  |                        struct rt_thread *td) | ||
|  | { | ||
|  |     rt_device_t dev = &tp->parent; | ||
|  |     int error; | ||
|  | 
 | ||
|  |     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)) | ||
|  | #ifdef USING_BSD_INIT_LOCK_DEVICE
 | ||
|  |             tp->t_termios = tp->t_termios_init_out; | ||
|  | #else
 | ||
|  |             ; | ||
|  | #endif /* USING_BSD_INIT_LOCK_DEVICE */
 | ||
|  |         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 lwp_tty *tp, int fflag, int devtype __unused, | ||
|  |                         struct rt_thread *td __unused) | ||
|  | { | ||
|  |     rt_device_t dev = &tp->parent; | ||
|  | 
 | ||
|  |     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; | ||
|  | } | ||
|  | 
 | ||
|  | int tty_wait_background(struct lwp_tty *tp, struct rt_thread *td, int sig) | ||
|  | { | ||
|  |     struct rt_lwp *p; | ||
|  |     struct rt_processgroup *pg; | ||
|  |     int error; | ||
|  | 
 | ||
|  |     MPASS(sig == SIGTTIN || sig == SIGTTOU); | ||
|  |     tty_assert_locked(tp); | ||
|  | 
 | ||
|  |     p = td->lwp; | ||
|  |     for (;;) | ||
|  |     { | ||
|  |         pg = p->pgrp; | ||
|  |         PGRP_LOCK(pg); | ||
|  |         LWP_LOCK(p); | ||
|  | 
 | ||
|  |         /*
 | ||
|  |          * pg may no longer be our process group. | ||
|  |          * Re-check after locking. | ||
|  |          */ | ||
|  |         if (p->pgrp != pg) | ||
|  |         { | ||
|  |             LWP_UNLOCK(p); | ||
|  |             PGRP_UNLOCK(pg); | ||
|  |             continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         /*
 | ||
|  |          * 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) || pg == tp->t_pgrp) | ||
|  |         { | ||
|  |             /* Allow the action to happen. */ | ||
|  |             LWP_UNLOCK(p); | ||
|  |             PGRP_UNLOCK(pg); | ||
|  |             return 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /* Note: process itself don't have a sigmask in smart */ | ||
|  |         if (lwp_sigisign(p, sig) || | ||
|  |             lwp_sigismember(&td->signal.sigset_mask, sig)) | ||
|  |         { | ||
|  |             /* Only allow them in write()/ioctl(). */ | ||
|  |             LWP_UNLOCK(p); | ||
|  |             PGRP_UNLOCK(pg); | ||
|  |             return (sig == SIGTTOU ? 0 : -EIO); | ||
|  |         } | ||
|  | 
 | ||
|  | #ifdef USING_VFORK_FLAG
 | ||
|  |         if ((p->p_flag & P_PPWAIT) != 0 || pg->is_orphaned) | ||
|  | #else
 | ||
|  |         if (pg->is_orphaned) | ||
|  | #endif
 | ||
|  |         { | ||
|  |             /* Don't allow the action to happen. */ | ||
|  |             LWP_UNLOCK(p); | ||
|  |             PGRP_UNLOCK(pg); | ||
|  |             return -EIO; | ||
|  |         } | ||
|  |         LWP_UNLOCK(p); | ||
|  | 
 | ||
|  |         /*
 | ||
|  |          * Send the signal and sleep until we're the new | ||
|  |          * foreground process group. | ||
|  |          */ | ||
|  |         if (sig != 0) | ||
|  |         { | ||
|  |             lwp_pgrp_signal_kill(pg, sig, SI_KERNEL, 0); | ||
|  |         } | ||
|  | 
 | ||
|  |         PGRP_UNLOCK(pg); | ||
|  | 
 | ||
|  |         error = tty_wait(tp, &tp->t_bgwait); | ||
|  |         if (error) | ||
|  |             return error; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | static int ttydev_read(struct lwp_tty *tp, struct uio *uio, int ioflag) | ||
|  | { | ||
|  |     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 lwp_tty *tp, struct uio *uio, int ioflag) | ||
|  | { | ||
|  | #ifdef USING_BSD_DEFER_STOP
 | ||
|  |     int defer; | ||
|  | #endif
 | ||
|  |     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; | ||
|  | #ifdef USING_BSD_DEFER_STOP
 | ||
|  |         defer = sigdeferstop(SIGDEFERSTOP_ERESTART); | ||
|  | #endif
 | ||
|  |         error = ttydisc_write(tp, uio, ioflag); | ||
|  | #ifdef USING_BSD_DEFER_STOP
 | ||
|  |         sigallowstop(defer); | ||
|  | #endif
 | ||
|  |         tp->t_flags &= ~TF_BUSY_OUT; | ||
|  |         cv_signal(&tp->t_outserwait); | ||
|  |     } | ||
|  | 
 | ||
|  | done: | ||
|  |     tty_unlock(tp); | ||
|  |     return error; | ||
|  | } | ||
|  | 
 | ||
|  | static int ttydev_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, rt_caddr_t data, int fflag, | ||
|  |                         struct rt_thread *td) | ||
|  | { | ||
|  |     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 USING_BSD_TIOCSDRAINWAIT
 | ||
|  |     case TIOCSDRAINWAIT: | ||
|  |     case TIOCSETD: | ||
|  | #endif /* USING_BSD_TIOCSDRAINWAIT */
 | ||
|  | #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; | ||
|  |     } | ||
|  | 
 | ||
|  | #ifdef USING_BSD_INIT_LOCK_DEVICE
 | ||
|  |     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; | ||
|  |     } | ||
|  | #endif /* USING_BSD_INIT_LOCK_DEVICE */
 | ||
|  | 
 | ||
|  |     error = tty_ioctl(tp, cmd, data, fflag, td); | ||
|  | done: | ||
|  |     tty_unlock(tp); | ||
|  | 
 | ||
|  |     return (error); | ||
|  | } | ||
|  | 
 | ||
|  | static int ttydev_poll(struct lwp_tty *tp, rt_pollreq_t *req, struct rt_thread *td) | ||
|  | { | ||
|  |     int events = req->_key; | ||
|  |     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)) | ||
|  |             rt_poll_add(&tp->t_inpoll, req); | ||
|  |         if (events & (POLLOUT | POLLWRNORM)) | ||
|  |             rt_poll_add(&tp->t_outpoll, req); | ||
|  |     } | ||
|  | 
 | ||
|  |     tty_unlock(tp); | ||
|  | 
 | ||
|  |     return revents; | ||
|  | } | ||
|  | 
 | ||
|  | static struct cdevsw ttydev_cdevsw = { | ||
|  |     .d_open = ttydev_open, | ||
|  |     .d_close = ttydev_close, | ||
|  |     .d_read = ttydev_read, | ||
|  |     .d_write = ttydev_write, | ||
|  |     .d_ioctl = ttydev_ioctl, | ||
|  | #if 0
 | ||
|  |     .d_kqfilter = ttydev_kqfilter, | ||
|  | #endif
 | ||
|  |     .d_poll = ttydev_poll, | ||
|  | #if 0
 | ||
|  |     .d_mmap = ttydev_mmap, | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #ifdef USING_BSD_RAW_CDEVSW
 | ||
|  |     .d_version = D_VERSION.d_name = "ttydev", | ||
|  |     .d_flags = D_TTY, | ||
|  | #endif /* USING_BSD_RAW_CDEVSW */
 | ||
|  | }; | ||
|  | 
 | ||
|  | extern struct cdevsw bsd_ttydev_methods __attribute__((alias("ttydev_cdevsw"))); | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * Standard device routine implementations, mostly meant for | ||
|  |  * pseudo-terminal device drivers. When a driver creates a new terminal | ||
|  |  * device class, missing routines are patched. | ||
|  |  */ | ||
|  | #define panic(msg) RT_ASSERT(0 && msg)
 | ||
|  | 
 | ||
|  | static int ttydevsw_defopen(struct lwp_tty *tp __unused) | ||
|  | { | ||
|  |     return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void ttydevsw_defclose(struct lwp_tty *tp __unused) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | static void ttydevsw_defoutwakeup(struct lwp_tty *tp __unused) | ||
|  | { | ||
|  |     panic("Terminal device has output, while not implemented"); | ||
|  | } | ||
|  | 
 | ||
|  | static void ttydevsw_definwakeup(struct lwp_tty *tp __unused) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | static int ttydevsw_defioctl(struct lwp_tty *tp __unused, rt_ubase_t cmd __unused, | ||
|  |                              rt_caddr_t data __unused, | ||
|  |                              struct rt_thread *td __unused) | ||
|  | { | ||
|  |     return -ENOSYS; | ||
|  | } | ||
|  | 
 | ||
|  | static int ttydevsw_defcioctl(struct lwp_tty *tp __unused, int unit __unused, | ||
|  |                               rt_ubase_t cmd __unused, rt_caddr_t data __unused, | ||
|  |                               struct rt_thread *td __unused) | ||
|  | { | ||
|  |     return -ENOSYS; | ||
|  | } | ||
|  | 
 | ||
|  | static int ttydevsw_defparam(struct lwp_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 lwp_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 lwp_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 lwp_tty *tp __unused, | ||
|  |                                   char event __unused) | ||
|  | { | ||
|  | } | ||
|  | 
 | ||
|  | static void ttydevsw_deffree(void *softc __unused) | ||
|  | { | ||
|  |     panic("Terminal device freed without a free-handler"); | ||
|  | } | ||
|  | 
 | ||
|  | static rt_bool_t ttydevsw_defbusy(struct lwp_tty *tp __unused) | ||
|  | { | ||
|  |     return (RT_FALSE); | ||
|  | } | ||
|  | 
 | ||
|  | void bsd_devsw_init(struct lwp_ttydevsw *tsw) | ||
|  | { | ||
|  |     /* 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
 | ||
|  | } | ||
|  | 
 | ||
|  | /* release tty, and free the cdev resource */ | ||
|  | static void tty_rel_free(struct lwp_tty *tp) | ||
|  | { | ||
|  | #ifdef USING_BSD_CHAR_DEVICE
 | ||
|  |     struct cdev *dev; | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     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; | ||
|  |     } | ||
|  | 
 | ||
|  | #ifdef USING_BSD_AIO
 | ||
|  |     /* Stop asynchronous I/O. */ | ||
|  |     funsetown(&tp->t_sigio); | ||
|  | #endif /* USING_BSD_AIO */
 | ||
|  | 
 | ||
|  | #ifdef USING_BSD_CHAR_DEVICE
 | ||
|  |     /* TTY can be deallocated. */ | ||
|  |     dev = tp->t_dev; | ||
|  |     tp->t_dev = NULL; | ||
|  | #endif /* USING_BSD_CHAR_DEVICE */
 | ||
|  | 
 | ||
|  |     tty_unlock(tp); | ||
|  | 
 | ||
|  | #ifdef USING_BSD_CHAR_DEVICE
 | ||
|  |     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); | ||
|  |     } | ||
|  | #else
 | ||
|  |     lwp_tty_delete(tp); | ||
|  | #endif
 | ||
|  | } | ||
|  | 
 | ||
|  | void tty_rel_pgrp(struct lwp_tty *tp, struct rt_processgroup *pg) | ||
|  | { | ||
|  |     MPASS(tp->t_sessioncnt > 0); | ||
|  |     tty_assert_locked(tp); | ||
|  | 
 | ||
|  |     if (tp->t_pgrp == pg) | ||
|  |         tp->t_pgrp = NULL; | ||
|  | 
 | ||
|  |     tty_unlock(tp); | ||
|  | } | ||
|  | 
 | ||
|  | void tty_rel_sess(struct lwp_tty *tp, struct rt_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); | ||
|  | } | ||
|  | 
 | ||
|  | /* deallocate the tty */ | ||
|  | void tty_rel_gone(struct lwp_tty *tp) | ||
|  | { | ||
|  |     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 lwp_tty *tp, struct rt_lwp *p) | ||
|  | { | ||
|  |     struct rt_session *session; | ||
|  | #ifdef USING_BSD_VNODE
 | ||
|  |     struct vnode *vp; | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     /*
 | ||
|  |      * 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); | ||
|  |     LWP_LOCK(p); | ||
|  |     tty_lock(tp); | ||
|  |     if (tty_gone(tp)) | ||
|  |     { | ||
|  |         LWP_UNLOCK(p); | ||
|  |         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->pgrp->session; | ||
|  |     if (session->ctty == NULL || session->ctty != tp) | ||
|  |     { | ||
|  |         LWP_UNLOCK(p); | ||
|  |         return -ENOTTY; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!is_sess_leader(p)) | ||
|  |     { | ||
|  |         LWP_UNLOCK(p); | ||
|  |         return -EPERM; | ||
|  |     } | ||
|  | 
 | ||
|  |     SESS_LOCK(session); | ||
|  | #ifdef USING_BSD_VNODE
 | ||
|  |     vp = session->s_ttyvp; | ||
|  | #endif
 | ||
|  |     session->ctty = NULL; | ||
|  | #ifdef USING_BSD_VNODE
 | ||
|  |     session->s_ttyvp = NULL; | ||
|  |     session->s_ttydp = NULL; | ||
|  | #endif
 | ||
|  |     SESS_UNLOCK(session); | ||
|  | 
 | ||
|  |     tp->t_sessioncnt--; | ||
|  |     p->term_ctrlterm = RT_FALSE; | ||
|  |     LWP_UNLOCK(p); | ||
|  | 
 | ||
|  | #ifdef USING_BSD_VNODE
 | ||
|  |     /*
 | ||
|  |      * 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) | ||
|  |         devfs_ctty_unref(vp); | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     return 0; | ||
|  | } | ||
|  | 
 | ||
|  | void tty_wakeup(struct lwp_tty *tp, int flags) | ||
|  | { | ||
|  | #ifdef USING_BSD_AIO
 | ||
|  |     if (tp->t_flags & TF_ASYNC && tp->t_sigio != NULL) | ||
|  |         pgsigio(&tp->t_sigio, SIGIO, (tp->t_session != NULL)); | ||
|  | #endif
 | ||
|  | 
 | ||
|  |     if (flags & FWRITE) | ||
|  |     { | ||
|  |         cv_broadcast(&tp->t_outwait); | ||
|  | #ifdef USING_BSD_POLL
 | ||
|  |         selwakeup(&tp->t_outpoll); | ||
|  |         KNOTE_LOCKED(&tp->t_outpoll.si_note, 0); | ||
|  | #else
 | ||
|  |         rt_wqueue_wakeup_all(&tp->t_outpoll, (void *)POLLOUT); | ||
|  | #endif
 | ||
|  |     } | ||
|  |     if (flags & FREAD) | ||
|  |     { | ||
|  |         cv_broadcast(&tp->t_inwait); | ||
|  | #ifdef USING_BSD_POLL
 | ||
|  |         selwakeup(&tp->t_inpoll); | ||
|  |         KNOTE_LOCKED(&tp->t_inpoll.si_note, 0); | ||
|  | #else
 | ||
|  |         rt_wqueue_wakeup_all(&tp->t_inpoll, (void *)POLLIN); | ||
|  | #endif
 | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | int tty_wait(struct lwp_tty *tp, struct rt_condvar *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 lwp_tty *tp, struct rt_condvar *cv, rt_tick_t timeout) | ||
|  | { | ||
|  |     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, timeout); | ||
|  | 
 | ||
|  |     /* 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; | ||
|  | } | ||
|  | 
 | ||
|  | /* discard data in I/O buffers */ | ||
|  | void tty_flush(struct lwp_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 lwp_tty *tp, const struct winsize *wsz) | ||
|  | { | ||
|  |     if (memcmp(&tp->t_winsize, wsz, sizeof(*wsz)) == 0) | ||
|  |         return; | ||
|  |     tp->t_winsize = *wsz; | ||
|  |     lwp_tty_signal_pgrp(tp, SIGWINCH); | ||
|  | } | ||
|  | 
 | ||
|  | static int tty_generic_ioctl(struct lwp_tty *tp, rt_ubase_t cmd, void *data, | ||
|  |                              int fflag, struct rt_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; | ||
|  | #if BSD_USING_FIO_OWNERSHIP
 | ||
|  |         case FIOSETOWN: | ||
|  |             if (tp->t_session != NULL && !tty_is_ctty(tp, td->lwp)) | ||
|  |                 /* 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->lwp)) | ||
|  |                 /* Not allowed to set ownership. */ | ||
|  |                 return -ENOTTY; | ||
|  | 
 | ||
|  |             /* Get ownership. */ | ||
|  |             *(int *)data = fgetown(&tp->t_sigio); | ||
|  |             return 0; | ||
|  | #endif
 | ||
|  |         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. | ||
|  |              * | ||
|  |              * TODO: change the _CONTROL('S') to a CSTOP? | ||
|  |              */ | ||
|  |             if (tp->t_termios.c_iflag & IXON && | ||
|  |                 tp->t_termios.c_cc[VSTOP] == _CONTROL('S') && | ||
|  |                 tp->t_termios.c_cc[VSTART] == _CONTROL('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->lwp)) | ||
|  |                 return -ENOTTY; | ||
|  | 
 | ||
|  |             if (tp->t_pgrp != NULL) | ||
|  |                 *(int *)data = tp->t_pgrp->pgid; | ||
|  |             else | ||
|  |                 *(int *)data = NO_PID; | ||
|  |             return 0; | ||
|  |         case TIOCGSID: | ||
|  |             if (!tty_is_ctty(tp, td->lwp)) | ||
|  |                 return -ENOTTY; | ||
|  | 
 | ||
|  |             MPASS(tp->t_session); | ||
|  |             *(int *)data = tp->t_session->sid; | ||
|  |             return 0; | ||
|  |         case TIOCNOTTY: | ||
|  |             return tty_drop_ctty(tp, td->lwp); | ||
|  |         case TIOCSCTTY: | ||
|  |             return lwp_tty_set_ctrl_proc(tp, td); | ||
|  |         case TIOCSPGRP: { | ||
|  |             int pgid; | ||
|  |             if (lwp_in_user_space((void *)data)) | ||
|  |             { | ||
|  |                 if (lwp_get_from_user(&pgid, data, sizeof(int)) != sizeof(int)) | ||
|  |                     return -EFAULT; | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 pgid = *(int *)data; | ||
|  |             } | ||
|  |             return lwp_tty_assign_foreground(tp, td, pgid); | ||
|  |         } | ||
|  |         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); | ||
|  |                 error = constty_set(tp); | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 error = constty_clear(tp); | ||
|  |             } | ||
|  |             return (error); | ||
|  |         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; | ||
|  |             tp->t_termios.c_lflag &= ~FLUSHO; | ||
|  |             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->lwp) && 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 lwp_tty *tp, rt_ubase_t cmd, void *data, int fflag, | ||
|  |               struct rt_thread *td) | ||
|  | { | ||
|  |     int error; | ||
|  | 
 | ||
|  |     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; | ||
|  | } | ||
|  | 
 | ||
|  | int tty_checkoutq(struct lwp_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 lwp_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 lwp_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); | ||
|  | } |