summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/libc/gen/tcsendbreak.333
-rw-r--r--share/man/man4/tty.413
-rw-r--r--sys/kern/tty.c102
-rw-r--r--sys/kern/tty_inq.c8
-rw-r--r--sys/kern/tty_outq.c8
-rw-r--r--sys/sys/tty.h1
-rw-r--r--sys/sys/ttyqueue.h4
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);
OpenPOWER on IntegriCloud