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 */ |
