All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] serial: 8250: serialize DMA cancellation with buffer reuse
@ 2026-10-21 22:37 James Hilliard
  2026-10-21 22:37 ` [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
  2026-10-21 22:37 ` [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
  0 siblings, 2 replies; 9+ messages in thread
From: James Hilliard @ 2026-10-21 22:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Raul E Rangel, Ilpo Järvinen,
	Andy Shevchenko, Heikki Krogerus
  Cc: linux-kernel, linux-serial, James Hilliard

Keep canceled transmit and receive buffers reserved until the provider's
completion callbacks have finished. The TX fix also separates the DMA
source from the serial core's kfifo, which is reset before the driver is
called to flush a transfer. The RX fix keeps partial-buffer delivery ahead
of the PIO fallback while the canceled descriptor is retired.

These changes repair the existing finite-transfer DMA path.
DMA providers must honor the existing terminate/synchronize contract;
fixes for providers that do not are separate DMAengine work.

Based on tty-next with the posted shutdown guard [0], channel
configuration error handling [1], and RX TTY accounting fix [2].

[0] https://lore.kernel.org/r/20261021-submit-serial-8250-dma-shutdown-v1-v1-1-3d3ce383c614@gmail.com
[1] https://lore.kernel.org/r/20261021-submit-serial-8250-dma-config-errors-v1-v1-1-c067587d1ef2@gmail.com
[2] https://lore.kernel.org/r/20261021-submit-serial-8250-dma-rx-accounting-v1-v1-1-c11795914ab4@gmail.com

Assisted-by: Codex:gpt-6-astra
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
James Hilliard (2):
      serial: 8250: drain canceled TX DMA before reusing its buffer
      serial: 8250: retire canceled RX DMA before reusing the receive buffer

 drivers/tty/serial/8250/8250.h      |  20 +++-
 drivers/tty/serial/8250/8250_dma.c  | 205 ++++++++++++++++++++++++++----------
 drivers/tty/serial/8250/8250_dw.c   |   8 +-
 drivers/tty/serial/8250/8250_mid.c  |   6 +-
 drivers/tty/serial/8250/8250_omap.c |  26 +++--
 drivers/tty/serial/8250/8250_port.c |   3 +-
 6 files changed, 191 insertions(+), 77 deletions(-)
---
base-commit: 1184f87e2790e8af9be3116f608d2e86b4792d7d
change-id: 20261021-submit-serial-8250-dma-cancel-v1-b208f84e89c1
prerequisite-patch-id: 3604bfaa3a1c214997de1f11162b81a4541af5ab
prerequisite-patch-id: 79dd093ffb35e254e790dffb8887faf51b64f947
prerequisite-patch-id: 80a5061d84c055a64c760268febed179c687b99f

Best regards,
--  
James Hilliard <james.hilliard1@gmail.com>


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer
  2026-10-21 22:37 [PATCH 0/2] serial: 8250: serialize DMA cancellation with buffer reuse James Hilliard
@ 2026-10-21 22:37 ` James Hilliard
  2026-10-21 22:50   ` sashiko-bot
                     ` (2 more replies)
  2026-10-21 22:37 ` [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
  1 sibling, 3 replies; 9+ messages in thread
From: James Hilliard @ 2026-10-21 22:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Raul E Rangel, Ilpo Järvinen,
	Andy Shevchenko, Heikki Krogerus
  Cc: linux-kernel, linux-serial, James Hilliard

uart_flush_buffer() resets the transmit kfifo before calling the driver's
flush callback. serial8250_tx_dma_flush() terminates DMA asynchronously and
clears tx_running, allowing a new write to replace tx_size before the old
completion callback has finished. That callback can then consume bytes
from the new transmission. The serial core can also overwrite the old
DMA source as soon as it reuses the reset kfifo.

Give DMA a private transmit buffer, keeping the kfifo offsets used by
FIFO-window providers. Copy only the queued ranges before handing the
buffer to the device. Flushing the kfifo can then no longer change an
in-flight transfer's source.

Keep tx_running set while a worker synchronizes a successfully terminated
channel. Suppress callbacks and new submissions during that interval.
The DesignWare idle helpers must not pause or resume a retiring channel.
Once synchronization finishes, process the zero-length completion under
the port lock and start any data queued after the flush. Reuse the normal
completion processing for OMAP too, including its delayed register
restore and runtime-PM handling. Drain the worker before releasing DMA
resources on shutdown.

Calculate the queued length before calling the platform preparation hook,
so it sees the length of the transfer being prepared rather than the
previous transfer's length.

If termination fails, leave the buffer owned by the transfer and let its
normal completion release it. Do not submit another transfer before that
completion. A DMA completion still does not mean that the UART transmitter
is empty; the existing line-status checks remain in place.

The race was found by inspecting cancellation and restart while preparing
H616 UART DMA support.

Fixes: a424a34b8fad ("serial: 8250: Fix TX deadlock when using DMA")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/tty/serial/8250/8250.h      |  13 +++-
 drivers/tty/serial/8250/8250_dma.c  | 118 ++++++++++++++++++++++++++----------
 drivers/tty/serial/8250/8250_omap.c |  26 ++++----
 3 files changed, 111 insertions(+), 46 deletions(-)

diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 457732e941af..002e456fa384 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -11,6 +11,7 @@
 #include <linux/serial_8250.h>
 #include <linux/serial_core.h>
 #include <linux/dmaengine.h>
+#include <linux/workqueue.h>
 
 #include "../serial_mctrl_gpio.h"
 
@@ -19,6 +20,10 @@ struct uart_8250_dma {
 	int (*rx_dma)(struct uart_8250_port *p);
 	void (*prepare_tx_dma)(struct uart_8250_port *p);
 	void (*prepare_rx_dma)(struct uart_8250_port *p);
+	/* Completion processing with the port lock held. */
+	void (*tx_complete)(struct uart_8250_port *p);
+	struct work_struct tx_flush_work;
+	struct uart_8250_port *port;
 
 	/* Filter function */
 	dma_filter_fn		fn;
@@ -44,6 +49,7 @@ struct uart_8250_dma {
 	dma_cookie_t		tx_cookie;
 
 	void			*rx_buf;
+	void			*tx_buf;
 
 	size_t			rx_size;
 	size_t			tx_size;
@@ -53,6 +59,7 @@ struct uart_8250_dma {
 	unsigned char		rx_running;
 	/* Protected by the UART port lock. */
 	bool			shutting_down;
+	bool			tx_terminating;
 };
 
 struct old_serial_port {
@@ -401,6 +408,8 @@ static inline int is_omap1510_8250(struct uart_8250_port *pt)
 #ifdef CONFIG_SERIAL_8250_DMA
 extern int serial8250_tx_dma(struct uart_8250_port *);
 extern void serial8250_tx_dma_flush(struct uart_8250_port *);
+void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
+				      struct scatterlist *sgl, unsigned int nents);
 extern int serial8250_rx_dma(struct uart_8250_port *);
 extern void serial8250_rx_dma_flush(struct uart_8250_port *);
 extern int serial8250_request_dma(struct uart_8250_port *);
@@ -433,7 +442,7 @@ static inline void serial8250_tx_dma_pause(struct uart_8250_port *p)
 {
 	struct uart_8250_dma *dma = p->dma;
 
-	if (!dma->tx_running)
+	if (!dma->tx_running || dma->tx_terminating || dma->shutting_down)
 		return;
 
 	dmaengine_pause(dma->txchan);
@@ -443,7 +452,7 @@ static inline void serial8250_tx_dma_resume(struct uart_8250_port *p)
 {
 	struct uart_8250_dma *dma = p->dma;
 
-	if (!dma->tx_running)
+	if (!dma->tx_running || dma->tx_terminating || dma->shutting_down)
 		return;
 
 	dmaengine_resume(dma->txchan);
diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
index 60e99a0db9b8..d4b1e2a059eb 100644
--- a/drivers/tty/serial/8250/8250_dma.c
+++ b/drivers/tty/serial/8250/8250_dma.c
@@ -8,21 +8,16 @@
 #include <linux/tty_flip.h>
 #include <linux/serial_reg.h>
 #include <linux/dma-mapping.h>
+#include <linux/slab.h>
 
 #include "8250.h"
 
-static void __dma_tx_complete(void *param)
+static void serial8250_tx_dma_complete(struct uart_8250_port *p)
 {
-	struct uart_8250_port	*p = param;
 	struct uart_8250_dma	*dma = p->dma;
 	struct tty_port		*tport = &p->port.state->port;
-	unsigned long	flags;
 	int		ret;
 
-	uart_port_lock_irqsave(&p->port, &flags);
-	if (dma->shutting_down)
-		goto out;
-
 	dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
 				UART_XMIT_SIZE, DMA_TO_DEVICE);
 
@@ -36,11 +31,52 @@ static void __dma_tx_complete(void *param)
 	ret = serial8250_tx_dma(p);
 	if (ret || !dma->tx_running)
 		serial8250_set_THRI(p);
+}
 
-out:
-	uart_port_unlock_irqrestore(&p->port, flags);
+static void __dma_tx_complete(void *param)
+{
+	struct uart_8250_port *p = param;
+	struct uart_8250_dma *dma = p->dma;
+
+	guard(uart_port_lock_irqsave)(&p->port);
+	if (!dma->shutting_down && !dma->tx_terminating)
+		serial8250_tx_dma_complete(p);
 }
 
+static void serial8250_tx_dma_flush_work(struct work_struct *work)
+{
+	struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
+					       tx_flush_work);
+	struct uart_8250_port *p = dma->port;
+
+	/* Neither the channel nor its buffer may be reused before this returns. */
+	dmaengine_synchronize(dma->txchan);
+
+	guard(uart_port_lock_irqsave)(&p->port);
+	dma->tx_terminating = false;
+	if (!dma->shutting_down)
+		dma->tx_complete(p);
+}
+
+void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
+				    struct scatterlist *sgl, unsigned int nents)
+{
+	struct uart_8250_dma *dma = p->dma;
+	struct scatterlist *sg;
+	unsigned int i;
+
+	/* Preserve ring offsets for DMA engines which implement a FIFO window. */
+	for_each_sg(sgl, sg, nents, i) {
+		size_t offset = sg_dma_address(sg) - dma->tx_addr;
+
+		memcpy(dma->tx_buf + offset,
+		       p->port.state->port.xmit_buf + offset, sg_dma_len(sg));
+	}
+	dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
+				   UART_XMIT_SIZE, DMA_TO_DEVICE);
+}
+EXPORT_SYMBOL_GPL(serial8250_tx_dma_prepare_buffer);
+
 static void __dma_rx_complete(struct uart_8250_port *p)
 {
 	struct uart_8250_dma	*dma = p->dma;
@@ -103,6 +139,8 @@ int serial8250_tx_dma(struct uart_8250_port *p)
 
 	if (dma->shutting_down)
 		return 0;
+	if (dma->tx_terminating)
+		return 0;
 
 	if (dma->tx_running) {
 		if (up->x_char) {
@@ -120,8 +158,6 @@ int serial8250_tx_dma(struct uart_8250_port *p)
 		return 0;
 	}
 
-	serial8250_do_prepare_tx_dma(p);
-
 	sg_init_table(sgl, ARRAY_SIZE(sgl));
 
 	ret = kfifo_dma_out_prepare_mapped(&tport->xmit_fifo, sgl, ARRAY_SIZE(sgl),
@@ -132,6 +168,8 @@ int serial8250_tx_dma(struct uart_8250_port *p)
 	for_each_sg(sgl, sg, ret, i)
 		dma->tx_size += sg_dma_len(sg);
 
+	serial8250_do_prepare_tx_dma(p);
+
 	desc = dmaengine_prep_slave_sg(dma->txchan, sgl, ret,
 				       DMA_MEM_TO_DEV,
 				       DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
@@ -141,14 +179,13 @@ int serial8250_tx_dma(struct uart_8250_port *p)
 	}
 
 	dma->tx_running = 1;
+	dma->tx_complete = serial8250_tx_dma_complete;
 	desc->callback = __dma_tx_complete;
 	desc->callback_param = p;
 
-	dma->tx_cookie = dmaengine_submit(desc);
-
-	dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
-				   UART_XMIT_SIZE, DMA_TO_DEVICE);
+	serial8250_tx_dma_prepare_buffer(p, sgl, ret);
 
+	dma->tx_cookie = dmaengine_submit(desc);
 	dma_async_issue_pending(dma->txchan);
 	serial8250_clear_THRI(p);
 	dma->tx_err = 0;
@@ -162,8 +199,9 @@ int serial8250_tx_dma(struct uart_8250_port *p)
 void serial8250_tx_dma_flush(struct uart_8250_port *p)
 {
 	struct uart_8250_dma *dma = p->dma;
+	int ret;
 
-	if (!dma->tx_running)
+	if (!dma->tx_running || dma->tx_terminating || dma->shutting_down)
 		return;
 
 	/*
@@ -176,18 +214,14 @@ void serial8250_tx_dma_flush(struct uart_8250_port *p)
 	 * We can't use `dmaengine_terminate_sync` because `uart_flush_buffer` is
 	 * holding the uart port spinlock.
 	 */
-	dmaengine_terminate_async(dma->txchan);
-
-	/*
-	 * The callback might or might not run. If it doesn't run, we need to ensure
-	 * that `tx_running` is cleared so that we can schedule new transactions.
-	 * If it does run, then the zombie callback will clear `tx_running` again
-	 * and perform a no-op since `tx_size` was cleared above.
-	 *
-	 * In either case, we ASSUME the DMA transaction will terminate before we
-	 * issue a new `serial8250_tx_dma`.
-	 */
-	dma->tx_running = 0;
+	dma->tx_terminating = true;
+	ret = dmaengine_terminate_async(dma->txchan);
+	if (ret) {
+		/* Keep the buffer owned by the transfer until its callback runs. */
+		dma->tx_terminating = false;
+		return;
+	}
+	schedule_work(&dma->tx_flush_work);
 }
 
 int serial8250_rx_dma(struct uart_8250_port *p)
@@ -313,20 +347,34 @@ int serial8250_request_dma(struct uart_8250_port *p)
 	}
 
 	/* TX buffer */
-	dma->tx_addr = dma_map_single(tx_dev, p->port.state->port.xmit_buf,
-				      UART_XMIT_SIZE, DMA_TO_DEVICE);
+	dma->tx_buf = kmalloc(UART_XMIT_SIZE, GFP_KERNEL);
+	if (!dma->tx_buf) {
+		ret = -ENOMEM;
+		goto free_rx;
+	}
+	dma->tx_addr = dma_map_single(tx_dev, dma->tx_buf,
+				     UART_XMIT_SIZE, DMA_TO_DEVICE);
 	if (dma_mapping_error(tx_dev, dma->tx_addr)) {
-		dma_free_coherent(rx_dev, dma->rx_size,
-				  dma->rx_buf, dma->rx_addr);
 		ret = -ENOMEM;
-		goto err;
+		goto free_tx;
 	}
+	dma_sync_single_for_cpu(tx_dev, dma->tx_addr,
+				UART_XMIT_SIZE, DMA_TO_DEVICE);
+
+	dma->port = p;
+	INIT_WORK(&dma->tx_flush_work, serial8250_tx_dma_flush_work);
+	dma->tx_terminating = false;
 
 	scoped_guard(uart_port_lock_irqsave, &p->port)
 		dma->shutting_down = false;
 	dev_dbg_ratelimited(p->port.dev, "got both dma channels\n");
 
 	return 0;
+free_tx:
+	kfree(dma->tx_buf);
+free_rx:
+	dma_free_coherent(rx_dev, dma->rx_size,
+			  dma->rx_buf, dma->rx_addr);
 err:
 	dma_release_channel(dma->txchan);
 release_rx:
@@ -342,6 +390,8 @@ void serial8250_release_dma(struct uart_8250_port *p)
 	if (!dma)
 		return;
 
+	cancel_work_sync(&dma->tx_flush_work);
+
 	/* Release RX resources */
 	dmaengine_terminate_sync(dma->rxchan);
 	dma->rx_running = 0;
@@ -354,6 +404,8 @@ void serial8250_release_dma(struct uart_8250_port *p)
 	dmaengine_terminate_sync(dma->txchan);
 	dma_unmap_single(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
 			 UART_XMIT_SIZE, DMA_TO_DEVICE);
+	kfree(dma->tx_buf);
+	dma->tx_buf = NULL;
 	dma_release_channel(dma->txchan);
 	dma->txchan = NULL;
 	dma->tx_running = 0;
diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index e3f3322da379..d8ea1a1a406a 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -1079,18 +1079,13 @@ static int omap_8250_rx_dma(struct uart_8250_port *p)
 
 static int omap_8250_tx_dma(struct uart_8250_port *p);
 
-static void omap_8250_dma_tx_complete(void *param)
+static void omap_8250_tx_dma_complete(struct uart_8250_port *p)
 {
-	struct uart_8250_port	*p = param;
 	struct uart_8250_dma	*dma = p->dma;
 	struct tty_port		*tport = &p->port.state->port;
 	bool			en_thri = false;
 	struct omap8250_priv	*priv = p->port.private_data;
 
-	guard(uart_port_lock_irqsave)(&p->port);
-	if (dma->shutting_down)
-		return;
-
 	dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
 				UART_XMIT_SIZE, DMA_TO_DEVICE);
 
@@ -1122,6 +1117,16 @@ static void omap_8250_dma_tx_complete(void *param)
 	}
 }
 
+static void omap_8250_dma_tx_complete(void *param)
+{
+	struct uart_8250_port *p = param;
+	struct uart_8250_dma *dma = p->dma;
+
+	guard(uart_port_lock_irqsave)(&p->port);
+	if (!dma->shutting_down && !dma->tx_terminating)
+		omap_8250_tx_dma_complete(p);
+}
+
 static int omap_8250_tx_dma(struct uart_8250_port *p)
 {
 	struct uart_8250_dma		*dma = p->dma;
@@ -1132,7 +1137,7 @@ static int omap_8250_tx_dma(struct uart_8250_port *p)
 	int skip_byte = -1;
 	int ret;
 
-	if (dma->shutting_down || dma->tx_running)
+	if (dma->shutting_down || dma->tx_terminating || dma->tx_running)
 		return 0;
 	if (uart_tx_stopped(&p->port) || kfifo_is_empty(&tport->xmit_fifo)) {
 
@@ -1200,15 +1205,14 @@ static int omap_8250_tx_dma(struct uart_8250_port *p)
 
 	dma->tx_size = sg_dma_len(&sg);
 	dma->tx_running = 1;
+	dma->tx_complete = omap_8250_tx_dma_complete;
 
 	desc->callback = omap_8250_dma_tx_complete;
 	desc->callback_param = p;
 
-	dma->tx_cookie = dmaengine_submit(desc);
-
-	dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
-				   UART_XMIT_SIZE, DMA_TO_DEVICE);
+	serial8250_tx_dma_prepare_buffer(p, &sg, 1);
 
+	dma->tx_cookie = dmaengine_submit(desc);
 	dma_async_issue_pending(dma->txchan);
 	if (dma->tx_err)
 		dma->tx_err = 0;

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer
  2026-10-21 22:37 [PATCH 0/2] serial: 8250: serialize DMA cancellation with buffer reuse James Hilliard
  2026-10-21 22:37 ` [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
@ 2026-10-21 22:37 ` James Hilliard
  2026-10-21 23:05   ` sashiko-bot
  2026-10-22 15:20   ` Raul E Rangel
  1 sibling, 2 replies; 9+ messages in thread
From: James Hilliard @ 2026-10-21 22:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Raul E Rangel, Ilpo Järvinen,
	Andy Shevchenko, Heikki Krogerus
  Cc: linux-kernel, linux-serial, James Hilliard

The RX timeout path pauses a transfer, copies its data, and terminates it
asynchronously. It also clears rx_running before an old callback has
necessarily returned. The next receive can therefore reuse the same
buffer and cookie while that callback is still waiting for the port lock.
Looking up the new cookie is not sufficient: a new transfer may already
be complete or paused when the old callback obtains the lock.

Keep the receive buffer reserved after termination and drain callbacks
in a worker before allowing another descriptor to use it. The interrupt
handler can receive through PIO while the canceled transfer is retired;
data already received by DMA is delivered first. Do not perform another
termination while synchronization is pending. Shutdown drains the worker
before freeing the buffer. Both the callback and retirement worker
honor disabled receive interrupts before restarting DMA.

Check pause failures before reading the DMA buffer or handing its FIFO
back to PIO. Make the DesignWare idle path abort rather than proceed with
an unpaused receiver. Track the delivered offset so a failed termination
followed by resumed DMA cannot deliver the same prefix a second time.

Serialize the Intel MID timeout callers with the same port lock used by
the callbacks and retirement worker. Keep that lock scoped to the flush,
since serial8250_handle_irq() takes it separately for the UART interrupt.

This relies on the provider honoring pause and synchronization; it does
not turn a pause-register write into a hardware-quiescence guarantee.

Fixes: 9ee4b83e51f7 ("serial: 8250: Add support for dmaengine")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/tty/serial/8250/8250.h      |  7 ++-
 drivers/tty/serial/8250/8250_dma.c  | 87 +++++++++++++++++++++++++++----------
 drivers/tty/serial/8250/8250_dw.c   |  8 +++-
 drivers/tty/serial/8250/8250_mid.c  |  6 ++-
 drivers/tty/serial/8250/8250_port.c |  3 +-
 5 files changed, 80 insertions(+), 31 deletions(-)

diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 002e456fa384..c69c41c9df65 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -23,6 +23,7 @@ struct uart_8250_dma {
 	/* Completion processing with the port lock held. */
 	void (*tx_complete)(struct uart_8250_port *p);
 	struct work_struct tx_flush_work;
+	struct work_struct rx_flush_work;
 	struct uart_8250_port *port;
 
 	/* Filter function */
@@ -52,6 +53,7 @@ struct uart_8250_dma {
 	void			*tx_buf;
 
 	size_t			rx_size;
+	size_t			rx_pos;
 	size_t			tx_size;
 
 	unsigned char		tx_running;
@@ -60,6 +62,7 @@ struct uart_8250_dma {
 	/* Protected by the UART port lock. */
 	bool			shutting_down;
 	bool			tx_terminating;
+	bool			rx_terminating;
 };
 
 struct old_serial_port {
@@ -411,7 +414,7 @@ extern void serial8250_tx_dma_flush(struct uart_8250_port *);
 void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
 				      struct scatterlist *sgl, unsigned int nents);
 extern int serial8250_rx_dma(struct uart_8250_port *);
-extern void serial8250_rx_dma_flush(struct uart_8250_port *);
+int serial8250_rx_dma_flush(struct uart_8250_port *p);
 extern int serial8250_request_dma(struct uart_8250_port *);
 extern void serial8250_release_dma(struct uart_8250_port *);
 
@@ -467,7 +470,7 @@ static inline int serial8250_rx_dma(struct uart_8250_port *p)
 {
 	return -1;
 }
-static inline void serial8250_rx_dma_flush(struct uart_8250_port *p) { }
+static inline int serial8250_rx_dma_flush(struct uart_8250_port *p) { return 0; }
 static inline int serial8250_request_dma(struct uart_8250_port *p)
 {
 	return -1;
diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
index d4b1e2a059eb..821ccc7a3a54 100644
--- a/drivers/tty/serial/8250/8250_dma.c
+++ b/drivers/tty/serial/8250/8250_dma.c
@@ -77,31 +77,29 @@ void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
 }
 EXPORT_SYMBOL_GPL(serial8250_tx_dma_prepare_buffer);
 
-static void __dma_rx_complete(struct uart_8250_port *p)
+static enum dma_status serial8250_rx_dma_complete(struct uart_8250_port *p)
 {
 	struct uart_8250_dma	*dma = p->dma;
 	struct tty_port		*tty_port = &p->port.state->port;
 	struct dma_tx_state	state;
 	enum dma_status		dma_status;
-	int			count;
+	size_t			count;
 
-	/*
-	 * New DMA Rx can be started during the completion handler before it
-	 * could acquire port's lock and it might still be ongoing. Don't to
-	 * anything in such case.
-	 */
+	/* Only inspect the buffer after completion or a successful pause. */
 	dma_status = dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state);
-	if (dma_status == DMA_IN_PROGRESS)
-		return;
+	if (dma_status != DMA_COMPLETE && dma_status != DMA_PAUSED)
+		return dma_status;
 
-	count = dma->rx_size - state.residue;
+	count = dma->rx_size - state.residue - dma->rx_pos;
 
-	if (tty_insert_flip_string(tty_port, dma->rx_buf, count) != count)
+	if (tty_insert_flip_string(tty_port, dma->rx_buf + dma->rx_pos,
+				   count) != count)
 		p->port.icount.buf_overrun++;
 	p->port.icount.rx += count;
-	dma->rx_running = 0;
+	dma->rx_pos += count;
 
 	tty_flip_buffer_push(tty_port);
+	return dma_status;
 }
 
 static void dma_rx_complete(void *param)
@@ -111,21 +109,37 @@ static void dma_rx_complete(void *param)
 	unsigned long flags;
 
 	uart_port_lock_irqsave(&p->port, &flags);
-	if (dma->shutting_down)
+	if (dma->shutting_down || dma->rx_terminating)
 		goto out;
-	if (dma->rx_running)
-		__dma_rx_complete(p);
+	if (dma->rx_running && serial8250_rx_dma_complete(p) == DMA_COMPLETE)
+		dma->rx_running = 0;
 
 	/*
-	 * Cannot be combined with the previous check because __dma_rx_complete()
-	 * changes dma->rx_running.
+	 * The completed transfer no longer owns the receive buffer.
 	 */
-	if (!dma->rx_running && (serial_lsr_in(p) & UART_LSR_DR))
+	if (!dma->rx_running && (p->ier & UART_IER_RDI) &&
+	    (serial_lsr_in(p) & UART_LSR_DR))
 		p->dma->rx_dma(p);
 out:
 	uart_port_unlock_irqrestore(&p->port, flags);
 }
 
+static void serial8250_rx_dma_flush_work(struct work_struct *work)
+{
+	struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
+					       rx_flush_work);
+	struct uart_8250_port *p = dma->port;
+
+	dmaengine_synchronize(dma->rxchan);
+
+	guard(uart_port_lock_irqsave)(&p->port);
+	dma->rx_terminating = false;
+	dma->rx_running = 0;
+	if (!dma->shutting_down && (p->ier & UART_IER_RDI) &&
+	    (serial_lsr_in(p) & UART_LSR_DR))
+		dma->rx_dma(p);
+}
+
 int serial8250_tx_dma(struct uart_8250_port *p)
 {
 	struct uart_8250_dma		*dma = p->dma;
@@ -241,6 +255,7 @@ int serial8250_rx_dma(struct uart_8250_port *p)
 		return -EBUSY;
 
 	dma->rx_running = 1;
+	dma->rx_pos = 0;
 	desc->callback = dma_rx_complete;
 	desc->callback_param = p;
 
@@ -251,16 +266,37 @@ 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;
+	enum dma_status status;
+	int ret;
 
-	if (!dma || !dma->rxchan || !dma->rx_running)
-		return;
+	lockdep_assert_held_once(&p->port.lock);
+
+	if (!dma || !dma->rxchan || !dma->rx_running || dma->rx_terminating)
+		return 0;
+
+	ret = dmaengine_pause(dma->rxchan);
+	if (ret)
+		return ret;
+
+	status = serial8250_rx_dma_complete(p);
+	if (status != DMA_PAUSED && status != DMA_COMPLETE) {
+		dmaengine_resume(dma->rxchan);
+		return -EIO;
+	}
+	dma->rx_terminating = true;
+	ret = dmaengine_terminate_async(dma->rxchan);
+	if (ret) {
+		dma->rx_terminating = false;
+		dmaengine_resume(dma->rxchan);
+		return ret;
+	}
 
-	dmaengine_pause(dma->rxchan);
-	__dma_rx_complete(p);
-	dmaengine_terminate_async(dma->rxchan);
+	/* Keep the buffer reserved until any old completion has returned. */
+	schedule_work(&dma->rx_flush_work);
+	return 0;
 }
 EXPORT_SYMBOL_GPL(serial8250_rx_dma_flush);
 
@@ -363,7 +399,9 @@ int serial8250_request_dma(struct uart_8250_port *p)
 
 	dma->port = p;
 	INIT_WORK(&dma->tx_flush_work, serial8250_tx_dma_flush_work);
+	INIT_WORK(&dma->rx_flush_work, serial8250_rx_dma_flush_work);
 	dma->tx_terminating = false;
+	dma->rx_terminating = false;
 
 	scoped_guard(uart_port_lock_irqsave, &p->port)
 		dma->shutting_down = false;
@@ -391,6 +429,7 @@ void serial8250_release_dma(struct uart_8250_port *p)
 		return;
 
 	cancel_work_sync(&dma->tx_flush_work);
+	cancel_work_sync(&dma->rx_flush_work);
 
 	/* Release RX resources */
 	dmaengine_terminate_sync(dma->rxchan);
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index eca02e8bd6d1..d8998b6f1d81 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -134,6 +134,7 @@ static int dw8250_idle_enter(struct uart_port *p)
 	unsigned int usr_reg = d->pdata ? d->pdata->usr_reg : DW_UART_USR;
 	struct uart_8250_port *up = up_to_u8250p(p);
 	int retries;
+	int ret;
 	u32 lsr;
 
 	lockdep_assert_held_once(&p->lock);
@@ -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) {
+			serial_port_out(p, UART_IER, up->ier);
+			d->in_idle = 0;
+			return ret;
+		}
 		if (serial8250_tx_dma_running(up))
 			serial8250_tx_dma_pause(up);
 	}
diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c
index 44b43163df51..83f928b1f643 100644
--- a/drivers/tty/serial/8250/8250_mid.c
+++ b/drivers/tty/serial/8250/8250_mid.c
@@ -94,7 +94,8 @@ static int tng_handle_irq(struct uart_port *p)
 	/* Rx DMA */
 	err = hsu_dma_get_status(chip, mid->dma_index * 2 + 1, &status);
 	if (err > 0) {
-		serial8250_rx_dma_flush(up);
+		scoped_guard(uart_port_lock_irqsave, p)
+			serial8250_rx_dma_flush(up);
 		ret |= 1;
 	} else if (err == 0)
 		ret |= hsu_dma_do_irq(chip, mid->dma_index * 2 + 1, status);
@@ -148,7 +149,8 @@ static int dnv_handle_irq(struct uart_port *p)
 	if (fisr & BIT(2)) {
 		err = hsu_dma_get_status(&mid->dma_chip, 1, &status);
 		if (err > 0) {
-			serial8250_rx_dma_flush(up);
+			scoped_guard(uart_port_lock_irqsave, p)
+				serial8250_rx_dma_flush(up);
 			ret |= 1;
 		} else if (err == 0)
 			ret |= hsu_dma_do_irq(&mid->dma_chip, 1, status);
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index d476ebc37647..b9837a3c4681 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -1815,8 +1815,7 @@ static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir)
 		fallthrough;
 	case UART_IIR_RLSI:
 	case UART_IIR_RX_TIMEOUT:
