nfsd4: fix error return when pseudoroot missing
[safe/jmp/linux-2.6] / drivers / char / tty_ldisc.c
index fd175e6..aafdbae 100644 (file)
@@ -48,6 +48,41 @@ static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
 /* Line disc dispatch table */
 static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
 
+static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld)
+{
+       if (ld)
+               atomic_inc(&ld->users);
+       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_lock_irqsave()",
+        * but we don't have it, so this does it by hand.
+        */
+       local_irq_save(flags);
+       if (atomic_dec_and_lock(&ld->users, &tty_ldisc_lock)) {
+               struct tty_ldisc_ops *ldo = ld->ops;
+
+               ldo->refcount--;
+               module_put(ldo->owner);
+               spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+
+               kfree(ld);
+               return;
+       }
+       local_irq_restore(flags);
+}
+
 /**
  *     tty_register_ldisc      -       install a line discipline
  *     @disc: ldisc number
@@ -110,48 +145,33 @@ int tty_unregister_ldisc(int disc)
 }
 EXPORT_SYMBOL(tty_unregister_ldisc);
 
-
-/**
- *     tty_ldisc_try_get       -       try and reference an ldisc
- *     @disc: ldisc number
- *
- *     Attempt to open and lock a line discipline into place. Return
- *     the line discipline refcounted or an error.
- */
-
-static struct tty_ldisc *tty_ldisc_try_get(int disc)
+static struct tty_ldisc_ops *get_ldops(int disc)
 {
        unsigned long flags;
-       struct tty_ldisc *ld;
-       struct tty_ldisc_ops *ldops;
-       int err = -EINVAL;
-
-       ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
-       if (ld == NULL)
-               return ERR_PTR(-ENOMEM);
+       struct tty_ldisc_ops *ldops, *ret;
 
        spin_lock_irqsave(&tty_ldisc_lock, flags);
-       ld->ops = NULL;
+       ret = ERR_PTR(-EINVAL);
        ldops = tty_ldiscs[disc];
-       /* Check the entry is defined */
        if (ldops) {
-               /* If the module is being unloaded we can't use it */
-               if (!try_module_get(ldops->owner))
-                       err = -EAGAIN;
-               else {
-                       /* lock it */
+               ret = ERR_PTR(-EAGAIN);
+               if (try_module_get(ldops->owner)) {
                        ldops->refcount++;
-                       ld->ops = ldops;
-                       atomic_set(&ld->users, 0);
-                       err = 0;
+                       ret = ldops;
                }
        }
        spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-       if (err) {
-               kfree(ld);
-               return ERR_PTR(err);
-       }
-       return ld;
+       return ret;
+}
+
+static void put_ldops(struct tty_ldisc_ops *ldops)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&tty_ldisc_lock, flags);
+       ldops->refcount--;
+       module_put(ldops->owner);
+       spin_unlock_irqrestore(&tty_ldisc_lock, flags);
 }
 
 /**
@@ -170,44 +190,32 @@ static struct tty_ldisc *tty_ldisc_try_get(int disc)
 static struct tty_ldisc *tty_ldisc_get(int disc)
 {
        struct tty_ldisc *ld;
+       struct tty_ldisc_ops *ldops;
 
        if (disc < N_TTY || disc >= NR_LDISCS)
                return ERR_PTR(-EINVAL);
-       ld = tty_ldisc_try_get(disc);
-       if (IS_ERR(ld)) {
+
+       /*
+        * Get the ldisc ops - we may need to request them to be loaded
+        * dynamically and try again.
+        */
+       ldops = get_ldops(disc);
+       if (IS_ERR(ldops)) {
                request_module("tty-ldisc-%d", disc);
-               ld = tty_ldisc_try_get(disc);
+               ldops = get_ldops(disc);
+               if (IS_ERR(ldops))
+                       return ERR_CAST(ldops);
        }
-       return ld;
-}
-
-/**
- *     tty_ldisc_put           -       drop ldisc reference
- *     @ld: ldisc
- *
- *     Drop a reference to a line discipline. Manage refcounts and
- *     module usage counts. Free the ldisc once the recount hits zero.
- *
- *     Locking:
- *             takes tty_ldisc_lock to guard against ldisc races
- */
 
-static void tty_ldisc_put(struct tty_ldisc *ld)
-{
-       unsigned long flags;
-       int disc = ld->ops->num;
-       struct tty_ldisc_ops *ldo;
-
-       BUG_ON(disc < N_TTY || disc >= NR_LDISCS);
+       ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
+       if (ld == NULL) {
+               put_ldops(ldops);
+               return ERR_PTR(-ENOMEM);
+       }
 
-       spin_lock_irqsave(&tty_ldisc_lock, flags);
-       ldo = tty_ldiscs[disc];
-       BUG_ON(ldo->refcount == 0);
-       ldo->refcount--;
-       module_put(ldo->owner);
-       spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-       WARN_ON(atomic_read(&ld->users));
-       kfree(ld);
+       ld->ops = ldops;
+       atomic_set(&ld->users, 1);
+       return ld;
 }
 
 static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
