diff options
Diffstat (limited to 'drivers/tty/n_tty.c')
| -rw-r--r-- | drivers/tty/n_tty.c | 2053 | 
1 files changed, 1227 insertions, 826 deletions
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 428f4fe0b5f..f44f1ba762c 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -49,8 +49,9 @@  #include <linux/file.h>  #include <linux/uaccess.h>  #include <linux/module.h> +#include <linux/ratelimit.h> +#include <linux/vmalloc.h> -#include <asm/system.h>  /* number of characters left in xmit buffer before select has we have room */  #define WAKEUP_CHARS 256 @@ -61,7 +62,7 @@   * controlling the space in the read buffer.   */  #define TTY_THRESHOLD_THROTTLE		128 /* now based on remaining room */ -#define TTY_THRESHOLD_UNTHROTTLE 	128 +#define TTY_THRESHOLD_UNTHROTTLE	128  /*   * Special byte codes used in the echo buffer to represent operations @@ -74,27 +75,104 @@  #define ECHO_OP_SET_CANON_COL 0x81  #define ECHO_OP_ERASE_TAB 0x82 +#define ECHO_COMMIT_WATERMARK	256 +#define ECHO_BLOCK		256 +#define ECHO_DISCARD_WATERMARK	N_TTY_BUF_SIZE - (ECHO_BLOCK + 32) + + +#undef N_TTY_TRACE +#ifdef N_TTY_TRACE +# define n_tty_trace(f, args...)	trace_printk(f, ##args) +#else +# define n_tty_trace(f, args...) +#endif + +struct n_tty_data { +	/* producer-published */ +	size_t read_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]; + +	int minimum_to_wake; + +	/* consumer-published */ +	size_t read_tail; +	size_t line_start; + +	/* protected by output lock */ +	unsigned int column; +	unsigned int canon_column; +	size_t echo_tail; + +	struct mutex atomic_read_lock; +	struct mutex output_lock; +}; + +static inline size_t read_cnt(struct n_tty_data *ldata) +{ +	return ldata->read_head - ldata->read_tail; +} + +static inline unsigned char read_buf(struct n_tty_data *ldata, size_t i) +{ +	return ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + +static inline unsigned char *read_buf_addr(struct n_tty_data *ldata, size_t i) +{ +	return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + +static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i) +{ +	return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; +} + +static inline unsigned char *echo_buf_addr(struct n_tty_data *ldata, size_t i) +{ +	return &ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)]; +} +  static inline int tty_put_user(struct tty_struct *tty, unsigned char x,  			       unsigned char __user *ptr)  { -	tty_audit_add_data(tty, &x, 1); +	struct n_tty_data *ldata = tty->disc_data; + +	tty_audit_add_data(tty, &x, 1, ldata->icanon);  	return put_user(x, ptr);  } -/** - *	n_tty_set__room	-	receive space - *	@tty: terminal - * - *	Called by the driver to find out how much data it is - *	permitted to feed to the line discipline without any being lost - *	and thus to manage flow control. Not serialized. Answers for the - *	"instant". - */ - -static void n_tty_set_room(struct tty_struct *tty) +static int receive_room(struct tty_struct *tty)  { -	/* tty->read_cnt is not read locked ? */ -	int	left = N_TTY_BUF_SIZE - tty->read_cnt - 1; +	struct n_tty_data *ldata = tty->disc_data; +	int left; + +	if (I_PARMRK(tty)) { +		/* Multiply read_cnt by 3, since each byte might take up to +		 * three times as many spaces when PARMRK is set (depending on +		 * its flags, e.g. parity error). */ +		left = N_TTY_BUF_SIZE - read_cnt(ldata) * 3 - 1; +	} else +		left = N_TTY_BUF_SIZE - read_cnt(ldata) - 1;  	/*  	 * If we are doing input canonicalization, and there are no @@ -103,112 +181,208 @@ static void n_tty_set_room(struct tty_struct *tty)  	 * characters will be beeped.  	 */  	if (left <= 0) -		left = tty->icanon && !tty->canon_data; -	tty->receive_room = left; +		left = ldata->icanon && ldata->canon_head == ldata->read_tail; + +	return left;  } -static void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty) +/** + *	n_tty_set_room	-	receive space + *	@tty: terminal + * + *	Re-schedules the flip buffer work if space just became available. + * + *	Caller holds exclusive termios_rwsem + *	   or + *	n_tty_read()/consumer path: + *		holds non-exclusive termios_rwsem + */ + +static void n_tty_set_room(struct tty_struct *tty)  { -	if (tty->read_cnt < N_TTY_BUF_SIZE) { -		tty->read_buf[tty->read_head] = c; -		tty->read_head = (tty->read_head + 1) & (N_TTY_BUF_SIZE-1); -		tty->read_cnt++; +	struct n_tty_data *ldata = tty->disc_data; + +	/* Did this open up the receive buffer? We may need to flip */ +	if (unlikely(ldata->no_room) && receive_room(tty)) { +		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"); +		queue_work(system_unbound_wq, &tty->port->buf.work);  	}  } +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 = read_cnt(ldata); +	else +		n = ldata->canon_head - ldata->read_tail; +	return n; +} +  /** - *	put_tty_queue		-	add character to tty - *	@c: character + *	n_tty_write_wakeup	-	asynchronous I/O notifier   *	@tty: tty device   * - *	Add a character to the tty read_buf queue. This is done under the - *	read_lock to serialize character addition and also to protect us - *	against parallel reads or flushes + *	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 put_tty_queue(unsigned char c, struct tty_struct *tty) +static void n_tty_write_wakeup(struct tty_struct *tty)  { -	unsigned long flags; +	if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) +		kill_fasync(&tty->fasync, SIGIO, POLL_OUT); +} + +static void n_tty_check_throttle(struct tty_struct *tty) +{ +	if (tty->driver->type == TTY_DRIVER_TYPE_PTY) +		return;  	/* -	 *	The problem of stomping on the buffers ends here. -	 *	Why didn't anyone see this one coming? --AJK -	*/ -	spin_lock_irqsave(&tty->read_lock, flags); -	put_tty_queue_nolock(c, tty); -	spin_unlock_irqrestore(&tty->read_lock, flags); +	 * Check the remaining room for the input canonicalization +	 * mode.  We don't want to throttle the driver if we're in +	 * canonical mode and don't have a newline yet! +	 */ +	while (1) { +		int throttled; +		tty_set_flow_change(tty, TTY_THROTTLE_SAFE); +		if (receive_room(tty) >= 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 && +	    tty->link->ldisc->ops->write_wakeup == n_tty_write_wakeup) { +		if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) +			return; +		if (!tty->count) +			return; +		n_tty_set_room(tty); +		n_tty_write_wakeup(tty->link); +		if (waitqueue_active(&tty->link->write_wait)) +			wake_up_interruptible_poll(&tty->link->write_wait, POLLOUT); +		return; +	} + +	/* If there is enough space in the read buffer now, let the +	 * low-level driver know. We use chars_in_buffer() to +	 * check the buffer, as it now knows about canonical mode. +	 * Otherwise, if the driver is throttled and the line is +	 * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, +	 * we won't get any more characters. +	 */ + +	while (1) { +		int unthrottled; +		tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE); +		if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE) +			break; +		if (!tty->count) +			break; +		n_tty_set_room(tty); +		unthrottled = tty_unthrottle_safe(tty); +		if (!unthrottled) +			break; +	} +	__tty_set_flow_change(tty, 0);  }  /** - *	check_unthrottle	-	allow new receive data - *	@tty; tty device + *	put_tty_queue		-	add character to tty + *	@c: character + *	@ldata: n_tty data + * + *	Add a character to the tty read_buf queue.   * - *	Check whether to call the driver unthrottle functions + *	n_tty_receive_buf()/producer path: + *		caller holds non-exclusive termios_rwsem + *		modifies read_head   * - *	Can sleep, may be called under the atomic_read_lock mutex but - *	this is not guaranteed. + *	read_head is only considered 'published' if canonical mode is + *	not active.   */ -static void check_unthrottle(struct tty_struct *tty) + +static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata)  { -	if (tty->count) -		tty_unthrottle(tty); +	*read_buf_addr(ldata, ldata->read_head++) = c;  }  /**   *	reset_buffer_flags	-	reset buffer state   *	@tty: terminal to reset   * - *	Reset the read buffer counters, clear the flags, - *	and make sure the driver is unthrottled. 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: tty_read_lock for read fields. + *	Locking: caller holds exclusive termios_rwsem + *		 (or locking is not required)   */ -static void reset_buffer_flags(struct tty_struct *tty) +static void reset_buffer_flags(struct n_tty_data *ldata)  { -	unsigned long flags; - -	spin_lock_irqsave(&tty->read_lock, flags); -	tty->read_head = tty->read_tail = tty->read_cnt = 0; -	spin_unlock_irqrestore(&tty->read_lock, flags); +	ldata->read_head = ldata->canon_head = ldata->read_tail = 0; +	ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; +	ldata->echo_mark = 0; +	ldata->line_start = 0; + +	ldata->erasing = 0; +	bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); +	ldata->push = 0; +} -	mutex_lock(&tty->echo_lock); -	tty->echo_pos = tty->echo_cnt = tty->echo_overrun = 0; -	mutex_unlock(&tty->echo_lock); +static void n_tty_packet_mode_flush(struct tty_struct *tty) +{ +	unsigned long flags; -	tty->canon_head = tty->canon_data = tty->erasing = 0; -	memset(&tty->read_flags, 0, sizeof tty->read_flags); -	n_tty_set_room(tty); -	check_unthrottle(tty); +	spin_lock_irqsave(&tty->ctrl_lock, flags); +	if (tty->link->packet) { +		tty->ctrl_status |= TIOCPKT_FLUSHREAD; +		if (waitqueue_active(&tty->link->read_wait)) +			wake_up_interruptible(&tty->link->read_wait); +	} +	spin_unlock_irqrestore(&tty->ctrl_lock, flags);  }  /**   *	n_tty_flush_buffer	-	clean input queue   *	@tty:	terminal device   * - *	Flush the input buffer. Called when the line discipline is - *	being closed, 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).   * - *	Locking: ctrl_lock, read_lock. + *	Holds termios_rwsem to exclude producer/consumer while + *	buffer indices are reset. + * + *	Locking: ctrl_lock, exclusive termios_rwsem   */  static void n_tty_flush_buffer(struct tty_struct *tty)  { -	unsigned long flags; -	/* clear everything and unthrottle the driver */ -	reset_buffer_flags(tty); - -	if (!tty->link) -		return; +	down_write(&tty->termios_rwsem); +	reset_buffer_flags(tty->disc_data); +	n_tty_set_room(tty); -	spin_lock_irqsave(&tty->ctrl_lock, flags); -	if (tty->link->packet) { -		tty->ctrl_status |= TIOCPKT_FLUSHREAD; -		wake_up_interruptible(&tty->link->read_wait); -	} -	spin_unlock_irqrestore(&tty->ctrl_lock, flags); +	if (tty->link) +		n_tty_packet_mode_flush(tty); +	up_write(&tty->termios_rwsem);  }  /** @@ -218,23 +392,18 @@ static void n_tty_flush_buffer(struct tty_struct *tty)   *	Report the number of characters buffered to be delivered to user   *	at this instant in time.   * - *	Locking: read_lock + *	Locking: exclusive termios_rwsem   */  static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)  { -	unsigned long flags; -	ssize_t n = 0; +	ssize_t n; -	spin_lock_irqsave(&tty->read_lock, flags); -	if (!tty->icanon) { -		n = tty->read_cnt; -	} else if (tty->canon_data) { -		n = (tty->canon_head > tty->read_tail) ? -			tty->canon_head - tty->read_tail : -			tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail); -	} -	spin_unlock_irqrestore(&tty->read_lock, flags); +	WARN_ONCE(1, "%s is deprecated and scheduled for removal.", __func__); + +	down_write(&tty->termios_rwsem); +	n = chars_in_buffer(tty); +	up_write(&tty->termios_rwsem);  	return n;  } @@ -289,6 +458,7 @@ static inline int is_continuation(unsigned char c, struct tty_struct *tty)  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) @@ -297,48 +467,48 @@ static int do_output_char(unsigned char c, struct tty_struct *tty, int space)  	switch (c) {  	case '\n':  		if (O_ONLRET(tty)) -			tty->column = 0; +			ldata->column = 0;  		if (O_ONLCR(tty)) {  			if (space < 2)  				return -1; -			tty->canon_column = tty->column = 0; +			ldata->canon_column = ldata->column = 0;  			tty->ops->write(tty, "\r\n", 2);  			return 2;  		} -		tty->canon_column = tty->column; +		ldata->canon_column = ldata->column;  		break;  	case '\r': -		if (O_ONOCR(tty) && tty->column == 0) +		if (O_ONOCR(tty) && ldata->column == 0)  			return 0;  		if (O_OCRNL(tty)) {  			c = '\n';  			if (O_ONLRET(tty)) -				tty->canon_column = tty->column = 0; +				ldata->canon_column = ldata->column = 0;  			break;  		} -		tty->canon_column = tty->column = 0; +		ldata->canon_column = ldata->column = 0;  		break;  	case '\t': -		spaces = 8 - (tty->column & 7); +		spaces = 8 - (ldata->column & 7);  		if (O_TABDLY(tty) == XTABS) {  			if (space < spaces)  				return -1; -			tty->column += spaces; +			ldata->column += spaces;  			tty->ops->write(tty, "        ", spaces);  			return spaces;  		} -		tty->column += spaces; +		ldata->column += spaces;  		break;  	case '\b': -		if (tty->column > 0) -			tty->column--; +		if (ldata->column > 0) +			ldata->column--;  		break;  	default:  		if (!iscntrl(c)) {  			if (O_OLCUC(tty))  				c = toupper(c);  			if (!is_continuation(c, tty)) -				tty->column++; +				ldata->column++;  		}  		break;  	} @@ -363,14 +533,15 @@ static int do_output_char(unsigned char c, struct tty_struct *tty, int space)  static int process_output(unsigned char c, struct tty_struct *tty)  { +	struct n_tty_data *ldata = tty->disc_data;  	int	space, retval; -	mutex_lock(&tty->output_lock); +	mutex_lock(&ldata->output_lock);  	space = tty_write_room(tty);  	retval = do_output_char(c, tty, space); -	mutex_unlock(&tty->output_lock); +	mutex_unlock(&ldata->output_lock);  	if (retval < 0)  		return -1;  	else @@ -399,15 +570,16 @@ static int process_output(unsigned char c, struct tty_struct *tty)  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; +	int	i;  	const unsigned char *cp; -	mutex_lock(&tty->output_lock); +	mutex_lock(&ldata->output_lock);  	space = tty_write_room(tty);  	if (!space) { -		mutex_unlock(&tty->output_lock); +		mutex_unlock(&ldata->output_lock);  		return 0;  	}  	if (nr > space) @@ -419,30 +591,30 @@ static ssize_t process_output_block(struct tty_struct *tty,  		switch (c) {  		case '\n':  			if (O_ONLRET(tty)) -				tty->column = 0; +				ldata->column = 0;  			if (O_ONLCR(tty))  				goto break_out; -			tty->canon_column = tty->column; +			ldata->canon_column = ldata->column;  			break;  		case '\r': -			if (O_ONOCR(tty) && tty->column == 0) +			if (O_ONOCR(tty) && ldata->column == 0)  				goto break_out;  			if (O_OCRNL(tty))  				goto break_out; -			tty->canon_column = tty->column = 0; +			ldata->canon_column = ldata->column = 0;  			break;  		case '\t':  			goto break_out;  		case '\b': -			if (tty->column > 0) -				tty->column--; +			if (ldata->column > 0) +				ldata->column--;  			break;  		default:  			if (!iscntrl(c)) {  				if (O_OLCUC(tty))  					goto break_out;  				if (!is_continuation(c, tty)) -					tty->column++; +					ldata->column++;  			}  			break;  		} @@ -450,7 +622,7 @@ static ssize_t process_output_block(struct tty_struct *tty,  break_out:  	i = tty->ops->write(tty, buf, i); -	mutex_unlock(&tty->output_lock); +	mutex_unlock(&ldata->output_lock);  	return i;  } @@ -476,32 +648,23 @@ break_out:   *	are prioritized.  Also, when control characters are echoed with a   *	prefixed "^", the pair is treated atomically and thus not separated.   * - *	Locking: output_lock to protect column state and space left, - *		 echo_lock to protect the echo buffer + *	Locking: callers must hold output_lock   */ -static void process_echoes(struct tty_struct *tty) +static size_t __process_echoes(struct tty_struct *tty)  { -	int	space, nr; +	struct n_tty_data *ldata = tty->disc_data; +	int	space, old_space; +	size_t tail;  	unsigned char c; -	unsigned char *cp, *buf_end; -	if (!tty->echo_cnt) -		return; - -	mutex_lock(&tty->output_lock); -	mutex_lock(&tty->echo_lock); - -	space = tty_write_room(tty); +	old_space = space = tty_write_room(tty); -	buf_end = tty->echo_buf + N_TTY_BUF_SIZE; -	cp = tty->echo_buf + tty->echo_pos; -	nr = tty->echo_cnt; -	while (nr > 0) { -		c = *cp; +	tail = ldata->echo_tail; +	while (ldata->echo_commit != tail) { +		c = echo_buf(ldata, tail);  		if (c == ECHO_OP_START) {  			unsigned char op; -			unsigned char *opp;  			int no_space_left = 0;  			/* @@ -509,18 +672,13 @@ static void process_echoes(struct tty_struct *tty)  			 * operation, get the next byte, which is either the  			 * op code or a control character value.  			 */ -			opp = cp + 1; -			if (opp == buf_end) -				opp -= N_TTY_BUF_SIZE; -			op = *opp; +			op = echo_buf(ldata, tail + 1);  			switch (op) {  				unsigned int num_chars, num_bs;  			case ECHO_OP_ERASE_TAB: -				if (++opp == buf_end) -					opp -= N_TTY_BUF_SIZE; -				num_chars = *opp; +				num_chars = echo_buf(ldata, tail + 2);  				/*  				 * Determine how many columns to go back @@ -533,7 +691,7 @@ static void process_echoes(struct tty_struct *tty)  				 * Otherwise, tab spacing is normal.  				 */  				if (!(num_chars & 0x80)) -					num_chars += tty->canon_column; +					num_chars += ldata->canon_column;  				num_bs = 8 - (num_chars & 7);  				if (num_bs > space) { @@ -543,24 +701,21 @@ static void process_echoes(struct tty_struct *tty)  				space -= num_bs;  				while (num_bs--) {  					tty_put_char(tty, '\b'); -					if (tty->column > 0) -						tty->column--; +					if (ldata->column > 0) +						ldata->column--;  				} -				cp += 3; -				nr -= 3; +				tail += 3;  				break;  			case ECHO_OP_SET_CANON_COL: -				tty->canon_column = tty->column; -				cp += 2; -				nr -= 2; +				ldata->canon_column = ldata->column; +				tail += 2;  				break;  			case ECHO_OP_MOVE_BACK_COL: -				if (tty->column > 0) -					tty->column--; -				cp += 2; -				nr -= 2; +				if (ldata->column > 0) +					ldata->column--; +				tail += 2;  				break;  			case ECHO_OP_START: @@ -570,10 +725,9 @@ static void process_echoes(struct tty_struct *tty)  					break;  				}  				tty_put_char(tty, ECHO_OP_START); -				tty->column++; +				ldata->column++;  				space--; -				cp += 2; -				nr -= 2; +				tail += 2;  				break;  			default: @@ -592,17 +746,15 @@ static void process_echoes(struct tty_struct *tty)  				}  				tty_put_char(tty, '^');  				tty_put_char(tty, op ^ 0100); -				tty->column += 2; +				ldata->column += 2;  				space -= 2; -				cp += 2; -				nr -= 2; +				tail += 2;  			}  			if (no_space_left)  				break;  		} else { -			if (O_OPOST(tty) && -			    !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { +			if (O_OPOST(tty)) {  				int retval = do_output_char(c, tty, space);  				if (retval < 0)  					break; @@ -613,126 +765,130 @@ static void process_echoes(struct tty_struct *tty)  				tty_put_char(tty, c);  				space -= 1;  			} -			cp += 1; -			nr -= 1; +			tail += 1;  		} - -		/* When end of circular buffer reached, wrap around */ -		if (cp >= buf_end) -			cp -= N_TTY_BUF_SIZE;  	} -	if (nr == 0) { -		tty->echo_pos = 0; -		tty->echo_cnt = 0; -		tty->echo_overrun = 0; -	} else { -		int num_processed = tty->echo_cnt - nr; -		tty->echo_pos += num_processed; -		tty->echo_pos &= N_TTY_BUF_SIZE - 1; -		tty->echo_cnt = nr; -		if (num_processed > 0) -			tty->echo_overrun = 0; +	/* If the echo buffer is nearly full (so that the possibility exists +	 * of echo overrun before the next commit), then discard enough +	 * data at the tail to prevent a subsequent overrun */ +	while (ldata->echo_commit - tail >= ECHO_DISCARD_WATERMARK) { +		if (echo_buf(ldata, tail) == ECHO_OP_START) { +			if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB) +				tail += 3; +			else +				tail += 2; +		} else +			tail++;  	} -	mutex_unlock(&tty->echo_lock); -	mutex_unlock(&tty->output_lock); +	ldata->echo_tail = tail; +	return old_space - space; +} + +static void commit_echoes(struct tty_struct *tty) +{ +	struct n_tty_data *ldata = tty->disc_data; +	size_t nr, old, echoed; +	size_t head; + +	head = ldata->echo_head; +	ldata->echo_mark = head; +	old = ldata->echo_commit - ldata->echo_tail; + +	/* Process committed echoes if the accumulated # of bytes +	 * is over the threshold (and try again each time another +	 * block is accumulated) */ +	nr = head - ldata->echo_tail; +	if (nr < ECHO_COMMIT_WATERMARK || (nr % ECHO_BLOCK > old % ECHO_BLOCK)) +		return; + +	mutex_lock(&ldata->output_lock); +	ldata->echo_commit = head; +	echoed = __process_echoes(tty); +	mutex_unlock(&ldata->output_lock); + +	if (echoed && tty->ops->flush_chars) +		tty->ops->flush_chars(tty); +} + +static void process_echoes(struct tty_struct *tty) +{ +	struct n_tty_data *ldata = tty->disc_data; +	size_t echoed; + +	if (ldata->echo_mark == ldata->echo_tail) +		return; + +	mutex_lock(&ldata->output_lock); +	ldata->echo_commit = ldata->echo_mark; +	echoed = __process_echoes(tty); +	mutex_unlock(&ldata->output_lock); -	if (tty->ops->flush_chars) +	if (echoed && tty->ops->flush_chars)  		tty->ops->flush_chars(tty);  } +/* NB: echo_mark and echo_head should be equivalent here */ +static void flush_echoes(struct tty_struct *tty) +{ +	struct n_tty_data *ldata = tty->disc_data; + +	if ((!L_ECHO(tty) && !L_ECHONL(tty)) || +	    ldata->echo_commit == ldata->echo_head) +		return; + +	mutex_lock(&ldata->output_lock); +	ldata->echo_commit = ldata->echo_head; +	__process_echoes(tty); +	mutex_unlock(&ldata->output_lock); +} +  /**   *	add_echo_byte	-	add a byte to the echo buffer   *	@c: unicode byte to echo - *	@tty: terminal device + *	@ldata: n_tty data   *   *	Add a character or operation byte to the echo buffer. - * - *	Should be called under the echo lock to protect the echo buffer.   */ -static void add_echo_byte(unsigned char c, struct tty_struct *tty) +static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata)  { -	int	new_byte_pos; - -	if (tty->echo_cnt == N_TTY_BUF_SIZE) { -		/* Circular buffer is already at capacity */ -		new_byte_pos = tty->echo_pos; - -		/* -		 * Since the buffer start position needs to be advanced, -		 * be sure to step by a whole operation byte group. -		 */ -		if (tty->echo_buf[tty->echo_pos] == ECHO_OP_START) { -			if (tty->echo_buf[(tty->echo_pos + 1) & -					  (N_TTY_BUF_SIZE - 1)] == -						ECHO_OP_ERASE_TAB) { -				tty->echo_pos += 3; -				tty->echo_cnt -= 2; -			} else { -				tty->echo_pos += 2; -				tty->echo_cnt -= 1; -			} -		} else { -			tty->echo_pos++; -		} -		tty->echo_pos &= N_TTY_BUF_SIZE - 1; - -		tty->echo_overrun = 1; -	} else { -		new_byte_pos = tty->echo_pos + tty->echo_cnt; -		new_byte_pos &= N_TTY_BUF_SIZE - 1; -		tty->echo_cnt++; -	} - -	tty->echo_buf[new_byte_pos] = c; +	*echo_buf_addr(ldata, ldata->echo_head++) = c;  }  /**   *	echo_move_back_col	-	add operation to move back a column - *	@tty: terminal device + *	@ldata: n_tty data   *   *	Add an operation to the echo buffer to move back one column. - * - *	Locking: echo_lock to protect the echo buffer   */ -static void echo_move_back_col(struct tty_struct *tty) +static void echo_move_back_col(struct n_tty_data *ldata)  { -	mutex_lock(&tty->echo_lock); - -	add_echo_byte(ECHO_OP_START, tty); -	add_echo_byte(ECHO_OP_MOVE_BACK_COL, tty); - -	mutex_unlock(&tty->echo_lock); +	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 - *	@tty: terminal device + *	@ldata: n_tty data   *   *	Add an operation to the echo buffer to set the canon column   *	to the current column. - * - *	Locking: echo_lock to protect the echo buffer   */ -static void echo_set_canon_col(struct tty_struct *tty) +static void echo_set_canon_col(struct n_tty_data *ldata)  { -	mutex_lock(&tty->echo_lock); - -	add_echo_byte(ECHO_OP_START, tty); -	add_echo_byte(ECHO_OP_SET_CANON_COL, tty); - -	mutex_unlock(&tty->echo_lock); +	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 - *	@tty: terminal device + *	@ldata: n_tty data   *   *	Add an operation to the echo buffer to erase a tab.   * @@ -741,17 +897,13 @@ static void echo_set_canon_col(struct tty_struct *tty)   *	of input.  This information will be used later, along with   *	canon column (if applicable), to go back the correct number   *	of columns. - * - *	Locking: echo_lock to protect the echo buffer   */  static void echo_erase_tab(unsigned int num_chars, int after_tab, -			   struct tty_struct *tty) +			   struct n_tty_data *ldata)  { -	mutex_lock(&tty->echo_lock); - -	add_echo_byte(ECHO_OP_START, tty); -	add_echo_byte(ECHO_OP_ERASE_TAB, tty); +	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; @@ -760,9 +912,7 @@ static void echo_erase_tab(unsigned int num_chars, int after_tab,  	if (after_tab)  		num_chars |= 0x80; -	add_echo_byte(num_chars, tty); - -	mutex_unlock(&tty->echo_lock); +	add_echo_byte(num_chars, ldata);  }  /** @@ -774,22 +924,16 @@ static void echo_erase_tab(unsigned int num_chars, int after_tab,   *	L_ECHO(tty) is true. Called from the driver receive_buf path.   *   *	This variant does not treat control characters specially. - * - *	Locking: echo_lock to protect the echo buffer   */ -static void echo_char_raw(unsigned char c, struct tty_struct *tty) +static void echo_char_raw(unsigned char c, struct n_tty_data *ldata)  { -	mutex_lock(&tty->echo_lock); -  	if (c == ECHO_OP_START) { -		add_echo_byte(ECHO_OP_START, tty); -		add_echo_byte(ECHO_OP_START, tty); +		add_echo_byte(ECHO_OP_START, ldata); +		add_echo_byte(ECHO_OP_START, ldata);  	} else { -		add_echo_byte(c, tty); +		add_echo_byte(c, ldata);  	} - -	mutex_unlock(&tty->echo_lock);  }  /** @@ -802,36 +946,32 @@ static void echo_char_raw(unsigned char c, struct tty_struct *tty)   *   *	This variant tags control characters to be echoed as "^X"   *	(where X is the letter representing the control char). - * - *	Locking: echo_lock to protect the echo buffer   */  static void echo_char(unsigned char c, struct tty_struct *tty)  { -	mutex_lock(&tty->echo_lock); +	struct n_tty_data *ldata = tty->disc_data;  	if (c == ECHO_OP_START) { -		add_echo_byte(ECHO_OP_START, tty); -		add_echo_byte(ECHO_OP_START, tty); +		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, tty); -		add_echo_byte(c, tty); +			add_echo_byte(ECHO_OP_START, ldata); +		add_echo_byte(c, ldata);  	} - -	mutex_unlock(&tty->echo_lock);  }  /**   *	finish_erasing		-	complete erase - *	@tty: tty doing the erase + *	@ldata: n_tty data   */ -static inline void finish_erasing(struct tty_struct *tty) +static inline void finish_erasing(struct n_tty_data *ldata)  { -	if (tty->erasing) { -		echo_char_raw('/', tty); -		tty->erasing = 0; +	if (ldata->erasing) { +		echo_char_raw('/', ldata); +		ldata->erasing = 0;  	}  } @@ -844,17 +984,23 @@ static inline void finish_erasing(struct tty_struct *tty)   *	present in the stream from the driver layer. Handles the complexities   *	of UTF-8 multibyte symbols.   * - *	Locking: read_lock for tty buffers + *	n_tty_receive_buf()/producer path: + *		caller holds non-exclusive termios_rwsem + *		modifies read_head + * + *	Modifying the read_head is not considered a publish in this context + *	because canonical mode is active -- only canon_head publishes   */  static void eraser(unsigned char c, struct tty_struct *tty)  { +	struct n_tty_data *ldata = tty->disc_data;  	enum { ERASE, WERASE, KILL } kill_type; -	int head, seen_alnums, cnt; -	unsigned long flags; +	size_t head; +	size_t cnt; +	int seen_alnums; -	/* FIXME: locking needed ? */ -	if (tty->read_head == tty->canon_head) { +	if (ldata->read_head == ldata->canon_head) {  		/* process_output('\a', tty); */ /* what do you think? */  		return;  	} @@ -864,39 +1010,30 @@ static void eraser(unsigned char c, struct tty_struct *tty)  		kill_type = WERASE;  	else {  		if (!L_ECHO(tty)) { -			spin_lock_irqsave(&tty->read_lock, flags); -			tty->read_cnt -= ((tty->read_head - tty->canon_head) & -					  (N_TTY_BUF_SIZE - 1)); -			tty->read_head = tty->canon_head; -			spin_unlock_irqrestore(&tty->read_lock, flags); +			ldata->read_head = ldata->canon_head;  			return;  		}  		if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) { -			spin_lock_irqsave(&tty->read_lock, flags); -			tty->read_cnt -= ((tty->read_head - tty->canon_head) & -					  (N_TTY_BUF_SIZE - 1)); -			tty->read_head = tty->canon_head; -			spin_unlock_irqrestore(&tty->read_lock, flags); -			finish_erasing(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', tty); +				echo_char_raw('\n', ldata);  			return;  		}  		kill_type = KILL;  	}  	seen_alnums = 0; -	/* FIXME: Locking ?? */ -	while (tty->read_head != tty->canon_head) { -		head = tty->read_head; +	while (ldata->read_head != ldata->canon_head) { +		head = ldata->read_head;  		/* erase a single possibly multibyte character */  		do { -			head = (head - 1) & (N_TTY_BUF_SIZE-1); -			c = tty->read_buf[head]; -		} while (is_continuation(c, tty) && head != tty->canon_head); +			head--; +			c = read_buf(ldata, head); +		} while (is_continuation(c, tty) && head != ldata->canon_head);  		/* do not partially erase */  		if (is_continuation(c, tty)) @@ -909,30 +1046,27 @@ static void eraser(unsigned char c, struct tty_struct *tty)  			else if (seen_alnums)  				break;  		} -		cnt = (tty->read_head - head) & (N_TTY_BUF_SIZE-1); -		spin_lock_irqsave(&tty->read_lock, flags); -		tty->read_head = head; -		tty->read_cnt -= cnt; -		spin_unlock_irqrestore(&tty->read_lock, flags); +		cnt = ldata->read_head - head; +		ldata->read_head = head;  		if (L_ECHO(tty)) {  			if (L_ECHOPRT(tty)) { -				if (!tty->erasing) { -					echo_char_raw('\\', tty); -					tty->erasing = 1; +				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 = (head+1) & (N_TTY_BUF_SIZE-1); -					echo_char_raw(tty->read_buf[head], tty); -					echo_move_back_col(tty); +					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; -				unsigned long tail = tty->read_head; +				size_t tail = ldata->read_head;  				/*  				 * Count the columns used for characters @@ -941,9 +1075,9 @@ static void eraser(unsigned char c, struct tty_struct *tty)  				 * This info is used to go back the correct  				 * number of columns.  				 */ -				while (tail != tty->canon_head) { -					tail = (tail-1) & (N_TTY_BUF_SIZE-1); -					c = tty->read_buf[tail]; +				while (tail != ldata->canon_head) { +					tail--; +					c = read_buf(ldata, tail);  					if (c == '\t') {  						after_tab = 1;  						break; @@ -954,48 +1088,44 @@ static void eraser(unsigned char c, struct tty_struct *tty)  						num_chars++;  					}  				} -				echo_erase_tab(num_chars, after_tab, tty); +				echo_erase_tab(num_chars, after_tab, ldata);  			} else {  				if (iscntrl(c) && L_ECHOCTL(tty)) { -					echo_char_raw('\b', tty); -					echo_char_raw(' ', tty); -					echo_char_raw('\b', 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', tty); -					echo_char_raw(' ', tty); -					echo_char_raw('\b', tty); +					echo_char_raw('\b', ldata); +					echo_char_raw(' ', ldata); +					echo_char_raw('\b', ldata);  				}  			}  		}  		if (kill_type == ERASE)  			break;  	} -	if (tty->read_head == tty->canon_head && L_ECHO(tty)) -		finish_erasing(tty); +	if (ldata->read_head == ldata->canon_head && L_ECHO(tty)) +		finish_erasing(ldata);  }  /**   *	isig		-	handle the ISIG optio   *	@sig: signal   *	@tty: terminal - *	@flush: force flush   * - *	Called when a signal is being sent due to terminal input. This - *	may caus terminal flushing to take place according to the termios - *	settings and character used. Called from the driver receive_buf - *	path so serialized. + *	Called when a signal is being sent due to terminal input. + *	Called from the driver receive_buf path so serialized.   * - *	Locking: ctrl_lock, read_lock (both via flush buffer) + *	Locking: ctrl_lock   */ -static inline void isig(int sig, struct tty_struct *tty, int flush) +static void isig(int sig, struct tty_struct *tty)  { -	if (tty->pgrp) -		kill_pgrp(tty->pgrp, sig, 1); -	if (flush || !L_NOFLSH(tty)) { -		n_tty_flush_buffer(tty); -		tty_driver_flush_buffer(tty); +	struct pid *tty_pgrp = tty_get_pgrp(tty); +	if (tty_pgrp) { +		kill_pgrp(tty_pgrp, sig, 1); +		put_pid(tty_pgrp);  	}  } @@ -1006,23 +1136,37 @@ static inline void isig(int sig, struct tty_struct *tty, int flush)   *	An RS232 break event has been hit in the incoming bitstream. This   *	can cause a variety of events depending upon the termios settings.   * - *	Called from the receive_buf path so single threaded. + *	n_tty_receive_buf()/producer path: + *		caller holds non-exclusive termios_rwsem + *		publishes read_head via put_tty_queue() + * + *	Note: may get exclusive termios_rwsem if flushing input buffer   */ -static inline void n_tty_receive_break(struct tty_struct *tty) +static void n_tty_receive_break(struct tty_struct *tty)  { +	struct n_tty_data *ldata = tty->disc_data; +  	if (I_IGNBRK(tty))  		return;  	if (I_BRKINT(tty)) { -		isig(SIGINT, tty, 1); +		isig(SIGINT, tty); +		if (!L_NOFLSH(tty)) { +			/* flushing needs exclusive termios_rwsem */ +			up_read(&tty->termios_rwsem); +			n_tty_flush_buffer(tty); +			tty_driver_flush_buffer(tty); +			down_read(&tty->termios_rwsem); +		}  		return;  	}  	if (I_PARMRK(tty)) { -		put_tty_queue('\377', tty); -		put_tty_queue('\0', tty); +		put_tty_queue('\377', ldata); +		put_tty_queue('\0', ldata);  	} -	put_tty_queue('\0', tty); -	wake_up_interruptible(&tty->read_wait); +	put_tty_queue('\0', ldata); +	if (waitqueue_active(&tty->read_wait)) +		wake_up_interruptible(&tty->read_wait);  }  /** @@ -1038,18 +1182,19 @@ static inline void n_tty_receive_break(struct tty_struct *tty)   *	private.   */ -static inline void n_tty_receive_overrun(struct tty_struct *tty) +static void n_tty_receive_overrun(struct tty_struct *tty)  { +	struct n_tty_data *ldata = tty->disc_data;  	char buf[64]; -	tty->num_overrun++; -	if (time_before(tty->overrun_time, jiffies - HZ) || -			time_after(tty->overrun_time, jiffies)) { +	ldata->num_overrun++; +	if (time_after(jiffies, ldata->overrun_time + HZ) || +			time_after(ldata->overrun_time, jiffies)) {  		printk(KERN_WARNING "%s: %d input overrun(s)\n",  			tty_name(tty, buf), -			tty->num_overrun); -		tty->overrun_time = jiffies; -		tty->num_overrun = 0; +			ldata->num_overrun); +		ldata->overrun_time = jiffies; +		ldata->num_overrun = 0;  	}  } @@ -1059,22 +1204,50 @@ static inline void n_tty_receive_overrun(struct tty_struct *tty)   *	@c: character   *   *	Process a parity error and queue the right data to indicate - *	the error case if necessary. Locking as per n_tty_receive_buf. + *	the error case if necessary. + * + *	n_tty_receive_buf()/producer path: + *		caller holds non-exclusive termios_rwsem + *		publishes read_head via put_tty_queue()   */ -static inline void n_tty_receive_parity_error(struct tty_struct *tty, -					      unsigned char c) +static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c)  { -	if (I_IGNPAR(tty)) -		return; -	if (I_PARMRK(tty)) { -		put_tty_queue('\377', tty); -		put_tty_queue('\0', tty); -		put_tty_queue(c, tty); -	} else	if (I_INPCK(tty)) -		put_tty_queue('\0', tty); -	else -		put_tty_queue(c, tty); -	wake_up_interruptible(&tty->read_wait); +	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); +	if (waitqueue_active(&tty->read_wait)) +		wake_up_interruptible(&tty->read_wait); +} + +static void +n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c) +{ +	if (!L_NOFLSH(tty)) { +		/* flushing needs exclusive termios_rwsem */ +		up_read(&tty->termios_rwsem); +		n_tty_flush_buffer(tty); +		tty_driver_flush_buffer(tty); +		down_read(&tty->termios_rwsem); +	} +	if (I_IXON(tty)) +		start_tty(tty); +	if (L_ECHO(tty)) { +		echo_char(c, tty); +		commit_echoes(tty); +	} else +		process_echoes(tty); +	isig(signal, tty); +	return;  }  /** @@ -1085,260 +1258,257 @@ static inline void n_tty_receive_parity_error(struct tty_struct *tty,   *	Process an individual character of input received from the driver.   *	This is serialized with respect to itself by the rules for the   *	driver above. + * + *	n_tty_receive_buf()/producer path: + *		caller holds non-exclusive termios_rwsem + *		publishes canon_head if canonical mode is active + *		otherwise, publishes read_head via put_tty_queue() + * + *	Returns 1 if LNEXT was received, else returns 0   */ -static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c) +static int +n_tty_receive_char_special(struct tty_struct *tty, unsigned char c)  { -	unsigned long flags; -	int parmrk; - -	if (tty->raw) { -		put_tty_queue(c, tty); -		return; -	} - -	if (I_ISTRIP(tty)) -		c &= 0x7f; -	if (I_IUCLC(tty) && L_IEXTEN(tty)) -		c = tolower(c); - -	if (L_EXTPROC(tty)) { -		put_tty_queue(c, tty); -		return; -	} - -	if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && -	    I_IXANY(tty) && c != START_CHAR(tty) && c != STOP_CHAR(tty) && -	    c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && c != SUSP_CHAR(tty)) { -		start_tty(tty); -		process_echoes(tty); -	} - -	if (tty->closing) { -		if (I_IXON(tty)) { -			if (c == START_CHAR(tty)) { -				start_tty(tty); -				process_echoes(tty); -			} else if (c == STOP_CHAR(tty)) -				stop_tty(tty); -		} -		return; -	} - -	/* -	 * If the previous character was LNEXT, or we know that this -	 * character is not one of the characters that we'll have to -	 * handle specially, do shortcut processing to speed things -	 * up. -	 */ -	if (!test_bit(c, tty->process_char_map) || tty->lnext) { -		tty->lnext = 0; -		parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; -		if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk - 1)) { -			/* beep if no space */ -			if (L_ECHO(tty)) -				process_output('\a', tty); -			return; -		} -		if (L_ECHO(tty)) { -			finish_erasing(tty); -			/* Record the column of first canon char. */ -			if (tty->canon_head == tty->read_head) -				echo_set_canon_col(tty); -			echo_char(c, tty); -			process_echoes(tty); -		} -		if (parmrk) -			put_tty_queue(c, tty); -		put_tty_queue(c, tty); -		return; -	} +	struct n_tty_data *ldata = tty->disc_data;  	if (I_IXON(tty)) {  		if (c == START_CHAR(tty)) {  			start_tty(tty);  			process_echoes(tty); -			return; +			return 0;  		}  		if (c == STOP_CHAR(tty)) {  			stop_tty(tty); -			return; +			return 0;  		}  	}  	if (L_ISIG(tty)) { -		int signal; -		signal = SIGINT; -		if (c == INTR_CHAR(tty)) -			goto send_signal; -		signal = SIGQUIT; -		if (c == QUIT_CHAR(tty)) -			goto send_signal; -		signal = SIGTSTP; -		if (c == SUSP_CHAR(tty)) { -send_signal: -			/* -			 * Note that we do not use isig() here because we want -			 * the order to be: -			 * 1) flush, 2) echo, 3) signal -			 */ -			if (!L_NOFLSH(tty)) { -				n_tty_flush_buffer(tty); -				tty_driver_flush_buffer(tty); -			} -			if (I_IXON(tty)) -				start_tty(tty); -			if (L_ECHO(tty)) { -				echo_char(c, tty); -				process_echoes(tty); -			} -			if (tty->pgrp) -				kill_pgrp(tty->pgrp, signal, 1); -			return; +		if (c == INTR_CHAR(tty)) { +			n_tty_receive_signal_char(tty, SIGINT, c); +			return 0; +		} else if (c == QUIT_CHAR(tty)) { +			n_tty_receive_signal_char(tty, SIGQUIT, c); +			return 0; +		} else if (c == SUSP_CHAR(tty)) { +			n_tty_receive_signal_char(tty, SIGTSTP, c); +			return 0;  		}  	} +	if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { +		start_tty(tty); +		process_echoes(tty); +	} +  	if (c == '\r') {  		if (I_IGNCR(tty)) -			return; +			return 0;  		if (I_ICRNL(tty))  			c = '\n';  	} else if (c == '\n' && I_INLCR(tty))  		c = '\r'; -	if (tty->icanon) { +	if (ldata->icanon) {  		if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) ||  		    (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) {  			eraser(c, tty); -			process_echoes(tty); -			return; +			commit_echoes(tty); +			return 0;  		}  		if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { -			tty->lnext = 1; +			ldata->lnext = 1;  			if (L_ECHO(tty)) { -				finish_erasing(tty); +				finish_erasing(ldata);  				if (L_ECHOCTL(tty)) { -					echo_char_raw('^', tty); -					echo_char_raw('\b', tty); -					process_echoes(tty); +					echo_char_raw('^', ldata); +					echo_char_raw('\b', ldata); +					commit_echoes(tty);  				}  			} -			return; +			return 1;  		} -		if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && -		    L_IEXTEN(tty)) { -			unsigned long tail = tty->canon_head; +		if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && L_IEXTEN(tty)) { +			size_t tail = ldata->canon_head; -			finish_erasing(tty); +			finish_erasing(ldata);  			echo_char(c, tty); -			echo_char_raw('\n', tty); -			while (tail != tty->read_head) { -				echo_char(tty->read_buf[tail], tty); -				tail = (tail+1) & (N_TTY_BUF_SIZE-1); +			echo_char_raw('\n', ldata); +			while (tail != ldata->read_head) { +				echo_char(read_buf(ldata, tail), tty); +				tail++;  			} -			process_echoes(tty); -			return; +			commit_echoes(tty); +			return 0;  		}  		if (c == '\n') { -			if (tty->read_cnt >= N_TTY_BUF_SIZE) { -				if (L_ECHO(tty)) -					process_output('\a', tty); -				return; -			}  			if (L_ECHO(tty) || L_ECHONL(tty)) { -				echo_char_raw('\n', tty); -				process_echoes(tty); +				echo_char_raw('\n', ldata); +				commit_echoes(tty);  			}  			goto handle_newline;  		}  		if (c == EOF_CHAR(tty)) { -			if (tty->read_cnt >= N_TTY_BUF_SIZE) -				return; -			if (tty->canon_head != tty->read_head) -				set_bit(TTY_PUSH, &tty->flags);  			c = __DISABLED_CHAR;  			goto handle_newline;  		}  		if ((c == EOL_CHAR(tty)) ||  		    (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { -			parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) -				 ? 1 : 0; -			if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk)) { -				if (L_ECHO(tty)) -					process_output('\a', tty); -				return; -			}  			/*  			 * XXX are EOL_CHAR and EOL2_CHAR echoed?!?  			 */  			if (L_ECHO(tty)) {  				/* Record the column of first canon char. */ -				if (tty->canon_head == tty->read_head) -					echo_set_canon_col(tty); +				if (ldata->canon_head == ldata->read_head) +					echo_set_canon_col(ldata);  				echo_char(c, tty); -				process_echoes(tty); +				commit_echoes(tty);  			}  			/*  			 * XXX does PARMRK doubling happen for  			 * EOL_CHAR and EOL2_CHAR?  			 */ -			if (parmrk) -				put_tty_queue(c, tty); +			if (c == (unsigned char) '\377' && I_PARMRK(tty)) +				put_tty_queue(c, ldata);  handle_newline: -			spin_lock_irqsave(&tty->read_lock, flags); -			set_bit(tty->read_head, tty->read_flags); -			put_tty_queue_nolock(c, tty); -			tty->canon_head = tty->read_head; -			tty->canon_data++; -			spin_unlock_irqrestore(&tty->read_lock, flags); +			set_bit(ldata->read_head & (N_TTY_BUF_SIZE - 1), ldata->read_flags); +			put_tty_queue(c, ldata); +			ldata->canon_head = ldata->read_head;  			kill_fasync(&tty->fasync, SIGIO, POLL_IN);  			if (waitqueue_active(&tty->read_wait))  				wake_up_interruptible(&tty->read_wait); -			return; +			return 0;  		}  	} -	parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; -	if (tty->read_cnt >= (N_TTY_BUF_SIZE - parmrk - 1)) { -		/* beep if no space */ -		if (L_ECHO(tty)) -			process_output('\a', tty); -		return; -	}  	if (L_ECHO(tty)) { -		finish_erasing(tty); +		finish_erasing(ldata);  		if (c == '\n') -			echo_char_raw('\n', tty); +			echo_char_raw('\n', ldata);  		else {  			/* Record the column of first canon char. */ -			if (tty->canon_head == tty->read_head) -				echo_set_canon_col(tty); +			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); +	return 0; +} + +static inline void +n_tty_receive_char_inline(struct tty_struct *tty, unsigned char c) +{ +	struct n_tty_data *ldata = tty->disc_data; + +	if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { +		start_tty(tty);  		process_echoes(tty);  	} +	if (L_ECHO(tty)) { +		finish_erasing(ldata); +		/* Record the column of first canon char. */ +		if (ldata->canon_head == ldata->read_head) +			echo_set_canon_col(ldata); +		echo_char(c, tty); +		commit_echoes(tty); +	} +	/* PARMRK doubling check */ +	if (c == (unsigned char) '\377' && I_PARMRK(tty)) +		put_tty_queue(c, ldata); +	put_tty_queue(c, ldata); +} -	if (parmrk) -		put_tty_queue(c, tty); +static void n_tty_receive_char(struct tty_struct *tty, unsigned char c) +{ +	n_tty_receive_char_inline(tty, c); +} -	put_tty_queue(c, tty); +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) +{ +	if (I_ISTRIP(tty)) +		c &= 0x7f; +	if (I_IUCLC(tty) && L_IEXTEN(tty)) +		c = tolower(c); -/** - *	n_tty_write_wakeup	-	asynchronous I/O notifier - *	@tty: tty device - * - *	Required for the ptys, serial driver etc. since processes - *	that attach themselves to the master and rely on ASYNC - *	IO must be woken up - */ +	if (I_IXON(tty)) { +		if (c == STOP_CHAR(tty)) +			stop_tty(tty); +		else if (c == START_CHAR(tty) || +			 (tty->stopped && !tty->flow_stopped && I_IXANY(tty) && +			  c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) && +			  c != SUSP_CHAR(tty))) { +			start_tty(tty); +			process_echoes(tty); +		} +	} +} -static void n_tty_write_wakeup(struct tty_struct *tty) +static void +n_tty_receive_char_flagged(struct tty_struct *tty, unsigned char c, char flag)  { -	if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) -		kill_fasync(&tty->fasync, SIGIO, POLL_OUT); +	char buf[64]; + +	switch (flag) { +	case TTY_BREAK: +		n_tty_receive_break(tty); +		break; +	case TTY_PARITY: +	case TTY_FRAME: +		n_tty_receive_parity_error(tty, c); +		break; +	case TTY_OVERRUN: +		n_tty_receive_overrun(tty); +		break; +	default: +		printk(KERN_ERR "%s: unknown flag %d\n", +		       tty_name(tty, buf), 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);  }  /** @@ -1352,82 +1522,209 @@ static void n_tty_write_wakeup(struct tty_struct *tty)   *	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) + * + *	n_tty_receive_buf()/producer path: + *		claims non-exclusive termios_rwsem + *		publishes read_head and canon_head   */ -static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, -			      char *fp, int count) +static void +n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp, +			   char *fp, int count)  { -	const unsigned char *p; -	char *f, flags = TTY_NORMAL; -	int	i; -	char	buf[64]; -	unsigned long cpuflags; +	struct n_tty_data *ldata = tty->disc_data; +	size_t n, head; + +	head = ldata->read_head & (N_TTY_BUF_SIZE - 1); +	n = N_TTY_BUF_SIZE - max(read_cnt(ldata), head); +	n = min_t(size_t, count, n); +	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 = N_TTY_BUF_SIZE - max(read_cnt(ldata), head); +	n = min_t(size_t, count, n); +	memcpy(read_buf_addr(ldata, head), cp, n); +	ldata->read_head += n; +} -	if (!tty->read_buf) -		return; +static void +n_tty_receive_buf_raw(struct tty_struct *tty, const unsigned char *cp, +		      char *fp, int count) +{ +	struct n_tty_data *ldata = tty->disc_data; +	char flag = TTY_NORMAL; + +	while (count--) { +		if (fp) +			flag = *fp++; +		if (likely(flag == TTY_NORMAL)) +			put_tty_queue(*cp++, ldata); +		else +			n_tty_receive_char_flagged(tty, *cp++, flag); +	} +} -	if (tty->real_raw) { -		spin_lock_irqsave(&tty->read_lock, cpuflags); -		i = min(N_TTY_BUF_SIZE - tty->read_cnt, -			N_TTY_BUF_SIZE - tty->read_head); -		i = min(count, i); -		memcpy(tty->read_buf + tty->read_head, cp, i); -		tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1); -		tty->read_cnt += i; -		cp += i; -		count -= i; - -		i = min(N_TTY_BUF_SIZE - tty->read_cnt, -			N_TTY_BUF_SIZE - tty->read_head); -		i = min(count, i); -		memcpy(tty->read_buf + tty->read_head, cp, i); -		tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1); -		tty->read_cnt += i; -		spin_unlock_irqrestore(&tty->read_lock, cpuflags); -	} else { -		for (i = count, p = cp, f = fp; i; i--, p++) { -			if (f) -				flags = *f++; -			switch (flags) { -			case TTY_NORMAL: -				n_tty_receive_char(tty, *p); -				break; -			case TTY_BREAK: -				n_tty_receive_break(tty); -				break; -			case TTY_PARITY: -			case TTY_FRAME: -				n_tty_receive_parity_error(tty, *p); -				break; -			case TTY_OVERRUN: -				n_tty_receive_overrun(tty); -				break; -			default: -				printk(KERN_ERR "%s: unknown flag %d\n", -				       tty_name(tty, buf), flags); -				break; +static void +n_tty_receive_buf_closing(struct tty_struct *tty, const unsigned char *cp, +			  char *fp, int count) +{ +	char flag = TTY_NORMAL; + +	while (count--) { +		if (fp) +			flag = *fp++; +		if (likely(flag == TTY_NORMAL)) +			n_tty_receive_char_closing(tty, *cp++); +		else +			n_tty_receive_char_flagged(tty, *cp++, flag); +	} +} + +static void +n_tty_receive_buf_standard(struct tty_struct *tty, const unsigned char *cp, +			  char *fp, int count) +{ +	struct n_tty_data *ldata = tty->disc_data; +	char flag = TTY_NORMAL; + +	while (count--) { +		if (fp) +			flag = *fp++; +		if (likely(flag == TTY_NORMAL)) { +			unsigned char c = *cp++; + +			if (I_ISTRIP(tty)) +				c &= 0x7f; +			if (I_IUCLC(tty) && L_IEXTEN(tty)) +				c = tolower(c); +			if (L_EXTPROC(tty)) { +				put_tty_queue(c, ldata); +				continue; +			} +			if (!test_bit(c, ldata->char_map)) +				n_tty_receive_char_inline(tty, c); +			else if (n_tty_receive_char_special(tty, c) && count) { +				if (fp) +					flag = *fp++; +				n_tty_receive_char_lnext(tty, *cp++, flag); +				count--; +			} +		} else +			n_tty_receive_char_flagged(tty, *cp++, flag); +	} +} + +static void +n_tty_receive_buf_fast(struct tty_struct *tty, const unsigned char *cp, +		       char *fp, int count) +{ +	struct n_tty_data *ldata = tty->disc_data; +	char flag = TTY_NORMAL; + +	while (count--) { +		if (fp) +			flag = *fp++; +		if (likely(flag == TTY_NORMAL)) { +			unsigned char c = *cp++; + +			if (!test_bit(c, ldata->char_map)) +				n_tty_receive_char_fast(tty, c); +			else if (n_tty_receive_char_special(tty, c) && count) { +				if (fp) +					flag = *fp++; +				n_tty_receive_char_lnext(tty, *cp++, flag); +				count--;  			} +		} else +			n_tty_receive_char_flagged(tty, *cp++, flag); +	} +} + +static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, +			  char *fp, int count) +{ +	struct n_tty_data *ldata = tty->disc_data; +	bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); + +	if (ldata->real_raw) +		n_tty_receive_buf_real_raw(tty, cp, fp, count); +	else if (ldata->raw || (L_EXTPROC(tty) && !preops)) +		n_tty_receive_buf_raw(tty, cp, fp, count); +	else if (tty->closing && !L_EXTPROC(tty)) +		n_tty_receive_buf_closing(tty, cp, fp, count); +	else { +		if (ldata->lnext) { +			char flag = TTY_NORMAL; + +			if (fp) +				flag = *fp++; +			n_tty_receive_char_lnext(tty, *cp++, flag); +			count--;  		} + +		if (!preops && !I_PARMRK(tty)) +			n_tty_receive_buf_fast(tty, cp, fp, count); +		else +			n_tty_receive_buf_standard(tty, cp, fp, count); + +		flush_echoes(tty);  		if (tty->ops->flush_chars)  			tty->ops->flush_chars(tty);  	} -	n_tty_set_room(tty); - -	if ((!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) || +	if ((!ldata->icanon && (read_cnt(ldata) >= ldata->minimum_to_wake)) ||  		L_EXTPROC(tty)) {  		kill_fasync(&tty->fasync, SIGIO, POLL_IN);  		if (waitqueue_active(&tty->read_wait))  			wake_up_interruptible(&tty->read_wait);  	} +} -	/* -	 * 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 (tty->receive_room < TTY_THRESHOLD_THROTTLE) -		tty_throttle(tty); +static int +n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp, +			 char *fp, int count, int flow) +{ +	struct n_tty_data *ldata = tty->disc_data; +	int room, n, rcvd = 0; + +	down_read(&tty->termios_rwsem); + +	while (1) { +		room = receive_room(tty); +		n = min(count, room); +		if (!n) { +			if (flow && !room) +				ldata->no_room = 1; +			break; +		} +		__receive_buf(tty, cp, fp, n); +		cp += n; +		if (fp) +			fp += n; +		count -= n; +		rcvd += n; +	} + +	tty->receive_room = room; +	n_tty_check_throttle(tty); +	up_read(&tty->termios_rwsem); + +	return rcvd; +} + +static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, +			      char *fp, int count) +{ +	n_tty_receive_buf_common(tty, cp, fp, count, 0); +} + +static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp, +			      char *fp, int count) +{ +	return n_tty_receive_buf_common(tty, cp, fp, count, 1);  }  int is_ignored(int sig) @@ -1447,87 +1744,93 @@ int is_ignored(int sig)   *	guaranteed that this function will not be re-entered or in progress   *	when the ldisc is closed.   * - *	Locking: Caller holds tty->termios_mutex + *	Locking: Caller holds tty->termios_rwsem   */  static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)  { -	int canon_change = 1; -	BUG_ON(!tty); - -	if (old) -		canon_change = (old->c_lflag ^ tty->termios->c_lflag) & ICANON; -	if (canon_change) { -		memset(&tty->read_flags, 0, sizeof tty->read_flags); -		tty->canon_head = tty->read_tail; -		tty->canon_data = 0; -		tty->erasing = 0; +	struct n_tty_data *ldata = tty->disc_data; + +	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) { +		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->erasing = 0; +		ldata->lnext = 0;  	} -	if (canon_change && !L_ICANON(tty) && tty->read_cnt) -		wake_up_interruptible(&tty->read_wait); +	ldata->icanon = (L_ICANON(tty) != 0); -	tty->icanon = (L_ICANON(tty) != 0); -	if (test_bit(TTY_HW_COOK_IN, &tty->flags)) { -		tty->raw = 1; -		tty->real_raw = 1; -		n_tty_set_room(tty); -		return; -	}  	if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||  	    I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||  	    I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||  	    I_PARMRK(tty)) { -		memset(tty->process_char_map, 0, 256/8); +		bitmap_zero(ldata->char_map, 256);  		if (I_IGNCR(tty) || I_ICRNL(tty)) -			set_bit('\r', tty->process_char_map); +			set_bit('\r', ldata->char_map);  		if (I_INLCR(tty)) -			set_bit('\n', tty->process_char_map); +			set_bit('\n', ldata->char_map);  		if (L_ICANON(tty)) { -			set_bit(ERASE_CHAR(tty), tty->process_char_map); -			set_bit(KILL_CHAR(tty), tty->process_char_map); -			set_bit(EOF_CHAR(tty), tty->process_char_map); -			set_bit('\n', tty->process_char_map); -			set_bit(EOL_CHAR(tty), tty->process_char_map); +			set_bit(ERASE_CHAR(tty), ldata->char_map); +			set_bit(KILL_CHAR(tty), ldata->char_map); +			set_bit(EOF_CHAR(tty), ldata->char_map); +			set_bit('\n', ldata->char_map); +			set_bit(EOL_CHAR(tty), ldata->char_map);  			if (L_IEXTEN(tty)) { -				set_bit(WERASE_CHAR(tty), -					tty->process_char_map); -				set_bit(LNEXT_CHAR(tty), -					tty->process_char_map); -				set_bit(EOL2_CHAR(tty), -					tty->process_char_map); +				set_bit(WERASE_CHAR(tty), ldata->char_map); +				set_bit(LNEXT_CHAR(tty), ldata->char_map); +				set_bit(EOL2_CHAR(tty), ldata->char_map);  				if (L_ECHO(tty))  					set_bit(REPRINT_CHAR(tty), -						tty->process_char_map); +						ldata->char_map);  			}  		}  		if (I_IXON(tty)) { -			set_bit(START_CHAR(tty), tty->process_char_map); -			set_bit(STOP_CHAR(tty), tty->process_char_map); +			set_bit(START_CHAR(tty), ldata->char_map); +			set_bit(STOP_CHAR(tty), ldata->char_map);  		}  		if (L_ISIG(tty)) { -			set_bit(INTR_CHAR(tty), tty->process_char_map); -			set_bit(QUIT_CHAR(tty), tty->process_char_map); -			set_bit(SUSP_CHAR(tty), tty->process_char_map); +			set_bit(INTR_CHAR(tty), ldata->char_map); +			set_bit(QUIT_CHAR(tty), ldata->char_map); +			set_bit(SUSP_CHAR(tty), ldata->char_map);  		} -		clear_bit(__DISABLED_CHAR, tty->process_char_map); -		tty->raw = 0; -		tty->real_raw = 0; +		clear_bit(__DISABLED_CHAR, ldata->char_map); +		ldata->raw = 0; +		ldata->real_raw = 0;  	} else { -		tty->raw = 1; +		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)) -			tty->real_raw = 1; +			ldata->real_raw = 1;  		else -			tty->real_raw = 0; +			ldata->real_raw = 0;  	}  	n_tty_set_room(tty); +	/* +	 * Fix tty hang when I_IXON(tty) is cleared, but the tty +	 * been stopped by STOP_CHAR(tty) before it. +	 */ +	if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow_stopped) { +		start_tty(tty); +		process_echoes(tty); +	} +  	/* The termios change make the tty ready for I/O */ -	wake_up_interruptible(&tty->write_wait); -	wake_up_interruptible(&tty->read_wait); +	if (waitqueue_active(&tty->write_wait)) +		wake_up_interruptible(&tty->write_wait); +	if (waitqueue_active(&tty->read_wait)) +		wake_up_interruptible(&tty->read_wait);  }  /** @@ -1542,15 +1845,13 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)  static void n_tty_close(struct tty_struct *tty)  { -	n_tty_flush_buffer(tty); -	if (tty->read_buf) { -		kfree(tty->read_buf); -		tty->read_buf = NULL; -	} -	if (tty->echo_buf) { -		kfree(tty->echo_buf); -		tty->echo_buf = NULL; -	} +	struct n_tty_data *ldata = tty->disc_data; + +	if (tty->link) +		n_tty_packet_mode_flush(tty); + +	vfree(ldata); +	tty->disc_data = NULL;  }  /** @@ -1565,43 +1866,49 @@ static void n_tty_close(struct tty_struct *tty)  static int n_tty_open(struct tty_struct *tty)  { -	if (!tty) -		return -EINVAL; - -	/* These are ugly. Currently a malloc failure here can panic */ -	if (!tty->read_buf) { -		tty->read_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); -		if (!tty->read_buf) -			return -ENOMEM; -	} -	if (!tty->echo_buf) { -		tty->echo_buf = kzalloc(N_TTY_BUF_SIZE, GFP_KERNEL); - -		if (!tty->echo_buf) -			return -ENOMEM; -	} -	reset_buffer_flags(tty); -	tty->column = 0; -	n_tty_set_termios(tty, NULL); -	tty->minimum_to_wake = 1; +	struct n_tty_data *ldata; + +	/* Currently a malloc failure here can panic */ +	ldata = vmalloc(sizeof(*ldata)); +	if (!ldata) +		goto err; + +	ldata->overrun_time = jiffies; +	mutex_init(&ldata->atomic_read_lock); +	mutex_init(&ldata->output_lock); + +	tty->disc_data = ldata; +	reset_buffer_flags(tty->disc_data); +	ldata->column = 0; +	ldata->canon_column = 0; +	ldata->minimum_to_wake = 1; +	ldata->num_overrun = 0; +	ldata->no_room = 0; +	ldata->lnext = 0;  	tty->closing = 0; +	/* indicate buffer work may resume */ +	clear_bit(TTY_LDISC_HALTED, &tty->flags); +	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 amt) +static inline int input_available_p(struct tty_struct *tty, int poll)  { -	tty_flush_to_ldisc(tty); -	if (tty->icanon && !L_EXTPROC(tty)) { -		if (tty->canon_data) -			return 1; -	} else if (tty->read_cnt >= (amt ? amt : 1)) -		return 1; +	struct n_tty_data *ldata = tty->disc_data; +	int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1; -	return 0; +	if (ldata->icanon && !L_EXTPROC(tty)) +		return ldata->canon_head != ldata->read_tail; +	else +		return read_cnt(ldata) >= amt;  }  /** - * 	copy_from_read_buf	-	copy read data directly + *	copy_from_read_buf	-	copy read data directly   *	@tty: terminal device   *	@b: user data   *	@nr: size of data @@ -1613,8 +1920,11 @@ static inline int input_available_p(struct tty_struct *tty, int amt)   *	buffer, and once to drain the space from the (physical) beginning of   *	the buffer to head pointer.   * - *	Called under the tty->atomic_read_lock sem + *	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, @@ -1622,34 +1932,129 @@ static int copy_from_read_buf(struct tty_struct *tty,  				      size_t *nr)  { +	struct n_tty_data *ldata = tty->disc_data;  	int retval;  	size_t n; -	unsigned long flags; +	bool is_eof; +	size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);  	retval = 0; -	spin_lock_irqsave(&tty->read_lock, flags); -	n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail); +	n = min(read_cnt(ldata), N_TTY_BUF_SIZE - tail);  	n = min(*nr, n); -	spin_unlock_irqrestore(&tty->read_lock, flags);  	if (n) { -		retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n); +		retval = copy_to_user(*b, read_buf_addr(ldata, tail), n);  		n -= retval; -		tty_audit_add_data(tty, &tty->read_buf[tty->read_tail], n); -		spin_lock_irqsave(&tty->read_lock, flags); -		tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1); -		tty->read_cnt -= n; +		is_eof = n == 1 && read_buf(ldata, tail) == EOF_CHAR(tty); +		tty_audit_add_data(tty, read_buf_addr(ldata, tail), n, +				ldata->icanon); +		ldata->read_tail += n;  		/* Turn single EOF into zero-length read */ -		if (L_EXTPROC(tty) && tty->icanon && n == 1) { -			if (!tty->read_cnt && (*b)[n-1] == EOF_CHAR(tty)) -				n--; -		} -		spin_unlock_irqrestore(&tty->read_lock, flags); +		if (L_EXTPROC(tty) && ldata->icanon && is_eof && !read_cnt(ldata)) +			n = 0;  		*b += n;  		*nr -= n;  	}  	return retval;  } +/** + *	canon_copy_from_read_buf	-	copy read data in canonical mode + *	@tty: terminal device + *	@b: user data + *	@nr: size of data + * + *	Helper function for n_tty_read.  It is only called when ICANON is on; + *	it copies one line of input up to and including the line-delimiting + *	character into the user-space buffer. + * + *	NB: When termios is changed from non-canonical to canonical mode and + *	the read buffer contains data, n_tty_set_termios() simulates an EOF + *	push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer. + *	This causes data already processed as input to be immediately available + *	as input although a newline has not been received. + * + *	Called under the atomic_read_lock mutex + * + *	n_tty_read()/consumer path: + *		caller holds non-exclusive termios_rwsem + *		read_tail published + */ + +static int canon_copy_from_read_buf(struct tty_struct *tty, +				    unsigned char __user **b, +				    size_t *nr) +{ +	struct n_tty_data *ldata = tty->disc_data; +	size_t n, size, more, c; +	size_t eol; +	size_t tail; +	int ret, found = 0; +	bool eof_push = 0; + +	/* N.B. avoid overrun if nr == 0 */ +	n = min(*nr, read_cnt(ldata)); +	if (!n) +		return 0; + +	tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1); +	size = min_t(size_t, tail + n, N_TTY_BUF_SIZE); + +	n_tty_trace("%s: nr:%zu tail:%zu n:%zu size:%zu\n", +		    __func__, *nr, tail, n, size); + +	eol = find_next_bit(ldata->read_flags, size, tail); +	more = n - (size - tail); +	if (eol == N_TTY_BUF_SIZE && more) { +		/* scan wrapped without finding set bit */ +		eol = find_next_bit(ldata->read_flags, more, 0); +		if (eol != more) +			found = 1; +	} else if (eol != size) +		found = 1; + +	size = N_TTY_BUF_SIZE - tail; +	n = eol - tail; +	if (n > 4096) +		n += 4096; +	n += found; +	c = n; + +	if (found && !ldata->push && read_buf(ldata, eol) == __DISABLED_CHAR) { +		n--; +		eof_push = !n && ldata->read_tail != ldata->line_start; +	} + +	n_tty_trace("%s: eol:%zu found:%d n:%zu c:%zu size:%zu more:%zu\n", +		    __func__, eol, found, n, c, size, more); + +	if (n > size) { +		ret = copy_to_user(*b, read_buf_addr(ldata, tail), size); +		if (ret) +			return -EFAULT; +		ret = copy_to_user(*b + size, ldata->read_buf, n - size); +	} else +		ret = copy_to_user(*b, read_buf_addr(ldata, tail), n); + +	if (ret) +		return -EFAULT; +	*b += n; +	*nr -= n; + +	if (found) +		clear_bit(eol, ldata->read_flags); +	smp_mb__after_atomic(); +	ldata->read_tail += c; + +	if (found) { +		if (!ldata->push) +			ldata->line_start = ldata->read_tail; +		else +			ldata->push = 0; +		tty_audit_push(tty); +	} +	return eof_push ? -EAGAIN : 0; +} +  extern ssize_t redirected_tty_write(struct file *, const char __user *,  							size_t, loff_t *); @@ -1662,10 +2067,9 @@ extern ssize_t redirected_tty_write(struct file *, const char __user *,   *	and if appropriate send any needed signals and return a negative   *	error code if action should be taken.   * - *	FIXME: - *	Locking: None - redirected write test is safe, testing - *	current->signal should possibly lock current->sighand - *	pgrp locking ? + *	Locking: redirected write test is safe + *		 current->signal->tty check is safe + *		 ctrl_lock to safely reference tty->pgrp   */  static int job_control(struct tty_struct *tty, struct file *file) @@ -1675,19 +2079,22 @@ 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 && -	    current->signal->tty == tty) { -		if (!tty->pgrp) -			printk(KERN_ERR "n_tty_read: no tty->pgrp!\n"); -		else if (task_pgrp(current) != tty->pgrp) { -			if (is_ignored(SIGTTIN) || -			    is_current_pgrp_orphaned()) -				return -EIO; -			kill_pgrp(task_pgrp(current), SIGTTIN, 1); -			set_thread_flag(TIF_SIGPENDING); -			return -ERESTARTSYS; -		} +	if (file->f_op->write == redirected_tty_write || +	    current->signal->tty != tty) +		return 0; + +	spin_lock_irq(&tty->ctrl_lock); +	if (!tty->pgrp) +		printk(KERN_ERR "n_tty_read: no tty->pgrp!\n"); +	else if (task_pgrp(current) != tty->pgrp) { +		spin_unlock_irq(&tty->ctrl_lock); +		if (is_ignored(SIGTTIN) || is_current_pgrp_orphaned()) +			return -EIO; +		kill_pgrp(task_pgrp(current), SIGTTIN, 1); +		set_thread_flag(TIF_SIGPENDING); +		return -ERESTARTSYS;  	} +	spin_unlock_irq(&tty->ctrl_lock);  	return 0;  } @@ -1705,60 +2112,59 @@ static int job_control(struct tty_struct *tty, struct file *file)   *	a hangup. Always called in user context, may sleep.   *   *	This code must be sure never to sleep through a hangup. + * + *	n_tty_read()/consumer path: + *		claims non-exclusive termios_rwsem + *		publishes read_tail   */  static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,  			 unsigned char __user *buf, size_t nr)  { +	struct n_tty_data *ldata = tty->disc_data;  	unsigned char __user *b = buf;  	DECLARE_WAITQUEUE(wait, current);  	int c;  	int minimum, time;  	ssize_t retval = 0; -	ssize_t size;  	long timeout;  	unsigned long flags;  	int packet; -do_it_again: - -	BUG_ON(!tty->read_buf); -  	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 (!tty->icanon) { -		time = (HZ / 10) * TIME_CHAR(tty); +	if (!ldata->icanon) {  		minimum = MIN_CHAR(tty);  		if (minimum) { +			time = (HZ / 10) * TIME_CHAR(tty);  			if (time) -				tty->minimum_to_wake = 1; +				ldata->minimum_to_wake = 1;  			else if (!waitqueue_active(&tty->read_wait) || -				 (tty->minimum_to_wake > minimum)) -				tty->minimum_to_wake = minimum; +				 (ldata->minimum_to_wake > minimum)) +				ldata->minimum_to_wake = minimum;  		} else { -			timeout = 0; -			if (time) { -				timeout = time; -				time = 0; -			} -			tty->minimum_to_wake = minimum = 1; +			timeout = (HZ / 10) * TIME_CHAR(tty); +			ldata->minimum_to_wake = minimum = 1;  		}  	} -	/* -	 *	Internal serialization of reads. -	 */ -	if (file->f_flags & O_NONBLOCK) { -		if (!mutex_trylock(&tty->atomic_read_lock)) -			return -EAGAIN; -	} else { -		if (mutex_lock_interruptible(&tty->atomic_read_lock)) -			return -ERESTARTSYS; -	}  	packet = tty->packet;  	add_wait_queue(&tty->read_wait, &wait); @@ -1785,31 +2191,40 @@ do_it_again:  		   TASK_RUNNING. */  		set_current_state(TASK_INTERRUPTIBLE); -		if (((minimum - (b - buf)) < tty->minimum_to_wake) && +		if (((minimum - (b - buf)) < ldata->minimum_to_wake) &&  		    ((minimum - (b - buf)) >= 1)) -			tty->minimum_to_wake = (minimum - (b - buf)); +			ldata->minimum_to_wake = (minimum - (b - buf));  		if (!input_available_p(tty, 0)) {  			if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { -				retval = -EIO; -				break; -			} -			if (tty_hung_up_p(file)) -				break; -			if (!timeout) -				break; -			if (file->f_flags & O_NONBLOCK) { -				retval = -EAGAIN; -				break; -			} -			if (signal_pending(current)) { -				retval = -ERESTARTSYS; -				break; +				up_read(&tty->termios_rwsem); +				tty_flush_to_ldisc(tty); +				down_read(&tty->termios_rwsem); +				if (!input_available_p(tty, 0)) { +					retval = -EIO; +					break; +				} +			} else { +				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; +				} +				n_tty_set_room(tty); +				up_read(&tty->termios_rwsem); + +				timeout = schedule_timeout(timeout); + +				down_read(&tty->termios_rwsem); +				continue;  			} -			/* FIXME: does n_tty_set_room need locking ? */ -			n_tty_set_room(tty); -			timeout = schedule_timeout(timeout); -			continue;  		}  		__set_current_state(TASK_RUNNING); @@ -1823,42 +2238,12 @@ do_it_again:  			nr--;  		} -		if (tty->icanon && !L_EXTPROC(tty)) { -			/* N.B. avoid overrun if nr == 0 */ -			while (nr && tty->read_cnt) { -				int eol; - -				eol = test_and_clear_bit(tty->read_tail, -						tty->read_flags); -				c = tty->read_buf[tty->read_tail]; -				spin_lock_irqsave(&tty->read_lock, flags); -				tty->read_tail = ((tty->read_tail+1) & -						  (N_TTY_BUF_SIZE-1)); -				tty->read_cnt--; -				if (eol) { -					/* this test should be redundant: -					 * we shouldn't be reading data if -					 * canon_data is 0 -					 */ -					if (--tty->canon_data < 0) -						tty->canon_data = 0; -				} -				spin_unlock_irqrestore(&tty->read_lock, flags); - -				if (!eol || (c != __DISABLED_CHAR)) { -					if (tty_put_user(tty, c, b++)) { -						retval = -EFAULT; -						b--; -						break; -					} -					nr--; -				} -				if (eol) { -					tty_audit_push(tty); -					break; -				} -			} -			if (retval) +		if (ldata->icanon && !L_EXTPROC(tty)) { +			retval = canon_copy_from_read_buf(tty, &b, &nr); +			if (retval == -EAGAIN) { +				retval = 0; +				continue; +			} else if (retval)  				break;  		} else {  			int uncopied; @@ -1872,39 +2257,26 @@ do_it_again:  			}  		} -		/* If there is enough space in the read buffer now, let the -		 * low-level driver know. We use n_tty_chars_in_buffer() to -		 * check the buffer, as it now knows about canonical mode. -		 * Otherwise, if the driver is throttled and the line is -		 * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, -		 * we won't get any more characters. -		 */ -		if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) { -			n_tty_set_room(tty); -			check_unthrottle(tty); -		} +		n_tty_check_unthrottle(tty);  		if (b - buf >= minimum)  			break;  		if (time)  			timeout = time;  	} -	mutex_unlock(&tty->atomic_read_lock); -	remove_wait_queue(&tty->read_wait, &wait); +	n_tty_set_room(tty); +	up_read(&tty->termios_rwsem); +	remove_wait_queue(&tty->read_wait, &wait);  	if (!waitqueue_active(&tty->read_wait)) -		tty->minimum_to_wake = minimum; +		ldata->minimum_to_wake = minimum; + +	mutex_unlock(&ldata->atomic_read_lock);  	__set_current_state(TASK_RUNNING); -	size = b - buf; -	if (size) { -		retval = size; -		if (nr) -			clear_bit(TTY_PUSH, &tty->flags); -	} else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) -		 goto do_it_again; +	if (b - buf) +		retval = b - buf; -	n_tty_set_room(tty);  	return retval;  } @@ -1945,6 +2317,8 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,  			return retval;  	} +	down_read(&tty->termios_rwsem); +  	/* Write out any echoed characters that are still pending */  	process_echoes(tty); @@ -1959,7 +2333,7 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,  			retval = -EIO;  			break;  		} -		if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { +		if (O_OPOST(tty)) {  			while (nr > 0) {  				ssize_t num = process_output_block(tty, b, nr);  				if (num < 0) { @@ -1980,8 +2354,12 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,  			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; @@ -1998,13 +2376,18 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,  			retval = -EAGAIN;  			break;  		} +		up_read(&tty->termios_rwsem); +  		schedule(); + +		down_read(&tty->termios_rwsem);  	}  break_out:  	__set_current_state(TASK_RUNNING);  	remove_wait_queue(&tty->write_wait, &wait);  	if (b - buf != nr && tty->fasync)  		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); +	up_read(&tty->termios_rwsem);  	return (b - buf) ? b - buf : retval;  } @@ -2025,11 +2408,12 @@ break_out:  static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,  							poll_table *wait)  { +	struct n_tty_data *ldata = tty->disc_data;  	unsigned int mask = 0;  	poll_wait(file, &tty->read_wait, wait);  	poll_wait(file, &tty->write_wait, wait); -	if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty))) +	if (input_available_p(tty, 1))  		mask |= POLLIN | POLLRDNORM;  	if (tty->packet && tty->link->ctrl_status)  		mask |= POLLPRI | POLLIN | POLLRDNORM; @@ -2039,9 +2423,9 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,  		mask |= POLLHUP;  	if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) {  		if (MIN_CHAR(tty) && !TIME_CHAR(tty)) -			tty->minimum_to_wake = MIN_CHAR(tty); +			ldata->minimum_to_wake = MIN_CHAR(tty);  		else -			tty->minimum_to_wake = 1; +			ldata->minimum_to_wake = 1;  	}  	if (tty->ops->write && !tty_is_writelocked(tty) &&  			tty_chars_in_buffer(tty) < WAKEUP_CHARS && @@ -2050,21 +2434,21 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,  	return mask;  } -static unsigned long inq_canon(struct tty_struct *tty) +static unsigned long inq_canon(struct n_tty_data *ldata)  { -	int nr, head, tail; +	size_t nr, head, tail; -	if (!tty->canon_data) +	if (ldata->canon_head == ldata->read_tail)  		return 0; -	head = tty->canon_head; -	tail = tty->read_tail; -	nr = (head - tail) & (N_TTY_BUF_SIZE-1); +	head = ldata->canon_head; +	tail = ldata->read_tail; +	nr = head - tail;  	/* Skip EOF-chars.. */  	while (head != tail) { -		if (test_bit(tail, tty->read_flags) && -		    tty->read_buf[tail] == __DISABLED_CHAR) +		if (test_bit(tail & (N_TTY_BUF_SIZE - 1), ldata->read_flags) && +		    read_buf(ldata, tail) == __DISABLED_CHAR)  			nr--; -		tail = (tail+1) & (N_TTY_BUF_SIZE-1); +		tail++;  	}  	return nr;  } @@ -2072,22 +2456,37 @@ static unsigned long inq_canon(struct tty_struct *tty)  static int n_tty_ioctl(struct tty_struct *tty, struct file *file,  		       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: -		/* FIXME: Locking */ -		retval = tty->read_cnt; +		down_write(&tty->termios_rwsem);  		if (L_ICANON(tty)) -			retval = inq_canon(tty); +			retval = inq_canon(ldata); +		else +			retval = read_cnt(ldata); +		up_write(&tty->termios_rwsem);  		return put_user(retval, (unsigned int __user *) arg);  	default:  		return n_tty_ioctl_helper(tty, file, cmd, arg);  	}  } +static void n_tty_fasync(struct tty_struct *tty, int on) +{ +	struct n_tty_data *ldata = tty->disc_data; + +	if (!waitqueue_active(&tty->read_wait)) { +		if (on) +			ldata->minimum_to_wake = 1; +		else if (!tty->fasync) +			ldata->minimum_to_wake = N_TTY_BUF_SIZE; +	} +} +  struct tty_ldisc_ops tty_ldisc_N_TTY = {  	.magic           = TTY_LDISC_MAGIC,  	.name            = "n_tty", @@ -2101,14 +2500,16 @@ struct tty_ldisc_ops tty_ldisc_N_TTY = {  	.set_termios     = n_tty_set_termios,  	.poll            = n_tty_poll,  	.receive_buf     = n_tty_receive_buf, -	.write_wakeup    = n_tty_write_wakeup +	.write_wakeup    = n_tty_write_wakeup, +	.fasync		 = n_tty_fasync, +	.receive_buf2	 = n_tty_receive_buf2,  };  /**   *	n_tty_inherit_ops	-	inherit N_TTY methods   *	@ops: struct tty_ldisc_ops where to save N_TTY methods   * - *	Used by a generic struct tty_ldisc_ops to easily inherit N_TTY + *	Enables a 'subclass' line discipline to 'inherit' N_TTY   *	methods.   */  | 
