Jiri Slaby (SUSE)tty: n_tty: use guard()s2fc541e11 months agoHistory
2535 lines · 63.2 KB · C
RawDownload
// 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/bitmap.h>#include <linux/bitops.h>#include <linux/ctype.h>#include <linux/errno.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/signal.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/tty.h>#include <linux/types.h>#include <linux/uaccess.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 #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 * 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) 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;	unsigned 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 */	u8 read_buf[N_TTY_BUF_SIZE];	DECLARE_BITMAP(read_flags, 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;	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 u8 read_buf(struct n_tty_data *ldata, size_t i){	return ldata->read_buf[MASK(i)];} static inline u8 *read_buf_addr(struct n_tty_data *ldata, size_t i){	return &ldata->read_buf[MASK(i)];} static inline u8 echo_buf(struct n_tty_data *ldata, size_t i){	smp_rmb(); /* Matches smp_wmb() in add_echo_byte(). */	return ldata->echo_buf[MASK(i)];} static inline u8 *echo_buf_addr(struct n_tty_data *ldata, size_t i){	return &ldata->echo_buf[MASK(i)];} /* 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;	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(const struct tty_struct *tty){	struct n_tty_data *ldata = tty->disc_data; 	/* Did the input worker stop? Restart it */	if (unlikely(READ_ONCE(ldata->no_room))) {		WRITE_ONCE(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(const struct tty_struct *tty){	const struct n_tty_data *ldata = tty->disc_data;	size_t head = ldata->icanon ? ldata->canon_head : ldata->commit_head; 	return head - ldata->read_tail;} /** * 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; 	do {		tty_set_flow_change(tty, TTY_THROTTLE_SAFE);		if (N_TTY_BUF_SIZE - read_cnt(ldata) >= TTY_THRESHOLD_THROTTLE)			break;	} while (!tty_throttle_safe(tty)); 	__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.	 */ 	do {		tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);		if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)			break; 		n_tty_kick_worker(tty);	} while (!tty_unthrottle_safe(tty)); 	__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(u8 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){	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 * * 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){	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);} /** * 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(u8 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(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 * * 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(u8 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(u8 c, struct tty_struct *tty){	struct n_tty_data *ldata = tty->disc_data; 	guard(mutex)(&ldata->output_lock); 	if (do_output_char(c, tty, tty_write_room(tty)) < 0)		return -1; 	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. *

Lines 1–500 of 2535.