#include <linux/smp_lock.h>
#include <linux/device.h>
#include <linux/idr.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/system.h>
-#include <asm/bitops.h>
#include <linux/kbd_kern.h>
#include <linux/vt_kern.h>
#include <linux/selection.h>
#include <linux/devfs_fs_kernel.h>
+#include <linux/vs_cvirt.h>
#include <linux/kmod.h>
EXPORT_SYMBOL(tty_std_termios);
+/* This list gets poked at by procfs and various bits of boot up code. This
+ could do with some rationalisation such as pulling the tty proc function
+ into this file */
+
LIST_HEAD(tty_drivers); /* linked list of tty drivers */
-struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */
-/* Semaphore to protect creating and releasing a tty */
-DECLARE_MUTEX(tty_sem);
+/* Semaphore to protect creating and releasing a tty. This is shared with
+ vt.c for deeply disgusting hack reasons */
+DEFINE_MUTEX(tty_mutex);
+
+int console_use_vt = 1;
#ifdef CONFIG_UNIX98_PTYS
extern struct tty_driver *ptm_driver; /* Unix98 pty masters; for /dev/ptmx */
extern int pty_limit; /* Config limit on Unix98 ptys */
static DEFINE_IDR(allocated_ptys);
static DECLARE_MUTEX(allocated_ptys_lock);
+static int ptmx_open(struct inode *, struct file *);
#endif
extern void disable_early_printk(void);
ssize_t redirected_tty_write(struct file *, const char __user *, size_t, loff_t *);
static unsigned int tty_poll(struct file *, poll_table *);
static int tty_open(struct inode *, struct file *);
-static int ptmx_open(struct inode *, struct file *);
static int tty_release(struct inode *, struct file *);
int tty_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg);
static int tty_fasync(int fd, struct file * filp, int on);
-extern void rs_360_init(void);
static void release_mem(struct tty_struct *tty, int idx);
return tty;
}
+static void tty_buffer_free_all(struct tty_struct *);
+
static inline void free_tty_struct(struct tty_struct *tty)
{
+ kfree(tty->write_buf);
+ tty_buffer_free_all(tty);
kfree(tty);
}
EXPORT_SYMBOL(tty_name);
-inline int tty_paranoia_check(struct tty_struct *tty, struct inode *inode,
+int tty_paranoia_check(struct tty_struct *tty, struct inode *inode,
const char *routine)
{
#ifdef TTY_PARANOIA_CHECK
return 0;
}
+/*
+ * Tty buffer allocation management
+ */
+
+static void tty_buffer_free_all(struct tty_struct *tty)
+{
+ struct tty_buffer *thead;
+ while((thead = tty->buf.head) != NULL) {
+ tty->buf.head = thead->next;
+ kfree(thead);
+ }
+ while((thead = tty->buf.free) != NULL) {
+ tty->buf.free = thead->next;
+ kfree(thead);
+ }
+ tty->buf.tail = NULL;
+}
+
+static void tty_buffer_init(struct tty_struct *tty)
+{
+ spin_lock_init(&tty->buf.lock);
+ tty->buf.head = NULL;
+ tty->buf.tail = NULL;
+ tty->buf.free = NULL;
+}
+
+static struct tty_buffer *tty_buffer_alloc(size_t size)
+{
+ struct tty_buffer *p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC);
+ if(p == NULL)
+ return NULL;
+ p->used = 0;
+ p->size = size;
+ p->next = NULL;
+ p->active = 0;
+ p->commit = 0;
+ p->read = 0;
+ p->char_buf_ptr = (char *)(p->data);
+ p->flag_buf_ptr = (unsigned char *)p->char_buf_ptr + size;
+/* printk("Flip create %p\n", p); */
+ return p;
+}
+
+/* Must be called with the tty_read lock held. This needs to acquire strategy
+ code to decide if we should kfree or relink a given expired buffer */
+
+static void tty_buffer_free(struct tty_struct *tty, struct tty_buffer *b)
+{
+ /* Dumb strategy for now - should keep some stats */
+/* printk("Flip dispose %p\n", b); */
+ if(b->size >= 512)
+ kfree(b);
+ else {
+ b->next = tty->buf.free;
+ tty->buf.free = b;
+ }
+}
+
+static struct tty_buffer *tty_buffer_find(struct tty_struct *tty, size_t size)
+{
+ struct tty_buffer **tbh = &tty->buf.free;
+ while((*tbh) != NULL) {
+ struct tty_buffer *t = *tbh;
+ if(t->size >= size) {
+ *tbh = t->next;
+ t->next = NULL;
+ t->used = 0;
+ t->commit = 0;
+ t->read = 0;
+ /* DEBUG ONLY */
+/* memset(t->data, '*', size); */
+/* printk("Flip recycle %p\n", t); */
+ return t;
+ }
+ tbh = &((*tbh)->next);
+ }
+ /* Round the buffer size out */
+ size = (size + 0xFF) & ~ 0xFF;
+ return tty_buffer_alloc(size);
+ /* Should possibly check if this fails for the largest buffer we
+ have queued and recycle that ? */
+}
+
+int tty_buffer_request_room(struct tty_struct *tty, size_t size)
+{
+ struct tty_buffer *b, *n;
+ int left;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tty->buf.lock, flags);
+
+ /* OPTIMISATION: We could keep a per tty "zero" sized buffer to
+ remove this conditional if its worth it. This would be invisible
+ to the callers */
+ if ((b = tty->buf.tail) != NULL) {
+ left = b->size - b->used;
+ b->active = 1;
+ } else
+ left = 0;
+
+ if (left < size) {
+ /* This is the slow path - looking for new buffers to use */
+ if ((n = tty_buffer_find(tty, size)) != NULL) {
+ if (b != NULL) {
+ b->next = n;
+ b->active = 0;
+ b->commit = b->used;
+ } else
+ tty->buf.head = n;
+ tty->buf.tail = n;
+ n->active = 1;
+ } else
+ size = left;
+ }
+
+ spin_unlock_irqrestore(&tty->buf.lock, flags);
+ return size;
+}
+EXPORT_SYMBOL_GPL(tty_buffer_request_room);
+
+int tty_insert_flip_string(struct tty_struct *tty, const unsigned char *chars,
+ size_t size)
+{
+ int copied = 0;
+ do {
+ int space = tty_buffer_request_room(tty, size - copied);
+ struct tty_buffer *tb = tty->buf.tail;
+ /* If there is no space then tb may be NULL */
+ if(unlikely(space == 0))
+ break;
+ memcpy(tb->char_buf_ptr + tb->used, chars, space);
+ memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space);
+ tb->used += space;
+ copied += space;
+ chars += space;
+ }
+ /* There is a small chance that we need to split the data over
+ several buffers. If this is the case we must loop */
+ while (unlikely(size > copied));
+ return copied;
+}
+EXPORT_SYMBOL(tty_insert_flip_string);
+
+int tty_insert_flip_string_flags(struct tty_struct *tty,
+ const unsigned char *chars, const char *flags, size_t size)
+{
+ int copied = 0;
+ do {
+ int space = tty_buffer_request_room(tty, size - copied);
+ struct tty_buffer *tb = tty->buf.tail;
+ /* If there is no space then tb may be NULL */
+ if(unlikely(space == 0))
+ break;
+ memcpy(tb->char_buf_ptr + tb->used, chars, space);
+ memcpy(tb->flag_buf_ptr + tb->used, flags, space);
+ tb->used += space;
+ copied += space;
+ chars += space;
+ flags += space;
+ }
+ /* There is a small chance that we need to split the data over
+ several buffers. If this is the case we must loop */
+ while (unlikely(size > copied));
+ return copied;
+}
+EXPORT_SYMBOL(tty_insert_flip_string_flags);
+
+void tty_schedule_flip(struct tty_struct *tty)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&tty->buf.lock, flags);
+ if (tty->buf.tail != NULL) {
+ tty->buf.tail->active = 0;
+ tty->buf.tail->commit = tty->buf.tail->used;
+ }
+ spin_unlock_irqrestore(&tty->buf.lock, flags);
+ schedule_delayed_work(&tty->buf.work, 1);
+}
+EXPORT_SYMBOL(tty_schedule_flip);
+
+/*
+ * Prepare a block of space in the buffer for data. Returns the length
+ * available and buffer pointer to the space which is now allocated and
+ * accounted for as ready for normal characters. This is used for drivers
+ * that need their own block copy routines into the buffer. There is no
+ * guarantee the buffer is a DMA target!
+ */
+
+int tty_prepare_flip_string(struct tty_struct *tty, unsigned char **chars, size_t size)
+{
+ int space = tty_buffer_request_room(tty, size);
+ if (likely(space)) {
+ struct tty_buffer *tb = tty->buf.tail;
+ *chars = tb->char_buf_ptr + tb->used;
+ memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space);
+ tb->used += space;
+ }
+ return space;
+}
+
+EXPORT_SYMBOL_GPL(tty_prepare_flip_string);
+
+/*
+ * Prepare a block of space in the buffer for data. Returns the length
+ * available and buffer pointer to the space which is now allocated and
+ * accounted for as ready for characters. This is used for drivers
+ * that need their own block copy routines into the buffer. There is no
+ * guarantee the buffer is a DMA target!
+ */
+
+int tty_prepare_flip_string_flags(struct tty_struct *tty, unsigned char **chars, char **flags, size_t size)
+{
+ int space = tty_buffer_request_room(tty, size);
+ if (likely(space)) {
+ struct tty_buffer *tb = tty->buf.tail;
+ *chars = tb->char_buf_ptr + tb->used;
+ *flags = tb->flag_buf_ptr + tb->used;
+ tb->used += space;
+ }
+ return space;
+}
+
+EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags);
+
+
+
+/*
+ * This is probably overkill for real world processors but
+ * they are not on hot paths so a little discipline won't do
+ * any harm.
+ */
+
+static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
+{
+ down(&tty->termios_sem);
+ tty->termios->c_line = num;
+ up(&tty->termios_sem);
+}
+
+/*
+ * This guards the refcounted line discipline lists. The lock
+ * must be taken with irqs off because there are hangup path
+ * callers who will do ldisc lookups and cannot sleep.
+ */
+
+static DEFINE_SPINLOCK(tty_ldisc_lock);
+static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
+static struct tty_ldisc tty_ldiscs[NR_LDISCS]; /* line disc dispatch table */
+
int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
{
+ unsigned long flags;
+ int ret = 0;
+
if (disc < N_TTY || disc >= NR_LDISCS)
return -EINVAL;
- if (new_ldisc) {
- ldiscs[disc] = *new_ldisc;
- ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
- ldiscs[disc].num = disc;
- } else
- memset(&ldiscs[disc], 0, sizeof(struct tty_ldisc));
+ 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;
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
- return 0;
+ return ret;
}
-
EXPORT_SYMBOL(tty_register_ldisc);
-/* Set the discipline of a tty line. */
+int tty_unregister_ldisc(int disc)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (disc < N_TTY || disc >= NR_LDISCS)
+ return -EINVAL;
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ if (tty_ldiscs[disc].refcount)
+ ret = -EBUSY;
+ else
+ tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+
+ return ret;
+}
+EXPORT_SYMBOL(tty_unregister_ldisc);
+
+struct tty_ldisc *tty_ldisc_get(int disc)
+{
+ unsigned long flags;
+ struct tty_ldisc *ld;
+
+ 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;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_get);
+
+void tty_ldisc_put(int disc)
+{
+ struct tty_ldisc *ld;
+ unsigned long flags;
+
+ 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);
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_put);
+
+static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+ tty->ldisc = *ld;
+ tty->ldisc.refcount = 0;
+}
+
+/**
+ * tty_ldisc_try - internal helper
+ * @tty: the tty
+ *
+ * Make a single attempt to grab and bump the refcount on
+ * the tty ldisc. Return 0 on failure or 1 on success. This is
+ * used to implement both the waiting and non waiting versions
+ * of tty_ldisc_ref
+ */
+
+static int 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))
+ {
+ ld->refcount++;
+ ret = 1;
+ }
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ return ret;
+}
+
+/**
+ * tty_ldisc_ref_wait - wait for the tty ldisc
+ * @tty: tty device
+ *
+ * Dereference the line discipline for the terminal and take a
+ * reference to it. If the line discipline is in flux then
+ * wait patiently until it changes.
+ *
+ * Note: Must not be called from an IRQ/timer context. The caller
+ * must also be careful not to hold other locks that will deadlock
+ * against a discipline change, such as an existing ldisc reference
+ * (which we check for)
+ */
+
+struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
+{
+ /* wait_event is a macro */
+ wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
+ if(tty->ldisc.refcount == 0)
+ printk(KERN_ERR "tty_ldisc_ref_wait\n");
+ return &tty->ldisc;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
+
+/**
+ * tty_ldisc_ref - get the tty ldisc
+ * @tty: tty device
+ *
+ * Dereference the line discipline for the terminal and take a
+ * reference to it. If the line discipline is in flux then
+ * return NULL. Can be called from IRQ and timer functions.
+ */
+
+struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
+{
+ if(tty_ldisc_try(tty))
+ return &tty->ldisc;
+ return NULL;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_ref);
+
+/**
+ * tty_ldisc_deref - free a tty ldisc reference
+ * @ld: reference to free up
+ *
+ * Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
+ * be called in IRQ context.
+ */
+
+void tty_ldisc_deref(struct tty_ldisc *ld)
+{
+ unsigned long flags;
+
+ BUG_ON(ld == NULL);
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ if(ld->refcount == 0)
+ printk(KERN_ERR "tty_ldisc_deref: no references.\n");
+ else
+ ld->refcount--;
+ if(ld->refcount == 0)
+ wake_up(&tty_ldisc_wait);
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_deref);
+
+/**
+ * tty_ldisc_enable - allow ldisc use
+ * @tty: terminal to activate ldisc on
+ *
+ * Set the TTY_LDISC flag when the line discipline can be called
+ * again. Do neccessary wakeups for existing sleepers.
+ *
+ * Note: nobody should set this bit except via this function. Clearing
+ * directly is allowed.
+ */
+
+static void tty_ldisc_enable(struct tty_struct *tty)
+{
+ set_bit(TTY_LDISC, &tty->flags);
+ wake_up(&tty_ldisc_wait);
+}
+
+/**
+ * tty_set_ldisc - set line discipline
+ * @tty: the terminal to set
+ * @ldisc: the line discipline
+ *
+ * Set the discipline of a tty line. Must be called from a process
+ * context.
+ */
+
static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
{
- int retval = 0;
- struct tty_ldisc o_ldisc;
+ int retval = 0;
+ struct tty_ldisc o_ldisc;
char buf[64];
+ 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 (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED)) {
+ if (ld == NULL) {
request_module("tty-ldisc-%d", ldisc);
+ ld = tty_ldisc_get(ldisc);
}
- if (!(ldiscs[ldisc].flags & LDISC_FLAG_DEFINED))
+ if (ld == NULL)
return -EINVAL;
- if (tty->ldisc.num == ldisc)
- return 0; /* We are already in the desired discipline */
+ /*
+ * No more input please, we are switching. The new ldisc
+ * will update this value in the ldisc open function
+ */
- if (!try_module_get(ldiscs[ldisc].owner))
- return -EINVAL;
-
- o_ldisc = tty->ldisc;
+ tty->receive_room = 0;
+
+ /*
+ * Problem: What do we do if this blocks ?
+ */
tty_wait_until_sent(tty, 0);
-
+
+ if (tty->ldisc.num == ldisc) {
+ tty_ldisc_put(ldisc);
+ return 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
+ * prevents anyone taking a reference once it is clear.
+ * We need the lock to avoid racing reference takers.
+ */
+
+ 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(ldisc);
+ /*
+ * 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(ldisc);
+ 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, &tty->flags)) {
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ tty_ldisc_put(ldisc);
+ ld = tty_ldisc_ref_wait(tty);
+ tty_ldisc_deref(ld);
+ goto restart;
+ }
+
+ clear_bit(TTY_LDISC, &tty->flags);
+ clear_bit(TTY_DONT_FLIP, &tty->flags);
+ if (o_tty) {
+ clear_bit(TTY_LDISC, &o_tty->flags);
+ clear_bit(TTY_DONT_FLIP, &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.
+ */
+
+ work = cancel_delayed_work(&tty->buf.work);
+ /*
+ * Wait for ->hangup_work and ->buf.work handlers to terminate
+ */
+
+ flush_scheduled_work();
/* Shutdown the current discipline. */
if (tty->ldisc.close)
(tty->ldisc.close)(tty);
/* Now set up the new line discipline. */
- tty->ldisc = ldiscs[ldisc];
- tty->termios->c_line = ldisc;
+ tty_ldisc_assign(tty, ld);
+ tty_set_termios_ldisc(tty, ldisc);
if (tty->ldisc.open)
retval = (tty->ldisc.open)(tty);
if (retval < 0) {
- tty->ldisc = o_ldisc;
- tty->termios->c_line = tty->ldisc.num;
+ 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 = ldiscs[N_TTY];
- tty->termios->c_line = N_TTY;
+ 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);
tty_name(tty, buf), r);
}
}
- } else {
- module_put(o_ldisc.owner);
}
+ /* 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->driver->set_ldisc)
tty->driver->set_ldisc(tty);
+
+ tty_ldisc_put(o_ldisc.num);
+
+ /*
+ * Allow ldisc referencing to occur as soon as the driver
+ * ldisc callback completes.
+ */
+
+ tty_ldisc_enable(tty);
+ if (o_tty)
+ tty_ldisc_enable(o_tty);
+
+ /* Restart it in case no characters kick it off. Safe if
+ already running */
+ if (work)
+ schedule_delayed_work(&tty->buf.work, 1);
return retval;
}
/*
* This routine returns a tty driver structure, given a device number
*/
-struct tty_driver *get_tty_driver(dev_t device, int *index)
+static struct tty_driver *get_tty_driver(dev_t device, int *index)
{
struct tty_driver *p;
.release = tty_release,
};
-static spinlock_t redirect_lock = SPIN_LOCK_UNLOCKED;
+static DEFINE_SPINLOCK(redirect_lock);
static struct file *redirect;
+
+/**
+ * tty_wakeup - request more data
+ * @tty: terminal
+ *
+ * Internal and external helper for wakeups of tty. This function
+ * informs the line discipline if present that the driver is ready
+ * to receive more output data.
+ */
+
+void tty_wakeup(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld;
+
+ if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) {
+ ld = tty_ldisc_ref(tty);
+ if(ld) {
+ if(ld->write_wakeup)
+ ld->write_wakeup(tty);
+ tty_ldisc_deref(ld);
+ }
+ }
+ wake_up_interruptible(&tty->write_wait);
+}
+
+EXPORT_SYMBOL_GPL(tty_wakeup);
+
+/**
+ * tty_ldisc_flush - flush line discipline queue
+ * @tty: tty
+ *
+ * Flush the line discipline queue (if any) for this tty. If there
+ * is no line discipline active this is a no-op.
+ */
+
+void tty_ldisc_flush(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld = tty_ldisc_ref(tty);
+ if(ld) {
+ if(ld->flush_buffer)
+ ld->flush_buffer(tty);
+ tty_ldisc_deref(ld);
+ }
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_flush);
+
/*
* 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..
*/
-void do_tty_hangup(void *data)
+static void do_tty_hangup(void *data)
{
struct tty_struct *tty = (struct tty_struct *) data;
struct file * cons_filp = NULL;
struct file *filp, *f = NULL;
struct task_struct *p;
- struct pid *pid;
+ struct tty_ldisc *ld;
int closecount = 0, n;
if (!tty)
check_tty_count(tty, "do_tty_hangup");
file_list_lock();
- list_for_each_entry(filp, &tty->tty_files, f_list) {
+ /* This breaks for file handles being sent over AF_UNIX sockets ? */
+ list_for_each_entry(filp, &tty->tty_files, f_u.fu_list) {
if (filp->f_op->write == redirected_tty_write)
cons_filp = filp;
if (filp->f_op->write != tty_write)
file_list_unlock();
/* FIXME! What are the locking issues here? This may me overdoing things..
- * this question is especially important now that we've removed the irqlock. */
- {
- unsigned long flags;
+ * this question is especially important now that we've removed the irqlock. */
- local_irq_save(flags); // FIXME: is this safe?
- if (tty->ldisc.flush_buffer)
- tty->ldisc.flush_buffer(tty);
+ 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 (tty->driver->flush_buffer)
tty->driver->flush_buffer(tty);
if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
- tty->ldisc.write_wakeup)
- (tty->ldisc.write_wakeup)(tty);
- local_irq_restore(flags); // FIXME: is this safe?
+ ld->write_wakeup)
+ ld->write_wakeup(tty);
+ if (ld->hangup)
+ ld->hangup(tty);
}
+ /* FIXME: Once we trust the LDISC code better we can wait here for
+ ldisc completion and fix the driver call race */
+
wake_up_interruptible(&tty->write_wait);
wake_up_interruptible(&tty->read_wait);
* N_TTY.
*/
if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
+ {
+ down(&tty->termios_sem);
*tty->termios = tty->driver->init_termios;
- if (tty->ldisc.num != ldiscs[N_TTY].num) {
- if (tty->ldisc.close)
- (tty->ldisc.close)(tty);
- module_put(tty->ldisc.owner);
-
- tty->ldisc = ldiscs[N_TTY];
- tty->termios->c_line = N_TTY;
- if (tty->ldisc.open) {
- int i = (tty->ldisc.open)(tty);
- if (i < 0)
- printk(KERN_ERR "do_tty_hangup: N_TTY open: "
- "error %d\n", -i);
- }
+ up(&tty->termios_sem);
}
+ /* Defer ldisc switch */
+ /* tty_deferred_ldisc_switch(N_TTY);
+
+ This should get done automatically when the port closes and
+ tty_release is called */
+
read_lock(&tasklist_lock);
if (tty->session > 0) {
- struct list_head *l;
- for_each_task_pid(tty->session, PIDTYPE_SID, p, l, pid) {
+ do_each_task_pid(tty->session, PIDTYPE_SID, p) {
if (p->signal->tty == tty)
p->signal->tty = NULL;
if (!p->signal->leader)
continue;
- send_group_sig_info(SIGHUP, SEND_SIG_PRIV, p);
- send_group_sig_info(SIGCONT, SEND_SIG_PRIV, p);
+ group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p);
+ group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p);
if (tty->pgrp > 0)
p->signal->tty_old_pgrp = tty->pgrp;
- }
+ } while_each_task_pid(tty->session, PIDTYPE_SID, p);
}
read_unlock(&tasklist_lock);
tty->driver->close(tty, cons_filp);
} else if (tty->driver->hangup)
(tty->driver->hangup)(tty);
+
+ /* We don't want to have driver/ldisc interactions beyond
+ the ones we did here. The driver layer expects no
+ calls after ->hangup() from the ldisc side. However we
+ can't yet guarantee all that */
+
+ set_bit(TTY_HUPPED, &tty->flags);
+ if (ld) {
+ tty_ldisc_enable(tty);
+ tty_ldisc_deref(ld);
+ }
unlock_kernel();
if (f)
fput(f);
{
struct tty_struct *tty;
struct task_struct *p;
- struct list_head *l;
- struct pid *pid;
int tty_pgrp = -1;
lock_kernel();
+ mutex_lock(&tty_mutex);
tty = current->signal->tty;
if (tty) {
tty_pgrp = tty->pgrp;
+ mutex_unlock(&tty_mutex);
if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
tty_vhangup(tty);
} else {
kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit);
kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit);
}
+ mutex_unlock(&tty_mutex);
unlock_kernel();
return;
}
kill_pg(tty_pgrp, SIGCONT, on_exit);
}
+ /* Must lock changes to tty_old_pgrp */
+ mutex_lock(&tty_mutex);
current->signal->tty_old_pgrp = 0;
tty->session = 0;
tty->pgrp = -1;
+ /* Now clear signal->tty under the lock */
read_lock(&tasklist_lock);
- for_each_task_pid(current->signal->session, PIDTYPE_SID, p, l, pid)
+ do_each_task_pid(current->signal->session, PIDTYPE_SID, p) {
p->signal->tty = NULL;
+ } while_each_task_pid(current->signal->session, PIDTYPE_SID, p);
read_unlock(&tasklist_lock);
+ mutex_unlock(&tty_mutex);
unlock_kernel();
}
}
if (tty->driver->start)
(tty->driver->start)(tty);
- if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
- tty->ldisc.write_wakeup)
- (tty->ldisc.write_wakeup)(tty);
+
+ /* If we have a running line discipline it may need kicking */
+ tty_wakeup(tty);
wake_up_interruptible(&tty->write_wait);
}
int i;
struct tty_struct * tty;
struct inode *inode;
+ struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
inode = file->f_dentry->d_inode;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
+ /* We want to wait for the line discipline to sort out in this
+ situation */
+ ld = tty_ldisc_ref_wait(tty);
lock_kernel();
- if (tty->ldisc.read)
- i = (tty->ldisc.read)(tty,file,buf,count);
+ if (ld->read)
+ i = (ld->read)(tty,file,buf,count);
else
i = -EIO;
+ tty_ldisc_deref(ld);
unlock_kernel();
if (i > 0)
- inode->i_atime = CURRENT_TIME;
+ inode->i_atime = current_fs_time(inode->i_sb);
return i;
}
* denial-of-service type attacks
*/
static inline ssize_t do_tty_write(
- ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char __user *, size_t),
+ ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
struct tty_struct *tty,
struct file *file,
- const unsigned char __user *buf,
+ const char __user *buf,
size_t count)
{
ssize_t ret = 0, written = 0;
+ unsigned int chunk;
- if (down_interruptible(&tty->atomic_write)) {
+ if (mutex_lock_interruptible(&tty->atomic_write_lock)) {
return -ERESTARTSYS;
}
- if ( test_bit(TTY_NO_WRITE_SPLIT, &tty->flags) ) {
+
+ /*
+ * We chunk up writes into a temporary buffer. This
+ * simplifies low-level drivers immensely, since they
+ * don't have locking issues and user mode accesses.
+ *
+ * But if TTY_NO_WRITE_SPLIT is set, we should use a
+ * big chunk-size..
+ *
+ * The default chunk-size is 2kB, because the NTTY
+ * layer has problems with bigger chunks. It will
+ * claim to be able to handle more characters than
+ * it actually does.
+ */
+ chunk = 2048;
+ if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
+ chunk = 65536;
+ if (count < chunk)
+ chunk = count;
+
+ /* write_buf/write_cnt is protected by the atomic_write_lock mutex */
+ if (tty->write_cnt < chunk) {
+ unsigned char *buf;
+
+ if (chunk < 1024)
+ chunk = 1024;
+
+ buf = kmalloc(chunk, GFP_KERNEL);
+ if (!buf) {
+ mutex_unlock(&tty->atomic_write_lock);
+ return -ENOMEM;
+ }
+ kfree(tty->write_buf);
+ tty->write_cnt = chunk;
+ tty->write_buf = buf;
+ }
+
+ /* Do the write .. */
+ for (;;) {
+ size_t size = count;
+ if (size > chunk)
+ size = chunk;
+ ret = -EFAULT;
+ if (copy_from_user(tty->write_buf, buf, size))
+ break;
lock_kernel();
- written = write(tty, file, buf, count);
+ ret = write(tty, file, tty->write_buf, size);
unlock_kernel();
- } else {
- for (;;) {
- unsigned long size = max((unsigned long)PAGE_SIZE*2, 16384UL);
- if (size > count)
- size = count;
- lock_kernel();
- ret = write(tty, file, buf, size);
- unlock_kernel();
- if (ret <= 0)
- break;
- written += ret;
- buf += ret;
- count -= ret;
- if (!count)
- break;
- ret = -ERESTARTSYS;
- if (signal_pending(current))
- break;
- cond_resched();
- }
+ if (ret <= 0)
+ break;
+ written += ret;
+ buf += ret;
+ count -= ret;
+ if (!count)
+ break;
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ break;
+ cond_resched();
}
if (written) {
- file->f_dentry->d_inode->i_mtime = CURRENT_TIME;
+ struct inode *inode = file->f_dentry->d_inode;
+ inode->i_mtime = current_fs_time(inode->i_sb);
ret = written;
}
- up(&tty->atomic_write);
+ mutex_unlock(&tty->atomic_write_lock);
return ret;
}
{
struct tty_struct * tty;
struct inode *inode = file->f_dentry->d_inode;
-
+ ssize_t ret;
+ struct tty_ldisc *ld;
+
tty = (struct tty_struct *)file->private_data;
if (tty_paranoia_check(tty, inode, "tty_write"))
return -EIO;
if (!tty || !tty->driver->write || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
- if (!tty->ldisc.write)
- return -EIO;
- return do_tty_write(tty->ldisc.write, tty, file,
- (const unsigned char __user *)buf, count);
+
+ ld = tty_ldisc_ref_wait(tty);
+ if (!ld->write)
+ ret = -EIO;
+ else
+ ret = do_tty_write(ld->write, tty, file, buf, count);
+ tty_ldisc_deref(ld);
+ return ret;
}
ssize_t redirected_tty_write(struct file * file, const char __user * buf, size_t count,
return tty_write(file, buf, count, ppos);
}
+static char ptychar[] = "pqrstuvwxyzabcde";
+
+static inline void pty_line_name(struct tty_driver *driver, int index, char *p)
+{
+ int i = index + driver->name_base;
+ /* ->name is initialized to "ttyp", but "tty" is expected */
+ sprintf(p, "%s%c%x",
+ driver->subtype == PTY_TYPE_SLAVE ? "tty" : driver->name,
+ ptychar[i >> 4 & 0xf], i & 0xf);
+}
+
static inline void tty_line_name(struct tty_driver *driver, int index, char *p)
{
sprintf(p, "%s%d", driver->name, index + driver->name_base);
/*
* WSH 06/09/97: Rewritten to remove races and properly clean up after a
- * failed open. The new code protects the open with a semaphore, so it's
- * really quite straightforward. The semaphore locking can probably be
+ * failed open. The new code protects the open with a mutex, so it's
+ * really quite straightforward. The mutex locking can probably be
* relaxed for the (most common) case of reopening a tty.
*/
static int init_dev(struct tty_driver *driver, int idx,
struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
int retval=0;
- /*
- * Check whether we need to acquire the tty semaphore to avoid
- * race conditions. For now, play it safe.
- */
- down(&tty_sem);
-
/* check whether we're reopening an existing tty */
if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
tty = devpts_get_tty(idx);
* If we fail here just call release_mem to clean up. No need
* to decrement the use counts, as release_mem doesn't care.
*/
+
if (tty->ldisc.open) {
retval = (tty->ldisc.open)(tty);
if (retval)
(tty->ldisc.close)(tty);
goto release_mem_out;
}
+ tty_ldisc_enable(o_tty);
}
+ tty_ldisc_enable(tty);
goto success;
/*
tty->count++;
tty->driver = driver; /* N.B. why do this every time?? */
+ /* FIXME */
+ if(!test_bit(TTY_LDISC, &tty->flags))
+ printk(KERN_ERR "init_dev but no ldisc\n");
success:
*ret_tty = tty;
- /* All paths come through here to release the semaphore */
+ /* All paths come through here to release the mutex */
end_init:
- up(&tty_sem);
return retval;
/* Release locally allocated memory ... nothing placed in slots */
free_mem_out:
- if (o_tp)
- kfree(o_tp);
+ kfree(o_tp);
if (o_tty)
free_tty_struct(o_tty);
- if (ltp)
- kfree(ltp);
- if (tp)
- kfree(tp);
+ kfree(ltp);
+ kfree(tp);
free_tty_struct(tty);
fail_no_mem:
{
struct tty_struct *tty, *o_tty;
int pty_master, tty_closing, o_tty_closing, do_sleep;
- int devpts_master, devpts;
+ int devpts;
int idx;
char buf[64];
+ unsigned long flags;
tty = (struct tty_struct *)filp->private_data;
if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "release_dev"))
pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
tty->driver->subtype == PTY_TYPE_MASTER);
devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0;
- devpts_master = pty_master && devpts;
o_tty = tty->link;
#ifdef TTY_PARANOIA_CHECK
}
}
#endif
-
if (tty->driver->close)
tty->driver->close(tty, filp);
* each iteration we avoid any problems.
*/
while (1) {
+ /* Guard against races with tty->count changes elsewhere and
+ opens on /dev/tty */
+
+ mutex_lock(&tty_mutex);
tty_closing = tty->count <= 1;
o_tty_closing = o_tty &&
(o_tty->count <= (pty_master ? 1 : 0));
printk(KERN_WARNING "release_dev: %s: read/write wait queue "
"active!\n", tty_name(tty, buf));
+ mutex_unlock(&tty_mutex);
schedule();
}
tty->count, tty_name(tty, buf));
tty->count = 0;
}
-
+
/*
* We've decremented tty->count, so we need to remove this file
* descriptor off the tty->tty_files list; this serves two
*/
if (tty_closing || o_tty_closing) {
struct task_struct *p;
- struct list_head *l;
- struct pid *pid;
read_lock(&tasklist_lock);
- for_each_task_pid(tty->session, PIDTYPE_SID, p, l, pid)
+ do_each_task_pid(tty->session, PIDTYPE_SID, p) {
p->signal->tty = NULL;
+ } while_each_task_pid(tty->session, PIDTYPE_SID, p);
if (o_tty)
- for_each_task_pid(o_tty->session, PIDTYPE_SID, p,l, pid)
+ do_each_task_pid(o_tty->session, PIDTYPE_SID, p) {
p->signal->tty = NULL;
+ } while_each_task_pid(o_tty->session, PIDTYPE_SID, p);
read_unlock(&tasklist_lock);
}
+ mutex_unlock(&tty_mutex);
+
/* check whether both sides are closing ... */
if (!tty_closing || (o_tty && !o_tty_closing))
return;
#ifdef TTY_DEBUG_HANGUP
printk(KERN_DEBUG "freeing tty structure...");
#endif
-
/*
* Prevent flush_to_ldisc() from rescheduling the work for later. Then
- * kill any delayed work.
+ * kill any delayed work. As this is the final close it does not
+ * race with the set_ldisc code path.
*/
+ clear_bit(TTY_LDISC, &tty->flags);
clear_bit(TTY_DONT_FLIP, &tty->flags);
- cancel_delayed_work(&tty->flip.work);
+ cancel_delayed_work(&tty->buf.work);
/*
- * Wait for ->hangup_work and ->flip.work handlers to terminate
+ * Wait for ->hangup_work and ->buf.work handlers to terminate
*/
+
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.
+ */
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ while(tty->ldisc.refcount)
+ {
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0);
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ }
+ 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);
- module_put(tty->ldisc.owner);
+ tty_ldisc_put(tty->ldisc.num);
- tty->ldisc = ldiscs[N_TTY];
- tty->termios->c_line = N_TTY;
+ /*
+ * Switch the line discipline back
+ */
+ tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+ 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);
- module_put(o_tty->ldisc.owner);
- o_tty->ldisc = ldiscs[N_TTY];
+ tty_ldisc_put(o_tty->ldisc.num);
+ tty_ldisc_assign(o_tty, tty_ldisc_get(N_TTY));
+ tty_set_termios_ldisc(o_tty,N_TTY);
}
-
/*
* The release_mem function takes care of the details of clearing
* the slots and preserving the termios structure.
unsigned short saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
+
retry_open:
noctty = filp->f_flags & O_NOCTTY;
index = -1;
retval = 0;
+
+ mutex_lock(&tty_mutex);
if (device == MKDEV(TTYAUX_MAJOR,0)) {
- if (!current->signal->tty)
+ if (!current->signal->tty) {
+ mutex_unlock(&tty_mutex);
return -ENXIO;
+ }
driver = current->signal->tty->driver;
index = current->signal->tty->index;
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
goto got_driver;
}
#ifdef CONFIG_VT
- if (device == MKDEV(TTY_MAJOR,0)) {
- extern int fg_console;
+ if (console_use_vt && (device == MKDEV(TTY_MAJOR,0))) {
extern struct tty_driver *console_driver;
driver = console_driver;
index = fg_console;
noctty = 1;
goto got_driver;
}
+ mutex_unlock(&tty_mutex);
return -ENODEV;
}
driver = get_tty_driver(device, &index);
- if (!driver)
+ if (!driver) {
+ mutex_unlock(&tty_mutex);
return -ENODEV;
+ }
got_driver:
retval = init_dev(driver, index, &tty);
+ mutex_unlock(&tty_mutex);
if (retval)
return retval;
}
up(&allocated_ptys_lock);
+ mutex_lock(&tty_mutex);
retval = init_dev(ptm_driver, index, &tty);
+ mutex_unlock(&tty_mutex);
+
if (retval)
goto out;
return 0;
out1:
release_dev(filp);
+ return retval;
out:
down(&allocated_ptys_lock);
idr_remove(&allocated_ptys, index);
static unsigned int tty_poll(struct file * filp, poll_table * wait)
{
struct tty_struct * tty;
+ struct tty_ldisc *ld;
+ int ret = 0;
tty = (struct tty_struct *)filp->private_data;
if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "tty_poll"))
return 0;
-
- if (tty->ldisc.poll)
- return (tty->ldisc.poll)(tty, filp, wait);
- return 0;
+
+ ld = tty_ldisc_ref_wait(tty);
+ if (ld->poll)
+ ret = (ld->poll)(tty, filp, wait);
+ tty_ldisc_deref(ld);
+ return ret;
}
static int tty_fasync(int fd, struct file * filp, int on)
static int tiocsti(struct tty_struct *tty, char __user *p)
{
char ch, mbz = 0;
-
+ struct tty_ldisc *ld;
+
if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN))
return -EPERM;
if (get_user(ch, p))
return -EFAULT;
- tty->ldisc.receive_buf(tty, &ch, &mbz, 1);
+ ld = tty_ldisc_ref_wait(tty);
+ ld->receive_buf(tty, &ch, &mbz, 1);
+ tty_ldisc_deref(ld);
return 0;
}
return 0;
#ifdef CONFIG_VT
if (tty->driver->type == TTY_DRIVER_TYPE_CONSOLE) {
- unsigned int currcons = tty->index;
int rc;
acquire_console_sem();
- rc = vc_resize(currcons, tmp_ws.ws_col, tmp_ws.ws_row);
+ rc = vc_resize(tty->driver_data, tmp_ws.ws_col, tmp_ws.ws_row);
release_console_sem();
if (rc)
return -ENXIO;
static int tioccons(struct file *file)
{
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
if (file->f_op->write == redirected_tty_write) {
struct file *f;
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
spin_lock(&redirect_lock);
f = redirect;
redirect = NULL;
static int tiocsctty(struct tty_struct *tty, int arg)
{
- struct list_head *l;
- struct pid *pid;
task_t *p;
if (current->signal->leader &&
*/
read_lock(&tasklist_lock);
- for_each_task_pid(tty->session, PIDTYPE_SID, p, l, pid)
+ do_each_task_pid(tty->session, PIDTYPE_SID, p) {
p->signal->tty = NULL;
+ } while_each_task_pid(tty->session, PIDTYPE_SID, p);
read_unlock(&tasklist_lock);
} else
return -EPERM;
static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
{
+ pid_t pgrp;
/*
* (tty == real_tty) is a cheap way of
* testing if the tty is NOT a master pty.
*/
if (tty == real_tty && current->signal->tty != real_tty)
return -ENOTTY;
- return put_user(real_tty->pgrp, p);
+
+ pgrp = vx_map_pid(real_tty->pgrp);
+ return put_user(pgrp, p);
}
static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
return -ENOTTY;
if (get_user(pgrp, p))
return -EFAULT;
+
+ pgrp = vx_rmap_pid(pgrp);
if (pgrp < 0)
return -EINVAL;
if (session_of_pgrp(pgrp) != current->signal->session)
return tty_set_ldisc(tty, ldisc);
}
-static int send_break(struct tty_struct *tty, int duration)
+static int send_break(struct tty_struct *tty, unsigned int duration)
{
- set_current_state(TASK_INTERRUPTIBLE);
-
tty->driver->break_ctl(tty, -1);
- if (!signal_pending(current))
- schedule_timeout(duration);
+ if (!signal_pending(current)) {
+ msleep_interruptible(duration);
+ }
tty->driver->break_ctl(tty, 0);
if (signal_pending(current))
return -EINTR;
struct tty_struct *tty, *real_tty;
void __user *p = (void __user *)arg;
int retval;
+ struct tty_ldisc *ld;
tty = (struct tty_struct *)file->private_data;
if (tty_paranoia_check(tty, inode, "tty_ioctl"))
case TIOCGSID:
return tiocgsid(tty, real_tty, p);
case TIOCGETD:
+ /* FIXME: check this is ok */
return put_user(tty->ldisc.num, (int __user *)p);
case TIOCSETD:
return tiocsetd(tty, p);
* all by anyone?
*/
if (!arg)
- return send_break(tty, HZ/4);
+ return send_break(tty, 250);
return 0;
case TCSBRKP: /* support for POSIX tcsendbreak() */
- return send_break(tty, arg ? arg*(HZ/10) : HZ/4);
+ return send_break(tty, arg ? arg*100 : 250);
case TIOCMGET:
return tty_tiocmget(tty, file, p);
return tty_tiocmset(tty, file, cmd, p);
}
if (tty->driver->ioctl) {
- int retval = (tty->driver->ioctl)(tty, file, cmd, arg);
+ retval = (tty->driver->ioctl)(tty, file, cmd, arg);
if (retval != -ENOIOCTLCMD)
return retval;
}
- if (tty->ldisc.ioctl) {
- int retval = (tty->ldisc.ioctl)(tty, file, cmd, arg);
- if (retval != -ENOIOCTLCMD)
- return retval;
+ ld = tty_ldisc_ref_wait(tty);
+ retval = -EINVAL;
+ if (ld->ioctl) {
+ retval = ld->ioctl(tty, file, cmd, arg);
+ if (retval == -ENOIOCTLCMD)
+ retval = -EINVAL;
}
- return -EINVAL;
+ tty_ldisc_deref(ld);
+ return retval;
}
tty_hangup(tty);
#else
struct tty_struct *tty = arg;
- struct task_struct *p;
- struct list_head *l;
- struct pid *pid;
+ struct task_struct *g, *p;
int session;
int i;
struct file *filp;
+ struct tty_ldisc *disc;
+ struct fdtable *fdt;
if (!tty)
return;
session = tty->session;
- if (tty->ldisc.flush_buffer)
- tty->ldisc.flush_buffer(tty);
+
+ /* We don't want an ldisc switch during this */
+ disc = tty_ldisc_ref(tty);
+ if (disc && disc->flush_buffer)
+ disc->flush_buffer(tty);
+ tty_ldisc_deref(disc);
+
if (tty->driver->flush_buffer)
tty->driver->flush_buffer(tty);
+
read_lock(&tasklist_lock);
- for_each_task_pid(session, PIDTYPE_SID, p, l, pid) {
- if (p->signal->tty == tty || session > 0) {
+ /* Kill the entire session */
+ do_each_task_pid(session, PIDTYPE_SID, p) {
+ printk(KERN_NOTICE "SAK: killed process %d"
+ " (%s): p->signal->session==tty->session\n",
+ p->pid, p->comm);
+ send_sig(SIGKILL, p, 1);
+ } while_each_task_pid(session, PIDTYPE_SID, p);
+ /* Now kill any processes that happen to have the
+ * tty open.
+ */
+ do_each_thread(g, p) {
+ if (p->signal->tty == tty) {
printk(KERN_NOTICE "SAK: killed process %d"
" (%s): p->signal->session==tty->session\n",
p->pid, p->comm);
}
task_lock(p);
if (p->files) {
+ /*
+ * We don't take a ref to the file, so we must
+ * hold ->file_lock instead.
+ */
spin_lock(&p->files->file_lock);
- for (i=0; i < p->files->max_fds; i++) {
+ fdt = files_fdtable(p->files);
+ for (i=0; i < fdt->max_fds; i++) {
filp = fcheck_files(p->files, i);
if (!filp)
continue;
printk(KERN_NOTICE "SAK: killed process %d"
" (%s): fd#%d opened to the tty\n",
p->pid, p->comm, i);
- send_sig(SIGKILL, p, 1);
+ force_sig(SIGKILL, p);
break;
}
}
spin_unlock(&p->files->file_lock);
}
task_unlock(p);
- }
+ } while_each_thread(g, p);
read_unlock(&tasklist_lock);
#endif
}
/*
* This routine is called out of the software interrupt to flush data
- * from the flip buffer to the line discipline.
+ * from the buffer chain to the line discipline.
*/
+
static void flush_to_ldisc(void *private_)
{
struct tty_struct *tty = (struct tty_struct *) private_;
- unsigned char *cp;
- char *fp;
- int count;
- unsigned long flags;
+ unsigned long flags;
+ struct tty_ldisc *disc;
+ struct tty_buffer *tbuf, *head;
+ int count;
+ char *char_buf;
+ unsigned char *flag_buf;
+
+ disc = tty_ldisc_ref(tty);
+ if (disc == NULL) /* !TTY_LDISC */
+ return;
if (test_bit(TTY_DONT_FLIP, &tty->flags)) {
/*
* Do it after the next timer tick:
*/
- schedule_delayed_work(&tty->flip.work, 1);
- return;
+ schedule_delayed_work(&tty->buf.work, 1);
+ goto out;
}
-
- spin_lock_irqsave(&tty->read_lock, flags);
- if (tty->flip.buf_num) {
- cp = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
- fp = tty->flip.flag_buf + TTY_FLIPBUF_SIZE;
- tty->flip.buf_num = 0;
- tty->flip.char_buf_ptr = tty->flip.char_buf;
- tty->flip.flag_buf_ptr = tty->flip.flag_buf;
- } else {
- cp = tty->flip.char_buf;
- fp = tty->flip.flag_buf;
- tty->flip.buf_num = 1;
- tty->flip.char_buf_ptr = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
- tty->flip.flag_buf_ptr = tty->flip.flag_buf + TTY_FLIPBUF_SIZE;
+ spin_lock_irqsave(&tty->buf.lock, flags);
+ head = tty->buf.head;
+ tty->buf.head = NULL;
+ while((tbuf = head) != NULL) {
+ while ((count = tbuf->commit - tbuf->read) != 0) {
+ char_buf = tbuf->char_buf_ptr + tbuf->read;
+ flag_buf = tbuf->flag_buf_ptr + tbuf->read;
+ tbuf->read += count;
+ spin_unlock_irqrestore(&tty->buf.lock, flags);
+ disc->receive_buf(tty, char_buf, flag_buf, count);
+ spin_lock_irqsave(&tty->buf.lock, flags);
+ }
+ if (tbuf->active) {
+ tty->buf.head = head;
+ break;
+ }
+ head = tbuf->next;
+ if (head == NULL)
+ tty->buf.tail = NULL;
+ tty_buffer_free(tty, tbuf);
}
- count = tty->flip.count;
- tty->flip.count = 0;
- spin_unlock_irqrestore(&tty->read_lock, flags);
-
- tty->ldisc.receive_buf(tty, cp, fp, count);
+ spin_unlock_irqrestore(&tty->buf.lock, flags);
+out:
+ tty_ldisc_deref(disc);
}
/*
static int n_baud_table = ARRAY_SIZE(baud_table);
+/**
+ * tty_termios_baud_rate
+ * @termios: termios structure
+ *
+ * Convert termios baud rate data into a speed. This should be called
+ * with the termios lock held if this termios is a terminal termios
+ * structure. May change the termios data.
+ */
+
int tty_termios_baud_rate(struct termios *termios)
{
- unsigned int cbaud = termios->c_cflag & CBAUD;
+ unsigned int cbaud;
+
+ cbaud = termios->c_cflag & CBAUD;
if (cbaud & CBAUDEX) {
cbaud &= ~CBAUDEX;
else
cbaud += 15;
}
-
return baud_table[cbaud];
}
EXPORT_SYMBOL(tty_termios_baud_rate);
+/**
+ * tty_get_baud_rate - get tty bit rates
+ * @tty: tty to query
+ *
+ * Returns the baud rate as an integer for this terminal. The
+ * termios lock must be held by the caller and the terminal bit
+ * flags may be updated.
+ */
+
int tty_get_baud_rate(struct tty_struct *tty)
{
int baud = tty_termios_baud_rate(tty->termios);
EXPORT_SYMBOL(tty_get_baud_rate);
+/**
+ * tty_flip_buffer_push - terminal
+ * @tty: tty to push
+ *
+ * Queue a push of the terminal flip buffers to the line discipline. This
+ * function must not be called from IRQ context if tty->low_latency is set.
+ *
+ * In the event of the queue being busy for flipping the work will be
+ * held off and retried later.
+ */
+
void tty_flip_buffer_push(struct tty_struct *tty)
{
+ unsigned long flags;
+ spin_lock_irqsave(&tty->buf.lock, flags);
+ if (tty->buf.tail != NULL) {
+ tty->buf.tail->active = 0;
+ tty->buf.tail->commit = tty->buf.tail->used;
+ }
+ spin_unlock_irqrestore(&tty->buf.lock, flags);
+
if (tty->low_latency)
flush_to_ldisc((void *) tty);
else
- schedule_delayed_work(&tty->flip.work, 1);
+ schedule_delayed_work(&tty->buf.work, 1);
}
EXPORT_SYMBOL(tty_flip_buffer_push);
+
/*
* This subroutine initializes a tty structure.
*/
{
memset(tty, 0, sizeof(struct tty_struct));
tty->magic = TTY_MAGIC;
- tty->ldisc = ldiscs[N_TTY];
+ tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
tty->pgrp = -1;
- tty->flip.char_buf_ptr = tty->flip.char_buf;
- tty->flip.flag_buf_ptr = tty->flip.flag_buf;
- INIT_WORK(&tty->flip.work, flush_to_ldisc, tty);
- init_MUTEX(&tty->flip.pty_sem);
+ tty->overrun_time = jiffies;
+ tty->buf.head = tty->buf.tail = NULL;
+ tty_buffer_init(tty);
+ INIT_WORK(&tty->buf.work, flush_to_ldisc, tty);
+ init_MUTEX(&tty->buf.pty_sem);
+ init_MUTEX(&tty->termios_sem);
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait);
INIT_WORK(&tty->hangup_work, do_tty_hangup, tty);
- sema_init(&tty->atomic_read, 1);
- sema_init(&tty->atomic_write, 1);
+ mutex_init(&tty->atomic_read_lock);
+ mutex_init(&tty->atomic_write_lock);
spin_lock_init(&tty->read_lock);
INIT_LIST_HEAD(&tty->tty_files);
INIT_WORK(&tty->SAK_work, NULL, NULL);
*/
static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
{
- tty->driver->write(tty, 0, &ch, 1);
+ tty->driver->write(tty, &ch, 1);
}
-static struct class_simple *tty_class;
+static struct class *tty_class;
/**
* tty_register_device - register a tty device
void tty_register_device(struct tty_driver *driver, unsigned index,
struct device *device)
{
+ char name[64];
dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
if (index >= driver->num) {
devfs_mk_cdev(dev, S_IFCHR | S_IRUSR | S_IWUSR,
"%s%d", driver->devfs_name, index + driver->name_base);
- /* we don't care about the ptys */
- /* how nice to hide this behind some crappy interface.. */
- if (driver->type != TTY_DRIVER_TYPE_PTY) {
- char name[64];
+ if (driver->type == TTY_DRIVER_TYPE_PTY)
+ pty_line_name(driver, index, name);
+ else
tty_line_name(driver, index, name);
- class_simple_device_add(tty_class, dev, device, name);
- }
+ class_device_create(tty_class, NULL, dev, device, "%s", name);
}
/**
void tty_unregister_device(struct tty_driver *driver, unsigned index)
{
devfs_remove("%s%d", driver->devfs_name, index + driver->name_base);
- class_simple_device_remove(MKDEV(driver->major, driver->minor_start) + index);
+ class_device_destroy(tty_class, MKDEV(driver->major, driver->minor_start) + index);
}
EXPORT_SYMBOL(tty_register_device);
#ifdef CONFIG_EARLY_PRINTK
disable_early_printk();
#endif
-#ifdef CONFIG_SERIAL_68360
- /* This is not a console initcall. I know not what it's doing here.
- So I haven't moved it. dwmw2 */
- rs_360_init();
-#endif
- call = &__con_initcall_start;
- while (call < &__con_initcall_end) {
+ call = __con_initcall_start;
+ while (call < __con_initcall_end) {
(*call)();
call++;
}
static int __init tty_class_init(void)
{
- tty_class = class_simple_create(THIS_MODULE, "tty");
+ tty_class = class_create(THIS_MODULE, "tty");
if (IS_ERR(tty_class))
return PTR_ERR(tty_class);
return 0;
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
panic("Couldn't register /dev/tty driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty");
- class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
+ class_device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
cdev_init(&console_cdev, &console_fops);
if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
panic("Couldn't register /dev/console driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 1), S_IFCHR|S_IRUSR|S_IWUSR, "console");
- class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 1), NULL, "console");
+ class_device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, "console");
#ifdef CONFIG_UNIX98_PTYS
cdev_init(&ptmx_cdev, &ptmx_fops);
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
panic("Couldn't register /dev/ptmx driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx");
- class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
+ class_device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
#endif
#ifdef CONFIG_VT
+ if (!console_use_vt)
+ goto out_vt;
cdev_init(&vc0_cdev, &console_fops);
if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||
register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
panic("Couldn't register /dev/tty0 driver\n");
devfs_mk_cdev(MKDEV(TTY_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vc/0");
- class_simple_device_add(tty_class, MKDEV(TTY_MAJOR, 0), NULL, "tty0");
+ class_device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), NULL, "tty0");
vty_init();
+ out_vt:
#endif
return 0;
}