aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/tty_io.c
diff options
context:
space:
mode:
authorJason Baron <jbaron@redhat.com>2005-09-09 16:01:57 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2005-09-09 16:57:31 -0400
commitff55fe2075e3901db4dbdc00e0b44a71bef97afd (patch)
treec6dfc8ba5d04fda4c5cb15cdebd5425ab674f8df /drivers/char/tty_io.c
parent69ac59647e66c1b53fb98fe8b6d0f2099cffad60 (diff)
[PATCH] pty_chars_in_buffer oops fix
The idea of this patch is to lock both sides of a ptmx/pty pair during line discipline changing. This is needed to ensure that say a poll on one side of the pty doesn't occur while the line discipline is actively being changed. This resulted in an oops reported on lkml, see: http://marc.theaimsgroup.com/?l=linux-kernel&m=111342171410005&w=2 A 'hacky' approach was previously implmemented which served to eliminate the poll vs. line discipline changing race. However, this patch takes a more general approach to the issue. The patch only adds locking on a less often used path, the line-discipline changing path, as opposed to locking the ptmx/pty pair on read/write/poll paths. The patch below, takes both ldisc locks in either order b/c the locks are both taken under the same spinlock(). I thought about locking the ptmx/pty separately, such as master always first but that introduces a 3 way deadlock. For example, process 1 does a blocking read on the slave side. Then, process 2 does an ldisc change on the slave side, which acquires the master ldisc lock but not the slave's. Finally, process 3 does a write which blocks on the process 2's ldisc reference. This patch does introduce some changes in semantics. For example, a line discipline change on side 'a' of a ptmx/pty pair, will now wait for a read/write to complete on the other side, or side 'b'. The current behavior is to simply wait for any read/writes on only side 'a', not both sides 'a' and 'b'. I think this behavior makes sense, but I wanted to point it out. I've tested the patch with a bunch of read/write/poll while changing the line discipline out from underneath. This patch obviates the need for the above "hide the problem" patch. Signed-off-by: Jason Baron <jbaron@redhat.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/char/tty_io.c')
-rw-r--r--drivers/char/tty_io.c79
1 files changed, 54 insertions, 25 deletions
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
index 9d657127f313..6a56ae4f7725 100644
--- a/drivers/char/tty_io.c
+++ b/drivers/char/tty_io.c
@@ -469,21 +469,19 @@ static void tty_ldisc_enable(struct tty_struct *tty)
469 469
470static int tty_set_ldisc(struct tty_struct *tty, int ldisc) 470static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
471{ 471{
472 int retval = 0; 472 int retval = 0;
473 struct tty_ldisc o_ldisc; 473 struct tty_ldisc o_ldisc;
474 char buf[64]; 474 char buf[64];
475 int work; 475 int work;
476 unsigned long flags; 476 unsigned long flags;
477 struct tty_ldisc *ld; 477 struct tty_ldisc *ld;
478 struct tty_struct *o_tty;
478 479
479 if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS)) 480 if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS))
480 return -EINVAL; 481 return -EINVAL;
481 482
482restart: 483restart:
483 484
484 if (tty->ldisc.num == ldisc)
485 return 0; /* We are already in the desired discipline */
486
487 ld = tty_ldisc_get(ldisc); 485 ld = tty_ldisc_get(ldisc);
488 /* Eduardo Blanco <ejbs@cs.cs.com.uy> */ 486 /* Eduardo Blanco <ejbs@cs.cs.com.uy> */
489 /* Cyrus Durgin <cider@speakeasy.org> */ 487 /* Cyrus Durgin <cider@speakeasy.org> */
@@ -494,45 +492,74 @@ restart:
494 if (ld == NULL) 492 if (ld == NULL)
495 return -EINVAL; 493 return -EINVAL;
496 494
497 o_ldisc = tty->ldisc;
498
499 tty_wait_until_sent(tty, 0); 495 tty_wait_until_sent(tty, 0);
500 496
497 if (tty->ldisc.num == ldisc) {
498 tty_ldisc_put(ldisc);
499 return 0;
500 }
501
502 o_ldisc = tty->ldisc;
503 o_tty = tty->link;
504
501 /* 505 /*
502 * Make sure we don't change while someone holds a 506 * Make sure we don't change while someone holds a
503 * reference to the line discipline. The TTY_LDISC bit 507 * reference to the line discipline. The TTY_LDISC bit
504 * prevents anyone taking a reference once it is clear. 508 * prevents anyone taking a reference once it is clear.
505 * We need the lock to avoid racing reference takers. 509 * We need the lock to avoid racing reference takers.
506 */ 510 */
507 511
508 spin_lock_irqsave(&tty_ldisc_lock, flags); 512 spin_lock_irqsave(&tty_ldisc_lock, flags);
509 if(tty->ldisc.refcount) 513 if (tty->ldisc.refcount || (o_tty && o_tty->ldisc.refcount)) {
510 { 514 if(tty->ldisc.refcount) {
511 /* Free the new ldisc we grabbed. Must drop the lock 515 /* Free the new ldisc we grabbed. Must drop the lock
512 first. */ 516 first. */
517 spin_unlock_irqrestore(&tty_ldisc_lock, flags);
518 tty_ldisc_put(ldisc);
519 /*
520 * There are several reasons we may be busy, including
521 * random momentary I/O traffic. We must therefore
522 * retry. We could distinguish between blocking ops
523 * and retries if we made tty_ldisc_wait() smarter. That
524 * is up for discussion.
525 */
526 if (wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0)
527 return -ERESTARTSYS;
528 goto restart;
529 }
530 if(o_tty && o_tty->ldisc.refcount) {
531 spin_unlock_irqrestore(&tty_ldisc_lock, flags);
532 tty_ldisc_put(ldisc);
533 if (wait_event_interruptible(tty_ldisc_wait, o_tty->ldisc.refcount == 0) < 0)
534 return -ERESTARTSYS;
535 goto restart;
536 }
537 }
538
539 /* if the TTY_LDISC bit is set, then we are racing against another ldisc change */
540
541 if (!test_bit(TTY_LDISC, &tty->flags)) {
513 spin_unlock_irqrestore(&tty_ldisc_lock, flags); 542 spin_unlock_irqrestore(&tty_ldisc_lock, flags);
514 tty_ldisc_put(ldisc); 543 tty_ldisc_put(ldisc);
515 /* 544 ld = tty_ldisc_ref_wait(tty);
516 * There are several reasons we may be busy, including 545 tty_ldisc_deref(ld);
517 * random momentary I/O traffic. We must therefore
518 * retry. We could distinguish between blocking ops
519 * and retries if we made tty_ldisc_wait() smarter. That
520 * is up for discussion.
521 */
522 if(wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0)
523 return -ERESTARTSYS;
524 goto restart; 546 goto restart;
525 } 547 }
526 clear_bit(TTY_LDISC, &tty->flags); 548
549 clear_bit(TTY_LDISC, &tty->flags);
527 clear_bit(TTY_DONT_FLIP, &tty->flags); 550 clear_bit(TTY_DONT_FLIP, &tty->flags);
551 if (o_tty) {
552 clear_bit(TTY_LDISC, &o_tty->flags);
553 clear_bit(TTY_DONT_FLIP, &o_tty->flags);
554 }
528 spin_unlock_irqrestore(&tty_ldisc_lock, flags); 555 spin_unlock_irqrestore(&tty_ldisc_lock, flags);
529 556
530 /* 557 /*
531 * From this point on we know nobody has an ldisc 558 * From this point on we know nobody has an ldisc
532 * usage reference, nor can they obtain one until 559 * usage reference, nor can they obtain one until
533 * we say so later on. 560 * we say so later on.
534 */ 561 */
535 562
536 work = cancel_delayed_work(&tty->flip.work); 563 work = cancel_delayed_work(&tty->flip.work);
537 /* 564 /*
538 * Wait for ->hangup_work and ->flip.work handlers to terminate 565 * Wait for ->hangup_work and ->flip.work handlers to terminate
@@ -583,10 +610,12 @@ restart:
583 */ 610 */
584 611
585 tty_ldisc_enable(tty); 612 tty_ldisc_enable(tty);
613 if (o_tty)
614 tty_ldisc_enable(o_tty);
586 615
587 /* Restart it in case no characters kick it off. Safe if 616 /* Restart it in case no characters kick it off. Safe if
588 already running */ 617 already running */
589 if(work) 618 if (work)
590 schedule_delayed_work(&tty->flip.work, 1); 619 schedule_delayed_work(&tty->flip.work, 1);
591 return retval; 620 return retval;
592} 621}