diff options
-rw-r--r-- | drivers/tty/tty_ldisc.c | 85 |
1 files changed, 69 insertions, 16 deletions
diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c index b0500a0a87b8..e4603b09863a 100644 --- a/drivers/tty/tty_ldisc.c +++ b/drivers/tty/tty_ldisc.c | |||
@@ -492,6 +492,41 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld) | |||
492 | } | 492 | } |
493 | 493 | ||
494 | /** | 494 | /** |
495 | * tty_ldisc_restore - helper for tty ldisc change | ||
496 | * @tty: tty to recover | ||
497 | * @old: previous ldisc | ||
498 | * | ||
499 | * Restore the previous line discipline or N_TTY when a line discipline | ||
500 | * change fails due to an open error | ||
501 | */ | ||
502 | |||
503 | static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old) | ||
504 | { | ||
505 | struct tty_ldisc *new_ldisc; | ||
506 | int r; | ||
507 | |||
508 | /* There is an outstanding reference here so this is safe */ | ||
509 | old = tty_ldisc_get(tty, old->ops->num); | ||
510 | WARN_ON(IS_ERR(old)); | ||
511 | tty->ldisc = old; | ||
512 | tty_set_termios_ldisc(tty, old->ops->num); | ||
513 | if (tty_ldisc_open(tty, old) < 0) { | ||
514 | tty_ldisc_put(old); | ||
515 | /* This driver is always present */ | ||
516 | new_ldisc = tty_ldisc_get(tty, N_TTY); | ||
517 | if (IS_ERR(new_ldisc)) | ||
518 | panic("n_tty: get"); | ||
519 | tty->ldisc = new_ldisc; | ||
520 | tty_set_termios_ldisc(tty, N_TTY); | ||
521 | r = tty_ldisc_open(tty, new_ldisc); | ||
522 | if (r < 0) | ||
523 | panic("Couldn't open N_TTY ldisc for " | ||
524 | "%s --- error %d.", | ||
525 | tty_name(tty), r); | ||
526 | } | ||
527 | } | ||
528 | |||
529 | /** | ||
495 | * tty_set_ldisc - set line discipline | 530 | * tty_set_ldisc - set line discipline |
496 | * @tty: the terminal to set | 531 | * @tty: the terminal to set |
497 | * @ldisc: the line discipline | 532 | * @ldisc: the line discipline |
@@ -504,7 +539,12 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld) | |||
504 | 539 | ||
505 | int tty_set_ldisc(struct tty_struct *tty, int disc) | 540 | int tty_set_ldisc(struct tty_struct *tty, int disc) |
506 | { | 541 | { |
507 | int retval, old_disc; | 542 | int retval; |
543 | struct tty_ldisc *old_ldisc, *new_ldisc; | ||
544 | |||
545 | new_ldisc = tty_ldisc_get(tty, disc); | ||
546 | if (IS_ERR(new_ldisc)) | ||
547 | return PTR_ERR(new_ldisc); | ||
508 | 548 | ||
509 | tty_lock(tty); | 549 | tty_lock(tty); |
510 | retval = tty_ldisc_lock(tty, 5 * HZ); | 550 | retval = tty_ldisc_lock(tty, 5 * HZ); |
@@ -517,8 +557,7 @@ int tty_set_ldisc(struct tty_struct *tty, int disc) | |||
517 | } | 557 | } |
518 | 558 | ||
519 | /* Check the no-op case */ | 559 | /* Check the no-op case */ |
520 | old_disc = tty->ldisc->ops->num; | 560 | if (tty->ldisc->ops->num == disc) |
521 | if (old_disc == disc) | ||
522 | goto out; | 561 | goto out; |
523 | 562 | ||
524 | if (test_bit(TTY_HUPPED, &tty->flags)) { | 563 | if (test_bit(TTY_HUPPED, &tty->flags)) { |
@@ -527,25 +566,34 @@ int tty_set_ldisc(struct tty_struct *tty, int disc) | |||
527 | goto out; | 566 | goto out; |
528 | } | 567 | } |
529 | 568 | ||
530 | retval = tty_ldisc_reinit(tty, disc); | 569 | old_ldisc = tty->ldisc; |
570 | |||
571 | /* Shutdown the old discipline. */ | ||
572 | tty_ldisc_close(tty, old_ldisc); | ||
573 | |||
574 | /* Now set up the new line discipline. */ | ||
575 | tty->ldisc = new_ldisc; | ||
576 | tty_set_termios_ldisc(tty, disc); | ||
577 | |||
578 | retval = tty_ldisc_open(tty, new_ldisc); | ||
531 | if (retval < 0) { | 579 | if (retval < 0) { |
532 | /* Back to the old one or N_TTY if we can't */ | 580 | /* Back to the old one or N_TTY if we can't */ |
533 | if (tty_ldisc_reinit(tty, old_disc) < 0) { | 581 | tty_ldisc_put(new_ldisc); |
534 | pr_err("tty: TIOCSETD failed, reinitializing N_TTY\n"); | 582 | tty_ldisc_restore(tty, old_ldisc); |
535 | if (tty_ldisc_reinit(tty, N_TTY) < 0) { | ||
536 | /* At this point we have tty->ldisc == NULL. */ | ||
537 | pr_err("tty: reinitializing N_TTY failed\n"); | ||
538 | } | ||
539 | } | ||
540 | } | 583 | } |
541 | 584 | ||
542 | if (tty->ldisc && tty->ldisc->ops->num != old_disc && | 585 | if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) { |
543 | tty->ops->set_ldisc) { | ||
544 | down_read(&tty->termios_rwsem); | 586 | down_read(&tty->termios_rwsem); |
545 | tty->ops->set_ldisc(tty); | 587 | tty->ops->set_ldisc(tty); |
546 | up_read(&tty->termios_rwsem); | 588 | up_read(&tty->termios_rwsem); |
547 | } | 589 | } |
548 | 590 | ||
591 | /* At this point we hold a reference to the new ldisc and a | ||
592 | reference to the old ldisc, or we hold two references to | ||
593 | the old ldisc (if it was restored as part of error cleanup | ||
594 | above). In either case, releasing a single reference from | ||
595 | the old ldisc is correct. */ | ||
596 | new_ldisc = old_ldisc; | ||
549 | out: | 597 | out: |
550 | tty_ldisc_unlock(tty); | 598 | tty_ldisc_unlock(tty); |
551 | 599 | ||
@@ -553,6 +601,7 @@ out: | |||
553 | already running */ | 601 | already running */ |
554 | tty_buffer_restart_work(tty->port); | 602 | tty_buffer_restart_work(tty->port); |
555 | err: | 603 | err: |
604 | tty_ldisc_put(new_ldisc); /* drop the extra reference */ | ||
556 | tty_unlock(tty); | 605 | tty_unlock(tty); |
557 | return retval; | 606 | return retval; |
558 | } | 607 | } |
@@ -613,8 +662,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc) | |||
613 | int retval; | 662 | int retval; |
614 | 663 | ||
615 | ld = tty_ldisc_get(tty, disc); | 664 | ld = tty_ldisc_get(tty, disc); |
616 | if (IS_ERR(ld)) | 665 | if (IS_ERR(ld)) { |
666 | BUG_ON(disc == N_TTY); | ||
617 | return PTR_ERR(ld); | 667 | return PTR_ERR(ld); |
668 | } | ||
618 | 669 | ||
619 | if (tty->ldisc) { | 670 | if (tty->ldisc) { |
620 | tty_ldisc_close(tty, tty->ldisc); | 671 | tty_ldisc_close(tty, tty->ldisc); |
@@ -626,8 +677,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc) | |||
626 | tty_set_termios_ldisc(tty, disc); | 677 | tty_set_termios_ldisc(tty, disc); |
627 | retval = tty_ldisc_open(tty, tty->ldisc); | 678 | retval = tty_ldisc_open(tty, tty->ldisc); |
628 | if (retval) { | 679 | if (retval) { |
629 | tty_ldisc_put(tty->ldisc); | 680 | if (!WARN_ON(disc == N_TTY)) { |
630 | tty->ldisc = NULL; | 681 | tty_ldisc_put(tty->ldisc); |
682 | tty->ldisc = NULL; | ||
683 | } | ||
631 | } | 684 | } |
632 | return retval; | 685 | return retval; |
633 | } | 686 | } |