#include <linux/uaccess.h>
#include <linux/ratelimit.h>
+#undef LDISC_DEBUG_HANGUP
+
+#ifdef LDISC_DEBUG_HANGUP
+#define tty_ldisc_debug(tty, f, args...) ({ \
+ char __b[64]; \
+ printk(KERN_DEBUG "%s: %s: " f, __func__, tty_name(tty, __b), ##args); \
+})
+#else
+#define tty_ldisc_debug(tty, f, args...)
+#endif
+
/*
* This guards the refcounted line discipline lists. The lock
* must be taken with irqs off because there are hangup path
/* 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_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
return ld;
}
+/**
+ * tty_ldisc_put - release the ldisc
+ *
+ * Complement of tty_ldisc_get().
+ */
+static inline void tty_ldisc_put(struct tty_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);
+}
+
static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
{
return (*pos < NR_LDISCS) ? pos : NULL;
.release = seq_release,
};
-/**
- * tty_ldisc_assign - set ldisc on a tty
- * @tty: tty to assign
- * @ld: line discipline
- *
- * Install an instance of a line discipline into a tty structure. The
- * ldisc must have a reference count above zero to ensure it remains.
- * The tty instance refcount starts at zero.
- *
- * Locking:
- * Caller must hold references
- */
-
-static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
-{
- tty->ldisc = ld;
-}
-
/**
* tty_ldisc_try - internal helper
* @tty: the tty
unsigned long flags;
struct tty_ldisc *ld;
+ /* FIXME: this allows reference acquire after TTY_LDISC is cleared */
raw_spin_lock_irqsave(&tty_ldisc_lock, flags);
ld = NULL;
- if (test_bit(TTY_LDISC, &tty->flags))
- ld = get_ldisc(tty->ldisc);
+ if (test_bit(TTY_LDISC, &tty->flags) && tty->ldisc) {
+ ld = tty->ldisc;
+ atomic_inc(&ld->users);
+ }
raw_spin_unlock_irqrestore(&tty_ldisc_lock, flags);
return ld;
}
void tty_ldisc_deref(struct tty_ldisc *ld)
{
- put_ldisc(ld);
-}
-EXPORT_SYMBOL_GPL(tty_ldisc_deref);
+ unsigned long flags;
-static inline void tty_ldisc_put(struct tty_ldisc *ld)
-{
- put_ldisc(ld);
+ 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_enable - allow ldisc use
/* There is an outstanding reference here so this is safe */
old = tty_ldisc_get(old->ops->num);
WARN_ON(IS_ERR(old));
- tty_ldisc_assign(tty, old);
+ tty->ldisc = old;
tty_set_termios_ldisc(tty, old->ops->num);
if (tty_ldisc_open(tty, old) < 0) {
tty_ldisc_put(old);
new_ldisc = tty_ldisc_get(N_TTY);
if (IS_ERR(new_ldisc))
panic("n_tty: get");
- tty_ldisc_assign(tty, new_ldisc);
+ tty->ldisc = new_ldisc;
tty_set_termios_ldisc(tty, N_TTY);
r = tty_ldisc_open(tty, new_ldisc);
if (r < 0)
return 0;
}
- tty_unlock(tty);
- /*
- * Problem: What do we do if this blocks ?
- * We could deadlock here
- */
-
- tty_wait_until_sent(tty, 0);
-
- tty_lock(tty);
mutex_lock(&tty->ldisc_mutex);
/*
tty_ldisc_close(tty, o_ldisc);
/* Now set up the new line discipline. */
- tty_ldisc_assign(tty, new_ldisc);
+ tty->ldisc = new_ldisc;
tty_set_termios_ldisc(tty, ldisc);
retval = tty_ldisc_open(tty, new_ldisc);
tty_ldisc_close(tty, tty->ldisc);
tty_ldisc_put(tty->ldisc);
- tty->ldisc = NULL;
/*
* Switch the line discipline back
*/
- tty_ldisc_assign(tty, ld);
+ tty->ldisc = ld;
tty_set_termios_ldisc(tty, ldisc);
return 0;
int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
int err = 0;
+ tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc);
+
/*
* FIXME! What are the locking issues here? This may me overdoing
* things... This question is especially important now that we've
*/
mutex_lock(&tty->ldisc_mutex);
- /* At this point we have a closed ldisc and we want to
- reopen it. We could defer this to the next open but
- it means auditing a lot of other paths so this is
- a FIXME */
if (tty_ldisc_hangup_halt(tty)) {
+
+ /* At this point we have a halted ldisc; we want to close it and
+ reopen a new ldisc. We could defer the reopen to the next
+ open but it means auditing a lot of other paths so this is
+ a FIXME */
if (reset == 0) {
if (!tty_ldisc_reinit(tty, tty->termios.c_line))
mutex_unlock(&tty->ldisc_mutex);
if (reset)
tty_reset_termios(tty);
+
+ tty_ldisc_debug(tty, "re-opened ldisc: %p\n", tty->ldisc);
}
/**
* it does not race with the set_ldisc code path.
*/
+ tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc);
+
tty_ldisc_halt(tty, o_tty, MAX_SCHEDULE_TIMEOUT);
tty_lock_pair(tty, o_tty);
tty_unlock_pair(tty, o_tty);
/* And the memory resources remaining (buffers, termios) will be
disposed of when the kref hits zero */
+
+ tty_ldisc_debug(tty, "ldisc closed\n");
}
/**
struct tty_ldisc *ld = tty_ldisc_get(N_TTY);
if (IS_ERR(ld))
panic("n_tty: init_tty");
- tty_ldisc_assign(tty, ld);
+ tty->ldisc = ld;
}
/**
*/
void tty_ldisc_deinit(struct tty_struct *tty)
{
- put_ldisc(tty->ldisc);
- tty_ldisc_assign(tty, NULL);
+ tty_ldisc_put(tty->ldisc);
+ tty->ldisc = NULL;
}
void tty_ldisc_begin(void)