Commit c9b3976e3fec266be25c5001a70aa0a890b6c476
Committed by
Linus Torvalds
1 parent
d0eafc7db8
Exists in
master
and in
20 other branches
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 | /* |
include/linux/tty.h
... | ... | @@ -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 */ |