patch-2.2.15 linux/drivers/char/n_tty.c

Next file: linux/drivers/char/pcxx.c
Previous file: linux/drivers/char/n_hdlc.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from ../../exclude v2.2.14/drivers/char/n_tty.c linux/drivers/char/n_tty.c
@@ -17,6 +17,10 @@
  * 
  * This file may be redistributed under the terms of the GNU Public
  * License.
+ *
+ * 2000/01/20   Fixed SMP locking on put_tty_queue using bits of 
+ *		the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu>
+ *		who actually finally proved there really was a race.
  */
 
 #include <linux/types.h>
@@ -38,6 +42,7 @@
 #include <asm/uaccess.h>
 #include <asm/system.h>
 #include <asm/bitops.h>
+#include <asm/spinlock.h>
 
 #define CONSOLE_DEV MKDEV(TTY_MAJOR,0)
 #define SYSCONS_DEV  MKDEV(TTYAUX_MAJOR,1)
@@ -59,11 +64,18 @@
 
 static inline void put_tty_queue(unsigned char c, struct tty_struct *tty)
 {
+	unsigned long flags;
+	/*
+	 *	The problem of stomping on the buffers ends here.
+	 *	Why didn't anyone see this one comming? --AJK
+	*/
+	spin_lock_irqsave(&tty->read_lock, flags);
 	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++;
 	}
+	spin_unlock_irqrestore(&tty->read_lock, flags);
 }
 
 /* 
@@ -86,7 +98,11 @@
  */
 static void reset_buffer_flags(struct tty_struct *tty)
 {
+	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);
 	tty->canon_head = tty->canon_data = tty->erasing = 0;
 	memset(&tty->read_flags, 0, sizeof tty->read_flags);
 	check_unthrottle(tty);
@@ -106,6 +122,7 @@
 	if (tty->link->packet) {
 		tty->ctrl_status |= TIOCPKT_FLUSHREAD;
 		wake_up_interruptible(&tty->link->read_wait);
+		wake_up_interruptible(&tty->link->poll_wait);
 	}
 }
 
@@ -114,14 +131,19 @@
  */
 ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
 {
-	if (tty->icanon) {
-		if (!tty->canon_data) return 0;
+	unsigned long flags;
+	ssize_t n = 0;
 
-		return (tty->canon_head > tty->read_tail) ?
+	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);
 	}
-	return tty->read_cnt;
+	spin_unlock_irqrestore(&tty->read_lock, flags);
+	return n;
 }
 
 /*
@@ -283,6 +305,7 @@
 {
 	enum { ERASE, WERASE, KILL } kill_type;
 	int head, seen_alnums;
+	unsigned long flags;
 
 	if (tty->read_head == tty->canon_head) {
 		/* opost('\a', tty); */		/* what do you think? */
@@ -294,15 +317,19 @@
 		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);
 			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);
 			echo_char(KILL_CHAR(tty), tty);
 			/* Add a newline if ECHOK is on and ECHOKE is off. */
@@ -324,8 +351,10 @@
 			else if (seen_alnums)
 				break;
 		}
+		spin_lock_irqsave(&tty->read_lock, flags);
 		tty->read_head = head;
 		tty->read_cnt--;
+		spin_unlock_irqrestore(&tty->read_lock, flags);
 		if (L_ECHO(tty)) {
 			if (L_ECHOPRT(tty)) {
 				if (!tty->erasing) {
@@ -413,6 +442,7 @@
 	}
 	put_tty_queue('\0', tty);
 	wake_up_interruptible(&tty->read_wait);
+	wake_up_interruptible(&tty->poll_wait);
 }
 
 static inline void n_tty_receive_overrun(struct tty_struct *tty)
@@ -443,6 +473,7 @@
 	else
 		put_tty_queue(c, tty);
 	wake_up_interruptible(&tty->read_wait);
+	wake_up_interruptible(&tty->poll_wait);
 }
 
 static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
@@ -605,8 +636,11 @@
 			tty->canon_data++;
 			if (tty->fasync)
 				kill_fasync(tty->fasync, SIGIO);
