浏览代码

tty: reorder ldisc locking

We need to release the BTM in paste_selection() when
sleeping in tty_ldisc_ref_wait to avoid deadlocks
with tty_ldisc_enable.

In tty_set_ldisc, we now always grab the BTM before
taking the ldisc_mutex in order to avoid AB-BA
deadlocks between the two.

tty_ldisc_halt potentially blocks on a workqueue
function that takes the BTM, so we must release
the BTM before calling it.

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Arnd Bergmann 15 年之前
父节点
当前提交
60af22d2ed
共有 2 个文件被更改,包括 27 次插入6 次删除
  1. 7 2
      drivers/char/selection.c
  2. 20 4
      drivers/char/tty_ldisc.c

+ 7 - 2
drivers/char/selection.c

@@ -319,8 +319,13 @@ int paste_selection(struct tty_struct *tty)
 	poke_blanked_console();
 	poke_blanked_console();
 	release_console_sem();
 	release_console_sem();
 
 
-	ld = tty_ldisc_ref_wait(tty);
-	
+	ld = tty_ldisc_ref(tty);
+	if (!ld) {
+		tty_unlock();
+		ld = tty_ldisc_ref_wait(tty);
+		tty_lock();
+	}
+
 	add_wait_queue(&vc->paste_wait, &wait);
 	add_wait_queue(&vc->paste_wait, &wait);
 	while (sel_buffer && sel_buffer_lth > pasted) {
 	while (sel_buffer && sel_buffer_lth > pasted) {
 		set_current_state(TASK_INTERRUPTIBLE);
 		set_current_state(TASK_INTERRUPTIBLE);

+ 20 - 4
drivers/char/tty_ldisc.c

@@ -582,6 +582,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
 
 	tty_wait_until_sent(tty, 0);
 	tty_wait_until_sent(tty, 0);
 
 
+	tty_lock();
 	mutex_lock(&tty->ldisc_mutex);
 	mutex_lock(&tty->ldisc_mutex);
 
 
 	/*
 	/*
@@ -591,13 +592,13 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
 
 	while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
 	while (test_bit(TTY_LDISC_CHANGING, &tty->flags)) {
 		mutex_unlock(&tty->ldisc_mutex);
 		mutex_unlock(&tty->ldisc_mutex);
+		tty_unlock();
 		wait_event(tty_ldisc_wait,
 		wait_event(tty_ldisc_wait,
 			test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0);
 			test_bit(TTY_LDISC_CHANGING, &tty->flags) == 0);
+		tty_lock();
 		mutex_lock(&tty->ldisc_mutex);
 		mutex_lock(&tty->ldisc_mutex);
 	}
 	}
 
 
-	tty_lock();
-
 	set_bit(TTY_LDISC_CHANGING, &tty->flags);
 	set_bit(TTY_LDISC_CHANGING, &tty->flags);
 
 
 	/*
 	/*
@@ -634,8 +635,8 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
 
 	flush_scheduled_work();
 	flush_scheduled_work();
 
 
-	mutex_lock(&tty->ldisc_mutex);
 	tty_lock();
 	tty_lock();
+	mutex_lock(&tty->ldisc_mutex);
 	if (test_bit(TTY_HUPPED, &tty->flags)) {
 	if (test_bit(TTY_HUPPED, &tty->flags)) {
 		/* We were raced by the hangup method. It will have stomped
 		/* We were raced by the hangup method. It will have stomped
 		   the ldisc data and closed the ldisc down */
 		   the ldisc data and closed the ldisc down */
@@ -782,7 +783,20 @@ void tty_ldisc_hangup(struct tty_struct *tty)
 	 * Avoid racing set_ldisc or tty_ldisc_release
 	 * Avoid racing set_ldisc or tty_ldisc_release
 	 */
 	 */
 	mutex_lock(&tty->ldisc_mutex);
 	mutex_lock(&tty->ldisc_mutex);
-	tty_ldisc_halt(tty);
+
+	/*
+	 * this is like tty_ldisc_halt, but we need to give up
+	 * the BTM before calling cancel_delayed_work_sync,
+	 * which may need to wait for another function taking the BTM
+	 */
+	clear_bit(TTY_LDISC, &tty->flags);
+	tty_unlock();
+	cancel_delayed_work_sync(&tty->buf.work);
+	mutex_unlock(&tty->ldisc_mutex);
+
+	tty_lock();
+	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
@@ -853,8 +867,10 @@ void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
 	 * race with the set_ldisc code path.
 	 * race with the set_ldisc code path.
 	 */
 	 */
 
 
+	tty_unlock();
 	tty_ldisc_halt(tty);
 	tty_ldisc_halt(tty);
 	flush_scheduled_work();
 	flush_scheduled_work();
+	tty_lock();
 
 
 	mutex_lock(&tty->ldisc_mutex);
 	mutex_lock(&tty->ldisc_mutex);
 	/*
 	/*