-		serial8250_rx_dma_flush(up);
-		return true;
+		return !serial8250_rx_dma_flush(up);
 	}
 	return up->dma->rx_dma(up);
 }

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer
  2026-10-21 22:37 ` [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
@ 2026-10-21 22:50   ` sashiko-bot
  2026-10-22 14:00   ` Andy Shevchenko
  2026-10-22 15:20   ` Raul E Rangel
  2 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-21 22:50 UTC (permalink / raw)
  To: James Hilliard; +Cc: linux-serial

> uart_flush_buffer() resets the transmit kfifo before calling the driver's
> flush callback. serial8250_tx_dma_flush() terminates DMA asynchronously and
> clears tx_running, allowing a new write to replace tx_size before the old
> completion callback has finished. That callback can then consume bytes
> from the new transmission. The serial core can also overwrite the old
> DMA source as soon as it reuses the reset kfifo.
> 
> Give DMA a private transmit buffer, keeping the kfifo offsets used by
> FIFO-window providers. Copy only the queued ranges before handing the
> buffer to the device. Flushing the kfifo can then no longer change an
> in-flight transfer's source.
> 
> Keep tx_running set while a worker synchronizes a successfully terminated
> channel. Suppress callbacks and new submissions during that interval.
> The DesignWare idle helpers must not pause or resume a retiring channel.
> [ ... ]
> The race was found by inspecting cancellation and restart while preparing
> H616 UART DMA support.
> 
> Fixes: a424a34b8fad ("serial: 8250: Fix TX deadlock when using DMA")
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-submit-serial-8250-dma-cancel-v1-v1-0-efe2b89f2303@gmail.com?part=1


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer
  2026-10-21 22:37 ` [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
@ 2026-10-21 23:05   ` sashiko-bot
  2026-10-22 15:20   ` Raul E Rangel
  1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-21 23:05 UTC (permalink / raw)
  To: James Hilliard; +Cc: linux-serial

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] serial: 8250: missing shutting_down check in serial8250_rx_dma_flush()
- [High] serial: 8250: rx dma flush fails on paused DMA_IN_PROGRESS and DMA_ERROR
--

commit d4e9133dca0ba3f456e79c0c4b6fad29f9996583
Author: James Hilliard <james.hilliard1@gmail.com>

serial: 8250: retire canceled RX DMA before reusing the receive buffer

This commit reserves the RX DMA buffer across asynchronous termination and
synchronizes in-flight completion callbacks in a workqueue worker before
allowing a new RX DMA transfer to reuse the buffer. It also checks pause and
status results during RX DMA flushes and serializes Intel MID timeout callers
with the UART port lock.

> diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c
> index d4b1e2a059eb1..821ccc7a3a54d 100644
> --- a/drivers/tty/serial/8250/8250_dma.c
> +++ b/drivers/tty/serial/8250/8250_dma.c
> @@ -77,31 +77,29 @@ void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
>  }
>  EXPORT_SYMBOL_GPL(serial8250_tx_dma_prepare_buffer);
>  
> -static void __dma_rx_complete(struct uart_8250_port *p)
> +static enum dma_status serial8250_rx_dma_complete(struct uart_8250_port *p)
>  {
>  	struct uart_8250_dma	*dma = p->dma;
>  	struct tty_port		*tty_port = &p->port.state->port;
>  	struct dma_tx_state	state;
>  	enum dma_status		dma_status;
> -	int			count;
> +	size_t			count;
>  
> -	/*
> -	 * New DMA Rx can be started during the completion handler before it
> -	 * could acquire port's lock and it might still be ongoing. Don't to
> -	 * anything in such case.
> -	 */
> +	/* Only inspect the buffer after completion or a successful pause. */
>  	dma_status = dmaengine_tx_status(dma->rxchan, dma->rx_cookie, &state);
> -	if (dma_status == DMA_IN_PROGRESS)
> -		return;
> +	if (dma_status != DMA_COMPLETE && dma_status != DMA_PAUSED)
> +		return dma_status;

[ ... ]

> +static void serial8250_rx_dma_flush_work(struct work_struct *work)
> +{
> +	struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
> +					       rx_flush_work);
> +	struct uart_8250_port *p = dma->port;
> +
> +	dmaengine_synchronize(dma->rxchan);
> +
> +	guard(uart_port_lock_irqsave)(&p->port);
> +	dma->rx_terminating = false;
> +	dma->rx_running = 0;
> +	if (!dma->shutting_down && (p->ier & UART_IER_RDI) &&
> +	    (serial_lsr_in(p) & UART_LSR_DR))
> +		dma->rx_dma(p);
> +}

[ ... ]

> -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;
> +	enum dma_status status;
> +	int ret;
>  
> -	if (!dma || !dma->rxchan || !dma->rx_running)
> -		return;
> +	lockdep_assert_held_once(&p->port.lock);
> +
> +	if (!dma || !dma->rxchan || !dma->rx_running || dma->rx_terminating)
> +		return 0;

[Severity: High]
Should serial8250_rx_dma_flush() also check dma->shutting_down before
proceeding, similar to serial8250_tx_dma_flush(), dma_rx_complete(), and
serial8250_rx_dma()?

During shutdown, serial8250_do_shutdown() in
drivers/tty/serial/8250/8250_port.c sets up->dma->shutting_down = true and
clears UART_IER under port->lock, calls synchronize_irq(port->irq), and then
calls serial8250_release_dma(up) while port->irq is still registered.

In drivers/tty/serial/8250/8250_dma.c, serial8250_release_dma() cancels
rx_flush_work before terminating dma->rxchan and clearing dma->rx_running:

drivers/tty/serial/8250/8250_dma.c:serial8250_release_dma() {
	...
	cancel_work_sync(&dma->tx_flush_work);
	cancel_work_sync(&dma->rx_flush_work);

	/* Release RX resources */
	dmaengine_terminate_sync(dma->rxchan);
	dma->rx_running = 0;
	dma_free_coherent(dmaengine_get_dma_device(dma->rxchan), dma->rx_size,
			  dma->rx_buf, dma->rx_addr);
	dma_release_channel(dma->rxchan);
	dma->rxchan = NULL;
	...
}

On Intel MID platforms, tng_handle_irq() and dnv_handle_irq() in
drivers/tty/serial/8250/8250_mid.c handle HSU DMA channel timeout interrupts
on shared port->irq, which are not masked by clearing UART_IER. While the RX
DMA transfer is still active prior to dmaengine_terminate_sync(dma->rxchan)
(or if a concurrent IRQ handler read UART_IIR just before
serial8250_do_shutdown() took port->lock), the handler can acquire port->lock
and call serial8250_rx_dma_flush(up) after cancel_work_sync(&dma->rx_flush_work)
has already returned.

Without a dma->shutting_down check, serial8250_rx_dma_flush() will push flip
buffer data after shutdown has started and call
schedule_work(&dma->rx_flush_work). When serial8250_rx_dma_flush_work()
subsequently runs, serial8250_release_dma() may have already released
dma->rxchan and set dma->rxchan = NULL, causing
dmaengine_synchronize(dma->rxchan) to dereference dma->rxchan == NULL or a
freed channel.

> +
> +	ret = dmaengine_pause(dma->rxchan);
> +	if (ret)
> +		return ret;
> +
> +	status = serial8250_rx_dma_complete(p);
> +	if (status != DMA_PAUSED && status != DMA_COMPLETE) {
> +		dmaengine_resume(dma->rxchan);
> +		return -EIO;
> +	}

[Severity: High]
Could rejecting statuses other than DMA_PAUSED and DMA_COMPLETE here and in
serial8250_rx_dma_complete() wedge RX DMA or cause an unhandled interrupt
storm on RX timeout?

First, several dmaengine providers that pass the capability checks in
serial8250_request_dma() (such as sun6i-dma in drivers/dma/sun6i-dma.c and
dw-axi-dmac in drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c, used with
8250_dw) pause the hardware channel in .device_pause and return 0, but their
.device_tx_status callbacks still return dma_cookie_status() (DMA_IN_PROGRESS)
rather than DMA_PAUSED. With this check, serial8250_rx_dma_flush() resumes
the channel and fails with -EIO on every RX timeout flush and in
dw8250_idle_enter().

Second, if an RX DMA transfer encounters a channel error (DMA_ERROR, for
example in drivers/dma/hsu/hsu.c or drivers/dma/idma64.c where .device_pause
only sets DMA_PAUSED when the descriptor is DMA_IN_PROGRESS), neither
dma_rx_complete() nor serial8250_rx_dma_flush() terminates the transfer or
clears dma->rx_running, leaving RX DMA permanently stuck.

Third, when serial8250_rx_dma_flush() returns an error on UART_IIR_RX_TIMEOUT,
UART_IIR_RDI, or UART_IIR_RLSI, handle_rx_dma() in
drivers/tty/serial/8250/8250_port.c now returns false, causing
serial8250_handle_irq_locked() to skip serial8250_rx_chars():

drivers/tty/serial/8250/8250_port.c:handle_rx_dma() {
	...
	case UART_IIR_RLSI:
	case UART_IIR_RX_TIMEOUT:
		return !serial8250_rx_dma_flush(up);
}

drivers/tty/serial/8250/8250_port.c:serial8250_handle_irq_locked() {
	...
	if (!up->dma || handle_rx_dma(up, iir))
		status = serial8250_rx_chars(up, status);
}

Because UART_IIR_RX_TIMEOUT is only cleared by reading UART_RX and the FIFO
level on a timeout is below the DMA burst watermark (and in
dw8250_handle_irq() in drivers/tty/serial/8250/8250_dw.c with
DW_UART_QUIRK_IS_DMA_FC, RZN1_UART_RDMACR has already been cleared to 0
before serial8250_handle_irq_locked() is called), returning from the ISR
without reading UART_RX via PIO leaves the level-triggered UART interrupt
asserted.

[ ... ]

>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021-submit-serial-8250-dma-cancel-v1-v1-0-efe2b89f2303@gmail.com?part=2

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer
  2026-10-21 22:37 ` [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
  2026-10-21 22:50   ` sashiko-bot
@ 2026-10-22 14:00   ` Andy Shevchenko
  2026-10-23  2:20     ` James Hilliard
  2026-10-22 15:20   ` Raul E Rangel
  2 siblings, 1 reply; 9+ messages in thread
From: Andy Shevchenko @ 2026-10-22 14:00 UTC (permalink / raw)
  To: James Hilliard
  Cc: Greg Kroah-Hartman, Jiri Slaby, Raul E Rangel, Ilpo Järvinen,
	Heikki Krogerus, linux-kernel, linux-serial

On Wed, Oct 21, 2026 at 04:37:43PM -0600, James Hilliard wrote:
> uart_flush_buffer() resets the transmit kfifo before calling the driver's
> flush callback. serial8250_tx_dma_flush() terminates DMA asynchronously and
> clears tx_running, allowing a new write to replace tx_size before the old
> completion callback has finished. That callback can then consume bytes
> from the new transmission. The serial core can also overwrite the old
> DMA source as soon as it reuses the reset kfifo.
> 
> Give DMA a private transmit buffer, keeping the kfifo offsets used by
> FIFO-window providers. Copy only the queued ranges before handing the
> buffer to the device. Flushing the kfifo can then no longer change an
> in-flight transfer's source.
> 
> Keep tx_running set while a worker synchronizes a successfully terminated
> channel. Suppress callbacks and new submissions during that interval.
> The DesignWare idle helpers must not pause or resume a retiring channel.
> Once synchronization finishes, process the zero-length completion under
> the port lock and start any data queued after the flush. Reuse the normal
> completion processing for OMAP too, including its delayed register
> restore and runtime-PM handling. Drain the worker before releasing DMA
> resources on shutdown.
> 
> Calculate the queued length before calling the platform preparation hook,
> so it sees the length of the transfer being prepared rather than the
> previous transfer's length.
> 
> If termination fails, leave the buffer owned by the transfer and let its
> normal completion release it. Do not submit another transfer before that
> completion. A DMA completion still does not mean that the UART transmitter
> is empty; the existing line-status checks remain in place.
> 
> The race was found by inspecting cancellation and restart while preparing
> H616 UART DMA support.

...

> +static void __dma_tx_complete(void *param)
> +{
> +	struct uart_8250_port *p = param;
> +	struct uart_8250_dma *dma = p->dma;
> +
> +	guard(uart_port_lock_irqsave)(&p->port);

Add a blank line after guard()() everywhere, otherwise it's harder to see it
in the code.

> +	if (!dma->shutting_down && !dma->tx_terminating)
> +		serial8250_tx_dma_complete(p);
>  }

...

> +static void serial8250_tx_dma_flush_work(struct work_struct *work)
> +{
> +	struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
> +					       tx_flush_work);

Make it a single line.

> +	struct uart_8250_port *p = dma->port;
> +
> +	/* Neither the channel nor its buffer may be reused before this returns. */
> +	dmaengine_synchronize(dma->txchan);
> +
> +	guard(uart_port_lock_irqsave)(&p->port);
> +	dma->tx_terminating = false;
> +	if (!dma->shutting_down)
> +		dma->tx_complete(p);
> +}
> +
> +void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
> +				    struct scatterlist *sgl, unsigned int nents)
> +{
> +	struct uart_8250_dma *dma = p->dma;
> +	struct scatterlist *sg;
> +	unsigned int i;
> +
> +	/* Preserve ring offsets for DMA engines which implement a FIFO window. */
> +	for_each_sg(sgl, sg, nents, i) {
> +		size_t offset = sg_dma_address(sg) - dma->tx_addr;
> +
> +		memcpy(dma->tx_buf + offset,
> +		       p->port.state->port.xmit_buf + offset, sg_dma_len(sg));
> +	}
> +	dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
> +				   UART_XMIT_SIZE, DMA_TO_DEVICE);
> +}

