aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/tty
diff options
context:
space:
mode:
authorPeter Hurley <peter@hurleysoftware.com>2013-03-20 13:20:43 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-04-09 20:05:02 -0400
commit39f610e40eecc39e4c34e047fd6904ca6b525520 (patch)
tree39af27120456a3a3b68b7068d65a71f5614db008 /drivers/tty
parent31bdfc649f4577c9b1120c67b7b1e85f7777aad0 (diff)
tty: Fix race condition if flushing tty flip buffers
As Ilya Zykov identified in his patch 'PROBLEM: Race condition in tty buffer's function flush_to_ldisc()', a race condition exists which allows a parallel flush_to_ldisc() to flush and free the tty flip buffers while those buffers are in-use. For example, CPU 0 | CPU 1 | CPU 2 | flush_to_ldisc() | | grab spin lock | tty_buffer_flush() | | flush_to_ldisc() wait for spin lock | | wait for spin lock | if (!test_and_set_bit(TTYP_FLUSHING)) | | while (next flip buffer) | | ... | | drop spin lock | grab spin lock | | if (test_bit(TTYP_FLUSHING)) | | set_bit(TTYP_FLUSHPENDING) | receive_buf() | drop spin lock | | | | grab spin lock | | if (!test_and_set_bit(TTYP_FLUSHING)) | | if (test_bit(TTYP_FLUSHPENDING)) | | __tty_buffer_flush() CPU 2 has just flushed and freed all tty flip buffers while CPU 1 is transferring data from the head flip buffer. The original patch was rejected under the assumption that parallel flush_to_ldisc() was not possible. Because of necessary changes to the workqueue api, work items can execute in parallel on SMP. This patch differs slightly from the original patch by testing for a pending flush _after_ each receive_buf(), since TTYP_FLUSHPENDING can only be set while the lock is dropped around receive_buf(). Reported-by: Ilya Zykov <linux@izyk.ru> Signed-off-by: Peter Hurley <peter@hurleysoftware.com> Acked-by: Ilya Zykov <linux@izyk.ru> Cc: Jiri Slaby <jslaby@suse.cz> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/tty_buffer.c22
1 files changed, 10 insertions, 12 deletions
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index 578aa7594b11..9121c1f7aeef 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -449,11 +449,6 @@ static void flush_to_ldisc(struct work_struct *work)
449 tty_buffer_free(port, head); 449 tty_buffer_free(port, head);
450 continue; 450 continue;
451 } 451 }
452 /* Ldisc or user is trying to flush the buffers
453 we are feeding to the ldisc, stop feeding the
454 line discipline as we want to empty the queue */
455 if (test_bit(TTYP_FLUSHPENDING, &port->iflags))
456 break;
457 if (!tty->receive_room) 452 if (!tty->receive_room)
458 break; 453 break;
459 if (count > tty->receive_room) 454 if (count > tty->receive_room)
@@ -465,17 +460,20 @@ static void flush_to_ldisc(struct work_struct *work)
465 disc->ops->receive_buf(tty, char_buf, 460 disc->ops->receive_buf(tty, char_buf,
466 flag_buf, count); 461 flag_buf, count);
467 spin_lock_irqsave(&buf->lock, flags); 462 spin_lock_irqsave(&buf->lock, flags);
463 /* Ldisc or user is trying to flush the buffers.
464 We may have a deferred request to flush the
465 input buffer, if so pull the chain under the lock
466 and empty the queue */
467 if (test_bit(TTYP_FLUSHPENDING, &port->iflags)) {
468 __tty_buffer_flush(port);
469 clear_bit(TTYP_FLUSHPENDING, &port->iflags);
470 wake_up(&tty->read_wait);
471 break;
472 }
468 } 473 }
469 clear_bit(TTYP_FLUSHING, &port->iflags); 474 clear_bit(TTYP_FLUSHING, &port->iflags);
470 } 475 }
471 476
472 /* We may have a deferred request to flush the input buffer,
473 if so pull the chain under the lock and empty the queue */
474 if (test_bit(TTYP_FLUSHPENDING, &port->iflags)) {
475 __tty_buffer_flush(port);
476 clear_bit(TTYP_FLUSHPENDING, &port->iflags);
477 wake_up(&tty->read_wait);
478 }
479 spin_unlock_irqrestore(&buf->lock, flags); 477 spin_unlock_irqrestore(&buf->lock, flags);
480 478
481 tty_ldisc_deref(disc); 479 tty_ldisc_deref(disc);