@@ -491,6 +491,41 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
491
491
tty_ldisc_debug (tty , "%p: closed\n" , ld );
492
492
}
493
493
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
+
494
529
/**
495
530
* tty_set_ldisc - set line discipline
496
531
* @tty: the terminal to set
@@ -504,7 +539,12 @@ static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
504
539
505
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
549
tty_lock (tty );
510
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
559
/* Check the no-op case */
520
- old_disc = tty -> ldisc -> ops -> num ;
521
- if (old_disc == disc )
560
+ if (tty -> ldisc -> ops -> num == disc )
522
561
goto out ;
523
562
524
563
if (test_bit (TTY_HUPPED , & tty -> flags )) {
@@ -527,32 +566,42 @@ int tty_set_ldisc(struct tty_struct *tty, int disc)
527
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
579
if (retval < 0 ) {
532
580
/* Back to the old one or N_TTY if we can't */
533
- if (tty_ldisc_reinit (tty , old_disc ) < 0 ) {
534
- pr_err ("tty: TIOCSETD failed, reinitializing N_TTY\n" );
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
- }
581
+ tty_ldisc_put (new_ldisc );
582
+ tty_ldisc_restore (tty , old_ldisc );
540
583
}
541
584
542
- if (tty -> ldisc && tty -> ldisc -> ops -> num != old_disc &&
543
- tty -> ops -> set_ldisc ) {
585
+ if (tty -> ldisc -> ops -> num != old_ldisc -> ops -> num && tty -> ops -> set_ldisc ) {
544
586
down_read (& tty -> termios_rwsem );
545
587
tty -> ops -> set_ldisc (tty );
546
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
597
out :
550
598
tty_ldisc_unlock (tty );
551
599
552
600
/* Restart the work queue in case no characters kick it off. Safe if
553
601
already running */
554
602
tty_buffer_restart_work (tty -> port );
555
603
err :
604
+ tty_ldisc_put (new_ldisc ); /* drop the extra reference */
556
605
tty_unlock (tty );
557
606
return retval ;
558
607
}
@@ -613,8 +662,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
613
662
int retval ;
614
663
615
664
ld = tty_ldisc_get (tty , disc );
616
- if (IS_ERR (ld ))
665
+ if (IS_ERR (ld )) {
666
+ BUG_ON (disc == N_TTY );
617
667
return PTR_ERR (ld );
668
+ }
618
669
619
670
if (tty -> ldisc ) {
620
671
tty_ldisc_close (tty , tty -> ldisc );
@@ -626,8 +677,10 @@ int tty_ldisc_reinit(struct tty_struct *tty, int disc)
626
677
tty_set_termios_ldisc (tty , disc );
627
678
retval = tty_ldisc_open (tty , tty -> ldisc );
628
679
if (retval ) {
629
- tty_ldisc_put (tty -> ldisc );
630
- tty -> ldisc = NULL ;
680
+ if (!WARN_ON (disc == N_TTY )) {
681
+ tty_ldisc_put (tty -> ldisc );
682
+ tty -> ldisc = NULL ;
683
+ }
631
684
}
632
685
return retval ;
633
686
}
0 commit comments