tty: Fix PPP hang under load
Alan Cox [Fri, 2 Jan 2009 13:44:56 +0000 (13:44 +0000)]
Signed-off-by: Alan Cox <alan@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

drivers/char/tty_ldisc.c
include/linux/tty.h

index f307f13..7a84b40 100644 (file)
@@ -316,8 +316,7 @@ struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
 {
        /* wait_event is a macro */
        wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
-       if (tty->ldisc.refcount == 0)
-               printk(KERN_ERR "tty_ldisc_ref_wait\n");
+       WARN_ON(tty->ldisc.refcount == 0);
        return &tty->ldisc;
 }
 
@@ -376,15 +375,17 @@ EXPORT_SYMBOL_GPL(tty_ldisc_deref);
  *     @tty: terminal to activate ldisc on
  *
  *     Set the TTY_LDISC flag when the line discipline can be called
- *     again. Do necessary wakeups for existing sleepers.
+ *     again. Do necessary wakeups for existing sleepers. Clear the LDISC
+ *     changing flag to indicate any ldisc change is now over.
  *
- *     Note: nobody should set this bit except via this function. Clearing
- *     directly is allowed.
+ *     Note: nobody should set the TTY_LDISC bit except via this function.
+ *     Clearing directly is allowed.
  */
 
 void tty_ldisc_enable(struct tty_struct *tty)
 {
        set_bit(TTY_LDISC, &tty->flags);
+       clear_bit(TTY_LDISC_CHANGING, &tty->flags);
        wake_up(&tty_ldisc_wait);
 }
 
@@ -496,7 +497,14 @@ restart:
         *      reference to the line discipline. The TTY_LDISC bit
         *      prevents anyone taking a reference once it is clear.
         *      We need the lock to avoid racing reference takers.
+        *
+        *      We must clear the TTY_LDISC bit here to avoid a livelock
+        *      with a userspace app continually trying to use the tty in
+        *      parallel to the change and re-referencing the tty.
         */
+       clear_bit(TTY_LDISC, &tty->flags);
+       if (o_tty)
+               clear_bit(TTY_LDISC, &o_tty->flags);
 
        spin_lock_irqsave(&tty_ldisc_lock, flags);
        if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) {
@@ -528,7 +536,7 @@ restart:
         *      If the TTY_LDISC bit is set, then we are racing against
         *      another ldisc change
         */
-       if (!test_bit(TTY_LDISC, &tty->flags)) {
+       if (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
                struct tty_ldisc *ld;
                spin_unlock_irqrestore(&tty_ldisc_lock, flags);
                tty_ldisc_put(new_ldisc.ops);
@@ -536,10 +544,14 @@ restart:
                tty_ldisc_deref(ld);
                goto restart;
        }
-
-       clear_bit(TTY_LDISC, &tty->flags);
+       /*
+        *      This flag is used to avoid two parallel ldisc changes. Once
+        *      open and close are fine grained locked this may work better
+        *      as a mutex shared with the open/close/hup paths
+        */
+       set_bit(TTY_LDISC_CHANGING, &tty->flags);
        if (o_tty)
-               clear_bit(TTY_LDISC, &o_tty->flags);
+               set_bit(TTY_LDISC_CHANGING, &o_tty->flags);
        spin_unlock_irqrestore(&tty_ldisc_lock, flags);
        
        /*
index f881697..bbbeaef 100644 (file)
@@ -301,6 +301,7 @@ struct tty_struct {
 #define TTY_PUSH               6       /* n_tty private */
 #define TTY_CLOSING            7       /* ->close() in progress */
 #define TTY_LDISC              9       /* Line discipline attached */
+#define TTY_LDISC_CHANGING     10      /* Line discipline changing */
 #define TTY_HW_COOK_OUT        14      /* Hardware can do output cooking */
 #define TTY_HW_COOK_IN                 15      /* Hardware can do input cooking */
 #define TTY_PTY_LOCK           16      /* pty private */