From: James Hilliard <james.hilliard1@gmail.com>
To: "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Jiri Slaby" <jirislaby@kernel.org>,
"Raul E Rangel" <rrangel@chromium.org>,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Andy Shevchenko" <andriy.shevchenko@linux.intel.com>,
"Heikki Krogerus" <heikki.krogerus@linux.intel.com>,
"Tony Lindgren" <tony@atomide.com>,
"Sebastian Andrzej Siewior" <bigeasy@linutronix.de>
Cc: linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
James Hilliard <james.hilliard1@gmail.com>
Subject: [PATCH v4 2/4] serial: core: allow drivers to reserve flushed transmit storage
Date: Fri, 23 Oct 2026 16:41:07 -0600 [thread overview]
Message-ID: <20261023-submit-serial-8250-dma-cancel-v1-v4-2-e759de1839de@gmail.com> (raw)
In-Reply-To: <20261023-submit-serial-8250-dma-cancel-v1-v4-0-e759de1839de@gmail.com>
uart_flush_buffer() resets the transmit FIFO before calling the driver's
flush callback. An asynchronous DMA transfer can still access that storage
after the callback returns, while a new write can immediately reuse it.
Add uart_xmit_block() and uart_xmit_unblock() so a driver can reserve the
flushed storage until its transfer and completion callbacks have retired.
Serialize the reservation with FIFO producers using the port lock.
While the storage is reserved, write_room() reports no space and write()
and put_char() accept no data. Defer write-ready notifications until the
reservation is released, preserving one-shot TTY wakeups. Keep the flush
wakeup under the port lock so it cannot race another reservation.
Suppress ordinary TX starts while the FIFO storage is blocked. Preserve
high-priority flow-control characters, which use separate storage.
Releasing the reservation wakes writers through the existing TTY wakeup
path. This preserves immediate logical flushing while applying write
backpressure until the storage is reusable.
Keep the reservation while the port is uninitialized so completion cannot
wake writers into shutdown. Leave it in place when the driver releases DMA
resources. Serial core clears it when allocating the FIFO for a new open,
or after a successful resume has restored the port. Failed resume keeps
writers blocked.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/tty/serial/serial_core.c | 61 +++++++++++++++++++++++++++++++++++++---
include/linux/serial_core.h | 17 +++++++++--
2 files changed, 72 insertions(+), 6 deletions(-)
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index c0deec0e6af4..e5756934b8f4 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -119,10 +119,56 @@ void uart_write_wakeup(struct uart_port *port)
* closed. No cookie for you.
*/
BUG_ON(!state);
+ if (state->xmit_blocked)
+ return;
tty_port_tty_wakeup(&state->port);
}
EXPORT_SYMBOL(uart_write_wakeup);
+/**
+ * uart_xmit_block - reserve flushed transmit storage
+ * @port: port whose transmit storage is still in use
+ *
+ * A driver may call this from ->flush_buffer() if DMA still owns the old
+ * transmit data. The logical FIFO has already been emptied, but writers
+ * must not overwrite its storage until uart_xmit_unblock() is called.
+ * write_room() reports zero and write()/put_char() accept no data meanwhile.
+ * Write wakeups are deferred until the reservation is released.
+ *
+ * The driver must unblock after retiring the transfer while the port is open.
+ * Shutdown must retire the transfer; serial core releases any reservation
+ * when the port is reopened or resumed.
+ * Repeated calls are idempotent, not nested reservations.
+ *
+ * Context: @port->lock held.
+ */
+void uart_xmit_block(struct uart_port *port)
+{
+ lockdep_assert_held_once(&port->lock);
+ port->state->xmit_blocked = true;
+}
+EXPORT_SYMBOL_GPL(uart_xmit_block);
+
+/**
+ * uart_xmit_unblock - release reserved transmit storage and wake writers
+ * @port: port whose storage is available again
+ *
+ * The old transfer must no longer access the FIFO storage, and any required
+ * DMA synchronization must have completed before this call.
+ * If shutdown has started, leave the storage blocked until reopen or resume.
+ *
+ * Context: @port->lock held.
+ */
+void uart_xmit_unblock(struct uart_port *port)
+{
+ lockdep_assert_held_once(&port->lock);
+ if (!port->state->xmit_blocked || !tty_port_initialized(&port->state->port))
+ return;
+ port->state->xmit_blocked = false;
+ uart_write_wakeup(port);
+}
+EXPORT_SYMBOL_GPL(uart_xmit_unblock);
+
static void uart_stop(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
@@ -143,6 +189,9 @@ static void __uart_start(struct uart_state *state)
if (!port || port->flags & UPF_DEAD || uart_tx_stopped(port))
return;
+ /* High-priority flow-control characters have their own storage. */
+ if (state->xmit_blocked && !port->x_char)
+ return;
port_dev = port->port_dev;
@@ -260,6 +309,7 @@ static int uart_alloc_xmit_buf(struct tty_port *port)
uport = uart_port_ref_lock(state, &flags);
if (!state->port.xmit_buf) {
state->port.xmit_buf = buf;
+ state->xmit_blocked = false;
kfifo_init(&state->port.xmit_fifo, state->port.xmit_buf,
PAGE_SIZE);
uart_port_unlock_deref(uport, flags);
@@ -593,7 +643,7 @@ static int uart_put_char(struct tty_struct *tty, u8 c)
return 0;
}
- if (port)
+ if (port && !state->xmit_blocked)
ret = kfifo_put(&state->port.xmit_fifo, c);
uart_port_unlock_deref(port, flags);
return ret;
@@ -624,7 +674,7 @@ static ssize_t uart_write(struct tty_struct *tty, const u8 *buf, size_t count)
return 0;
}
- if (port)
+ if (port && !state->xmit_blocked)
ret = kfifo_in(&state->port.xmit_fifo, buf, count);
__uart_start(state);
@@ -640,7 +690,7 @@ static unsigned int uart_write_room(struct tty_struct *tty)
unsigned int ret;
port = uart_port_ref_lock(state, &flags);
- if (!state->port.xmit_buf)
+ if (!state->port.xmit_buf || state->xmit_blocked)
ret = 0;
else
ret = kfifo_avail(&state->port.xmit_fifo);
@@ -682,8 +732,9 @@ static void uart_flush_buffer(struct tty_struct *tty)
kfifo_reset(&state->port.xmit_fifo);
if (port->ops->flush_buffer)
port->ops->flush_buffer(port);
+ /* Serialize the wakeup with reservation changes, including another flush. */
+ uart_write_wakeup(port);
uart_port_unlock_deref(port, flags);
- tty_port_tty_wakeup(&state->port);
}
/*
@@ -2500,6 +2551,8 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport)
ops->start_tx(uport);
}
tty_port_set_initialized(port, true);
+ scoped_guard(uart_port_lock_irq, uport)
+ uart_xmit_unblock(uport);
} else {
/*
* Failed to resume - maybe hardware went away?
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 4eeb8253752a..9b6f118d4db3 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -208,8 +208,17 @@ struct gpio_desc;
* Flush any write buffers, reset any DMA state and stop any ongoing DMA
* transfers.
*
- * This will be called whenever the @port->state->xmit circular buffer is
- * cleared.
+ * This is called after the transmit FIFO has been cleared, with @port->lock
+ * still held. A driver whose asynchronous transfer can still access the
+ * flushed storage must call uart_xmit_block() before returning. This keeps
+ * write_room() at zero and prevents write() and put_char() from reusing the
+ * storage until the driver calls uart_xmit_unblock().
+ *
+ * Release the reservation only after the transfer and its callbacks have
+ * retired and any required DMA synchronization has completed. Shutdown must
+ * also retire the transfer before returning, since serial core then frees
+ * the transmit buffer. Leave the reservation in place during shutdown;
+ * serial core releases it when the port is reopened or resumed.
*
* Locking: @port->lock taken.
* Interrupts: locally disabled.
@@ -834,6 +843,8 @@ enum uart_pm_state {
*/
struct uart_state {
struct tty_port port;
+ /* Flushed TX storage is reserved; protected by the UART port lock. */
+ bool xmit_blocked;
enum uart_pm_state pm_state;
@@ -909,6 +920,8 @@ struct uart_driver {
};
void uart_write_wakeup(struct uart_port *port);
+void uart_xmit_block(struct uart_port *port);
+void uart_xmit_unblock(struct uart_port *port);
/**
* enum UART_TX_FLAGS -- flags for uart_port_tx_flags()
--
2.53.0
next prev parent reply other threads:[~2026-10-23 22:41 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 22:41 [PATCH v4 0/4] serial: 8250: serialize DMA cancellation with buffer reuse James Hilliard
2026-10-23 22:41 ` [PATCH v4 1/4] serial: 8250_omap: separate locked TX DMA completion processing James Hilliard
2026-10-23 22:41 ` James Hilliard [this message]
2026-10-23 22:41 ` [PATCH v4 3/4] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
2026-10-23 22:41 ` [PATCH v4 4/4] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
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=20261023-submit-serial-8250-dma-cancel-v1-v4-2-e759de1839de@gmail.com \
--to=james.hilliard1@gmail.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=bigeasy@linutronix.de \
--cc=gregkh@linuxfoundation.org \
--cc=heikki.krogerus@linux.intel.com \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=jirislaby@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=rrangel@chromium.org \
--cc=tony@atomide.com \
/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
all inboxes | Powered by JetHome®