diff options
Diffstat (limited to 'drivers/tty/n_tty.c')
| -rw-r--r-- | drivers/tty/n_tty.c | 2094 |
1 files changed, 1077 insertions, 1017 deletions
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index bdf0e6e89991..e6a0f5b40d0a 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-1.0+ /* * n_tty.c --- implements the N_TTY line discipline. * @@ -15,9 +16,6 @@ * This file also contains code originally written by Linus Torvalds, * Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994. * - * This file may be redistributed under the terms of the GNU General Public - * License. - * * Reduced memory usage for older ARM systems - Russell King. * * 2000/01/20 Fixed SMP locking on put_tty_queue using bits of @@ -30,32 +28,36 @@ * EAGAIN */ -#include <linux/types.h> -#include <linux/major.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/ctype.h> #include <linux/errno.h> -#include <linux/signal.h> +#include <linux/export.h> #include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/jiffies.h> +#include <linux/math.h> +#include <linux/poll.h> +#include <linux/ratelimit.h> #include <linux/sched.h> -#include <linux/interrupt.h> -#include <linux/tty.h> -#include <linux/timer.h> -#include <linux/ctype.h> -#include <linux/mm.h> -#include <linux/string.h> +#include <linux/signal.h> #include <linux/slab.h> -#include <linux/poll.h> -#include <linux/bitops.h> -#include <linux/audit.h> -#include <linux/file.h> +#include <linux/string.h> +#include <linux/tty.h> +#include <linux/types.h> #include <linux/uaccess.h> -#include <linux/module.h> -#include <linux/ratelimit.h> #include <linux/vmalloc.h> +#include "tty.h" -/* number of characters left in xmit buffer before select has we have room */ +/* + * Until this number of characters is queued in the xmit buffer, select will + * return "we have room for writes". + */ #define WAKEUP_CHARS 256 +#define N_TTY_BUF_SIZE 4096 + /* * This defines the low- and high-watermarks for throttling and * unthrottling the TTY driver. These watermarks are used for @@ -79,14 +81,6 @@ #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 { /* producer-published */ size_t read_head; @@ -99,7 +93,7 @@ struct n_tty_data { /* private to n_tty_receive_overrun (single-threaded) */ unsigned long overrun_time; - int num_overrun; + unsigned int num_overrun; /* non-atomic */ bool no_room; @@ -109,14 +103,17 @@ struct n_tty_data { unsigned char push:1; /* shared by producer and consumer */ - char read_buf[N_TTY_BUF_SIZE]; + u8 read_buf[N_TTY_BUF_SIZE]; DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE); - unsigned char echo_buf[N_TTY_BUF_SIZE]; + u8 echo_buf[N_TTY_BUF_SIZE]; /* consumer-published */ size_t read_tail; size_t line_start; + /* # of chars looked ahead (to find software flow control chars) */ + size_t lookahead_count; + /* protected by output lock */ unsigned int column; unsigned int canon_column; @@ -126,72 +123,80 @@ struct n_tty_data { struct mutex output_lock; }; +#define MASK(x) ((x) & (N_TTY_BUF_SIZE - 1)) + 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) +static inline u8 read_buf(struct n_tty_data *ldata, size_t i) { - return ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; + return ldata->read_buf[MASK(i)]; } -static inline unsigned char *read_buf_addr(struct n_tty_data *ldata, size_t i) +static inline u8 *read_buf_addr(struct n_tty_data *ldata, size_t i) { - return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; + return &ldata->read_buf[MASK(i)]; } -static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i) +static inline u8 echo_buf(struct n_tty_data *ldata, size_t i) { - return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; + smp_rmb(); /* Matches smp_wmb() in add_echo_byte(). */ + return ldata->echo_buf[MASK(i)]; } -static inline unsigned char *echo_buf_addr(struct n_tty_data *ldata, size_t i) +static inline u8 *echo_buf_addr(struct n_tty_data *ldata, size_t i) { - return &ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; + return &ldata->echo_buf[MASK(i)]; } -static int tty_copy_to_user(struct tty_struct *tty, void __user *to, - size_t tail, size_t n) +/* If we are not echoing the data, perhaps this is a secret so erase it */ +static void zero_buffer(const struct tty_struct *tty, u8 *buffer, size_t size) +{ + if (L_ICANON(tty) && !L_ECHO(tty)) + memset(buffer, 0, size); +} + +static void tty_copy(const struct tty_struct *tty, void *to, size_t tail, + size_t n) { struct n_tty_data *ldata = tty->disc_data; size_t size = N_TTY_BUF_SIZE - tail; - const void *from = read_buf_addr(ldata, tail); - int uncopied; + void *from = read_buf_addr(ldata, tail); if (n > size) { tty_audit_add_data(tty, from, size); - uncopied = copy_to_user(to, from, size); - if (uncopied) - return uncopied; + memcpy(to, from, size); + zero_buffer(tty, from, size); to += size; n -= size; from = ldata->read_buf; } tty_audit_add_data(tty, from, n); - return copy_to_user(to, from, n); + memcpy(to, from, n); + zero_buffer(tty, from, n); } /** - * n_tty_kick_worker - start input worker (if required) - * @tty: terminal + * n_tty_kick_worker - start input worker (if required) + * @tty: terminal * - * Re-schedules the flip buffer work if it may have stopped + * 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 + * Locking: + * * Caller holds exclusive %termios_rwsem, or + * * n_tty_read()/consumer path: + * holds non-exclusive %termios_rwsem */ - -static void n_tty_kick_worker(struct tty_struct *tty) +static void n_tty_kick_worker(const struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; /* Did the input worker stop? Restart it */ - if (unlikely(ldata->no_room)) { - ldata->no_room = 0; + if (unlikely(READ_ONCE(ldata->no_room))) { + WRITE_ONCE(ldata->no_room, 0); WARN_RATELIMIT(tty->port->itty == NULL, "scheduling with invalid itty\n"); @@ -205,27 +210,21 @@ static void n_tty_kick_worker(struct tty_struct *tty) } } -static ssize_t chars_in_buffer(struct tty_struct *tty) +static ssize_t chars_in_buffer(const struct tty_struct *tty) { - struct n_tty_data *ldata = tty->disc_data; - ssize_t n = 0; + const struct n_tty_data *ldata = tty->disc_data; + size_t head = ldata->icanon ? ldata->canon_head : ldata->commit_head; - if (!ldata->icanon) - n = ldata->commit_head - ldata->read_tail; - else - n = ldata->canon_head - ldata->read_tail; - return n; + return head - ldata->read_tail; } /** - * n_tty_write_wakeup - asynchronous I/O notifier - * @tty: tty device + * 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 + * 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) { clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); @@ -244,15 +243,12 @@ static void n_tty_check_throttle(struct tty_struct *tty) if (ldata->icanon && ldata->canon_head == ldata->read_tail) return; - while (1) { - int throttled; + do { 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; - } + } while (!tty_throttle_safe(tty)); + __tty_set_flow_change(tty, 0); } @@ -274,147 +270,136 @@ static void n_tty_check_unthrottle(struct tty_struct *tty) * we won't get any more characters. */ - while (1) { - int unthrottled; + do { tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) break; + n_tty_kick_worker(tty); - unthrottled = tty_unthrottle_safe(tty); - if (!unthrottled) - break; - } + } while (!tty_unthrottle_safe(tty)); + __tty_set_flow_change(tty, 0); } /** - * put_tty_queue - add character to tty - * @c: character - * @ldata: n_tty data + * put_tty_queue - add character to tty + * @c: character + * @ldata: n_tty data * - * Add a character to the tty read_buf queue. + * Add a character to the tty read_buf queue. * - * n_tty_receive_buf()/producer path: - * caller holds non-exclusive termios_rwsem + * Locking: + * * n_tty_receive_buf()/producer path: + * caller holds non-exclusive %termios_rwsem */ - -static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata) +static inline void put_tty_queue(u8 c, struct n_tty_data *ldata) { *read_buf_addr(ldata, ldata->read_head) = c; ldata->read_head++; } /** - * reset_buffer_flags - reset buffer state - * @tty: terminal to reset + * reset_buffer_flags - reset buffer state + * @ldata: line disc data to reset * - * Reset the read buffer counters and clear the flags. - * Called from n_tty_open() and n_tty_flush_buffer(). + * Reset the read buffer counters and clear the flags. Called from + * n_tty_open() and n_tty_flush_buffer(). * - * Locking: caller holds exclusive termios_rwsem - * (or locking is not required) + * Locking: + * * caller holds exclusive %termios_rwsem, or + * * (locking is not required) */ - static void reset_buffer_flags(struct n_tty_data *ldata) { 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; ldata->erasing = 0; bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); ldata->push = 0; + + ldata->lookahead_count = 0; } static void n_tty_packet_mode_flush(struct tty_struct *tty) { - unsigned long flags; - - if (tty->link->packet) { - spin_lock_irqsave(&tty->ctrl_lock, flags); - tty->ctrl_status |= TIOCPKT_FLUSHREAD; - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (tty->link->ctrl.packet) { + scoped_guard(spinlock_irqsave, &tty->ctrl.lock) + tty->ctrl.pktstatus |= TIOCPKT_FLUSHREAD; wake_up_interruptible(&tty->link->read_wait); } } /** - * n_tty_flush_buffer - clean input queue - * @tty: terminal device + * n_tty_flush_buffer - clean input queue + * @tty: terminal device * - * Flush the input buffer. Called when the tty layer wants the - * buffer flushed (eg at hangup) or when the N_TTY line discipline - * internally has to clean the pending queue (for example some signals). + * Flush the input buffer. Called when the tty layer wants the buffer flushed + * (eg at hangup) or when the %N_TTY line discipline internally has to clean + * the pending queue (for example some signals). * - * Holds termios_rwsem to exclude producer/consumer while - * buffer indices are reset. + * Holds %termios_rwsem to exclude producer/consumer while buffer indices are + * reset. * - * Locking: ctrl_lock, exclusive termios_rwsem + * Locking: %ctrl.lock, exclusive %termios_rwsem */ - static void n_tty_flush_buffer(struct tty_struct *tty) { - down_write(&tty->termios_rwsem); + guard(rwsem_write)(&tty->termios_rwsem); reset_buffer_flags(tty->disc_data); n_tty_kick_worker(tty); if (tty->link) n_tty_packet_mode_flush(tty); - up_write(&tty->termios_rwsem); } /** - * is_utf8_continuation - utf8 multibyte check - * @c: byte to check + * is_utf8_continuation - utf8 multibyte check + * @c: byte to check * - * Returns true if the utf8 character 'c' is a multibyte continuation - * character. We use this to correctly compute the on screen size - * of the character when printing + * Returns: true if the utf8 character @c is a multibyte continuation + * character. We use this to correctly compute the on-screen size of the + * character when printing. */ - -static inline int is_utf8_continuation(unsigned char c) +static inline int is_utf8_continuation(u8 c) { return (c & 0xc0) == 0x80; } /** - * is_continuation - multibyte check - * @c: byte to check + * is_continuation - multibyte check + * @c: byte to check + * @tty: terminal device * - * Returns true if the utf8 character 'c' is a multibyte continuation - * character and the terminal is in unicode mode. + * Returns: true if the utf8 character @c is a multibyte continuation character + * and the terminal is in unicode mode. */ - -static inline int is_continuation(unsigned char c, struct tty_struct *tty) +static inline int is_continuation(u8 c, const struct tty_struct *tty) { return I_IUTF8(tty) && is_utf8_continuation(c); } /** - * do_output_char - output one character - * @c: character (or partial unicode symbol) - * @tty: terminal device - * @space: space available in tty driver write buffer + * 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. + * 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. * - * 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. * - * Returns the number of bytes of buffer space used or -1 if - * no space left. + * 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 + * Locking: should be called under the %output_lock to protect the column state + * and space left in the buffer. */ - -static int do_output_char(unsigned char c, struct tty_struct *tty, int space) +static int do_output_char(u8 c, struct tty_struct *tty, int space) { struct n_tty_data *ldata = tty->disc_data; int spaces; @@ -476,93 +461,84 @@ static int do_output_char(unsigned char c, struct tty_struct *tty, int space) } /** - * process_output - output post processor - * @c: character (or partial unicode symbol) - * @tty: terminal device + * 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. + * Output one character with OPOST processing. * - * Locking: output_lock to protect column state and space left - * (also, this is called from n_tty_write under the - * tty layer write lock) + * 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) +static int process_output(u8 c, struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - int space, retval; - mutex_lock(&ldata->output_lock); - - space = tty_write_room(tty); - retval = do_output_char(c, tty, space); + guard(mutex)(&ldata->output_lock); - mutex_unlock(&ldata->output_lock); - if (retval < 0) + if (do_output_char(c, tty, tty_write_room(tty)) < 0) return -1; - else - return 0; + + return 0; } /** - * process_output_block - block post processor - * @tty: terminal device - * @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. - * - * Locking: output_lock to protect column state and space left - * (also, this is called from n_tty_write under the - * tty layer write lock) + * process_output_block - block post processor + * @tty: terminal device + * @buf: character buffer + * @nr: number of bytes to output + * + * Output a block of characters with OPOST processing. + * + * 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. + * + * Returns: the number of characters output. + * + * 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 process_output_block(struct tty_struct *tty, - const unsigned char *buf, unsigned int nr) + const u8 *buf, unsigned int nr) { struct n_tty_data *ldata = tty->disc_data; - int space; - int i; - const unsigned char *cp; + unsigned int space, i; + const u8 *cp; - mutex_lock(&ldata->output_lock); + guard(mutex)(&ldata->output_lock); space = tty_write_room(tty); - if (!space) { - mutex_unlock(&ldata->output_lock); + if (space == 0) return 0; - } + if (nr > space) nr = space; for (i = 0, cp = buf; i < nr; i++, cp++) { - unsigned char c = *cp; + u8 c = *cp; switch (c) { case '\n': if (O_ONLRET(tty)) ldata->column = 0; if (O_ONLCR(tty)) - goto break_out; + goto do_write; ldata->canon_column = ldata->column; break; case '\r': if (O_ONOCR(tty) && ldata->column == 0) - goto break_out; + goto do_write; if (O_OCRNL(tty)) - goto break_out; + goto do_write; ldata->canon_column = ldata->column = 0; break; case '\t': - goto break_out; + goto do_write; case '\b': if (ldata->column > 0) ldata->column--; @@ -570,147 +546,152 @@ static ssize_t process_output_block(struct tty_struct *tty, default: if (!iscntrl(c)) { if (O_OLCUC(tty)) - goto break_out; + goto do_write; if (!is_continuation(c, tty)) ldata->column++; } break; } } -break_out: - i = tty->ops->write(tty, buf, i); +do_write: + return tty->ops->write(tty, buf, i); +} - mutex_unlock(&ldata->output_lock); - return i; +static int n_tty_process_echo_ops(struct tty_struct *tty, size_t *tail, + int space) +{ + struct n_tty_data *ldata = tty->disc_data; + u8 op; + + /* + * Since add_echo_byte() is called without holding output_lock, we + * might see only portion of multi-byte operation. + */ + if (MASK(ldata->echo_commit) == MASK(*tail + 1)) + return -ENODATA; + + /* + * 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. + */ + op = echo_buf(ldata, *tail + 1); + + switch (op) { + case ECHO_OP_ERASE_TAB: { + unsigned int num_chars, num_bs; + + if (MASK(ldata->echo_commit) == MASK(*tail + 2)) + return -ENODATA; + + num_chars = echo_buf(ldata, *tail + 2); + + /* + * 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 += ldata->canon_column; + num_bs = 8 - (num_chars & 7); + + if (num_bs > space) + return -ENOSPC; + + space -= num_bs; + while (num_bs--) { + tty_put_char(tty, '\b'); + if (ldata->column > 0) + ldata->column--; + } + *tail += 3; + break; + } + case ECHO_OP_SET_CANON_COL: + ldata->canon_column = ldata->column; + *tail += 2; + break; + + case ECHO_OP_MOVE_BACK_COL: + if (ldata->column > 0) + ldata->column--; + *tail += 2; + break; + + case ECHO_OP_START: + /* This is an escaped echo op start code */ + if (!space) + return -ENOSPC; + + tty_put_char(tty, ECHO_OP_START); + ldata->column++; + space--; + *tail += 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) + return -ENOSPC; + + tty_put_char(tty, '^'); + tty_put_char(tty, op ^ 0100); + ldata->column += 2; + space -= 2; + *tail += 2; + break; + } + + return space; } /** - * process_echoes - write pending echo characters - * @tty: terminal device + * __process_echoes - write pending echo characters + * @tty: terminal device * - * Write previously buffered echo (and other ldisc-generated) - * characters to the tty. + * 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. + * 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. + * 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. + * 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: callers must hold output_lock + * Locking: callers must hold %output_lock. */ - static size_t __process_echoes(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; - int space, old_space; + unsigned int space, old_space; size_t tail; - unsigned char c; + u8 c; old_space = space = tty_write_room(tty); tail = ldata->echo_tail; - while (ldata->echo_commit != tail) { + while (MASK(ldata->echo_commit) != MASK(tail)) { c = echo_buf(ldata, tail); if (c == ECHO_OP_START) { - unsigned char op; - 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. - */ - op = echo_buf(ldata, tail + 1); - - switch (op) { - unsigned int num_chars, num_bs; - - case ECHO_OP_ERASE_TAB: - num_chars = echo_buf(ldata, tail + 2); - - /* - * 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 += ldata->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 (ldata->column > 0) - ldata->column--; - } - tail += 3; - break; - - case ECHO_OP_SET_CANON_COL: - ldata->canon_column = ldata->column; - tail += 2; - break; - - case ECHO_OP_MOVE_BACK_COL: - if (ldata->column > 0) - ldata->column--; - tail += 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); - ldata->column++; - space--; - tail += 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); - ldata->column += 2; - space -= 2; - tail += 2; - } - - if (no_space_left) + int ret = n_tty_process_echo_ops(tty, &tail, space); + if (ret == -ENODATA) + goto not_yet_stored; + if (ret < 0) break; + space = ret; } else { if (O_OPOST(tty)) { int retval = do_output_char(c, tty, space); @@ -730,7 +711,8 @@ static size_t __process_echoes(struct tty_struct *tty) /* 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) { + while (ldata->echo_commit > tail && + 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; @@ -740,6 +722,7 @@ static size_t __process_echoes(struct tty_struct *tty) tail++; } + not_yet_stored: ldata->echo_tail = tail; return old_space - space; } @@ -750,21 +733,22 @@ static void commit_echoes(struct tty_struct *tty) size_t nr, old, echoed; size_t head; - head = ldata->echo_head; - ldata->echo_mark = head; - old = ldata->echo_commit - ldata->echo_tail; + scoped_guard(mutex, &ldata->output_lock) { + 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; + /* + * 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); + ldata->echo_commit = head; + echoed = __process_echoes(tty); + } if (echoed && tty->ops->flush_chars) tty->ops->flush_chars(tty); @@ -778,10 +762,10 @@ static void process_echoes(struct tty_struct *tty) 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); + scoped_guard(mutex, &ldata->output_lock) { + ldata->echo_commit = ldata->echo_mark; + echoed = __process_echoes(tty); + } if (echoed && tty->ops->flush_chars) tty->ops->flush_chars(tty); @@ -796,32 +780,31 @@ static void flush_echoes(struct tty_struct *tty) ldata->echo_commit == ldata->echo_head) return; - mutex_lock(&ldata->output_lock); + guard(mutex)(&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_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. + * Add a character or operation byte to the echo buffer. */ - -static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata) +static inline void add_echo_byte(u8 c, struct n_tty_data *ldata) { - *echo_buf_addr(ldata, ldata->echo_head++) = c; + *echo_buf_addr(ldata, ldata->echo_head) = c; + smp_wmb(); /* Matches smp_rmb() in echo_buf(). */ + ldata->echo_head++; } /** - * echo_move_back_col - add operation to move back a column - * @ldata: n_tty data + * echo_move_back_col - add operation to move back a column + * @ldata: n_tty data * - * Add an operation to the echo buffer to move back one column. + * Add an operation to the echo buffer to move back one column. */ - static void echo_move_back_col(struct n_tty_data *ldata) { add_echo_byte(ECHO_OP_START, ldata); @@ -829,13 +812,12 @@ static void echo_move_back_col(struct n_tty_data *ldata) } /** - * echo_set_canon_col - add operation to set the canon column - * @ldata: n_tty data + * echo_set_canon_col - add operation to set the canon column + * @ldata: n_tty data * - * Add an operation to the echo buffer to set the canon column - * to the current column. + * Add an operation to the echo buffer to set the canon column to the current + * column. */ - static void echo_set_canon_col(struct n_tty_data *ldata) { add_echo_byte(ECHO_OP_START, ldata); @@ -843,20 +825,18 @@ static void echo_set_canon_col(struct n_tty_data *ldata) } /** - * 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 - * @ldata: n_tty data - * - * 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. + * 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 + * @ldata: n_tty data + * + * 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. */ - static void echo_erase_tab(unsigned int num_chars, int after_tab, struct n_tty_data *ldata) { @@ -874,17 +854,16 @@ static void echo_erase_tab(unsigned int num_chars, int after_tab, } /** - * echo_char_raw - echo a character raw - * @c: unicode byte to echo - * @tty: terminal device + * echo_char_raw - echo a character raw + * @c: unicode byte to echo + * @ldata: line disc data * - * 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. + * Echo user input back onto the screen. This must be called only when + * L_ECHO(tty) is true. Called from the &tty_driver.receive_buf() path. * - * This variant does not treat control characters specially. + * This variant does not treat control characters specially. */ - -static void echo_char_raw(unsigned char c, struct n_tty_data *ldata) +static void echo_char_raw(u8 c, struct n_tty_data *ldata) { if (c == ECHO_OP_START) { add_echo_byte(ECHO_OP_START, ldata); @@ -895,18 +874,17 @@ static void echo_char_raw(unsigned char c, struct n_tty_data *ldata) } /** - * echo_char - echo a character - * @c: unicode byte to echo - * @tty: terminal device + * 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. + * Echo user input back onto the screen. This must be called only when + * L_ECHO(tty) is true. Called from the &tty_driver.receive_buf() path. * - * This variant tags control characters to be echoed as "^X" - * (where X is the letter representing the control char). + * This variant tags control characters to be echoed as "^X" (where X is the + * letter representing the control char). */ - -static void echo_char(unsigned char c, struct tty_struct *tty) +static void echo_char(u8 c, const struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; @@ -921,10 +899,9 @@ static void echo_char(unsigned char c, struct tty_struct *tty) } /** - * finish_erasing - complete erase - * @ldata: n_tty data + * finish_erasing - complete erase + * @ldata: n_tty data */ - static inline void finish_erasing(struct n_tty_data *ldata) { if (ldata->erasing) { @@ -934,19 +911,18 @@ static inline void finish_erasing(struct n_tty_data *ldata) } /** - * eraser - handle erase function - * @c: character input - * @tty: terminal device + * eraser - handle erase function + * @c: character input + * @tty: terminal device * - * 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. + * 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. * - * n_tty_receive_buf()/producer path: - * caller holds non-exclusive termios_rwsem + * Locking: n_tty_receive_buf()/producer path: + * caller holds non-exclusive %termios_rwsem */ - -static void eraser(unsigned char c, struct tty_struct *tty) +static void eraser(u8 c, const struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; enum { ERASE, WERASE, KILL } kill_type; @@ -980,14 +956,15 @@ static void eraser(unsigned char c, struct tty_struct *tty) } seen_alnums = 0; - while (ldata->read_head != ldata->canon_head) { + while (MASK(ldata->read_head) != MASK(ldata->canon_head)) { head = ldata->read_head; /* erase a single possibly multibyte character */ do { head--; c = read_buf(ldata, head); - } while (is_continuation(c, tty) && head != ldata->canon_head); + } while (is_continuation(c, tty) && + MASK(head) != MASK(ldata->canon_head)); /* do not partially erase */ if (is_continuation(c, tty)) @@ -1029,7 +1006,7 @@ static void eraser(unsigned char c, struct tty_struct *tty) * This info is used to go back the correct * number of columns. */ - while (tail != ldata->canon_head) { + while (MASK(tail) != MASK(ldata->canon_head)) { tail--; c = read_buf(ldata, tail); if (c == '\t') { @@ -1063,20 +1040,6 @@ static void eraser(unsigned char c, struct tty_struct *tty) finish_erasing(ldata); } -/** - * isig - handle the ISIG optio - * @sig: signal - * @tty: terminal - * - * 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 void __isig(int sig, struct tty_struct *tty) { @@ -1087,6 +1050,20 @@ static void __isig(int sig, struct tty_struct *tty) } } +/** + * isig - handle the ISIG optio + * @sig: signal + * @tty: terminal + * + * Called when a signal is being sent due to terminal input. Called from the + * &tty_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 void isig(int sig, struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; @@ -1094,18 +1071,19 @@ static void isig(int sig, struct tty_struct *tty) if (L_NOFLSH(tty)) { /* signal only */ __isig(sig, tty); + return; + } - } else { /* signal and flush */ - up_read(&tty->termios_rwsem); - down_write(&tty->termios_rwsem); - + /* signal and flush */ + up_read(&tty->termios_rwsem); + scoped_guard(rwsem_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); + scoped_guard(mutex, &ldata->output_lock) { + ldata->echo_head = ldata->echo_tail = 0; + ldata->echo_mark = ldata->echo_commit = 0; + } /* clear output buffer */ tty_driver_flush_buffer(tty); @@ -1116,25 +1094,22 @@ static void isig(int sig, struct tty_struct *tty) /* notify pty master of flush */ if (tty->link) n_tty_packet_mode_flush(tty); - - up_write(&tty->termios_rwsem); - down_read(&tty->termios_rwsem); } + down_read(&tty->termios_rwsem); } /** - * n_tty_receive_break - handle break - * @tty: terminal + * n_tty_receive_break - handle break + * @tty: terminal * - * An RS232 break event has been hit in the incoming bitstream. This - * can cause a variety of events depending upon the termios settings. + * An RS232 break event has been hit in the incoming bitstream. This can cause + * a variety of events depending upon the termios settings. * - * n_tty_receive_buf()/producer path: - * caller holds non-exclusive termios_rwsem + * Locking: n_tty_receive_buf()/producer path: + * caller holds non-exclusive termios_rwsem * - * Note: may get exclusive termios_rwsem if flushing input buffer + * Note: may get exclusive %termios_rwsem if flushing input buffer */ - static void n_tty_receive_break(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; @@ -1153,43 +1128,40 @@ static void n_tty_receive_break(struct tty_struct *tty) } /** - * n_tty_receive_overrun - handle overrun reporting - * @tty: terminal + * n_tty_receive_overrun - handle overrun reporting + * @tty: terminal * - * Data arrived faster than we could process it. While the tty - * driver has flagged this the bits that were missed are gone - * forever. + * Data arrived faster than we could process it. While the tty driver has + * flagged this the bits that were missed are gone forever. * - * Called from the receive_buf path so single threaded. Does not - * need locking as num_overrun and overrun_time are function - * private. + * Called from the receive_buf path so single threaded. Does not need locking + * as num_overrun and overrun_time are function private. */ - -static void n_tty_receive_overrun(struct tty_struct *tty) +static void n_tty_receive_overrun(const struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; ldata->num_overrun++; - if (time_after(jiffies, ldata->overrun_time + HZ) || - time_after(ldata->overrun_time, jiffies)) { - tty_warn(tty, "%d input overrun(s)\n", ldata->num_overrun); + if (time_is_before_jiffies(ldata->overrun_time + HZ)) { + tty_warn(tty, "%u input overrun(s)\n", ldata->num_overrun); ldata->overrun_time = jiffies; ldata->num_overrun = 0; } } /** - * n_tty_receive_parity_error - error notifier - * @tty: terminal device - * @c: character + * n_tty_receive_parity_error - error notifier + * @tty: terminal device + * @c: character * - * Process a parity error and queue the right data to indicate - * the error case if necessary. + * Process a parity error and queue the right data to indicate the error case + * if necessary. * - * n_tty_receive_buf()/producer path: - * caller holds non-exclusive termios_rwsem + * Locking: n_tty_receive_buf()/producer path: + * caller holds non-exclusive %termios_rwsem */ -static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) +static void n_tty_receive_parity_error(const struct tty_struct *tty, + u8 c) { struct n_tty_data *ldata = tty->disc_data; @@ -1207,7 +1179,7 @@ static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) } static void -n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) +n_tty_receive_signal_char(struct tty_struct *tty, int signal, u8 c) { isig(signal, tty); if (I_IXON(tty)) @@ -1217,139 +1189,180 @@ n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) commit_echoes(tty); } else process_echoes(tty); - return; +} + +static bool n_tty_is_char_flow_ctrl(struct tty_struct *tty, u8 c) +{ + return c == START_CHAR(tty) || c == STOP_CHAR(tty); } /** - * n_tty_receive_char - perform processing - * @tty: terminal device - * @c: character + * n_tty_receive_char_flow_ctrl - receive flow control chars + * @tty: terminal device + * @c: character + * @lookahead_done: lookahead has processed this character already * - * Process an individual character of input received from the driver. - * This is serialized with respect to itself by the rules for the - * driver above. + * Receive and process flow control character actions. * - * n_tty_receive_buf()/producer path: - * caller holds non-exclusive termios_rwsem - * publishes canon_head if canonical mode is active + * In case lookahead for flow control chars already handled the character in + * advance to the normal receive, the actions are skipped during normal + * receive. * - * Returns 1 if LNEXT was received, else returns 0 + * Returns true if @c is consumed as flow-control character, the character + * must not be treated as normal character. */ +static bool n_tty_receive_char_flow_ctrl(struct tty_struct *tty, u8 c, + bool lookahead_done) +{ + if (!n_tty_is_char_flow_ctrl(tty, c)) + return false; + + if (lookahead_done) + return true; + + if (c == START_CHAR(tty)) { + start_tty(tty); + process_echoes(tty); + return true; + } + + /* STOP_CHAR */ + stop_tty(tty); + return true; +} -static int -n_tty_receive_char_special(struct tty_struct *tty, unsigned char c) +static void n_tty_receive_handle_newline(struct tty_struct *tty, u8 c) { struct n_tty_data *ldata = tty->disc_data; - if (I_IXON(tty)) { - if (c == START_CHAR(tty)) { - start_tty(tty); - process_echoes(tty); - return 0; + set_bit(MASK(ldata->read_head), ldata->read_flags); + put_tty_queue(c, ldata); + smp_store_release(&ldata->canon_head, ldata->read_head); + kill_fasync(&tty->fasync, SIGIO, POLL_IN); + wake_up_interruptible_poll(&tty->read_wait, EPOLLIN | EPOLLRDNORM); +} + +static bool n_tty_receive_char_canon(struct tty_struct *tty, u8 c) +{ + struct n_tty_data *ldata = tty->disc_data; + + if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) || + (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) { + eraser(c, tty); + commit_echoes(tty); + + return true; + } + + if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { + ldata->lnext = 1; + if (L_ECHO(tty)) { + finish_erasing(ldata); + if (L_ECHOCTL(tty)) { + echo_char_raw('^', ldata); + echo_char_raw('\b', ldata); + commit_echoes(tty); + } } - if (c == STOP_CHAR(tty)) { - stop_tty(tty); - return 0; + + return true; + } + + 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 (MASK(tail) != MASK(ldata->read_head)) { + echo_char(read_buf(ldata, tail), tty); + tail++; } + commit_echoes(tty); + + return true; + } + + if (c == '\n') { + if (L_ECHO(tty) || L_ECHONL(tty)) { + echo_char_raw('\n', ldata); + commit_echoes(tty); + } + n_tty_receive_handle_newline(tty, c); + + return true; + } + + if (c == EOF_CHAR(tty)) { + c = __DISABLED_CHAR; + n_tty_receive_handle_newline(tty, c); + + return true; + } + + if ((c == EOL_CHAR(tty)) || + (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { + /* + * XXX are EOL_CHAR and EOL2_CHAR echoed?!? + */ + if (L_ECHO(tty)) { + /* 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); + } + /* + * XXX does PARMRK doubling happen for + * EOL_CHAR and EOL2_CHAR? + */ + if (c == '\377' && I_PARMRK(tty)) + put_tty_queue(c, ldata); + + n_tty_receive_handle_newline(tty, c); + + return true; } + return false; +} + +static void n_tty_receive_char_special(struct tty_struct *tty, u8 c, + bool lookahead_done) +{ + struct n_tty_data *ldata = tty->disc_data; + + if (I_IXON(tty) && n_tty_receive_char_flow_ctrl(tty, c, lookahead_done)) + return; + if (L_ISIG(tty)) { if (c == INTR_CHAR(tty)) { n_tty_receive_signal_char(tty, SIGINT, c); - return 0; + return; } else if (c == QUIT_CHAR(tty)) { n_tty_receive_signal_char(tty, SIGQUIT, c); - return 0; + return; } else if (c == SUSP_CHAR(tty)) { n_tty_receive_signal_char(tty, SIGTSTP, c); - return 0; + return; } } - if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { + if (tty->flow.stopped && !tty->flow.tco_stopped && I_IXON(tty) && I_IXANY(tty)) { start_tty(tty); process_echoes(tty); } if (c == '\r') { if (I_IGNCR(tty)) - return 0; + return; if (I_ICRNL(tty)) c = '\n'; } else if (c == '\n' && I_INLCR(tty)) c = '\r'; - if (ldata->icanon) { - if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) || - (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) { - eraser(c, tty); - commit_echoes(tty); - return 0; - } - if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { - ldata->lnext = 1; - if (L_ECHO(tty)) { - finish_erasing(ldata); - if (L_ECHOCTL(tty)) { - echo_char_raw('^', ldata); - echo_char_raw('\b', ldata); - commit_echoes(tty); - } - } - return 1; - } - 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(read_buf(ldata, tail), tty); - tail++; - } - commit_echoes(tty); - return 0; - } - if (c == '\n') { - if (L_ECHO(tty) || L_ECHONL(tty)) { - echo_char_raw('\n', ldata); - commit_echoes(tty); - } - goto handle_newline; - } - if (c == EOF_CHAR(tty)) { - c = __DISABLED_CHAR; - goto handle_newline; - } - if ((c == EOL_CHAR(tty)) || - (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { - /* - * XXX are EOL_CHAR and EOL2_CHAR echoed?!? - */ - if (L_ECHO(tty)) { - /* 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); - } - /* - * XXX does PARMRK doubling happen for - * EOL_CHAR and EOL2_CHAR? - */ - if (c == (unsigned char) '\377' && I_PARMRK(tty)) - put_tty_queue(c, ldata); - -handle_newline: - 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); - wake_up_interruptible_poll(&tty->read_wait, POLLIN); - return 0; - } - } + if (ldata->icanon && n_tty_receive_char_canon(tty, c)) + return; if (L_ECHO(tty)) { finish_erasing(ldata); @@ -1365,19 +1378,29 @@ handle_newline: } /* PARMRK doubling check */ - if (c == (unsigned char) '\377' && I_PARMRK(tty)) + if (c == '\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) +/** + * n_tty_receive_char - perform processing + * @tty: terminal device + * @c: character + * + * Process an individual character of input received from the driver. This is + * serialized with respect to itself by the rules for the driver above. + * + * Locking: n_tty_receive_buf()/producer path: + * caller holds non-exclusive %termios_rwsem + * publishes canon_head if canonical mode is active + */ +static void n_tty_receive_char(struct tty_struct *tty, u8 c) { struct n_tty_data *ldata = tty->disc_data; - if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { + if (tty->flow.stopped && !tty->flow.tco_stopped && I_IXON(tty) && I_IXANY(tty)) { start_tty(tty); process_echoes(tty); } @@ -1390,37 +1413,13 @@ n_tty_receive_char_inline(struct tty_struct *tty, unsigned char c) commit_echoes(tty); } /* PARMRK doubling check */ - if (c == (unsigned char) '\377' && I_PARMRK(tty)) + if (c == '\377' && I_PARMRK(tty)) put_tty_queue(c, ldata); put_tty_queue(c, ldata); } -static void n_tty_receive_char(struct tty_struct *tty, unsigned char c) -{ - n_tty_receive_char_inline(tty, c); -} - -static inline void -n_tty_receive_char_fast(struct tty_struct *tty, unsigned char c) -{ - struct n_tty_data *ldata = tty->disc_data; - - 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) +static void n_tty_receive_char_closing(struct tty_struct *tty, u8 c, + bool lookahead_done) { if (I_ISTRIP(tty)) c &= 0x7f; @@ -1428,12 +1427,10 @@ static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c) 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))) { + if (!n_tty_receive_char_flow_ctrl(tty, c, lookahead_done) && + tty->flow.stopped && !tty->flow.tco_stopped && I_IXANY(tty) && + c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && + c != SUSP_CHAR(tty)) { start_tty(tty); process_echoes(tty); } @@ -1441,7 +1438,7 @@ static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c) } static void -n_tty_receive_char_flagged(struct tty_struct *tty, unsigned char c, char flag) +n_tty_receive_char_flagged(struct tty_struct *tty, u8 c, u8 flag) { switch (flag) { case TTY_BREAK: @@ -1455,13 +1452,13 @@ n_tty_receive_char_flagged(struct tty_struct *tty, unsigned char c, char flag) n_tty_receive_overrun(tty); break; default: - tty_err(tty, "unknown flag %d\n", flag); + tty_err(tty, "unknown flag %u\n", flag); break; } } static void -n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag) +n_tty_receive_char_lnext(struct tty_struct *tty, u8 c, u8 flag) { struct n_tty_data *ldata = tty->disc_data; @@ -1476,32 +1473,52 @@ n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag) n_tty_receive_char_flagged(tty, c, flag); } +/* Caller must ensure count > 0 */ +static void n_tty_lookahead_flow_ctrl(struct tty_struct *tty, const u8 *cp, + const u8 *fp, size_t count) +{ + struct n_tty_data *ldata = tty->disc_data; + u8 flag = TTY_NORMAL; + + ldata->lookahead_count += count; + + if (!I_IXON(tty)) + return; + + while (count--) { + if (fp) + flag = *fp++; + if (likely(flag == TTY_NORMAL)) + n_tty_receive_char_flow_ctrl(tty, *cp, false); + cp++; + } +} + static void -n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +n_tty_receive_buf_real_raw(const struct tty_struct *tty, const u8 *cp, + size_t 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; + /* handle buffer wrap-around by a loop */ + for (unsigned int i = 0; i < 2; i++) { + size_t head = MASK(ldata->read_head); + size_t n = min(count, N_TTY_BUF_SIZE - head); + + memcpy(read_buf_addr(ldata, head), cp, 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; + ldata->read_head += n; + cp += n; + count -= n; + } } static void -n_tty_receive_buf_raw(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +n_tty_receive_buf_raw(struct tty_struct *tty, const u8 *cp, const u8 *fp, + size_t count) { struct n_tty_data *ldata = tty->disc_data; - char flag = TTY_NORMAL; + u8 flag = TTY_NORMAL; while (count--) { if (fp) @@ -1514,111 +1531,97 @@ n_tty_receive_buf_raw(struct tty_struct *tty, const unsigned char *cp, } static void -n_tty_receive_buf_closing(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +n_tty_receive_buf_closing(struct tty_struct *tty, const u8 *cp, const u8 *fp, + size_t count, bool lookahead_done) { - char flag = TTY_NORMAL; + u8 flag = TTY_NORMAL; while (count--) { if (fp) flag = *fp++; if (likely(flag == TTY_NORMAL)) - n_tty_receive_char_closing(tty, *cp++); + n_tty_receive_char_closing(tty, *cp++, lookahead_done); } } -static void -n_tty_receive_buf_standard(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +static void n_tty_receive_buf_standard(struct tty_struct *tty, const u8 *cp, + const u8 *fp, size_t count, + bool lookahead_done) { struct n_tty_data *ldata = tty->disc_data; - char flag = TTY_NORMAL; + u8 flag = TTY_NORMAL; while (count--) { + u8 c = *cp++; + 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; + if (ldata->lnext) { + n_tty_receive_char_lnext(tty, c, flag); + continue; + } - 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); + if (unlikely(flag != TTY_NORMAL)) { + n_tty_receive_char_flagged(tty, c, flag); + continue; + } + + 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_special(tty, c, lookahead_done); + else + n_tty_receive_char(tty, c); } } -static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +static void __receive_buf(struct tty_struct *tty, const u8 *cp, const u8 *fp, + size_t count) { struct n_tty_data *ldata = tty->disc_data; bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); + size_t la_count = min(ldata->lookahead_count, count); if (ldata->real_raw) - n_tty_receive_buf_real_raw(tty, cp, fp, count); + n_tty_receive_buf_real_raw(tty, cp, 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; - + else if (tty->closing && !L_EXTPROC(tty)) { + if (la_count > 0) { + n_tty_receive_buf_closing(tty, cp, fp, la_count, true); + cp += la_count; if (fp) - flag = *fp++; - n_tty_receive_char_lnext(tty, *cp++, flag); - count--; + fp += la_count; + count -= la_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); + if (count > 0) + n_tty_receive_buf_closing(tty, cp, fp, count, false); + } else { + if (la_count > 0) { + n_tty_receive_buf_standard(tty, cp, fp, la_count, true); + cp += la_count; + if (fp) + fp += la_count; + count -= la_count; + } + if (count > 0) + n_tty_receive_buf_standard(tty, cp, fp, count, false); flush_echoes(tty); if (tty->ops->flush_chars) tty->ops->flush_chars(tty); } + ldata->lookahead_count -= la_count; + if (ldata->icanon && !L_EXTPROC(tty)) return; @@ -1627,53 +1630,54 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, if (read_cnt(ldata)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); - wake_up_interruptible_poll(&tty->read_wait, POLLIN); + wake_up_interruptible_poll(&tty->read_wait, EPOLLIN | EPOLLRDNORM); } } /** - * 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 + * 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 + * @flow: enable flow control + * + * 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. + * + * Locking: 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) +static size_t +n_tty_receive_buf_common(struct tty_struct *tty, const u8 *cp, const u8 *fp, + size_t count, bool flow) { struct n_tty_data *ldata = tty->disc_data; - int room, n, rcvd = 0, overflow; + size_t n, rcvd = 0; + int room, overflow; - down_read(&tty->termios_rwsem); + guard(rwsem_read)(&tty->termios_rwsem); - while (1) { + do { /* * When PARMRK is set, each input char may take up to 3 chars * in the read buf; reduce the buffer space avail by 3x @@ -1691,18 +1695,18 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, room = N_TTY_BUF_SIZE - (ldata->read_head - tail); if (I_PARMRK(tty)) - room = (room + 2) / 3; + room = DIV_ROUND_UP(room, 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; + WRITE_ONCE(ldata->no_room, flow && !room); } else overflow = 0; - n = min(count, room); + n = min_t(size_t, count, room); if (!n) break; @@ -1715,7 +1719,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, fp += n; count -= n; rcvd += n; - } + } while (!test_bit(TTY_LDISC_CHANGING, &tty->flags)); tty->receive_room = room; @@ -1729,50 +1733,56 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, } else n_tty_check_throttle(tty); - up_read(&tty->termios_rwsem); + if (unlikely(ldata->no_room)) { + /* + * Barrier here is to ensure to read the latest read_tail in + * chars_in_buffer() and to make sure that read_tail is not loaded + * before ldata->no_room is set. + */ + smp_mb(); + if (!chars_in_buffer(tty)) + n_tty_kick_worker(tty); + } return rcvd; } -static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +static void n_tty_receive_buf(struct tty_struct *tty, const u8 *cp, + const u8 *fp, size_t count) { - n_tty_receive_buf_common(tty, cp, fp, count, 0); + n_tty_receive_buf_common(tty, cp, fp, count, false); } -static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +static size_t n_tty_receive_buf2(struct tty_struct *tty, const u8 *cp, + const u8 *fp, size_t count) { - return n_tty_receive_buf_common(tty, cp, fp, count, 1); + return n_tty_receive_buf_common(tty, cp, fp, count, true); } /** - * n_tty_set_termios - termios data changed - * @tty: terminal - * @old: previous data + * n_tty_set_termios - termios data changed + * @tty: terminal + * @old: previous data * - * Called by the tty layer when the user changes termios flags so - * that the line discipline can plan ahead. This function cannot sleep - * 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. + * Called by the tty layer when the user changes termios flags so that the line + * discipline can plan ahead. This function cannot sleep 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_rwsem + * Locking: Caller holds @tty->termios_rwsem */ - -static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) +static void n_tty_set_termios(struct tty_struct *tty, const struct ktermios *old) { struct n_tty_data *ldata = tty->disc_data; - if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) { + if (!old || (old->c_lflag ^ tty->termios.c_lflag) & (ICANON | EXTPROC)) { bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); 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); + set_bit(MASK(ldata->read_head - 1), ldata->read_flags); ldata->canon_head = ldata->read_head; ldata->push = 1; } @@ -1834,7 +1844,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) * 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) { + if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow.tco_stopped) { start_tty(tty); process_echoes(tty); } @@ -1845,15 +1855,13 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) } /** - * n_tty_close - close the ldisc for this tty - * @tty: device + * n_tty_close - close the ldisc for this tty + * @tty: device * - * Called from the terminal layer when this line discipline is - * being shut down, either because of a close or becsuse of a - * discipline change. The function will not be called while other - * ldisc methods are in progress. + * Called from the terminal layer when this line discipline is being shut down, + * either because of a close or becsuse of a discipline change. The function + * will not be called while other ldisc methods are in progress. */ - static void n_tty_close(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; @@ -1861,54 +1869,44 @@ static void n_tty_close(struct tty_struct *tty) if (tty->link) n_tty_packet_mode_flush(tty); + guard(rwsem_write)(&tty->termios_rwsem); vfree(ldata); tty->disc_data = NULL; } /** - * n_tty_open - open an ldisc - * @tty: terminal to open + * n_tty_open - open an ldisc + * @tty: terminal to open * - * Called when this line discipline is being attached to the - * terminal device. Can sleep. Called serialized so that no - * other events will occur in parallel. No further open will occur - * until a close. + * Called when this line discipline is being attached to the terminal device. + * Can sleep. Called serialized so that no other events will occur in parallel. + * No further open will occur until a close. */ - static int n_tty_open(struct tty_struct *tty) { struct n_tty_data *ldata; /* Currently a malloc failure here can panic */ - ldata = vmalloc(sizeof(*ldata)); + ldata = vzalloc(sizeof(*ldata)); if (!ldata) - goto err; + return -ENOMEM; ldata->overrun_time = jiffies; mutex_init(&ldata->atomic_read_lock); mutex_init(&ldata->output_lock); tty->disc_data = ldata; - reset_buffer_flags(tty->disc_data); - ldata->column = 0; - ldata->canon_column = 0; - 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); n_tty_set_termios(tty, NULL); tty_unthrottle(tty); - return 0; -err: - return -ENOMEM; } -static inline int input_available_p(struct tty_struct *tty, int poll) +static inline int input_available_p(const struct tty_struct *tty, int poll) { - struct n_tty_data *ldata = tty->disc_data; + const struct n_tty_data *ldata = tty->disc_data; int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1; if (ldata->icanon && !L_EXTPROC(tty)) @@ -1918,107 +1916,102 @@ static inline int input_available_p(struct tty_struct *tty, int poll) } /** - * copy_from_read_buf - copy read data directly - * @tty: terminal device - * @b: user data - * @nr: size of data - * - * 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 - * buffer, and once to drain the space from the (physical) beginning of - * the buffer to head pointer. - * - * Called under the ldata->atomic_read_lock sem - * - * n_tty_read()/consumer path: - * caller holds non-exclusive termios_rwsem + * copy_from_read_buf - copy read data directly + * @tty: terminal device + * @kbp: data + * @nr: size of data + * + * Helper function to speed up n_tty_read(). It is only called when %ICANON is + * off; it copies characters straight from the tty queue. + * + * Returns: true if it successfully copied data, but there is still more data + * to be had. + * + * Locking: + * * 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, - unsigned char __user **b, - size_t *nr) +static bool copy_from_read_buf(const struct tty_struct *tty, u8 **kbp, + size_t *nr) { struct n_tty_data *ldata = tty->disc_data; - int retval; size_t n; 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; - n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail); - n = min(*nr, n); - if (n) { - const unsigned char *from = read_buf_addr(ldata, tail); - retval = copy_to_user(*b, from, n); - n -= retval; - is_eof = n == 1 && *from == EOF_CHAR(tty); - tty_audit_add_data(tty, from, 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 && - (head == ldata->read_tail)) - n = 0; - *b += n; - *nr -= n; - } - return retval; + size_t tail = MASK(ldata->read_tail); + + n = min3(head - ldata->read_tail, N_TTY_BUF_SIZE - tail, *nr); + if (!n) + return false; + + u8 *from = read_buf_addr(ldata, tail); + memcpy(*kbp, from, n); + is_eof = n == 1 && *from == EOF_CHAR(tty); + tty_audit_add_data(tty, from, n); + zero_buffer(tty, from, 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 && + head == ldata->read_tail) + return false; + + *kbp += n; + *nr -= n; + + /* If we have more to copy, let the caller know */ + return head != ldata->read_tail; } /** - * 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 + * canon_copy_from_read_buf - copy read data in canonical mode + * @tty: terminal device + * @kbp: 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 result buffer. + * + * Note: 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. + * + * Locking: + * * 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) +static bool canon_copy_from_read_buf(const struct tty_struct *tty, u8 **kbp, + 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; + size_t tail, canon_head; + int found = 0; /* N.B. avoid overrun if nr == 0 */ if (!*nr) - return 0; + return false; - n = min(*nr + 1, smp_load_acquire(&ldata->canon_head) - ldata->read_tail); + canon_head = smp_load_acquire(&ldata->canon_head); + n = min(*nr, canon_head - ldata->read_tail); - tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); + tail = MASK(ldata->read_tail); 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); + eol = find_first_bit(ldata->read_flags, more); found = eol != more; } else found = eol != size; @@ -2028,18 +2021,11 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, n += N_TTY_BUF_SIZE; c = n + found; - if (!found || read_buf(ldata, eol) != __DISABLED_CHAR) { - c = min(*nr, c); + if (!found || read_buf(ldata, eol) != __DISABLED_CHAR) n = c; - } - n_tty_trace("%s: eol:%zu found:%d n:%zu c:%zu tail:%zu more:%zu\n", - __func__, eol, found, n, c, tail, more); - - ret = tty_copy_to_user(tty, *b, tail, n); - if (ret) - return -EFAULT; - *b += n; + tty_copy(tty, *kbp, tail, n); + *kbp += n; *nr -= n; if (found) @@ -2052,27 +2038,55 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, else ldata->push = 0; tty_audit_push(); + return false; } - return 0; + + /* No EOL found - do a continuation retry if there is more data */ + return ldata->read_tail != canon_head; } -extern ssize_t redirected_tty_write(struct file *, const char __user *, - size_t, loff_t *); +/* + * If we finished a read at the exact location of an + * EOF (special EOL character that's a __DISABLED_CHAR) + * in the stream, silently eat the EOF. + */ +static void canon_skip_eof(struct n_tty_data *ldata) +{ + size_t tail, canon_head; + + canon_head = smp_load_acquire(&ldata->canon_head); + tail = ldata->read_tail; + + // No data? + if (tail == canon_head) + return; + + // See if the tail position is EOF in the circular buffer + tail &= (N_TTY_BUF_SIZE - 1); + if (!test_bit(tail, ldata->read_flags)) + return; + if (read_buf(ldata, tail) != __DISABLED_CHAR) + return; + + // Clear the EOL bit, skip the EOF char. + clear_bit(tail, ldata->read_flags); + smp_store_release(&ldata->read_tail, ldata->read_tail + 1); +} /** - * job_control - check job control - * @tty: tty - * @file: file handle - * - * Perform job control management checks on this file/tty descriptor - * and if appropriate send any needed signals and return a negative - * error code if action should be taken. - * - * Locking: redirected write test is safe - * current->signal->tty check is safe - * ctrl_lock to safely reference tty->pgrp + * job_control - check job control + * @tty: tty + * @file: file handle + * + * Perform job control management checks on this @file/@tty descriptor and if + * appropriate send any needed signals and return a negative error code if + * action should be taken. + * + * Locking: + * * redirected write test is safe + * * current->signal->tty check is safe + * * ctrl.lock to safely reference @tty->ctrl.pgrp */ - static int job_control(struct tty_struct *tty, struct file *file) { /* Job control check -- must be done at start and after @@ -2080,48 +2094,112 @@ static int job_control(struct tty_struct *tty, struct file *file) /* 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) + if (file->f_op->write_iter == redirected_tty_write) return 0; return __tty_check_change(tty, SIGTTIN); } +/* + * We still hold the atomic_read_lock and the termios_rwsem, and can just + * continue to copy data. + */ +static ssize_t n_tty_continue_cookie(struct tty_struct *tty, u8 *kbuf, + size_t nr, void **cookie) +{ + struct n_tty_data *ldata = tty->disc_data; + u8 *kb = kbuf; + + if (ldata->icanon && !L_EXTPROC(tty)) { + /* + * If we have filled the user buffer, see if we should skip an + * EOF character before releasing the lock and returning done. + */ + if (!nr) + canon_skip_eof(ldata); + else if (canon_copy_from_read_buf(tty, &kb, &nr)) + return kb - kbuf; + } else { + if (copy_from_read_buf(tty, &kb, &nr)) + return kb - kbuf; + } + + /* No more data - release locks and stop retries */ + n_tty_kick_worker(tty); + n_tty_check_unthrottle(tty); + up_read(&tty->termios_rwsem); + mutex_unlock(&ldata->atomic_read_lock); + *cookie = NULL; + + return kb - kbuf; +} + +static int n_tty_wait_for_input(struct tty_struct *tty, struct file *file, + struct wait_queue_entry *wait, long *timeout) +{ + if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) + return -EIO; + if (tty_hung_up_p(file)) + return 0; + /* + * Abort readers for ttys which never actually get hung up. + * See __tty_hangup(). + */ + if (test_bit(TTY_HUPPING, &tty->flags)) + return 0; + if (!*timeout) + return 0; + if (tty_io_nonblock(tty, file)) + return -EAGAIN; + if (signal_pending(current)) + return -ERESTARTSYS; + + up_read(&tty->termios_rwsem); + *timeout = wait_woken(wait, TASK_INTERRUPTIBLE, *timeout); + down_read(&tty->termios_rwsem); + + return 1; +} /** - * n_tty_read - read function for tty - * @tty: tty device - * @file: file object - * @buf: userspace buffer pointer - * @nr: size of I/O - * - * Perform reads for the line discipline. We are guaranteed that the - * line discipline will not be closed under us but we may get multiple - * parallel readers and must handle this ourselves. We may also get - * 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 + * n_tty_read - read function for tty + * @tty: tty device + * @file: file object + * @kbuf: kernelspace buffer pointer + * @nr: size of I/O + * @cookie: if non-%NULL, this is a continuation read + * @offset: where to continue reading from (unused in n_tty) + * + * Perform reads for the line discipline. We are guaranteed that the line + * discipline will not be closed under us but we may get multiple parallel + * readers and must handle this ourselves. We may also get a hangup. Always + * called in user context, may sleep. + * + * This code must be sure never to sleep through a hangup. + * + * Locking: 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, - unsigned char __user *buf, size_t nr) +static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, u8 *kbuf, + size_t nr, void **cookie, unsigned long offset) { struct n_tty_data *ldata = tty->disc_data; - unsigned char __user *b = buf; + u8 *kb = kbuf; DEFINE_WAIT_FUNC(wait, woken_wake_function); - int c; int minimum, time; - ssize_t retval = 0; + ssize_t retval; long timeout; - int packet; - size_t tail; + bool packet; + size_t old_tail; + + /* Is this a continuation of a read started earlier? */ + if (*cookie) + return n_tty_continue_cookie(tty, kbuf, nr, cookie); - c = job_control(tty, file); - if (c < 0) - return c; + retval = job_control(tty, file); + if (retval < 0) + return retval; /* * Internal serialization of reads. @@ -2148,25 +2226,21 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, } } - packet = tty->packet; - tail = ldata->read_tail; + packet = tty->ctrl.packet; + old_tail = ldata->read_tail; add_wait_queue(&tty->read_wait, &wait); while (nr) { /* First test for status change. */ - if (packet && tty->link->ctrl_status) { - unsigned char cs; - if (b != buf) - break; - spin_lock_irq(&tty->link->ctrl_lock); - cs = tty->link->ctrl_status; - tty->link->ctrl_status = 0; - spin_unlock_irq(&tty->link->ctrl_lock); - if (put_user(cs, b)) { - retval = -EFAULT; + if (packet && tty->link->ctrl.pktstatus) { + u8 cs; + if (kb != kbuf) break; + scoped_guard(spinlock_irq, &tty->link->ctrl.lock) { + cs = tty->link->ctrl.pktstatus; + tty->link->ctrl.pktstatus = 0; } - b++; + *kb++ = cs; nr--; break; } @@ -2176,115 +2250,103 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, 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; + int ret = n_tty_wait_for_input(tty, file, &wait, + &timeout); + if (ret <= 0) { + retval = ret; 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; - } - up_read(&tty->termios_rwsem); - - timeout = wait_woken(&wait, TASK_INTERRUPTIBLE, - timeout); - - down_read(&tty->termios_rwsem); continue; } } if (ldata->icanon && !L_EXTPROC(tty)) { - retval = canon_copy_from_read_buf(tty, &b, &nr); - if (retval) - break; + if (canon_copy_from_read_buf(tty, &kb, &nr)) + goto more_to_be_read; } else { - int uncopied; - /* Deal with packet mode. */ - if (packet && b == buf) { - if (put_user(TIOCPKT_DATA, b)) { - retval = -EFAULT; - break; - } - b++; + if (packet && kb == kbuf) { + *kb++ = TIOCPKT_DATA; nr--; } - uncopied = copy_from_read_buf(tty, &b, &nr); - uncopied += copy_from_read_buf(tty, &b, &nr); - if (uncopied) { - retval = -EFAULT; - break; - } + if (copy_from_read_buf(tty, &kb, &nr) && kb - kbuf >= minimum) + goto more_to_be_read; } n_tty_check_unthrottle(tty); - if (b - buf >= minimum) + if (kb - kbuf >= minimum) break; if (time) timeout = time; } - if (tail != ldata->read_tail) + if (old_tail != ldata->read_tail) { + /* + * Make sure no_room is not read in n_tty_kick_worker() + * before setting ldata->read_tail in copy_from_read_buf(). + */ + smp_mb(); n_tty_kick_worker(tty); + } up_read(&tty->termios_rwsem); remove_wait_queue(&tty->read_wait, &wait); mutex_unlock(&ldata->atomic_read_lock); - if (b - buf) - retval = b - buf; + if (kb - kbuf) + retval = kb - kbuf; return retval; +more_to_be_read: + /* + * There is more to be had and we have nothing more to wait for, so + * let's mark us for retries. + * + * NOTE! We return here with both the termios_sem and atomic_read_lock + * still held, the retries will release them when done. + */ + remove_wait_queue(&tty->read_wait, &wait); + *cookie = cookie; + + return kb - kbuf; } /** - * 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 - * respect to other write callers but not to termios changes, reads - * 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) + * 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 respect to + * other write callers but not to termios changes, reads 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 n_tty_write(struct tty_struct *tty, struct file *file, - const unsigned char *buf, size_t nr) + const u8 *buf, size_t nr) { - const unsigned char *b = buf; + const u8 *b = buf; DEFINE_WAIT_FUNC(wait, woken_wake_function); - int c; - ssize_t retval = 0; + ssize_t num, retval = 0; /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */ - if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) { + if (L_TOSTOP(tty) && file->f_op->write_iter != redirected_tty_write) { retval = tty_check_change(tty); if (retval) return retval; } - down_read(&tty->termios_rwsem); + guard(rwsem_read)(&tty->termios_rwsem); /* Write out any echoed characters that are still pending */ process_echoes(tty); @@ -2301,7 +2363,7 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, } if (O_OPOST(tty)) { while (nr > 0) { - ssize_t num = process_output_block(tty, b, nr); + num = process_output_block(tty, b, nr); if (num < 0) { if (num == -EAGAIN) break; @@ -2312,8 +2374,7 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, nr -= num; if (nr == 0) break; - c = *b; - if (process_output(c, tty) < 0) + if (process_output(*b, tty) < 0) break; b++; nr--; } @@ -2323,22 +2384,21 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, struct n_tty_data *ldata = tty->disc_data; while (nr > 0) { - mutex_lock(&ldata->output_lock); - c = tty->ops->write(tty, b, nr); - mutex_unlock(&ldata->output_lock); - if (c < 0) { - retval = c; + scoped_guard(mutex, &ldata->output_lock) + num = tty->ops->write(tty, b, nr); + if (num < 0) { + retval = num; goto break_out; } - if (!c) + if (!num) break; - b += c; - nr -= c; + b += num; + nr -= num; } } if (!nr) break; - if (file->f_flags & O_NONBLOCK) { + if (tty_io_nonblock(tty, file)) { retval = -EAGAIN; break; } @@ -2352,48 +2412,48 @@ break_out: remove_wait_queue(&tty->write_wait, &wait); if (nr && tty->fasync) set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); - up_read(&tty->termios_rwsem); + return (b - buf) ? b - buf : retval; } /** - * n_tty_poll - poll method for N_TTY - * @tty: terminal device - * @file: file accessing it - * @wait: poll table + * n_tty_poll - poll method for N_TTY + * @tty: terminal device + * @file: file accessing it + * @wait: poll table + * + * Called when the line discipline is asked to poll() for data or for special + * events. This code is not serialized with respect to other events save + * open/close. * - * Called when the line discipline is asked to poll() for data or - * for special events. This code is not serialized with respect to - * other events save open/close. + * This code must be sure never to sleep through a hangup. * - * This code must be sure never to sleep through a hangup. - * Called without the kernel lock held - fine + * Locking: called without the kernel lock held -- fine. */ - -static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, +static __poll_t n_tty_poll(struct tty_struct *tty, struct file *file, poll_table *wait) { - unsigned int mask = 0; + __poll_t mask = 0; poll_wait(file, &tty->read_wait, wait); poll_wait(file, &tty->write_wait, wait); if (input_available_p(tty, 1)) - mask |= POLLIN | POLLRDNORM; + mask |= EPOLLIN | EPOLLRDNORM; else { tty_buffer_flush_work(tty->port); if (input_available_p(tty, 1)) - mask |= POLLIN | POLLRDNORM; + mask |= EPOLLIN | EPOLLRDNORM; } - if (tty->packet && tty->link->ctrl_status) - mask |= POLLPRI | POLLIN | POLLRDNORM; + if (tty->ctrl.packet && tty->link->ctrl.pktstatus) + mask |= EPOLLPRI | EPOLLIN | EPOLLRDNORM; if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) - mask |= POLLHUP; + mask |= EPOLLHUP; if (tty_hung_up_p(file)) - mask |= POLLHUP; + mask |= EPOLLHUP; if (tty->ops->write && !tty_is_writelocked(tty) && tty_chars_in_buffer(tty) < WAKEUP_CHARS && tty_write_room(tty) > 0) - mask |= POLLOUT | POLLWRNORM; + mask |= EPOLLOUT | EPOLLWRNORM; return mask; } @@ -2407,8 +2467,8 @@ static unsigned long inq_canon(struct n_tty_data *ldata) tail = ldata->read_tail; nr = head - tail; /* Skip EOF-chars.. */ - while (head != tail) { - if (test_bit(tail & (N_TTY_BUF_SIZE - 1), ldata->read_flags) && + while (MASK(head) != MASK(tail)) { + if (test_bit(MASK(tail), ldata->read_flags) && read_buf(ldata, tail) == __DISABLED_CHAR) nr--; tail++; @@ -2416,30 +2476,30 @@ static unsigned long inq_canon(struct n_tty_data *ldata) return nr; } -static int n_tty_ioctl(struct tty_struct *tty, struct file *file, - unsigned int cmd, unsigned long arg) +static int n_tty_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) { struct n_tty_data *ldata = tty->disc_data; - int retval; + unsigned int num; switch (cmd) { case TIOCOUTQ: return put_user(tty_chars_in_buffer(tty), (int __user *) arg); case TIOCINQ: - 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); + scoped_guard(rwsem_write, &tty->termios_rwsem) + if (L_ICANON(tty) && !L_EXTPROC(tty)) + num = inq_canon(ldata); + else + num = read_cnt(ldata); + return put_user(num, (unsigned int __user *) arg); default: - return n_tty_ioctl_helper(tty, file, cmd, arg); + return n_tty_ioctl_helper(tty, cmd, arg); } } static struct tty_ldisc_ops n_tty_ops = { - .magic = TTY_LDISC_MAGIC, + .owner = THIS_MODULE, + .num = N_TTY, .name = "n_tty", .open = n_tty_open, .close = n_tty_close, @@ -2452,6 +2512,7 @@ static struct tty_ldisc_ops n_tty_ops = { .receive_buf = n_tty_receive_buf, .write_wakeup = n_tty_write_wakeup, .receive_buf2 = n_tty_receive_buf2, + .lookahead_buf = n_tty_lookahead_flow_ctrl, }; /** @@ -2465,11 +2526,10 @@ void n_tty_inherit_ops(struct tty_ldisc_ops *ops) { *ops = n_tty_ops; ops->owner = NULL; - ops->refcount = ops->flags = 0; } EXPORT_SYMBOL_GPL(n_tty_inherit_ops); void __init n_tty_init(void) { - tty_register_ldisc(N_TTY, &n_tty_ops); + tty_register_ldisc(&n_tty_ops); } |