-			if (tty->read_wait)
+			if (tty->read_wait || tty->poll_wait)
+			{
 				wake_up_interruptible(&tty->read_wait);
+				wake_up_interruptible(&tty->poll_wait);
+			}
 			return;
 		}
 	}
@@ -658,11 +692,13 @@
 	char *f, flags = TTY_NORMAL;
 	int	i;
 	char	buf[64];
+	unsigned long cpuflags;
 
 	if (!tty->read_buf)
 		return;
 
 	if (tty->real_raw) {
+		spin_lock_irqsave(&tty->read_lock, cpuflags);
 		i = MIN(count, MIN(N_TTY_BUF_SIZE - tty->read_cnt,
 				   N_TTY_BUF_SIZE - tty->read_head));
 		memcpy(tty->read_buf + tty->read_head, cp, i);
@@ -676,6 +712,7 @@
 		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)
@@ -707,8 +744,11 @@
 	if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) {
 		if (tty->fasync)
 			kill_fasync(tty->fasync, SIGIO);
-		if (tty->read_wait)
+		if (tty->read_wait||tty->poll_wait)
+		{
 			wake_up_interruptible(&tty->read_wait);
+			wake_up_interruptible(&tty->poll_wait);
+		}
 	}
 
 	/*
@@ -850,15 +890,20 @@
 {
 	int retval;
 	ssize_t n;
+	unsigned long flags;
 
 	retval = 0;
+	spin_lock_irqsave(&tty->read_lock, flags);
 	n = MIN(*nr, MIN(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail));
+	spin_unlock_irqrestore(&tty->read_lock, flags);
 	if (n) {
 		mb();
 		retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
 		n -= retval;
+		spin_lock_irqsave(&tty->read_lock, flags);
 		tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
 		tty->read_cnt -= n;
+		spin_unlock_irqrestore(&tty->read_lock, flags);
 		*b += n;
 		*nr -= n;
 	}
@@ -875,6 +920,7 @@
 	ssize_t retval = 0;
 	ssize_t size;
 	long timeout;
+	unsigned long flags;
 
 do_it_again:
 
@@ -910,7 +956,7 @@
 		if (minimum) {
 			if (time)
 				tty->minimum_to_wake = 1;
-			else if (!waitqueue_active(&tty->read_wait) ||
+			else if ((!waitqueue_active(&tty->read_wait) && !waitqueue_active(&tty->poll_wait)) ||
 				 (tty->minimum_to_wake > minimum))
 				tty->minimum_to_wake = minimum;
 		} else {
@@ -993,9 +1039,11 @@
 				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--;
+				spin_unlock_irqrestore(&tty->read_lock, flags);
 
 				if (!eol || (c != __DISABLED_CHAR)) {
 					put_user(c, b++);
@@ -1040,7 +1088,7 @@
 	up(&tty->atomic_read);
 	remove_wait_queue(&tty->read_wait, &wait);
 
-	if (!waitqueue_active(&tty->read_wait))
+	if (!waitqueue_active(&tty->read_wait) && !waitqueue_active(&tty->poll_wait))
 		tty->minimum_to_wake = minimum;
 
 	current->state = TASK_RUNNING;
@@ -1094,7 +1142,9 @@
 				nr -= num;
 				if (nr == 0)
 					break;
+				current->state = TASK_RUNNING;
 				get_user(c, b);
+				current->state = TASK_INTERRUPTIBLE;
 				if (opost(c, tty) < 0)
 					break;
 				b++; nr--;
@@ -1102,7 +1152,9 @@
 			if (tty->driver.flush_chars)
 				tty->driver.flush_chars(tty);
 		} else {
+			current->state = TASK_RUNNING;
 			c = tty->driver.write(tty, 1, b, nr);
+			current->state = TASK_INTERRUPTIBLE;
 			if (c < 0) {
 				retval = c;
 				goto break_out;
@@ -1128,8 +1180,7 @@
 {
 	unsigned int mask = 0;
 
-	poll_wait(file, &tty->read_wait, wait);
-	poll_wait(file, &tty->write_wait, wait);
+	poll_wait(file, &tty->poll_wait, wait);
 	if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty)))
 		mask |= POLLIN | POLLRDNORM;
 	if (tty->packet && tty->link->ctrl_status)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)