From: sashiko-bot@kernel.org
To: "Samarth Narang" <samarth.colleges@gmail.com>
Cc: linux-serial@vger.kernel.org, imx@lists.linux.dev, Frank.Li@kernel.org
Subject: Re: [PATCH 2/2] tty: serial: fsl_lpuart: don't unmap TX buffer before DMA has stopped
Date: Sat, 31 Oct 2026 12:53:11 +0000 [thread overview]
Message-ID: <sashiko-outbox-191947@kernel.org> (raw)
In-Reply-To: <20261031123749.3783352-3-samarth.colleges@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] tty: serial: fsl_lpuart: race scheduling dma_tx_flush_work after flush_work()
- [High] tty: serial: fsl_lpuart: lpuart_dma_tx_flush_work() restarts DMA on teardown
--
commit 45f7dcf3d930bb1e96c498ca260fc93b635e30f0
Author: Samarth Narang <samarth.colleges@gmail.com>
tty: serial: fsl_lpuart: don't unmap TX buffer before DMA has stopped
This commit defers TX DMA scatterlist unmapping from lpuart_flush_buffer() to
a work item that runs dmaengine_synchronize() before releasing the mapping,
and flushes that work item in lpuart_dma_shutdown() and lpuart_suspend().
> diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c
> index c8575c9652035..8343351199eb5 100644
> --- a/drivers/tty/serial/fsl_lpuart.c
> +++ b/drivers/tty/serial/fsl_lpuart.c
[ ... ]
> @@ -558,6 +561,36 @@ static void lpuart_dma_tx_complete(void *arg)
> uart_port_unlock_irqrestore(&sport->port, flags);
> }
>
> +/*
> + * flush_buffer runs under the port lock and can only request termination.
> + * Unmap the TX scatterlist here, once the terminated transfer and its
> + * completion callback are known to have finished.
> + */
> +static void lpuart_dma_tx_flush_work(struct work_struct *work)
> +{
> + struct lpuart_port *sport = container_of(work, struct lpuart_port,
> + dma_tx_flush_work);
> + struct dma_chan *chan = sport->dma_tx_chan;
> + unsigned long flags;
> +
> + dmaengine_synchronize(chan);
> +
> + uart_port_lock_irqsave(&sport->port, &flags);
> + if (sport->dma_tx_flushing) {
> + dma_unmap_sg(dmaengine_get_dma_device(chan), &sport->tx_sgl[0],
> + sport->dma_tx_nents, DMA_TO_DEVICE);
> + sport->dma_tx_flushing = false;
> + sport->dma_tx_in_progress = false;
> + }
> + /* As in the TX callback: no restart while shutdown waits on dma_wait */
> + if (!waitqueue_active(&sport->dma_wait) &&
> + !lpuart_stopped_or_empty(&sport->port))
> + lpuart_dma_tx(sport);
[Severity: High]
Can lpuart_dma_tx_flush_work() still restart TX DMA and leak the scatterlist
mapping on sport->tx_sgl during shutdown or suspend?
In lpuart_dma_shutdown(), flush_work(&sport->dma_tx_flush_work) is called
before wait_event_interruptible_timeout() adds a waiter to sport->dma_wait:
if (sport->lpuart_dma_tx_use) {
flush_work(&sport->dma_tx_flush_work);
if (wait_event_interruptible_timeout(sport->dma_wait,
!sport->dma_tx_in_progress, msecs_to_jiffies(300)) <= 0) {
sport->dma_tx_in_progress = false;
dmaengine_terminate_sync(sport->dma_tx_chan);
}
sport->lpuart_dma_tx_use = false;
}
In lpuart_suspend(), flush_work(&sport->dma_tx_flush_work) is also called
with no waiter on sport->dma_wait:
if (sport->lpuart_dma_tx_use) {
...
uart_port_unlock_irqrestore(&sport->port, flags);
flush_work(&sport->dma_tx_flush_work);
sport->dma_tx_in_progress = false;
dmaengine_terminate_sync(sport->dma_tx_chan);
}
Because waitqueue_active(&sport->dma_wait) is false while flush_work() runs
in both paths, if xmit_fifo is non-empty (for example, if bytes were queued
after uart_flush_buffer() called tty_port_tty_wakeup() while
dma_tx_in_progress was held true by a pending flush, or when suspending an
active wakeup-enabled port where uart_suspend_port() returns early),
lpuart_dma_tx_flush_work() will call lpuart_dma_tx(sport) and map
sport->tx_sgl after the UART transmitter and TX DMA request bits have already
been disabled in hardware.
Since hardware TX DMA is already disabled, that newly submitted transfer
cannot complete: lpuart_dma_shutdown() will wait for the 300ms timeout (or
return immediately on a pending signal) and then call
dmaengine_terminate_sync(sport->dma_tx_chan) without unmapping sport->tx_sgl
before uart_shutdown() frees xmit_buf, and lpuart_suspend() will likewise
clear sport->dma_tx_in_progress and call dmaengine_terminate_sync() without
unmapping sport->tx_sgl.
> + uart_port_unlock_irqrestore(&sport->port, flags);
> +
> + wake_up(&sport->dma_wait);
> +}
[ ... ]
> @@ -1760,6 +1797,8 @@ static void lpuart_tx_dma_startup(struct lpuart_port *sport)
> goto err;
>
> init_waitqueue_head(&sport->dma_wait);
> + INIT_WORK(&sport->dma_tx_flush_work, lpuart_dma_tx_flush_work);
> + sport->dma_tx_flushing = false;
> sport->lpuart_dma_tx_use = true;
> if (lpuart_is_32(sport)) {
> uartbaud = lpuart32_read(&sport->port, UARTBAUD);
> @@ -1926,6 +1965,7 @@ static void lpuart_dma_shutdown(struct lpuart_port *sport)
> }
>
> if (sport->lpuart_dma_tx_use) {
> + flush_work(&sport->dma_tx_flush_work);
> if (wait_event_interruptible_timeout(sport->dma_wait,
> !sport->dma_tx_in_progress, msecs_to_jiffies(300)) <= 0) {
> sport->dma_tx_in_progress = false;
> @@ -3179,6 +3219,7 @@ static int lpuart_suspend(struct device *dev)
> writeb(temp, sport->port.membase + UARTCR5);
> }
> uart_port_unlock_irqrestore(&sport->port, flags);
> + flush_work(&sport->dma_tx_flush_work);
> sport->dma_tx_in_progress = false;
> dmaengine_terminate_sync(sport->dma_tx_chan);
> }
[Severity: Critical]
What prevents lpuart_flush_buffer() from scheduling sport->dma_tx_flush_work
again after flush_work(&sport->dma_tx_flush_work) has already returned in
lpuart_dma_shutdown() or lpuart_suspend()?
uart_flush_buffer() only takes sport->port.lock and does not hold
state->port.mutex or check tty_port_initialized(), so it can run concurrently
via tty_ioctl(TCFLSH) -> tty_perform_flush() or line discipline signal
handling (isig() -> tty_driver_flush_buffer()):
if (sport->lpuart_dma_tx_use) {
dmaengine_terminate_async(chan);
if (sport->dma_tx_in_progress && !sport->dma_tx_flushing) {
sport->dma_tx_flushing = true;
schedule_work(&sport->dma_tx_flush_work);
}
}
In lpuart_dma_shutdown(), after flush_work(&sport->dma_tx_flush_work)
returns, the task sleeps in wait_event_interruptible_timeout() without
holding sport->port.lock while sport->lpuart_dma_tx_use is still true and
(if an un-flushed TX DMA transfer was active when lpuart_shutdown() or
lpuart32_shutdown() disabled hardware TX) sport->dma_tx_in_progress is true
and sport->dma_tx_flushing is false.
If lpuart_flush_buffer() runs during that wait and schedules
sport->dma_tx_flush_work, and wait_event_interruptible_timeout() is
interrupted by a signal or times out before the work runs,
lpuart_dma_shutdown() proceeds to call
dma_release_channel(sport->dma_tx_chan) without flushing
sport->dma_tx_flush_work again, allowing lpuart_dma_tx_flush_work() to
dereference the released sport->dma_tx_chan.
Similarly, in lpuart_suspend() on an active wakeup-enabled port,
sport->lpuart_dma_tx_use remains true across suspend and
sport->dma_tx_in_progress is cleared outside sport->port.lock after
flush_work(). If sport->dma_tx_flush_work is queued after flush_work() in
either path, a subsequent open or resume calling lpuart_tx_dma_startup()
will also execute INIT_WORK(&sport->dma_tx_flush_work, ...) while the work
item is still pending or running.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261031123749.3783352-1-samarth.colleges@gmail.com?part=2
next prev parent reply other threads:[~2026-10-31 12:53 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 0:54 [BUG] serial: amba-pl011, imx, fsl_lpuart: ->flush_buffer() unmaps the TX DMA buffer before the transfer has stopped Samarth Narang
2026-10-31 8:55 ` Greg Kroah-Hartman
2026-10-31 12:37 ` [PATCH 0/2] serial: keep the flushed TX buffer mapped until DMA " Samarth Narang
2026-10-31 12:37 ` [PATCH 1/2] serial: amba-pl011: keep the flushed TX buffer mapped until DMA stops Samarth Narang
2026-10-31 12:52 ` sashiko-bot
2026-10-31 12:37 ` [PATCH 2/2] tty: serial: fsl_lpuart: don't unmap TX buffer before DMA has stopped Samarth Narang
2026-10-31 12:53 ` sashiko-bot [this message]
2026-10-31 13:00 ` [PATCH 0/2] serial: keep the flushed TX buffer mapped until " Greg Kroah-Hartman
2026-10-31 13:44 ` Samarth Narang
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-191947@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=imx@lists.linux.dev \
--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