Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

serial: stm32: fix threaded interrupt handling

When DMA is enabled the receive handler runs in a threaded handler, but
the primary handler up until very recently neither disabled interrupts
in the device or used IRQF_ONESHOT. This would lead to a deadlock if an
interrupt comes in while the threaded receive handler is running under
the port lock.

Commit ad7676812437 ("serial: stm32: fix a deadlock condition with
wakeup event") claimed to fix an unrelated deadlock, but unfortunately
also disabled interrupts in the threaded handler. While this prevents
the deadlock mentioned in the previous paragraph it also defeats the
purpose of using a threaded handler in the first place.

Fix this by making the interrupt one-shot and not disabling interrupts
in the threaded handler.

Note that (receive) DMA must not be used for a console port as the
threaded handler could be interrupted while holding the port lock,
something which could lead to a deadlock in case an interrupt handler
ends up calling printk.

Fixes: ad7676812437 ("serial: stm32: fix a deadlock condition with wakeup event")
Fixes: 3489187204eb ("serial: stm32: adding dma support")
Cc: stable@vger.kernel.org # 4.9
Cc: Alexandre TORGUE <alexandre.torgue@st.com>
Cc: Gerald Baeza <gerald.baeza@st.com>
Reviewed-by: Valentin Caron<valentin.caron@foss.st.com>
Signed-off-by: Johan Hovold <johan@kernel.org>
Link: https://lore.kernel.org/r/20210416140557.25177-3-johan@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Johan Hovold and committed by
Greg Kroah-Hartman
e359b441 75f4e830

+12 -10
+12 -10
drivers/tty/serial/stm32-usart.c
··· 214 214 struct tty_port *tport = &port->state->port; 215 215 struct stm32_port *stm32_port = to_stm32_port(port); 216 216 const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; 217 - unsigned long c, flags; 217 + unsigned long c; 218 218 u32 sr; 219 219 char flag; 220 220 221 - if (threaded) 222 - spin_lock_irqsave(&port->lock, flags); 223 - else 224 - spin_lock(&port->lock); 221 + spin_lock(&port->lock); 225 222 226 223 while (stm32_usart_pending_rx(port, &sr, &stm32_port->last_res, 227 224 threaded)) { ··· 275 278 uart_insert_char(port, sr, USART_SR_ORE, c, flag); 276 279 } 277 280 278 - if (threaded) 279 - spin_unlock_irqrestore(&port->lock, flags); 280 - else 281 - spin_unlock(&port->lock); 281 + spin_unlock(&port->lock); 282 282 283 283 tty_flip_buffer_push(tport); 284 284 } ··· 661 667 662 668 ret = request_threaded_irq(port->irq, stm32_usart_interrupt, 663 669 stm32_usart_threaded_interrupt, 664 - IRQF_NO_SUSPEND, name, port); 670 + IRQF_ONESHOT | IRQF_NO_SUSPEND, 671 + name, port); 665 672 if (ret) 666 673 return ret; 667 674 ··· 1150 1155 struct dma_slave_config config; 1151 1156 struct dma_async_tx_descriptor *desc = NULL; 1152 1157 int ret; 1158 + 1159 + /* 1160 + * Using DMA and threaded handler for the console could lead to 1161 + * deadlocks. 1162 + */ 1163 + if (uart_console(port)) 1164 + return -ENODEV; 1153 1165 1154 1166 /* Request DMA RX channel */ 1155 1167 stm32port->rx_ch = dma_request_slave_channel(dev, "rx");