This is a bounce buffer, why do we need it? Shouldn't this be rather the
respective DMAengine issue on how to treat the pieces?

...

Overall I have a feeling that we turned somewhere in a wrong direction.
This all activity on 8250 DMA support looks like over engineered approach.

Perhaps we need to reconsider the whole picture?

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer
  2026-10-21 22:37 ` [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
  2026-10-21 22:50   ` sashiko-bot
  2026-10-22 14:00   ` Andy Shevchenko
@ 2026-10-22 15:20   ` Raul E Rangel
  2 siblings, 0 replies; 9+ messages in thread
From: Raul E Rangel @ 2026-10-22 15:20 UTC (permalink / raw)
  To: James Hilliard
  Cc: Greg Kroah-Hartman, Jiri Slaby, Ilpo Järvinen,
	Andy Shevchenko, Heikki Krogerus, linux-kernel, linux-serial

Hi James,

Thanks for looking into the asynchronous termination race with xmit_fifo
reuse. A few issues with this patch:

> +static void serial8250_tx_dma_flush_work(struct work_struct *work)
> +{
> +	struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
> +						 tx_flush_work);
> +	struct uart_8250_port *p = dma->port;
> +
> +	dmaengine_synchronize(dma->txchan);
> +
> +	guard(uart_port_lock_irqsave)(&p->port);
> +	dma->tx_terminating = false;
> +	if (!dma->shutting_down)
> +		dma->tx_complete(p);
> +}

