/**
* tty_ldisc_try_get - try and reference an ldisc
* @disc: ldisc number
- * @ld: tty ldisc structure to complete
*
* Attempt to open and lock a line discipline into place. Return
- * the line discipline refcounted and assigned in ld. On an error
- * report the error code back
+ * the line discipline refcounted or an error.
*/
-static int tty_ldisc_try_get(int disc, struct tty_ldisc *ld)
+static struct tty_ldisc *tty_ldisc_try_get(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);
+
spin_lock_irqsave(&tty_ldisc_lock, flags);
ld->ops = NULL;
ldops = tty_ldiscs[disc];
/* lock it */
ldops->refcount++;
ld->ops = ldops;
+ ld->refcount = 0;
err = 0;
}
}
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- return err;
+ if (err)
+ return ERR_PTR(err);
+ return ld;
}
/**
* tty_ldisc_get - take a reference to an ldisc
* @disc: ldisc number
- * @ld: tty line discipline structure to use
*
* Takes a reference to a line discipline. Deals with refcounts and
* module locking counts. Returns NULL if the discipline is not available.
* takes tty_ldisc_lock to guard against ldisc races
*/
-static int tty_ldisc_get(int disc, struct tty_ldisc *ld)
+static struct tty_ldisc *tty_ldisc_get(int disc)
{
- int err;
+ struct tty_ldisc *ld;
if (disc < N_TTY || disc >= NR_LDISCS)
- return -EINVAL;
- err = tty_ldisc_try_get(disc, ld);
- if (err < 0) {
+ return ERR_PTR(-EINVAL);
+ ld = tty_ldisc_try_get(disc);
+ if (IS_ERR(ld)) {
request_module("tty-ldisc-%d", disc);
- err = tty_ldisc_try_get(disc, ld);
+ ld = tty_ldisc_try_get(disc);
}
- return err;
+ return ld;
}
/**
* tty_ldisc_put - drop ldisc reference
- * @disc: ldisc number
+ * @ld: ldisc
*
* Drop a reference to a line discipline. Manage refcounts and
- * module usage counts
+ * 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_ops *ld)
+static void tty_ldisc_put(struct tty_ldisc *ld)
{
unsigned long flags;
- int disc = ld->num;
+ int disc = ld->ops->num;
+ struct tty_ldisc_ops *ldo;
BUG_ON(disc < N_TTY || disc >= NR_LDISCS);
spin_lock_irqsave(&tty_ldisc_lock, flags);
- ld = tty_ldiscs[disc];
- BUG_ON(ld->refcount == 0);
- ld->refcount--;
- module_put(ld->owner);
+ ldo = tty_ldiscs[disc];
+ BUG_ON(ldo->refcount == 0);
+ ldo->refcount--;
+ module_put(ldo->owner);
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ kfree(ld);
}
static void * tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
{
int i = *(loff_t *)v;
- struct tty_ldisc ld;
+ struct tty_ldisc *ld;
- if (tty_ldisc_get(i, &ld) < 0)
+ ld = tty_ldisc_try_get(i);
+ if (IS_ERR(ld))
return 0;
- seq_printf(m, "%-10s %2d\n", ld.ops->name ? ld.ops->name : "???", i);
- tty_ldisc_put(ld.ops);
+ seq_printf(m, "%-10s %2d\n", ld->ops->name ? ld->ops->name : "???", i);
+ tty_ldisc_put(ld);
return 0;
}
static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
{
- ld->refcount = 0;
- tty->ldisc = *ld;
+ tty->ldisc = ld;
}
/**
int ret = 0;
spin_lock_irqsave(&tty_ldisc_lock, flags);
- ld = &tty->ldisc;
+ ld = tty->ldisc;
if (test_bit(TTY_LDISC, &tty->flags)) {
ld->refcount++;
ret = 1;
{
/* wait_event is a macro */
wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
- WARN_ON(tty->ldisc.refcount == 0);
- return &tty->ldisc;
+ WARN_ON(tty->ldisc->refcount == 0);
+ return tty->ldisc;
}
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 tty->ldisc;
return NULL;
}
mutex_unlock(&tty->termios_mutex);
}
+/**
+ * tty_ldisc_open - open a line discipline
+ * @tty: tty we are opening the ldisc on
+ * @ld: discipline to open
+ *
+ * A helper opening method. Also a convenient debugging and check
+ * point.
+ */
+
+static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+ WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
+ if (ld->ops->open)
+ return ld->ops->open(tty);
+ return 0;
+}
+
+/**
+ * tty_ldisc_close - close a line discipline
+ * @tty: tty we are opening the ldisc on
+ * @ld: discipline to close
+ *
+ * A helper close method. Also a convenient debugging and check
+ * point.
+ */
+
+static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+ WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
+ clear_bit(TTY_LDISC_OPEN, &tty->flags);
+ if (ld->ops->close)
+ ld->ops->close(tty);
+}
/**
* tty_ldisc_restore - helper for tty ldisc change
static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
{
char buf[64];
- struct tty_ldisc new_ldisc;
+ struct tty_ldisc *new_ldisc;
+ int r;
/* There is an outstanding reference here so this is safe */
- tty_ldisc_get(old->ops->num, old);
+ old = tty_ldisc_get(old->ops->num);
+ WARN_ON(IS_ERR(old));
tty_ldisc_assign(tty, old);
tty_set_termios_ldisc(tty, old->ops->num);
- if (old->ops->open && (old->ops->open(tty) < 0)) {
- tty_ldisc_put(old->ops);
+ if (tty_ldisc_open(tty, old) < 0) {
+ tty_ldisc_put(old);
/* This driver is always present */
- if (tty_ldisc_get(N_TTY, &new_ldisc) < 0)
+ new_ldisc =tty_ldisc_get(N_TTY);
+ if (IS_ERR(new_ldisc))
panic("n_tty: get");
- tty_ldisc_assign(tty, &new_ldisc);
+ tty_ldisc_assign(tty, new_ldisc);
tty_set_termios_ldisc(tty, N_TTY);
- if (new_ldisc.ops->open) {
- int r = new_ldisc.ops->open(tty);
- if (r < 0)
- panic("Couldn't open N_TTY ldisc for "
- "%s --- error %d.",
- tty_name(tty, buf), r);
- }
+ r = tty_ldisc_open(tty, new_ldisc);
+ if (r < 0)
+ panic("Couldn't open N_TTY ldisc for "
+ "%s --- error %d.",
+ tty_name(tty, buf), r);
}
}
/**
- * tty_ldisc_halt - shutdown the line discipline
+ * tty_ldisc_halt - shut down the line discipline
* @tty: tty device
*
* Shut down the line discipline and work queue for this tty device.
* tty_ldisc_wait_idle.
*/
-static void tty_ldisc_halt(struct tty_struct *tty)
+static int tty_ldisc_halt(struct tty_struct *tty)
{
clear_bit(TTY_LDISC, &tty->flags);
- cancel_delayed_work(&tty->buf.work);
- /*
- * Wait for ->hangup_work and ->buf.work handlers to terminate
- */
- flush_scheduled_work();
+ return cancel_delayed_work(&tty->buf.work);
}
/**
* 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 void tty_ldisc_wait_idle(struct tty_struct *tty)
+static int tty_ldisc_wait_idle(struct tty_struct *tty)
{
unsigned long flags;
spin_lock_irqsave(&tty_ldisc_lock, flags);
- while (tty->ldisc.refcount) {
+ while (tty->ldisc->refcount) {
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0);
+ if (wait_event_timeout(tty_ldisc_wait,
+ tty->ldisc->refcount == 0, 5 * HZ) == 0)
+ return -EBUSY;
spin_lock_irqsave(&tty_ldisc_lock, flags);
}
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ return 0;
}
/**
* @ldisc: the line discipline
*
* Set the discipline of a tty line. Must be called from a process
- * context.
+ * context. The ldisc change logic has to protect itself against any
+ * overlapping ldisc change (including on the other end of pty pairs),
+ * the close of one side of a tty/pty pair, and eventually hangup.
*
- * Locking: takes tty_ldisc_lock.
- * called functions take termios_mutex
+ * Locking: takes tty_ldisc_lock, termios_mutex
*/
int tty_set_ldisc(struct tty_struct *tty, int ldisc)
{
int retval;
- struct tty_ldisc o_ldisc, new_ldisc;
- int work;
- unsigned long flags;
+ struct tty_ldisc *o_ldisc, *new_ldisc;
+ int work, o_work = 0;
struct tty_struct *o_tty;
-restart:
- /* This is a bit ugly for now but means we can break the 'ldisc
- is part of the tty struct' assumption later */
- retval = tty_ldisc_get(ldisc, &new_ldisc);
- if (retval)
- return retval;
+ new_ldisc = tty_ldisc_get(ldisc);
+ if (IS_ERR(new_ldisc))
+ return PTR_ERR(new_ldisc);
/*
- * Problem: What do we do if this blocks ?
+ * We need to look at the tty locking here for pty/tty pairs
+ * when both sides try to change in parallel.
*/
- tty_wait_until_sent(tty, 0);
+ o_tty = tty->link; /* o_tty is the pty side or NULL */
+
- if (tty->ldisc.ops->num == ldisc) {
- tty_ldisc_put(new_ldisc.ops);
+ /*
+ * Check the no-op case
+ */
+
+ if (tty->ldisc->ops->num == ldisc) {
+ tty_ldisc_put(new_ldisc);
return 0;
}
+ /*
+ * Problem: What do we do if this blocks ?
+ * We could deadlock here
+ */
+
+ tty_wait_until_sent(tty, 0);
+
+ mutex_lock(&tty->ldisc_mutex);
+
+ /*
+ * We could be midstream of another ldisc change which has
+ * dropped the lock during processing. If so we need to wait.
+ */
+
+ while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
+ mutex_unlock(&tty->ldisc_mutex);
+ wait_event(tty_ldisc_wait,
+ test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0);
+ mutex_lock(&tty->ldisc_mutex);
+ }
+ set_bit(TTY_LDISC_CHANGING, &tty->flags);
+
/*
* No more input please, we are switching. The new ldisc
* will update this value in the ldisc open function
tty->receive_room = 0;
o_ldisc = tty->ldisc;
- o_tty = tty->link;
-
/*
* Make sure we don't change while someone holds a
* reference to the line discipline. The TTY_LDISC bit
* with a userspace app continually trying to use the tty in
* parallel to the change and re-referencing the tty.
*/
- clear_bit(TTY_LDISC, &tty->flags);
- if (o_tty)
- clear_bit(TTY_LDISC, &o_tty->flags);
- spin_lock_irqsave(&tty_ldisc_lock, flags);
- if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) {
- if (tty->ldisc.refcount) {
- /* Free the new ldisc we grabbed. Must drop the lock
- first. */
- spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- tty_ldisc_put(o_ldisc.ops);
- /*
- * There are several reasons we may be busy, including
- * random momentary I/O traffic. We must therefore
- * retry. We could distinguish between blocking ops
- * and retries if we made tty_ldisc_wait() smarter.
- * That is up for discussion.
- */
- if (wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0)
- return -ERESTARTSYS;
- goto restart;
- }
- if (o_tty && o_tty->ldisc.refcount) {
- spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- tty_ldisc_put(o_tty->ldisc.ops);
- if (wait_event_interruptible(tty_ldisc_wait, o_tty->ldisc.refcount == 0) < 0)
- return -ERESTARTSYS;
- goto restart;
- }
- }
- /*
- * If the TTY_LDISC bit is set, then we are racing against
- * another ldisc change
- */
- if (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
- struct tty_ldisc *ld;
- spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- tty_ldisc_put(new_ldisc.ops);
- ld = tty_ldisc_ref_wait(tty);
- tty_ldisc_deref(ld);
- goto restart;
- }
- /*
- * This flag is used to avoid two parallel ldisc changes. Once
- * open and close are fine grained locked this may work better
- * as a mutex shared with the open/close/hup paths
- */
- set_bit(TTY_LDISC_CHANGING, &tty->flags);
+ work = tty_ldisc_halt(tty);
if (o_tty)
- set_bit(TTY_LDISC_CHANGING, &o_tty->flags);
- spin_unlock_irqrestore(&tty_ldisc_lock, flags);
-
- /*
- * From this point on we know nobody has an ldisc
- * usage reference, nor can they obtain one until
- * we say so later on.
- */
+ o_work = tty_ldisc_halt(o_tty);
- work = cancel_delayed_work(&tty->buf.work);
/*
- * Wait for ->hangup_work and ->buf.work handlers to terminate
- * MUST NOT hold locks here.
+ * Wait for ->hangup_work and ->buf.work handlers to terminate.
+ * We must drop the mutex here in case a hangup is also in process.
*/
+
+ mutex_unlock(&tty->ldisc_mutex);
+
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
+ the ldisc data and closed the ldisc down */
+ clear_bit(TTY_LDISC_CHANGING, &tty->flags);
+ mutex_unlock(&tty->ldisc_mutex);
+ tty_ldisc_put(new_ldisc);
+ return -EIO;
+ }
+
/* Shutdown the current discipline. */
- if (o_ldisc.ops->close)
- (o_ldisc.ops->close)(tty);
+ tty_ldisc_close(tty, o_ldisc);
/* Now set up the new line discipline. */
- tty_ldisc_assign(tty, &new_ldisc);
+ tty_ldisc_assign(tty, new_ldisc);
tty_set_termios_ldisc(tty, ldisc);
- if (new_ldisc.ops->open)
- retval = (new_ldisc.ops->open)(tty);
+
+ retval = tty_ldisc_open(tty, new_ldisc);
if (retval < 0) {
- tty_ldisc_put(new_ldisc.ops);
- tty_ldisc_restore(tty, &o_ldisc);
+ /* Back to the old one or N_TTY if we can't */
+ tty_ldisc_put(new_ldisc);
+ tty_ldisc_restore(tty, o_ldisc);
}
+
/* At this point we hold a reference to the new ldisc and a
a reference to the old ldisc. If we ended up flipping back
to the existing ldisc we have two references to it */
- if (tty->ldisc.ops->num != o_ldisc.ops->num && tty->ops->set_ldisc)
+ if (tty->ldisc->ops->num != o_ldisc->ops->num && tty->ops->set_ldisc)
tty->ops->set_ldisc(tty);
- tty_ldisc_put(o_ldisc.ops);
+ tty_ldisc_put(o_ldisc);
/*
- * Allow ldisc referencing to occur as soon as the driver
- * ldisc callback completes.
+ * Allow ldisc referencing to occur again
*/
tty_ldisc_enable(tty);
if (o_tty)
tty_ldisc_enable(o_tty);
- /* Restart it in case no characters kick it off. Safe if
+ /* Restart the work queue in case no characters kick it off. Safe if
already running */
if (work)
schedule_delayed_work(&tty->buf.work, 1);
+ if (o_work)
+ schedule_delayed_work(&o_tty->buf.work, 1);
+ mutex_unlock(&tty->ldisc_mutex);
return retval;
}
+/**
+ * tty_reset_termios - reset terminal state
+ * @tty: tty to reset
+ *
+ * Restore a terminal to the driver default state.
+ */
+
+static void tty_reset_termios(struct tty_struct *tty)
+{
+ mutex_lock(&tty->termios_mutex);
+ *tty->termios = tty->driver->init_termios;
+ tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios);
+ tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios);
+ mutex_unlock(&tty->termios_mutex);
+}
+
+
+/**
+ * tty_ldisc_reinit - reinitialise the tty ldisc
+ * @tty: tty to reinit
+ *
+ * Switch the tty back to N_TTY line discipline and leave the
+ * ldisc state closed
+ */
+
+static void tty_ldisc_reinit(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld;
+
+ tty_ldisc_close(tty, tty->ldisc);
+ tty_ldisc_put(tty->ldisc);
+ tty->ldisc = NULL;
+ /*
+ * Switch the line discipline back
+ */
+ ld = tty_ldisc_get(N_TTY);
+ BUG_ON(IS_ERR(ld));
+ tty_ldisc_assign(tty, ld);
+ tty_set_termios_ldisc(tty, N_TTY);
+}
+
+/**
+ * tty_ldisc_hangup - hangup ldisc reset
+ * @tty: tty being hung up
+ *
+ * Some tty devices reset their termios when they receive a hangup
+ * event. In that situation we must also switch back to N_TTY properly
+ * before we reset the termios data.
+ *
+ * Locking: We can take the ldisc mutex as the rest of the code is
+ * careful to allow for this.
+ *
+ * In the pty pair case this occurs in the close() path of the
+ * tty itself so we must be careful about locking rules.
+ */
+
+void tty_ldisc_hangup(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld;
+
+ /*
+ * FIXME! What are the locking issues here? This may me overdoing
+ * things... This question is especially important now that we've
+ * removed the irqlock.
+ */
+ ld = tty_ldisc_ref(tty);
+ if (ld != NULL) {
+ /* We may have no line discipline at this point */
+ if (ld->ops->flush_buffer)
+ ld->ops->flush_buffer(tty);
+ tty_driver_flush_buffer(tty);
+ if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
+ ld->ops->write_wakeup)
+ ld->ops->write_wakeup(tty);
+ if (ld->ops->hangup)
+ ld->ops->hangup(tty);
+ tty_ldisc_deref(ld);
+ }
+ /*
+ * FIXME: Once we trust the LDISC code better we can wait here for
+ * ldisc completion and fix the driver call race
+ */
+ wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
+ wake_up_interruptible_poll(&tty->read_wait, POLLIN);
+ /*
+ * Shutdown the current line discipline, and reset it to
+ * N_TTY.
+ */
+ if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
+ /* Avoid racing set_ldisc */
+ mutex_lock(&tty->ldisc_mutex);
+ /* Switch back to N_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
+ it means auditing a lot of other paths so this is a FIXME */
+ WARN_ON(tty_ldisc_open(tty, tty->ldisc));
+ tty_ldisc_enable(tty);
+ mutex_unlock(&tty->ldisc_mutex);
+ tty_reset_termios(tty);
+ }
+}
/**
* tty_ldisc_setup - open line discipline
* @o_tty: pair tty for pty/tty pairs
*
* Called during the initial open of a tty/pty pair in order to set up the
- * line discplines and bind them to the tty.
+ * line disciplines and bind them to the tty. This has no locking issues
+ * as the device isn't yet active.
*/
int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
{
- struct tty_ldisc *ld = &tty->ldisc;
+ struct tty_ldisc *ld = tty->ldisc;
int retval;
- if (ld->ops->open) {
- retval = (ld->ops->open)(tty);
- if (retval)
- return retval;
- }
- if (o_tty && o_tty->ldisc.ops->open) {
- retval = (o_tty->ldisc.ops->open)(o_tty);
+ retval = tty_ldisc_open(tty, ld);
+ if (retval)
+ return retval;
+
+ if (o_tty) {
+ retval = tty_ldisc_open(o_tty, o_tty->ldisc);
if (retval) {
- if (ld->ops->close)
- (ld->ops->close)(tty);
+ tty_ldisc_close(tty, ld);
return retval;
}
tty_ldisc_enable(o_tty);
tty_ldisc_enable(tty);
return 0;
}
-
-static void tty_ldisc_reinit(struct tty_struct *tty)
-{
- struct tty_ldisc ld;
-
- if (tty->ldisc.ops->close)
- (tty->ldisc.ops->close)(tty);
- tty_ldisc_put(tty->ldisc.ops);
- /*
- * Switch the line discipline back
- */
- WARN_ON(tty_ldisc_get(N_TTY, &ld));
- tty_ldisc_assign(tty, &ld);
- tty_set_termios_ldisc(tty, N_TTY);
-}
-
/**
* tty_ldisc_release - release line discipline
* @tty: tty being shut down
* @o_tty: pair tty for pty/tty pairs
*
* Called during the final close of a tty/pty pair in order to shut down the
- * line discpline layer.
+ * line discpline layer. On exit the ldisc assigned is N_TTY and the
+ * ldisc has not been opened.
*/
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
*/
tty_ldisc_halt(tty);
+ flush_scheduled_work();
/*
* Wait for any short term users (we know they are just driver
*/
tty_ldisc_reinit(tty);
- if (o_tty) {
- /* FIXME: could o_tty be in setldisc here ? */
- clear_bit(TTY_LDISC, &o_tty->flags);
- tty_ldisc_reinit(o_tty);
- }
+ /* This will need doing differently if we need to lock */
+ if (o_tty)
+ tty_ldisc_release(o_tty, NULL);
}
/**
void tty_ldisc_init(struct tty_struct *tty)
{
- struct tty_ldisc ld;
- if (tty_ldisc_get(N_TTY, &ld) < 0)
+ 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_assign(tty, ld);
}
void tty_ldisc_begin(void)