drm/radeon: 9800 SE has only one quadpipe
[safe/jmp/linux-2.6] / drivers / char / n_tty.c
index b4f5dcc..bdae832 100644 (file)
@@ -26,7 +26,7 @@
  *
  * 2002/03/18   Implemented n_tty_wakeup to send SIGIO POLL_OUTs to
  *             waiting writing processes-Sapan Bhatia <sapan@corewars.org>.
- *             Also fixed a bug in BLOCKING mode where write_chan returns
+ *             Also fixed a bug in BLOCKING mode where n_tty_write returns
  *             EAGAIN
  */
 
@@ -47,8 +47,9 @@
 #include <linux/bitops.h>
 #include <linux/audit.h>
 #include <linux/file.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
 
-#include <asm/uaccess.h>
 #include <asm/system.h>
 
 /* number of characters left in xmit buffer before select has we have room */
 #define TTY_THRESHOLD_THROTTLE         128 /* now based on remaining room */
 #define TTY_THRESHOLD_UNTHROTTLE       128
 
-static inline unsigned char *alloc_buf(void)
-{
-       gfp_t prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
-
-       if (PAGE_SIZE != N_TTY_BUF_SIZE)
-               return kmalloc(N_TTY_BUF_SIZE, prio);
-       else
-               return (unsigned char *)__get_free_page(prio);
-}
-
-static inline void free_buf(unsigned char *buf)
-{
-       if (PAGE_SIZE != N_TTY_BUF_SIZE)
-               kfree(buf);
-       else
-               free_page((unsigned long) buf);
-}
+/*
+ * Special byte codes used in the echo buffer to represent operations
+ * or special handling of characters.  Bytes in the echo buffer that
+ * are not part of such special blocks are treated as normal character
+ * codes.
+ */
+#define ECHO_OP_START 0xff
+#define ECHO_OP_MOVE_BACK_COL 0x80
+#define ECHO_OP_SET_CANON_COL 0x81
+#define ECHO_OP_ERASE_TAB 0x82
 
 static inline int tty_put_user(struct tty_struct *tty, unsigned char x,
                               unsigned char __user *ptr)