1. Missing runtime PM reference in process context:
   `serial8250_tx_dma_flush_work()` runs asynchronously on `system_wq` and
   calls `dma->tx_complete(p)` (`serial8250_tx_dma_complete()` or
   `omap_8250_tx_dma_complete()`), which accesses UART registers
   (`UART_IER`, `RZN1_UART_TDMACR`, `UART_TX`, `omap8250_restore_regs()`)
   without calling `serial8250_rpm_get(p)` / `serial8250_rpm_put(p)`. If the
   port autosuspended before the work item ran, this will access unclocked
   registers. Also, on `UART_CAP_RPM` ports (such as `8250_omap`), if
   `dma->shutting_down` is true here, `dma->tx_complete(p)` is skipped and
   `serial8250_rpm_put_tx(p)` is never called, leaking `rpm_tx_active`.

2. `dmaengine_synchronize()` is a no-op on several 8250 DMA providers:
   `dmaengine_synchronize()` only waits if the provider implements
   `.device_synchronize`. Several DMA controllers used with 8250 — including
   `drivers/dma/sun6i-dma.c` (Allwinner H616, mentioned in your commit
   message), `drivers/dma/dw/core.c` (`dw_dmac`), `drivers/dma/pl330.c`, and
   `drivers/dma/mediatek/mtk-uart-apdma.c` — schedule callbacks from a
   tasklet (`virt-dma` `vc.task` or driver tasklet) but do not wire up
   `.device_synchronize`. On those controllers, `dmaengine_synchronize()`
   returns immediately while a completion tasklet from the old transfer can
   still be scheduled, so clearing `dma->tx_terminating = false` and starting
   a new transfer re-exposes the stale-callback race.

