#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
* Clearing directly is allowed.
*/
-void tty_ldisc_enable(struct tty_struct *tty)
+static void tty_ldisc_enable(struct tty_struct *tty)
{
clear_bit(TTY_LDISC_HALTED, &tty->flags);
set_bit(TTY_LDISC, &tty->flags);
/* 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)
}
}
-/**
- * tty_ldisc_flush_works - flush all works of a tty
- * @tty: tty device to flush works for
- *
- * Sync flush all works belonging to @tty.
- */
-static void tty_ldisc_flush_works(struct tty_struct *tty)
-{
- flush_work(&tty->hangup_work);
- flush_work(&tty->SAK_work);
- flush_work(&tty->port->buf.work);
-}
-
/**
* tty_ldisc_wait_idle - wait for the ldisc to become idle
* @tty: tty to wait for
* tty_ldisc_halt - shut down the line discipline
* @tty: tty device
* @o_tty: paired pty device (can be NULL)
- * @pending: returns true if work was scheduled when cancelled
- * (can be set to NULL)
- * @o_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 and
* its paired pty (if exists). Clearing the TTY_LDISC flag ensures
- * no further references can be obtained while the 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.
+ * no further references can be obtained, while waiting for existing
+ * references to be released ensures no more data is fed to the ldisc.
*
* You need to do a 'flush_scheduled_work()' (outside the ldisc_mutex)
* in order to make sure any currently executing ldisc work is also
*/
static int tty_ldisc_halt(struct tty_struct *tty, struct tty_struct *o_tty,
- int *pending, int *o_pending, long timeout)
+ long timeout)
{
- int scheduled, o_scheduled, retval;
+ int retval;
clear_bit(TTY_LDISC, &tty->flags);
if (o_tty)
if (retval)
return retval;
- scheduled = cancel_work_sync(&tty->port->buf.work);
set_bit(TTY_LDISC_HALTED, &tty->flags);
- if (o_tty) {
- o_scheduled = cancel_work_sync(&o_tty->port->buf.work);
+ if (o_tty)
set_bit(TTY_LDISC_HALTED, &o_tty->flags);
- }
- if (pending)
- *pending = scheduled;
- if (o_tty && o_pending)
- *o_pending = o_scheduled;
return 0;
}
*
* Shut down the line discipline and work queue for the tty device
* being hungup. Clear the TTY_LDISC flag to ensure no further
- * references can be obtained, wait for remaining references to be
- * released, and cancel pending buffer work to ensure no more
- * data is fed to this ldisc.
+ * references can be obtained and wait for remaining references to be
+ * released to ensure no more data is fed to this ldisc.
* Caller must hold legacy and ->ldisc_mutex.
*
* NB: tty_set_ldisc() is prevented from changing the ldisc concurrently
* with this function by checking the TTY_HUPPING flag.
- *
- * NB: if tty->ldisc is NULL then buffer work does not need to be
- * cancelled because it must already have done as a precondition
- * of closing the ldisc and setting tty->ldisc to NULL
*/
static bool tty_ldisc_hangup_halt(struct tty_struct *tty)
{
tty_name(tty, tty_n));
}
- cancel_work_sync(&tty->port->buf.work);
set_bit(TTY_LDISC_HALTED, &tty->flags);
/* must reacquire both locks and preserve lock order */
{
int retval;
struct tty_ldisc *o_ldisc, *new_ldisc;
- int work, o_work = 0;
struct tty_struct *o_tty;
new_ldisc = tty_ldisc_get(ldisc);
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);
/*
* parallel to the change and re-referencing the tty.
*/
- retval = tty_ldisc_halt(tty, o_tty, &work, &o_work, 5 * HZ);
+ retval = tty_ldisc_halt(tty, o_tty, 5 * HZ);
/*
- * Wait for ->hangup_work and ->buf.work handlers to terminate.
+ * Wait for hangup to complete, if pending.
* We must drop the mutex here in case a hangup is also in process.
*/
mutex_unlock(&tty->ldisc_mutex);
- tty_ldisc_flush_works(tty);
+ flush_work(&tty->hangup_work);
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);
/* Restart the work queue in case no characters kick it off. Safe if
already running */
- if (work)
- schedule_work(&tty->port->buf.work);
- if (o_work)
+ schedule_work(&tty->port->buf.work);
+ if (o_tty)
schedule_work(&o_tty->port->buf.work);
+
mutex_unlock(&tty->ldisc_mutex);
tty_unlock(tty);
return retval;
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);
}
/**
void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
{
/*
- * Prevent flush_to_ldisc() from rescheduling the work for later. Then
- * kill any delayed work. As this is the final close it does not
- * race with the set_ldisc code path.
+ * Shutdown this line discipline. As this is the final close,
+ * it does not race with the set_ldisc code path.
*/
- tty_ldisc_halt(tty, o_tty, NULL, NULL, MAX_SCHEDULE_TIMEOUT);
- tty_ldisc_flush_works(tty);
- if (o_tty)
- tty_ldisc_flush_works(o_tty);
+ 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);
/* This will need doing differently if we need to lock */
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)