mirror of
https://github.com/torvalds/linux.git
synced 2024-11-29 23:51:37 +00:00
65534736d9
Use n_tty_receive_char_flow_ctrl also on the closing path. This makes the code cleaner and consistent. However, there a small change of regression! The earlier closing path has a small difference compared with the normal receive path. If START_CHAR and STOP_CHAR are equal, their precedence is different depending on which path a character is processed. I don't know whether this difference was intentional or not, and if equal START_CHAR and STOP_CHAR is actually used anywhere. But it feels not so useful corner case. While this change would logically belong to those earlier changes, having a separate patch for this is useful. If this regresses, bisect can pinpoint this change rather than the large patch. Also, this change is not necessary to minimal fix for the issue addressed in the previous patch. Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Link: https://lore.kernel.org/r/20220606153652.63554-3-ilpo.jarvinen@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2523 lines
64 KiB
C
2523 lines
64 KiB
C
// SPDX-License-Identifier: GPL-1.0+
|
|
/*
|
|
* n_tty.c --- implements the N_TTY line discipline.
|
|
*
|
|
* This code used to be in tty_io.c, but things are getting hairy
|
|
* enough that it made sense to split things off. (The N_TTY
|
|
* processing has changed so much that it's hardly recognizable,
|
|
* anyway...)
|
|
*
|
|
* Note that the open routine for N_TTY is guaranteed never to return
|
|
* an error. This is because Linux will fall back to setting a line
|
|
* to N_TTY if it can not switch to any other line discipline.
|
|
*
|
|
* Written by Theodore Ts'o, Copyright 1994.
|
|
*
|
|
* This file also contains code originally written by Linus Torvalds,
|
|
* Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994.
|
|
*
|
|
* Reduced memory usage for older ARM systems - Russell King.
|
|
*
|
|
* 2000/01/20 Fixed SMP locking on put_tty_queue using bits of
|
|
* the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu>
|
|
* who actually finally proved there really was a race.
|
|
*
|
|
* 2002/03/18 Implemented n_tty_wakeup to send SIGIO POLL_OUTs to
|
|
* waiting writing processes-Sapan Bhatia <sapan@corewars.org>.
|
|
* Also fixed a bug in BLOCKING mode where n_tty_write returns
|
|
* EAGAIN
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/major.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/fcntl.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/slab.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/audit.h>
|
|
#include <linux/file.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ratelimit.h>
|
|
#include <linux/vmalloc.h>
|
|
#include "tty.h"
|
|
|
|
/*
|
|
* Until this number of characters is queued in the xmit buffer, select will
|
|
* return "we have room for writes".
|
|
*/
|
|
#define WAKEUP_CHARS 256
|
|
|
|
/*
|
|
* This defines the low- and high-watermarks for throttling and
|
|
* unthrottling the TTY driver. These watermarks are used for
|
|
* controlling the space in the read buffer.
|
|
*/
|
|
#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */
|
|
#define TTY_THRESHOLD_UNTHROTTLE 128
|
|
|
|
/*
|
|
* Special byte codes used in the echo buffer to represent operations
|
|
* or special handling of characters. Bytes in the echo buffer that
|
|
* are not part of such special blocks are treated as normal character
|
|
* codes.
|
|
*/
|
|
#define ECHO_OP_START 0xff
|
|
#define ECHO_OP_MOVE_BACK_COL 0x80
|
|
#define ECHO_OP_SET_CANON_COL 0x81
|
|
#define ECHO_OP_ERASE_TAB 0x82
|
|
|
|
#define ECHO_COMMIT_WATERMARK 256
|
|
#define ECHO_BLOCK 256
|
|
#define ECHO_DISCARD_WATERMARK N_TTY_BUF_SIZE - (ECHO_BLOCK + 32)
|
|
|
|
|
|
#undef N_TTY_TRACE
|
|
#ifdef N_TTY_TRACE
|
|
# define n_tty_trace(f, args...) trace_printk(f, ##args)
|
|
#else
|
|
# define n_tty_trace(f, args...) no_printk(f, ##args)
|
|
#endif
|
|
|
|
struct n_tty_data {
|
|
/* producer-published */
|
|
size_t read_head;
|
|
size_t commit_head;
|
|
size_t canon_head;
|
|
size_t echo_head;
|
|
size_t echo_commit;
|
|
size_t echo_mark;
|
|
DECLARE_BITMAP(char_map, 256);
|
|
|
|
/* private to n_tty_receive_overrun (single-threaded) */
|
|
unsigned long overrun_time;
|
|
int num_overrun;
|
|
|
|
/* non-atomic */
|
|
bool no_room;
|
|
|
|
/* must hold exclusive termios_rwsem to reset these */
|
|
unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
|
|
unsigned char push:1;
|
|
|
|
/* shared by producer and consumer */
|
|
char read_buf[N_TTY_BUF_SIZE];
|
|
DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE);
|
|
unsigned char echo_buf[N_TTY_BUF_SIZE];
|
|
|
|
/* 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;
|
|
size_t echo_tail;
|
|
|
|
struct mutex atomic_read_lock;
|
|
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)
|
|
{
|
|
return ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
}
|
|
|
|
static inline unsigned char *read_buf_addr(struct n_tty_data *ldata, size_t i)
|
|
{
|
|
return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
}
|
|
|
|
static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i)
|
|
{
|
|
smp_rmb(); /* Matches smp_wmb() in add_echo_byte(). */
|
|
return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
}
|
|
|
|
static inline unsigned char *echo_buf_addr(struct n_tty_data *ldata, size_t i)
|
|
{
|
|
return &ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
}
|
|
|
|
/* If we are not echoing the data, perhaps this is a secret so erase it */
|
|
static void zero_buffer(struct tty_struct *tty, u8 *buffer, int size)
|
|
{
|
|
bool icanon = !!L_ICANON(tty);
|
|
bool no_echo = !L_ECHO(tty);
|
|
|
|
if (icanon && no_echo)
|
|
memset(buffer, 0x00, size);
|
|
}
|
|
|
|
static void tty_copy(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;
|
|
void *from = read_buf_addr(ldata, tail);
|
|
|
|
if (n > size) {
|
|
tty_audit_add_data(tty, from, size);
|
|
memcpy(to, from, size);
|
|
zero_buffer(tty, from, size);
|
|
to += size;
|
|
n -= size;
|
|
from = ldata->read_buf;
|
|
}
|
|
|
|
tty_audit_add_data(tty, from, n);
|
|
memcpy(to, from, n);
|
|
zero_buffer(tty, from, n);
|
|
}
|
|
|
|
/**
|
|
* n_tty_kick_worker - start input worker (if required)
|
|
* @tty: terminal
|
|
*
|
|
* Re-schedules the flip buffer work if it may have stopped.
|
|
*
|
|
* 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)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
/* Did the input worker stop? Restart it */
|
|
if (unlikely(ldata->no_room)) {
|
|
ldata->no_room = 0;
|
|
|
|
WARN_RATELIMIT(tty->port->itty == NULL,
|
|
"scheduling with invalid itty\n");
|
|
/* see if ldisc has been killed - if so, this means that
|
|
* even though the ldisc has been halted and ->buf.work
|
|
* cancelled, ->buf.work is about to be rescheduled
|
|
*/
|
|
WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags),
|
|
"scheduling buffer work for halted ldisc\n");
|
|
tty_buffer_restart_work(tty->port);
|
|
}
|
|
}
|
|
|
|
static ssize_t chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
ssize_t n = 0;
|
|
|
|
if (!ldata->icanon)
|
|
n = ldata->commit_head - ldata->read_tail;
|
|
else
|
|
n = ldata->canon_head - ldata->read_tail;
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* n_tty_write_wakeup - asynchronous I/O notifier
|
|
* @tty: tty device
|
|
*
|
|
* Required for the ptys, serial driver etc. since processes that attach
|
|
* themselves to the master and rely on ASYNC IO must be woken up.
|
|
*/
|
|
static void n_tty_write_wakeup(struct tty_struct *tty)
|
|
{
|
|
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
|
|
kill_fasync(&tty->fasync, SIGIO, POLL_OUT);
|
|
}
|
|
|
|
static void n_tty_check_throttle(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
/*
|
|
* Check the remaining room for the input canonicalization
|
|
* mode. We don't want to throttle the driver if we're in
|
|
* canonical mode and don't have a newline yet!
|
|
*/
|
|
if (ldata->icanon && ldata->canon_head == ldata->read_tail)
|
|
return;
|
|
|
|
while (1) {
|
|
int throttled;
|
|
tty_set_flow_change(tty, TTY_THROTTLE_SAFE);
|
|
if (N_TTY_BUF_SIZE - read_cnt(ldata) >= TTY_THRESHOLD_THROTTLE)
|
|
break;
|
|
throttled = tty_throttle_safe(tty);
|
|
if (!throttled)
|
|
break;
|
|
}
|
|
__tty_set_flow_change(tty, 0);
|
|
}
|
|
|
|
static void n_tty_check_unthrottle(struct tty_struct *tty)
|
|
{
|
|
if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
|
|
if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
|
|
return;
|
|
n_tty_kick_worker(tty);
|
|
tty_wakeup(tty->link);
|
|
return;
|
|
}
|
|
|
|
/* If there is enough space in the read buffer now, let the
|
|
* low-level driver know. We use chars_in_buffer() to
|
|
* check the buffer, as it now knows about canonical mode.
|
|
* Otherwise, if the driver is throttled and the line is
|
|
* longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
|
|
* we won't get any more characters.
|
|
*/
|
|
|
|
while (1) {
|
|
int unthrottled;
|
|
tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
|
|
if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
|
|
break;
|
|
n_tty_kick_worker(tty);
|
|
unthrottled = tty_unthrottle_safe(tty);
|
|
if (!unthrottled)
|
|
break;
|
|
}
|
|
__tty_set_flow_change(tty, 0);
|
|
}
|
|
|
|
/**
|
|
* put_tty_queue - add character to tty
|
|
* @c: character
|
|
* @ldata: n_tty data
|
|
*
|
|
* Add a character to the tty read_buf queue.
|
|
*
|
|
* 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)
|
|
{
|
|
*read_buf_addr(ldata, ldata->read_head) = c;
|
|
ldata->read_head++;
|
|
}
|
|
|
|
/**
|
|
* 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().
|
|
*
|
|
* 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->commit_head = 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->ctrl.packet) {
|
|
spin_lock_irqsave(&tty->ctrl.lock, flags);
|
|
tty->ctrl.pktstatus |= TIOCPKT_FLUSHREAD;
|
|
spin_unlock_irqrestore(&tty->ctrl.lock, flags);
|
|
wake_up_interruptible(&tty->link->read_wait);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
*
|
|
* Holds %termios_rwsem to exclude producer/consumer while buffer indices are
|
|
* reset.
|
|
*
|
|
* Locking: %ctrl.lock, exclusive %termios_rwsem
|
|
*/
|
|
static void n_tty_flush_buffer(struct tty_struct *tty)
|
|
{
|
|
down_write(&tty->termios_rwsem);
|
|
reset_buffer_flags(tty->disc_data);
|
|
n_tty_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
|
|
*
|
|
* 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)
|
|
{
|
|
return (c & 0xc0) == 0x80;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static inline int is_continuation(unsigned char c, 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
|
|
*
|
|
* 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.
|
|
*
|
|
* Returns: the number of bytes of buffer space used or -1 if no space left.
|
|
*
|
|
* Locking: should be called under the %output_lock to protect the column state
|
|
* and space left in the buffer.
|
|
*/
|
|
static int do_output_char(unsigned char c, struct tty_struct *tty, int space)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
int spaces;
|
|
|
|
if (!space)
|
|
return -1;
|
|
|
|
switch (c) {
|
|
case '\n':
|
|
if (O_ONLRET(tty))
|
|
ldata->column = 0;
|
|
if (O_ONLCR(tty)) {
|
|
if (space < 2)
|
|
return -1;
|
|
ldata->canon_column = ldata->column = 0;
|
|
tty->ops->write(tty, "\r\n", 2);
|
|
return 2;
|
|
}
|
|
ldata->canon_column = ldata->column;
|
|
break;
|
|
case '\r':
|
|
if (O_ONOCR(tty) && ldata->column == 0)
|
|
return 0;
|
|
if (O_OCRNL(tty)) {
|
|
c = '\n';
|
|
if (O_ONLRET(tty))
|
|
ldata->canon_column = ldata->column = 0;
|
|
break;
|
|
}
|
|
ldata->canon_column = ldata->column = 0;
|
|
break;
|
|
case '\t':
|
|
spaces = 8 - (ldata->column & 7);
|
|
if (O_TABDLY(tty) == XTABS) {
|
|
if (space < spaces)
|
|
return -1;
|
|
ldata->column += spaces;
|
|
tty->ops->write(tty, " ", spaces);
|
|
return spaces;
|
|
}
|
|
ldata->column += spaces;
|
|
break;
|
|
case '\b':
|
|
if (ldata->column > 0)
|
|
ldata->column--;
|
|
break;
|
|
default:
|
|
if (!iscntrl(c)) {
|
|
if (O_OLCUC(tty))
|
|
c = toupper(c);
|
|
if (!is_continuation(c, tty))
|
|
ldata->column++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
tty_put_char(tty, c);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* process_output - output post processor
|
|
* @c: character (or partial unicode symbol)
|
|
* @tty: terminal device
|
|
*
|
|
* Output one character with OPOST processing.
|
|
*
|
|
* Returns: -1 when the output device is full and the character must be
|
|
* retried.
|
|
*
|
|
* Locking: %output_lock to protect column state and space left (also, this is
|
|
*called from n_tty_write() under the tty layer write lock).
|
|
*/
|
|
static int process_output(unsigned char c, struct tty_struct *tty)
|
|
{
|
|
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);
|
|
|
|
mutex_unlock(&ldata->output_lock);
|
|
if (retval < 0)
|
|
return -1;
|
|
else
|
|
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.
|
|
*
|
|
* 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)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
int space;
|
|
int i;
|
|
const unsigned char *cp;
|
|
|
|
mutex_lock(&ldata->output_lock);
|
|
|
|
space = tty_write_room(tty);
|
|
if (space <= 0) {
|
|
mutex_unlock(&ldata->output_lock);
|
|
return space;
|
|
}
|
|
if (nr > space)
|
|
nr = space;
|
|
|
|
for (i = 0, cp = buf; i < nr; i++, cp++) {
|
|
unsigned char c = *cp;
|
|
|
|
switch (c) {
|
|
case '\n':
|
|
if (O_ONLRET(tty))
|
|
ldata->column = 0;
|
|
if (O_ONLCR(tty))
|
|
goto break_out;
|
|
ldata->canon_column = ldata->column;
|
|
break;
|
|
case '\r':
|
|
if (O_ONOCR(tty) && ldata->column == 0)
|
|
goto break_out;
|
|
if (O_OCRNL(tty))
|
|
goto break_out;
|
|
ldata->canon_column = ldata->column = 0;
|
|
break;
|
|
case '\t':
|
|
goto break_out;
|
|
case '\b':
|
|
if (ldata->column > 0)
|
|
ldata->column--;
|
|
break;
|
|
default:
|
|
if (!iscntrl(c)) {
|
|
if (O_OLCUC(tty))
|
|
goto break_out;
|
|
if (!is_continuation(c, tty))
|
|
ldata->column++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break_out:
|
|
i = tty->ops->write(tty, buf, i);
|
|
|
|
mutex_unlock(&ldata->output_lock);
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* __process_echoes - write pending echo characters
|
|
* @tty: terminal device
|
|
*
|
|
* Write previously buffered echo (and other ldisc-generated) characters to the
|
|
* tty.
|
|
*
|
|
* Characters generated by the ldisc (including echoes) need to be buffered
|
|
* because the driver's write buffer can fill during heavy program output.
|
|
* Echoing straight to the driver will often fail under these conditions,
|
|
* causing lost characters and resulting mismatches of ldisc state information.
|
|
*
|
|
* Since the ldisc state must represent the characters actually sent to the
|
|
* driver at the time of the write, operations like certain changes in column
|
|
* state are also saved in the buffer and executed here.
|
|
*
|
|
* A circular fifo buffer is used so that the most recent characters are
|
|
* prioritized. Also, when control characters are echoed with a prefixed "^",
|
|
* the pair is treated atomically and thus not separated.
|
|
*
|
|
* Locking: 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;
|
|
size_t tail;
|
|
unsigned char c;
|
|
|
|
old_space = space = tty_write_room(tty);
|
|
|
|
tail = ldata->echo_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;
|
|
|
|
/*
|
|
* 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))
|
|
goto not_yet_stored;
|
|
/*
|
|
* 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))
|
|
goto not_yet_stored;
|
|
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)
|
|
break;
|
|
} else {
|
|
if (O_OPOST(tty)) {
|
|
int retval = do_output_char(c, tty, space);
|
|
if (retval < 0)
|
|
break;
|
|
space -= retval;
|
|
} else {
|
|
if (!space)
|
|
break;
|
|
tty_put_char(tty, c);
|
|
space -= 1;
|
|
}
|
|
tail += 1;
|
|
}
|
|
}
|
|
|
|
/* 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 &&
|
|
ldata->echo_commit - tail >= ECHO_DISCARD_WATERMARK) {
|
|
if (echo_buf(ldata, tail) == ECHO_OP_START) {
|
|
if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB)
|
|
tail += 3;
|
|
else
|
|
tail += 2;
|
|
} else
|
|
tail++;
|
|
}
|
|
|
|
not_yet_stored:
|
|
ldata->echo_tail = tail;
|
|
return old_space - space;
|
|
}
|
|
|
|
static void commit_echoes(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
size_t nr, old, echoed;
|
|
size_t head;
|
|
|
|
mutex_lock(&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)) {
|
|
mutex_unlock(&ldata->output_lock);
|
|
return;
|
|
}
|
|
|
|
ldata->echo_commit = head;
|
|
echoed = __process_echoes(tty);
|
|
mutex_unlock(&ldata->output_lock);
|
|
|
|
if (echoed && tty->ops->flush_chars)
|
|
tty->ops->flush_chars(tty);
|
|
}
|
|
|
|
static void process_echoes(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
size_t echoed;
|
|
|
|
if (ldata->echo_mark == ldata->echo_tail)
|
|
return;
|
|
|
|
mutex_lock(&ldata->output_lock);
|
|
ldata->echo_commit = ldata->echo_mark;
|
|
echoed = __process_echoes(tty);
|
|
mutex_unlock(&ldata->output_lock);
|
|
|
|
if (echoed && tty->ops->flush_chars)
|
|
tty->ops->flush_chars(tty);
|
|
}
|
|
|
|
/* NB: echo_mark and echo_head should be equivalent here */
|
|
static void flush_echoes(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
if ((!L_ECHO(tty) && !L_ECHONL(tty)) ||
|
|
ldata->echo_commit == ldata->echo_head)
|
|
return;
|
|
|
|
mutex_lock(&ldata->output_lock);
|
|
ldata->echo_commit = ldata->echo_head;
|
|
__process_echoes(tty);
|
|
mutex_unlock(&ldata->output_lock);
|
|
}
|
|
|
|
/**
|
|
* add_echo_byte - add a byte to the echo buffer
|
|
* @c: unicode byte to echo
|
|
* @ldata: n_tty data
|
|
*
|
|
* Add a character or operation byte to the echo buffer.
|
|
*/
|
|
static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata)
|
|
{
|
|
*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
|
|
*
|
|
* 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);
|
|
add_echo_byte(ECHO_OP_MOVE_BACK_COL, ldata);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static void echo_set_canon_col(struct n_tty_data *ldata)
|
|
{
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
add_echo_byte(ECHO_OP_SET_CANON_COL, 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.
|
|
*/
|
|
static void echo_erase_tab(unsigned int num_chars, int after_tab,
|
|
struct n_tty_data *ldata)
|
|
{
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
add_echo_byte(ECHO_OP_ERASE_TAB, ldata);
|
|
|
|
/* We only need to know this modulo 8 (tab spacing) */
|
|
num_chars &= 7;
|
|
|
|
/* Set the high bit as a flag if num_chars is after a previous tab */
|
|
if (after_tab)
|
|
num_chars |= 0x80;
|
|
|
|
add_echo_byte(num_chars, ldata);
|
|
}
|
|
|
|
/**
|
|
* 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 &tty_driver.receive_buf() path.
|
|
*
|
|
* This variant does not treat control characters specially.
|
|
*/
|
|
static void echo_char_raw(unsigned char c, struct n_tty_data *ldata)
|
|
{
|
|
if (c == ECHO_OP_START) {
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
} else {
|
|
add_echo_byte(c, ldata);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 &tty_driver.receive_buf() path.
|
|
*
|
|
* 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)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
if (c == ECHO_OP_START) {
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
} else {
|
|
if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t')
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
add_echo_byte(c, ldata);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* finish_erasing - complete erase
|
|
* @ldata: n_tty data
|
|
*/
|
|
static inline void finish_erasing(struct n_tty_data *ldata)
|
|
{
|
|
if (ldata->erasing) {
|
|
echo_char_raw('/', ldata);
|
|
ldata->erasing = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Locking: n_tty_receive_buf()/producer path:
|
|
* caller holds non-exclusive %termios_rwsem
|
|
*/
|
|
static void eraser(unsigned char c, struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
enum { ERASE, WERASE, KILL } kill_type;
|
|
size_t head;
|
|
size_t cnt;
|
|
int seen_alnums;
|
|
|
|
if (ldata->read_head == ldata->canon_head) {
|
|
/* process_output('\a', tty); */ /* what do you think? */
|
|
return;
|
|
}
|
|
if (c == ERASE_CHAR(tty))
|
|
kill_type = ERASE;
|
|
else if (c == WERASE_CHAR(tty))
|
|
kill_type = WERASE;
|
|
else {
|
|
if (!L_ECHO(tty)) {
|
|
ldata->read_head = ldata->canon_head;
|
|
return;
|
|
}
|
|
if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) {
|
|
ldata->read_head = ldata->canon_head;
|
|
finish_erasing(ldata);
|
|
echo_char(KILL_CHAR(tty), tty);
|
|
/* Add a newline if ECHOK is on and ECHOKE is off. */
|
|
if (L_ECHOK(tty))
|
|
echo_char_raw('\n', ldata);
|
|
return;
|
|
}
|
|
kill_type = KILL;
|
|
}
|
|
|
|
seen_alnums = 0;
|
|
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) &&
|
|
MASK(head) != MASK(ldata->canon_head));
|
|
|
|
/* do not partially erase */
|
|
if (is_continuation(c, tty))
|
|
break;
|
|
|
|
if (kill_type == WERASE) {
|
|
/* Equivalent to BSD's ALTWERASE. */
|
|
if (isalnum(c) || c == '_')
|
|
seen_alnums++;
|
|
else if (seen_alnums)
|
|
break;
|
|
}
|
|
cnt = ldata->read_head - head;
|
|
ldata->read_head = head;
|
|
if (L_ECHO(tty)) {
|
|
if (L_ECHOPRT(tty)) {
|
|
if (!ldata->erasing) {
|
|
echo_char_raw('\\', ldata);
|
|
ldata->erasing = 1;
|
|
}
|
|
/* if cnt > 1, output a multi-byte character */
|
|
echo_char(c, tty);
|
|
while (--cnt > 0) {
|
|
head++;
|
|
echo_char_raw(read_buf(ldata, head), ldata);
|
|
echo_move_back_col(ldata);
|
|
}
|
|
} else if (kill_type == ERASE && !L_ECHOE(tty)) {
|
|
echo_char(ERASE_CHAR(tty), tty);
|
|
} else if (c == '\t') {
|
|
unsigned int num_chars = 0;
|
|
int after_tab = 0;
|
|
size_t tail = ldata->read_head;
|
|
|
|
/*
|
|
* Count the columns used for characters
|
|
* since the start of input or after a
|
|
* previous tab.
|
|
* This info is used to go back the correct
|
|
* number of columns.
|
|
*/
|
|
while (MASK(tail) != MASK(ldata->canon_head)) {
|
|
tail--;
|
|
c = read_buf(ldata, tail);
|
|
if (c == '\t') {
|
|
after_tab = 1;
|
|
break;
|
|
} else if (iscntrl(c)) {
|
|
if (L_ECHOCTL(tty))
|
|
num_chars += 2;
|
|
} else if (!is_continuation(c, tty)) {
|
|
num_chars++;
|
|
}
|
|
}
|
|
echo_erase_tab(num_chars, after_tab, ldata);
|
|
} else {
|
|
if (iscntrl(c) && L_ECHOCTL(tty)) {
|
|
echo_char_raw('\b', ldata);
|
|
echo_char_raw(' ', ldata);
|
|
echo_char_raw('\b', ldata);
|
|
}
|
|
if (!iscntrl(c) || L_ECHOCTL(tty)) {
|
|
echo_char_raw('\b', ldata);
|
|
echo_char_raw(' ', ldata);
|
|
echo_char_raw('\b', ldata);
|
|
}
|
|
}
|
|
}
|
|
if (kill_type == ERASE)
|
|
break;
|
|
}
|
|
if (ldata->read_head == ldata->canon_head && L_ECHO(tty))
|
|
finish_erasing(ldata);
|
|
}
|
|
|
|
|
|
static void __isig(int sig, struct tty_struct *tty)
|
|
{
|
|
struct pid *tty_pgrp = tty_get_pgrp(tty);
|
|
if (tty_pgrp) {
|
|
kill_pgrp(tty_pgrp, sig, 1);
|
|
put_pid(tty_pgrp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
if (L_NOFLSH(tty)) {
|
|
/* signal only */
|
|
__isig(sig, tty);
|
|
|
|
} else { /* signal and flush */
|
|
up_read(&tty->termios_rwsem);
|
|
down_write(&tty->termios_rwsem);
|
|
|
|
__isig(sig, tty);
|
|
|
|
/* clear echo buffer */
|
|
mutex_lock(&ldata->output_lock);
|
|
ldata->echo_head = ldata->echo_tail = 0;
|
|
ldata->echo_mark = ldata->echo_commit = 0;
|
|
mutex_unlock(&ldata->output_lock);
|
|
|
|
/* clear output buffer */
|
|
tty_driver_flush_buffer(tty);
|
|
|
|
/* clear input buffer */
|
|
reset_buffer_flags(tty->disc_data);
|
|
|
|
/* notify pty master of flush */
|
|
if (tty->link)
|
|
n_tty_packet_mode_flush(tty);
|
|
|
|
up_write(&tty->termios_rwsem);
|
|
down_read(&tty->termios_rwsem);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* n_tty_receive_break - handle break
|
|
* @tty: terminal
|
|
*
|
|
* An RS232 break event has been hit in the incoming bitstream. This can cause
|
|
* a variety of events depending upon the termios settings.
|
|
*
|
|
* Locking: n_tty_receive_buf()/producer path:
|
|
* caller holds non-exclusive termios_rwsem
|
|
*
|
|
* 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;
|
|
|
|
if (I_IGNBRK(tty))
|
|
return;
|
|
if (I_BRKINT(tty)) {
|
|
isig(SIGINT, tty);
|
|
return;
|
|
}
|
|
if (I_PARMRK(tty)) {
|
|
put_tty_queue('\377', ldata);
|
|
put_tty_queue('\0', ldata);
|
|
}
|
|
put_tty_queue('\0', ldata);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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)
|
|
{
|
|
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);
|
|
ldata->overrun_time = jiffies;
|
|
ldata->num_overrun = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
if (I_INPCK(tty)) {
|
|
if (I_IGNPAR(tty))
|
|
return;
|
|
if (I_PARMRK(tty)) {
|
|
put_tty_queue('\377', ldata);
|
|
put_tty_queue('\0', ldata);
|
|
put_tty_queue(c, ldata);
|
|
} else
|
|
put_tty_queue('\0', ldata);
|
|
} else
|
|
put_tty_queue(c, ldata);
|
|
}
|
|
|
|
static void
|
|
n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c)
|
|
{
|
|
isig(signal, tty);
|
|
if (I_IXON(tty))
|
|
start_tty(tty);
|
|
if (L_ECHO(tty)) {
|
|
echo_char(c, tty);
|
|
commit_echoes(tty);
|
|
} else
|
|
process_echoes(tty);
|
|
}
|
|
|
|
static bool n_tty_is_char_flow_ctrl(struct tty_struct *tty, unsigned char c)
|
|
{
|
|
return c == START_CHAR(tty) || c == STOP_CHAR(tty);
|
|
}
|
|
|
|
/**
|
|
* n_tty_receive_char_flow_ctrl - receive flow control chars
|
|
* @tty: terminal device
|
|
* @c: character
|
|
* @lookahead_done: lookahead has processed this character already
|
|
*
|
|
* Receive and process flow control character actions.
|
|
*
|
|
* 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 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, unsigned char 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 void n_tty_receive_char_special(struct tty_struct *tty, unsigned char 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;
|
|
} else if (c == QUIT_CHAR(tty)) {
|
|
n_tty_receive_signal_char(tty, SIGQUIT, c);
|
|
return;
|
|
} else if (c == SUSP_CHAR(tty)) {
|
|
n_tty_receive_signal_char(tty, SIGTSTP, c);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
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, EPOLLIN | EPOLLRDNORM);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (L_ECHO(tty)) {
|
|
finish_erasing(ldata);
|
|
if (c == '\n')
|
|
echo_char_raw('\n', ldata);
|
|
else {
|
|
/* Record the column of first canon char. */
|
|
if (ldata->canon_head == ldata->read_head)
|
|
echo_set_canon_col(ldata);
|
|
echo_char(c, tty);
|
|
}
|
|
commit_echoes(tty);
|
|
}
|
|
|
|
/* PARMRK doubling check */
|
|
if (c == (unsigned char) '\377' && I_PARMRK(tty))
|
|
put_tty_queue(c, ldata);
|
|
|
|
put_tty_queue(c, ldata);
|
|
}
|
|
|
|
/**
|
|
* 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, unsigned char c)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
if (tty->flow.stopped && !tty->flow.tco_stopped && I_IXON(tty) && I_IXANY(tty)) {
|
|
start_tty(tty);
|
|
process_echoes(tty);
|
|
}
|
|
if (L_ECHO(tty)) {
|
|
finish_erasing(ldata);
|
|
/* Record the column of first canon char. */
|
|
if (ldata->canon_head == ldata->read_head)
|
|
echo_set_canon_col(ldata);
|
|
echo_char(c, tty);
|
|
commit_echoes(tty);
|
|
}
|
|
/* PARMRK doubling check */
|
|
if (c == (unsigned char) '\377' && I_PARMRK(tty))
|
|
put_tty_queue(c, ldata);
|
|
put_tty_queue(c, ldata);
|
|
}
|
|
|
|
static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c,
|
|
bool lookahead_done)
|
|
{
|
|
if (I_ISTRIP(tty))
|
|
c &= 0x7f;
|
|
if (I_IUCLC(tty) && L_IEXTEN(tty))
|
|
c = tolower(c);
|
|
|
|
if (I_IXON(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
n_tty_receive_char_flagged(struct tty_struct *tty, unsigned char c, char flag)
|
|
{
|
|
switch (flag) {
|
|
case TTY_BREAK:
|
|
n_tty_receive_break(tty);
|
|
break;
|
|
case TTY_PARITY:
|
|
case TTY_FRAME:
|
|
n_tty_receive_parity_error(tty, c);
|
|
break;
|
|
case TTY_OVERRUN:
|
|
n_tty_receive_overrun(tty);
|
|
break;
|
|
default:
|
|
tty_err(tty, "unknown flag %d\n", flag);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
ldata->lnext = 0;
|
|
if (likely(flag == TTY_NORMAL)) {
|
|
if (I_ISTRIP(tty))
|
|
c &= 0x7f;
|
|
if (I_IUCLC(tty) && L_IEXTEN(tty))
|
|
c = tolower(c);
|
|
n_tty_receive_char(tty, c);
|
|
} else
|
|
n_tty_receive_char_flagged(tty, c, flag);
|
|
}
|
|
|
|
/* Caller must ensure count > 0 */
|
|
static void n_tty_lookahead_flow_ctrl(struct tty_struct *tty, const unsigned char *cp,
|
|
const unsigned char *fp, unsigned int count)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
unsigned char 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,
|
|
const char *fp, int count)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
size_t n, head;
|
|
|
|
head = ldata->read_head & (N_TTY_BUF_SIZE - 1);
|
|
n = min_t(size_t, count, N_TTY_BUF_SIZE - head);
|
|
memcpy(read_buf_addr(ldata, head), cp, n);
|
|
ldata->read_head += n;
|
|
cp += n;
|
|
count -= n;
|
|
|
|
head = ldata->read_head & (N_TTY_BUF_SIZE - 1);
|
|
n = min_t(size_t, count, N_TTY_BUF_SIZE - head);
|
|
memcpy(read_buf_addr(ldata, head), cp, n);
|
|
ldata->read_head += n;
|
|
}
|
|
|
|
static void
|
|
n_tty_receive_buf_raw(struct tty_struct *tty, const unsigned char *cp,
|
|
const char *fp, int count)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
char flag = TTY_NORMAL;
|
|
|
|
while (count--) {
|
|
if (fp)
|
|
flag = *fp++;
|
|
if (likely(flag == TTY_NORMAL))
|
|
put_tty_queue(*cp++, ldata);
|
|
else
|
|
n_tty_receive_char_flagged(tty, *cp++, flag);
|
|
}
|
|
}
|
|
|
|
static void
|
|
n_tty_receive_buf_closing(struct tty_struct *tty, const unsigned char *cp,
|
|
const char *fp, int count, bool lookahead_done)
|
|
{
|
|
char flag = TTY_NORMAL;
|
|
|
|
while (count--) {
|
|
if (fp)
|
|
flag = *fp++;
|
|
if (likely(flag == TTY_NORMAL))
|
|
n_tty_receive_char_closing(tty, *cp++, lookahead_done);
|
|
}
|
|
}
|
|
|
|
static void n_tty_receive_buf_standard(struct tty_struct *tty,
|
|
const unsigned char *cp, const char *fp, int count, bool lookahead_done)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
char flag = TTY_NORMAL;
|
|
|
|
while (count--) {
|
|
unsigned char c = *cp++;
|
|
|
|
if (fp)
|
|
flag = *fp++;
|
|
|
|
if (ldata->lnext) {
|
|
n_tty_receive_char_lnext(tty, c, flag);
|
|
continue;
|
|
}
|
|
|
|
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,
|
|
const char *fp, int 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_t(size_t, ldata->lookahead_count, count);
|
|
|
|
if (ldata->real_raw)
|
|
n_tty_receive_buf_real_raw(tty, cp, fp, count);
|
|
else if (ldata->raw || (L_EXTPROC(tty) && !preops))
|
|
n_tty_receive_buf_raw(tty, cp, fp, count);
|
|
else if (tty->closing && !L_EXTPROC(tty)) {
|
|
if (la_count > 0)
|
|
n_tty_receive_buf_closing(tty, cp, fp, la_count, true);
|
|
if (count > la_count)
|
|
n_tty_receive_buf_closing(tty, cp, fp, count - la_count, false);
|
|
} else {
|
|
if (la_count > 0)
|
|
n_tty_receive_buf_standard(tty, cp, fp, la_count, true);
|
|
if (count > la_count)
|
|
n_tty_receive_buf_standard(tty, cp, fp, count - la_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;
|
|
|
|
/* publish read_head to consumer */
|
|
smp_store_release(&ldata->commit_head, ldata->read_head);
|
|
|
|
if (read_cnt(ldata)) {
|
|
kill_fasync(&tty->fasync, SIGIO, POLL_IN);
|
|
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
|
|
* @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,
|
|
const char *fp, int count, int flow)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
int room, n, rcvd = 0, overflow;
|
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
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
|
|
*
|
|
* If we are doing input canonicalization, and there are no
|
|
* pending newlines, let characters through without limit, so
|
|
* that erase characters will be handled. Other excess
|
|
* characters will be beeped.
|
|
*
|
|
* paired with store in *_copy_from_read_buf() -- guarantees
|
|
* the consumer has loaded the data in read_buf up to the new
|
|
* read_tail (so this producer will not overwrite unread data)
|
|
*/
|
|
size_t tail = smp_load_acquire(&ldata->read_tail);
|
|
|
|
room = N_TTY_BUF_SIZE - (ldata->read_head - tail);
|
|
if (I_PARMRK(tty))
|
|
room = (room + 2) / 3;
|
|
room--;
|
|
if (room <= 0) {
|
|
overflow = ldata->icanon && ldata->canon_head == tail;
|
|
if (overflow && room < 0)
|
|
ldata->read_head--;
|
|
room = overflow;
|
|
ldata->no_room = flow && !room;
|
|
} else
|
|
overflow = 0;
|
|
|
|
n = min(count, room);
|
|
if (!n)
|
|
break;
|
|
|
|
/* ignore parity errors if handling overflow */
|
|
if (!overflow || !fp || *fp != TTY_PARITY)
|
|
__receive_buf(tty, cp, fp, n);
|
|
|
|
cp += n;
|
|
if (fp)
|
|
fp += n;
|
|
count -= n;
|
|
rcvd += n;
|
|
} while (!test_bit(TTY_LDISC_CHANGING, &tty->flags));
|
|
|
|
tty->receive_room = room;
|
|
|
|
/* Unthrottle if handling overflow on pty */
|
|
if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
|
|
if (overflow) {
|
|
tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
|
|
tty_unthrottle_safe(tty);
|
|
__tty_set_flow_change(tty, 0);
|
|
}
|
|
} else
|
|
n_tty_check_throttle(tty);
|
|
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
return rcvd;
|
|
}
|
|
|
|
static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
|
|
const char *fp, int count)
|
|
{
|
|
n_tty_receive_buf_common(tty, cp, fp, count, 0);
|
|
}
|
|
|
|
static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
|
|
const char *fp, int count)
|
|
{
|
|
return n_tty_receive_buf_common(tty, cp, fp, count, 1);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Locking: Caller holds @tty->termios_rwsem
|
|
*/
|
|
static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
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);
|
|
ldata->canon_head = ldata->read_head;
|
|
ldata->push = 1;
|
|
}
|
|
ldata->commit_head = ldata->read_head;
|
|
ldata->erasing = 0;
|
|
ldata->lnext = 0;
|
|
}
|
|
|
|
ldata->icanon = (L_ICANON(tty) != 0);
|
|
|
|
if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||
|
|
I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
|
|
I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
|
|
I_PARMRK(tty)) {
|
|
bitmap_zero(ldata->char_map, 256);
|
|
|
|
if (I_IGNCR(tty) || I_ICRNL(tty))
|
|
set_bit('\r', ldata->char_map);
|
|
if (I_INLCR(tty))
|
|
set_bit('\n', ldata->char_map);
|
|
|
|
if (L_ICANON(tty)) {
|
|
set_bit(ERASE_CHAR(tty), ldata->char_map);
|
|
set_bit(KILL_CHAR(tty), ldata->char_map);
|
|
set_bit(EOF_CHAR(tty), ldata->char_map);
|
|
set_bit('\n', ldata->char_map);
|
|
set_bit(EOL_CHAR(tty), ldata->char_map);
|
|
if (L_IEXTEN(tty)) {
|
|
set_bit(WERASE_CHAR(tty), ldata->char_map);
|
|
set_bit(LNEXT_CHAR(tty), ldata->char_map);
|
|
set_bit(EOL2_CHAR(tty), ldata->char_map);
|
|
if (L_ECHO(tty))
|
|
set_bit(REPRINT_CHAR(tty),
|
|
ldata->char_map);
|
|
}
|
|
}
|
|
if (I_IXON(tty)) {
|
|
set_bit(START_CHAR(tty), ldata->char_map);
|
|
set_bit(STOP_CHAR(tty), ldata->char_map);
|
|
}
|
|
if (L_ISIG(tty)) {
|
|
set_bit(INTR_CHAR(tty), ldata->char_map);
|
|
set_bit(QUIT_CHAR(tty), ldata->char_map);
|
|
set_bit(SUSP_CHAR(tty), ldata->char_map);
|
|
}
|
|
clear_bit(__DISABLED_CHAR, ldata->char_map);
|
|
ldata->raw = 0;
|
|
ldata->real_raw = 0;
|
|
} else {
|
|
ldata->raw = 1;
|
|
if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) &&
|
|
(I_IGNPAR(tty) || !I_INPCK(tty)) &&
|
|
(tty->driver->flags & TTY_DRIVER_REAL_RAW))
|
|
ldata->real_raw = 1;
|
|
else
|
|
ldata->real_raw = 0;
|
|
}
|
|
/*
|
|
* 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.tco_stopped) {
|
|
start_tty(tty);
|
|
process_echoes(tty);
|
|
}
|
|
|
|
/* The termios change make the tty ready for I/O */
|
|
wake_up_interruptible(&tty->write_wait);
|
|
wake_up_interruptible(&tty->read_wait);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static void n_tty_close(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
if (tty->link)
|
|
n_tty_packet_mode_flush(tty);
|
|
|
|
down_write(&tty->termios_rwsem);
|
|
vfree(ldata);
|
|
tty->disc_data = NULL;
|
|
up_write(&tty->termios_rwsem);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static int n_tty_open(struct tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata;
|
|
|
|
/* Currently a malloc failure here can panic */
|
|
ldata = vzalloc(sizeof(*ldata));
|
|
if (!ldata)
|
|
return -ENOMEM;
|
|
|
|
ldata->overrun_time = jiffies;
|
|
mutex_init(&ldata->atomic_read_lock);
|
|
mutex_init(&ldata->output_lock);
|
|
|
|
tty->disc_data = ldata;
|
|
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;
|
|
}
|
|
|
|
static inline int input_available_p(struct tty_struct *tty, int poll)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1;
|
|
|
|
if (ldata->icanon && !L_EXTPROC(tty))
|
|
return ldata->canon_head != ldata->read_tail;
|
|
else
|
|
return ldata->commit_head - ldata->read_tail >= amt;
|
|
}
|
|
|
|
/**
|
|
* 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 bool copy_from_read_buf(struct tty_struct *tty,
|
|
unsigned char **kbp,
|
|
size_t *nr)
|
|
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
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);
|
|
|
|
n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail);
|
|
n = min(*nr, n);
|
|
if (n) {
|
|
unsigned char *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;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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 bool canon_copy_from_read_buf(struct tty_struct *tty,
|
|
unsigned char **kbp,
|
|
size_t *nr)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
size_t n, size, more, c;
|
|
size_t eol;
|
|
size_t tail, canon_head;
|
|
int found = 0;
|
|
|
|
/* N.B. avoid overrun if nr == 0 */
|
|
if (!*nr)
|
|
return false;
|
|
|
|
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);
|
|
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_first_bit(ldata->read_flags, more);
|
|
found = eol != more;
|
|
} else
|
|
found = eol != size;
|
|
|
|
n = eol - tail;
|
|
if (n > N_TTY_BUF_SIZE)
|
|
n += N_TTY_BUF_SIZE;
|
|
c = n + found;
|
|
|
|
if (!found || read_buf(ldata, eol) != __DISABLED_CHAR)
|
|
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);
|
|
|
|
tty_copy(tty, *kbp, tail, n);
|
|
*kbp += n;
|
|
*nr -= n;
|
|
|
|
if (found)
|
|
clear_bit(eol, ldata->read_flags);
|
|
smp_store_release(&ldata->read_tail, ldata->read_tail + c);
|
|
|
|
if (found) {
|
|
if (!ldata->push)
|
|
ldata->line_start = ldata->read_tail;
|
|
else
|
|
ldata->push = 0;
|
|
tty_audit_push();
|
|
return false;
|
|
}
|
|
|
|
/* No EOL found - do a continuation retry if there is more data */
|
|
return ldata->read_tail != canon_head;
|
|
}
|
|
|
|
/*
|
|
* 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 tty_struct *tty)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
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->ctrl.pgrp
|
|
*/
|
|
static int job_control(struct tty_struct *tty, struct file *file)
|
|
{
|
|
/* Job control check -- must be done at start and after
|
|
every sleep (POSIX.1 7.1.1.4). */
|
|
/* 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_iter == redirected_tty_write)
|
|
return 0;
|
|
|
|
return __tty_check_change(tty, SIGTTIN);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 *kbuf, size_t nr,
|
|
void **cookie, unsigned long offset)
|
|
{
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
unsigned char *kb = kbuf;
|
|
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
|
int c;
|
|
int minimum, time;
|
|
ssize_t retval = 0;
|
|
long timeout;
|
|
bool packet;
|
|
size_t tail;
|
|
|
|
/*
|
|
* Is this a continuation of a read started earler?
|
|
*
|
|
* If so, we still hold the atomic_read_lock and the
|
|
* termios_rwsem, and can just continue to copy data.
|
|
*/
|
|
if (*cookie) {
|
|
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(tty);
|
|
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;
|
|
}
|
|
|
|
c = job_control(tty, file);
|
|
if (c < 0)
|
|
return c;
|
|
|
|
/*
|
|
* Internal serialization of reads.
|
|
*/
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
if (!mutex_trylock(&ldata->atomic_read_lock))
|
|
return -EAGAIN;
|
|
} else {
|
|
if (mutex_lock_interruptible(&ldata->atomic_read_lock))
|
|
return -ERESTARTSYS;
|
|
}
|
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
minimum = time = 0;
|
|
timeout = MAX_SCHEDULE_TIMEOUT;
|
|
if (!ldata->icanon) {
|
|
minimum = MIN_CHAR(tty);
|
|
if (minimum) {
|
|
time = (HZ / 10) * TIME_CHAR(tty);
|
|
} else {
|
|
timeout = (HZ / 10) * TIME_CHAR(tty);
|
|
minimum = 1;
|
|
}
|
|
}
|
|
|
|
packet = tty->ctrl.packet;
|
|
tail = ldata->read_tail;
|
|
|
|
add_wait_queue(&tty->read_wait, &wait);
|
|
while (nr) {
|
|
/* First test for status change. */
|
|
if (packet && tty->link->ctrl.pktstatus) {
|
|
unsigned char cs;
|
|
if (kb != kbuf)
|
|
break;
|
|
spin_lock_irq(&tty->link->ctrl.lock);
|
|
cs = tty->link->ctrl.pktstatus;
|
|
tty->link->ctrl.pktstatus = 0;
|
|
spin_unlock_irq(&tty->link->ctrl.lock);
|
|
*kb++ = cs;
|
|
nr--;
|
|
break;
|
|
}
|
|
|
|
if (!input_available_p(tty, 0)) {
|
|
up_read(&tty->termios_rwsem);
|
|
tty_buffer_flush_work(tty->port);
|
|
down_read(&tty->termios_rwsem);
|
|
if (!input_available_p(tty, 0)) {
|
|
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
|
|
retval = -EIO;
|
|
break;
|
|
}
|
|
if (tty_hung_up_p(file))
|
|
break;
|
|
/*
|
|
* Abort readers for ttys which never actually
|
|
* get hung up. See __tty_hangup().
|
|
*/
|
|
if (test_bit(TTY_HUPPING, &tty->flags))
|
|
break;
|
|
if (!timeout)
|
|
break;
|
|
if (tty_io_nonblock(tty, file)) {
|
|
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)) {
|
|
if (canon_copy_from_read_buf(tty, &kb, &nr))
|
|
goto more_to_be_read;
|
|
} else {
|
|
/* Deal with packet mode. */
|
|
if (packet && kb == kbuf) {
|
|
*kb++ = TIOCPKT_DATA;
|
|
nr--;
|
|
}
|
|
|
|
/*
|
|
* Copy data, and if there is more to be had
|
|
* and we have nothing more to wait for, then
|
|
* 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.
|
|
*/
|
|
if (copy_from_read_buf(tty, &kb, &nr) && kb - kbuf >= minimum) {
|
|
more_to_be_read:
|
|
remove_wait_queue(&tty->read_wait, &wait);
|
|
*cookie = cookie;
|
|
return kb - kbuf;
|
|
}
|
|
}
|
|
|
|
n_tty_check_unthrottle(tty);
|
|
|
|
if (kb - kbuf >= minimum)
|
|
break;
|
|
if (time)
|
|
timeout = time;
|
|
}
|
|
if (tail != ldata->read_tail)
|
|
n_tty_kick_worker(tty);
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
remove_wait_queue(&tty->read_wait, &wait);
|
|
mutex_unlock(&ldata->atomic_read_lock);
|
|
|
|
if (kb - kbuf)
|
|
retval = kb - kbuf;
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* 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 unsigned char *b = buf;
|
|
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
|
int c;
|
|
ssize_t retval = 0;
|
|
|
|
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
|
|
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);
|
|
|
|
/* Write out any echoed characters that are still pending */
|
|
process_echoes(tty);
|
|
|
|
add_wait_queue(&tty->write_wait, &wait);
|
|
while (1) {
|
|
if (signal_pending(current)) {
|
|
retval = -ERESTARTSYS;
|
|
break;
|
|
}
|
|
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
|
|
retval = -EIO;
|
|
break;
|
|
}
|
|
if (O_OPOST(tty)) {
|
|
while (nr > 0) {
|
|
ssize_t num = process_output_block(tty, b, nr);
|
|
if (num < 0) {
|
|
if (num == -EAGAIN)
|
|
break;
|
|
retval = num;
|
|
goto break_out;
|
|
}
|
|
b += num;
|
|
nr -= num;
|
|
if (nr == 0)
|
|
break;
|
|
c = *b;
|
|
if (process_output(c, tty) < 0)
|
|
break;
|
|
b++; nr--;
|
|
}
|
|
if (tty->ops->flush_chars)
|
|
tty->ops->flush_chars(tty);
|
|
} else {
|
|
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;
|
|
goto break_out;
|
|
}
|
|
if (!c)
|
|
break;
|
|
b += c;
|
|
nr -= c;
|
|
}
|
|
}
|
|
if (!nr)
|
|
break;
|
|
if (tty_io_nonblock(tty, file)) {
|
|
retval = -EAGAIN;
|
|
break;
|
|
}
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
|
|
|
|
down_read(&tty->termios_rwsem);
|
|
}
|
|
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
|
|
*
|
|
* 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.
|
|
*
|
|
* Locking: called without the kernel lock held -- fine.
|
|
*/
|
|
static __poll_t n_tty_poll(struct tty_struct *tty, struct file *file,
|
|
poll_table *wait)
|
|
{
|
|
__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 |= EPOLLIN | EPOLLRDNORM;
|
|
else {
|
|
tty_buffer_flush_work(tty->port);
|
|
if (input_available_p(tty, 1))
|
|
mask |= EPOLLIN | EPOLLRDNORM;
|
|
}
|
|
if (tty->ctrl.packet && tty->link->ctrl.pktstatus)
|
|
mask |= EPOLLPRI | EPOLLIN | EPOLLRDNORM;
|
|
if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
|
|
mask |= EPOLLHUP;
|
|
if (tty_hung_up_p(file))
|
|
mask |= EPOLLHUP;
|
|
if (tty->ops->write && !tty_is_writelocked(tty) &&
|
|
tty_chars_in_buffer(tty) < WAKEUP_CHARS &&
|
|
tty_write_room(tty) > 0)
|
|
mask |= EPOLLOUT | EPOLLWRNORM;
|
|
return mask;
|
|
}
|
|
|
|
static unsigned long inq_canon(struct n_tty_data *ldata)
|
|
{
|
|
size_t nr, head, tail;
|
|
|
|
if (ldata->canon_head == ldata->read_tail)
|
|
return 0;
|
|
head = ldata->canon_head;
|
|
tail = ldata->read_tail;
|
|
nr = head - tail;
|
|
/* Skip EOF-chars.. */
|
|
while (MASK(head) != MASK(tail)) {
|
|
if (test_bit(tail & (N_TTY_BUF_SIZE - 1), ldata->read_flags) &&
|
|
read_buf(ldata, tail) == __DISABLED_CHAR)
|
|
nr--;
|
|
tail++;
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
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;
|
|
|
|
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) && !L_EXTPROC(tty))
|
|
retval = inq_canon(ldata);
|
|
else
|
|
retval = read_cnt(ldata);
|
|
up_write(&tty->termios_rwsem);
|
|
return put_user(retval, (unsigned int __user *) arg);
|
|
default:
|
|
return n_tty_ioctl_helper(tty, cmd, arg);
|
|
}
|
|
}
|
|
|
|
static struct tty_ldisc_ops n_tty_ops = {
|
|
.owner = THIS_MODULE,
|
|
.num = N_TTY,
|
|
.name = "n_tty",
|
|
.open = n_tty_open,
|
|
.close = n_tty_close,
|
|
.flush_buffer = n_tty_flush_buffer,
|
|
.read = n_tty_read,
|
|
.write = n_tty_write,
|
|
.ioctl = n_tty_ioctl,
|
|
.set_termios = n_tty_set_termios,
|
|
.poll = n_tty_poll,
|
|
.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,
|
|
};
|
|
|
|
/**
|
|
* n_tty_inherit_ops - inherit N_TTY methods
|
|
* @ops: struct tty_ldisc_ops where to save N_TTY methods
|
|
*
|
|
* Enables a 'subclass' line discipline to 'inherit' N_TTY methods.
|
|
*/
|
|
|
|
void n_tty_inherit_ops(struct tty_ldisc_ops *ops)
|
|
{
|
|
*ops = n_tty_ops;
|
|
ops->owner = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(n_tty_inherit_ops);
|
|
|
|
void __init n_tty_init(void)
|
|
{
|
|
tty_register_ldisc(&n_tty_ops);
|
|
}
|