> +void serial8250_tx_dma_flush(struct uart_8250_port *p)
> +{
...
>  	dma->tx_size = 0;
> +	dma->tx_terminating = true;
> +	ret = dmaengine_terminate_async(dma->txchan);
> +	if (ret) {
> +		/* Keep the buffer owned by the transfer until its callback runs. */
> +		dma->tx_terminating = false;
> +		return;
> +	}

3. Zeroing `dma->tx_size = 0` before `dmaengine_terminate_async()`:
   If `dmaengine_terminate_async()` fails (`ret != 0`), the transfer
   continues to completion, but `dma->tx_size` has already been zeroed so
   `uart_xmit_advance()` in `tx_complete()` will not account for the
   transmitted bytes in `icount.tx`.

> +void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
> +				     struct scatterlist *sgl, unsigned int nents)
> +{
> +	struct uart_8250_dma *dma = p->dma;
> +	struct scatterlist *sg;
> +	unsigned int i;
> +
> +	for_each_sg(sgl, sg, nents, i) {
> +		size_t offset = sg_dma_address(sg) - dma->tx_addr;
> +
> +		memcpy(dma->tx_buf + offset,
> +		       p->port.state->port.xmit_buf + offset, sg_dma_len(sg));
> +	}
> +	dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
> +				   UART_XMIT_SIZE, DMA_TO_DEVICE);
> +}

