vserver 1.9.5.x5
[linux-2.6.git] / drivers / char / tty_io.c
index 0c76237..5a6191d 100644 (file)
 #include <linux/module.h>
 #include <linux/smp_lock.h>
 #include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/wait.h>
+#include <linux/bitops.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>
 
@@ -119,15 +122,22 @@ struct termios tty_std_termios = {        /* for the benefit of tty drivers  */
 
 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 */
+/* Semaphore to protect creating and releasing a tty. This is shared with
+   vt.c for deeply disgusting hack reasons */
 DECLARE_MUTEX(tty_sem);
 
 #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);
@@ -159,6 +169,7 @@ static struct tty_struct *alloc_tty_struct(void)
 
 static inline void free_tty_struct(struct tty_struct *tty)
 {
+       kfree(tty->write_buf);
        kfree(tty);
 }
 
@@ -220,65 +231,323 @@ static int check_tty_count(struct tty_struct *tty, const char *routine)
        return 0;
 }
 
+/*
+ *     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;
        
+       spin_lock_irqsave(&tty_ldisc_lock, flags);
        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));
+               tty_ldiscs[disc] = *new_ldisc;
+               tty_ldiscs[disc].num = disc;
+               tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
+               tty_ldiscs[disc].refcount = 0;
+       } else {
+               if(tty_ldiscs[disc].refcount)
+                       ret = -EBUSY;
+               else
+                       tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
+       }
+       spin_unlock_irqrestore(&tty_ldisc_lock, flags);
        
-       return 0;
+       return ret;
 }
 
 EXPORT_SYMBOL(tty_register_ldisc);
 
-/* Set the discipline of a tty line. */
+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;
+       
+       if (disc < N_TTY || disc >= NR_LDISCS)
+               BUG();
+               
+       spin_lock_irqsave(&tty_ldisc_lock, flags);
+       ld = &tty_ldiscs[disc];
+       if(ld->refcount == 0)
+               BUG();
+       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;
+
+       if(ld == NULL)
+               BUG();
+               
+       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;
        char buf[64];
+       int work;
+       unsigned long flags;
+       struct tty_ldisc *ld;
 
        if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS))
                return -EINVAL;
+
+restart:
+
+       if (tty->ldisc.num == ldisc)
+               return 0;       /* We are already in the desired discipline */
+       
+       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 */
-
-       if (!try_module_get(ldiscs[ldisc].owner))
-               return -EINVAL;
-       
        o_ldisc = tty->ldisc;
 
        tty_wait_until_sent(tty, 0);
+
+       /*
+        *      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)
+       {
+               /* 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;
+       }
+       clear_bit(TTY_LDISC, &tty->flags);      
+       clear_bit(TTY_DONT_FLIP, &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->flip.work);
+       /*
+        * Wait for ->hangup_work and ->flip.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);
 
@@ -288,19 +557,34 @@ static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
                                              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);
+       
+       /* Restart it in case no characters kick it off. Safe if
+          already running */
+       if(work)
+               schedule_delayed_work(&tty->flip.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;
 
@@ -342,18 +626,12 @@ EXPORT_SYMBOL(tty_check_change);
 static ssize_t hung_up_tty_read(struct file * file, char __user * buf,
                                size_t count, loff_t *ppos)
 {
-       /* Can't seek (pread) on ttys.  */
-       if (ppos != &file->f_pos)
-               return -ESPIPE;
        return 0;
 }
 
 static ssize_t hung_up_tty_write(struct file * file, const char __user * buf,
                                 size_t count, loff_t *ppos)
 {
-       /* Can't seek (pwrite) on ttys.  */
-       if (ppos != &file->f_pos)
-               return -ESPIPE;
        return -EIO;
 }
 
@@ -380,6 +658,19 @@ static struct file_operations tty_fops = {
        .fasync         = tty_fasync,
 };
 
+#ifdef CONFIG_UNIX98_PTYS
+static struct file_operations ptmx_fops = {
+       .llseek         = no_llseek,
+       .read           = tty_read,
+       .write          = tty_write,
+       .poll           = tty_poll,
+       .ioctl          = tty_ioctl,
+       .open           = ptmx_open,
+       .release        = tty_release,
+       .fasync         = tty_fasync,
+};
+#endif
+
 static struct file_operations console_fops = {
        .llseek         = no_llseek,
        .read           = tty_read,
@@ -400,20 +691,67 @@ static struct file_operations hung_up_tty_fops = {
        .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)
@@ -431,6 +769,7 @@ void do_tty_hangup(void *data)
        
        check_tty_count(tty, "do_tty_hangup");
        file_list_lock();
+       /* This breaks for file handles being sent over AF_UNIX sockets ? */
        list_for_each_entry(filp, &tty->tty_files, f_list) {
                if (filp->f_op->write == redirected_tty_write)
                        cons_filp = filp;
@@ -443,21 +782,25 @@ void do_tty_hangup(void *data)
        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);
 
@@ -466,26 +809,21 @@ void do_tty_hangup(void *data)
         * 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)
@@ -494,7 +832,7 @@ void do_tty_hangup(void *data)
                        send_group_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);
 
