summaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorPeter Hurley <peter@hurleysoftware.com>2013-03-06 14:20:52 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-03-19 00:11:59 +0100
commit70bc126471af30bb115e635512dcf6d86fe6e29a (patch)
treee6fc82736558f3a1b1f1601e237bc395b78a9dcd /drivers/tty
parentn_tty: Inline check_unthrottle() at lone call site (diff)
downloadlinux-70bc126471af30bb115e635512dcf6d86fe6e29a.tar.xz
linux-70bc126471af30bb115e635512dcf6d86fe6e29a.zip
tty: Add safe tty throttle/unthrottle functions
The tty driver can become stuck throttled due to race conditions between throttle and unthrottle, when the decision to throttle or unthrottle is conditional. The following example helps to illustrate the race: CPU 0 | CPU 1 | if (condition A) | | <processing such that A not true> | if (!condition A) | unthrottle() throttle() | | Note the converse is also possible; ie., CPU 0 | CPU 1 | | if (!condition A) <processing such that A true> | if (condition A) | throttle() | | unthrottle() | Add new throttle/unthrottle functions based on the familiar model of task state and schedule/wake. For example, while (1) { tty_set_flow_change(tty, TTY_THROTTLE_SAFE); if (!condition) break; if (!tty_throttle_safe(tty)) break; } __tty_set_flow_change(tty, 0); In this example, if an unthrottle occurs after the condition is evaluated but before tty_throttle_safe(), then tty_throttle_safe() will return non-zero, looping and forcing the re-evaluation of condition. Reported-by: Vincent Pillet <vincentx.pillet@intel.com> Signed-off-by: Peter Hurley <peter@hurleysoftware.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/tty_ioctl.c64
1 files changed, 64 insertions, 0 deletions
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
index d58b92cc187c..132d452578bb 100644
--- a/drivers/tty/tty_ioctl.c
+++ b/drivers/tty/tty_ioctl.c
@@ -106,6 +106,7 @@ void tty_throttle(struct tty_struct *tty)
if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) &&
tty->ops->throttle)
tty->ops->throttle(tty);
+ tty->flow_change = 0;
mutex_unlock(&tty->termios_mutex);
}
EXPORT_SYMBOL(tty_throttle);
@@ -129,11 +130,74 @@ void tty_unthrottle(struct tty_struct *tty)
if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) &&
tty->ops->unthrottle)
tty->ops->unthrottle(tty);
+ tty->flow_change = 0;
mutex_unlock(&tty->termios_mutex);
}
EXPORT_SYMBOL(tty_unthrottle);
/**
+ * tty_throttle_safe - flow control
+ * @tty: terminal
+ *
+ * Similar to tty_throttle() but will only attempt throttle
+ * if tty->flow_change is TTY_THROTTLE_SAFE. Prevents an accidental
+ * throttle due to race conditions when throttling is conditional
+ * on factors evaluated prior to throttling.
+ *
+ * Returns 0 if tty is throttled (or was already throttled)
+ */
+
+int tty_throttle_safe(struct tty_struct *tty)
+{
+ int ret = 0;
+
+ mutex_lock(&tty->termios_mutex);
+ if (!test_bit(TTY_THROTTLED, &tty->flags)) {
+ if (tty->flow_change != TTY_THROTTLE_SAFE)
+ ret = 1;
+ else {
+ __set_bit(TTY_THROTTLED, &tty->flags);
+ if (tty->ops->throttle)
+ tty->ops->throttle(tty);
+ }
+ }
+ mutex_unlock(&tty->termios_mutex);
+
+ return ret;
+}
+
+/**
+ * tty_unthrottle_safe - flow control
+ * @tty: terminal
+ *
+ * Similar to tty_unthrottle() but will only attempt unthrottle
+ * if tty->flow_change is TTY_UNTHROTTLE_SAFE. Prevents an accidental
+ * unthrottle due to race conditions when unthrottling is conditional
+ * on factors evaluated prior to unthrottling.
+ *
+ * Returns 0 if tty is unthrottled (or was already unthrottled)
+ */
+
+int tty_unthrottle_safe(struct tty_struct *tty)
+{
+ int ret = 0;
+
+ mutex_lock(&tty->termios_mutex);
+ if (test_bit(TTY_THROTTLED, &tty->flags)) {
+ if (tty->flow_change != TTY_UNTHROTTLE_SAFE)
+ ret = 1;
+ else {
+ __clear_bit(TTY_THROTTLED, &tty->flags);
+ if (tty->ops->unthrottle)
+ tty->ops->unthrottle(tty);
+ }
+ }
+ mutex_unlock(&tty->termios_mutex);
+
+ return ret;
+}
+
+/**
* tty_wait_until_sent - wait for I/O to finish
* @tty: tty we are waiting for
* @timeout: how long we will wait