tty: Strengthen no-subsequent-use guarantee of tty_ldisc_halt()
[firefly-linux-kernel-4.4.55.git] / drivers / tty / tty_ldisc.c
index f691c7604d9a05285014ceb0b21b35ce18097a68..525ee535c10db050b664d130da77aeee225fc83d 100644 (file)
@@ -530,24 +530,38 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout)
 /**
  *     tty_ldisc_halt          -       shut down the line discipline
  *     @tty: tty device
+ *     @pending: returns true if work was scheduled when cancelled
+ *               (can be set to NULL)
+ *     @timeout: # of jiffies to wait for ldisc refs to be released
  *
  *     Shut down the line discipline and work queue for this tty device.
  *     The TTY_LDISC flag being cleared ensures no further references can
  *     be obtained while the delayed work queue halt ensures that no more
  *     data is fed to the ldisc.
  *
+ *     Furthermore, guarantee that existing ldisc references have been
+ *     released, which in turn, guarantees that no future buffer work
+ *     can be rescheduled.
+ *
  *     You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
  *     in order to make sure any currently executing ldisc work is also
  *     flushed.
  */
 
-static int tty_ldisc_halt(struct tty_struct *tty)
+static int tty_ldisc_halt(struct tty_struct *tty, int *pending, long timeout)
 {
-       int scheduled;
+       int scheduled, retval;
+
        clear_bit(TTY_LDISC, &tty->flags);
+       retval = tty_ldisc_wait_idle(tty, timeout);
+       if (retval)
+               return retval;
+
        scheduled = cancel_work_sync(&tty->port->buf.work);
        set_bit(TTY_LDISC_HALTED, &tty->flags);
-       return scheduled;
+       if (pending)
+               *pending = scheduled;
+       return 0;
 }
 
 /**
@@ -688,9 +702,9 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
         *      parallel to the change and re-referencing the tty.
         */
 
-       work = tty_ldisc_halt(tty);
-       if (o_tty)
-               o_work = tty_ldisc_halt(o_tty);
+       retval = tty_ldisc_halt(tty, &work, 5 * HZ);
+       if (!retval && o_tty)
+               retval = tty_ldisc_halt(o_tty, &o_work, 5 * HZ);
 
        /*
         * Wait for ->hangup_work and ->buf.work handlers to terminate.
@@ -701,8 +715,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
        tty_ldisc_flush_works(tty);
 
-       retval = tty_ldisc_wait_idle(tty, 5 * HZ);
-
        tty_lock(tty);
        mutex_lock(&tty->ldisc_mutex);
 
@@ -921,11 +933,6 @@ int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
 
 static void tty_ldisc_kill(struct tty_struct *tty)
 {
-       /* There cannot be users from userspace now. But there still might be
-        * drivers holding a reference via tty_ldisc_ref. Do not steal them the
-        * ldisc until they are done. */
-       tty_ldisc_wait_idle(tty, MAX_SCHEDULE_TIMEOUT);
-
        mutex_lock(&tty->ldisc_mutex);
        /*
         * Now kill off the ldisc
@@ -958,13 +965,12 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
         * race with the set_ldisc code path.
         */
 
-       tty_ldisc_halt(tty);
-       if (o_tty)
-               tty_ldisc_halt(o_tty);
-
+       tty_ldisc_halt(tty, NULL, MAX_SCHEDULE_TIMEOUT);
        tty_ldisc_flush_works(tty);
-       if (o_tty)
+       if (o_tty) {
+               tty_ldisc_halt(o_tty, NULL, MAX_SCHEDULE_TIMEOUT);
                tty_ldisc_flush_works(o_tty);
+       }
 
        tty_lock_pair(tty, o_tty);
        /* This will need doing differently if we need to lock */