diff options
-rw-r--r-- | lib/libc/gen/tcsendbreak.3 | 33 | ||||
-rw-r--r-- | share/man/man4/tty.4 | 13 | ||||
-rw-r--r-- | sys/kern/tty.c | 102 | ||||
-rw-r--r-- | sys/kern/tty_inq.c | 8 | ||||
-rw-r--r-- | sys/kern/tty_outq.c | 8 | ||||
-rw-r--r-- | sys/sys/tty.h | 1 | ||||
-rw-r--r-- | sys/sys/ttyqueue.h | 4 |
7 files changed, 134 insertions, 35 deletions
diff --git a/lib/libc/gen/tcsendbreak.3 b/lib/libc/gen/tcsendbreak.3 index a7e86d0..dd43a29 100644 --- a/lib/libc/gen/tcsendbreak.3 +++ b/lib/libc/gen/tcsendbreak.3 @@ -28,7 +28,7 @@ .\" @(#)tcsendbreak.3 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" -.Dd June 4, 1993 +.Dd January 11, 2017 .Dt TCSENDBREAK 3 .Os .Sh NAME @@ -137,17 +137,44 @@ is not a terminal. A signal interrupted the .Fn tcdrain function. +.It Bq Er EWOULDBLOCK +The configured timeout expired before the +.Fn tcdrain +function could write all buffered output. .El .Sh SEE ALSO .Xr tcsetattr 3 , -.Xr termios 4 +.Xr termios 4 , +.Xr tty 4 , +.Xr comcontrol 8 .Sh STANDARDS The .Fn tcsendbreak , -.Fn tcdrain , .Fn tcflush and .Fn tcflow functions are expected to be compliant with the .St -p1003.1-88 specification. +.Pp +The +.Fn tcdrain +function is expected to be compliant with +.St -p1003.1-88 +when the drain wait value is set to zero with +.Xr comcontrol 8 , +or with +.Xr ioctl 2 +.Va TIOCSDRAINWAIT , +or with +.Xr sysctl 8 +.Va kern.tty_drainwait . +A non-zero drain wait value can result in +.Fn tcdrain +returning +.Va EWOULDBLOCK +without writing all output. +The default value for +.Va kern.tty_drainwait +is 300 seconds. + diff --git a/share/man/man4/tty.4 b/share/man/man4/tty.4 index ec6d9f1..ef5bed8 100644 --- a/share/man/man4/tty.4 +++ b/share/man/man4/tty.4 @@ -28,7 +28,7 @@ .\" @(#)tty.4 8.3 (Berkeley) 4/19/94 .\" $FreeBSD$ .\" -.Dd December 26, 2009 +.Dd January 11, 2017 .Dt TTY 4 .Os .Sh NAME @@ -238,7 +238,16 @@ Start output on the terminal (like typing ^Q at the keyboard). Make the terminal the controlling terminal for the process (the process must not currently have a controlling terminal). .It Dv TIOCDRAIN Fa void -Wait until all output is drained. +Wait until all output is drained, or until the drain wait timeout expires. +.It Dv TIOCGDRAINWAIT Fa int *timeout +Return the current drain wait timeout in seconds. +.It Dv TIOCSDRAINWAIT Fa int *timeout +Set the drain wait timeout in seconds. +A value of zero disables timeouts. +The default drain wait timeout is controlled by the tunable +.Xr sysctl 8 +OID +.Va kern.tty_drainwait . .It Dv TIOCEXCL Fa void Set exclusive use on the terminal. No further opens are permitted except by root. diff --git a/sys/kern/tty.c b/sys/kern/tty.c index b37d5a5..b0a535c 100644 --- a/sys/kern/tty.c +++ b/sys/kern/tty.c @@ -95,64 +95,101 @@ static const char *dev_console_filename; #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 -static void +/* + * 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 0.2 seconds of data. */ + /* 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); - ttyinq_setsize(&tp->t_inq, tp, bs); + 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 0.2 seconds of data. */ + /* Provide an output buffer for 2 seconds of data. */ bs = MIN(tp->t_termios.c_ospeed / 5, TTYBUF_MAX); - ttyoutq_setsize(&tp->t_outq, tp, bs); + 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) { - size_t bytesused; + sbintime_t timeout_at; + size_t bytes; int error; if (ttyhook_hashook(tp, getc_inject)) /* buffer is inaccessible */ return (0); - while (ttyoutq_bytesused(&tp->t_outq) > 0 || ttydevsw_busy(tp)) { - ttydevsw_outwakeup(tp); - /* Could be handled synchronously. */ - bytesused = ttyoutq_bytesused(&tp->t_outq); - if (bytesused == 0 && !ttydevsw_busy(tp)) - return (0); - - /* Wait for data to be drained. */ - if (leaving) { - error = tty_timedwait(tp, &tp->t_outwait, hz); - if (error == EWOULDBLOCK && - ttyoutq_bytesused(&tp->t_outq) < bytesused) - error = 0; - } else - error = tty_wait(tp, &tp->t_outwait); + /* + * 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; - if (error) + /* + * 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); + } } - - return (0); } /* @@ -294,7 +331,9 @@ ttydev_open(struct cdev *dev, int oflags, int devtype __unused, goto done; ttydisc_open(tp); - tty_watermarks(tp); /* XXXGL: drops lock */ + error = tty_watermarks(tp); + if (error != 0) + goto done; } /* Wait for Carrier Detect. */ @@ -1015,6 +1054,7 @@ tty_alloc_mutex(struct ttydevsw *tsw, void *sc, struct mtx *mutex) tp->t_devsw = tsw; tp->t_devswsoftc = sc; tp->t_flags = tsw->tsw_flags; + tp->t_drainwait = tty_drainwait; tty_init_termios(tp); @@ -1602,7 +1642,9 @@ tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, tp->t_termios.c_ospeed = t->c_ospeed; /* Baud rate has changed - update watermarks. */ - tty_watermarks(tp); + error = tty_watermarks(tp); + if (error) + return (error); } /* Copy new non-device driver parameters. */ @@ -1755,6 +1797,14 @@ tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag, 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) { diff --git a/sys/kern/tty_inq.c b/sys/kern/tty_inq.c index 97017ac..163b194 100644 --- a/sys/kern/tty_inq.c +++ b/sys/kern/tty_inq.c @@ -112,7 +112,7 @@ static uma_zone_t ttyinq_zone; TTYINQ_INSERT_TAIL(ti, tib); \ } while (0) -void +int ttyinq_setsize(struct ttyinq *ti, struct tty *tp, size_t size) { struct ttyinq_block *tib; @@ -134,8 +134,14 @@ ttyinq_setsize(struct ttyinq *ti, struct tty *tp, size_t size) tib = uma_zalloc(ttyinq_zone, M_WAITOK); tty_lock(tp); + if (tty_gone(tp)) { + uma_zfree(ttyinq_zone, tib); + return (ENXIO); + } + TTYINQ_INSERT_TAIL(ti, tib); } + return (0); } void diff --git a/sys/kern/tty_outq.c b/sys/kern/tty_outq.c index 5d40abe..3f86e11 100644 --- a/sys/kern/tty_outq.c +++ b/sys/kern/tty_outq.c @@ -89,7 +89,7 @@ ttyoutq_flush(struct ttyoutq *to) to->to_end = 0; } -void +int ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t size) { struct ttyoutq_block *tob; @@ -111,8 +111,14 @@ ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t size) tob = uma_zalloc(ttyoutq_zone, M_WAITOK); tty_lock(tp); + if (tty_gone(tp)) { + uma_zfree(ttyoutq_zone, tob); + return (ENXIO); + } + TTYOUTQ_INSERT_TAIL(to, tob); } + return (0); } void diff --git a/sys/sys/tty.h b/sys/sys/tty.h index f1a044f..09e0419 100644 --- a/sys/sys/tty.h +++ b/sys/sys/tty.h @@ -62,6 +62,7 @@ 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. */ diff --git a/sys/sys/ttyqueue.h b/sys/sys/ttyqueue.h index 2d1a565..c8d85d6 100644 --- a/sys/sys/ttyqueue.h +++ b/sys/sys/ttyqueue.h @@ -69,7 +69,7 @@ struct ttyoutq { #ifdef _KERNEL /* Input queue handling routines. */ -void ttyinq_setsize(struct ttyinq *ti, struct tty *tp, size_t len); +int ttyinq_setsize(struct ttyinq *ti, struct tty *tp, size_t len); void ttyinq_free(struct ttyinq *ti); int ttyinq_read_uio(struct ttyinq *ti, struct tty *tp, struct uio *uio, size_t readlen, size_t flushlen); @@ -136,7 +136,7 @@ void ttyinq_line_iterate_from_reprintpos(struct ttyinq *ti, /* Output queue handling routines. */ void ttyoutq_flush(struct ttyoutq *to); -void ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t len); +int ttyoutq_setsize(struct ttyoutq *to, struct tty *tp, size_t len); void ttyoutq_free(struct ttyoutq *to); size_t ttyoutq_read(struct ttyoutq *to, void *buf, size_t len); int ttyoutq_read_uio(struct ttyoutq *to, struct tty *tp, struct uio *uio); |