[PATCH 1/5] tty: serial: samsung: fix TX kfifo underflow on flush
Samarth Narang
samarth.colleges at gmail.com
Sat Oct 31 05:39:07 PDT 2026
uart_flush_buffer() resets the transmit kfifo under the port lock before
calling the driver's flush_buffer() hook. Since samsung_tty does not
implement this hook, an outstanding TX DMA transfer may complete after
the flush and call uart_xmit_advance() using the old transfer's byte
count. This can advance the kfifo tail past its head, causing
kfifo_len() to wrap and the driver to transmit stale buffer contents.
The same issue was addressed in 8250 by commit 9e512eaaf8f4 ("serial:
8250: Fix fifo underflow on flush").
Add a flush_buffer() hook that terminates the outstanding TX DMA
transfer and clears tx_in_progress. Since the TX buffer remains mapped
while the port is open, DMA synchronization is not required before
returning from the hook.
A completion that is already running when the flush occurs may have
calculated its byte count before taking the port lock, so it must skip
the kfifo update if the transfer has since been retired by either
flush_buffer() or stop_tx(), which already advances the kfifo itself.
Fixes: 29bef7990845 ("serial: samsung: add DMA support for TX")
Cc: stable at vger.kernel.org
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges at gmail.com>
---
drivers/tty/serial/samsung_tty.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 640f85e2d636..2bea7ef078d2 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -317,6 +317,20 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port)
ourport->tx_mode = 0;
}
+static void s3c24xx_serial_flush_buffer(struct uart_port *port)
+{
+ struct s3c24xx_uart_port *ourport = to_ourport(port);
+ struct s3c24xx_uart_dma *dma = ourport->dma;
+
+ if (!dma || !dma->tx_chan || ourport->tx_in_progress != S3C24XX_TX_DMA)
+ return;
+
+ /* The kfifo has been reset: the completion must not advance it */
+ dmaengine_terminate_async(dma->tx_chan);
+ async_tx_ack(dma->tx_desc);
+ ourport->tx_in_progress = 0;
+}
+
static void s3c24xx_serial_start_next_tx(struct s3c24xx_uart_port *ourport);
static void s3c24xx_serial_tx_dma_complete(void *args)
@@ -339,6 +353,12 @@ static void s3c24xx_serial_tx_dma_complete(void *args)
uart_port_lock_irqsave(port, &flags);
+ /* Already retired by stop_tx or flush_buffer */
+ if (ourport->tx_in_progress != S3C24XX_TX_DMA) {
+ uart_port_unlock_irqrestore(port, flags);
+ return;
+ }
+
uart_xmit_advance(port, count);
ourport->tx_in_progress = 0;
@@ -1682,6 +1702,7 @@ static const struct uart_ops s3c64xx_serial_ops = {
.get_mctrl = s3c24xx_serial_get_mctrl,
.set_mctrl = s3c24xx_serial_set_mctrl,
.stop_tx = s3c24xx_serial_stop_tx,
+ .flush_buffer = s3c24xx_serial_flush_buffer,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
.break_ctl = s3c24xx_serial_break_ctl,
@@ -1703,6 +1724,7 @@ static const struct uart_ops apple_s5l_serial_ops = {
.get_mctrl = s3c24xx_serial_get_mctrl,
.set_mctrl = s3c24xx_serial_set_mctrl,
.stop_tx = s3c24xx_serial_stop_tx,
+ .flush_buffer = s3c24xx_serial_flush_buffer,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
.break_ctl = s3c24xx_serial_break_ctl,
--
2.34.1
More information about the linux-arm-kernel
mailing list