* [BUG] serial: TX kfifo underflow when TCOFLUSH races with TX DMA (samsung, sprd, msm, pch_uart, tegra)
@ 2026-10-31 0:47 Samarth Narang
2026-10-31 8:54 ` Greg Kroah-Hartman
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
0 siblings, 2 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 0:47 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Orson Zhai, Baolin Wang, Chunyan Zhang,
Laxman Dewangan, Thierry Reding, Jonathan Hunter, linux-serial,
linux-samsung-soc, linux-arm-msm, linux-tegra, linux-arm-kernel,
linux-kernel
Hi,
The TX FIFO underflow previously fixed in 8250 by 9e512eaaf8f4 ("serial:
8250: Fix fifo underflow on flush") and in qcom-geni by 452d6fa37ae9,
7ea38c49e717, and e3c04834ae1a appears to affect five other serial
drivers. The line numbers below refer to mainline at 83f32c773255.
uart_flush_buffer() resets the transmit kfifo under the port lock before
calling the driver's flush_buffer() callback. If a TX DMA transfer is
still active and the driver does not account for it during the flush,
its completion callback may subsequently call uart_xmit_advance() using
the byte count from the old transfer. This can advance kfifo->out past
kfifo->in, causing kfifo_len() to wrap and the driver to transmit stale
buffer contents. As shown in the 8250 fix, a subsequent close can then
wait in tty_wait_until_sent() until it times out. The issue may arise
when a large write is followed by TCFLSH(TCOFLUSH), or when the port is
closed while TX DMA is active.
Four drivers have no flush_buffer() callback and therefore do not stop
or account for outstanding TX DMA when the kfifo is reset:
1. samsung_tty: Neither s3c64xx_serial_ops (line 1679) nor
apple_s5l_serial_ops (line 1700) implements flush_buffer(), while
s3c24xx_serial_tx_dma_complete() advances the kfifo by
tx_bytes_requested - residue (line 342) without checking whether a
flush occurred.
2. sprd_serial: serial_sprd_ops (line 944) has no flush_buffer()
callback, and sprd_complete_tx_dma() advances the kfifo by
tx_dma.trans_len (line 276).
3. msm_serial: msm_uart_pops (line 1547) does not implement
flush_buffer(), while msm_complete_tx_dma() advances by
sg_dma_len(&dma->tx_sg) - residue (line 482). Its check for an
already stopped transfer relies on tx_sg, which is not cleared by a
flush.
4. pch_uart: pch_uart_ops (line 1518) has no flush_buffer() callback,
and pch_dma_tx_complete() advances the kfifo using each scatterlist
entry's sg_dma_len() (line 756) without holding the port lock.
The fifth driver, serial-tegra, implements tegra_uart_flush_buffer()
(lines 1272-1279), but only clears tx_bytes and calls
dmaengine_terminate_all(). Its completion callback calculates the
transferred byte count from tx_bytes_requested - residue (line 537)
before acquiring the port lock and advances the kfifo afterward (line
540), while tx_bytes_requested is left unchanged by the flush. Although
terminated descriptors do not invoke their callbacks, a completion
already running when the flush occurs, particularly one waiting to
acquire the port lock held by uart_flush_buffer(), may still advance the
reset kfifo. This is the same race addressed by 452d6fa37ae9.
The existing 8250 and qcom-geni fixes provide possible approaches, with
the former clearing the outstanding DMA byte count under the port lock
and the latter marking completions as stale during a flush. As discussed
in 7ea38c49e717, comparing the completion count against kfifo_len()
alone is insufficient.
These findings came from a research static analyzer that checks whether
uart_ops implementations using TX DMA provide flush handling that
accounts for outstanding transfers, followed by manual inspection of the
reported paths. None has been reproduced on hardware. The same analysis
found flush handling in the 8250 core, atmel, fsl_lpuart, imx, sh-sci,
and qcom-geni, while amba-pl011 is not affected because it advances the
kfifo when preparing a transfer rather than on completion.
I would be happy to prepare separate patches for the affected drivers if
the maintainers agree with the analysis.
Thanks,
Samarth
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [BUG] serial: TX kfifo underflow when TCOFLUSH races with TX DMA (samsung, sprd, msm, pch_uart, tegra)
2026-10-31 0:47 [BUG] serial: TX kfifo underflow when TCOFLUSH races with TX DMA (samsung, sprd, msm, pch_uart, tegra) Samarth Narang
@ 2026-10-31 8:54 ` Greg Kroah-Hartman
2026-10-31 15:26 ` [PATCH 0/1] tty: serial: samsung: fix TX fifo underflow on flush race tianci xie
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
1 sibling, 1 reply; 11+ messages in thread
From: Greg Kroah-Hartman @ 2026-10-31 8:54 UTC (permalink / raw)
To: Samarth Narang
Cc: Jiri Slaby, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Orson Zhai, Baolin Wang, Chunyan Zhang,
Laxman Dewangan, Thierry Reding, Jonathan Hunter, linux-serial,
linux-samsung-soc, linux-arm-msm, linux-tegra, linux-arm-kernel,
linux-kernel
On Sat, Oct 31, 2026 at 12:47:33AM +0000, Samarth Narang wrote:
> I would be happy to prepare separate patches for the affected drivers if
> the maintainers agree with the analysis.
Always just send us patches, after testing them, you never need to ask
if you should do so.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers
2026-10-31 0:47 [BUG] serial: TX kfifo underflow when TCOFLUSH races with TX DMA (samsung, sprd, msm, pch_uart, tegra) Samarth Narang
2026-10-31 8:54 ` Greg Kroah-Hartman
@ 2026-10-31 12:39 ` Samarth Narang
2026-10-31 12:39 ` [PATCH 1/5] tty: serial: samsung: fix TX kfifo underflow on flush Samarth Narang
` (5 more replies)
1 sibling, 6 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
This series addresses the TX kfifo underflow reported in the parent
thread, where uart_flush_buffer() resets the transmit kfifo but a
completion callback from an earlier DMA transfer may still call
uart_xmit_advance() using the byte count from before the flush.
The fixes take two approaches depending on how each driver manages its
TX DMA mappings. In samsung and tegra, which map the TX buffer once,
samsung's new flush_buffer() terminates the outstanding transfer, as
tegra's already does, following the approach introduced in 8250 by
9e512eaaf8f4. A completion already in progress skips the kfifo update if
the transfer has since been retired by flush_buffer() or stop_tx().
In sprd, msm, and pch_uart, which map each transfer separately,
terminating DMA there would leave the flush hook to unmap a buffer the
controller may still be reading. These drivers therefore leave the
transfer running and mark it as flushed, allowing the completion
callback to unmap the buffer without advancing the kfifo. This means
that the data already queued for DMA before the flush may still be
transmitted.
All five patches have only been compile-tested with arm64 defconfig and
the relevant drivers enabled, including pch_uart through COMPILE_TEST,
using W=1 with no new warnings. I do not have access to the affected
hardware, so testing by someone with suitable devices would be
appreciated.
One race remains, which is also present in the 8250 fix: a completion
may already be running when the flush occurs but acquire the port lock
only after a new transfer has started, potentially mistaking the new
transfer's state for its own. Preventing this would require tracking
individual transfers, for example with a generation counter, which I
have left out of this series to keep the changes focused.
Samarth Narang (5):
tty: serial: samsung: fix TX kfifo underflow on flush
serial: tegra: don't advance the TX kfifo for a flushed transfer
serial: sprd: fix TX kfifo underflow on flush
tty: serial: msm: fix TX kfifo underflow on flush
serial: pch_uart: fix TX kfifo underflow on flush
drivers/tty/serial/msm_serial.c | 19 ++++++++++++++++++-
drivers/tty/serial/pch_uart.c | 27 +++++++++++++++++++++++++--
drivers/tty/serial/samsung_tty.c | 22 ++++++++++++++++++++++
drivers/tty/serial/serial-tegra.c | 8 ++++++++
drivers/tty/serial/sprd_serial.c | 24 ++++++++++++++++++++++--
5 files changed, 95 insertions(+), 5 deletions(-)
base-commit: 36aad21d50d1af3f6954bb0c988257a908202477
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/5] tty: serial: samsung: fix TX kfifo underflow on flush
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
@ 2026-10-31 12:39 ` Samarth Narang
2026-10-31 12:39 ` [PATCH 2/5] serial: tegra: don't advance the TX kfifo for a flushed transfer Samarth Narang
` (4 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
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@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges@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
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/5] serial: tegra: don't advance the TX kfifo for a flushed transfer
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
2026-10-31 12:39 ` [PATCH 1/5] tty: serial: samsung: fix TX kfifo underflow on flush Samarth Narang
@ 2026-10-31 12:39 ` Samarth Narang
2026-10-31 12:39 ` [PATCH 3/5] serial: sprd: fix TX kfifo underflow on flush Samarth Narang
` (3 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
tegra_uart_flush_buffer() terminates the TX DMA transfer after the
serial core has reset the transmit kfifo, but leaves tx_in_progress set.
A completion callback that was already running may have calculated its
byte count from tx_bytes_requested before acquiring the port lock,
allowing it to call uart_xmit_advance() on the reset kfifo after the
flush. This can advance the kfifo tail past its head, causing
kfifo_len() to wrap and the driver to transmit stale buffer contents.
The same race was fixed in qcom-geni by commit 452d6fa37ae9 ("serial:
qcom_geni: fix kfifo underflow when flush precedes DMA completion IRQ").
Clear tx_in_progress in the flush hook and have the completion callback
skip the kfifo update if the transfer has already been retired by either
the flush or tegra_uart_stop_tx(), which also advances the kfifo itself.
Fixes: e9ea096dd225 ("serial: tegra: add serial driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges@gmail.com>
---
drivers/tty/serial/serial-tegra.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/tty/serial/serial-tegra.c b/drivers/tty/serial/serial-tegra.c
index b0fc4ae3686f..8b712a9bc293 100644
--- a/drivers/tty/serial/serial-tegra.c
+++ b/drivers/tty/serial/serial-tegra.c
@@ -537,6 +537,11 @@ static void tegra_uart_tx_dma_complete(void *args)
count = tup->tx_bytes_requested - state.residue;
async_tx_ack(tup->tx_dma_desc);
uart_port_lock_irqsave(&tup->uport, &flags);
+ /* Already retired by stop_tx or flush_buffer */
+ if (tup->tx_in_progress != TEGRA_UART_TX_DMA) {
+ uart_port_unlock_irqrestore(&tup->uport, flags);
+ return;
+ }
uart_xmit_advance(&tup->uport, count);
tup->tx_in_progress = 0;
if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
@@ -1276,6 +1281,9 @@ static void tegra_uart_flush_buffer(struct uart_port *u)
tup->tx_bytes = 0;
if (tup->tx_dma_chan)
dmaengine_terminate_all(tup->tx_dma_chan);
+ /* The kfifo has been reset: the completion must not advance it */
+ if (tup->tx_in_progress == TEGRA_UART_TX_DMA)
+ tup->tx_in_progress = 0;
}
static void tegra_uart_shutdown(struct uart_port *u)
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/5] serial: sprd: fix TX kfifo underflow on flush
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
2026-10-31 12:39 ` [PATCH 1/5] tty: serial: samsung: fix TX kfifo underflow on flush Samarth Narang
2026-10-31 12:39 ` [PATCH 2/5] serial: tegra: don't advance the TX kfifo for a flushed transfer Samarth Narang
@ 2026-10-31 12:39 ` Samarth Narang
2026-10-31 12:39 ` [PATCH 4/5] tty: serial: msm: " Samarth Narang
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
uart_flush_buffer() resets the transmit kfifo under the port lock before
calling the driver's flush_buffer() hook. Since sprd_serial does not
implement this hook, an outstanding TX DMA transfer may complete after
the flush and call uart_xmit_advance() using the flushed transfer's
tx_dma.trans_len. 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 fixed in 8250 by commit 9e512eaaf8f4 ("serial: 8250:
Fix fifo underflow on flush").
Since the TX buffer is mapped separately for each transfer and the flush
hook runs under the port lock, DMA cannot be terminated and its mapping
safely released synchronously from the hook. Instead, leave the
outstanding transfer running and mark it as flushed, allowing the
completion callback to unmap the buffer without advancing the kfifo.
Apply the same check in sprd_stop_tx_dma() if the transfer is stopped
before completion. With this approach, the data already queued for DMA
before the flush may still be transmitted.
Fixes: f4487db58eb7 ("serial: sprd: Add DMA mode support")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges@gmail.com>
---
drivers/tty/serial/sprd_serial.c | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c
index 3fdf5939a62c..718732b683f1 100644
--- a/drivers/tty/serial/sprd_serial.c
+++ b/drivers/tty/serial/sprd_serial.c
@@ -133,6 +133,7 @@ struct sprd_uart_port {
struct clk *clk;
struct sprd_uart_dma tx_dma;
struct sprd_uart_dma rx_dma;
+ bool tx_flushed;
dma_addr_t pos;
unsigned char *rx_buf_tail;
const struct sprd_uart_data *pdata;
@@ -236,13 +237,15 @@ static void sprd_stop_tx_dma(struct uart_port *port)
dmaengine_tx_status(sp->tx_dma.chn, sp->tx_dma.cookie, &state);
if (state.residue) {
trans_len = state.residue - sp->tx_dma.phys_addr;
- uart_xmit_advance(port, trans_len);
+ if (!sp->tx_flushed)
+ uart_xmit_advance(port, trans_len);
dma_unmap_single(port->dev, sp->tx_dma.phys_addr,
sp->tx_dma.trans_len, DMA_TO_DEVICE);
}
dmaengine_terminate_all(sp->tx_dma.chn);
sp->tx_dma.trans_len = 0;
+ sp->tx_flushed = false;
}
static int sprd_tx_buf_remap(struct uart_port *port)
@@ -273,7 +276,9 @@ static void sprd_complete_tx_dma(void *data)
dma_unmap_single(port->dev, sp->tx_dma.phys_addr,
sp->tx_dma.trans_len, DMA_TO_DEVICE);
- uart_xmit_advance(port, sp->tx_dma.trans_len);
+ if (!sp->tx_flushed)
+ uart_xmit_advance(port, sp->tx_dma.trans_len);
+ sp->tx_flushed = false;
if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
uart_write_wakeup(port);
@@ -555,6 +560,20 @@ static void sprd_stop_tx(struct uart_port *port)
serial_out(port, SPRD_ICLR, iclr);
}
+static void sprd_flush_buffer(struct uart_port *port)
+{
+ struct sprd_uart_port *sp = container_of(port, struct sprd_uart_port,
+ port);
+
+ /*
+ * The kfifo has been reset. Let the running transfer finish, so that
+ * its buffer is unmapped only once it has stopped, but do not advance
+ * the kfifo for it.
+ */
+ if (sp->tx_dma.trans_len)
+ sp->tx_flushed = true;
+}
+
static void sprd_start_tx(struct uart_port *port)
{
struct sprd_uart_port *sp = container_of(port, struct sprd_uart_port,
@@ -946,6 +965,7 @@ static const struct uart_ops serial_sprd_ops = {
.get_mctrl = sprd_get_mctrl,
.set_mctrl = sprd_set_mctrl,
.stop_tx = sprd_stop_tx,
+ .flush_buffer = sprd_flush_buffer,
.start_tx = sprd_start_tx,
.stop_rx = sprd_stop_rx,
.break_ctl = sprd_break_ctl,
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 4/5] tty: serial: msm: fix TX kfifo underflow on flush
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
` (2 preceding siblings ...)
2026-10-31 12:39 ` [PATCH 3/5] serial: sprd: fix TX kfifo underflow on flush Samarth Narang
@ 2026-10-31 12:39 ` Samarth Narang
2026-10-31 12:39 ` [PATCH 5/5] serial: pch_uart: " Samarth Narang
2026-10-31 12:59 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Greg Kroah-Hartman
5 siblings, 0 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
uart_flush_buffer() resets the transmit kfifo under the port lock before
calling the driver's flush_buffer() hook. Since msm_serial does not
implement this hook, an outstanding TX DMA transfer may complete after
the flush and call uart_xmit_advance() using the flushed transfer's byte
count. The existing check for a stopped transfer relies on tx_sg, which
the flush does not clear, so it does not catch this case. The kfifo tail
then advances past its head, causing kfifo_len() to wrap and the driver
to transmit stale buffer contents. The same issue was fixed in 8250 by
commit 9e512eaaf8f4 ("serial: 8250: Fix fifo underflow on flush").
Since the TX buffer is mapped separately for each transfer and the flush
hook runs under the port lock, the driver cannot synchronously terminate
DMA and safely unmap the buffer from that context. Instead, leave the
outstanding transfer running and mark it as flushed, allowing the
completion callback to unmap the buffer without advancing the kfifo.
Clear the flag when preparing the next transfer. This avoids the kfifo
underflow, although the data already queued for DMA before the flush may
still be transmitted.
Fixes: 3a878c430fd6 ("tty: serial: msm: Add TX DMA support")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges@gmail.com>
---
drivers/tty/serial/msm_serial.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c
index bfa44b01c3e9..a9eca971e9ff 100644
--- a/drivers/tty/serial/msm_serial.c
+++ b/drivers/tty/serial/msm_serial.c
@@ -185,6 +185,7 @@ struct msm_port {
bool break_detected;
struct msm_dma tx_dma;
struct msm_dma rx_dma;
+ bool tx_dma_flushed;
};
static inline struct msm_port *to_msm_port(struct uart_port *up)
@@ -428,6 +429,19 @@ static void msm_stop_tx(struct uart_port *port)
msm_write(port, msm_port->imr, MSM_UART_IMR);
}
+static void msm_flush_buffer(struct uart_port *port)
+{
+ struct msm_port *msm_port = to_msm_port(port);
+
+ /*
+ * The kfifo has been reset. Let the running transfer finish, so that
+ * its buffer is unmapped only once it has stopped, but do not advance
+ * the kfifo for it.
+ */
+ if (sg_dma_len(&msm_port->tx_dma.tx_sg))
+ msm_port->tx_dma_flushed = true;
+}
+
static void msm_start_tx(struct uart_port *port)
{
struct msm_port *msm_port = to_msm_port(port);
@@ -479,7 +493,8 @@ static void msm_complete_tx_dma(void *args)
}
count = sg_dma_len(&dma->tx_sg) - state.residue;
- uart_xmit_advance(port, count);
+ if (!msm_port->tx_dma_flushed)
+ uart_xmit_advance(port, count);
sg_init_table(&dma->tx_sg, 1);
/* Restore "Tx FIFO below watermark" interrupt */
@@ -503,6 +518,7 @@ static int msm_handle_tx_dma(struct msm_port *msm_port, unsigned int count)
int ret;
u32 val;
+ msm_port->tx_dma_flushed = false;
sg_init_table(&dma->tx_sg, 1);
kfifo_dma_out_prepare(&tport->xmit_fifo, &dma->tx_sg, 1, count);
@@ -1549,6 +1565,7 @@ static const struct uart_ops msm_uart_pops = {
.set_mctrl = msm_set_mctrl,
.get_mctrl = msm_get_mctrl,
.stop_tx = msm_stop_tx,
+ .flush_buffer = msm_flush_buffer,
.start_tx = msm_start_tx,
.stop_rx = msm_stop_rx,
.enable_ms = msm_enable_ms,
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 5/5] serial: pch_uart: fix TX kfifo underflow on flush
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
` (3 preceding siblings ...)
2026-10-31 12:39 ` [PATCH 4/5] tty: serial: msm: " Samarth Narang
@ 2026-10-31 12:39 ` Samarth Narang
2026-10-31 12:59 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Greg Kroah-Hartman
5 siblings, 0 replies; 11+ messages in thread
From: Samarth Narang @ 2026-10-31 12:39 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Samarth Narang, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
uart_flush_buffer() resets the transmit kfifo under the port lock before
calling the driver's flush_buffer() hook. Since pch_uart does not
implement this hook, an outstanding TX DMA transfer may complete after
the flush and call uart_xmit_advance() for each entry of the flushed
transfer's scatterlist. This can advance the kfifo tail past its head,
causing kfifo_len() to wrap and the driver to transmit stale buffer
contents. The completion callback also updates the kfifo without holding
the port lock, allowing it to race with the flush itself. The same issue
was fixed in 8250 by commit 9e512eaaf8f4 ("serial: 8250: Fix fifo
underflow on flush").
Since the TX buffer is mapped separately for each transfer and the flush
hook runs under the port lock, the driver cannot synchronously terminate
DMA and safely unmap the buffer from that context. Instead, leave the
outstanding transfer running and mark it as flushed, allowing the
completion callback to unmap the buffer without advancing the kfifo.
Take the port lock in the completion callback to synchronize access to
the flush flag and kfifo state. This prevents the underflow, although
the data already queued for DMA before the flush may still be
transmitted.
Fixes: 3c6a483275f4 ("Serial: EG20T: add PCH_UART driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Samarth Narang <samarth.colleges@gmail.com>
---
drivers/tty/serial/pch_uart.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/pch_uart.c b/drivers/tty/serial/pch_uart.c
index 9505d255f09c..122025c4a0a6 100644
--- a/drivers/tty/serial/pch_uart.c
+++ b/drivers/tty/serial/pch_uart.c
@@ -230,6 +230,7 @@ struct eg20t_port {
struct scatterlist *sg_tx_p;
int nent;
int orig_nent;
+ bool tx_dma_flushed;
struct scatterlist sg_rx;
int tx_dma_use;
void *rx_buf_virt;
@@ -750,18 +751,23 @@ static void pch_dma_tx_complete(void *arg)
struct eg20t_port *priv = arg;
struct uart_port *port = &priv->port;
struct scatterlist *sg = priv->sg_tx_p;
+ unsigned long flags;
int i;
- for (i = 0; i < priv->nent; i++, sg++)
- uart_xmit_advance(port, sg_dma_len(sg));
+ uart_port_lock_irqsave(port, &flags);
+ if (!priv->tx_dma_flushed)
+ for (i = 0; i < priv->nent; i++, sg++)
+ uart_xmit_advance(port, sg_dma_len(sg));
async_tx_ack(priv->desc_tx);
dma_unmap_sg(port->dev, priv->sg_tx_p, priv->orig_nent, DMA_TO_DEVICE);
priv->tx_dma_use = 0;
+ priv->tx_dma_flushed = false;
priv->nent = 0;
priv->orig_nent = 0;
kfree(priv->sg_tx_p);
pch_uart_hal_enable_interrupt(priv, PCH_UART_HAL_TX_INT);
+ uart_port_unlock_irqrestore(port, flags);
}
static int handle_rx_to(struct eg20t_port *priv)
@@ -947,6 +953,7 @@ static unsigned int dma_handle_tx(struct eg20t_port *priv)
}
priv->orig_nent = num;
priv->nent = nent;
+ priv->tx_dma_flushed = false;
for (i = 0; i < nent; i++, sg++) {
sg->offset = tail + fifo_size * i;
@@ -1150,6 +1157,21 @@ static void pch_uart_stop_tx(struct uart_port *port)
priv->tx_dma_use = 0;
}
+static void pch_uart_flush_buffer(struct uart_port *port)
+{
+ struct eg20t_port *priv;
+
+ priv = container_of(port, struct eg20t_port, port);
+
+ /*
+ * The kfifo has been reset. Let the running transfer finish, so that
+ * its buffer is unmapped only once it has stopped, but do not advance
+ * the kfifo for it.
+ */
+ if (priv->nent)
+ priv->tx_dma_flushed = true;
+}
+
static void pch_uart_start_tx(struct uart_port *port)
{
struct eg20t_port *priv;
@@ -1520,6 +1542,7 @@ static const struct uart_ops pch_uart_ops = {
.set_mctrl = pch_uart_set_mctrl,
.get_mctrl = pch_uart_get_mctrl,
.stop_tx = pch_uart_stop_tx,
+ .flush_buffer = pch_uart_flush_buffer,
.start_tx = pch_uart_start_tx,
.stop_rx = pch_uart_stop_rx,
.enable_ms = pch_uart_enable_ms,
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
` (4 preceding siblings ...)
2026-10-31 12:39 ` [PATCH 5/5] serial: pch_uart: " Samarth Narang
@ 2026-10-31 12:59 ` Greg Kroah-Hartman
5 siblings, 0 replies; 11+ messages in thread
From: Greg Kroah-Hartman @ 2026-10-31 12:59 UTC (permalink / raw)
To: Samarth Narang
Cc: Jiri Slaby, Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Laxman Dewangan, Thierry Reding,
Jonathan Hunter, Orson Zhai, Baolin Wang, Chunyan Zhang,
linux-serial, linux-samsung-soc, linux-arm-msm, linux-tegra,
linux-arm-kernel, linux-kernel
On Sat, Oct 31, 2026 at 12:39:06PM +0000, Samarth Narang wrote:
> This series addresses the TX kfifo underflow reported in the parent
> thread, where uart_flush_buffer() resets the transmit kfifo but a
> completion callback from an earlier DMA transfer may still call
> uart_xmit_advance() using the byte count from before the flush.
>
> The fixes take two approaches depending on how each driver manages its
> TX DMA mappings. In samsung and tegra, which map the TX buffer once,
> samsung's new flush_buffer() terminates the outstanding transfer, as
> tegra's already does, following the approach introduced in 8250 by
> 9e512eaaf8f4. A completion already in progress skips the kfifo update if
> the transfer has since been retired by flush_buffer() or stop_tx().
>
> In sprd, msm, and pch_uart, which map each transfer separately,
> terminating DMA there would leave the flush hook to unmap a buffer the
> controller may still be reading. These drivers therefore leave the
> transfer running and mark it as flushed, allowing the completion
> callback to unmap the buffer without advancing the kfifo. This means
> that the data already queued for DMA before the flush may still be
> transmitted.
>
> All five patches have only been compile-tested with arm64 defconfig and
> the relevant drivers enabled, including pch_uart through COMPILE_TEST,
> using W=1 with no new warnings. I do not have access to the affected
> hardware, so testing by someone with suitable devices would be
> appreciated.
If you don't have the hardware, and can't test that the race is even
there, are you sure this is needed? :)
What about using qemu?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 0/1] tty: serial: samsung: fix TX fifo underflow on flush race
2026-10-31 8:54 ` Greg Kroah-Hartman
@ 2026-10-31 15:26 ` tianci xie
2026-10-31 15:26 ` [PATCH] " tianci xie
0 siblings, 1 reply; 11+ messages in thread
From: tianci xie @ 2026-10-31 15:26 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Samarth Narang, linux-serial,
linux-samsung-soc, linux-arm-kernel, linux-kernel, tianci xie
On Sat, Oct 31, 2026 at 09:54:55AM +0100, Greg Kroah-Hartman wrote:
> Always just send us patches, after testing them, you never need to ask
> if you should do so.
Understood. Here is the samsung_tty part of the report; the other four
drivers are separate and I have not touched them.
The analysis holds up against mainline. Neither s3c64xx_serial_ops nor
apple_s5l_serial_ops has a ->flush_buffer() callback, and
s3c24xx_serial_tx_dma_complete() samples tx_bytes_requested before it
takes the port lock. A completion that raced with uart_flush_buffer()
therefore advances the freshly reset kfifo by the byte count of the
discarded transfer, and since kfifo_skip_count() does not clamp, ->out
passes ->in and kfifo_len() wraps.
One detail worth adding to the report. In 29bef7990845, where TX DMA was
introduced, the callback advanced the buffer with
xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1);
so an over-advance was masked away and could not wrap. The wrap only
became possible once the driver moved to the kfifo helpers, which drop
the mask. That is probably why this never showed up on the older code.
The patch follows the 8250 fix in 9e512eaaf8f4, with one addition. The
8250 changelog says that clearing the outstanding byte count under the
port lock is sufficient "since the port lock is held when the kfifo is
reset in uart_flush_buffer() and in __dma_tx_complete()". That holds for
8250 because __dma_tx_complete() reads tx_size inside the lock. Here the
count is read before the lock, so clearing alone would not help; the
patch also moves the sample under the lock. The same gap applies to
serial-tegra as reported.
Testing so far:
- BUILD_RESULT: builds cleanly with gcc 12.3 on x86_64 (COMPILE_TEST,
CONFIG_SERIAL_SAMSUNG=m, W=1) and with aarch64-linux-gcc 13.3.0 on
arm64 (arm64 defconfig, which enables ARCH_EXYNOS, ARCH_APPLE and
SERIAL_SAMSUNG_CONSOLE); no warnings from the new code.
- BOOT_OR_RUNTIME_RESULT: not booted; I have no Exynos or Apple board.
Runtime behaviour is covered by the exhaustive interleaving model.
- the patch applies to mainline at 83f32c773255 with git am;
- a model of the two actors, with the kfifo semantics taken from
include/linux/kfifo.h, exhausts every interleaving under the port
lock: the unpatched callback wraps kfifo_len() in all four flush
scenarios, the patched one in none, and both agree when no flush
happens.
I do not have a Linux machine of my own. The x86_64 build above was run
on a rented Linux server, and the arm64 build was cross-compiled there
with a no-libc aarch64 toolchain; that is as far as I can take testing
without hardware. There is no Exynos or Apple board available to me, so
the model is what stands in for a runtime reproduction. If you would
rather have the flush modelled as a KUnit test in the tree, say so and I
will send that instead.
Thanks,
tianci xie
tianci xie (1):
tty: serial: samsung: fix TX fifo underflow on flush race
drivers/tty/serial/samsung_tty.c | 40 +++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH] tty: serial: samsung: fix TX fifo underflow on flush race
2026-10-31 15:26 ` [PATCH 0/1] tty: serial: samsung: fix TX fifo underflow on flush race tianci xie
@ 2026-10-31 15:26 ` tianci xie
0 siblings, 0 replies; 11+ messages in thread
From: tianci xie @ 2026-10-31 15:26 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: Krzysztof Kozlowski, Peter Griffin, Alim Akhtar,
Marek Szyprowski, Samarth Narang, linux-serial,
linux-samsung-soc, linux-arm-kernel, linux-kernel, tianci xie
uart_flush_buffer() resets the xmit kfifo and then calls ->flush_buffer()
with the port lock held. Neither s3c64xx_serial_ops nor
apple_s5l_serial_ops implements that callback, and
s3c24xx_serial_tx_dma_complete() computes the transferred byte count from
dma->tx_bytes_requested before it takes the port lock.
A completion that has already sampled that count can therefore run after
a TCOFLUSH has reset the kfifo, and advance the freshly reset kfifo by
the byte count of the discarded transfer. kfifo_skip_count() does not
clamp, so ->out moves past ->in and kfifo_len() wraps to a huge value.
The driver then keeps transmitting stale buffer contents, and a later
close can wait in tty_wait_until_sent() until it times out.
Handle this the way 8250 does in 9e512eaaf8f4 ("serial: 8250: Fix fifo
underflow on flush"):
- add s3c24xx_serial_flush_buffer(), which clears the outstanding byte
count and terminates the TX channel asynchronously. The port lock is
held here, so dmaengine_terminate_sync() cannot be used;
- sample dma->tx_bytes_requested under the port lock, so that a
completion racing with the flush observes the cleared value and
advances by zero.
The callback may still run after dmaengine_terminate_async(); for the
fifo it is then a no-op, but it does clear tx_in_progress so that a new
transfer can be started. As in 8250, this assumes that a terminated
descriptor is retired before the next one is queued.
Reported-by: Samarth Narang <samarth.colleges@gmail.com>
Link: https://lore.kernel.org/all/20261031004733.3345841-1-samarth.colleges@gmail.com/
Fixes: 29bef7990845 ("serial: samsung: add DMA support for TX")
Signed-off-by: tianci xie <xiex16070@gmail.com>
---
drivers/tty/serial/samsung_tty.c | 40 +++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 640f85e..e955b7e 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -330,7 +330,6 @@ static void s3c24xx_serial_tx_dma_complete(void *args)
int count;
dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state);
- count = dma->tx_bytes_requested - state.residue;
async_tx_ack(dma->tx_desc);
dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->tx_chan),
@@ -339,6 +338,17 @@ static void s3c24xx_serial_tx_dma_complete(void *args)
uart_port_lock_irqsave(port, &flags);
+ /*
+ * uart_flush_buffer() resets the xmit fifo and then calls
+ * ->flush_buffer(), which clears tx_bytes_requested. Sample the byte
+ * count under the port lock so that a completion which raced with a
+ * flush cannot advance the already reset fifo past its write index.
+ */
+ if (dma->tx_bytes_requested)
+ count = dma->tx_bytes_requested - state.residue;
+ else
+ count = 0;
+
uart_xmit_advance(port, count);
ourport->tx_in_progress = 0;
@@ -1162,6 +1172,32 @@ static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p)
}
}
+/*
+ * Called from uart_flush_buffer() with the port lock held, after the xmit fifo
+ * has already been reset. A TX DMA transfer still in flight carries data that
+ * no longer exists, so drop its byte count before terminating it: the
+ * completion callback may still run and must not advance the reset fifo.
+ */
+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;
+
+ dma->tx_bytes_requested = 0;
+
+ /*
+ * dmaengine_terminate_sync() cannot be used here because the port
+ * lock is held. The callback may or may not run afterwards; either
+ * way it is a no-op for the fifo because tx_bytes_requested is zero.
+ */
+ dmaengine_terminate_async(dma->tx_chan);
+
+ ourport->tx_in_progress = 0;
+}
+
static void s3c64xx_serial_shutdown(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
@@ -1684,6 +1720,7 @@ static const struct uart_ops s3c64xx_serial_ops = {
.stop_tx = s3c24xx_serial_stop_tx,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
+ .flush_buffer = s3c24xx_serial_flush_buffer,
.break_ctl = s3c24xx_serial_break_ctl,
.startup = s3c64xx_serial_startup,
.shutdown = s3c64xx_serial_shutdown,
@@ -1705,6 +1742,7 @@ static const struct uart_ops apple_s5l_serial_ops = {
.stop_tx = s3c24xx_serial_stop_tx,
.start_tx = s3c24xx_serial_start_tx,
.stop_rx = s3c24xx_serial_stop_rx,
+ .flush_buffer = s3c24xx_serial_flush_buffer,
.break_ctl = s3c24xx_serial_break_ctl,
.startup = apple_s5l_serial_startup,
.shutdown = apple_s5l_serial_shutdown,
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-31 15:27 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31 0:47 [BUG] serial: TX kfifo underflow when TCOFLUSH races with TX DMA (samsung, sprd, msm, pch_uart, tegra) Samarth Narang
2026-10-31 8:54 ` Greg Kroah-Hartman
2026-10-31 15:26 ` [PATCH 0/1] tty: serial: samsung: fix TX fifo underflow on flush race tianci xie
2026-10-31 15:26 ` [PATCH] " tianci xie
2026-10-31 12:39 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Samarth Narang
2026-10-31 12:39 ` [PATCH 1/5] tty: serial: samsung: fix TX kfifo underflow on flush Samarth Narang
2026-10-31 12:39 ` [PATCH 2/5] serial: tegra: don't advance the TX kfifo for a flushed transfer Samarth Narang
2026-10-31 12:39 ` [PATCH 3/5] serial: sprd: fix TX kfifo underflow on flush Samarth Narang
2026-10-31 12:39 ` [PATCH 4/5] tty: serial: msm: " Samarth Narang
2026-10-31 12:39 ` [PATCH 5/5] serial: pch_uart: " Samarth Narang
2026-10-31 12:59 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Greg Kroah-Hartman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®