@@ -514,6 +852,17 @@ void do_tty_hangup(void *data)
                                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);
@@ -566,15 +915,15 @@ void disassociate_ctty(int on_exit)
 {
        struct tty_struct *tty;
        struct task_struct *p;
-       struct list_head *l;
-       struct pid *pid;
        int tty_pgrp = -1;
 
        lock_kernel();
 
+       down(&tty_sem);
        tty = current->signal->tty;
        if (tty) {
                tty_pgrp = tty->pgrp;
+               up(&tty_sem);
                if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
                        tty_vhangup(tty);
        } else {
@@ -582,6 +931,7 @@ void disassociate_ctty(int on_exit)
                        kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit);
                        kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit);
                }
+               up(&tty_sem);
                unlock_kernel();        
                return;
        }
@@ -591,14 +941,19 @@ void disassociate_ctty(int on_exit)
                        kill_pg(tty_pgrp, SIGCONT, on_exit);
        }
 
+       /* Must lock changes to tty_old_pgrp */
+       down(&tty_sem);
        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);
+       up(&tty_sem);
        unlock_kernel();
 }
 
@@ -630,9 +985,9 @@ void start_tty(struct tty_struct *tty)
        }
        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);
 }
 
@@ -644,10 +999,7 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
        int i;
        struct tty_struct * tty;
        struct inode *inode;
-
-       /* Can't seek (pread) on ttys.  */
-       if (ppos != &file->f_pos)
-               return -ESPIPE;
+       struct tty_ldisc *ld;
 
        tty = (struct tty_struct *)file->private_data;
        inode = file->f_dentry->d_inode;
@@ -656,14 +1008,18 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
        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;
 }
 
