]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
tty: Avoid dropping ldisc_mutex over hangup tty re-initialization
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 4 Oct 2009 04:44:21 +0000 (21:44 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 12 Oct 2009 19:40:05 +0000 (12:40 -0700)
commit 0b5759c654e74c8dc317ea2c6b3a7476160f688a upstream.

A couple of people have hit the WARN_ON() in drivers/char/tty_io.c,
tty_open() that is unhappy about seeing the tty line discipline go away
during the tty hangup. See for example

http://bugzilla.kernel.org/show_bug.cgi?id=14255

and the reason is that we do the tty_ldisc_halt() outside the
ldisc_mutex in order to be able to flush the scheduled work without a
deadlock with vhangup_work.

However, it turns out that we can solve this particular case by

 - using "cancel_delayed_work_sync()" in tty_ldisc_halt(), which waits
   for just the particular work, rather than synchronizing with any
   random outstanding pending work.

   This won't deadlock, since the buf.work we synchronize with doesn't
   care about the ldisc_mutex, it just flushes the tty ldisc buffers.

 - realize that for this particular case, we don't need to wait for any
   hangup work, because we are inside the hangup codepaths ourselves.

so as a result we can just drop the flush_scheduled_work() entirely, and
then move the tty_ldisc_halt() call to inside the mutex.  That way we
never expose the partially torn down ldisc state to tty_open(), and hold
the ldisc_mutex over the whole sequence.

Reported-by: Ingo Molnar <mingo@elte.hu>
Reported-by: Heinz Diehl <htd@fancy-poultry.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/char/tty_ldisc.c

index e48af9f79219a0dec76315724eeeaaae3a4102d6..414372a442d9bed1f0d62de9b0b6a1d88483c843 100644 (file)
@@ -516,7 +516,7 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
 static int tty_ldisc_halt(struct tty_struct *tty)
 {
        clear_bit(TTY_LDISC, &tty->flags);
-       return cancel_delayed_work(&tty->buf.work);
+       return cancel_delayed_work_sync(&tty->buf.work);
 }
 
 /**
@@ -754,12 +754,9 @@ void tty_ldisc_hangup(struct tty_struct *tty)
         * N_TTY.
         */
        if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
-               /* Make sure the old ldisc is quiescent */
-               tty_ldisc_halt(tty);
-               flush_scheduled_work();
-
                /* Avoid racing set_ldisc or tty_ldisc_release */
                mutex_lock(&tty->ldisc_mutex);
+               tty_ldisc_halt(tty);
                if (tty->ldisc) {       /* Not yet closed */
                        /* Switch back to N_TTY */
                        tty_ldisc_reinit(tty);