From: sashiko-bot@kernel.org
To: "James Hilliard" <james.hilliard1@gmail.com>
Cc: linux-serial@vger.kernel.org
Subject: Re: [PATCH v3 4/4] serial: 8250: retire canceled RX DMA before reusing the receive buffer
Date: Fri, 23 Oct 2026 18:32:35 +0000 [thread overview]
Message-ID: <sashiko-outbox-183066@kernel.org> (raw)
In-Reply-To: <20261023-submit-serial-8250-dma-cancel-v1-v3-4-d78f9e4c4932@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] serial: 8250: serial8250_rx_dma_flush() returns -ESHUTDOWN when DMA is stopped
- [High] serial: 8250: serial8250_rx_dma_flush() drops data when pause fails on complete
- [High] serial: 8250_dw: DLAB write failure in dw8250_set_divisor() corrupts UART_IER
--
commit f21a925e2ab02b4a2962f22b9f5a07f563e44794
Author: James Hilliard <james.hilliard1@gmail.com>
serial: 8250: retire canceled RX DMA before reusing the receive buffer
This commit retires canceled RX DMA callbacks via a work item before
allowing the receive buffer to be reused, separates FIFO ownership from
callback retirement, and adds recovery and shutdown handling for RX DMA
failures.
> diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
> index 611e4dee98297..2485029b989c4 100644
> --- a/drivers/tty/serial/8250/8250_dma.c
> +++ b/drivers/tty/serial/8250/8250_dma.c
[ ... ]
> @@ -262,16 +308,49 @@ int serial8250_rx_dma(struct uart_8250_port *p)
> return 0;
> }
>
> -void serial8250_rx_dma_flush(struct uart_8250_port *p)
> +int serial8250_rx_dma_flush(struct uart_8250_port *p)
> {
> struct uart_8250_dma *dma = p->dma;
> + struct dma_tx_state state;
> + enum dma_status status;
> + int ret;
>
> - if (!dma || !dma->rxchan || !dma->rx_running)
> - return;
> + lockdep_assert_held_once(&p->port.lock);
>
> - dmaengine_pause(dma->rxchan);
> - __dma_rx_complete(p);
> - dmaengine_terminate_async(dma->rxchan);
> + if (!dma)
> + return 0;
> + /* A late interrupt must not hand a still-active receiver to PIO. */
> + if (dma->shutting_down)
> + return -ESHUTDOWN;
> + if (!dma->rx_running)
> + return 0;
[Severity: High]
Because serial8250_rx_dma_flush() checks dma->shutting_down before
!dma->rx_running, it returns -ESHUTDOWN even when RX DMA is not running.
During port shutdown in serial8250_do_shutdown(), up->dma->shutting_down is
set to true and serial8250_release_dma() releases the DMA channels and sets
dma->rx_running = 0, but leaves up->dma non-NULL and dma->shutting_down set
to true until serial8250_request_dma() runs near the end of a subsequent
serial8250_do_startup():
drivers/tty/serial/8250/8250_port.c:serial8250_do_shutdown() {
scoped_guard(uart_port_lock_irqsave, port) {
if (up->dma)
up->dma->shutting_down = true;
up->ier = 0;
serial_port_out(port, UART_IER, 0);
}
synchronize_irq(port->irq);
if (up->dma)
serial8250_release_dma(up);
...
/* Disable break condition */
lcr = serial_port_in(port, UART_LCR);
lcr &= ~UART_LCR_SBC;
serial_port_out(port, UART_LCR, lcr);
}
On DesignWare UARTs, if DW_UART_USR_BUSY is asserted during that UART_LCR
write in serial8250_do_shutdown(), or during serial8250_initialize() in
serial8250_do_startup() before serial8250_request_dma() clears
dma->shutting_down, the call chain
dw8250_serial_out() -> dw8250_check_lcr() -> dw8250_idle_enter() calls
serial8250_rx_dma_flush(up), which returns -ESHUTDOWN and aborts the LCR
write even though DMA is already stopped and released.
Similarly, on port reopen in serial8250_do_startup(), up->ops->setup_irq()
and serial8250_initialize() run while dma->shutting_down is still true, so
any interrupt firing before serial8250_request_dma() hits the
up->dma->shutting_down early return in dw8250_handle_irq() and
serial8250_handle_irq_locked() without reading status registers to clear the
interrupt.
Should dma->shutting_down be cleared once serial8250_release_dma() finishes
releasing the channels (or at the start of serial8250_do_startup()), and
should serial8250_rx_dma_flush() check !dma->rx_running before
dma->shutting_down?
> + if (dma->rx_terminating)
> + return -EIO;
> +
> + ret = dmaengine_pause(dma->rxchan);
> + if (ret)
> + goto recover;
> +
> + status = dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state);
> + if (status == DMA_ERROR) {
> + ret = -EIO;
> + goto recover;
> + }
> + /* Some providers still report IN_PROGRESS after a successful pause. */
> + serial8250_rx_dma_copy(p, &state);
[Severity: High]
Can calling dmaengine_pause() before checking dmaengine_tx_status() drop a
completed RX DMA buffer?
dma_rx_complete() runs asynchronously (for example in a vchan tasklet) and
must acquire p->port.lock before copying dma->rx_buf and clearing
dma->rx_running. If a full dma->rx_size transfer completes in hardware and a
UART interrupt (UART_IIR_RDI, UART_IIR_RX_TIMEOUT, or UART_IIR_RLSI in
serial8250_handle_irq_locked() -> handle_rx_dma()) or dw8250_idle_enter()
calls serial8250_rx_dma_flush() before dma_rx_complete() acquires
p->port.lock, dma->rx_running is still 1 while the descriptor status is
already DMA_COMPLETE.
Several DMA engine drivers that advertise cmd_pause return an error from
device_pause when the transfer has already completed in hardware: for
example, dw-axi-dmac's dma_chan_pause() polls for DWAXIDMAC_IRQ_SUSPENDED on
a channel already disabled by axi_chan_block_xfer_complete() and returns
-EAGAIN, while stm32_dma_pause() and dw_edma_device_pause() return -EPERM
and edma_dma_pause() and omap_dma_pause() return -EINVAL when the channel is
not currently active.
When dmaengine_pause() returns an error here, serial8250_rx_dma_flush()
jumps to recover, where serial8250_rx_dma_recover() sets dma->rx_err = true
and dma->rx_terminating = true without calling serial8250_rx_dma_copy().
When the pending dma_rx_complete() callback then acquires p->port.lock, it
sees dma->rx_terminating and returns early without copying dma->rx_buf
either, losing the completed buffer and disabling RX DMA until reopen.
> diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
> index eca02e8bd6d13..3880ec6a9368e 100644
> --- a/drivers/tty/serial/8250/8250_dw.c
> +++ b/drivers/tty/serial/8250/8250_dw.c
[ ... ]
> @@ -147,7 +148,12 @@ static int dw8250_idle_enter(struct uart_port *p)
> serial_port_out(p, UART_IER, 0);
>
> if (up->dma) {
> - serial8250_rx_dma_flush(up);
> + ret = serial8250_rx_dma_flush(up);
> + if (ret) {
> + serial8250_out_IER(up, up->ier);
> + d->in_idle = 0;
> + return ret;
> + }
> if (serial8250_tx_dma_running(up))
> serial8250_tx_dma_pause(up);
> }
[Severity: High]
When dw8250_idle_enter() returns an error here, dw8250_check_lcr() aborts
without writing UART_LCR:
drivers/tty/serial/8250/8250_dw.c:dw8250_check_lcr() {
...
ret = dw8250_idle_enter(p);
if (ret < 0)
goto write_err;
serial_port_out(p, UART_LCR, value);
dw8250_idle_exit(p);
return;
}
On DesignWare UARTs with fractional divisor support (where DW_UART_DLF is
non-zero), dw8250_setup_port() in 8250_dwlib.c overrides p->set_divisor with
its own dw8250_set_divisor(), which calls serial8250_do_set_divisor():
drivers/tty/serial/8250/8250_dwlib.c:dw8250_set_divisor() {
dw8250_writel_ext(p, DW_UART_DLF, quot_frac);
serial8250_do_set_divisor(p, baud, quot);
}
drivers/tty/serial/8250/8250_port.c:serial8250_do_set_divisor() {
if (!(up->capabilities & UART_CAP_MINI))
serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB);
serial_dl_write(up, quot);
}
If serial8250_do_set_termios() runs while the UART is busy and
serial8250_rx_dma_flush() returns an error in dw8250_idle_enter() (for
example, while RX DMA recovery is active with dma->rx_running &&
dma->rx_terminating, or when pausing/terminating RX DMA fails), the write to
set UART_LCR_DLAB fails in dw8250_check_lcr() and DLAB remains 0 in
hardware.
serial8250_do_set_divisor() then unconditionally calls serial_dl_write() ->
default_serial_dl_write(), which writes quot & 0xff to offset 0 (UART_TX
when DLAB is 0) and (quot >> 8) & 0xff to offset 1 (UART_IER when DLAB is 0)
via raw serial_out(). Can this corrupt UART_IER and bypass
serial8250_out_IER() to unmask receive interrupts in hardware during RX DMA
recovery?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-submit-serial-8250-dma-cancel-v1-v3-0-d78f9e4c4932@gmail.com?part=4
prev parent reply other threads:[~2026-10-23 18:32 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 18:04 [PATCH v3 0/4] serial: 8250: serialize DMA cancellation with buffer reuse James Hilliard
2026-10-23 18:04 ` [PATCH v3 1/4] serial: 8250_omap: separate locked TX DMA completion processing James Hilliard
2026-10-23 18:11 ` sashiko-bot
2026-10-23 18:04 ` [PATCH v3 2/4] serial: core: allow drivers to reserve flushed transmit storage James Hilliard
2026-10-23 18:40 ` sashiko-bot
2026-10-23 18:04 ` [PATCH v3 3/4] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
2026-10-23 18:22 ` sashiko-bot
2026-10-23 18:04 ` [PATCH v3 4/4] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
2026-10-23 18:32 ` sashiko-bot [this message]
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-183066@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=james.hilliard1@gmail.com \
--cc=linux-serial@vger.kernel.org \
--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