tty: Separate release semantics of ldisc reference
authorPeter Hurley <peter@hurleysoftware.com>
Mon, 11 Mar 2013 20:44:40 +0000 (16:44 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 18 Mar 2013 23:48:42 +0000 (16:48 -0700)
tty_ldisc_ref()/tty_ldisc_unref() have usage semantics
equivalent to down_read_trylock()/up_read(). Only
callers of tty_ldisc_put() are performing the additional
operations necessary for proper ldisc teardown, and then only
after ensuring no outstanding 'read lock' remains.

Thus, tty_ldisc_unref() should never be the last reference;
WARN if it is. Conversely, tty_ldisc_put() should never be
destructing if the use count != 1.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/tty_ldisc.c

index 328ff5b544a5622405d98bf6c1da9107bdf74ef6..9362a1030c959df1c72fe47df6adc9cc349896f7 100644 (file)
@@ -49,37 +49,6 @@ static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld)
        return ld;
 }
 
-static void put_ldisc(struct tty_ldisc *ld)
-{
-       unsigned long flags;
-
-       if (WARN_ON_ONCE(!ld))
-               return;
-
-       /*
-        * If this is the last user, free the ldisc, and
-        * release the ldisc ops.
-        *
-        * We really want an "atomic_dec_and_raw_lock_irqsave()",
-        * but we don't have it, so this does it by hand.
-        */
-       raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
-       if (atomic_dec_and_test(&ld->users)) {
-               struct tty_ldisc_ops *ldo = ld->ops;
-
-               ldo->refcount--;
-               module_put(ldo->owner);
-               raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-
-               kfree(ld);
-               return;
-       }
-       raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-
-       if (waitqueue_active(&ld->wq_idle))
-               wake_up(&ld->wq_idle);
-}
-
 /**
  *     tty_register_ldisc      -       install a line discipline
  *     @disc: ldisc number
@@ -363,13 +332,45 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref);
 
 void tty_ldisc_deref(struct tty_ldisc *ld)
 {
-       put_ldisc(ld);
+       unsigned long flags;
+
+       if (WARN_ON_ONCE(!ld))
+               return;
+
+       raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
+       /*
+        * WARNs if one-too-many reader references were released
+        * - the last reference must be released with tty_ldisc_put
+        */
+       WARN_ON(atomic_dec_and_test(&ld->users));
+       raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+
+       if (waitqueue_active(&ld->wq_idle))
+               wake_up(&ld->wq_idle);
 }
 EXPORT_SYMBOL_GPL(tty_ldisc_deref);
 
+/**
+ *     tty_ldisc_put           -       release the ldisc
+ *
+ *     Complement of tty_ldisc_get().
+ */
 static inline void tty_ldisc_put(struct tty_ldisc *ld)
 {
-       put_ldisc(ld);
+       unsigned long flags;
+
+       if (WARN_ON_ONCE(!ld))
+               return;
+
+       raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
+
+       /* unreleased reader reference(s) will cause this WARN */
+       WARN_ON(!atomic_dec_and_test(&ld->users));
+
+       ld->ops->refcount--;
+       module_put(ld->ops->owner);
+       kfree(ld);
+       raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);
 }
 
 /**
@@ -1001,7 +1002,7 @@ void tty_ldisc_init(struct tty_struct *tty)
  */
 void tty_ldisc_deinit(struct tty_struct *tty)
 {
-       put_ldisc(tty->ldisc);
+       tty_ldisc_put(tty->ldisc);
        tty_ldisc_assign(tty, NULL);
 }