diff options
author | Jason Baron <jbaron@redhat.com> | 2005-09-09 16:01:57 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-09-09 16:57:31 -0400 |
commit | ff55fe2075e3901db4dbdc00e0b44a71bef97afd (patch) | |
tree | c6dfc8ba5d04fda4c5cb15cdebd5425ab674f8df | |
parent | 69ac59647e66c1b53fb98fe8b6d0f2099cffad60 (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>
-rw-r--r-- | drivers/char/pty.c | 5 | ||||
-rw-r--r-- | drivers/char/tty_io.c | 79 |
2 files changed, 56 insertions, 28 deletions
diff --git a/drivers/char/pty.c b/drivers/char/pty.c index da32889d22c0..49f3997fd251 100644 --- a/drivers/char/pty.c +++ b/drivers/char/pty.c | |||
@@ -149,15 +149,14 @@ static int pty_write_room(struct tty_struct *tty) | |||
149 | static int pty_chars_in_buffer(struct tty_struct *tty) | 149 | static int pty_chars_in_buffer(struct tty_struct *tty) |
150 | { | 150 | { |
151 | struct tty_struct *to = tty->link; | 151 | struct tty_struct *to = tty->link; |
152 | ssize_t (*chars_in_buffer)(struct tty_struct *); | ||
153 | int count; | 152 | int count; |
154 | 153 | ||
155 | /* We should get the line discipline lock for "tty->link" */ | 154 | /* We should get the line discipline lock for "tty->link" */ |
156 | if (!to || !(chars_in_buffer = to->ldisc.chars_in_buffer)) | 155 | if (!to || !to->ldisc.chars_in_buffer) |
157 | return 0; | 156 | return 0; |
158 | 157 | ||
159 | /* The ldisc must report 0 if no characters available to be read */ | 158 | /* The ldisc must report 0 if no characters available to be read */ |
160 | count = chars_in_buffer(to); | 159 | count = to->ldisc.chars_in_buffer(to); |
161 | 160 | ||
162 | if (tty->driver->subtype == PTY_TYPE_SLAVE) return count; | 161 | if (tty->driver->subtype == PTY_TYPE_SLAVE) return count; |
163 | 162 | ||
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 | ||
470 | static int tty_set_ldisc(struct tty_struct *tty, int ldisc) | 470 | static 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 | ||
482 | restart: | 483 | restart: |
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 | } |