From: sashiko-bot@kernel.org
To: "Samarth Narang" <samarth.colleges@gmail.com>
Cc: linux-serial@vger.kernel.org
Subject: Re: [PATCH 5/5] serial: pch_uart: fix TX kfifo underflow on flush
Date: Sat, 31 Oct 2026 12:55:24 +0000 [thread overview]
Message-ID: <sashiko-outbox-191950@kernel.org> (raw)
In-Reply-To: <20261031123911.3784690-6-samarth.colleges@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] serial: pch_uart: unlocked sg_tx_p read and TX DMA state overwrite after stop_tx
- [High] serial: pch_uart: lock inversion and self-deadlock in pch_dma_tx_complete()
- [High] serial: pch_uart: active DMA buffer overwrite and duplicate TX after flush
--
commit 41c9c81259479647a63a193b50f8884cd2a2a063
Author: Samarth Narang <samarth.colleges@gmail.com>
serial: pch_uart: fix TX kfifo underflow on flush
This commit adds a flush_buffer() callback to mark in-flight TX DMA
transfers as flushed and takes the port lock in pch_dma_tx_complete() so
completed flushed transfers do not advance the transmit kfifo.
> diff --git a/drivers/tty/serial/pch_uart.c b/drivers/tty/serial/pch_uart.c
> index 9505d255f09c5..122025c4a0a66 100644
> --- a/drivers/tty/serial/pch_uart.c
> +++ b/drivers/tty/serial/pch_uart.c
[ ... ]
> @@ -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);
[Severity: High]
Can taking port->lock in pch_dma_tx_complete() cause a lock inversion with
pd_chan->lock as well as a single-CPU recursive deadlock on port->lock?
In pch_uart_interrupt(), the interrupt handler holds priv->port.lock while
calling dma_handle_tx(), which calls into drivers/dma/pch_dma.c:
pch_uart_interrupt()
uart_port_lock(&priv->port)
dma_handle_tx()
dmaengine_prep_slave_sg() -> pd_prep_slave_sg() -> pdc_desc_get()
spin_lock(&pd_chan->lock)
dmaengine_submit() -> pd_tx_submit()
spin_lock(&pd_chan->lock)
dma_async_issue_pending() -> pd_issue_pending()
spin_lock(&pd_chan->lock)
This establishes the lock order priv->port.lock -> pd_chan->lock.
In drivers/dma/pch_dma.c, every path that invokes the descriptor callback
pch_dma_tx_complete() holds pd_chan->lock:
pdc_tasklet() / pd_issue_pending() / pd_device_terminate_all()
spin_lock(&pd_chan->lock)
pdc_advance_work() / pdc_complete_all() / pdc_handle_error()
pdc_chain_complete()
dmaengine_desc_callback_invoke()
pch_dma_tx_complete()
uart_port_lock_irqsave(port, &flags)
On SMP, if one CPU enters pch_uart_interrupt() -> dma_handle_tx() while
another CPU runs pdc_tasklet() -> pch_dma_tx_complete(), can this deadlock
between priv->port.lock and pd_chan->lock?
In addition, in pd_tx_submit(), when pd_chan->active_list is empty,
pdc_dostart() starts the hardware DMA transfer immediately. Two lines later
in dma_handle_tx() (still holding priv->port.lock),
dma_async_issue_pending() calls pd_issue_pending():
if (pdc_is_idle(pd_chan)) {
spin_lock(&pd_chan->lock);
pdc_advance_work(pd_chan);
spin_unlock(&pd_chan->lock);
}
If a short transfer into the UART TX FIFO has already finished by the time
pdc_is_idle() is checked, pdc_advance_work() will invoke
pch_dma_tx_complete() synchronously on the same CPU while
pch_uart_interrupt() already holds priv->port.lock.
> + 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;
[Severity: Critical]
What happens if pch_uart_stop_tx() is called while a TX DMA transfer is in
flight, followed by a flush and a subsequent TX restart before that first
transfer completes?
In pch_dma_tx_complete(), sg is initialized from priv->sg_tx_p before
uart_port_lock_irqsave() is called:
struct scatterlist *sg = priv->sg_tx_p;
...
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));
Meanwhile, pch_uart_stop_tx() clears priv->tx_dma_use = 0 without stopping
the in-flight DMA transfer. If TX is restarted via pch_uart_start_tx(),
dma_handle_tx() sees priv->tx_dma_use == 0 and starts a second TX DMA
transfer while the first transfer is still running:
priv->sg_tx_p = kmalloc_objs(struct scatterlist, num, GFP_ATOMIC);
...
priv->orig_nent = num;
priv->nent = nent;
priv->tx_dma_flushed = false;
When the first transfer's pch_dma_tx_complete() runs:
1. Because dma_handle_tx() reset priv->tx_dma_flushed to false, the flushed
first transfer still calls uart_xmit_advance() and underflows the kfifo.
2. If pch_dma_tx_complete() read sg = priv->sg_tx_p before dma_handle_tx()
replaced priv->sg_tx_p under port->lock, and the second transfer has a
larger priv->nent than the first, the loop over priv->nent reads past the
end of the first scatterlist allocation.
3. dma_unmap_sg() and kfree(priv->sg_tx_p) in the first completion will
unmap and free the second transfer's active scatterlist while the first
scatterlist is leaked, and the second completion will then unmap and kfree()
priv->sg_tx_p a second time.
>
> 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;
> +}
[Severity: High]
Could leaving the TX DMA transfer running after uart_flush_buffer() resets
the kfifo cause post-flush writes to overwrite the active DMA buffer and be
transmitted twice?
When uart_flush_buffer() runs during an active TX DMA transfer:
uart_flush_buffer()
uart_port_lock_irqsave()
kfifo_reset(&state->port.xmit_fifo)
pch_uart_flush_buffer()
priv->tx_dma_flushed = true
uart_port_unlock_irqrestore()
tty_port_tty_wakeup(&state->port)
Because kfifo_reset() resets xmit_fifo.in and xmit_fifo.out to offset 0 of
state->port.xmit_buf while the in-flight DMA transfer still has
state->port.xmit_buf mapped DMA_TO_DEVICE via priv->sg_tx_p, any new data
written through uart_write() or uart_put_char() before the DMA transfer
completes will write into state->port.xmit_buf starting at offset 0 while
DMA is actively reading from that buffer.
Then, if the in-flight DMA transfer reads those newly written bytes from
offset 0 and finishes, pch_dma_tx_complete() skips uart_xmit_advance()
because priv->tx_dma_flushed is set and enables PCH_UART_HAL_TX_INT, causing
dma_handle_tx() to transmit the same post-flush bytes from xmit_fifo a
second time.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261031123911.3784690-1-samarth.colleges@gmail.com?part=5
next prev parent reply other threads:[~2026-10-31 12:55 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
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 15:48 ` sashiko-bot
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 13:02 ` sashiko-bot
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:55 ` sashiko-bot
2026-10-31 12:39 ` [PATCH 3/5] serial: sprd: fix TX kfifo underflow on flush Samarth Narang
2026-10-31 12:58 ` sashiko-bot
2026-10-31 12:39 ` [PATCH 4/5] tty: serial: msm: " Samarth Narang
2026-10-31 12:55 ` sashiko-bot
2026-10-31 12:39 ` [PATCH 5/5] serial: pch_uart: " Samarth Narang
2026-10-31 12:55 ` sashiko-bot [this message]
2026-10-31 12:59 ` [PATCH 0/5] serial: fix TX kfifo underflow on flush in five drivers Greg Kroah-Hartman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-191950@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=samarth.colleges@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox