diff options
author | Peter Hurley <peter@hurleysoftware.com> | 2013-03-11 16:44:28 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-03-18 19:38:58 -0400 |
commit | cf5284765862ac65e4a3e5b34652e593ffda2bdf (patch) | |
tree | 7e87db80173d9a032cd4bb568728c6aa9a6b03d3 /drivers/tty/tty_ldisc.c | |
parent | 11cf48eab21887a120f7f47c67b44a829e3c371d (diff) |
tty: Strengthen no-subsequent-use guarantee of tty_ldisc_halt()
In preparation for destructing and freeing the tty, the line discipline
must first be brought to an inactive state before it can be destructed.
This line discipline shutdown must:
- disallow new users of the ldisc
- wait for existing ldisc users to finish
- only then, cancel/flush their pending/running work
Factor tty_ldisc_wait_idle() from tty_set_ldisc() and tty_ldisc_kill()
to ensure this shutdown order.
Failure to provide this guarantee can result in scheduled work
running after the tty has already been freed, as indicated in the
following log message:
[ 88.331234] WARNING: at drivers/tty/tty_buffer.c:435 flush_to_ldisc+0x194/0x1d0()
[ 88.334505] Hardware name: Bochs
[ 88.335618] tty is bad=-1
[ 88.335703] Modules linked in: netconsole configfs bnep rfcomm bluetooth ......
[ 88.345272] Pid: 39, comm: kworker/1:1 Tainted: G W 3.7.0-next-20121129+ttydebug-xeon #20121129+ttydebug
[ 88.347736] Call Trace:
[ 88.349024] [<ffffffff81058aff>] warn_slowpath_common+0x7f/0xc0
[ 88.350383] [<ffffffff81058bf6>] warn_slowpath_fmt+0x46/0x50
[ 88.351745] [<ffffffff81432bd4>] flush_to_ldisc+0x194/0x1d0
[ 88.353047] [<ffffffff816f7fe1>] ? _raw_spin_unlock_irq+0x21/0x50
[ 88.354190] [<ffffffff8108a809>] ? finish_task_switch+0x49/0xe0
[ 88.355436] [<ffffffff81077ad1>] process_one_work+0x121/0x490
[ 88.357674] [<ffffffff81432a40>] ? __tty_buffer_flush+0x90/0x90
[ 88.358954] [<ffffffff81078c84>] worker_thread+0x164/0x3e0
[ 88.360247] [<ffffffff81078b20>] ? manage_workers+0x120/0x120
[ 88.361282] [<ffffffff8107e230>] kthread+0xc0/0xd0
[ 88.362284] [<ffffffff816f0000>] ? cmos_do_probe+0x2eb/0x3bf
[ 88.363391] [<ffffffff8107e170>] ? flush_kthread_worker+0xb0/0xb0
[ 88.364797] [<ffffffff816fff6c>] ret_from_fork+0x7c/0xb0
[ 88.366087] [<ffffffff8107e170>] ? flush_kthread_worker+0xb0/0xb0
[ 88.367266] ---[ end trace 453a7c9f38fbfec0 ]---
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty/tty_ldisc.c')
-rw-r--r-- | drivers/tty/tty_ldisc.c | 42 |
1 files changed, 24 insertions, 18 deletions
diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c index f691c7604d9a..525ee535c10d 100644 --- a/drivers/tty/tty_ldisc.c +++ b/drivers/tty/tty_ldisc.c | |||
@@ -530,24 +530,38 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout) | |||
530 | /** | 530 | /** |
531 | * tty_ldisc_halt - shut down the line discipline | 531 | * tty_ldisc_halt - shut down the line discipline |
532 | * @tty: tty device | 532 | * @tty: tty device |
533 | * @pending: returns true if work was scheduled when cancelled | ||
534 | * (can be set to NULL) | ||
535 | * @timeout: # of jiffies to wait for ldisc refs to be released | ||
533 | * | 536 | * |
534 | * Shut down the line discipline and work queue for this tty device. | 537 | * Shut down the line discipline and work queue for this tty device. |
535 | * The TTY_LDISC flag being cleared ensures no further references can | 538 | * The TTY_LDISC flag being cleared ensures no further references can |
536 | * be obtained while the delayed work queue halt ensures that no more | 539 | * be obtained while the delayed work queue halt ensures that no more |
537 | * data is fed to the ldisc. | 540 | * data is fed to the ldisc. |
538 | * | 541 | * |
542 | * Furthermore, guarantee that existing ldisc references have been | ||
543 | * released, which in turn, guarantees that no future buffer work | ||
544 | * can be rescheduled. | ||
545 | * | ||
539 | * You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex) | 546 | * You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex) |
540 | * in order to make sure any currently executing ldisc work is also | 547 | * in order to make sure any currently executing ldisc work is also |
541 | * flushed. | 548 | * flushed. |
542 | */ | 549 | */ |
543 | 550 | ||
544 | static int tty_ldisc_halt(struct tty_struct *tty) | 551 | static int tty_ldisc_halt(struct tty_struct *tty, int *pending, long timeout) |
545 | { | 552 | { |
546 | int scheduled; | 553 | int scheduled, retval; |
554 | |||
547 | clear_bit(TTY_LDISC, &tty->flags); | 555 | clear_bit(TTY_LDISC, &tty->flags); |
556 | retval = tty_ldisc_wait_idle(tty, timeout); | ||
557 | if (retval) | ||
558 | return retval; | ||
559 | |||
548 | scheduled = cancel_work_sync(&tty->port->buf.work); | 560 | scheduled = cancel_work_sync(&tty->port->buf.work); |
549 | set_bit(TTY_LDISC_HALTED, &tty->flags); | 561 | set_bit(TTY_LDISC_HALTED, &tty->flags); |
550 | return scheduled; | 562 | if (pending) |
563 | *pending = scheduled; | ||
564 | return 0; | ||
551 | } | 565 | } |
552 | 566 | ||
553 | /** | 567 | /** |
@@ -688,9 +702,9 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) | |||
688 | * parallel to the change and re-referencing the tty. | 702 | * parallel to the change and re-referencing the tty. |
689 | */ | 703 | */ |
690 | 704 | ||
691 | work = tty_ldisc_halt(tty); | 705 | retval = tty_ldisc_halt(tty, &work, 5 * HZ); |
692 | if (o_tty) | 706 | if (!retval && o_tty) |
693 | o_work = tty_ldisc_halt(o_tty); | 707 | retval = tty_ldisc_halt(o_tty, &o_work, 5 * HZ); |
694 | 708 | ||
695 | /* | 709 | /* |
696 | * Wait for ->hangup_work and ->buf.work handlers to terminate. | 710 | * Wait for ->hangup_work and ->buf.work handlers to terminate. |
@@ -701,8 +715,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) | |||
701 | 715 | ||
702 | tty_ldisc_flush_works(tty); | 716 | tty_ldisc_flush_works(tty); |
703 | 717 | ||
704 | retval = tty_ldisc_wait_idle(tty, 5 * HZ); | ||
705 | |||
706 | tty_lock(tty); | 718 | tty_lock(tty); |
707 | mutex_lock(&tty->ldisc_mutex); | 719 | mutex_lock(&tty->ldisc_mutex); |
708 | 720 | ||
@@ -921,11 +933,6 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty) | |||
921 | 933 | ||
922 | static void tty_ldisc_kill(struct tty_struct *tty) | 934 | static void tty_ldisc_kill(struct tty_struct *tty) |
923 | { | 935 | { |
924 | /* There cannot be users from userspace now. But there still might be | ||
925 | * drivers holding a reference via tty_ldisc_ref. Do not steal them the | ||
926 | * ldisc until they are done. */ | ||
927 | tty_ldisc_wait_idle(tty, MAX_SCHEDULE_TIMEOUT); | ||
928 | |||
929 | mutex_lock(&tty->ldisc_mutex); | 936 | mutex_lock(&tty->ldisc_mutex); |
930 | /* | 937 | /* |
931 | * Now kill off the ldisc | 938 | * Now kill off the ldisc |
@@ -958,13 +965,12 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty) | |||
958 | * race with the set_ldisc code path. | 965 | * race with the set_ldisc code path. |
959 | */ | 966 | */ |
960 | 967 | ||
961 | tty_ldisc_halt(tty); | 968 | tty_ldisc_halt(tty, NULL, MAX_SCHEDULE_TIMEOUT); |
962 | if (o_tty) | ||
963 | tty_ldisc_halt(o_tty); | ||
964 | |||
965 | tty_ldisc_flush_works(tty); | 969 | tty_ldisc_flush_works(tty); |
966 | if (o_tty) | 970 | if (o_tty) { |
971 | tty_ldisc_halt(o_tty, NULL, MAX_SCHEDULE_TIMEOUT); | ||
967 | tty_ldisc_flush_works(o_tty); | 972 | tty_ldisc_flush_works(o_tty); |
973 | } | ||
968 | 974 | ||
969 | tty_lock_pair(tty, o_tty); | 975 | tty_lock_pair(tty, o_tty); |
970 | /* This will need doing differently if we need to lock */ | 976 | /* This will need doing differently if we need to lock */ |