@@ -99,6 +93,7 @@ static inline int tty_put_user(struct tty_struct *tty, unsigned char x,
 
 static void n_tty_set_room(struct tty_struct *tty)
 {
+       /* tty->read_cnt is not read locked ? */
        int     left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
 
        /*
@@ -121,6 +116,16 @@ static void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty)
        }
 }
 
+/**
+ *     put_tty_queue           -       add character to tty
+ *     @c: character
+ *     @tty: tty device
+ *
+ *     Add a character to the tty read_buf queue. This is done under the
+ *     read_lock to serialize character addition and also to protect us
+ *     against parallel reads or flushes
+ */
+
 static void put_tty_queue(unsigned char c, struct tty_struct *tty)
 {
        unsigned long flags;
@@ -137,14 +142,11 @@ static void put_tty_queue(unsigned char c, struct tty_struct *tty)
  *     check_unthrottle        -       allow new receive data
  *     @tty; tty device
  *
- *     Check whether to call the driver.unthrottle function.
- *     We test the TTY_THROTTLED bit first so that it always
- *     indicates the current state. The decision about whether
- *     it is worth allowing more input has been taken by the caller.
+ *     Check whether to call the driver unthrottle functions
+ *
  *     Can sleep, may be called under the atomic_read_lock mutex but
  *     this is not guaranteed.
  */
-
 static void check_unthrottle(struct tty_struct *tty)
 {
        if (tty->count)
@@ -158,7 +160,10 @@ static void check_unthrottle(struct tty_struct *tty)
  *     Reset the read buffer counters, clear the flags,
  *     and make sure the driver is unthrottled. Called
  *     from n_tty_open() and n_tty_flush_buffer().
+ *
+ *     Locking: tty_read_lock for read fields.
  */
+
 static void reset_buffer_flags(struct tty_struct *tty)
 {
        unsigned long flags;
@@ -166,6 +171,11 @@ static void reset_buffer_flags(struct tty_struct *tty)
        spin_lock_irqsave(&tty->read_lock, flags);
        tty->read_head = tty->read_tail = tty->read_cnt = 0;
        spin_unlock_irqrestore(&tty->read_lock, flags);
+
+       mutex_lock(&tty->echo_lock);
+       tty->echo_pos = tty->echo_cnt = tty->echo_overrun = 0;
+       mutex_unlock(&tty->echo_lock);
+
        tty->canon_head = tty->canon_data = tty->erasing = 0;
        memset(&tty->read_flags, 0, sizeof tty->read_flags);
        n_tty_set_room(tty);
@@ -181,7 +191,7 @@ static void reset_buffer_flags(struct tty_struct *tty)
  *     at hangup) or when the N_TTY line discipline internally has to
  *     clean the pending queue (for example some signals).
  *
- *     Locking: ctrl_lock
+ *     Locking: ctrl_lock, read_lock.
  */
 
 static void n_tty_flush_buffer(struct tty_struct *tty)
@@ -207,6 +217,8 @@ static void n_tty_flush_buffer(struct tty_struct *tty)
  *
  *     Report the number of characters buffered to be delivered to user
  *     at this instant in time.
+ *
+ *     Locking: read_lock
  */
 
 static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
@@ -254,118 +266,157 @@ static inline int is_continuation(unsigned char c, struct tty_struct *tty)
 }
 
 /**
- *     opost                   -       output post processor
+ *     do_output_char                  -       output one character
  *     @c: character (or partial unicode symbol)
  *     @tty: terminal device
+ *     @space: space available in tty driver write buffer
+ *
+ *     This is a helper function that handles one output character
+ *     (including special characters like TAB, CR, LF, etc.),
+ *     doing OPOST processing and putting the results in the
+ *     tty driver's write buffer.
  *
- *     Perform OPOST processing.  Returns -1 when the output device is
- *     full and the character must be retried. Note that Linux currently
- *     ignores TABDLY, CRDLY, VTDLY, FFDLY and NLDLY. They simply aren't
- *     relevant in the world today. If you ever need them, add them here.
+ *     Note that Linux currently ignores TABDLY, CRDLY, VTDLY, FFDLY
+ *     and NLDLY.  They simply aren't relevant in the world today.
+ *     If you ever need them, add them here.
  *
- *     Called from both the receive and transmit sides and can be called
- *     re-entrantly. Relies on lock_kernel() for tty->column state.
+ *     Returns the number of bytes of buffer space used or -1 if
+ *     no space left.
+ *
+ *     Locking: should be called under the output_lock to protect
+ *              the column state and space left in the buffer
  */
 
-static int opost(unsigned char c, struct tty_struct *tty)
+static int do_output_char(unsigned char c, struct tty_struct *tty, int space)
 {
-       int     space, spaces;
+       int     spaces;
 
-       space = tty_write_room(tty);
        if (!space)
                return -1;
 
-       lock_kernel();
-       if (O_OPOST(tty)) {
-               switch (c) {
-               case '\n':
-                       if (O_ONLRET(tty))
-                               tty->column = 0;
-                       if (O_ONLCR(tty)) {
-                               if (space < 2) {
-                                       unlock_kernel();
-                                       return -1;
-                               }
-                               tty_put_char(tty, '\r');
-                               tty->column = 0;
-                       }
-                       tty->canon_column = tty->column;
-                       break;
-               case '\r':
-                       if (O_ONOCR(tty) && tty->column == 0) {
-                               unlock_kernel();
-                               return 0;
-                       }
-                       if (O_OCRNL(tty)) {
-                               c = '\n';
-                               if (O_ONLRET(tty))
-                                       tty->canon_column = tty->column = 0;
-                               break;
-                       }
+       switch (c) {
+       case '\n':
+               if (O_ONLRET(tty))
+                       tty->column = 0;
+               if (O_ONLCR(tty)) {
+                       if (space < 2)
+                               return -1;
                        tty->canon_column = tty->column = 0;
+                       tty->ops->write(tty, "\r\n", 2);
+                       return 2;
+               }
+               tty->canon_column = tty->column;
+               break;
+       case '\r':
+               if (O_ONOCR(tty) && tty->column == 0)
+                       return 0;
+               if (O_OCRNL(tty)) {
+                       c = '\n';
+                       if (O_ONLRET(tty))
+                               tty->canon_column = tty->column = 0;
                        break;
-               case '\t':
-                       spaces = 8 - (tty->column & 7);
-                       if (O_TABDLY(tty) == XTABS) {
-                               if (space < spaces) {
-                                       unlock_kernel();
-                                       return -1;
-                               }
-                               tty->column += spaces;
-                               tty->ops->write(tty, "        ", spaces);
-                               unlock_kernel();
-                               return 0;
-                       }
+               }
+               tty->canon_column = tty->column = 0;
+               break;
+       case '\t':
+               spaces = 8 - (tty->column & 7);
+               if (O_TABDLY(tty) == XTABS) {
+                       if (space < spaces)
+                               return -1;
                        tty->column += spaces;
-                       break;
-               case '\b':
-                       if (tty->column > 0)
-                               tty->column--;
-                       break;
-               default:
+                       tty->ops->write(tty, "        ", spaces);
+                       return spaces;
+               }
+               tty->column += spaces;
+               break;
+       case '\b':
+               if (tty->column > 0)
+                       tty->column--;
+               break;
+       default:
+               if (!iscntrl(c)) {
                        if (O_OLCUC(tty))
                                c = toupper(c);
-                       if (!iscntrl(c) && !is_continuation(c, tty))
+                       if (!is_continuation(c, tty))
                                tty->column++;
-                       break;
                }
+               break;
        }
+
        tty_put_char(tty, c);
-       unlock_kernel();
-       return 0;
+       return 1;
+}
+
+/**
+ *     process_output                  -       output post processor
+ *     @c: character (or partial unicode symbol)
+ *     @tty: terminal device
+ *
+ *     Output one character with OPOST processing.
+ *     Returns -1 when the output device is full and the character
+ *     must be retried.
+ *
+ *     Locking: output_lock to protect column state and space left
+ *              (also, this is called from n_tty_write under the
+ *               tty layer write lock)
+ */
+
+static int process_output(unsigned char c, struct tty_struct *tty)
+{
+       int     space, retval;
+
+       mutex_lock(&tty->output_lock);
+
+       space = tty_write_room(tty);
+       retval = do_output_char(c, tty, space);
+
+       mutex_unlock(&tty->output_lock);
+       if (retval < 0)
+               return -1;
+       else
+               return 0;
 }
 
 /**
- *     opost_block             -       block postprocess
+ *     process_output_block            -       block post processor
  *     @tty: terminal device
- *     @inbuf: user buffer
- *     @nr: number of bytes
+ *     @buf: character buffer
+ *     @nr: number of bytes to output
+ *
+ *     Output a block of characters with OPOST processing.
+ *     Returns the number of characters output.
  *
  *     This path is used to speed up block console writes, among other
  *     things when processing blocks of output data. It handles only
  *     the simple cases normally found and helps to generate blocks of
  *     symbols for the console driver and thus improve performance.
  *
- *     Called from write_chan under the tty layer write lock. Relies
- *     on lock_kernel for the tty->column state.
+ *     Locking: output_lock to protect column state and space left
+ *              (also, this is called from n_tty_write under the
+ *               tty layer write lock)
  */
 
-static ssize_t opost_block(struct tty_struct *tty,
-                      const unsigned char *buf, unsigned int nr)
+static ssize_t process_output_block(struct tty_struct *tty,
+                                   const unsigned char *buf, unsigned int nr)
 {
        int     space;
        int     i;
        const unsigned char *cp;
 
+       mutex_lock(&tty->output_lock);
+
        space = tty_write_room(tty);
-       if (!space)
+       if (!space) {
+               mutex_unlock(&tty->output_lock);
                return 0;
+       }
        if (nr > space)
                nr = space;
 
-       lock_kernel();
        for (i = 0, cp = buf; i < nr; i++, cp++) {
-               switch (*cp) {
+               unsigned char c = *cp;
+
+               switch (c) {
                case '\n':
                        if (O_ONLRET(tty))
                                tty->column = 0;
@@ -387,46 +438,399 @@ static ssize_t opost_block(struct tty_struct *tty,
                                tty->column--;
                        break;
                default:
-                       if (O_OLCUC(tty))
-                               goto break_out;
-                       if (!iscntrl(*cp))
-                               tty->column++;
+                       if (!iscntrl(c)) {
+                               if (O_OLCUC(tty))
+                                       goto break_out;
+                               if (!is_continuation(c, tty))
+                                       tty->column++;
+                       }
                        break;
                }
        }
 break_out:
-       if (tty->ops->flush_chars)
-               tty->ops->flush_chars(tty);
        i = tty->ops->write(tty, buf, i);
-       unlock_kernel();
+
+       mutex_unlock(&tty->output_lock);
        return i;
 }
 
+/**
+ *     process_echoes  -       write pending echo characters
+ *     @tty: terminal device
+ *
+ *     Write previously buffered echo (and other ldisc-generated)
+ *     characters to the tty.
+ *
+ *     Characters generated by the ldisc (including echoes) need to
+ *     be buffered because the driver's write buffer can fill during
+ *     heavy program output.  Echoing straight to the driver will
+ *     often fail under these conditions, causing lost characters and
+ *     resulting mismatches of ldisc state information.
+ *
+ *     Since the ldisc state must represent the characters actually sent
+ *     to the driver at the time of the write, operations like certain
+ *     changes in column state are also saved in the buffer and executed
+ *     here.
+ *
+ *     A circular fifo buffer is used so that the most recent characters
+ *     are prioritized.  Also, when control characters are echoed with a
+ *     prefixed "^", the pair is treated atomically and thus not separated.
+ *
+ *     Locking: output_lock to protect column state and space left,
+ *              echo_lock to protect the echo buffer
+ */
+
+static void process_echoes(struct tty_struct *tty)
+{
+       int     space, nr;
+       unsigned char c;
+       unsigned char *cp, *buf_end;
+
+       if (!tty->echo_cnt)
+               return;
+
+       mutex_lock(&tty->output_lock);
+       mutex_lock(&tty->echo_lock);
+
+       space = tty_write_room(tty);
+
+       buf_end = tty->echo_buf + N_TTY_BUF_SIZE;
+       cp = tty->echo_buf + tty->echo_pos;
+       nr = tty->echo_cnt;
+       while (nr > 0) {
+               c = *cp;
+               if (c == ECHO_OP_START) {
+                       unsigned char op;
+                       unsigned char *opp;
+                       int no_space_left = 0;
+
+                       /*
+                        * If the buffer byte is the start of a multi-byte
+                        * operation, get the next byte, which is either the
+                        * op code or a control character value.
+                        */
+                       opp = cp + 1;
+                       if (opp == buf_end)
+                               opp -= N_TTY_BUF_SIZE;
+                       op = *opp;
+
+                       switch (op) {
+                               unsigned int num_chars, num_bs;
+
+                       case ECHO_OP_ERASE_TAB:
+                               if (++opp == buf_end)
+                                       opp -= N_TTY_BUF_SIZE;
+                               num_chars = *opp;
+
+                               /*
+                                * Determine how many columns to go back
+                                * in order to erase the tab.
+                                * This depends on the number of columns
+                                * used by other characters within the tab
+                                * area.  If this (modulo 8) count is from
+                                * the start of input rather than from a
+                                * previous tab, we offset by canon column.
+                                * Otherwise, tab spacing is normal.
+                                */
+                               if (!(num_chars & 0x80))
+                                       num_chars += tty->canon_column;
+                               num_bs = 8 - (num_chars & 7);
+
+                               if (num_bs > space) {
+                                       no_space_left = 1;
+                                       break;
+                               }
+                               space -= num_bs;
+                               while (num_bs--) {
+                                       tty_put_char(tty, '\b');
+                                       if (tty->column > 0)
+                                               tty->column--;
+                               }
+                               cp += 3;
+                               nr -= 3;
+                               break;
+
+                       case ECHO_OP_SET_CANON_COL:
+                               tty->canon_column = tty->column;
+                               cp += 2;
+                               nr -= 2;
+                               break;
+
+                       case ECHO_OP_MOVE_BACK_COL:
+                               if (tty->column > 0)
+                                       tty->column--;
+                               cp += 2;
+                               nr -= 2;
+                               break;
+
+                       case ECHO_OP_START:
+                               /* This is an escaped echo op start code */
+                               if (!space) {
+                                       no_space_left = 1;
+                                       break;
+                               }
+                               tty_put_char(tty, ECHO_OP_START);
+                               tty->column++;
+                               space--;
+                               cp += 2;
+                               nr -= 2;
+                               break;
+
+                       default:
+                               /*
+                                * If the op is not a special byte code,
+                                * it is a ctrl char tagged to be echoed
+                                * as "^X" (where X is the letter
+                                * representing the control char).
+                                * Note that we must ensure there is
+                                * enough space for the whole ctrl pair.
+                                *
+                                */
+                               if (space < 2) {
+                                       no_space_left = 1;
+                                       break;
+                               }
+                               tty_put_char(tty, '^');
+                               tty_put_char(tty, op ^ 0100);
+                               tty->column += 2;
+                               space -= 2;
+                               cp += 2;
+                               nr -= 2;
+                       }
+
+                       if (no_space_left)
+                               break;
+               } else {
+                       if (O_OPOST(tty) &&
+                           !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
+                               int retval = do_output_char(c, tty, space);
+                               if (retval < 0)
+                                       break;
+                               space -= retval;
+                       } else {
+                               if (!space)
+                                       break;
+                               tty_put_char(tty, c);
+                               space -= 1;
+                       }
+                       cp += 1;
+                       nr -= 1;
+               }
+
+               /* When end of circular buffer reached, wrap around */
+               if (cp >= buf_end)
+                       cp -= N_TTY_BUF_SIZE;
+       }
+
+       if (nr == 0) {
+               tty->echo_pos = 0;
+               tty->echo_cnt = 0;
+               tty->echo_overrun = 0;
+       } else {
+               int num_processed = tty->echo_cnt - nr;
+               tty->echo_pos += num_processed;
+               tty->echo_pos &= N_TTY_BUF_SIZE - 1;
+               tty->echo_cnt = nr;
+               if (num_processed > 0)
+                       tty->echo_overrun = 0;
+       }
+
+       mutex_unlock(&tty->echo_lock);
+       mutex_unlock(&tty->output_lock);
+
+       if (tty->ops->flush_chars)
+               tty->ops->flush_chars(tty);
+}
 
 /**
- *     echo_char       -       echo characters
+ *     add_echo_byte   -       add a byte to the echo buffer
+ *     @c: unicode byte to echo
+ *     @tty: terminal device
+ *
+ *     Add a character or operation byte to the echo buffer.
+ *
+ *     Should be called under the echo lock to protect the echo buffer.
+ */
+
+static void add_echo_byte(unsigned char c, struct tty_struct *tty)
+{
+       int     new_byte_pos;
+
+       if (tty->echo_cnt == N_TTY_BUF_SIZE) {
+               /* Circular buffer is already at capacity */
+               new_byte_pos = tty->echo_pos;
+
+               /*
+                * Since the buffer start position needs to be advanced,
+                * be sure to step by a whole operation byte group.
+                */
+               if (tty->echo_buf[tty->echo_pos] == ECHO_OP_START) {
+                       if (tty->echo_buf[(tty->echo_pos + 1) &
+                                         (N_TTY_BUF_SIZE - 1)] ==
+                                               ECHO_OP_ERASE_TAB) {
+                               tty->echo_pos += 3;
+                               tty->echo_cnt -= 2;
+                       } else {
+                               tty->echo_pos += 2;
+                               tty->echo_cnt -= 1;
+                       }
+               } else {
+                       tty->echo_pos++;
+               }
+               tty->echo_pos &= N_TTY_BUF_SIZE - 1;
+
+               tty->echo_overrun = 1;
+       } else {
+               new_byte_pos = tty->echo_pos + tty->echo_cnt;
+               new_byte_pos &= N_TTY_BUF_SIZE - 1;
+               tty->echo_cnt++;
+       }
+
+       tty->echo_buf[new_byte_pos] = c;
+}
+
+/**
+ *     echo_move_back_col      -       add operation to move back a column
+ *     @tty: terminal device
+ *
+ *     Add an operation to the echo buffer to move back one column.
+ *
+ *     Locking: echo_lock to protect the echo buffer
+ */
+
+static void echo_move_back_col(struct tty_struct *tty)
+{
+       mutex_lock(&tty->echo_lock);
+
+       add_echo_byte(ECHO_OP_START, tty);
+       add_echo_byte(ECHO_OP_MOVE_BACK_COL, tty);
+
+       mutex_unlock(&tty->echo_lock);
+}
+
+/**
+ *     echo_set_canon_col      -       add operation to set the canon column
+ *     @tty: terminal device
+ *
+ *     Add an operation to the echo buffer to set the canon column
+ *     to the current column.
+ *
+ *     Locking: echo_lock to protect the echo buffer
+ */
+
+static void echo_set_canon_col(struct tty_struct *tty)
+{
+       mutex_lock(&tty->echo_lock);
+
+       add_echo_byte(ECHO_OP_START, tty);
+       add_echo_byte(ECHO_OP_SET_CANON_COL, tty);
+
+       mutex_unlock(&tty->echo_lock);
+}
+
+/**
+ *     echo_erase_tab  -       add operation to erase a tab
+ *     @num_chars: number of character columns already used
+ *     @after_tab: true if num_chars starts after a previous tab
+ *     @tty: terminal device
+ *
+ *     Add an operation to the echo buffer to erase a tab.
+ *
+ *     Called by the eraser function, which knows how many character
+ *     columns have been used since either a previous tab or the start
+ *     of input.  This information will be used later, along with
+ *     canon column (if applicable), to go back the correct number
+ *     of columns.
+ *
+ *     Locking: echo_lock to protect the echo buffer
+ */
+
+static void echo_erase_tab(unsigned int num_chars, int after_tab,
+                          struct tty_struct *tty)
+{
+       mutex_lock(&tty->echo_lock);
+
+       add_echo_byte(ECHO_OP_START, tty);
+       add_echo_byte(ECHO_OP_ERASE_TAB, tty);
+
+       /* We only need to know this modulo 8 (tab spacing) */
+       num_chars &= 7;
+
+       /* Set the high bit as a flag if num_chars is after a previous tab */
+       if (after_tab)
+               num_chars |= 0x80;
+
+       add_echo_byte(num_chars, tty);
+
+       mutex_unlock(&tty->echo_lock);
+}
+
+/**
+ *     echo_char_raw   -       echo a character raw
  *     @c: unicode byte to echo
  *     @tty: terminal device
  *
  *     Echo user input back onto the screen. This must be called only when
  *     L_ECHO(tty) is true. Called from the driver receive_buf path.
+ *
+ *     This variant does not treat control characters specially.
+ *
+ *     Locking: echo_lock to protect the echo buffer
+ */
+
+static void echo_char_raw(unsigned char c, struct tty_struct *tty)
+{
+       mutex_lock(&tty->echo_lock);
+
+       if (c == ECHO_OP_START) {
+               add_echo_byte(ECHO_OP_START, tty);
+               add_echo_byte(ECHO_OP_START, tty);
+       } else {
+               add_echo_byte(c, tty);
+       }
+
+       mutex_unlock(&tty->echo_lock);
+}
+
+/**
+ *     echo_char       -       echo a character
+ *     @c: unicode byte to echo
+ *     @tty: terminal device
+ *
+ *     Echo user input back onto the screen. This must be called only when
+ *     L_ECHO(tty) is true. Called from the driver receive_buf path.
+ *
+ *     This variant tags control characters to be echoed as "^X"
+ *     (where X is the letter representing the control char).
+ *
+ *     Locking: echo_lock to protect the echo buffer
  */
 
 static void echo_char(unsigned char c, struct tty_struct *tty)
 {
-       if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') {
-               tty_put_char(tty, '^');
-               tty_put_char(tty, c ^ 0100);
-               tty->column += 2;
-       } else
-               opost(c, tty);
+       mutex_lock(&tty->echo_lock);
+
+       if (c == ECHO_OP_START) {
+               add_echo_byte(ECHO_OP_START, tty);
+               add_echo_byte(ECHO_OP_START, tty);
+       } else {
+               if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t')
+                       add_echo_byte(ECHO_OP_START, tty);
+               add_echo_byte(c, tty);
+       }
+
+       mutex_unlock(&tty->echo_lock);
 }
 
+/**
+ *     finish_erasing          -       complete erase
+ *     @tty: tty doing the erase
+ */
+
 static inline void finish_erasing(struct tty_struct *tty)
 {
        if (tty->erasing) {
-               tty_put_char(tty, '/');
-               tty->column++;
+               echo_char_raw('/', tty);
                tty->erasing = 0;
        }
 }
@@ -439,6 +843,8 @@ static inline void finish_erasing(struct tty_struct *tty)
  *     Perform erase and necessary output when an erase character is
  *     present in the stream from the driver layer. Handles the complexities
  *     of UTF-8 multibyte symbols.
+ *
+ *     Locking: read_lock for tty buffers
  */
 
 static void eraser(unsigned char c, struct tty_struct *tty)
@@ -447,8 +853,9 @@ static void eraser(unsigned char c, struct tty_struct *tty)
        int head, seen_alnums, cnt;
        unsigned long flags;
 
+       /* FIXME: locking needed ? */
        if (tty->read_head == tty->canon_head) {
-               /* opost('\a', tty); */         /* what do you think? */
+               /* process_output('\a', tty); */ /* what do you think? */
                return;
        }
        if (c == ERASE_CHAR(tty))
@@ -474,13 +881,14 @@ static void eraser(unsigned char c, struct tty_struct *tty)
                        echo_char(KILL_CHAR(tty), tty);
                        /* Add a newline if ECHOK is on and ECHOKE is off. */
                        if (L_ECHOK(tty))
-                               opost('\n', tty);
+                               echo_char_raw('\n', tty);
                        return;
                }
                kill_type = KILL;
        }
 
        seen_alnums = 0;
+       /* FIXME: Locking ?? */
        while (tty->read_head != tty->canon_head) {
                head = tty->read_head;
 
@@ -509,67 +917,61 @@ static void eraser(unsigned char c, struct tty_struct *tty)
                if (L_ECHO(tty)) {
                        if (L_ECHOPRT(tty)) {
                                if (!tty->erasing) {
-                                       tty_put_char(tty, '\\');
-                                       tty->column++;
+                                       echo_char_raw('\\', tty);
                                        tty->erasing = 1;
                                }
                                /* if cnt > 1, output a multi-byte character */
                                echo_char(c, tty);
                                while (--cnt > 0) {
                                        head = (head+1) & (N_TTY_BUF_SIZE-1);
-                                       tty_put_char(tty, tty->read_buf[head]);
+                                       echo_char_raw(tty->read_buf[head], tty);
+                                       echo_move_back_col(tty);
                                }
                        } else if (kill_type == ERASE && !L_ECHOE(tty)) {
                                echo_char(ERASE_CHAR(tty), tty);
                        } else if (c == '\t') {
-                               unsigned int col = tty->canon_column;
-                               unsigned long tail = tty->canon_head;
-
-                               /* Find the column of the last char. */
-                               while (tail != tty->read_head) {
+                               unsigned int num_chars = 0;
+                               int after_tab = 0;
+                               unsigned long tail = tty->read_head;
+
+                               /*
+                                * Count the columns used for characters
+                                * since the start of input or after a
+                                * previous tab.
+                                * This info is used to go back the correct
+                                * number of columns.
+                                */
+                               while (tail != tty->canon_head) {
+                                       tail = (tail-1) & (N_TTY_BUF_SIZE-1);
                                        c = tty->read_buf[tail];
-                                       if (c == '\t')
-                                               col = (col | 7) + 1;
-                                       else if (iscntrl(c)) {
+                                       if (c == '\t') {
+                                               after_tab = 1;
+                                               break;
+                                       } else if (iscntrl(c)) {
                                                if (L_ECHOCTL(tty))
-                                                       col += 2;
-                                       } else if (!is_continuation(c, tty))
-                                               col++;
-                                       tail = (tail+1) & (N_TTY_BUF_SIZE-1);
-                               }
-
-                               /* should never happen */
-                               if (tty->column > 0x80000000)
-                                       tty->column = 0;
-
-                               /* Now backup to that column. */
-                               while (tty->column > col) {
-                                       /* Can't use opost here. */
-                                       tty_put_char(tty, '\b');
-                                       if (tty->column > 0)
-                                               tty->column--;
+                                                       num_chars += 2;
+                                       } else if (!is_continuation(c, tty)) {
+                                               num_chars++;
+                                       }
                                }
+                               echo_erase_tab(num_chars, after_tab, tty);
                        } else {
                                if (iscntrl(c) && L_ECHOCTL(tty)) {
-                                       tty_put_char(tty, '\b');
-                                       tty_put_char(tty, ' ');
-                                       tty_put_char(tty, '\b');
-                                       if (tty->column > 0)
-                                               tty->column--;
+                                       echo_char_raw('\b', tty);
+                                       echo_char_raw(' ', tty);
+                                       echo_char_raw('\b', tty);
                                }
                                if (!iscntrl(c) || L_ECHOCTL(tty)) {
-                                       tty_put_char(tty, '\b');
-                                       tty_put_char(tty, ' ');
-                                       tty_put_char(tty, '\b');
-                                       if (tty->column > 0)
-                                               tty->column--;
+                                       echo_char_raw('\b', tty);
+                                       echo_char_raw(' ', tty);
+                                       echo_char_raw('\b', tty);
                                }
                        }
                }
                if (kill_type == ERASE)
                        break;
        }
-       if (tty->read_head == tty->canon_head)
+       if (tty->read_head == tty->canon_head && L_ECHO(tty))
                finish_erasing(tty);
 }
 
@@ -583,6 +985,8 @@ static void eraser(unsigned char c, struct tty_struct *tty)
  *     may caus terminal flushing to take place according to the termios
  *     settings and character used. Called from the driver receive_buf
  *     path so serialized.
+ *
+ *     Locking: ctrl_lock, read_lock (both via flush buffer)
  */
 
 static inline void isig(int sig, struct tty_struct *tty, int flush)
@@ -686,6 +1090,7 @@ static inline void n_tty_receive_parity_error(struct tty_struct *tty,
 static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
 {
        unsigned long flags;
+       int parmrk;
 
        if (tty->raw) {
                put_tty_queue(c, tty);
@@ -695,18 +1100,21 @@ static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
        if (I_ISTRIP(tty))
                c &= 0x7f;
        if (I_IUCLC(tty) && L_IEXTEN(tty))
-               c=tolower(c);
+               c = tolower(c);
 
        if (tty->stopped && !tty->flow_stopped && I_IXON(tty) &&
-           ((I_IXANY(tty) && c != START_CHAR(tty) && c != STOP_CHAR(tty)) ||
-            c == INTR_CHAR(tty) || c == QUIT_CHAR(tty) || c == SUSP_CHAR(tty)))
+           I_IXANY(tty) && c != START_CHAR(tty) && c != STOP_CHAR(tty) &&
+           c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && c != SUSP_CHAR(tty)) {
                start_tty(tty);
+               process_echoes(tty);
+       }
 
        if (tty->closing) {
                if (I_IXON(tty)) {
-                       if (c == START_CHAR(tty))
+                       if (c == START_CHAR(tty)) {
                                start_tty(tty);
-                       else if (c == STOP_CHAR(tty))
+                               process_echoes(tty);
+                       } else if (c == STOP_CHAR(tty))
                                stop_tty(tty);
                }
                return;
@@ -719,19 +1127,23 @@ static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
         * up.
         */
        if (!test_bit(c, tty->process_char_map) || tty->lnext) {
-               finish_erasing(tty);
                tty->lnext = 0;
+               parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0;
+               if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk - 1)) {
+                       /* beep if no space */
+                       if (L_ECHO(tty))
+                               process_output('\a', tty);
+                       return;
+               }
                if (L_ECHO(tty)) {
-                       if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
-                               tty_put_char(tty, '\a'); /* beep if no space */
-                               return;
-                       }
+                       finish_erasing(tty);
                        /* Record the column of first canon char. */
                        if (tty->canon_head == tty->read_head)
-                               tty->canon_column = tty->column;
+                               echo_set_canon_col(tty);
                        echo_char(c, tty);
+                       process_echoes(tty);
                }
-               if (I_PARMRK(tty) && c == (unsigned char) '\377')
+               if (parmrk)
                        put_tty_queue(c, tty);
                put_tty_queue(c, tty);
                return;
@@ -740,6 +1152,7 @@ static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
        if (I_IXON(tty)) {
                if (c == START_CHAR(tty)) {
                        start_tty(tty);
+                       process_echoes(tty);
                        return;
                }
                if (c == STOP_CHAR(tty)) {
@@ -760,7 +1173,6 @@ static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
                if (c == SUSP_CHAR(tty)) {
 send_signal:
                        /*
-                        * Echo character, and then send the signal.
                         * Note that we do not use isig() here because we want
                         * the order to be:
                         * 1) flush, 2) echo, 3) signal
@@ -769,8 +1181,12 @@ send_signal:
                                n_tty_flush_buffer(tty);
                                tty_driver_flush_buffer(tty);
                        }
-                       if (L_ECHO(tty))
+                       if (I_IXON(tty))
+                               start_tty(tty);
+                       if (L_ECHO(tty)) {
                                echo_char(c, tty);
+                               process_echoes(tty);
+                       }
                        if (tty->pgrp)
                                kill_pgrp(tty->pgrp, signal, 1);
                        return;
@@ -789,6 +1205,7 @@ send_signal:
                if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) ||
                    (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) {
                        eraser(c, tty);
+                       process_echoes(tty);
                        return;
                }
                if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) {
@@ -796,8 +1213,9 @@ send_signal:
                        if (L_ECHO(tty)) {
                                finish_erasing(tty);
                                if (L_ECHOCTL(tty)) {
-                                       tty_put_char(tty, '^');
-                                       tty_put_char(tty, '\b');
+                                       echo_char_raw('^', tty);
+                                       echo_char_raw('\b', tty);
+                                       process_echoes(tty);
                                }
                        }
                        return;
@@ -808,22 +1226,29 @@ send_signal:
 
                        finish_erasing(tty);
                        echo_char(c, tty);
-                       opost('\n', tty);
+                       echo_char_raw('\n', tty);
                        while (tail != tty->read_head) {
                                echo_char(tty->read_buf[tail], tty);
                                tail = (tail+1) & (N_TTY_BUF_SIZE-1);
                        }
+                       process_echoes(tty);
                        return;
                }
                if (c == '\n') {
+                       if (tty->read_cnt >= N_TTY_BUF_SIZE) {
+                               if (L_ECHO(tty))
+                                       process_output('\a', tty);
+                               return;
+                       }
                        if (L_ECHO(tty) || L_ECHONL(tty)) {
-                               if (tty->read_cnt >= N_TTY_BUF_SIZE-1)
-                                       tty_put_char(tty, '\a');
-                               opost('\n', tty);
+                               echo_char_raw('\n', tty);
+                               process_echoes(tty);
                        }
                        goto handle_newline;
                }
                if (c == EOF_CHAR(tty)) {
+                       if (tty->read_cnt >= N_TTY_BUF_SIZE)
+                               return;
                        if (tty->canon_head != tty->read_head)
                                set_bit(TTY_PUSH, &tty->flags);
                        c = __DISABLED_CHAR;
@@ -831,22 +1256,28 @@ send_signal:
                }
                if ((c == EOL_CHAR(tty)) ||
                    (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) {
+                       parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty))
+                                ? 1 : 0;
+                       if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk)) {
+                               if (L_ECHO(tty))
+                                       process_output('\a', tty);
+                               return;
+                       }
                        /*
                         * XXX are EOL_CHAR and EOL2_CHAR echoed?!?
                         */
                        if (L_ECHO(tty)) {
-                               if (tty->read_cnt >= N_TTY_BUF_SIZE-1)
-                                       tty_put_char(tty, '\a');
                                /* Record the column of first canon char. */
                                if (tty->canon_head == tty->read_head)
-                                       tty->canon_column = tty->column;
+                                       echo_set_canon_col(tty);
                                echo_char(c, tty);
+                               process_echoes(tty);
                        }
                        /*
                         * XXX does PARMRK doubling happen for
                         * EOL_CHAR and EOL2_CHAR?
                         */
-                       if (I_PARMRK(tty) && c == (unsigned char) '\377')
+                       if (parmrk)
                                put_tty_queue(c, tty);
 
 handle_newline:
@@ -863,23 +1294,27 @@ handle_newline:
                }
        }
 
-       finish_erasing(tty);
+       parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0;
+       if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk - 1)) {
+               /* beep if no space */
+               if (L_ECHO(tty))
+                       process_output('\a', tty);
+               return;
+       }
        if (L_ECHO(tty)) {
-               if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
-                       tty_put_char(tty, '\a'); /* beep if no space */
-                       return;
-               }
+               finish_erasing(tty);
                if (c == '\n')
-                       opost('\n', tty);
+                       echo_char_raw('\n', tty);
                else {
                        /* Record the column of first canon char. */
                        if (tty->canon_head == tty->read_head)
-                               tty->canon_column = tty->column;
+                               echo_set_canon_col(tty);
                        echo_char(c, tty);
                }
+               process_echoes(tty);
        }
 
-       if (I_PARMRK(tty) && c == (unsigned char) '\377')
+       if (parmrk)
                put_tty_queue(c, tty);
 
        put_tty_queue(c, tty);
@@ -897,10 +1332,8 @@ handle_newline:
 
 static void n_tty_write_wakeup(struct tty_struct *tty)
 {
-       if (tty->fasync) {
-               set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+       if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags))
                kill_fasync(&tty->fasync, SIGIO, POLL_OUT);
-       }
 }
 
 /**
@@ -1007,6 +1440,8 @@ int is_ignored(int sig)
  *     and is protected from re-entry by the tty layer. The user is
  *     guaranteed that this function will not be re-entered or in progress
  *     when the ldisc is closed.
+ *
+ *     Locking: Caller holds tty->termios_mutex
  */
 
 static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
@@ -1103,9 +1538,13 @@ static void n_tty_close(struct tty_struct *tty)
 {
        n_tty_flush_buffer(tty);
        if (tty->read_buf) {
-               free_buf(tty->read_buf);
+               kfree(tty->read_buf);
                tty->read_buf = NULL;
        }
+       if (tty->echo_buf) {
+               kfree(tty->echo_buf);
+               tty->echo_buf = NULL;
+       }
 }
 
 /**
@@ -1123,13 +1562,18 @@ static int n_tty_open(struct tty_struct *tty)
        if (!tty)
                return -EINVAL;
 
-       /* This one is ugly. Currently a malloc failure here can panic */
+       /* These are ugly. Currently a malloc failure here can panic */
        if (!tty->read_buf) {
-               tty->read_buf = alloc_buf();
+               tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
                if (!tty->read_buf)
                        return -ENOMEM;
        }
-       memset(tty->read_buf, 0, N_TTY_BUF_SIZE);
+       if (!tty->echo_buf) {
+               tty->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL);
+
+               if (!tty->echo_buf)
+                       return -ENOMEM;
+       }
        reset_buffer_flags(tty);
        tty->column = 0;
        n_tty_set_termios(tty, NULL);
