diff options
author | Alan Cox <alan@redhat.com> | 2009-01-02 08:44:56 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-01-02 13:19:38 -0500 |
commit | c9b3976e3fec266be25c5001a70aa0a890b6c476 (patch) | |
tree | 6742587ae3193fcfbee2b394aa4f5daca392da17 | |
parent | d0eafc7db8f170d534a16b5f04617e98ae2025de (diff) |
tty: Fix PPP hang under load
Signed-off-by: Alan Cox <alan@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/char/tty_ldisc.c | 30 | ||||
-rw-r--r-- | include/linux/tty.h | 1 |
2 files changed, 22 insertions, 9 deletions
diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index f307f135cbfb..7a84b406a952 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c | |||
@@ -316,8 +316,7 @@ struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty) | |||
316 | { | 316 | { |
317 | /* wait_event is a macro */ | 317 | /* wait_event is a macro */ |
318 | wait_event(tty_ldisc_wait, tty_ldisc_try(tty)); | 318 | wait_event(tty_ldisc_wait, tty_ldisc_try(tty)); |
319 | if (tty->ldisc.refcount == 0) | 319 | WARN_ON(tty->ldisc.refcount == 0); |
320 | printk(KERN_ERR "tty_ldisc_ref_wait\n"); | ||
321 | return &tty->ldisc; | 320 | return &tty->ldisc; |
322 | } | 321 | } |
323 | 322 | ||
@@ -376,15 +375,17 @@ EXPORT_SYMBOL_GPL(tty_ldisc_deref); | |||
376 | * @tty: terminal to activate ldisc on | 375 | * @tty: terminal to activate ldisc on |
377 | * | 376 | * |
378 | * Set the TTY_LDISC flag when the line discipline can be called | 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 | 381 | * Note: nobody should set the TTY_LDISC bit except via this function. |
382 | * directly is allowed. | 382 | * Clearing directly is allowed. |
383 | */ | 383 | */ |
384 | 384 | ||
385 | void tty_ldisc_enable(struct tty_struct *tty) | 385 | void tty_ldisc_enable(struct tty_struct *tty) |
386 | { | 386 | { |
387 | set_bit(TTY_LDISC, &tty->flags); | 387 | set_bit(TTY_LDISC, &tty->flags); |
388 | clear_bit(TTY_LDISC_CHANGING, &tty->flags); | ||
388 | wake_up(&tty_ldisc_wait); | 389 | wake_up(&tty_ldisc_wait); |
389 | } | 390 | } |
390 | 391 | ||
@@ -496,7 +497,14 @@ restart: | |||
496 | * reference to the line discipline. The TTY_LDISC bit | 497 | * reference to the line discipline. The TTY_LDISC bit |
497 | * prevents anyone taking a reference once it is clear. | 498 | * prevents anyone taking a reference once it is clear. |
498 | * We need the lock to avoid racing reference takers. | 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 | spin_lock_irqsave(&tty_ldisc_lock, flags); | 509 | spin_lock_irqsave(&tty_ldisc_lock, flags); |
502 | if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) { | 510 | if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) { |
@@ -528,7 +536,7 @@ restart: | |||
528 | * If the TTY_LDISC bit is set, then we are racing against | 536 | * If the TTY_LDISC bit is set, then we are racing against |
529 | * another ldisc change | 537 | * another ldisc change |
530 | */ | 538 | */ |
531 | if (!test_bit(TTY_LDISC, &tty->flags)) { | 539 | if (test_bit(TTY_LDISC_CHANGING, &tty->flags)) { |
532 | struct tty_ldisc *ld; | 540 | struct tty_ldisc *ld; |
533 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | 541 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); |
534 | tty_ldisc_put(new_ldisc.ops); | 542 | tty_ldisc_put(new_ldisc.ops); |
@@ -536,10 +544,14 @@ restart: | |||
536 | tty_ldisc_deref(ld); | 544 | tty_ldisc_deref(ld); |
537 | goto restart; | 545 | goto restart; |
538 | } | 546 | } |
539 | 547 | /* | |
540 | clear_bit(TTY_LDISC, &tty->flags); | 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 | if (o_tty) | 553 | if (o_tty) |
542 | clear_bit(TTY_LDISC, &o_tty->flags); | 554 | set_bit(TTY_LDISC_CHANGING, &o_tty->flags); |
543 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); | 555 | spin_unlock_irqrestore(&tty_ldisc_lock, flags); |
544 | 556 | ||
545 | /* | 557 | /* |
diff --git a/include/linux/tty.h b/include/linux/tty.h index f88169787a5f..bbbeaef99626 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h | |||
@@ -301,6 +301,7 @@ struct tty_struct { | |||
301 | #define TTY_PUSH 6 /* n_tty private */ | 301 | #define TTY_PUSH 6 /* n_tty private */ |
302 | #define TTY_CLOSING 7 /* ->close() in progress */ | 302 | #define TTY_CLOSING 7 /* ->close() in progress */ |
303 | #define TTY_LDISC 9 /* Line discipline attached */ | 303 | #define TTY_LDISC 9 /* Line discipline attached */ |
304 | #define TTY_LDISC_CHANGING 10 /* Line discipline changing */ | ||
304 | #define TTY_HW_COOK_OUT 14 /* Hardware can do output cooking */ | 305 | #define TTY_HW_COOK_OUT 14 /* Hardware can do output cooking */ |
305 | #define TTY_HW_COOK_IN 15 /* Hardware can do input cooking */ | 306 | #define TTY_HW_COOK_IN 15 /* Hardware can do input cooking */ |
306 | #define TTY_PTY_LOCK 16 /* pty private */ | 307 | #define TTY_PTY_LOCK 16 /* pty private */ |