@@ -672,44 +1028,81 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
  * 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)) {
                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 semaphore */
+       if (tty->write_cnt < chunk) {
+               unsigned char *buf;
+
+               if (chunk < 1024)
+                       chunk = 1024;
+
+               buf = kmalloc(chunk, GFP_KERNEL);
+               if (!buf) {
+                       up(&tty->atomic_write);
+                       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);
@@ -722,20 +1115,22 @@ static ssize_t tty_write(struct file * file, const char __user * buf, size_t cou
 {
        struct tty_struct * tty;
        struct inode *inode = file->f_dentry->d_inode;
-
-       /* Can't seek (pwrite) on ttys.  */
-       if (ppos != &file->f_pos)
-               return -ESPIPE;
-
+       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,
@@ -752,9 +1147,6 @@ ssize_t redirected_tty_write(struct file * file, const char __user * buf, size_t
 
        if (p) {
                ssize_t res;
-               /* Can't seek (pwrite) on ttys.  */
-               if (ppos != &file->f_pos)
-                       return -ESPIPE;
                res = vfs_write(p, buf, count, &p->f_pos);
                fput(p);
                return res;
@@ -763,6 +1155,17 @@ ssize_t redirected_tty_write(struct file * file, const char __user * buf, size_t
        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);
@@ -782,12 +1185,6 @@ 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);
@@ -924,6 +1321,7 @@ static int init_dev(struct tty_driver *driver, int 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)
@@ -936,7 +1334,9 @@ static int init_dev(struct tty_driver *driver, int idx,
                                (tty->ldisc.close)(tty);
                        goto release_mem_out;
                }
+               tty_ldisc_enable(o_tty);
        }
+       tty_ldisc_enable(tty);
        goto success;
 
        /*
@@ -965,12 +1365,14 @@ fast_track:
        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 */
 end_init:
-       up(&tty_sem);
        return retval;
 
        /* Release locally allocated memory ... nothing placed in slots */
@@ -1065,8 +1467,10 @@ static void release_dev(struct file * filp)
 {
        struct tty_struct *tty, *o_tty;
        int     pty_master, tty_closing, o_tty_closing, do_sleep;
+       int     devpts_master, 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"))
@@ -1079,6 +1483,8 @@ static void release_dev(struct file * filp)
        idx = tty->index;
        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
@@ -1141,7 +1547,6 @@ static void release_dev(struct file * filp)
                }
        }
 #endif
-
        if (tty->driver->close)
                tty->driver->close(tty, filp);
 
@@ -1163,9 +1568,14 @@ static void release_dev(struct file * filp)
         * each iteration we avoid any problems.
         */
        while (1) {
+               /* Guard against races with tty->count changes elsewhere and
+                  opens on /dev/tty */
+                  
+               down(&tty_sem);
                tty_closing = tty->count <= 1;
                o_tty_closing = o_tty &&
                        (o_tty->count <= (pty_master ? 1 : 0));
+               up(&tty_sem);
                do_sleep = 0;
 
                if (tty_closing) {
@@ -1201,6 +1611,8 @@ static void release_dev(struct file * filp)
         * both sides, and we've completed the last operation that could 
         * block, so it's safe to proceed with closing.
         */
+        
+       down(&tty_sem);
        if (pty_master) {
                if (--o_tty->count < 0) {
                        printk(KERN_WARNING "release_dev: bad pty slave count "
@@ -1214,7 +1626,8 @@ static void release_dev(struct file * filp)
                       tty->count, tty_name(tty, buf));
                tty->count = 0;
        }
-
+       up(&tty_sem);
+       
        /*
         * We've decremented tty->count, so we need to remove this file
         * descriptor off the tty->tty_files list; this serves two
@@ -1246,15 +1659,15 @@ static void release_dev(struct file * filp)
         */
        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);
        }
 
@@ -1265,41 +1678,73 @@ static void release_dev(struct file * filp)
 #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);
 
        /*
         * Wait for ->hangup_work and ->flip.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.
         */
        release_mem(tty, idx);
+
+#ifdef CONFIG_UNIX98_PTYS
+       /* Make this pty number available for reallocation */
+       if (devpts) {
+               down(&allocated_ptys_lock);
+               idr_remove(&allocated_ptys, idx);
+               up(&allocated_ptys_lock);
+       }
+#endif
+
 }
 
 /*
@@ -1322,11 +1767,21 @@ static int tty_open(struct inode * inode, struct file * filp)
        int index;
        dev_t device = inode->i_rdev;
        unsigned short saved_flags = filp->f_flags;
+
+       nonseekable_open(inode, filp);
+       
 retry_open:
        noctty = filp->f_flags & O_NOCTTY;
+       index  = -1;
+       retval = 0;
+       
+       down(&tty_sem);
+
        if (device == MKDEV(TTYAUX_MAJOR,0)) {
-               if (!current->signal->tty)
+               if (!current->signal->tty) {
+                       up(&tty_sem);
                        return -ENXIO;
+               }
                driver = current->signal->tty->driver;
                index = current->signal->tty->index;
                filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
@@ -1344,52 +1799,27 @@ retry_open:
        }
 #endif
        if (device == MKDEV(TTYAUX_MAJOR,1)) {
-               struct console *c = console_drivers;
-               for (c = console_drivers; c; c = c->next) {
-                       if (!c->device)
-                               continue;
-                       driver = c->device(c, &index);
-                       if (!driver)
-                               continue;
+               driver = console_device(&index);
+               if (driver) {
                        /* Don't let /dev/console block */
                        filp->f_flags |= O_NONBLOCK;
                        noctty = 1;
                        goto got_driver;
                }
+               up(&tty_sem);
                return -ENODEV;
        }
 
-#ifdef CONFIG_UNIX98_PTYS
-       if (device == MKDEV(TTYAUX_MAJOR,2)) {
-               /* find a device that is not in use. */
-               static int next_ptmx_dev = 0;
-               retval = -1;
-               driver = ptm_driver;
-               while (driver->refcount < pty_limit) {
-                       index = next_ptmx_dev;
-                       next_ptmx_dev = (next_ptmx_dev+1) % driver->num;
-                       if (!init_dev(driver, index, &tty))
-                               goto ptmx_found; /* ok! */
-               }
-               return -EIO; /* no free ptys */
-       ptmx_found:
-               set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
-               if (devpts_pty_new(tty->link)) {
-                       /* BADNESS - need to destroy both ptm and pts! */
-                       return -ENOMEM;
-               }
-               noctty = 1;
-       } else
-#endif
-       {
-               driver = get_tty_driver(device, &index);
-               if (!driver)
-                       return -ENODEV;
-got_driver:
-               retval = init_dev(driver, index, &tty);
-               if (retval)
-                       return retval;
+       driver = get_tty_driver(device, &index);
+       if (!driver) {
+               up(&tty_sem);
+               return -ENODEV;
        }
+got_driver:
+       retval = init_dev(driver, index, &tty);
+       up(&tty_sem);
+       if (retval)
+               return retval;
 
        filp->private_data = tty;
        file_move(filp, &tty->tty_files);
@@ -1400,10 +1830,12 @@ got_driver:
 #ifdef TTY_DEBUG_HANGUP
        printk(KERN_DEBUG "opening %s...", tty->name);
 #endif
-       if (tty->driver->open)
-               retval = tty->driver->open(tty, filp);
-       else
-               retval = -ENODEV;
+       if (!retval) {
+               if (tty->driver->open)
+                       retval = tty->driver->open(tty, filp);
+               else
+                       retval = -ENODEV;
+       }
        filp->f_flags = saved_flags;
 
        if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && !capable(CAP_SYS_ADMIN))
@@ -1414,7 +1846,6 @@ got_driver:
                printk(KERN_DEBUG "error %d in opening %s...", retval,
                       tty->name);
 #endif
-
                release_dev(filp);
                if (retval != -ERESTARTSYS)
                        return retval;
@@ -1442,6 +1873,65 @@ got_driver:
        return 0;
 }
 
