]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
serial: 8250: Fix TX deadlock when using DMA
authorRaul E Rangel <rrangel@chromium.org>
Mon, 9 Feb 2026 20:58:18 +0000 (13:58 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Mar 2026 14:29:59 +0000 (15:29 +0100)
`dmaengine_terminate_async` does not guarantee that the
`__dma_tx_complete` callback will run. The callback is currently the
only place where `dma->tx_running` gets cleared. If the transaction is
canceled and the callback never runs, then `dma->tx_running` will never
get cleared and we will never schedule new TX DMA transactions again.

This change makes it so we clear `dma->tx_running` after we terminate
the DMA transaction. This is "safe" because `serial8250_tx_dma_flush`
is holding the UART port lock. The first thing the callback does is also
grab the UART port lock, so access to `dma->tx_running` is serialized.

Fixes: 9e512eaaf8f4 ("serial: 8250: Fix fifo underflow on flush")
Cc: stable <stable@kernel.org>
Signed-off-by: Raul E Rangel <rrangel@google.com>
Link: https://patch.msgid.link/20260209135815.1.I16366ecb0f62f3c96fe3dd5763fcf6f3c2b4d8cd@changeid
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/8250/8250_dma.c

index bdd26c9f34bdf236028bf1388d002951d4b16ec7..3b6452e759d5b5181776c100bd804dabe017c06c 100644 (file)
@@ -162,7 +162,22 @@ void serial8250_tx_dma_flush(struct uart_8250_port *p)
         */
        dma->tx_size = 0;
 
+       /*
+        * We can't use `dmaengine_terminate_sync` because `uart_flush_buffer` is
+        * holding the uart port spinlock.
+        */
        dmaengine_terminate_async(dma->txchan);
+
+       /*
+        * The callback might or might not run. If it doesn't run, we need to ensure
+        * that `tx_running` is cleared so that we can schedule new transactions.
+        * If it does run, then the zombie callback will clear `tx_running` again
+        * and perform a no-op since `tx_size` was cleared above.
+        *
+        * In either case, we ASSUME the DMA transaction will terminate before we
+        * issue a new `serial8250_tx_dma`.
+        */
+       dma->tx_running = 0;
 }
 
 int serial8250_rx_dma(struct uart_8250_port *p)