Selaa lähdekoodia

tty: Fix ldisc halt sequence on hangup

Flip buffer work cannot be cancelled until all outstanding ldisc
references have been released. Convert the ldisc ref wait into
a full ldisc halt with buffer work cancellation.

Note that the legacy mutex is not held while cancelling.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Peter Hurley 12 vuotta sitten
vanhempi
commit
76bc35e78f
1 muutettua tiedostoa jossa 19 lisäystä ja 20 poistoa
  1. 19 20
      drivers/tty/tty_ldisc.c

+ 19 - 20
drivers/tty/tty_ldisc.c

@@ -551,22 +551,30 @@ static int tty_ldisc_wait_idle(struct tty_struct *tty, long timeout)
 }
 }
 
 
 /**
 /**
- *	tty_ldisc_hangup_wait_idle - wait for the ldisc to become idle
- *	@tty: tty to wait for
- *
- *	Wait for the line discipline to become idle. The discipline must
- *	have been halted for this to guarantee it remains idle.
+ *	tty_ldisc_hangup_halt - halt the line discipline for hangup
+ *	@tty: tty being hung up
  *
  *
+ *	Shut down the line discipline and work queue for the tty device
+ *	being hungup. Clear the TTY_LDISC flag to ensure no further
+ *	references can be obtained, wait for remaining references to be
+ *	released, and cancel pending buffer work to ensure no more
+ *	data is fed to this ldisc.
  *	Caller must hold legacy and ->ldisc_mutex.
  *	Caller must hold legacy and ->ldisc_mutex.
  *
  *
  *	NB: tty_set_ldisc() is prevented from changing the ldisc concurrently
  *	NB: tty_set_ldisc() is prevented from changing the ldisc concurrently
  *	with this function by checking the TTY_HUPPING flag.
  *	with this function by checking the TTY_HUPPING flag.
+ *
+ *	NB: if tty->ldisc is NULL then buffer work does not need to be
+ *	cancelled because it must already have done as a precondition
+ *	of closing the ldisc and setting tty->ldisc to NULL
  */
  */
-static bool tty_ldisc_hangup_wait_idle(struct tty_struct *tty)
+static bool tty_ldisc_hangup_halt(struct tty_struct *tty)
 {
 {
 	char cur_n[TASK_COMM_LEN], tty_n[64];
 	char cur_n[TASK_COMM_LEN], tty_n[64];
 	long timeout = 3 * HZ;
 	long timeout = 3 * HZ;
 
 
+	clear_bit(TTY_LDISC, &tty->flags);
+
 	if (tty->ldisc) {	/* Not yet closed */
 	if (tty->ldisc) {	/* Not yet closed */
 		tty_unlock(tty);
 		tty_unlock(tty);
 
 
@@ -577,6 +585,10 @@ static bool tty_ldisc_hangup_wait_idle(struct tty_struct *tty)
 				__func__, get_task_comm(cur_n, current),
 				__func__, get_task_comm(cur_n, current),
 				tty_name(tty, tty_n));
 				tty_name(tty, tty_n));
 		}
 		}
+
+		cancel_work_sync(&tty->port->buf.work);
+		set_bit(TTY_LDISC_HALTED, &tty->flags);
+
 		/* must reacquire both locks and preserve lock order */
 		/* must reacquire both locks and preserve lock order */
 		mutex_unlock(&tty->ldisc_mutex);
 		mutex_unlock(&tty->ldisc_mutex);
 		tty_lock(tty);
 		tty_lock(tty);
@@ -851,24 +863,11 @@ void tty_ldisc_hangup(struct tty_struct *tty)
 	 */
 	 */
 	mutex_lock(&tty->ldisc_mutex);
 	mutex_lock(&tty->ldisc_mutex);
 
 
-	/*
-	 * this is like tty_ldisc_halt, but we need to give up
-	 * the BTM before calling cancel_work_sync, which may
-	 * need to wait for another function taking the BTM
-	 */
-	clear_bit(TTY_LDISC, &tty->flags);
-	tty_unlock(tty);
-	cancel_work_sync(&tty->port->buf.work);
-	set_bit(TTY_LDISC_HALTED, &tty->flags);
-	mutex_unlock(&tty->ldisc_mutex);
-	tty_lock(tty);
-	mutex_lock(&tty->ldisc_mutex);
-
 	/* At this point we have a closed ldisc and we want to
 	/* At this point we have a closed ldisc and we want to
 	   reopen it. We could defer this to the next open but
 	   reopen it. We could defer this to the next open but
 	   it means auditing a lot of other paths so this is
 	   it means auditing a lot of other paths so this is
 	   a FIXME */
 	   a FIXME */
-	if (tty_ldisc_hangup_wait_idle(tty)) {
+	if (tty_ldisc_hangup_halt(tty)) {
 		if (reset == 0) {
 		if (reset == 0) {
 
 
 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))
 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))