+#ifdef CONFIG_UNIX98_PTYS
+static int ptmx_open(struct inode * inode, struct file * filp)
+{
+       struct tty_struct *tty;
+       int retval;
+       int index;
+       int idr_ret;
+
+       nonseekable_open(inode, filp);
+
+       /* find a device that is not in use. */
+       down(&allocated_ptys_lock);
+       if (!idr_pre_get(&allocated_ptys, GFP_KERNEL)) {
+               up(&allocated_ptys_lock);
+               return -ENOMEM;
+       }
+       idr_ret = idr_get_new(&allocated_ptys, NULL, &index);
+       if (idr_ret < 0) {
+               up(&allocated_ptys_lock);
+               if (idr_ret == -EAGAIN)
+                       return -ENOMEM;
+               return -EIO;
+       }
+       if (index >= pty_limit) {
+               idr_remove(&allocated_ptys, index);
+               up(&allocated_ptys_lock);
+               return -EIO;
+       }
+       up(&allocated_ptys_lock);
+
+       down(&tty_sem);
+       retval = init_dev(ptm_driver, index, &tty);
+       up(&tty_sem);
+       
+       if (retval)
+               goto out;
+
+       set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
+       filp->private_data = tty;
+       file_move(filp, &tty->tty_files);
+
+       retval = -ENOMEM;
+       if (devpts_pty_new(tty->link))
+               goto out1;
+
+       check_tty_count(tty, "tty_open");
+       retval = ptm_driver->open(tty, filp);
+       if (!retval)
+               return 0;
+out1:
+       release_dev(filp);
+out:
+       down(&allocated_ptys_lock);
+       idr_remove(&allocated_ptys, index);
+       up(&allocated_ptys_lock);
+       return retval;
+}
+#endif
+
 static int tty_release(struct inode * inode, struct file * filp)
 {
        lock_kernel();
@@ -1454,14 +1944,18 @@ static int tty_release(struct inode * inode, struct file * filp)
 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)
@@ -1493,12 +1987,15 @@ 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;
 }
 
@@ -1541,10 +2038,10 @@ static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty,
 
 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;
