#include <linux/wait.h>
#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/seq_file.h>
-#include <asm/uaccess.h>
+#include <linux/uaccess.h>
#include <asm/system.h>
#include <linux/kbd_kern.h>
static DEFINE_SPINLOCK(tty_ldisc_lock);
static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
/* Line disc dispatch table */
-static struct tty_ldisc tty_ldiscs[NR_LDISCS];
+static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
/**
* tty_register_ldisc - install a line discipline
* takes tty_ldisc_lock to guard against ldisc races
*/
-int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
+int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
unsigned long flags;
int ret = 0;
return -EINVAL;
spin_lock_irqsave(&tty_ldisc_lock, flags);
- tty_ldiscs[disc] = *new_ldisc;
- tty_ldiscs[disc].num = disc;
- tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
- tty_ldiscs[disc].refcount = 0;
+ tty_ldiscs[disc] = new_ldisc;
+ new_ldisc->num = disc;
+ new_ldisc->refcount = 0;
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
return ret;
return -EINVAL;
spin_lock_irqsave(&tty_ldisc_lock, flags);
- if (tty_ldiscs[disc].refcount)
+ if (tty_ldiscs[disc]->refcount)
ret = -EBUSY;
else
- tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
+ tty_ldiscs[disc] = NULL;
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
return ret;
}
EXPORT_SYMBOL(tty_unregister_ldisc);
+
+/**
+ * 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
+ */
+
+static int tty_ldisc_try_get(int disc, struct tty_ldisc *ld)
+{
+ unsigned long flags;
+ struct tty_ldisc_ops *ldops;
+ int err = -EINVAL;
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ ld->ops = NULL;
+ 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 */
+ ldops->refcount++;
+ ld->ops = ldops;
+ err = 0;
+ }
+ }
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ return err;
+}
+
/**
* 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
*/
-struct tty_ldisc *tty_ldisc_get(int disc)
+static int tty_ldisc_get(int disc, struct tty_ldisc *ld)
{
- unsigned long flags;
- struct tty_ldisc *ld;
+ int err;
if (disc < N_TTY || disc >= NR_LDISCS)
- return NULL;
-
- spin_lock_irqsave(&tty_ldisc_lock, flags);
-
- ld = &tty_ldiscs[disc];
- /* Check the entry is defined */
- if (ld->flags & LDISC_FLAG_DEFINED) {
- /* If the module is being unloaded we can't use it */
- if (!try_module_get(ld->owner))
- ld = NULL;
- else /* lock it */
- ld->refcount++;
- } else
- ld = NULL;
- spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- return ld;
+ return -EINVAL;
+ err = tty_ldisc_try_get(disc, ld);
+ if (err == -EAGAIN) {
+ request_module("tty-ldisc-%d", disc);
+ err = tty_ldisc_try_get(disc, ld);
+ }
+ return err;
}
-EXPORT_SYMBOL_GPL(tty_ldisc_get);
-
/**
* tty_ldisc_put - drop ldisc reference
* @disc: ldisc number
* takes tty_ldisc_lock to guard against ldisc races
*/
-void tty_ldisc_put(int disc)
+static void tty_ldisc_put(struct tty_ldisc_ops *ld)
{
- struct tty_ldisc *ld;
unsigned long flags;
+ int disc = ld->num;
BUG_ON(disc < N_TTY || disc >= NR_LDISCS);
spin_lock_irqsave(&tty_ldisc_lock, flags);
- ld = &tty_ldiscs[disc];
+ ld = tty_ldiscs[disc];
BUG_ON(ld->refcount == 0);
ld->refcount--;
module_put(ld->owner);
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
}
-EXPORT_SYMBOL_GPL(tty_ldisc_put);
+static void * tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
+{
+ return (*pos < NR_LDISCS) ? pos : NULL;
+}
+
+static void * tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ (*pos)++;
+ return (*pos < NR_LDISCS) ? pos : NULL;
+}
+
+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;
+
+ if (tty_ldisc_get(i, &ld) < 0)
+ return 0;
+ seq_printf(m, "%-10s %2d\n", ld.ops->name ? ld.ops->name : "???", i);
+ tty_ldisc_put(ld.ops);
+ return 0;
+}
+
+static const struct seq_operations tty_ldiscs_seq_ops = {
+ .start = tty_ldiscs_seq_start,
+ .next = tty_ldiscs_seq_next,
+ .stop = tty_ldiscs_seq_stop,
+ .show = tty_ldiscs_seq_show,
+};
+
+static int proc_tty_ldiscs_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &tty_ldiscs_seq_ops);
+}
+
+const struct file_operations tty_ldiscs_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = proc_tty_ldiscs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
/**
* tty_ldisc_assign - set ldisc on a tty
static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
{
+ ld->refcount = 0;
tty->ldisc = *ld;
- tty->ldisc.refcount = 0;
}
/**
wake_up(&tty_ldisc_wait);
}
+/**
+ * tty_ldisc_restore - helper for tty ldisc change
+ * @tty: tty to recover
+ * @old: previous ldisc
+ *
+ * Restore the previous line discipline or N_TTY when a line discipline
+ * change fails due to an open error
+ */
+
+static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
+{
+ char buf[64];
+ struct tty_ldisc new_ldisc;
+
+ /* There is an outstanding reference here so this is safe */
+ tty_ldisc_get(old->ops->num, 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);
+ /* This driver is always present */
+ if (tty_ldisc_get(N_TTY, &new_ldisc) < 0)
+ panic("n_tty: get");
+ 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);
+ }
+ }
+}
+
/**
* tty_set_ldisc - set line discipline
* @tty: the terminal to set
static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
{
- int retval = 0;
- struct tty_ldisc o_ldisc;
- char buf[64];
+ int retval;
+ struct tty_ldisc o_ldisc, new_ldisc;
int work;
unsigned long flags;
- struct tty_ldisc *ld;
struct tty_struct *o_tty;
- if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS))
- return -EINVAL;
-
restart:
-
- ld = tty_ldisc_get(ldisc);
- /* Eduardo Blanco <ejbs@cs.cs.com.uy> */
- /* Cyrus Durgin <cider@speakeasy.org> */
- if (ld == NULL) {
- request_module("tty-ldisc-%d", ldisc);
- ld = tty_ldisc_get(ldisc);
- }
- if (ld == NULL)
- return -EINVAL;
+ /* 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;
/*
* Problem: What do we do if this blocks ?
tty_wait_until_sent(tty, 0);
- if (tty->ldisc.num == ldisc) {
- tty_ldisc_put(ldisc);
+ if (tty->ldisc.ops->num == ldisc) {
+ tty_ldisc_put(new_ldisc.ops);
return 0;
}
/* Free the new ldisc we grabbed. Must drop the lock
first. */
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- tty_ldisc_put(ldisc);
+ tty_ldisc_put(o_ldisc.ops);
/*
* There are several reasons we may be busy, including
* random momentary I/O traffic. We must therefore
}
if (o_tty && o_tty->ldisc.refcount) {
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- tty_ldisc_put(ldisc);
+ tty_ldisc_put(o_tty->ldisc.ops);
if (wait_event_interruptible(tty_ldisc_wait, o_tty->ldisc.refcount == 0) < 0)
return -ERESTARTSYS;
goto restart;
* another ldisc change
*/
if (!test_bit(TTY_LDISC, &tty->flags)) {
+ struct tty_ldisc *ld;
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- tty_ldisc_put(ldisc);
+ tty_ldisc_put(new_ldisc.ops);
ld = tty_ldisc_ref_wait(tty);
tty_ldisc_deref(ld);
goto restart;
if (o_tty)
clear_bit(TTY_LDISC, &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
work = cancel_delayed_work(&tty->buf.work);
/*
* Wait for ->hangup_work and ->buf.work handlers to terminate
+ * MUST NOT hold locks here.
*/
flush_scheduled_work();
/* Shutdown the current discipline. */
- if (tty->ldisc.close)
- (tty->ldisc.close)(tty);
+ if (o_ldisc.ops->close)
+ (o_ldisc.ops->close)(tty);
/* Now set up the new line discipline. */
- tty_ldisc_assign(tty, ld);
+ tty_ldisc_assign(tty, &new_ldisc);
tty_set_termios_ldisc(tty, ldisc);
- if (tty->ldisc.open)
- retval = (tty->ldisc.open)(tty);
+ if (new_ldisc.ops->open)
+ retval = (new_ldisc.ops->open)(tty);
if (retval < 0) {
- tty_ldisc_put(ldisc);
- /* There is an outstanding reference here so this is safe */
- tty_ldisc_assign(tty, tty_ldisc_get(o_ldisc.num));
- tty_set_termios_ldisc(tty, tty->ldisc.num);
- if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) {
- tty_ldisc_put(o_ldisc.num);
- /* This driver is always present */
- tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
- tty_set_termios_ldisc(tty, N_TTY);
- if (tty->ldisc.open) {
- int r = tty->ldisc.open(tty);
-
- if (r < 0)
- panic("Couldn't open N_TTY ldisc for "
- "%s --- error %d.",
- tty_name(tty, buf), r);
- }
- }
+ tty_ldisc_put(new_ldisc.ops);
+ 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.num != o_ldisc.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.num);
+ tty_ldisc_put(o_ldisc.ops);
/*
* Allow ldisc referencing to occur as soon as the driver
if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) {
ld = tty_ldisc_ref(tty);
if (ld) {
- if (ld->write_wakeup)
- ld->write_wakeup(tty);
+ if (ld->ops->write_wakeup)
+ ld->ops->write_wakeup(tty);
tty_ldisc_deref(ld);
}
}
{
struct tty_ldisc *ld = tty_ldisc_ref(tty);
if (ld) {
- if (ld->flush_buffer)
- ld->flush_buffer(tty);
+ if (ld->ops->flush_buffer)
+ ld->ops->flush_buffer(tty);
tty_ldisc_deref(ld);
}
tty_buffer_flush(tty);
* do_tty_hangup - actual handler for hangup events
* @work: tty device
*
- * This can be called by the "eventd" kernel thread. That is process
+k * This can be called by the "eventd" kernel thread. That is process
* synchronous but doesn't hold any locks, so we need to make sure we
* have the appropriate locks for what we're doing.
*
ld = tty_ldisc_ref(tty);
if (ld != NULL) {
/* We may have no line discipline at this point */
- if (ld->flush_buffer)
- ld->flush_buffer(tty);
+ 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->write_wakeup)
- ld->write_wakeup(tty);
- if (ld->hangup)
- ld->hangup(tty);
+ ld->ops->write_wakeup)
+ ld->ops->write_wakeup(tty);
+ if (ld->ops->hangup)
+ ld->ops->hangup(tty);
}
/*
* FIXME: Once we trust the LDISC code better we can wait here for
/* We want to wait for the line discipline to sort out in this
situation */
ld = tty_ldisc_ref_wait(tty);
- if (ld->read)
- i = (ld->read)(tty, file, buf, count);
+ if (ld->ops->read)
+ i = (ld->ops->read)(tty, file, buf, count);
else
i = -EIO;
tty_ldisc_deref(ld);
printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
tty->driver->name);
ld = tty_ldisc_ref_wait(tty);
- if (!ld->write)
+ if (!ld->ops->write)
ret = -EIO;
else
- ret = do_tty_write(ld->write, tty, file, buf, count);
+ ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}
struct ktermios *tp, **tp_loc, *o_tp, **o_tp_loc;
struct ktermios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
int retval = 0;
+ struct tty_ldisc *ld;
/* check whether we're reopening an existing tty */
if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
* If we fail here just call release_tty to clean up. No need
* to decrement the use counts, as release_tty doesn't care.
*/
+
+ ld = &tty->ldisc;
- if (tty->ldisc.open) {
- retval = (tty->ldisc.open)(tty);
+ if (ld->ops->open) {
+ retval = (ld->ops->open)(tty);
if (retval)
goto release_mem_out;
}
- if (o_tty && o_tty->ldisc.open) {
- retval = (o_tty->ldisc.open)(o_tty);
+ if (o_tty && o_tty->ldisc.ops->open) {
+ retval = (o_tty->ldisc.ops->open)(o_tty);
if (retval) {
- if (tty->ldisc.close)
- (tty->ldisc.close)(tty);
+ if (ld->ops->close)
+ (ld->ops->close)(tty);
goto release_mem_out;
}
tty_ldisc_enable(o_tty);
static void release_dev(struct file *filp)
{
struct tty_struct *tty, *o_tty;
+ struct tty_ldisc ld;
int pty_master, tty_closing, o_tty_closing, do_sleep;
int devpts;
int idx;
spin_unlock_irqrestore(&tty_ldisc_lock, flags);
/*
* Shutdown the current line discipline, and reset it to N_TTY.
- * N.B. why reset ldisc when we're releasing the memory??
*
* FIXME: this MUST get fixed for the new reflocking
*/
- if (tty->ldisc.close)
- (tty->ldisc.close)(tty);
- tty_ldisc_put(tty->ldisc.num);
+ if (tty->ldisc.ops->close)
+ (tty->ldisc.ops->close)(tty);
+ tty_ldisc_put(tty->ldisc.ops);
/*
* Switch the line discipline back
*/
- tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+ WARN_ON(tty_ldisc_get(N_TTY, &ld));
+ tty_ldisc_assign(tty, &ld);
tty_set_termios_ldisc(tty, N_TTY);
if (o_tty) {
/* FIXME: could o_tty be in setldisc here ? */
clear_bit(TTY_LDISC, &o_tty->flags);
- if (o_tty->ldisc.close)
- (o_tty->ldisc.close)(o_tty);
- tty_ldisc_put(o_tty->ldisc.num);
- tty_ldisc_assign(o_tty, tty_ldisc_get(N_TTY));
+ if (o_tty->ldisc.ops->close)
+ (o_tty->ldisc.ops->close)(o_tty);
+ tty_ldisc_put(o_tty->ldisc.ops);
+ WARN_ON(tty_ldisc_get(N_TTY, &ld));
+ tty_ldisc_assign(o_tty, &ld);
tty_set_termios_ldisc(o_tty, N_TTY);
}
/*
return 0;
ld = tty_ldisc_ref_wait(tty);
- if (ld->poll)
- ret = (ld->poll)(tty, filp, wait);
+ if (ld->ops->poll)
+ ret = (ld->ops->poll)(tty, filp, wait);
tty_ldisc_deref(ld);
return ret;
}
if (get_user(ch, p))
return -EFAULT;
ld = tty_ldisc_ref_wait(tty);
- ld->receive_buf(tty, &ch, &mbz, 1);
+ ld->ops->receive_buf(tty, &ch, &mbz, 1);
tty_ldisc_deref(ld);
return 0;
}
case TIOCGSID:
return tiocgsid(tty, real_tty, p);
case TIOCGETD:
- return put_user(tty->ldisc.num, (int __user *)p);
+ return put_user(tty->ldisc.ops->num, (int __user *)p);
case TIOCSETD:
return tiocsetd(tty, p);
#ifdef CONFIG_VT
}
ld = tty_ldisc_ref_wait(tty);
retval = -EINVAL;
- if (ld->ioctl) {
- retval = ld->ioctl(tty, file, cmd, arg);
+ if (ld->ops->ioctl) {
+ retval = ld->ops->ioctl(tty, file, cmd, arg);
if (retval == -ENOIOCTLCMD)
retval = -EINVAL;
}
}
ld = tty_ldisc_ref_wait(tty);
- if (ld->compat_ioctl)
- retval = ld->compat_ioctl(tty, file, cmd, arg);
+ if (ld->ops->compat_ioctl)
+ retval = ld->ops->compat_ioctl(tty, file, cmd, arg);
tty_ldisc_deref(ld);
return retval;
flag_buf = head->flag_buf_ptr + head->read;
head->read += count;
spin_unlock_irqrestore(&tty->buf.lock, flags);
- disc->receive_buf(tty, char_buf, flag_buf, count);
+ disc->ops->receive_buf(tty, char_buf,
+ flag_buf, count);
spin_lock_irqsave(&tty->buf.lock, flags);
}
/* Restore the queue head */
static void initialize_tty_struct(struct tty_struct *tty)
{
+ struct tty_ldisc ld;
memset(tty, 0, sizeof(struct tty_struct));
tty->magic = TTY_MAGIC;
- tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+ if (tty_ldisc_get(N_TTY, &ld) < 0)
+ panic("n_tty: init_tty");
+ tty_ldisc_assign(tty, &ld);
tty->session = NULL;
tty->pgrp = NULL;
tty->overrun_time = jiffies;