@@ -228,13 +236,13 @@ static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
 static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
 {
        int i = *(loff_t *)v;
-       struct tty_ldisc *ld;
+       struct tty_ldisc_ops *ldops;
 
-       ld = tty_ldisc_try_get(i);
-       if (IS_ERR(ld))
+       ldops = get_ldops(i);
+       if (IS_ERR(ldops))
                return 0;
-       seq_printf(m, "%-10s %2d\n", ld->ops->name ? ld->ops->name : "???", i);
-       tty_ldisc_put(ld);
+       seq_printf(m, "%-10s %2d\n", ldops->name ? ldops->name : "???", i);
+       put_ldops(ldops);
        return 0;
 }
 
@@ -288,20 +296,17 @@ static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
  *     Locking: takes tty_ldisc_lock
  */
 
-static int tty_ldisc_try(struct tty_struct *tty)
+static struct tty_ldisc *tty_ldisc_try(struct tty_struct *tty)
 {
        unsigned long flags;
        struct tty_ldisc *ld;
-       int ret = 0;
 
        spin_lock_irqsave(&tty_ldisc_lock, flags);
-       ld = tty->ldisc;
-       if (test_bit(TTY_LDISC, &tty->flags)) {
-               atomic_inc(&ld->users);
-               ret = 1;
-       }
+       ld = NULL;
+       if (test_bit(TTY_LDISC, &tty->flags))
+               ld = get_ldisc(tty->ldisc);
        spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-       return ret;
+       return ld;
 }
 
 /**
@@ -322,10 +327,11 @@ static int tty_ldisc_try(struct tty_struct *tty)
 
 struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
 {
+       struct tty_ldisc *ld;
+
        /* wait_event is a macro */
-       wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
-       WARN_ON(atomic_read(&tty->ldisc->users) == 0);
-       return tty->ldisc;
+       wait_event(tty_ldisc_wait, (ld = tty_ldisc_try(tty)) != NULL);
+       return ld;
 }
 EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
 
@@ -342,9 +348,7 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
 
 struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
 {
-       if (tty_ldisc_try(tty))
-               return tty->ldisc;
-       return NULL;
+       return tty_ldisc_try(tty);
 }
 EXPORT_SYMBOL_GPL(tty_ldisc_ref);
 
@@ -360,19 +364,15 @@ EXPORT_SYMBOL_GPL(tty_ldisc_ref);
 
 void tty_ldisc_deref(struct tty_ldisc *ld)
 {
-       unsigned long flags;
-
-       BUG_ON(ld == NULL);
-
-       spin_lock_irqsave(&tty_ldisc_lock, flags);
-       if (atomic_read(&ld->users) == 0)
-               printk(KERN_ERR "tty_ldisc_deref: no references.\n");
-       else if (atomic_dec_and_test(&ld->users))
-               wake_up(&tty_ldisc_wait);
-       spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+       put_ldisc(ld);
 }
 EXPORT_SYMBOL_GPL(tty_ldisc_deref);
 
+static inline void tty_ldisc_put(struct tty_ldisc *ld)
+{
+       put_ldisc(ld);
+}
+
 /**
  *     tty_ldisc_enable        -       allow ldisc use
  *     @tty: terminal to activate ldisc on
@@ -510,8 +510,9 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
  *     be obtained while the delayed work queue halt ensures that no more
  *     data is fed to the ldisc.
  *
- *     In order to wait for any existing references to complete see
- *     tty_ldisc_wait_idle.
+ *     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)
@@ -521,31 +522,6 @@ static int tty_ldisc_halt(struct tty_struct *tty)
 }
 
 /**
- *     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.
- *
- *     tty_ldisc_lock protects the ref counts currently.
- */
-
-static int tty_ldisc_wait_idle(struct tty_struct *tty)
-{
-       unsigned long flags;
-       spin_lock_irqsave(&tty_ldisc_lock, flags);
-       while (atomic_read(&tty->ldisc->users)) {
-               spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-               if (wait_event_timeout(tty_ldisc_wait,
-                               atomic_read(&tty->ldisc->users) == 0, 5 * HZ) == 0)
-                       return -EBUSY;
-               spin_lock_irqsave(&tty_ldisc_lock, flags);
-       }
-       spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-       return 0;
-}
-
-/**
  *     tty_set_ldisc           -       set line discipline
  *     @tty: the terminal to set
  *     @ldisc: the line discipline
@@ -640,14 +616,6 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
        flush_scheduled_work();
 
-       /* Let any existing reference holders finish */
-       retval = tty_ldisc_wait_idle(tty);
-       if (retval < 0) {
-               clear_bit(TTY_LDISC_CHANGING, &tty->flags);
-               tty_ldisc_put(new_ldisc);
-               return retval;
-       }
-
        mutex_lock(&tty->ldisc_mutex);
        if (test_bit(TTY_HUPPED, &tty->flags)) {
                /* We were raced by the hangup method. It will have stomped
@@ -788,12 +756,14 @@ 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);
                if (tty->ldisc) {       /* Not yet closed */
                        /* Switch back to N_TTY */
-                       tty_ldisc_halt(tty);
-                       tty_ldisc_wait_idle(tty);
                        tty_ldisc_reinit(tty);
                        /* At this point we have a closed ldisc and we want to
                           reopen it. We could defer this to the next open but
@@ -858,14 +828,6 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
        tty_ldisc_halt(tty);
        flush_scheduled_work();
 
-       /*
-        * Wait for any short term users (we know they are just driver
-        * side waiters as the file is closing so user count on the file
-        * side is zero.
-        */
-
-       tty_ldisc_wait_idle(tty);
-
        mutex_lock(&tty->ldisc_mutex);
        /*
         * Now kill off the ldisc