From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.161.51 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793661287; b=ZRNLnK60mMQiSYF/2D/WmbG2bkLLcfde7zuAzJqHPZrmkJ5iXQl7sVId5FY2+qGKyKbJ CYfV87IC19HmqVJUOaMoHfyJg0N8f77DSrpcGwdIUBQexYTe65Vay4V8Pkd6sRceMCbLr amIM0TT5hvEl/2ZasgeAjgkEBhLNS1Tt57NCZmYbjbNQ8rBfC4KYsujqNyOEANT2tWQRk NRW5zq8M7sx4pdS826/JDaWhlzjxUmThpCeUu52RgyH3jeNNOYlK2CSRQjmhPWAwktiSW USh2R2noIqE5aZd4qm/cAMcvqUze6vjDE+WrbSXoUi87L/tUKYrTs+HFwpHsZPTdswA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793661287; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Gm-Message-State:X-Gm-Gg:X-Received:Received:From:Date:Subject: MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id: References:In-Reply-To:To:Cc:X-Mailer; bh=nXgXZUSLPJ2SYhx2+7LVQLzHvdnsFJZM3SR6gpGR+IU=; b=3DX77bF8p9xDIWfW4Q/uob+Hp8mUs3n+sutUeHNoKaJRfKbyDvjNJLlFugmc+2QK0JBJ yyX1hmtuKEE/wxeWZJzCY1cL7SsQJihlGZfZ09SDSbPKkZLDxwoxIxrtSyZVCT8+aRhST PHUuegCmfwMkPB62J9nih528i+iw535tBwMmyQUwG5x7U94w2CMgAiXZ/q1s7KsI1P29f /3EMpCN0og2dpyROfLe8+lr6Z0YfTvUiwqgRWPpHM2NMmayT+7i5bDT7YwZHfB9svI1NH fgVBMX0HQT7na5QVWnnsaJpeBiLZD2QlH83rhZfXPnX1AQ7HBle6oseNJsFK8NrNyQw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=PC1Bs1Ta; arc=none smtp.remote-ip=209.85.161.51 Received: from mail-oo1-f51.google.com (mail-oo1-f51.google.com [209.85.161.51]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5D24F6082BF6 for ; Mon, 2 Nov 2026 23:14:46 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 5D24F6082BF6 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=PC1Bs1Ta Received: by mail-oo1-f51.google.com with SMTP id 006d021491bc7-6df0b02fe91so97022eaf.0 for ; Mon, 02 Nov 2026 15:14:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793661285; x=1794266085; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=nXgXZUSLPJ2SYhx2+7LVQLzHvdnsFJZM3SR6gpGR+IU=; b=PC1Bs1TaqUERCYB+ym7JA8cbC+sZOF8cbI9aBEm5bjIMBh+HEjj5eB3gY1YcFqoDLt hWL4CEcgNjDt5iBtBDeIzTchG+V2QW5nQuoO/KMpaQOEF8txNBO7mmcoPbnSegDDTIy0 IGoJ49No1COhzuw9Nj96gMfAkND6gbHrO3yylEO4IucR+thdbi+0BI8Mif0ZCUfIs8q+ IHHOPJwfK6IX2VSxnyPBRGmBx4ul5+8d7oorHr4NDG2cLr6hthHWIfBvt0jZYX0bMB6I pYwMR0PXPikh56nY71mOfeGjQ4ZCd6aaXtZemm+DrlZcZudYb/wUvjAqpNtMFmLNbE7K M5hg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793661285; x=1794266085; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=nXgXZUSLPJ2SYhx2+7LVQLzHvdnsFJZM3SR6gpGR+IU=; b=WY3NZUjhETwQOe+TK9isjbPWoVZg213/2UufUJ1wcsh2YbZXSVPaHO3kTn9I/9jh8D Nznu729hK8fKqanWQitH7dpNSQVO/Ti6gv9oPWOgah2NlTc7ygRqg7n718cajNc1lBpO dWtgBjQW4X9MfkxwM1r5W3lF64f3CGV3H2sIlfX4b8GEIolFE/mrWB0Pnjj4hYd8JOe8 3X9eyD8nbuFH17NBlzaipnuSD4DMY3tn3hreaMAAXKLojzAwwgEoGBOli6tB7nbbnv4f kwPLmI1wIPwRKo4OIrv7mlcz/zJrt9esLBin+rgxaXZ5IUed6DZnDIGhaQ/Ucnlka0t0 dK+w== X-Gm-Message-State: AFq9FYKh65ASSt1rlt1wZvTV/zwVE64+/ZbVuP53o3tI8+rAte2v9Z7q YVeQ4FL9uCk17WZih1U5E6jVqYhgnOdhCAvdXBqChAQRb41drky5O3dE X-Gm-Gg: AYBFou2muYXs8VcjFqyLpqhP8r5j7kBfxmyaw/0jNNFkfdQT9GsKNYEVi5t5vk66ST6 MdEcSpKG3wn4qVQIoNmb+3W0K8S1Vq6gQJMcPkM19cmRuMK/Xvsq2zkfWzVlvhWXnqg0GD7GCg4 aDSmqUCzGh4RR3s//OMhZKAAeSF7YXblx7emdIch8eXU42vurmamiXyMSr9719xwVU66rYMLx5p 54SXAEl9v8KXaBclNr/623t0cdp1aMu3KtcaTKxhpO/k+7R7dXa+7oz2chyhvbpbtuF9N0Nog03 XxWR9+eDDYbDrqLHZ1iwJCXcvfnUq3KvBeL4gTI15GQ/MMJI1k4ZXRsJ4Ge0o1wwn20wFwhp8hR bIJvTfKJIFNKCRwnrZyVXtFhU/Ur7MMTwqj0RQEGrWImnFTVaWDAgomrkUC3l40+xpDT3LiBB+U n+Us7xF+lCQcnSDQXHaTIODowqC6Q0ViYCZKvQrXdWYFvOpmCDNr1AP0WPvGjqEK0s0ejZIOIxD 4zz4+0yV7sJH4sq53NNqjzRCi6rDs5xJLaxjaK16tYVW3Wzoh+sxGkaBMj2Cj/bax7F+lvK6soR s40BXrPUio6zB2jzvNqnnBQmITO7czZKrSxvnkT6EBFMemkOk9851wSu+Xlp2eIwA+Gjw7IaWw0 x/0JBiJABjjDTkXH9eKXnsJh3+yGcEk4AqrgALC1WV53cDVXTHonCwCUhvscCq7TvEWXv6wJOEo ncD65b5ec2XKcETb+P7+jn8GTP6Jj5EIkj2PZPRls4mckUb0Wevt5a X-Received: by 2002:a05:6820:c2cc:10b0:709:a86c:7c68 with SMTP id 006d021491bc7-714f2a53011mr3369727eaf.31.1793661285222; Mon, 02 Nov 2026 15:14:45 -0800 (PST) Received: from [127.0.1.1] (174-29-1-49.hlrn.qwest.net. [174.29.1.49]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-714f2ce869asm10134463eaf.4.2026.11.02.15.14.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Nov 2026 15:14:44 -0800 (PST) From: James Hilliard Date: Mon, 02 Nov 2026 16:14:32 -0700 Subject: [PATCH v9 5/8] serial: core: allow drivers to reserve flushed transmit storage X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261102-submit-serial-8250-dma-cancel-v1-v9-5-9159caade148@gmail.com> References: <20261102-submit-serial-8250-dma-cancel-v1-v9-0-9159caade148@gmail.com> In-Reply-To: <20261102-submit-serial-8250-dma-cancel-v1-v9-0-9159caade148@gmail.com> To: Greg Kroah-Hartman , Jiri Slaby , Raul E Rangel , =?utf-8?q?Ilpo_J=C3=A4rvinen?= , Andy Shevchenko , Heikki Krogerus , Tony Lindgren , Sebastian Andrzej Siewior , Richard Genoud , Nicolas Ferre , Alexandre Belloni , Claudiu Beznea , Jacky Huang , Shan-Chun Hung , =?utf-8?q?Pali_Roh=C3=A1r?= , Frank Li , Sascha Hauer , Pengutronix Kernel Team , Fabio Estevam , "Maciej W. Rozycki" , Timur Tabi , Peter Ujfalusi , Vignesh Raghavendra , Patrice Chotard Cc: linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org, linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev, linuxppc-dev@lists.ozlabs.org, James Hilliard X-Mailer: b4 0.15.2 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's write-ready notification under the port lock so it cannot race another reservation. Wake tty->write_wait after flushing even when writes remain blocked. tty_wait_until_sent() waits for the logical FIFO to empty, independently of whether its storage can be reused. Notify these drain waiters without invoking the line discipline's write_wakeup() callback, so a reservation does not prevent a completed logical flush from waking a drain waiter or consume a one-shot write-ready notification prematurely. 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. Track port readiness separately from DMA ownership. Retirement releases the reservation even during startup or shutdown, while writes and wakeups stay gated until successful startup or resume. Reopening or restarting a port wakes deferred writers with either a new or retained FIFO. Restart queued output when reusing a FIFO after suspension. Keep its write-ready notification pending until the retained output drains below the normal wakeup threshold. Publish resume readiness under the same port lock as the TX restart, without clearing any reservation. A concurrent flush can then reserve a new transfer without resume releasing its storage prematurely. Assert the port-lock requirement at uart_write_wakeup(), alongside the reservation helpers, so callers cannot inspect readiness or release a one-shot notification concurrently with a reservation change. Signed-off-by: James Hilliard --- drivers/tty/serial/serial_core.c | 106 +++++++++++++++++++++++++++++++++++---- include/linux/serial_core.h | 18 ++++++- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index c0deec0e6af4..baf786d5c34f 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -100,6 +100,11 @@ static inline struct uart_port *uart_port_check(struct uart_state *state) return state->uart_port; } +static bool uart_xmit_writable(const struct uart_state *state) +{ + return state->xmit_ready && !state->xmit_blocked; +} + /** * uart_write_wakeup - schedule write processing * @port: port to be processed @@ -114,15 +119,79 @@ static inline struct uart_port *uart_port_check(struct uart_state *state) void uart_write_wakeup(struct uart_port *port) { struct uart_state *state = port->state; + + lockdep_assert_held_once(&port->lock); + /* * This means you called this function _after_ the port was * closed. No cookie for you. */ BUG_ON(!state); + if (!uart_xmit_writable(state)) + return; tty_port_tty_wakeup(&state->port); } EXPORT_SYMBOL(uart_write_wakeup); +/* Publish readiness without releasing any transfer's storage reservation. */ +static void uart_xmit_enable(struct uart_port *port) +{ + struct uart_state *state = port->state; + + lockdep_assert_held_once(&port->lock); + if (state->xmit_ready) + return; + state->xmit_ready = true; + /* Retained output must drain before consuming a write-ready notification. */ + if (kfifo_len(&state->port.xmit_fifo) < WAKEUP_CHARS) + uart_write_wakeup(port); +} + +/** + * 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. + * Line-discipline write wakeups are deferred until the reservation is released. + * + * The driver must unblock after retiring the transfer, including on shutdown. + * Serial core separately gates writes and wakeups until startup or resume + * has made the port ready. + * 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. + * Retirement is recorded even while the port is inactive. Writes and wakeups + * remain deferred until startup or resume has made the port ready. + * + * Context: @port->lock held. + */ +void uart_xmit_unblock(struct uart_port *port) +{ + lockdep_assert_held_once(&port->lock); + if (!port->state->xmit_blocked) + 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; @@ -141,7 +210,10 @@ static void __uart_start(struct uart_state *state) struct serial_port_device *port_dev; int err; - if (!port || port->flags & UPF_DEAD || uart_tx_stopped(port)) + if (!port || port->flags & UPF_DEAD || uart_tx_stopped(port) || !state->xmit_ready) + return; + /* High-priority flow-control characters have their own storage. */ + if (state->xmit_blocked && !port->x_char) return; port_dev = port->port_dev; @@ -375,6 +447,11 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state, return -EIO; serial_base_port_startup(uport); + scoped_guard(uart_port_lock_irq, uport) { + uart_xmit_enable(uport); + if (!kfifo_is_empty(&port->xmit_fifo) || uport->x_char) + __uart_start(state); + } return 0; } @@ -397,8 +474,11 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state) if (tty) set_bit(TTY_IO_ERROR, &tty->flags); - if (uport) + if (uport) { + scoped_guard(uart_port_lock_irq, uport) + state->xmit_ready = false; serial_base_port_shutdown(uport); + } if (tty_port_initialized(port)) { tty_port_set_initialized(port, false); @@ -593,7 +673,7 @@ static int uart_put_char(struct tty_struct *tty, u8 c) return 0; } - if (port) + if (port && uart_xmit_writable(state)) ret = kfifo_put(&state->port.xmit_fifo, c); uart_port_unlock_deref(port, flags); return ret; @@ -624,7 +704,7 @@ static ssize_t uart_write(struct tty_struct *tty, const u8 *buf, size_t count) return 0; } - if (port) + if (port && uart_xmit_writable(state)) ret = kfifo_in(&state->port.xmit_fifo, buf, count); __uart_start(state); @@ -640,7 +720,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 || !uart_xmit_writable(state)) ret = 0; else ret = kfifo_avail(&state->port.xmit_fifo); @@ -682,8 +762,12 @@ 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); + + /* Drain waiters observe FIFO emptiness, not write readiness. */ + wake_up_interruptible(&tty->write_wait); } /* @@ -1748,8 +1832,10 @@ static void uart_tty_port_shutdown(struct tty_port *port) if (WARN(!uport, "detached port still initialized!\n")) return; - scoped_guard(uart_port_lock_irq, uport) + scoped_guard(uart_port_lock_irq, uport) { + state->xmit_ready = false; uport->ops->stop_rx(uport); + } serial_base_port_shutdown(uport); uart_port_shutdown(port); @@ -2391,9 +2477,10 @@ int uart_suspend_port(struct uart_driver *drv, struct uart_port *uport) unsigned int mctrl; tty_port_set_suspended(port, true); - tty_port_set_initialized(port, false); scoped_guard(uart_port_lock_irq, uport) { + state->xmit_ready = false; + tty_port_set_initialized(port, false); ops->stop_tx(uport); if (!(uport->rs485.flags & SER_RS485_ENABLED)) ops->set_mctrl(uport, 0); @@ -2498,8 +2585,9 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport) if (!(uport->rs485.flags & SER_RS485_ENABLED)) ops->set_mctrl(uport, uport->mctrl); ops->start_tx(uport); + tty_port_set_initialized(port, true); + uart_xmit_enable(uport); } - tty_port_set_initialized(port, true); } else { /* * Failed to resume - maybe hardware went away? diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 4eeb8253752a..41598a38a408 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 and release the reservation before returning, + * since serial core then frees the transmit buffer. Serial core separately + * gates writes and wakeups until startup or resume has made the port ready. * * Locking: @port->lock taken. * Interrupts: locally disabled. @@ -834,6 +843,9 @@ enum uart_pm_state { */ struct uart_state { struct tty_port port; + /* TX readiness and storage ownership; protected by the UART port lock. */ + bool xmit_ready; + bool xmit_blocked; enum uart_pm_state pm_state; @@ -909,6 +921,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