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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.