summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2017-03-02 04:23:53 +0000
committerian <ian@FreeBSD.org>2017-03-02 04:23:53 +0000
commit441c0c0ed529fc9fb17e0feb520d123e21acb7f5 (patch)
tree798f99c36b3f220290c85d5b78d50866d47b5875 /sys
parenta8e4be5c56fa510af7512ec8fc2bfe5dc41ffe45 (diff)
downloadFreeBSD-src-441c0c0ed529fc9fb17e0feb520d123e21acb7f5.zip
FreeBSD-src-441c0c0ed529fc9fb17e0feb520d123e21acb7f5.tar.gz
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.
Diffstat (limited to 'sys')
-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
5 files changed, 93 insertions, 30 deletions
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