4. Bounce buffer layout and DMA sync:
   - Both `serial8250_tx_dma()` and `omap_8250_tx_dma()` call
     `dmaengine_prep_slave_sg()` *before* `serial8250_tx_dma_prepare_buffer()`
     copies the bytes and calls `dma_sync_single_for_device()`. The copy and
     DMA sync should happen before preparing the descriptor.
   - Syncing the full `UART_XMIT_SIZE` (4 KB) on every transfer (even 1 byte)
     is unnecessary overhead, especially with swiotlb or non-coherent DMA.
   - If we are copying out of `xmit_fifo` into a dedicated `dma->tx_buf`
     anyway, why preserve the `kfifo` ring offsets (`sgl[2]`) instead of
     copying contiguously to `dma->tx_buf` at offset 0 and syncing only
     `dma->tx_size` bytes?

5. Please also consider splitting the Renesas RZ/N1 `serial8250_do_prepare_tx_dma()`
   `tx_size` ordering fix (`Fixes: 16b539ffb574`) and the `8250_omap`
   completion refactoring into separate preparatory patches rather than
   bundling them into a single `Fixes:` commit.

Thanks,
Raul

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer
  2026-10-21 22:37 ` [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
  2026-10-21 23:05   ` sashiko-bot
@ 2026-10-22 15:20   ` Raul E Rangel
  1 sibling, 0 replies; 9+ messages in thread
From: Raul E Rangel @ 2026-10-22 15:20 UTC (permalink / raw)
  To: James Hilliard
  Cc: Greg Kroah-Hartman, Jiri Slaby, Ilpo Järvinen,
	Andy Shevchenko, Heikki Krogerus, linux-kernel, linux-serial

Hi James,

Beyond the two issues Sashiko already flagged (`dma->shutting_down` not
checked in `serial8250_rx_dma_flush()`, and `DMA_IN_PROGRESS` rejected after
`dmaengine_pause()` leading to an unhandled `UART_IIR_RX_TIMEOUT` interrupt
storm when `!serial8250_rx_dma_flush(up)` returns `false` in `handle_rx_dma()`),
there are a few additional issues here:

> -static void __dma_rx_complete(struct uart_8250_port *p)
> +static enum dma_status serial8250_rx_dma_complete(struct uart_8250_port *p)
>  {
...
> -	ret = tty_insert_flip_string(tty_port, dma->rx_buf, count);
> -	p->port.icount.rx += ret;
> -	p->port.icount.buf_overrun += count - ret;
> +	count = dma->rx_size - state.residue - dma->rx_pos;
> +
> +	if (tty_insert_flip_string(tty_port, dma->rx_buf + dma->rx_pos,
> +				   count) != count)
> +		p->port.icount.buf_overrun++;
> +	p->port.icount.rx += count;
> +	dma->rx_pos += count;

1. Accidental revert of prerequisite patch [2]:
   This chunk reverts your prerequisite patch ("serial: 8250: account for
   partial receive DMA delivery to TTY") in `8250_dma.c`, restoring the old
   `icount.rx += count` and `icount.buf_overrun++` accounting.

> +static void serial8250_rx_dma_flush_work(struct work_struct *work)
> +{
> +	struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
> +						 rx_flush_work);
> +	struct uart_8250_port *p = dma->port;
> +
> +	dmaengine_synchronize(dma->rxchan);
> +
> +	guard(uart_port_lock_irqsave)(&p->port);
> +	dma->rx_terminating = false;
> +	dma->rx_running = 0;
> +	if (!dma->shutting_down && (p->ier & UART_IER_RDI) &&
> +	    (serial_lsr_in(p) & UART_LSR_DR))
> +		dma->rx_dma(p);
> +}

2. Spurious RX DMA restart during `dw8250_idle_enter()`:
   In `drivers/tty/serial/8250/8250_dw.c`, `dw8250_idle_enter()` sets
   `d->in_idle = 1` and writes `0` to hardware `UART_IER`, but intentionally
   leaves `UART_IER_RDI` set in `up->ier` so `dw8250_idle_exit()` can restore
   it. `dw8250_idle_enter()` then calls `serial8250_rx_dma_flush(up)`, which
   schedules `rx_flush_work`. If `rx_flush_work` runs while `d->in_idle == 1`
   (while the port is in loopback mode reconfiguring LCR/divisors),
   `p->ier & UART_IER_RDI` is still true and `rx_flush_work` will restart
   `dma->rx_dma(p)` in the middle of the idle sequence.

3. Missing runtime PM reference and `serial_lsr_in()` side effects in
   `serial8250_rx_dma_flush_work()`:
   - `serial8250_rx_dma_flush_work()` runs on `system_wq` and reads
     `serial_lsr_in(p)` / starts `dma->rx_dma(p)` without acquiring
     `serial8250_rpm_get(p)` / `serial8250_rpm_put(p)`. After the RX timeout
     ISR exits and drops its runtime PM reference, the port can autosuspend
     before `rx_flush_work` executes.
   - Reading `serial_lsr_in(p)` outside IRQ context clears any pending
     `UART_IIR_RLSI` in hardware and starts RX DMA before any saved LSR error
     bits (`OE`/`PE`/`FE`/`BI`) are processed with the corresponding byte in
     the FIFO.
   - If `UART_IIR_RDI` is enabled in hardware, why does `rx_flush_work` need
     to poll `UART_LSR_DR` and restart `rx_dma(p)` at all rather than letting
     the UART interrupt handler start RX DMA (or drain via PIO) when data is
     present?

4. `dmaengine_resume()` on error in `serial8250_rx_dma_flush()`:
   `serial8250_request_dma()` only checks `caps.cmd_pause` and
   `caps.cmd_terminate`, not `caps.cmd_resume` (e.g. `pl330` and
   `mtk-uart-apdma` implement pause to read residue before termination, but
   do not implement `.device_resume`). Also, on Renesas RZ/N1
   (`DW_UART_QUIRK_IS_DMA_FC`), `dw8250_handle_irq()` has already cleared
   `RZN1_UART_RDMACR = 0` before calling `serial8250_rx_dma_flush()`, so the
   error-recovery path leaves RX DMA disabled.

Thanks,
Raul

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer
  2026-10-22 14:00   ` Andy Shevchenko
