svn commit: r314538 - in stable/11: lib/libc/gen share/man/man4 sys/kern sys/sys

Ian Lepore ian at FreeBSD.org
Thu Mar 2 04:23:55 UTC 2017


Author: ian
Date: Thu Mar  2 04:23:53 2017
New Revision: 314538
URL: https://svnweb.freebsd.org/changeset/base/314538

Log:
  MFC r311954, r311996, r312077, r312080:
  
    Rework tty_drain() to poll the hardware for completion, and restore
    drain timeout handling to historical freebsd behavior.
  
    The primary reason for these changes is the need to have tty_drain() call
    ttydevsw_busy() at some reasonable sub-second rate, to poll hardware that
    doesn't signal an interrupt when the transmit shift register becomes empty
    (which includes virtually all USB serial hardware).  Such hardware hangs
    in a ttyout wait, because it never gets an opportunity to trigger a wakeup
    from the sleep in tty_drain() by calling ttydisc_getc() again, after
    handing the last of the buffered data to the hardware.
  
    Restructure the tty_drain loop so that device-busy is checked one more time
    after tty_timedwait() returns an error only if the error is EWOULDBLOCK;
    other errors cause an immediate return.  This fixes the case of the tty
    disappearing while in tty_drain().
  
    Check tty_gone() after allocating IO buffers.  The tty lock has to be
    dropped then reacquired due to using M_WAITOK, which opens a window in
    which the tty device can disappear.  Check for this and return ENXIO
    back up the call chain so that callers can cope.
  
    Correct the comments about how much buffer is allocated.

Modified:
  stable/11/lib/libc/gen/tcsendbreak.3
  stable/11/share/man/man4/tty.4
  stable/11/sys/kern/tty.c
  stable/11/sys/kern/tty_inq.c
  stable/11/sys/kern/tty_outq.c
  stable/11/sys/sys/tty.h
  stable/11/sys/sys/ttyqueue.h
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/lib/libc/gen/tcsendbreak.3
==============================================================================
--- stable/11/lib/libc/gen/tcsendbreak.3	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/lib/libc/gen/tcsendbreak.3	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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.
+

Modified: stable/11/share/man/man4/tty.4
==============================================================================
--- stable/11/share/man/man4/tty.4	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/share/man/man4/tty.4	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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 typin
 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.

Modified: stable/11/sys/kern/tty.c
==============================================================================
--- stable/11/sys/kern/tty.c	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/sys/kern/tty.c	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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
 			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, vo
 	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
 			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
 	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) {

Modified: stable/11/sys/kern/tty_inq.c
==============================================================================
--- stable/11/sys/kern/tty_inq.c	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/sys/kern/tty_inq.c	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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
 		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

Modified: stable/11/sys/kern/tty_outq.c
==============================================================================
--- stable/11/sys/kern/tty_outq.c	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/sys/kern/tty_outq.c	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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, stru
 		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

Modified: stable/11/sys/sys/tty.h
==============================================================================
--- stable/11/sys/sys/tty.h	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/sys/sys/tty.h	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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. */

Modified: stable/11/sys/sys/ttyqueue.h
==============================================================================
--- stable/11/sys/sys/ttyqueue.h	Thu Mar  2 04:11:18 2017	(r314537)
+++ stable/11/sys/sys/ttyqueue.h	Thu Mar  2 04:23:53 2017	(r314538)
@@ -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
 
 /* 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);


More information about the svn-src-all mailing list