@@ -1140,6 +1584,7 @@ static int n_tty_open(struct tty_struct *tty)
 
 static inline int input_available_p(struct tty_struct *tty, int amt)
 {
+       tty_flush_to_ldisc(tty);
        if (tty->icanon) {
                if (tty->canon_data)
                        return 1;
@@ -1155,7 +1600,7 @@ static inline int input_available_p(struct tty_struct *tty, int amt)
  *     @b: user data
  *     @nr: size of data
  *
- *     Helper function to speed up read_chan.  It is only called when
+ *     Helper function to speed up n_tty_read.  It is only called when
  *     ICANON is off; it copies characters straight from the tty queue to
  *     user space directly.  It can be profitably called twice; once to
  *     drain the space from the tail pointer to the (physical) end of the
@@ -1222,7 +1667,7 @@ static int job_control(struct tty_struct *tty, struct file *file)
        if (file->f_op->write != redirected_tty_write &&
            current->signal->tty == tty) {
                if (!tty->pgrp)
-                       printk(KERN_ERR "read_chan: no tty->pgrp!\n");
+                       printk(KERN_ERR "n_tty_read: no tty->pgrp!\n");
                else if (task_pgrp(current) != tty->pgrp) {
                        if (is_ignored(SIGTTIN) ||
                            is_current_pgrp_orphaned())
@@ -1237,7 +1682,7 @@ static int job_control(struct tty_struct *tty, struct file *file)
 
 
 /**
- *     read_chan               -       read function for tty
+ *     n_tty_read              -       read function for tty
  *     @tty: tty device
  *     @file: file object
  *     @buf: userspace buffer pointer
@@ -1251,7 +1696,7 @@ static int job_control(struct tty_struct *tty, struct file *file)
  *     This code must be sure never to sleep through a hangup.
  */
 
-static ssize_t read_chan(struct tty_struct *tty, struct file *file,
+static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
                         unsigned char __user *buf, size_t nr)
 {
        unsigned char __user *b = buf;
@@ -1266,10 +1711,7 @@ static ssize_t read_chan(struct tty_struct *tty, struct file *file,
 
 do_it_again:
 
-       if (!tty->read_buf) {
-               printk(KERN_ERR "n_tty_read_chan: read_buf == NULL?!?\n");
-               return -EIO;
-       }
+       BUG_ON(!tty->read_buf);
 
        c = job_control(tty, file);
        if (c < 0)
@@ -1456,22 +1898,29 @@ do_it_again:
 }
 
 /**
- *     write_chan              -       write function for tty
+ *     n_tty_write             -       write function for tty
  *     @tty: tty device
  *     @file: file object
  *     @buf: userspace buffer pointer
  *     @nr: size of I/O
  *
- *     Write function of the terminal device. This is serialized with
+ *     Write function of the terminal device.  This is serialized with
  *     respect to other write callers but not to termios changes, reads
- *     and other such events. We must be careful with N_TTY as the receive
- *     code will echo characters, thus calling driver write methods.
+ *     and other such events.  Since the receive code will echo characters,
+ *     thus calling driver write methods, the output_lock is used in
+ *     the output processing functions called here as well as in the
+ *     echo processing function to protect the column state and space
+ *     left in the buffer.
  *
  *     This code must be sure never to sleep through a hangup.
+ *
+ *     Locking: output_lock to protect column state and space left
+ *              (note that the process_output*() functions take this
+ *               lock themselves)
  */
 
-static ssize_t write_chan(struct tty_struct *tty, struct file *file,
-                         const unsigned char *buf, size_t nr)
+static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
+                          const unsigned char *buf, size_t nr)
 {
        const unsigned char *b = buf;
        DECLARE_WAITQUEUE(wait, current);
@@ -1485,6 +1934,9 @@ static ssize_t write_chan(struct tty_struct *tty, struct file *file,
                        return retval;
        }
 
+       /* Write out any echoed characters that are still pending */
+       process_echoes(tty);
+
        add_wait_queue(&tty->write_wait, &wait);
        while (1) {
                set_current_state(TASK_INTERRUPTIBLE);
@@ -1498,7 +1950,7 @@ static ssize_t write_chan(struct tty_struct *tty, struct file *file,
                }
                if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
                        while (nr > 0) {
-                               ssize_t num = opost_block(tty, b, nr);
+                               ssize_t num = process_output_block(tty, b, nr);
                                if (num < 0) {
                                        if (num == -EAGAIN)
                                                break;
@@ -1510,7 +1962,7 @@ static ssize_t write_chan(struct tty_struct *tty, struct file *file,
                                if (nr == 0)
                                        break;
                                c = *b;
-                               if (opost(c, tty) < 0)
+                               if (process_output(c, tty) < 0)
                                        break;
                                b++; nr--;
                        }
@@ -1540,11 +1992,13 @@ static ssize_t write_chan(struct tty_struct *tty, struct file *file,
 break_out:
        __set_current_state(TASK_RUNNING);
        remove_wait_queue(&tty->write_wait, &wait);
+       if (b - buf != nr && tty->fasync)
+               set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
        return (b - buf) ? b - buf : retval;
 }
 
 /**
- *     normal_poll             -       poll method for N_TTY
+ *     n_tty_poll              -       poll method for N_TTY
  *     @tty: terminal device
  *     @file: file accessing it
  *     @wait: poll table
@@ -1557,7 +2011,7 @@ break_out:
  *     Called without the kernel lock held - fine
  */
 
-static unsigned int normal_poll(struct tty_struct *tty, struct file *file,
+static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,
                                                        poll_table *wait)
 {
        unsigned int mask = 0;
@@ -1589,7 +2043,7 @@ static unsigned long inq_canon(struct tty_struct *tty)
 {
        int nr, head, tail;
 
-       if (!tty->canon_data || !tty->read_buf)
+       if (!tty->canon_data)
                return 0;
        head = tty->canon_head;
        tail = tty->read_tail;
@@ -1613,6 +2067,7 @@ static int n_tty_ioctl(struct tty_struct *tty, struct file *file,
        case TIOCOUTQ:
                return put_user(tty_chars_in_buffer(tty), (int __user *) arg);
        case TIOCINQ:
+               /* FIXME: Locking */
                retval = tty->read_cnt;
                if (L_ICANON(tty))
                        retval = inq_canon(tty);
@@ -1629,12 +2084,27 @@ struct tty_ldisc_ops tty_ldisc_N_TTY = {
        .close           = n_tty_close,
        .flush_buffer    = n_tty_flush_buffer,
        .chars_in_buffer = n_tty_chars_in_buffer,
-       .read            = read_chan,
-       .write           = write_chan,
+       .read            = n_tty_read,
+       .write           = n_tty_write,
        .ioctl           = n_tty_ioctl,
        .set_termios     = n_tty_set_termios,
-       .poll            = normal_poll,
+       .poll            = n_tty_poll,
        .receive_buf     = n_tty_receive_buf,
        .write_wakeup    = n_tty_write_wakeup
 };
 
+/**
+ *     n_tty_inherit_ops       -       inherit N_TTY methods
+ *     @ops: struct tty_ldisc_ops where to save N_TTY methods
+ *
+ *     Used by a generic struct tty_ldisc_ops to easily inherit N_TTY
+ *     methods.
+ */
+
+void n_tty_inherit_ops(struct tty_ldisc_ops *ops)
+{
+       *ops = tty_ldisc_N_TTY;
+       ops->owner = NULL;
+       ops->refcount = ops->flags = 0;
+}
+EXPORT_SYMBOL_GPL(n_tty_inherit_ops);