@ 2026-10-23  2:20     ` James Hilliard
  0 siblings, 0 replies; 9+ messages in thread
From: James Hilliard @ 2026-10-23  2:20 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Greg Kroah-Hartman, Jiri Slaby, Raul E Rangel, Ilpo Järvinen,
	Heikki Krogerus, linux-kernel, linux-serial

On Thu, Oct 22, 2026 at 8:00 AM Andy Shevchenko
<andriy.shevchenko@linux.intel.com> wrote:
>
> On Wed, Oct 21, 2026 at 04:37:43PM -0600, James Hilliard wrote:
> > uart_flush_buffer() resets the transmit kfifo before calling the driver's
> > flush callback. serial8250_tx_dma_flush() terminates DMA asynchronously and
> > clears tx_running, allowing a new write to replace tx_size before the old
> > completion callback has finished. That callback can then consume bytes
> > from the new transmission. The serial core can also overwrite the old
> > DMA source as soon as it reuses the reset kfifo.
> >
> > Give DMA a private transmit buffer, keeping the kfifo offsets used by
> > FIFO-window providers. Copy only the queued ranges before handing the
> > buffer to the device. Flushing the kfifo can then no longer change an
> > in-flight transfer's source.
> >
> > Keep tx_running set while a worker synchronizes a successfully terminated
> > channel. Suppress callbacks and new submissions during that interval.
> > The DesignWare idle helpers must not pause or resume a retiring channel.
> > Once synchronization finishes, process the zero-length completion under
> > the port lock and start any data queued after the flush. Reuse the normal
> > completion processing for OMAP too, including its delayed register
> > restore and runtime-PM handling. Drain the worker before releasing DMA
> > resources on shutdown.
> >
> > Calculate the queued length before calling the platform preparation hook,
> > so it sees the length of the transfer being prepared rather than the
> > previous transfer's length.
> >
> > If termination fails, leave the buffer owned by the transfer and let its
> > normal completion release it. Do not submit another transfer before that
> > completion. A DMA completion still does not mean that the UART transmitter
> > is empty; the existing line-status checks remain in place.
> >
> > The race was found by inspecting cancellation and restart while preparing
> > H616 UART DMA support.
>
> ...
>
> > +static void __dma_tx_complete(void *param)
> > +{
> > +     struct uart_8250_port *p = param;
> > +     struct uart_8250_dma *dma = p->dma;
> > +
> > +     guard(uart_port_lock_irqsave)(&p->port);
>
> Add a blank line after guard()() everywhere, otherwise it's harder to see it
> in the code.
>
> > +     if (!dma->shutting_down && !dma->tx_terminating)
> > +             serial8250_tx_dma_complete(p);
> >  }
>
> ...
>
> > +static void serial8250_tx_dma_flush_work(struct work_struct *work)
> > +{
> > +     struct uart_8250_dma *dma = container_of(work, struct uart_8250_dma,
> > +                                            tx_flush_work);
>
> Make it a single line.
>
> > +     struct uart_8250_port *p = dma->port;
> > +
> > +     /* Neither the channel nor its buffer may be reused before this returns. */
> > +     dmaengine_synchronize(dma->txchan);
> > +
> > +     guard(uart_port_lock_irqsave)(&p->port);
> > +     dma->tx_terminating = false;
> > +     if (!dma->shutting_down)
> > +             dma->tx_complete(p);
> > +}
> > +
> > +void serial8250_tx_dma_prepare_buffer(struct uart_8250_port *p,
> > +                                 struct scatterlist *sgl, unsigned int nents)
> > +{
> > +     struct uart_8250_dma *dma = p->dma;
> > +     struct scatterlist *sg;
> > +     unsigned int i;
> > +
> > +     /* Preserve ring offsets for DMA engines which implement a FIFO window. */
> > +     for_each_sg(sgl, sg, nents, i) {
> > +             size_t offset = sg_dma_address(sg) - dma->tx_addr;
> > +
> > +             memcpy(dma->tx_buf + offset,
> > +                    p->port.state->port.xmit_buf + offset, sg_dma_len(sg));
> > +     }
> > +     dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr,
> > +                                UART_XMIT_SIZE, DMA_TO_DEVICE);
> > +}
>
> This is a bounce buffer, why do we need it? Shouldn't this be rather the
> respective DMAengine issue on how to treat the pieces?

The problem is ownership of the serial core's transmit storage across
flush. uart_flush_buffer() resets the FIFO before calling the driver,
and writers can reuse that storage after the callback returns.

dmaengine_terminate_async() does not guarantee that the transfer or its
completion callback has finished when it returns. The driver's flush
callback holds the port spinlock, so it cannot wait there using
dmaengine_synchronize(). This lifetime gap therefore exists even with
a provider implementing the DMAengine contract correctly.

The bounce buffer in v1 protected the transfer's source from those new
writes, but it added an allocation and copying to normal transmission.

> ...
>
> Overall I have a feeling that we turned somewhere in a wrong direction.
> This all activity on 8250 DMA support looks like over engineered approach.
>
> Perhaps we need to reconsider the whole picture?

I've reworked TX in v2[0] around a serial-core storage reservation instead.
The logical FIFO is still flushed immediately, but the driver can reserve
its storage until DMA and the old callbacks have retired. During that
interval write_room() reports zero, and write()/put_char() accept no data.

Normal transmission continues to DMA directly from the FIFO. A worker
performs synchronization outside the port lock, returns the mapping to
CPU ownership and releases the reservation. This removes the private TX
buffer and copying while retaining the required lifetime protection.

[0] https://lore.kernel.org/r/20261022-submit-serial-8250-dma-cancel-v1-v2-0-088ea51e3891@gmail.com/

>
> --
> With Best Regards,
> Andy Shevchenko
>
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-10-23  2:20 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-21 22:37 [PATCH 0/2] serial: 8250: serialize DMA cancellation with buffer reuse James Hilliard
2026-10-21 22:37 ` [PATCH 1/2] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
2026-10-21 22:50   ` sashiko-bot
2026-10-22 14:00   ` Andy Shevchenko
2026-10-23  2:20     ` James Hilliard
2026-10-22 15:20   ` Raul E Rangel
2026-10-21 22:37 ` [PATCH 2/2] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
2026-10-21 23:05   ` sashiko-bot
2026-10-22 15:20   ` Raul E Rangel

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.