--- zzzz-none-000/linux-3.10.107/drivers/tty/n_tty.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/tty/n_tty.c 2021-02-04 17:41:59.000000000 +0000 @@ -50,6 +50,7 @@ #include #include #include +#include /* number of characters left in xmit buffer before select has we have room */ @@ -74,36 +75,84 @@ #define ECHO_OP_SET_CANON_COL 0x81 #define ECHO_OP_ERASE_TAB 0x82 +#define ECHO_COMMIT_WATERMARK 256 +#define ECHO_BLOCK 256 +#define ECHO_DISCARD_WATERMARK N_TTY_BUF_SIZE - (ECHO_BLOCK + 32) + + +#undef N_TTY_TRACE +#ifdef N_TTY_TRACE +# define n_tty_trace(f, args...) trace_printk(f, ##args) +#else +# define n_tty_trace(f, args...) +#endif + struct n_tty_data { - unsigned int column; + /* producer-published */ + size_t read_head; + size_t commit_head; + size_t canon_head; + size_t echo_head; + size_t echo_commit; + size_t echo_mark; + DECLARE_BITMAP(char_map, 256); + + /* private to n_tty_receive_overrun (single-threaded) */ unsigned long overrun_time; int num_overrun; + /* non-atomic */ + bool no_room; + + /* must hold exclusive termios_rwsem to reset these */ unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1; - unsigned char echo_overrun:1; + unsigned char push:1; - DECLARE_BITMAP(process_char_map, 256); + /* shared by producer and consumer */ + char read_buf[N_TTY_BUF_SIZE]; DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE); + unsigned char echo_buf[N_TTY_BUF_SIZE]; + + int minimum_to_wake; - char *read_buf; - int read_head; - int read_tail; - int read_cnt; - - unsigned char *echo_buf; - unsigned int echo_pos; - unsigned int echo_cnt; + /* consumer-published */ + size_t read_tail; + size_t line_start; - int canon_data; - unsigned long canon_head; + /* protected by output lock */ + unsigned int column; unsigned int canon_column; + size_t echo_tail; struct mutex atomic_read_lock; struct mutex output_lock; - struct mutex echo_lock; - raw_spinlock_t read_lock; }; +static inline size_t read_cnt(struct n_tty_data *ldata) +{ + return ldata->read_head - ldata->read_tail; +} + +static inline unsigned char read_buf(struct n_tty_data *ldata, size_t i) +{ + return ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + +static inline unsigned char *read_buf_addr(struct n_tty_data *ldata, size_t i) +{ + return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + +static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i) +{ + return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + +static inline unsigned char *echo_buf_addr(struct n_tty_data *ldata, size_t i) +{ + return &ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + static inline int tty_put_user(struct tty_struct *tty, unsigned char x, unsigned char __user *ptr) { @@ -113,44 +162,37 @@ return put_user(x, ptr); } +static inline int tty_copy_to_user(struct tty_struct *tty, + void __user *to, + const void *from, + unsigned long n) +{ + struct n_tty_data *ldata = tty->disc_data; + + tty_audit_add_data(tty, from, n, ldata->icanon); + return copy_to_user(to, from, n); +} + /** - * n_tty_set__room - receive space + * n_tty_kick_worker - start input worker (if required) * @tty: terminal * - * Called by the driver to find out how much data it is - * permitted to feed to the line discipline without any being lost - * and thus to manage flow control. Not serialized. Answers for the - * "instant". + * Re-schedules the flip buffer work if it may have stopped + * + * Caller holds exclusive termios_rwsem + * or + * n_tty_read()/consumer path: + * holds non-exclusive termios_rwsem */ -static void n_tty_set_room(struct tty_struct *tty) +static void n_tty_kick_worker(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - int left; - int old_left; - - /* ldata->read_cnt is not read locked ? */ - if (I_PARMRK(tty)) { - /* Multiply read_cnt by 3, since each byte might take up to - * three times as many spaces when PARMRK is set (depending on - * its flags, e.g. parity error). */ - left = N_TTY_BUF_SIZE - ldata->read_cnt * 3 - 1; - } else - left = N_TTY_BUF_SIZE - ldata->read_cnt - 1; - /* - * If we are doing input canonicalization, and there are no - * pending newlines, let characters through without limit, so - * that erase characters will be handled. Other excess - * characters will be beeped. - */ - if (left <= 0) - left = ldata->icanon && !ldata->canon_data; - old_left = tty->receive_room; - tty->receive_room = left; + /* Did the input worker stop? Restart it */ + if (unlikely(ldata->no_room)) { + ldata->no_room = 0; - /* Did this open up the receive buffer? We may need to flip */ - if (left && !old_left) { WARN_RATELIMIT(tty->port->itty == NULL, "scheduling with invalid itty\n"); /* see if ldisc has been killed - if so, this means that @@ -159,17 +201,94 @@ */ WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags), "scheduling buffer work for halted ldisc\n"); - schedule_work(&tty->port->buf.work); + tty_buffer_restart_work(tty->port); } } -static void put_tty_queue_nolock(unsigned char c, struct n_tty_data *ldata) +static ssize_t chars_in_buffer(struct tty_struct *tty) +{ + struct n_tty_data *ldata = tty->disc_data; + ssize_t n = 0; + + if (!ldata->icanon) + n = ldata->commit_head - ldata->read_tail; + else + n = ldata->canon_head - ldata->read_tail; + return n; +} + +/** + * n_tty_write_wakeup - asynchronous I/O notifier + * @tty: tty device + * + * Required for the ptys, serial driver etc. since processes + * that attach themselves to the master and rely on ASYNC + * IO must be woken up + */ + +static void n_tty_write_wakeup(struct tty_struct *tty) { - if (ldata->read_cnt < N_TTY_BUF_SIZE) { - ldata->read_buf[ldata->read_head] = c; - ldata->read_head = (ldata->read_head + 1) & (N_TTY_BUF_SIZE-1); - ldata->read_cnt++; + if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) + kill_fasync(&tty->fasync, SIGIO, POLL_OUT); +} + +static void n_tty_check_throttle(struct tty_struct *tty) +{ + struct n_tty_data *ldata = tty->disc_data; + + /* + * Check the remaining room for the input canonicalization + * mode. We don't want to throttle the driver if we're in + * canonical mode and don't have a newline yet! + */ + if (ldata->icanon && ldata->canon_head == ldata->read_tail) + return; + + while (1) { + int throttled; + tty_set_flow_change(tty, TTY_THROTTLE_SAFE); + if (N_TTY_BUF_SIZE - read_cnt(ldata) >= TTY_THRESHOLD_THROTTLE) + break; + throttled = tty_throttle_safe(tty); + if (!throttled) + break; } + __tty_set_flow_change(tty, 0); +} + +static void n_tty_check_unthrottle(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY) { + if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) + return; + if (!tty->count) + return; + n_tty_kick_worker(tty); + tty_wakeup(tty->link); + return; + } + + /* If there is enough space in the read buffer now, let the + * low-level driver know. We use chars_in_buffer() to + * check the buffer, as it now knows about canonical mode. + * Otherwise, if the driver is throttled and the line is + * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, + * we won't get any more characters. + */ + + while (1) { + int unthrottled; + tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); + if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) + break; + if (!tty->count) + break; + n_tty_kick_worker(tty); + unthrottled = tty_unthrottle_safe(tty); + if (!unthrottled) + break; + } + __tty_set_flow_change(tty, 0); } /** @@ -177,21 +296,16 @@ * @c: character * @ldata: n_tty data * - * 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 + * Add a character to the tty read_buf queue. + * + * n_tty_receive_buf()/producer path: + * caller holds non-exclusive termios_rwsem */ -static void put_tty_queue(unsigned char c, struct n_tty_data *ldata) +static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata) { - unsigned long flags; - /* - * The problem of stomping on the buffers ends here. - * Why didn't anyone see this one coming? --AJK - */ - raw_spin_lock_irqsave(&ldata->read_lock, flags); - put_tty_queue_nolock(c, ldata); - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); + *read_buf_addr(ldata, ldata->read_head) = c; + ldata->read_head++; } /** @@ -201,35 +315,33 @@ * Reset the read buffer counters and clear the flags. * Called from n_tty_open() and n_tty_flush_buffer(). * - * Locking: tty_read_lock for read fields. + * Locking: caller holds exclusive termios_rwsem + * (or locking is not required) */ static void reset_buffer_flags(struct n_tty_data *ldata) { - unsigned long flags; + ldata->read_head = ldata->canon_head = ldata->read_tail = 0; + ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; + ldata->commit_head = 0; + ldata->echo_mark = 0; + ldata->line_start = 0; - raw_spin_lock_irqsave(&ldata->read_lock, flags); - ldata->read_head = ldata->read_tail = ldata->read_cnt = 0; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); - - mutex_lock(&ldata->echo_lock); - ldata->echo_pos = ldata->echo_cnt = ldata->echo_overrun = 0; - mutex_unlock(&ldata->echo_lock); - - ldata->canon_head = ldata->canon_data = ldata->erasing = 0; + ldata->erasing = 0; bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); + ldata->push = 0; } static void n_tty_packet_mode_flush(struct tty_struct *tty) { unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); if (tty->link->packet) { + spin_lock_irqsave(&tty->ctrl_lock, flags); tty->ctrl_status |= TIOCPKT_FLUSHREAD; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); wake_up_interruptible(&tty->link->read_wait); } - spin_unlock_irqrestore(&tty->ctrl_lock, flags); } /** @@ -240,16 +352,21 @@ * buffer flushed (eg at hangup) or when the N_TTY line discipline * internally has to clean the pending queue (for example some signals). * - * Locking: ctrl_lock, read_lock. + * Holds termios_rwsem to exclude producer/consumer while + * buffer indices are reset. + * + * Locking: ctrl_lock, exclusive termios_rwsem */ static void n_tty_flush_buffer(struct tty_struct *tty) { + down_write(&tty->termios_rwsem); reset_buffer_flags(tty->disc_data); - n_tty_set_room(tty); + n_tty_kick_worker(tty); if (tty->link) n_tty_packet_mode_flush(tty); + up_write(&tty->termios_rwsem); } /** @@ -259,24 +376,18 @@ * Report the number of characters buffered to be delivered to user * at this instant in time. * - * Locking: read_lock + * Locking: exclusive termios_rwsem */ static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty) { - struct n_tty_data *ldata = tty->disc_data; - unsigned long flags; - ssize_t n = 0; + ssize_t n; - raw_spin_lock_irqsave(&ldata->read_lock, flags); - if (!ldata->icanon) { - n = ldata->read_cnt; - } else if (ldata->canon_data) { - n = (ldata->canon_head > ldata->read_tail) ? - ldata->canon_head - ldata->read_tail : - ldata->canon_head + (N_TTY_BUF_SIZE - ldata->read_tail); - } - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); + WARN_ONCE(1, "%s is deprecated and scheduled for removal.", __func__); + + down_write(&tty->termios_rwsem); + n = chars_in_buffer(tty); + up_write(&tty->termios_rwsem); return n; } @@ -521,33 +632,23 @@ * 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 + * Locking: callers must hold output_lock */ -static void process_echoes(struct tty_struct *tty) +static size_t __process_echoes(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - int space, nr; + int space, old_space; + size_t tail; unsigned char c; - unsigned char *cp, *buf_end; - - if (!ldata->echo_cnt) - return; - - mutex_lock(&ldata->output_lock); - mutex_lock(&ldata->echo_lock); - space = tty_write_room(tty); + old_space = space = tty_write_room(tty); - buf_end = ldata->echo_buf + N_TTY_BUF_SIZE; - cp = ldata->echo_buf + ldata->echo_pos; - nr = ldata->echo_cnt; - while (nr > 0) { - c = *cp; + tail = ldata->echo_tail; + while (ldata->echo_commit != tail) { + c = echo_buf(ldata, tail); if (c == ECHO_OP_START) { unsigned char op; - unsigned char *opp; int no_space_left = 0; /* @@ -555,18 +656,13 @@ * 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; + op = echo_buf(ldata, tail + 1); 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; + num_chars = echo_buf(ldata, tail + 2); /* * Determine how many columns to go back @@ -592,21 +688,18 @@ if (ldata->column > 0) ldata->column--; } - cp += 3; - nr -= 3; + tail += 3; break; case ECHO_OP_SET_CANON_COL: ldata->canon_column = ldata->column; - cp += 2; - nr -= 2; + tail += 2; break; case ECHO_OP_MOVE_BACK_COL: if (ldata->column > 0) ldata->column--; - cp += 2; - nr -= 2; + tail += 2; break; case ECHO_OP_START: @@ -618,8 +711,7 @@ tty_put_char(tty, ECHO_OP_START); ldata->column++; space--; - cp += 2; - nr -= 2; + tail += 2; break; default: @@ -640,15 +732,13 @@ tty_put_char(tty, op ^ 0100); ldata->column += 2; space -= 2; - cp += 2; - nr -= 2; + tail += 2; } if (no_space_left) break; } else { - if (O_OPOST(tty) && - !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { + if (O_OPOST(tty)) { int retval = do_output_char(c, tty, space); if (retval < 0) break; @@ -659,80 +749,96 @@ tty_put_char(tty, c); space -= 1; } - cp += 1; - nr -= 1; + tail += 1; } - - /* When end of circular buffer reached, wrap around */ - if (cp >= buf_end) - cp -= N_TTY_BUF_SIZE; } - if (nr == 0) { - ldata->echo_pos = 0; - ldata->echo_cnt = 0; - ldata->echo_overrun = 0; - } else { - int num_processed = ldata->echo_cnt - nr; - ldata->echo_pos += num_processed; - ldata->echo_pos &= N_TTY_BUF_SIZE - 1; - ldata->echo_cnt = nr; - if (num_processed > 0) - ldata->echo_overrun = 0; + /* If the echo buffer is nearly full (so that the possibility exists + * of echo overrun before the next commit), then discard enough + * data at the tail to prevent a subsequent overrun */ + while (ldata->echo_commit - tail >= ECHO_DISCARD_WATERMARK) { + if (echo_buf(ldata, tail) == ECHO_OP_START) { + if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB) + tail += 3; + else + tail += 2; + } else + tail++; } - mutex_unlock(&ldata->echo_lock); + ldata->echo_tail = tail; + return old_space - space; +} + +static void commit_echoes(struct tty_struct *tty) +{ + struct n_tty_data *ldata = tty->disc_data; + size_t nr, old, echoed; + size_t head; + + head = ldata->echo_head; + ldata->echo_mark = head; + old = ldata->echo_commit - ldata->echo_tail; + + /* Process committed echoes if the accumulated # of bytes + * is over the threshold (and try again each time another + * block is accumulated) */ + nr = head - ldata->echo_tail; + if (nr < ECHO_COMMIT_WATERMARK || (nr % ECHO_BLOCK > old % ECHO_BLOCK)) + return; + + mutex_lock(&ldata->output_lock); + ldata->echo_commit = head; + echoed = __process_echoes(tty); + mutex_unlock(&ldata->output_lock); + + if (echoed && tty->ops->flush_chars) + tty->ops->flush_chars(tty); +} + +static void process_echoes(struct tty_struct *tty) +{ + struct n_tty_data *ldata = tty->disc_data; + size_t echoed; + + if (ldata->echo_mark == ldata->echo_tail) + return; + + mutex_lock(&ldata->output_lock); + ldata->echo_commit = ldata->echo_mark; + echoed = __process_echoes(tty); mutex_unlock(&ldata->output_lock); - if (tty->ops->flush_chars) + if (echoed && tty->ops->flush_chars) tty->ops->flush_chars(tty); } +/* NB: echo_mark and echo_head should be equivalent here */ +static void flush_echoes(struct tty_struct *tty) +{ + struct n_tty_data *ldata = tty->disc_data; + + if ((!L_ECHO(tty) && !L_ECHONL(tty)) || + ldata->echo_commit == ldata->echo_head) + return; + + mutex_lock(&ldata->output_lock); + ldata->echo_commit = ldata->echo_head; + __process_echoes(tty); + mutex_unlock(&ldata->output_lock); +} + /** * add_echo_byte - add a byte to the echo buffer * @c: unicode byte to echo * @ldata: n_tty data * * 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 n_tty_data *ldata) +static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata) { - int new_byte_pos; - - if (ldata->echo_cnt == N_TTY_BUF_SIZE) { - /* Circular buffer is already at capacity */ - new_byte_pos = ldata->echo_pos; - - /* - * Since the buffer start position needs to be advanced, - * be sure to step by a whole operation byte group. - */ - if (ldata->echo_buf[ldata->echo_pos] == ECHO_OP_START) { - if (ldata->echo_buf[(ldata->echo_pos + 1) & - (N_TTY_BUF_SIZE - 1)] == - ECHO_OP_ERASE_TAB) { - ldata->echo_pos += 3; - ldata->echo_cnt -= 2; - } else { - ldata->echo_pos += 2; - ldata->echo_cnt -= 1; - } - } else { - ldata->echo_pos++; - } - ldata->echo_pos &= N_TTY_BUF_SIZE - 1; - - ldata->echo_overrun = 1; - } else { - new_byte_pos = ldata->echo_pos + ldata->echo_cnt; - new_byte_pos &= N_TTY_BUF_SIZE - 1; - ldata->echo_cnt++; - } - - ldata->echo_buf[new_byte_pos] = c; + *echo_buf_addr(ldata, ldata->echo_head++) = c; } /** @@ -740,16 +846,12 @@ * @ldata: n_tty data * * 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 n_tty_data *ldata) { - mutex_lock(&ldata->echo_lock); add_echo_byte(ECHO_OP_START, ldata); add_echo_byte(ECHO_OP_MOVE_BACK_COL, ldata); - mutex_unlock(&ldata->echo_lock); } /** @@ -758,16 +860,12 @@ * * 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 n_tty_data *ldata) { - mutex_lock(&ldata->echo_lock); add_echo_byte(ECHO_OP_START, ldata); add_echo_byte(ECHO_OP_SET_CANON_COL, ldata); - mutex_unlock(&ldata->echo_lock); } /** @@ -783,15 +881,11 @@ * 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 n_tty_data *ldata) { - mutex_lock(&ldata->echo_lock); - add_echo_byte(ECHO_OP_START, ldata); add_echo_byte(ECHO_OP_ERASE_TAB, ldata); @@ -803,8 +897,6 @@ num_chars |= 0x80; add_echo_byte(num_chars, ldata); - - mutex_unlock(&ldata->echo_lock); } /** @@ -816,20 +908,16 @@ * 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 n_tty_data *ldata) { - mutex_lock(&ldata->echo_lock); if (c == ECHO_OP_START) { add_echo_byte(ECHO_OP_START, ldata); add_echo_byte(ECHO_OP_START, ldata); } else { add_echo_byte(c, ldata); } - mutex_unlock(&ldata->echo_lock); } /** @@ -842,16 +930,12 @@ * * 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) { struct n_tty_data *ldata = tty->disc_data; - mutex_lock(&ldata->echo_lock); - if (c == ECHO_OP_START) { add_echo_byte(ECHO_OP_START, ldata); add_echo_byte(ECHO_OP_START, ldata); @@ -860,8 +944,6 @@ add_echo_byte(ECHO_OP_START, ldata); add_echo_byte(c, ldata); } - - mutex_unlock(&ldata->echo_lock); } /** @@ -886,17 +968,18 @@ * present in the stream from the driver layer. Handles the complexities * of UTF-8 multibyte symbols. * - * Locking: read_lock for tty buffers + * n_tty_receive_buf()/producer path: + * caller holds non-exclusive termios_rwsem */ static void eraser(unsigned char c, struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; enum { ERASE, WERASE, KILL } kill_type; - int head, seen_alnums, cnt; - unsigned long flags; + size_t head; + size_t cnt; + int seen_alnums; - /* FIXME: locking needed ? */ if (ldata->read_head == ldata->canon_head) { /* process_output('\a', tty); */ /* what do you think? */ return; @@ -907,19 +990,11 @@ kill_type = WERASE; else { if (!L_ECHO(tty)) { - raw_spin_lock_irqsave(&ldata->read_lock, flags); - ldata->read_cnt -= ((ldata->read_head - ldata->canon_head) & - (N_TTY_BUF_SIZE - 1)); ldata->read_head = ldata->canon_head; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); return; } if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) { - raw_spin_lock_irqsave(&ldata->read_lock, flags); - ldata->read_cnt -= ((ldata->read_head - ldata->canon_head) & - (N_TTY_BUF_SIZE - 1)); ldata->read_head = ldata->canon_head; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); finish_erasing(ldata); echo_char(KILL_CHAR(tty), tty); /* Add a newline if ECHOK is on and ECHOKE is off. */ @@ -931,14 +1006,13 @@ } seen_alnums = 0; - /* FIXME: Locking ?? */ while (ldata->read_head != ldata->canon_head) { head = ldata->read_head; /* erase a single possibly multibyte character */ do { - head = (head - 1) & (N_TTY_BUF_SIZE-1); - c = ldata->read_buf[head]; + head--; + c = read_buf(ldata, head); } while (is_continuation(c, tty) && head != ldata->canon_head); /* do not partially erase */ @@ -952,11 +1026,8 @@ else if (seen_alnums) break; } - cnt = (ldata->read_head - head) & (N_TTY_BUF_SIZE-1); - raw_spin_lock_irqsave(&ldata->read_lock, flags); + cnt = ldata->read_head - head; ldata->read_head = head; - ldata->read_cnt -= cnt; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); if (L_ECHO(tty)) { if (L_ECHOPRT(tty)) { if (!ldata->erasing) { @@ -966,9 +1037,8 @@ /* if cnt > 1, output a multi-byte character */ echo_char(c, tty); while (--cnt > 0) { - head = (head+1) & (N_TTY_BUF_SIZE-1); - echo_char_raw(ldata->read_buf[head], - ldata); + head++; + echo_char_raw(read_buf(ldata, head), ldata); echo_move_back_col(ldata); } } else if (kill_type == ERASE && !L_ECHOE(tty)) { @@ -976,7 +1046,7 @@ } else if (c == '\t') { unsigned int num_chars = 0; int after_tab = 0; - unsigned long tail = ldata->read_head; + size_t tail = ldata->read_head; /* * Count the columns used for characters @@ -986,8 +1056,8 @@ * number of columns. */ while (tail != ldata->canon_head) { - tail = (tail-1) & (N_TTY_BUF_SIZE-1); - c = ldata->read_buf[tail]; + tail--; + c = read_buf(ldata, tail); if (c == '\t') { after_tab = 1; break; @@ -1027,10 +1097,14 @@ * Called when a signal is being sent due to terminal input. * Called from the driver receive_buf path so serialized. * + * Performs input and output flush if !NOFLSH. In this context, the echo + * buffer is 'output'. The signal is processed first to alert any current + * readers or writers to discontinue and exit their i/o loops. + * * Locking: ctrl_lock */ -static inline void isig(int sig, struct tty_struct *tty) +static void __isig(int sig, struct tty_struct *tty) { struct pid *tty_pgrp = tty_get_pgrp(tty); if (tty_pgrp) { @@ -1039,6 +1113,41 @@ } } +static void isig(int sig, struct tty_struct *tty) +{ + struct n_tty_data *ldata = tty->disc_data; + + if (L_NOFLSH(tty)) { + /* signal only */ + __isig(sig, tty); + + } else { /* signal and flush */ + up_read(&tty->termios_rwsem); + down_write(&tty->termios_rwsem); + + __isig(sig, tty); + + /* clear echo buffer */ + mutex_lock(&ldata->output_lock); + ldata->echo_head = ldata->echo_tail = 0; + ldata->echo_mark = ldata->echo_commit = 0; + mutex_unlock(&ldata->output_lock); + + /* clear output buffer */ + tty_driver_flush_buffer(tty); + + /* clear input buffer */ + reset_buffer_flags(tty->disc_data); + + /* notify pty master of flush */ + if (tty->link) + n_tty_packet_mode_flush(tty); + + up_write(&tty->termios_rwsem); + down_read(&tty->termios_rwsem); + } +} + /** * n_tty_receive_break - handle break * @tty: terminal @@ -1046,10 +1155,13 @@ * An RS232 break event has been hit in the incoming bitstream. This * can cause a variety of events depending upon the termios settings. * - * Called from the receive_buf path so single threaded. + * n_tty_receive_buf()/producer path: + * caller holds non-exclusive termios_rwsem + * + * Note: may get exclusive termios_rwsem if flushing input buffer */ -static inline void n_tty_receive_break(struct tty_struct *tty) +static void n_tty_receive_break(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; @@ -1057,10 +1169,6 @@ return; if (I_BRKINT(tty)) { isig(SIGINT, tty); - if (!L_NOFLSH(tty)) { - n_tty_flush_buffer(tty); - tty_driver_flush_buffer(tty); - } return; } if (I_PARMRK(tty)) { @@ -1068,7 +1176,6 @@ put_tty_queue('\0', ldata); } put_tty_queue('\0', ldata); - wake_up_interruptible(&tty->read_wait); } /** @@ -1084,16 +1191,15 @@ * private. */ -static inline void n_tty_receive_overrun(struct tty_struct *tty) +static void n_tty_receive_overrun(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - char buf[64]; ldata->num_overrun++; if (time_after(jiffies, ldata->overrun_time + HZ) || time_after(ldata->overrun_time, jiffies)) { printk(KERN_WARNING "%s: %d input overrun(s)\n", - tty_name(tty, buf), + tty_name(tty), ldata->num_overrun); ldata->overrun_time = jiffies; ldata->num_overrun = 0; @@ -1106,24 +1212,40 @@ * @c: character * * Process a parity error and queue the right data to indicate - * the error case if necessary. Locking as per n_tty_receive_buf. + * the error case if necessary. + * + * n_tty_receive_buf()/producer path: + * caller holds non-exclusive termios_rwsem */ -static inline void n_tty_receive_parity_error(struct tty_struct *tty, - unsigned char c) +static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) { struct n_tty_data *ldata = tty->disc_data; - if (I_IGNPAR(tty)) - return; - if (I_PARMRK(tty)) { - put_tty_queue('\377', ldata); - put_tty_queue('\0', ldata); - put_tty_queue(c, ldata); - } else if (I_INPCK(tty)) - put_tty_queue('\0', ldata); - else + if (I_INPCK(tty)) { + if (I_IGNPAR(tty)) + return; + if (I_PARMRK(tty)) { + put_tty_queue('\377', ldata); + put_tty_queue('\0', ldata); + put_tty_queue(c, ldata); + } else + put_tty_queue('\0', ldata); + } else put_tty_queue(c, ldata); - wake_up_interruptible(&tty->read_wait); +} + +static void +n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) +{ + isig(signal, tty); + if (I_IXON(tty)) + start_tty(tty); + if (L_ECHO(tty)) { + echo_char(c, tty); + commit_echoes(tty); + } else + process_echoes(tty); + return; } /** @@ -1134,117 +1256,52 @@ * Process an individual character of input received from the driver. * This is serialized with respect to itself by the rules for the * driver above. + * + * n_tty_receive_buf()/producer path: + * caller holds non-exclusive termios_rwsem + * publishes canon_head if canonical mode is active + * + * Returns 1 if LNEXT was received, else returns 0 */ -static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c) +static int +n_tty_receive_char_special(struct tty_struct *tty, unsigned char c) { struct n_tty_data *ldata = tty->disc_data; - unsigned long flags; - int parmrk; - - if (ldata->raw) { - put_tty_queue(c, ldata); - return; - } - - if (I_ISTRIP(tty)) - c &= 0x7f; - if (I_IUCLC(tty) && L_IEXTEN(tty)) - c = tolower(c); - - if (L_EXTPROC(tty)) { - put_tty_queue(c, ldata); - return; - } - - 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)) { - start_tty(tty); - process_echoes(tty); - } - - if (tty->closing) { - if (I_IXON(tty)) { - if (c == START_CHAR(tty)) { - start_tty(tty); - process_echoes(tty); - } else if (c == STOP_CHAR(tty)) - stop_tty(tty); - } - return; - } - - /* - * If the previous character was LNEXT, or we know that this - * character is not one of the characters that we'll have to - * handle specially, do shortcut processing to speed things - * up. - */ - if (!test_bit(c, ldata->process_char_map) || ldata->lnext) { - ldata->lnext = 0; - parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; - if (ldata->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)) { - finish_erasing(ldata); - /* Record the column of first canon char. */ - if (ldata->canon_head == ldata->read_head) - echo_set_canon_col(ldata); - echo_char(c, tty); - process_echoes(tty); - } - if (parmrk) - put_tty_queue(c, ldata); - put_tty_queue(c, ldata); - return; - } if (I_IXON(tty)) { if (c == START_CHAR(tty)) { start_tty(tty); process_echoes(tty); - return; + return 0; } if (c == STOP_CHAR(tty)) { stop_tty(tty); - return; + return 0; } } if (L_ISIG(tty)) { - int signal; - signal = SIGINT; - if (c == INTR_CHAR(tty)) - goto send_signal; - signal = SIGQUIT; - if (c == QUIT_CHAR(tty)) - goto send_signal; - signal = SIGTSTP; - if (c == SUSP_CHAR(tty)) { -send_signal: - if (!L_NOFLSH(tty)) { - n_tty_flush_buffer(tty); - tty_driver_flush_buffer(tty); - } - if (I_IXON(tty)) - start_tty(tty); - if (L_ECHO(tty)) { - echo_char(c, tty); - process_echoes(tty); - } - isig(signal, tty); - return; + if (c == INTR_CHAR(tty)) { + n_tty_receive_signal_char(tty, SIGINT, c); + return 0; + } else if (c == QUIT_CHAR(tty)) { + n_tty_receive_signal_char(tty, SIGQUIT, c); + return 0; + } else if (c == SUSP_CHAR(tty)) { + n_tty_receive_signal_char(tty, SIGTSTP, c); + return 0; } } + if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { + start_tty(tty); + process_echoes(tty); + } + if (c == '\r') { if (I_IGNCR(tty)) - return; + return 0; if (I_ICRNL(tty)) c = '\n'; } else if (c == '\n' && I_INLCR(tty)) @@ -1254,8 +1311,8 @@ if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) || (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) { eraser(c, tty); - process_echoes(tty); - return; + commit_echoes(tty); + return 0; } if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { ldata->lnext = 1; @@ -1264,54 +1321,37 @@ if (L_ECHOCTL(tty)) { echo_char_raw('^', ldata); echo_char_raw('\b', ldata); - process_echoes(tty); + commit_echoes(tty); } } - return; + return 1; } - if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && - L_IEXTEN(tty)) { - unsigned long tail = ldata->canon_head; + if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && L_IEXTEN(tty)) { + size_t tail = ldata->canon_head; finish_erasing(ldata); echo_char(c, tty); echo_char_raw('\n', ldata); while (tail != ldata->read_head) { - echo_char(ldata->read_buf[tail], tty); - tail = (tail+1) & (N_TTY_BUF_SIZE-1); + echo_char(read_buf(ldata, tail), tty); + tail++; } - process_echoes(tty); - return; + commit_echoes(tty); + return 0; } if (c == '\n') { - if (ldata->read_cnt >= N_TTY_BUF_SIZE) { - if (L_ECHO(tty)) - process_output('\a', tty); - return; - } if (L_ECHO(tty) || L_ECHONL(tty)) { echo_char_raw('\n', ldata); - process_echoes(tty); + commit_echoes(tty); } goto handle_newline; } if (c == EOF_CHAR(tty)) { - if (ldata->read_cnt >= N_TTY_BUF_SIZE) - return; - if (ldata->canon_head != ldata->read_head) - set_bit(TTY_PUSH, &tty->flags); c = __DISABLED_CHAR; goto handle_newline; } if ((c == EOL_CHAR(tty)) || (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { - parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) - ? 1 : 0; - if (ldata->read_cnt >= (N_TTY_BUF_SIZE - parmrk)) { - if (L_ECHO(tty)) - process_output('\a', tty); - return; - } /* * XXX are EOL_CHAR and EOL2_CHAR echoed?!? */ @@ -1320,36 +1360,25 @@ if (ldata->canon_head == ldata->read_head) echo_set_canon_col(ldata); echo_char(c, tty); - process_echoes(tty); + commit_echoes(tty); } /* * XXX does PARMRK doubling happen for * EOL_CHAR and EOL2_CHAR? */ - if (parmrk) + if (c == (unsigned char) '\377' && I_PARMRK(tty)) put_tty_queue(c, ldata); handle_newline: - raw_spin_lock_irqsave(&ldata->read_lock, flags); - set_bit(ldata->read_head, ldata->read_flags); - put_tty_queue_nolock(c, ldata); - ldata->canon_head = ldata->read_head; - ldata->canon_data++; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); + set_bit(ldata->read_head & (N_TTY_BUF_SIZE - 1), ldata->read_flags); + put_tty_queue(c, ldata); + smp_store_release(&ldata->canon_head, ldata->read_head); kill_fasync(&tty->fasync, SIGIO, POLL_IN); - if (waitqueue_active(&tty->read_wait)) - wake_up_interruptible(&tty->read_wait); - return; + wake_up_interruptible_poll(&tty->read_wait, POLLIN); + return 0; } } - parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; - if (ldata->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)) { finish_erasing(ldata); if (c == '\n') @@ -1360,122 +1389,392 @@ echo_set_canon_col(ldata); echo_char(c, tty); } - process_echoes(tty); + commit_echoes(tty); } - if (parmrk) + /* PARMRK doubling check */ + if (c == (unsigned char) '\377' && I_PARMRK(tty)) put_tty_queue(c, ldata); put_tty_queue(c, ldata); + return 0; } +static inline void +n_tty_receive_char_inline(struct tty_struct *tty, unsigned char c) +{ + struct n_tty_data *ldata = tty->disc_data; -/** - * n_tty_write_wakeup - asynchronous I/O notifier - * @tty: tty device - * - * Required for the ptys, serial driver etc. since processes - * that attach themselves to the master and rely on ASYNC - * IO must be woken up - */ + if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { + start_tty(tty); + process_echoes(tty); + } + if (L_ECHO(tty)) { + finish_erasing(ldata); + /* Record the column of first canon char. */ + if (ldata->canon_head == ldata->read_head) + echo_set_canon_col(ldata); + echo_char(c, tty); + commit_echoes(tty); + } + /* PARMRK doubling check */ + if (c == (unsigned char) '\377' && I_PARMRK(tty)) + put_tty_queue(c, ldata); + put_tty_queue(c, ldata); +} -static void n_tty_write_wakeup(struct tty_struct *tty) +static void n_tty_receive_char(struct tty_struct *tty, unsigned char c) { - if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) - kill_fasync(&tty->fasync, SIGIO, POLL_OUT); + n_tty_receive_char_inline(tty, c); } -/** - * n_tty_receive_buf - data receive - * @tty: terminal device - * @cp: buffer - * @fp: flag buffer - * @count: characters - * - * Called by the terminal driver when a block of characters has - * been received. This function must be called from soft contexts - * not from interrupt context. The driver is responsible for making - * calls one at a time and in order (or using flush_to_ldisc) - */ +static inline void +n_tty_receive_char_fast(struct tty_struct *tty, unsigned char c) +{ + struct n_tty_data *ldata = tty->disc_data; -static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) + if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { + start_tty(tty); + process_echoes(tty); + } + if (L_ECHO(tty)) { + finish_erasing(ldata); + /* Record the column of first canon char. */ + if (ldata->canon_head == ldata->read_head) + echo_set_canon_col(ldata); + echo_char(c, tty); + commit_echoes(tty); + } + put_tty_queue(c, ldata); +} + +static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c) +{ + if (I_ISTRIP(tty)) + c &= 0x7f; + if (I_IUCLC(tty) && L_IEXTEN(tty)) + c = tolower(c); + + if (I_IXON(tty)) { + if (c == STOP_CHAR(tty)) + stop_tty(tty); + else if (c == START_CHAR(tty) || + (tty->stopped && !tty->flow_stopped && I_IXANY(tty) && + c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && + c != SUSP_CHAR(tty))) { + start_tty(tty); + process_echoes(tty); + } + } +} + +static void +n_tty_receive_char_flagged(struct tty_struct *tty, unsigned char c, char flag) +{ + switch (flag) { + case TTY_BREAK: + n_tty_receive_break(tty); + break; + case TTY_PARITY: + case TTY_FRAME: + n_tty_receive_parity_error(tty, c); + break; + case TTY_OVERRUN: + n_tty_receive_overrun(tty); + break; + default: + printk(KERN_ERR "%s: unknown flag %d\n", + tty_name(tty), flag); + break; + } +} + +static void +n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag) { struct n_tty_data *ldata = tty->disc_data; - const unsigned char *p; - char *f, flags = TTY_NORMAL; - int i; - char buf[64]; - unsigned long cpuflags; - if (ldata->real_raw) { - raw_spin_lock_irqsave(&ldata->read_lock, cpuflags); - i = min(N_TTY_BUF_SIZE - ldata->read_cnt, - N_TTY_BUF_SIZE - ldata->read_head); - i = min(count, i); - memcpy(ldata->read_buf + ldata->read_head, cp, i); - ldata->read_head = (ldata->read_head + i) & (N_TTY_BUF_SIZE-1); - ldata->read_cnt += i; - cp += i; - count -= i; - - i = min(N_TTY_BUF_SIZE - ldata->read_cnt, - N_TTY_BUF_SIZE - ldata->read_head); - i = min(count, i); - memcpy(ldata->read_buf + ldata->read_head, cp, i); - ldata->read_head = (ldata->read_head + i) & (N_TTY_BUF_SIZE-1); - ldata->read_cnt += i; - raw_spin_unlock_irqrestore(&ldata->read_lock, cpuflags); - } else { - for (i = count, p = cp, f = fp; i; i--, p++) { - if (f) - flags = *f++; - switch (flags) { - case TTY_NORMAL: - n_tty_receive_char(tty, *p); - break; - case TTY_BREAK: - n_tty_receive_break(tty); - break; - case TTY_PARITY: - case TTY_FRAME: - n_tty_receive_parity_error(tty, *p); - break; - case TTY_OVERRUN: - n_tty_receive_overrun(tty); - break; - default: - printk(KERN_ERR "%s: unknown flag %d\n", - tty_name(tty, buf), flags); - break; + ldata->lnext = 0; + if (likely(flag == TTY_NORMAL)) { + if (I_ISTRIP(tty)) + c &= 0x7f; + if (I_IUCLC(tty) && L_IEXTEN(tty)) + c = tolower(c); + n_tty_receive_char(tty, c); + } else + n_tty_receive_char_flagged(tty, c, flag); +} + +static void +n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct n_tty_data *ldata = tty->disc_data; + size_t n, head; + + head = ldata->read_head & (N_TTY_BUF_SIZE - 1); + n = min_t(size_t, count, N_TTY_BUF_SIZE - head); + memcpy(read_buf_addr(ldata, head), cp, n); + ldata->read_head += n; + cp += n; + count -= n; + + head = ldata->read_head & (N_TTY_BUF_SIZE - 1); + n = min_t(size_t, count, N_TTY_BUF_SIZE - head); + memcpy(read_buf_addr(ldata, head), cp, n); + ldata->read_head += n; +} + +static void +n_tty_receive_buf_raw(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct n_tty_data *ldata = tty->disc_data; + char flag = TTY_NORMAL; + + while (count--) { + if (fp) + flag = *fp++; + if (likely(flag == TTY_NORMAL)) + put_tty_queue(*cp++, ldata); + else + n_tty_receive_char_flagged(tty, *cp++, flag); + } +} + +static void +n_tty_receive_buf_closing(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + char flag = TTY_NORMAL; + + while (count--) { + if (fp) + flag = *fp++; + if (likely(flag == TTY_NORMAL)) + n_tty_receive_char_closing(tty, *cp++); + else + n_tty_receive_char_flagged(tty, *cp++, flag); + } +} + +static void +n_tty_receive_buf_standard(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct n_tty_data *ldata = tty->disc_data; + char flag = TTY_NORMAL; + + while (count--) { + if (fp) + flag = *fp++; + if (likely(flag == TTY_NORMAL)) { + unsigned char c = *cp++; + + if (I_ISTRIP(tty)) + c &= 0x7f; + if (I_IUCLC(tty) && L_IEXTEN(tty)) + c = tolower(c); + if (L_EXTPROC(tty)) { + put_tty_queue(c, ldata); + continue; } + if (!test_bit(c, ldata->char_map)) + n_tty_receive_char_inline(tty, c); + else if (n_tty_receive_char_special(tty, c) && count) { + if (fp) + flag = *fp++; + n_tty_receive_char_lnext(tty, *cp++, flag); + count--; + } + } else + n_tty_receive_char_flagged(tty, *cp++, flag); + } +} + +static void +n_tty_receive_buf_fast(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct n_tty_data *ldata = tty->disc_data; + char flag = TTY_NORMAL; + + while (count--) { + if (fp) + flag = *fp++; + if (likely(flag == TTY_NORMAL)) { + unsigned char c = *cp++; + + if (!test_bit(c, ldata->char_map)) + n_tty_receive_char_fast(tty, c); + else if (n_tty_receive_char_special(tty, c) && count) { + if (fp) + flag = *fp++; + n_tty_receive_char_lnext(tty, *cp++, flag); + count--; + } + } else + n_tty_receive_char_flagged(tty, *cp++, flag); + } +} + +static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct n_tty_data *ldata = tty->disc_data; + bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); + + if (ldata->real_raw) + n_tty_receive_buf_real_raw(tty, cp, fp, count); + else if (ldata->raw || (L_EXTPROC(tty) && !preops)) + n_tty_receive_buf_raw(tty, cp, fp, count); + else if (tty->closing && !L_EXTPROC(tty)) + n_tty_receive_buf_closing(tty, cp, fp, count); + else { + if (ldata->lnext) { + char flag = TTY_NORMAL; + + if (fp) + flag = *fp++; + n_tty_receive_char_lnext(tty, *cp++, flag); + count--; } + + if (!preops && !I_PARMRK(tty)) + n_tty_receive_buf_fast(tty, cp, fp, count); + else + n_tty_receive_buf_standard(tty, cp, fp, count); + + flush_echoes(tty); if (tty->ops->flush_chars) tty->ops->flush_chars(tty); } - n_tty_set_room(tty); + if (ldata->icanon && !L_EXTPROC(tty)) + return; - if ((!ldata->icanon && (ldata->read_cnt >= tty->minimum_to_wake)) || - L_EXTPROC(tty)) { + /* publish read_head to consumer */ + smp_store_release(&ldata->commit_head, ldata->read_head); + + if ((read_cnt(ldata) >= ldata->minimum_to_wake) || L_EXTPROC(tty)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); - if (waitqueue_active(&tty->read_wait)) - wake_up_interruptible(&tty->read_wait); + wake_up_interruptible_poll(&tty->read_wait, POLLIN); } +} + +/** + * n_tty_receive_buf_common - process input + * @tty: device to receive input + * @cp: input chars + * @fp: flags for each char (if NULL, all chars are TTY_NORMAL) + * @count: number of input chars in @cp + * + * Called by the terminal driver when a block of characters has + * been received. This function must be called from soft contexts + * not from interrupt context. The driver is responsible for making + * calls one at a time and in order (or using flush_to_ldisc) + * + * Returns the # of input chars from @cp which were processed. + * + * In canonical mode, the maximum line length is 4096 chars (including + * the line termination char); lines longer than 4096 chars are + * truncated. After 4095 chars, input data is still processed but + * not stored. Overflow processing ensures the tty can always + * receive more input until at least one line can be read. + * + * In non-canonical mode, the read buffer will only accept 4095 chars; + * this provides the necessary space for a newline char if the input + * mode is switched to canonical. + * + * Note it is possible for the read buffer to _contain_ 4096 chars + * in non-canonical mode: the read buffer could already contain the + * maximum canon line of 4096 chars when the mode is switched to + * non-canonical. + * + * n_tty_receive_buf()/producer path: + * claims non-exclusive termios_rwsem + * publishes commit_head or canon_head + */ +static int +n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count, int flow) +{ + struct n_tty_data *ldata = tty->disc_data; + int room, n, rcvd = 0, overflow; + + down_read(&tty->termios_rwsem); - /* - * Check the remaining room for the input canonicalization - * mode. We don't want to throttle the driver if we're in - * canonical mode and don't have a newline yet! - */ while (1) { - tty_set_flow_change(tty, TTY_THROTTLE_SAFE); - if (tty->receive_room >= TTY_THRESHOLD_THROTTLE) - break; - if (!tty_throttle_safe(tty)) + /* + * When PARMRK is set, each input char may take up to 3 chars + * in the read buf; reduce the buffer space avail by 3x + * + * If we are doing input canonicalization, and there are no + * pending newlines, let characters through without limit, so + * that erase characters will be handled. Other excess + * characters will be beeped. + * + * paired with store in *_copy_from_read_buf() -- guarantees + * the consumer has loaded the data in read_buf up to the new + * read_tail (so this producer will not overwrite unread data) + */ + size_t tail = smp_load_acquire(&ldata->read_tail); + + room = N_TTY_BUF_SIZE - (ldata->read_head - tail); + if (I_PARMRK(tty)) + room = (room + 2) / 3; + room--; + if (room <= 0) { + overflow = ldata->icanon && ldata->canon_head == tail; + if (overflow && room < 0) + ldata->read_head--; + room = overflow; + ldata->no_room = flow && !room; + } else + overflow = 0; + + n = min(count, room); + if (!n) break; - } - __tty_set_flow_change(tty, 0); + + /* ignore parity errors if handling overflow */ + if (!overflow || !fp || *fp != TTY_PARITY) + __receive_buf(tty, cp, fp, n); + + cp += n; + if (fp) + fp += n; + count -= n; + rcvd += n; + } + + tty->receive_room = room; + + /* Unthrottle if handling overflow on pty */ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY) { + if (overflow) { + tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); + tty_unthrottle_safe(tty); + __tty_set_flow_change(tty, 0); + } + } else + n_tty_check_throttle(tty); + + up_read(&tty->termios_rwsem); + + return rcvd; +} + +static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + n_tty_receive_buf_common(tty, cp, fp, count, 0); +} + +static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + return n_tty_receive_buf_common(tty, cp, fp, count, 1); } int is_ignored(int sig) @@ -1495,72 +1794,68 @@ * guaranteed that this function will not be re-entered or in progress * when the ldisc is closed. * - * Locking: Caller holds tty->termios_mutex + * Locking: Caller holds tty->termios_rwsem */ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) { struct n_tty_data *ldata = tty->disc_data; - int canon_change = 1; - if (old) - canon_change = (old->c_lflag ^ tty->termios.c_lflag) & ICANON; - if (canon_change) { + if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) { bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); - ldata->canon_head = ldata->read_tail; - ldata->canon_data = 0; + ldata->line_start = ldata->read_tail; + if (!L_ICANON(tty) || !read_cnt(ldata)) { + ldata->canon_head = ldata->read_tail; + ldata->push = 0; + } else { + set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1), + ldata->read_flags); + ldata->canon_head = ldata->read_head; + ldata->push = 1; + } + ldata->commit_head = ldata->read_head; ldata->erasing = 0; + ldata->lnext = 0; } - if (canon_change && !L_ICANON(tty) && ldata->read_cnt) - wake_up_interruptible(&tty->read_wait); - ldata->icanon = (L_ICANON(tty) != 0); - if (test_bit(TTY_HW_COOK_IN, &tty->flags)) { - ldata->raw = 1; - ldata->real_raw = 1; - n_tty_set_room(tty); - return; - } + if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) || I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) || I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) || I_PARMRK(tty)) { - bitmap_zero(ldata->process_char_map, 256); + bitmap_zero(ldata->char_map, 256); if (I_IGNCR(tty) || I_ICRNL(tty)) - set_bit('\r', ldata->process_char_map); + set_bit('\r', ldata->char_map); if (I_INLCR(tty)) - set_bit('\n', ldata->process_char_map); + set_bit('\n', ldata->char_map); if (L_ICANON(tty)) { - set_bit(ERASE_CHAR(tty), ldata->process_char_map); - set_bit(KILL_CHAR(tty), ldata->process_char_map); - set_bit(EOF_CHAR(tty), ldata->process_char_map); - set_bit('\n', ldata->process_char_map); - set_bit(EOL_CHAR(tty), ldata->process_char_map); + set_bit(ERASE_CHAR(tty), ldata->char_map); + set_bit(KILL_CHAR(tty), ldata->char_map); + set_bit(EOF_CHAR(tty), ldata->char_map); + set_bit('\n', ldata->char_map); + set_bit(EOL_CHAR(tty), ldata->char_map); if (L_IEXTEN(tty)) { - set_bit(WERASE_CHAR(tty), - ldata->process_char_map); - set_bit(LNEXT_CHAR(tty), - ldata->process_char_map); - set_bit(EOL2_CHAR(tty), - ldata->process_char_map); + set_bit(WERASE_CHAR(tty), ldata->char_map); + set_bit(LNEXT_CHAR(tty), ldata->char_map); + set_bit(EOL2_CHAR(tty), ldata->char_map); if (L_ECHO(tty)) set_bit(REPRINT_CHAR(tty), - ldata->process_char_map); + ldata->char_map); } } if (I_IXON(tty)) { - set_bit(START_CHAR(tty), ldata->process_char_map); - set_bit(STOP_CHAR(tty), ldata->process_char_map); + set_bit(START_CHAR(tty), ldata->char_map); + set_bit(STOP_CHAR(tty), ldata->char_map); } if (L_ISIG(tty)) { - set_bit(INTR_CHAR(tty), ldata->process_char_map); - set_bit(QUIT_CHAR(tty), ldata->process_char_map); - set_bit(SUSP_CHAR(tty), ldata->process_char_map); + set_bit(INTR_CHAR(tty), ldata->char_map); + set_bit(QUIT_CHAR(tty), ldata->char_map); + set_bit(SUSP_CHAR(tty), ldata->char_map); } - clear_bit(__DISABLED_CHAR, ldata->process_char_map); + clear_bit(__DISABLED_CHAR, ldata->char_map); ldata->raw = 0; ldata->real_raw = 0; } else { @@ -1572,13 +1867,13 @@ else ldata->real_raw = 0; } - n_tty_set_room(tty); /* * Fix tty hang when I_IXON(tty) is cleared, but the tty * been stopped by STOP_CHAR(tty) before it. */ if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow_stopped) { start_tty(tty); + process_echoes(tty); } /* The termios change make the tty ready for I/O */ @@ -1603,9 +1898,7 @@ if (tty->link) n_tty_packet_mode_flush(tty); - kfree(ldata->read_buf); - kfree(ldata->echo_buf); - kfree(ldata); + vfree(ldata); tty->disc_data = NULL; } @@ -1623,26 +1916,23 @@ { struct n_tty_data *ldata; - ldata = kzalloc(sizeof(*ldata), GFP_KERNEL); + /* Currently a malloc failure here can panic */ + ldata = vmalloc(sizeof(*ldata)); if (!ldata) goto err; ldata->overrun_time = jiffies; mutex_init(&ldata->atomic_read_lock); mutex_init(&ldata->output_lock); - mutex_init(&ldata->echo_lock); - raw_spin_lock_init(&ldata->read_lock); - - /* These are ugly. Currently a malloc failure here can panic */ - ldata->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); - ldata->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); - if (!ldata->read_buf || !ldata->echo_buf) - goto err_free_bufs; tty->disc_data = ldata; reset_buffer_flags(tty->disc_data); ldata->column = 0; - tty->minimum_to_wake = 1; + ldata->canon_column = 0; + ldata->minimum_to_wake = 1; + ldata->num_overrun = 0; + ldata->no_room = 0; + ldata->lnext = 0; tty->closing = 0; /* indicate buffer work may resume */ clear_bit(TTY_LDISC_HALTED, &tty->flags); @@ -1650,26 +1940,19 @@ tty_unthrottle(tty); return 0; -err_free_bufs: - kfree(ldata->read_buf); - kfree(ldata->echo_buf); - kfree(ldata); err: return -ENOMEM; } -static inline int input_available_p(struct tty_struct *tty, int amt) +static inline int input_available_p(struct tty_struct *tty, int poll) { struct n_tty_data *ldata = tty->disc_data; + int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1; - tty_flush_to_ldisc(tty); - if (ldata->icanon && !L_EXTPROC(tty)) { - if (ldata->canon_data) - return 1; - } else if (ldata->read_cnt >= (amt ? amt : 1)) - return 1; - - return 0; + if (ldata->icanon && !L_EXTPROC(tty)) + return ldata->canon_head != ldata->read_tail; + else + return ldata->commit_head - ldata->read_tail >= amt; } /** @@ -1687,6 +1970,9 @@ * * Called under the ldata->atomic_read_lock sem * + * n_tty_read()/consumer path: + * caller holds non-exclusive termios_rwsem + * read_tail published */ static int copy_from_read_buf(struct tty_struct *tty, @@ -1697,34 +1983,126 @@ struct n_tty_data *ldata = tty->disc_data; int retval; size_t n; - unsigned long flags; bool is_eof; + size_t head = smp_load_acquire(&ldata->commit_head); + size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); retval = 0; - raw_spin_lock_irqsave(&ldata->read_lock, flags); - n = min(ldata->read_cnt, N_TTY_BUF_SIZE - ldata->read_tail); + n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail); n = min(*nr, n); - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); if (n) { - retval = copy_to_user(*b, &ldata->read_buf[ldata->read_tail], n); + retval = copy_to_user(*b, read_buf_addr(ldata, tail), n); n -= retval; - is_eof = n == 1 && - ldata->read_buf[ldata->read_tail] == EOF_CHAR(tty); - tty_audit_add_data(tty, &ldata->read_buf[ldata->read_tail], n, + is_eof = n == 1 && read_buf(ldata, tail) == EOF_CHAR(tty); + tty_audit_add_data(tty, read_buf_addr(ldata, tail), n, ldata->icanon); - raw_spin_lock_irqsave(&ldata->read_lock, flags); - ldata->read_tail = (ldata->read_tail + n) & (N_TTY_BUF_SIZE-1); - ldata->read_cnt -= n; + smp_store_release(&ldata->read_tail, ldata->read_tail + n); /* Turn single EOF into zero-length read */ - if (L_EXTPROC(tty) && ldata->icanon && is_eof && !ldata->read_cnt) + if (L_EXTPROC(tty) && ldata->icanon && is_eof && + (head == ldata->read_tail)) n = 0; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); *b += n; *nr -= n; } return retval; } +/** + * canon_copy_from_read_buf - copy read data in canonical mode + * @tty: terminal device + * @b: user data + * @nr: size of data + * + * Helper function for n_tty_read. It is only called when ICANON is on; + * it copies one line of input up to and including the line-delimiting + * character into the user-space buffer. + * + * NB: When termios is changed from non-canonical to canonical mode and + * the read buffer contains data, n_tty_set_termios() simulates an EOF + * push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer. + * This causes data already processed as input to be immediately available + * as input although a newline has not been received. + * + * Called under the atomic_read_lock mutex + * + * n_tty_read()/consumer path: + * caller holds non-exclusive termios_rwsem + * read_tail published + */ + +static int canon_copy_from_read_buf(struct tty_struct *tty, + unsigned char __user **b, + size_t *nr) +{ + struct n_tty_data *ldata = tty->disc_data; + size_t n, size, more, c; + size_t eol; + size_t tail; + int ret, found = 0; + + /* N.B. avoid overrun if nr == 0 */ + if (!*nr) + return 0; + + n = min(*nr + 1, smp_load_acquire(&ldata->canon_head) - ldata->read_tail); + + tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); + size = min_t(size_t, tail + n, N_TTY_BUF_SIZE); + + n_tty_trace("%s: nr:%zu tail:%zu n:%zu size:%zu\n", + __func__, *nr, tail, n, size); + + eol = find_next_bit(ldata->read_flags, size, tail); + more = n - (size - tail); + if (eol == N_TTY_BUF_SIZE && more) { + /* scan wrapped without finding set bit */ + eol = find_next_bit(ldata->read_flags, more, 0); + if (eol != more) + found = 1; + } else if (eol != size) + found = 1; + + size = N_TTY_BUF_SIZE - tail; + n = eol - tail; + if (n > N_TTY_BUF_SIZE) + n += N_TTY_BUF_SIZE; + c = n + found; + + if (!found || read_buf(ldata, eol) != __DISABLED_CHAR) { + c = min(*nr, c); + n = c; + } + + n_tty_trace("%s: eol:%zu found:%d n:%zu c:%zu size:%zu more:%zu\n", + __func__, eol, found, n, c, size, more); + + if (n > size) { + ret = tty_copy_to_user(tty, *b, read_buf_addr(ldata, tail), size); + if (ret) + return -EFAULT; + ret = tty_copy_to_user(tty, *b + size, ldata->read_buf, n - size); + } else + ret = tty_copy_to_user(tty, *b, read_buf_addr(ldata, tail), n); + + if (ret) + return -EFAULT; + *b += n; + *nr -= n; + + if (found) + clear_bit(eol, ldata->read_flags); + smp_store_release(&ldata->read_tail, ldata->read_tail + c); + + if (found) { + if (!ldata->push) + ldata->line_start = ldata->read_tail; + else + ldata->push = 0; + tty_audit_push(tty); + } + return 0; +} + extern ssize_t redirected_tty_write(struct file *, const char __user *, size_t, loff_t *); @@ -1749,23 +2127,10 @@ /* NOTE: not yet done after every sleep pending a thorough check of the logic of this change. -- jlc */ /* don't stop on /dev/console */ - if (file->f_op->write == redirected_tty_write || - current->signal->tty != tty) + if (file->f_op->write == redirected_tty_write) return 0; - spin_lock_irq(&tty->ctrl_lock); - if (!tty->pgrp) - printk(KERN_ERR "n_tty_read: no tty->pgrp!\n"); - else if (task_pgrp(current) != tty->pgrp) { - spin_unlock_irq(&tty->ctrl_lock); - if (is_ignored(SIGTTIN) || is_current_pgrp_orphaned()) - return -EIO; - kill_pgrp(task_pgrp(current), SIGTTIN, 1); - set_thread_flag(TIF_SIGPENDING); - return -ERESTARTSYS; - } - spin_unlock_irq(&tty->ctrl_lock); - return 0; + return __tty_check_change(tty, SIGTTIN); } @@ -1782,6 +2147,10 @@ * a hangup. Always called in user context, may sleep. * * This code must be sure never to sleep through a hangup. + * + * n_tty_read()/consumer path: + * claims non-exclusive termios_rwsem + * publishes read_tail */ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, @@ -1789,52 +2158,50 @@ { struct n_tty_data *ldata = tty->disc_data; unsigned char __user *b = buf; - DECLARE_WAITQUEUE(wait, current); + DEFINE_WAIT_FUNC(wait, woken_wake_function); int c; int minimum, time; ssize_t retval = 0; - ssize_t size; long timeout; - unsigned long flags; int packet; + size_t tail; -do_it_again: c = job_control(tty, file); if (c < 0) return c; + /* + * Internal serialization of reads. + */ + if (file->f_flags & O_NONBLOCK) { + if (!mutex_trylock(&ldata->atomic_read_lock)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&ldata->atomic_read_lock)) + return -ERESTARTSYS; + } + + down_read(&tty->termios_rwsem); + minimum = time = 0; timeout = MAX_SCHEDULE_TIMEOUT; if (!ldata->icanon) { - time = (HZ / 10) * TIME_CHAR(tty); minimum = MIN_CHAR(tty); if (minimum) { + time = (HZ / 10) * TIME_CHAR(tty); if (time) - tty->minimum_to_wake = 1; + ldata->minimum_to_wake = 1; else if (!waitqueue_active(&tty->read_wait) || - (tty->minimum_to_wake > minimum)) - tty->minimum_to_wake = minimum; + (ldata->minimum_to_wake > minimum)) + ldata->minimum_to_wake = minimum; } else { - timeout = 0; - if (time) { - timeout = time; - time = 0; - } - tty->minimum_to_wake = minimum = 1; + timeout = (HZ / 10) * TIME_CHAR(tty); + ldata->minimum_to_wake = minimum = 1; } } - /* - * Internal serialization of reads. - */ - if (file->f_flags & O_NONBLOCK) { - if (!mutex_trylock(&ldata->atomic_read_lock)) - return -EAGAIN; - } else { - if (mutex_lock_interruptible(&ldata->atomic_read_lock)) - return -ERESTARTSYS; - } packet = tty->packet; + tail = ldata->read_tail; add_wait_queue(&tty->read_wait, &wait); while (nr) { @@ -1843,10 +2210,10 @@ unsigned char cs; if (b != buf) break; - spin_lock_irqsave(&tty->link->ctrl_lock, flags); + spin_lock_irq(&tty->link->ctrl_lock); cs = tty->link->ctrl_status; tty->link->ctrl_status = 0; - spin_unlock_irqrestore(&tty->link->ctrl_lock, flags); + spin_unlock_irq(&tty->link->ctrl_lock); if (tty_put_user(tty, cs, b++)) { retval = -EFAULT; b--; @@ -1855,94 +2222,59 @@ nr--; break; } - /* This statement must be first before checking for input - so that any interrupt will set the state back to - TASK_RUNNING. */ - set_current_state(TASK_INTERRUPTIBLE); - if (((minimum - (b - buf)) < tty->minimum_to_wake) && + if (((minimum - (b - buf)) < ldata->minimum_to_wake) && ((minimum - (b - buf)) >= 1)) - tty->minimum_to_wake = (minimum - (b - buf)); + ldata->minimum_to_wake = (minimum - (b - buf)); if (!input_available_p(tty, 0)) { - if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { - retval = -EIO; - break; - } - if (tty_hung_up_p(file)) - break; - if (!timeout) - break; - if (file->f_flags & O_NONBLOCK) { - retval = -EAGAIN; - break; - } - if (signal_pending(current)) { - retval = -ERESTARTSYS; - break; - } - /* FIXME: does n_tty_set_room need locking ? */ - n_tty_set_room(tty); - timeout = schedule_timeout(timeout); - continue; - } - __set_current_state(TASK_RUNNING); - - /* Deal with packet mode. */ - if (packet && b == buf) { - if (tty_put_user(tty, TIOCPKT_DATA, b++)) { - retval = -EFAULT; - b--; - break; - } - nr--; - } - - if (ldata->icanon && !L_EXTPROC(tty)) { - /* N.B. avoid overrun if nr == 0 */ - raw_spin_lock_irqsave(&ldata->read_lock, flags); - while (nr && ldata->read_cnt) { - int eol; - - eol = test_and_clear_bit(ldata->read_tail, - ldata->read_flags); - c = ldata->read_buf[ldata->read_tail]; - ldata->read_tail = ((ldata->read_tail+1) & - (N_TTY_BUF_SIZE-1)); - ldata->read_cnt--; - if (eol) { - /* this test should be redundant: - * we shouldn't be reading data if - * canon_data is 0 - */ - if (--ldata->canon_data < 0) - ldata->canon_data = 0; + up_read(&tty->termios_rwsem); + tty_buffer_flush_work(tty->port); + down_read(&tty->termios_rwsem); + if (!input_available_p(tty, 0)) { + if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { + retval = -EIO; + break; } - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); - - if (!eol || (c != __DISABLED_CHAR)) { - if (tty_put_user(tty, c, b++)) { - retval = -EFAULT; - b--; - raw_spin_lock_irqsave(&ldata->read_lock, flags); - break; - } - nr--; + if (tty_hung_up_p(file)) + break; + if (!timeout) + break; + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; } - if (eol) { - tty_audit_push(tty); - raw_spin_lock_irqsave(&ldata->read_lock, flags); + if (signal_pending(current)) { + retval = -ERESTARTSYS; break; } - raw_spin_lock_irqsave(&ldata->read_lock, flags); + up_read(&tty->termios_rwsem); + + timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, + timeout); + + down_read(&tty->termios_rwsem); + continue; } - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); + } + + if (ldata->icanon && !L_EXTPROC(tty)) { + retval = canon_copy_from_read_buf(tty, &b, &nr); if (retval) break; } else { int uncopied; - /* The copy function takes the read lock and handles - locking internally for this case */ + + /* Deal with packet mode. */ + if (packet && b == buf) { + if (tty_put_user(tty, TIOCPKT_DATA, b++)) { + retval = -EFAULT; + b--; + break; + } + nr--; + } + uncopied = copy_from_read_buf(tty, &b, &nr); uncopied += copy_from_read_buf(tty, &b, &nr); if (uncopied) { @@ -1951,46 +2283,26 @@ } } - /* If there is enough space in the read buffer now, let the - * low-level driver know. We use n_tty_chars_in_buffer() to - * check the buffer, as it now knows about canonical mode. - * Otherwise, if the driver is throttled and the line is - * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, - * we won't get any more characters. - */ - while (1) { - tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); - if (n_tty_chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) - break; - if (!tty->count) - break; - n_tty_set_room(tty); - if (!tty_unthrottle_safe(tty)) - break; - } - __tty_set_flow_change(tty, 0); + n_tty_check_unthrottle(tty); if (b - buf >= minimum) break; if (time) timeout = time; } - mutex_unlock(&ldata->atomic_read_lock); - remove_wait_queue(&tty->read_wait, &wait); + if (tail != ldata->read_tail) + n_tty_kick_worker(tty); + up_read(&tty->termios_rwsem); + remove_wait_queue(&tty->read_wait, &wait); if (!waitqueue_active(&tty->read_wait)) - tty->minimum_to_wake = minimum; + ldata->minimum_to_wake = minimum; - __set_current_state(TASK_RUNNING); - size = b - buf; - if (size) { - retval = size; - if (nr) - clear_bit(TTY_PUSH, &tty->flags); - } else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) - goto do_it_again; + mutex_unlock(&ldata->atomic_read_lock); + + if (b - buf) + retval = b - buf; - n_tty_set_room(tty); return retval; } @@ -2020,7 +2332,7 @@ const unsigned char *buf, size_t nr) { const unsigned char *b = buf; - DECLARE_WAITQUEUE(wait, current); + DEFINE_WAIT_FUNC(wait, woken_wake_function); int c; ssize_t retval = 0; @@ -2031,12 +2343,13 @@ return retval; } + down_read(&tty->termios_rwsem); + /* 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); if (signal_pending(current)) { retval = -ERESTARTSYS; break; @@ -2045,7 +2358,7 @@ retval = -EIO; break; } - if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { + if (O_OPOST(tty)) { while (nr > 0) { ssize_t num = process_output_block(tty, b, nr); if (num < 0) { @@ -2088,13 +2401,17 @@ retval = -EAGAIN; break; } - schedule(); + up_read(&tty->termios_rwsem); + + wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); + + down_read(&tty->termios_rwsem); } 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); + up_read(&tty->termios_rwsem); return (b - buf) ? b - buf : retval; } @@ -2115,12 +2432,18 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, poll_table *wait) { + struct n_tty_data *ldata = tty->disc_data; unsigned int mask = 0; poll_wait(file, &tty->read_wait, wait); poll_wait(file, &tty->write_wait, wait); - if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty))) + if (input_available_p(tty, 1)) mask |= POLLIN | POLLRDNORM; + else { + tty_buffer_flush_work(tty->port); + if (input_available_p(tty, 1)) + mask |= POLLIN | POLLRDNORM; + } if (tty->packet && tty->link->ctrl_status) mask |= POLLPRI | POLLIN | POLLRDNORM; if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) @@ -2129,9 +2452,9 @@ mask |= POLLHUP; if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) { if (MIN_CHAR(tty) && !TIME_CHAR(tty)) - tty->minimum_to_wake = MIN_CHAR(tty); + ldata->minimum_to_wake = MIN_CHAR(tty); else - tty->minimum_to_wake = 1; + ldata->minimum_to_wake = 1; } if (tty->ops->write && !tty_is_writelocked(tty) && tty_chars_in_buffer(tty) < WAKEUP_CHARS && @@ -2142,19 +2465,19 @@ static unsigned long inq_canon(struct n_tty_data *ldata) { - int nr, head, tail; + size_t nr, head, tail; - if (!ldata->canon_data) + if (ldata->canon_head == ldata->read_tail) return 0; head = ldata->canon_head; tail = ldata->read_tail; - nr = (head - tail) & (N_TTY_BUF_SIZE-1); + nr = head - tail; /* Skip EOF-chars.. */ while (head != tail) { - if (test_bit(tail, ldata->read_flags) && - ldata->read_buf[tail] == __DISABLED_CHAR) + if (test_bit(tail & (N_TTY_BUF_SIZE - 1), ldata->read_flags) && + read_buf(ldata, tail) == __DISABLED_CHAR) nr--; - tail = (tail+1) & (N_TTY_BUF_SIZE-1); + tail++; } return nr; } @@ -2169,16 +2492,30 @@ case TIOCOUTQ: return put_user(tty_chars_in_buffer(tty), (int __user *) arg); case TIOCINQ: - /* FIXME: Locking */ - retval = ldata->read_cnt; + down_write(&tty->termios_rwsem); if (L_ICANON(tty)) retval = inq_canon(ldata); + else + retval = read_cnt(ldata); + up_write(&tty->termios_rwsem); return put_user(retval, (unsigned int __user *) arg); default: return n_tty_ioctl_helper(tty, file, cmd, arg); } } +static void n_tty_fasync(struct tty_struct *tty, int on) +{ + struct n_tty_data *ldata = tty->disc_data; + + if (!waitqueue_active(&tty->read_wait)) { + if (on) + ldata->minimum_to_wake = 1; + else if (!tty->fasync) + ldata->minimum_to_wake = N_TTY_BUF_SIZE; + } +} + struct tty_ldisc_ops tty_ldisc_N_TTY = { .magic = TTY_LDISC_MAGIC, .name = "n_tty", @@ -2192,7 +2529,9 @@ .set_termios = n_tty_set_termios, .poll = n_tty_poll, .receive_buf = n_tty_receive_buf, - .write_wakeup = n_tty_write_wakeup + .write_wakeup = n_tty_write_wakeup, + .fasync = n_tty_fasync, + .receive_buf2 = n_tty_receive_buf2, }; /**