]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
serial: 8250: Fix fifo underflow on flush
authorJohn Keeping <jkeeping@inmusicbrands.com>
Sat, 8 Feb 2025 12:41:44 +0000 (12:41 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 13 Mar 2025 11:47:19 +0000 (12:47 +0100)
commit 9e512eaaf8f4008c44ede3dfc0fbc9d9c5118583 upstream.

When flushing the serial port's buffer, uart_flush_buffer() calls
kfifo_reset() but if there is an outstanding DMA transfer then the
completion function will consume data from the kfifo via
uart_xmit_advance(), underflowing and leading to ongoing DMA as the
driver tries to transmit another 2^32 bytes.

This is readily reproduced with serial-generic and amidi sending even
short messages as closing the device on exit will wait for the fifo to
drain and in the underflow case amidi hangs for 30 seconds on exit in
tty_wait_until_sent().  A trace of that gives:

     kworker/1:1-84    [001]    51.769423: bprint:               serial8250_tx_dma: tx_size=3 fifo_len=3
           amidi-763   [001]    51.769460: bprint:               uart_flush_buffer: resetting fifo
 irq/21-fe530000-76    [000]    51.769474: bprint:               __dma_tx_complete: tx_size=3
 irq/21-fe530000-76    [000]    51.769479: bprint:               serial8250_tx_dma: tx_size=4096 fifo_len=4294967293
 irq/21-fe530000-76    [000]    51.781295: bprint:               __dma_tx_complete: tx_size=4096
 irq/21-fe530000-76    [000]    51.781301: bprint:               serial8250_tx_dma: tx_size=4096 fifo_len=4294963197
 irq/21-fe530000-76    [000]    51.793131: bprint:               __dma_tx_complete: tx_size=4096
 irq/21-fe530000-76    [000]    51.793135: bprint:               serial8250_tx_dma: tx_size=4096 fifo_len=4294959101
 irq/21-fe530000-76    [000]    51.804949: bprint:               __dma_tx_complete: tx_size=4096

Since the port lock is held in when the kfifo is reset in
uart_flush_buffer() and in __dma_tx_complete(), adding a flush_buffer
hook to adjust the outstanding DMA byte count is sufficient to avoid the
kfifo underflow.

Fixes: 9ee4b83e51f74 ("serial: 8250: Add support for dmaengine")
Cc: stable <stable@kernel.org>
Signed-off-by: John Keeping <jkeeping@inmusicbrands.com>
Link: https://lore.kernel.org/r/20250208124148.1189191-1-jkeeping@inmusicbrands.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/8250/8250.h
drivers/tty/serial/8250/8250_dma.c
drivers/tty/serial/8250/8250_port.c

index 61b11490ae5be2d6102964f83b2367efd54a97e3..5f3368bb6a03c80ae077e88c349b2b942b4db0b5 100644 (file)
@@ -325,6 +325,7 @@ static inline int is_omap1510_8250(struct uart_8250_port *pt)
 
 #ifdef CONFIG_SERIAL_8250_DMA
 extern int serial8250_tx_dma(struct uart_8250_port *);
+extern void serial8250_tx_dma_flush(struct uart_8250_port *);
 extern int serial8250_rx_dma(struct uart_8250_port *);
 extern void serial8250_rx_dma_flush(struct uart_8250_port *);
 extern int serial8250_request_dma(struct uart_8250_port *);
@@ -341,6 +342,7 @@ static inline int serial8250_tx_dma(struct uart_8250_port *p)
 {
        return -1;
 }
+static inline void serial8250_tx_dma_flush(struct uart_8250_port *p) { }
 static inline int serial8250_rx_dma(struct uart_8250_port *p)
 {
        return -1;
index 33ce4b218d9efdefbb6b130bcaa70d8ebaf9142a..cbaac0a5137d7005124e5f96b0ea6ba629e49258 100644 (file)
@@ -135,6 +135,22 @@ err:
        return ret;
 }
 
+void serial8250_tx_dma_flush(struct uart_8250_port *p)
+{
+       struct uart_8250_dma *dma = p->dma;
+
+       if (!dma->tx_running)
+               return;
+
+       /*
+        * kfifo_reset() has been called by the serial core, avoid
+        * advancing and underflowing in __dma_tx_complete().
+        */
+       dma->tx_size = 0;
+
+       dmaengine_terminate_async(dma->rxchan);
+}
+
 int serial8250_rx_dma(struct uart_8250_port *p)
 {
        struct uart_8250_dma            *dma = p->dma;
index 6098e87a34046453f5277ec4e7a99cd8781676c8..0042ac7e713b729d648b0e6f36a85addc6825764 100644 (file)
@@ -2509,6 +2509,14 @@ static unsigned int npcm_get_divisor(struct uart_8250_port *up,
        return DIV_ROUND_CLOSEST(port->uartclk, 16 * baud + 2) - 2;
 }
 
+static void serial8250_flush_buffer(struct uart_port *port)
+{
+       struct uart_8250_port *up = up_to_u8250p(port);
+
+       if (up->dma)
+               serial8250_tx_dma_flush(up);
+}
+
 static unsigned int serial8250_do_get_divisor(struct uart_port *port,
                                              unsigned int baud,
                                              unsigned int *frac)
@@ -3209,6 +3217,7 @@ static const struct uart_ops serial8250_pops = {
        .break_ctl      = serial8250_break_ctl,
        .startup        = serial8250_startup,
        .shutdown       = serial8250_shutdown,
+       .flush_buffer   = serial8250_flush_buffer,
        .set_termios    = serial8250_set_termios,
        .set_ldisc      = serial8250_set_ldisc,
        .pm             = serial8250_pm,