disable_irq_nosync() masks the UART interrupt without waiting for a
running handler. That handler can have observed up->dma and continue
using it while shutdown releases the DMA channels and clears the pointer.
Synchronizing the IRQ later in serial core does not protect those accesses.
Publish the DMA shutdown gate under the port lock before the final RX
flush and interrupt masking. Have DMA IRQ handling check the same gate
under the lock, so a queued interrupt cannot restart receive processing
after the final flush.
Drain the UART IRQ outside the port lock before releasing DMA state.
The lock excludes competing RX processing during the flush, while
disable_irq() also waits for a handler that observed up->dma before
acquiring the lock.
Clear EFR2 after draining the IRQ so an in-flight handler cannot restore
TIMEOUT_BEHAVE after shutdown has cleared it. This also waits for the PIO
timeout workaround, which updates EFR2 outside the port lock.
Fixes: fef4f600319e ("serial: 8250: omap: Fix life cycle issues for interrupt
handlers")
Closes: https://lore.kernel.org/r/[email protected]/
Reviewed-by: Raul E Rangel <[email protected]>
Signed-off-by: James Hilliard <[email protected]>
---
drivers/tty/serial/8250/8250_omap.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_omap.c
b/drivers/tty/serial/8250/8250_omap.c
index 5e114dd8f9bf..f1980c15f85a 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -771,24 +771,24 @@ static void omap_8250_shutdown(struct uart_port *port)
guard(serial8250_rpm)(up);
flush_work(&priv->qos_work);
- if (up->dma) {
- guard(uart_port_lock_irq)(port);
- omap_8250_rx_dma_flush(up);
- }
serial_out(up, UART_OMAP_WER, 0);
- if (priv->habit & UART_HAS_EFR2)
- serial_out(up, UART_OMAP_EFR2, 0x0);
/* Synchronize UART_IER access against the console. */
scoped_guard(uart_port_lock_irq, port) {
- if (up->dma)
+ if (up->dma) {
up->dma->shutting_down = true;
+ omap_8250_rx_dma_flush(up);
+ }
up->ier = 0;
serial_out(up, UART_IER, 0);
}
- disable_irq_nosync(port->irq);
+ /* An in-flight handler may still be using up->dma. */
+ disable_irq(port->irq);
+
+ if (priv->habit & UART_HAS_EFR2)
+ serial_out(up, UART_OMAP_EFR2, 0x0);
serial8250_release_dma(up);
up->dma = NULL;
@@ -1330,6 +1330,11 @@ static int omap_8250_dma_handle_irq(struct uart_port
*port)
uart_port_lock(port);
+ if (up->dma->shutting_down) {
+ uart_port_unlock(port);
+ return IRQ_HANDLED;
+ }
+
status = serial_port_in(port, UART_LSR);
if ((iir & 0x3f) != UART_IIR_THRI) {
--
2.53.0