[PATCH v8 6/6] serial: 8250: retain DMA resources when termination fails
James Hilliard
james.hilliard1 at gmail.com
Thu Oct 29 00:37:02 PDT 2026
serial8250_release_dma() ignores the return from dmaengine_terminate_sync()
before freeing the coherent RX buffer, unmapping the TX FIFO and releasing
the channels. If the provider fails to stop a channel, the DMAengine
wrapper returns the error without synchronizing its callbacks. Hardware
or a detached callback can then access the released storage and state.
Checking the error and returning early would not preserve their lifetime:
serial core frees the TX FIFO after the driver's shutdown callback returns,
and port removal can then release the callback context as well.
Require successful synchronous termination of both channels before
releasing any DMA resources. Retry failed channels after a sleep outside
the port lock, leaving channels that have already stopped and drained
alone. Keep the shutdown gate and storage reservation in place throughout
the retries.
A permanently failed DMA engine blocks close or removal. This deliberately
retains the buffers, mappings, channels and callback context until the
provider can establish that they are no longer in use.
Fixes: 9ee4b83e51f7 ("serial: 8250: Add support for dmaengine")
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
drivers/tty/serial/8250/8250_dma.c | 29 +++++++++++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
index f980b4faf6a3..ff7574b1be40 100644
--- a/drivers/tty/serial/8250/8250_dma.c
+++ b/drivers/tty/serial/8250/8250_dma.c
@@ -505,6 +505,32 @@ int serial8250_request_dma(struct uart_8250_port *p)
}
EXPORT_SYMBOL_GPL(serial8250_request_dma);
+static void serial8250_terminate_dma(struct uart_8250_port *p)
+{
+ struct uart_8250_dma *dma = p->dma;
+ int rx_ret, tx_ret;
+
+ rx_ret = dmaengine_terminate_sync(dma->rxchan);
+ tx_ret = dmaengine_terminate_sync(dma->txchan);
+
+ /*
+ * Shutdown cannot return while DMA owns storage or callback state:
+ * serial core frees the TX FIFO afterwards, and removal frees the port.
+ * Retain both channels and retry outside the port lock. A permanently
+ * failed DMA engine therefore prevents teardown from completing.
+ */
+ while (rx_ret || tx_ret) {
+ dev_err_ratelimited(p->port.dev,
+ "DMA termination failed: RX %d, TX %d; retrying\n",
+ rx_ret, tx_ret);
+ msleep(100);
+ if (rx_ret)
+ rx_ret = dmaengine_terminate_sync(dma->rxchan);
+ if (tx_ret)
+ tx_ret = dmaengine_terminate_sync(dma->txchan);
+ }
+}
+
void serial8250_release_dma(struct uart_8250_port *p)
{
struct uart_8250_dma *dma = p->dma;
@@ -514,9 +540,9 @@ void serial8250_release_dma(struct uart_8250_port *p)
cancel_work_sync(&dma->tx_flush_work);
cancel_work_sync(&dma->rx_flush_work);
+ serial8250_terminate_dma(p);
/* Release RX resources */
- dmaengine_terminate_sync(dma->rxchan);
scoped_guard(uart_port_lock_irqsave, &p->port) {
dma->rx_running = 0;
dma->rx_terminating = false;
@@ -528,7 +554,6 @@ void serial8250_release_dma(struct uart_8250_port *p)
dma->rxchan = NULL;
/* Release TX resources */
- dmaengine_terminate_sync(dma->txchan);
dma_unmap_single(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
UART_XMIT_SIZE, DMA_TO_DEVICE);
dma_release_channel(dma->txchan);
--
2.53.0
More information about the linux-arm-kernel
mailing list