@@ -1581,8 +2078,6 @@ static int fionbio(struct file *file, int __user *p)
 
 static int tiocsctty(struct tty_struct *tty, int arg)
 {
-       struct list_head *l;
-       struct pid *pid;
        task_t *p;
 
        if (current->signal->leader &&
@@ -1605,8 +2100,9 @@ static int tiocsctty(struct tty_struct *tty, int arg)
                         */
 
                        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;
@@ -1622,13 +2118,16 @@ static int tiocsctty(struct tty_struct *tty, int arg)
 
 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)
@@ -1646,6 +2145,8 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t
                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)
@@ -1678,11 +2179,11 @@ static int tiocsetd(struct tty_struct *tty, int __user *p)
 
 static int send_break(struct tty_struct *tty, int duration)
 {
-       set_current_state(TASK_INTERRUPTIBLE);
-
        tty->driver->break_ctl(tty, -1);
-       if (!signal_pending(current))
+       if (!signal_pending(current)) {
+               set_current_state(TASK_INTERRUPTIBLE);
                schedule_timeout(duration);
+       }
        tty->driver->break_ctl(tty, 0);
        if (signal_pending(current))
                return -EINTR;
@@ -1747,6 +2248,7 @@ int tty_ioctl(struct inode * inode, struct file * file,
        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"))
@@ -1836,6 +2338,7 @@ int tty_ioctl(struct inode * inode, struct file * file,
                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);
@@ -1874,16 +2377,19 @@ int tty_ioctl(struct inode * inode, struct file * file,
                        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;
 }
 
 
@@ -1913,21 +2419,26 @@ static void __do_SAK(void *arg)
 #else
        struct tty_struct *tty = arg;
        struct task_struct *p;
-       struct list_head *l;
-       struct pid *pid;
        int session;
        int             i;
        struct file     *filp;
+       struct tty_ldisc *disc;
        
        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) {
+       do_each_task_pid(session, PIDTYPE_SID, p) {
                if (p->signal->tty == tty || session > 0) {
                        printk(KERN_NOTICE "SAK: killed process %d"
                            " (%s): p->signal->session==tty->session\n",
@@ -1954,7 +2465,7 @@ static void __do_SAK(void *arg)
                        spin_unlock(&p->files->file_lock);
                }
                task_unlock(p);
-       }
+       } while_each_task_pid(session, PIDTYPE_SID, p);
        read_unlock(&tasklist_lock);
 #endif
 }
@@ -1977,24 +2488,29 @@ EXPORT_SYMBOL(do_SAK);
 
 /*
  * This routine is called out of the software interrupt to flush data
- * from the flip buffer to the line discipline.
+ * from the flip buffer 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;
+
+       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;
+               goto out;
        }
-
        spin_lock_irqsave(&tty->read_lock, flags);
        if (tty->flip.buf_num) {
                cp = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
@@ -2013,7 +2529,9 @@ static void flush_to_ldisc(void *private_)
        tty->flip.count = 0;
        spin_unlock_irqrestore(&tty->read_lock, flags);
 
-       tty->ldisc.receive_buf(tty, cp, fp, count);
+       disc->receive_buf(tty, cp, fp, count);
+out:
+       tty_ldisc_deref(disc);
 }
 
 /*
@@ -2035,9 +2553,20 @@ static int baud_table[] = {
 
 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;
@@ -2047,12 +2576,20 @@ int tty_termios_baud_rate(struct termios *termios)
                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);
@@ -2071,6 +2608,17 @@ int tty_get_baud_rate(struct tty_struct *tty)
 
 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)
 {
        if (tty->low_latency)
@@ -2088,12 +2636,13 @@ static void initialize_tty_struct(struct tty_struct *tty)
 {
        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);
+       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);
@@ -2109,7 +2658,7 @@ static void initialize_tty_struct(struct tty_struct *tty)
  */
 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;
@@ -2129,6 +2678,7 @@ static struct class_simple *tty_class;
 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) {
@@ -2140,13 +2690,11 @@ void tty_register_device(struct tty_driver *driver, unsigned index,
        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_simple_device_add(tty_class, dev, device, name);
 }
 
 /**
@@ -2364,8 +2912,8 @@ void __init console_init(void)
           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++;
        }
@@ -2416,7 +2964,7 @@ static int __init tty_init(void)
        class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 1), NULL, "console");
 
 #ifdef CONFIG_UNIX98_PTYS
-       cdev_init(&ptmx_cdev, &tty_fops);
+       cdev_init(&ptmx_cdev, &ptmx_fops);
        if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
            register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
                panic("Couldn't register /dev/ptmx driver\n");