When the synth output cannot make progress, the catch-up loop in _spk_do_catch_up() (and its local copies in the serial synth drivers) can spin forever: nothing tells it that the synth is dead. This happens when the tty write keeps returning 0 because nothing consumes the data (e.g. a serial port with no device attached), or when an I/O error deactivates the synth and the loop never notices that synth->alive became 0. While speakup_thread() holds spk_mutex across the loop, any synth_add(), synth_remove() or synth_init() then blocks forever, reported as a hung task on module unload: mutex_lock+0x54/0x68 synth_remove+0x28/0xb8 [speakup] _spk_do_catch_up+... [speakup_apollo] Check synth->alive in the loops as well, so catch_up() stops as soon as the synth is dead. synth_release() writes it under speakup_info.spinlock and the I/O error paths write it without the lock; the loops only need to eventually observe the change. On its own this fixes the I/O error case, where the synth is already deactivated when the loop retries. In the no-room case nothing clears synth->alive and speakup_thread() still holds spk_mutex across the loop; the follow-up patch completes the fix by clearing synth->alive from synth_release() and by dropping spk_mutex from the catch_up() call. Fixes: 2067fd92d75b6 ("staging/speakup: Move out of staging") Cc: stable@vger.kernel.org Cc: Samuel Thibault Reviewed-by: Samuel Thibault Signed-off-by: Hongyu Xie --- drivers/accessibility/speakup/speakup_acntpc.c | 4 ++++ drivers/accessibility/speakup/speakup_apollo.c | 4 ++++ drivers/accessibility/speakup/speakup_decext.c | 4 ++++ drivers/accessibility/speakup/speakup_decpc.c | 4 ++++ drivers/accessibility/speakup/speakup_dectlk.c | 4 ++++ drivers/accessibility/speakup/speakup_dtlk.c | 4 ++++ drivers/accessibility/speakup/speakup_keypc.c | 4 ++++ drivers/accessibility/speakup/synth.c | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c index a27e6bbf05da..fe19ba3f0005 100644 --- a/drivers/accessibility/speakup/speakup_acntpc.c +++ b/drivers/accessibility/speakup/speakup_acntpc.c @@ -192,6 +192,10 @@ static void do_catch_up(struct spk_synth *synth) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_apollo.c b/drivers/accessibility/speakup/speakup_apollo.c index d2fbb3f57221..b62696e050fc 100644 --- a/drivers/accessibility/speakup/speakup_apollo.c +++ b/drivers/accessibility/speakup/speakup_apollo.c @@ -157,6 +157,10 @@ static void do_catch_up(struct spk_synth *synth) while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } jiffy_delta_val = jiffy_delta->u.n.value; full_time_val = full_time->u.n.value; delay_time_val = delay_time->u.n.value; diff --git a/drivers/accessibility/speakup/speakup_decext.c b/drivers/accessibility/speakup/speakup_decext.c index 271bcf279bf9..e6be5a99670b 100644 --- a/drivers/accessibility/speakup/speakup_decext.c +++ b/drivers/accessibility/speakup/speakup_decext.c @@ -171,6 +171,10 @@ static void do_catch_up(struct spk_synth *synth) while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c index 1bf36d1e5477..cfafd9475963 100644 --- a/drivers/accessibility/speakup/speakup_decpc.c +++ b/drivers/accessibility/speakup/speakup_decpc.c @@ -391,6 +391,10 @@ static void do_catch_up(struct spk_synth *synth) while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_dectlk.c b/drivers/accessibility/speakup/speakup_dectlk.c index 56334405d865..d9247c53d327 100644 --- a/drivers/accessibility/speakup/speakup_dectlk.c +++ b/drivers/accessibility/speakup/speakup_dectlk.c @@ -246,6 +246,10 @@ static void do_catch_up(struct spk_synth *synth) spin_unlock_irqrestore(&flush_lock, flags); spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c index fa826568937b..73cd39e781b8 100644 --- a/drivers/accessibility/speakup/speakup_dtlk.c +++ b/drivers/accessibility/speakup/speakup_dtlk.c @@ -206,6 +206,10 @@ static void do_catch_up(struct spk_synth *synth) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c index 9356f6379560..e229009107f7 100644 --- a/drivers/accessibility/speakup/speakup_keypc.c +++ b/drivers/accessibility/speakup/speakup_keypc.c @@ -193,6 +193,10 @@ static void do_catch_up(struct spk_synth *synth) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index 11cba1fd8715..6a49ff475c16 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -75,6 +75,10 @@ static void _spk_do_catch_up(struct spk_synth *synth, int unicode) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); -- 2.32.0