summaryrefslogtreecommitdiff
path: root/drivers/tty/n_tty.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/n_tty.c')
-rw-r--r--drivers/tty/n_tty.c2094
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);
}