]> git.karo-electronics.de Git - linux-beck.git/blobdiff - drivers/tty/tty_ldisc.c
Merge branches 'x86-fixes-for-linus', 'perf-fixes-for-linus' and 'sched-fixes-for...
[linux-beck.git] / drivers / tty / tty_ldisc.c
index 412f9775d19c5f2deee27b2174532e35ab3e14d1..4214d58276f709a8743c03f431cef5b6360c2b46 100644 (file)
@@ -47,6 +47,7 @@
 
 static DEFINE_SPINLOCK(tty_ldisc_lock);
 static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
+static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_idle);
 /* Line disc dispatch table */
 static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
 
@@ -83,6 +84,7 @@ static void put_ldisc(struct tty_ldisc *ld)
                return;
        }
        local_irq_restore(flags);
+       wake_up(&tty_ldisc_idle);
 }
 
 /**
@@ -452,6 +454,8 @@ static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
                 /* BTM here locks versus a hangup event */
                WARN_ON(!tty_locked());
                ret = ld->ops->open(tty);
+               if (ret)
+                       clear_bit(TTY_LDISC_OPEN, &tty->flags);
                return ret;
        }
        return 0;
@@ -530,6 +534,23 @@ static int tty_ldisc_halt(struct tty_struct *tty)
        return cancel_delayed_work_sync(&tty->buf.work);
 }
 
+/**
+ *     tty_ldisc_wait_idle     -       wait for the ldisc to become idle
+ *     @tty: tty to wait for
+ *
+ *     Wait for the line discipline to become idle. The discipline must
+ *     have been halted for this to guarantee it remains idle.
+ */
+static int tty_ldisc_wait_idle(struct tty_struct *tty)
+{
+       int ret;
+       ret = wait_event_interruptible_timeout(tty_ldisc_idle,
+                       atomic_read(&tty->ldisc->users) == 1, 5 * HZ);
+       if (ret < 0)
+               return ret;
+       return ret > 0 ? 0 : -EBUSY;
+}
+
 /**
  *     tty_set_ldisc           -       set line discipline
  *     @tty: the terminal to set
@@ -634,8 +655,17 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
        flush_scheduled_work();
 
+       retval = tty_ldisc_wait_idle(tty);
+
        tty_lock();
        mutex_lock(&tty->ldisc_mutex);
+
+       /* handle wait idle failure locked */
+       if (retval) {
+               tty_ldisc_put(new_ldisc);
+               goto enable;
+       }
+
        if (test_bit(TTY_HUPPED, &tty->flags)) {
                /* We were raced by the hangup method. It will have stomped
                   the ldisc data and closed the ldisc down */
@@ -669,6 +699,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
        tty_ldisc_put(o_ldisc);
 
+enable:
        /*
         *      Allow ldisc referencing to occur again
         */
@@ -714,9 +745,12 @@ static void tty_reset_termios(struct tty_struct *tty)
  *     state closed
  */
 
-static void tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
+static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
 {
-       struct tty_ldisc *ld;
+       struct tty_ldisc *ld = tty_ldisc_get(ldisc);
+
+       if (IS_ERR(ld))
+               return -1;
 
        tty_ldisc_close(tty, tty->ldisc);
        tty_ldisc_put(tty->ldisc);
@@ -724,10 +758,10 @@ static void tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
        /*
         *      Switch the line discipline back
         */
-       ld = tty_ldisc_get(ldisc);
-       BUG_ON(IS_ERR(ld));
        tty_ldisc_assign(tty, ld);
        tty_set_termios_ldisc(tty, ldisc);
+
+       return 0;
 }
 
 /**
@@ -802,13 +836,16 @@ void tty_ldisc_hangup(struct tty_struct *tty)
           a FIXME */
        if (tty->ldisc) {       /* Not yet closed */
                if (reset == 0) {
-                       tty_ldisc_reinit(tty, tty->termios->c_line);
-                       err = tty_ldisc_open(tty, tty->ldisc);
+
+                       if (!tty_ldisc_reinit(tty, tty->termios->c_line))
+                               err = tty_ldisc_open(tty, tty->ldisc);
+                       else
+                               err = 1;
                }
                /* If the re-open fails or we reset then go to N_TTY. The
                   N_TTY open cannot fail */
                if (reset || err) {
-                       tty_ldisc_reinit(tty, N_TTY);
+                       BUG_ON(tty_ldisc_reinit(tty, N_TTY));
                        WARN_ON(tty_ldisc_open(tty, tty->ldisc));
                }
                tty_ldisc_enable(tty);