diff options
Diffstat (limited to 'drivers/tty/n_tty.c')
| -rw-r--r-- | drivers/tty/n_tty.c | 236 | 
1 files changed, 130 insertions, 106 deletions
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index c9a9ddd1d0b..f44f1ba762c 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -93,6 +93,7 @@ struct n_tty_data {  	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) */ @@ -104,6 +105,7 @@ struct n_tty_data {  	/* 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]; @@ -274,7 +276,8 @@ static void n_tty_check_unthrottle(struct tty_struct *tty)  			return;  		n_tty_set_room(tty);  		n_tty_write_wakeup(tty->link); -		wake_up_interruptible_poll(&tty->link->write_wait, POLLOUT); +		if (waitqueue_active(&tty->link->write_wait)) +			wake_up_interruptible_poll(&tty->link->write_wait, POLLOUT);  		return;  	} @@ -336,10 +339,12 @@ static void reset_buffer_flags(struct n_tty_data *ldata)  {  	ldata->read_head = ldata->canon_head = ldata->read_tail = 0;  	ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; +	ldata->echo_mark = 0;  	ldata->line_start = 0;  	ldata->erasing = 0;  	bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); +	ldata->push = 0;  }  static void n_tty_packet_mode_flush(struct tty_struct *tty) @@ -349,7 +354,8 @@ static void n_tty_packet_mode_flush(struct tty_struct *tty)  	spin_lock_irqsave(&tty->ctrl_lock, flags);  	if (tty->link->packet) {  		tty->ctrl_status |= TIOCPKT_FLUSHREAD; -		wake_up_interruptible(&tty->link->read_wait); +		if (waitqueue_active(&tty->link->read_wait)) +			wake_up_interruptible(&tty->link->read_wait);  	}  	spin_unlock_irqrestore(&tty->ctrl_lock, flags);  } @@ -767,8 +773,8 @@ static size_t __process_echoes(struct tty_struct *tty)  	 * 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) == ECHO_OP_ERASE_TAB) +		if (echo_buf(ldata, tail) == ECHO_OP_START) { +			if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB)  				tail += 3;  			else  				tail += 2; @@ -787,6 +793,7 @@ static void commit_echoes(struct tty_struct *tty)  	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 @@ -810,10 +817,11 @@ static void process_echoes(struct tty_struct *tty)  	struct n_tty_data *ldata = tty->disc_data;  	size_t echoed; -	if (!L_ECHO(tty) || ldata->echo_commit == ldata->echo_tail) +	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); @@ -821,11 +829,13 @@ static void process_echoes(struct tty_struct *tty)  		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) || ldata->echo_commit == ldata->echo_head) +	if ((!L_ECHO(tty) && !L_ECHONL(tty)) || +	    ldata->echo_commit == ldata->echo_head)  		return;  	mutex_lock(&ldata->output_lock); @@ -1155,7 +1165,8 @@ static void n_tty_receive_break(struct tty_struct *tty)  		put_tty_queue('\0', ldata);  	}  	put_tty_queue('\0', ldata); -	wake_up_interruptible(&tty->read_wait); +	if (waitqueue_active(&tty->read_wait)) +		wake_up_interruptible(&tty->read_wait);  }  /** @@ -1203,17 +1214,19 @@ static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c)  {  	struct n_tty_data *ldata = tty->disc_data; -	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	if (I_INPCK(tty)) -		put_tty_queue('\0', ldata); -	else +	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); -	wake_up_interruptible(&tty->read_wait); +	if (waitqueue_active(&tty->read_wait)) +		wake_up_interruptible(&tty->read_wait);  }  static void @@ -1231,7 +1244,8 @@ n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c)  	if (L_ECHO(tty)) {  		echo_char(c, tty);  		commit_echoes(tty); -	} +	} else +		process_echoes(tty);  	isig(signal, tty);  	return;  } @@ -1257,12 +1271,11 @@ static int  n_tty_receive_char_special(struct tty_struct *tty, unsigned char c)  {  	struct n_tty_data *ldata = tty->disc_data; -	int parmrk;  	if (I_IXON(tty)) {  		if (c == START_CHAR(tty)) {  			start_tty(tty); -			commit_echoes(tty); +			process_echoes(tty);  			return 0;  		}  		if (c == STOP_CHAR(tty)) { @@ -1342,8 +1355,6 @@ n_tty_receive_char_special(struct tty_struct *tty, unsigned char c)  		}  		if ((c == EOL_CHAR(tty)) ||  		    (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { -			parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) -				 ? 1 : 0;  			/*  			 * XXX are EOL_CHAR and EOL2_CHAR echoed?!?  			 */ @@ -1358,7 +1369,7 @@ n_tty_receive_char_special(struct tty_struct *tty, unsigned char c)  			 * XXX does PARMRK doubling happen for  			 * EOL_CHAR and EOL2_CHAR?  			 */ -			if (parmrk) +			if (c == (unsigned char) '\377' && I_PARMRK(tty))  				put_tty_queue(c, ldata);  handle_newline: @@ -1372,7 +1383,6 @@ handle_newline:  		}  	} -	parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0;  	if (L_ECHO(tty)) {  		finish_erasing(ldata);  		if (c == '\n') @@ -1386,7 +1396,8 @@ handle_newline:  		commit_echoes(tty);  	} -	if (parmrk) +	/* PARMRK doubling check */ +	if (c == (unsigned char) '\377' && I_PARMRK(tty))  		put_tty_queue(c, ldata);  	put_tty_queue(c, ldata); @@ -1397,7 +1408,6 @@ static inline void  n_tty_receive_char_inline(struct tty_struct *tty, unsigned char c)  {  	struct n_tty_data *ldata = tty->disc_data; -	int parmrk;  	if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) {  		start_tty(tty); @@ -1411,13 +1421,13 @@ n_tty_receive_char_inline(struct tty_struct *tty, unsigned char c)  		echo_char(c, tty);  		commit_echoes(tty);  	} -	parmrk = (c == (unsigned char) '\377' && I_PARMRK(tty)) ? 1 : 0; -	if (parmrk) +	/* PARMRK doubling check */ +	if (c == (unsigned char) '\377' && I_PARMRK(tty))  		put_tty_queue(c, ldata);  	put_tty_queue(c, ldata);  } -static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c) +static void n_tty_receive_char(struct tty_struct *tty, unsigned char c)  {  	n_tty_receive_char_inline(tty, c);  } @@ -1442,8 +1452,7 @@ n_tty_receive_char_fast(struct tty_struct *tty, unsigned char c)  	put_tty_queue(c, ldata);  } -static inline void -n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c) +static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c)  {  	if (I_ISTRIP(tty))  		c &= 0x7f; @@ -1674,32 +1683,9 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,  	}  } -static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, -			      char *fp, int count) -{ -	int room, n; - -	down_read(&tty->termios_rwsem); - -	while (1) { -		room = receive_room(tty); -		n = min(count, room); -		if (!n) -			break; -		__receive_buf(tty, cp, fp, n); -		cp += n; -		if (fp) -			fp += n; -		count -= n; -	} - -	tty->receive_room = room; -	n_tty_check_throttle(tty); -	up_read(&tty->termios_rwsem); -} - -static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp, -			      char *fp, int count) +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; @@ -1710,7 +1696,7 @@ static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,  		room = receive_room(tty);  		n = min(count, room);  		if (!n) { -			if (!room) +			if (flow && !room)  				ldata->no_room = 1;  			break;  		} @@ -1729,6 +1715,18 @@ static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,  	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)  {  	return (sigismember(¤t->blocked, sig) || @@ -1752,21 +1750,23 @@ int is_ignored(int sig)  static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)  {  	struct n_tty_data *ldata = tty->disc_data; -	int canon_change = 1; -	if (old) -		canon_change = (old->c_lflag ^ tty->termios.c_lflag) & ICANON; -	if (canon_change) { +	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) {  		bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); -		ldata->line_start = 0; -		ldata->canon_head = ldata->read_tail; +		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) && read_cnt(ldata)) -		wake_up_interruptible(&tty->read_wait); -  	ldata->icanon = (L_ICANON(tty) != 0);  	if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) || @@ -1823,11 +1823,14 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)  	 */  	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);  }  /** @@ -1893,17 +1896,15 @@ 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)  {  	struct n_tty_data *ldata = tty->disc_data; +	int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1; -	if (ldata->icanon && !L_EXTPROC(tty)) { -		if (ldata->canon_head != ldata->read_tail) -			return 1; -	} else if (read_cnt(ldata) >= (amt ? amt : 1)) -		return 1; - -	return 0; +	if (ldata->icanon && !L_EXTPROC(tty)) +		return ldata->canon_head != ldata->read_tail; +	else +		return read_cnt(ldata) >= amt;  }  /** @@ -1966,6 +1967,12 @@ static int copy_from_read_buf(struct tty_struct *tty,   *	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: @@ -2006,10 +2013,13 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,  		found = 1;  	size = N_TTY_BUF_SIZE - tail; -	n = (found + eol + size) & (N_TTY_BUF_SIZE - 1); +	n = eol - tail; +	if (n > 4096) +		n += 4096; +	n += found;  	c = n; -	if (found && read_buf(ldata, eol) == __DISABLED_CHAR) { +	if (found && !ldata->push && read_buf(ldata, eol) == __DISABLED_CHAR) {  		n--;  		eof_push = !n && ldata->read_tail != ldata->line_start;  	} @@ -2032,11 +2042,14 @@ static int canon_copy_from_read_buf(struct tty_struct *tty,  	if (found)  		clear_bit(eol, ldata->read_flags); -	smp_mb__after_clear_bit(); +	smp_mb__after_atomic();  	ldata->read_tail += c;  	if (found) { -		ldata->line_start = ldata->read_tail; +		if (!ldata->push) +			ldata->line_start = ldata->read_tail; +		else +			ldata->push = 0;  		tty_audit_push(tty);  	}  	return eof_push ? -EAGAIN : 0; @@ -2184,28 +2197,34 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,  		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; -			} -			n_tty_set_room(tty); -			up_read(&tty->termios_rwsem); +				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); +				timeout = schedule_timeout(timeout); -			down_read(&tty->termios_rwsem); -			continue; +				down_read(&tty->termios_rwsem); +				continue; +			}  		}  		__set_current_state(TASK_RUNNING); @@ -2245,18 +2264,19 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,  		if (time)  			timeout = time;  	} -	mutex_unlock(&ldata->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))  		ldata->minimum_to_wake = minimum; +	mutex_unlock(&ldata->atomic_read_lock); +  	__set_current_state(TASK_RUNNING);  	if (b - buf)  		retval = b - buf; -	n_tty_set_room(tty); -	up_read(&tty->termios_rwsem);  	return retval;  } @@ -2334,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; @@ -2389,7 +2413,7 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,  	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;  | 
