Commit c9b3976e3fec266be25c5001a70aa0a890b6c476

Authored by Alan Cox
Committed by Linus Torvalds
1 parent d0eafc7db8

tty: Fix PPP hang under load

Signed-off-by: Alan Cox <alan@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Showing 2 changed files with 22 additions and 9 deletions Side-by-side Diff

drivers/char/tty_ldisc.c
... ... @@ -316,8 +316,7 @@
316 316 {
317 317 /* wait_event is a macro */
318 318 wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
319   - if (tty->ldisc.refcount == 0)
320   - printk(KERN_ERR "tty_ldisc_ref_wait\n");
  319 + WARN_ON(tty->ldisc.refcount == 0);
321 320 return &tty->ldisc;
322 321 }
323 322  
324 323  
325 324  
... ... @@ -376,15 +375,17 @@
376 375 * @tty: terminal to activate ldisc on
377 376 *
378 377 * Set the TTY_LDISC flag when the line discipline can be called
379   - * again. Do necessary wakeups for existing sleepers.
  378 + * again. Do necessary wakeups for existing sleepers. Clear the LDISC
  379 + * changing flag to indicate any ldisc change is now over.
380 380 *
381   - * Note: nobody should set this bit except via this function. Clearing
382   - * directly is allowed.
  381 + * Note: nobody should set the TTY_LDISC bit except via this function.
  382 + * Clearing directly is allowed.
383 383 */
384 384  
385 385 void tty_ldisc_enable(struct tty_struct *tty)
386 386 {
387 387 set_bit(TTY_LDISC, &tty->flags);
  388 + clear_bit(TTY_LDISC_CHANGING, &tty->flags);
388 389 wake_up(&tty_ldisc_wait);
389 390 }
390 391  
391 392  
... ... @@ -496,7 +497,14 @@
496 497 * reference to the line discipline. The TTY_LDISC bit
497 498 * prevents anyone taking a reference once it is clear.
498 499 * We need the lock to avoid racing reference takers.
  500 + *
  501 + * We must clear the TTY_LDISC bit here to avoid a livelock
  502 + * with a userspace app continually trying to use the tty in
  503 + * parallel to the change and re-referencing the tty.
499 504 */
  505 + clear_bit(TTY_LDISC, &tty->flags);
  506 + if (o_tty)
  507 + clear_bit(TTY_LDISC, &o_tty->flags);
500 508  
501 509 spin_lock_irqsave(&tty_ldisc_lock, flags);
502 510 if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) {
... ... @@ -528,7 +536,7 @@
528 536 * If the TTY_LDISC bit is set, then we are racing against
529 537 * another ldisc change
530 538 */
531   - if (!test_bit(TTY_LDISC, &tty->flags)) {
  539 + if (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
532 540 struct tty_ldisc *ld;
533 541 spin_unlock_irqrestore(&tty_ldisc_lock, flags);
534 542 tty_ldisc_put(new_ldisc.ops);
535 543  
... ... @@ -536,10 +544,14 @@
536 544 tty_ldisc_deref(ld);
537 545 goto restart;
538 546 }
539   -
540   - clear_bit(TTY_LDISC, &tty->flags);
  547 + /*
  548 + * This flag is used to avoid two parallel ldisc changes. Once
  549 + * open and close are fine grained locked this may work better
  550 + * as a mutex shared with the open/close/hup paths
  551 + */
  552 + set_bit(TTY_LDISC_CHANGING, &tty->flags);
541 553 if (o_tty)
542   - clear_bit(TTY_LDISC, &o_tty->flags);
  554 + set_bit(TTY_LDISC_CHANGING, &o_tty->flags);
543 555 spin_unlock_irqrestore(&tty_ldisc_lock, flags);
544 556  
545 557 /*
... ... @@ -301,6 +301,7 @@
301 301 #define TTY_PUSH 6 /* n_tty private */
302 302 #define TTY_CLOSING 7 /* ->close() in progress */
303 303 #define TTY_LDISC 9 /* Line discipline attached */
  304 +#define TTY_LDISC_CHANGING 10 /* Line discipline changing */
304 305 #define TTY_HW_COOK_OUT 14 /* Hardware can do output cooking */
305 306 #define TTY_HW_COOK_IN 15 /* Hardware can do input cooking */
306 307 #define TTY_PTY_LOCK 16 /* pty private */