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>,
"Richard Genoud" <richard.genoud@bootlin.com>,
"Nicolas Ferre" <nicolas.ferre@microchip.com>,
"Alexandre Belloni" <alexandre.belloni@bootlin.com>,
"Claudiu Beznea" <claudiu.beznea@tuxon.dev>,
"Jacky Huang" <ychuang3@nuvoton.com>,
"Shan-Chun Hung" <schung@nuvoton.com>,
"Pali Rohár" <pali@kernel.org>, "Frank Li" <Frank.Li@nxp.com>,
"Sascha Hauer" <s.hauer@pengutronix.de>,
"Pengutronix Kernel Team" <kernel@pengutronix.de>,
"Fabio Estevam" <festevam@gmail.com>,
"Maciej W. Rozycki" <macro@orcam.me.uk>,
"Timur Tabi" <timur@kernel.org>
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 <james.hilliard1@gmail.com>
Subject: [PATCH v8 2/6] serial: serialize asynchronous write wakeups with the port lock
Date: Thu, 29 Oct 2026 01:36:58 -0600 [thread overview]
Message-ID: <20261029-submit-serial-8250-dma-cancel-v1-v8-2-3fd56ba352ed@gmail.com> (raw)
In-Reply-To: <20261029-submit-serial-8250-dma-cancel-v1-v8-0-3fd56ba352ed@gmail.com>
uart_write_wakeup() expects the UART port lock to be held. Several worker,
DMA completion and interrupt paths call it without that lock, including
indirect calls from uart_handle_cts_change() and the transmit helpers.
Take the port lock around these notifications or their transmit helper.
Keep sleeping SPI and regmap accesses outside the locked region, and keep
the FSL LPUART completion lock held through its wakeup. Acquire the port
lock after releasing private channel locks to preserve the ordering used
by UART callbacks. In the MUX timer, publish available FIFO space under
the port lock and keep the hardware FIFO-drain wait outside it.
Use IRQ-safe locking in the Atmel RX and TX tasklets and SB1250 termios
path, since their interrupt handlers now acquire the same port lock.
Preserve the caller's interrupt state when releasing those locks.
On SB1250, lock modem-state updates after reading and filtering the change
status, and wake modem-status waiters after unlocking. Take the TX lock
inside its service routine so RX-only interrupts need no acquisition.
Keep that lock across x_char handling, FIFO consumption and the decision
to mask TX interrupts: a writer must not enqueue data and enable TX between
the empty-FIFO check and the interrupt-mask write. Each TX invocation sends
at most one character.
Serialize CLPS711x TX servicing and its interrupt-enable state with the
port lock. Disable TX interrupts without waiting in stop_tx(), since a
threaded handler may need that same lock. Have an already queued handler
leave stopped output untouched, and drain it outside the lock during
shutdown before the transmit FIFO can be released.
SCCNXP handles interrupts under a controller lock which its UART callbacks
acquire with the port lock already held. Collect the ports needing wakeups
in an unsigned long bitmap while servicing the controller, then release
that lock before visiting the set bits, taking each port lock and
rechecking the wakeup threshold. Removal drains the IRQ or timer before
unregistering the ports.
This serializes write notifications with port-locked serial-core state
changes, allowing readiness and transmit-storage reservations to gate the
notification without a check-versus-shutdown race.
Cover DZ TX servicing and JSM Classic modem notifications as well. On
DECstation ZS, acquire the port lock before the SCC lock and notify writers
after releasing the SCC lock. Defer normal TX restart after console output
to a worker, since console output can already run under the port lock.
Check transmitter readiness before consuming another character and drain
the restart worker during shutdown and removal. Release Lantiq's hardware
lock through the transmit helper's completion hook before notifying the
line discipline.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/tty/serial/21285.c | 2 +
drivers/tty/serial/atmel_serial.c | 12 ++---
drivers/tty/serial/clps711x.c | 23 +++++++---
drivers/tty/serial/cpm_uart.c | 2 +
drivers/tty/serial/dz.c | 9 ++--
drivers/tty/serial/fsl_lpuart.c | 2 +-
drivers/tty/serial/jsm/jsm_cls.c | 6 ++-
drivers/tty/serial/jsm/jsm_neo.c | 9 ++--
drivers/tty/serial/lantiq.c | 12 +++--
drivers/tty/serial/ma35d1_serial.c | 6 ++-
drivers/tty/serial/max3100.c | 6 ++-
drivers/tty/serial/max310x.c | 9 ++--
drivers/tty/serial/mux.c | 13 ++++--
drivers/tty/serial/mvebu-uart.c | 2 +
drivers/tty/serial/mxs-auart.c | 2 +
drivers/tty/serial/sb1250-duart.c | 25 ++++++----
drivers/tty/serial/sccnxp.c | 45 +++++++++++++-----
drivers/tty/serial/ucc_uart.c | 6 ++-
drivers/tty/serial/zs.c | 94 ++++++++++++++++++++++++++++----------
drivers/tty/serial/zs.h | 3 ++
20 files changed, 200 insertions(+), 88 deletions(-)
diff --git a/drivers/tty/serial/21285.c b/drivers/tty/serial/21285.c
index 4de0c975ebdc..e4149aea4f66 100644
--- a/drivers/tty/serial/21285.c
+++ b/drivers/tty/serial/21285.c
@@ -157,6 +157,8 @@ static irqreturn_t serial21285_tx_chars(int irq, void *dev_id)
struct uart_port *port = dev_id;
u8 ch;
+ guard(uart_port_lock_irqsave)(port);
+
uart_port_tx_limited(port, ch, 256,
!(*CSR_UARTFLG & 0x20),
*CSR_UARTDR = ch,
diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c
index c3880259e3bc..0433bc70f279 100644
--- a/drivers/tty/serial/atmel_serial.c
+++ b/drivers/tty/serial/atmel_serial.c
@@ -1371,6 +1371,8 @@ atmel_handle_status(struct uart_port *port, unsigned int pending,
if (status_change & (ATMEL_US_RI | ATMEL_US_DSR
| ATMEL_US_DCD | ATMEL_US_CTS)) {
+ guard(uart_port_lock_irqsave)(port);
+
/* TODO: All reads to CSR will clear these interrupts! */
if (status_change & ATMEL_US_RI)
port->icount.rng++;
@@ -1702,10 +1704,9 @@ static void atmel_tasklet_rx_func(struct tasklet_struct *t)
tasklet_rx);
struct uart_port *port = &atmel_port->uart;
- /* The interrupt handler does not take the lock */
- uart_port_lock(port);
+ guard(uart_port_lock_irqsave)(port);
+
atmel_port->schedule_rx(port);
- uart_port_unlock(port);
}
static void atmel_tasklet_tx_func(struct tasklet_struct *t)
@@ -1714,10 +1715,9 @@ static void atmel_tasklet_tx_func(struct tasklet_struct *t)
tasklet_tx);
struct uart_port *port = &atmel_port->uart;
- /* The interrupt handler does not take the lock */
- uart_port_lock(port);
+ guard(uart_port_lock_irqsave)(port);
+
atmel_port->schedule_tx(port);
- uart_port_unlock(port);
}
static void atmel_init_property(struct atmel_uart_port *atmel_port,
diff --git a/drivers/tty/serial/clps711x.c b/drivers/tty/serial/clps711x.c
index 83186bf50002..06ed8ba7c9fb 100644
--- a/drivers/tty/serial/clps711x.c
+++ b/drivers/tty/serial/clps711x.c
@@ -73,7 +73,8 @@ static void uart_clps711x_stop_tx(struct uart_port *port)
struct clps711x_port *s = dev_get_drvdata(port->dev);
if (s->tx_enabled) {
- disable_irq(port->irq);
+ /* The handler also takes the port lock. */
+ disable_irq_nosync(port->irq);
s->tx_enabled = 0;
}
}
@@ -146,9 +147,16 @@ static irqreturn_t uart_clps711x_int_tx(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
struct clps711x_port *s = dev_get_drvdata(port->dev);
- struct tty_port *tport = &port->state->port;
+ struct tty_port *tport;
unsigned char c;
+ guard(uart_port_lock_irqsave)(port);
+
+ if (!s->tx_enabled)
+ return IRQ_HANDLED;
+
+ tport = &port->state->port;
+
if (port->x_char) {
writew(port->x_char, port->membase + UARTDR_OFFSET);
port->icount.tx++;
@@ -157,10 +165,7 @@ static irqreturn_t uart_clps711x_int_tx(int irq, void *dev_id)
}
if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) {
- if (s->tx_enabled) {
- disable_irq_nosync(port->irq);
- s->tx_enabled = 0;
- }
+ uart_clps711x_stop_tx(port);
return IRQ_HANDLED;
}
@@ -245,8 +250,14 @@ static void uart_clps711x_shutdown(struct uart_port *port)
{
struct clps711x_port *s = dev_get_drvdata(port->dev);
+ scoped_guard(uart_port_lock_irqsave, port)
+ uart_clps711x_stop_tx(port);
+
/* Disable the port */
regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON_UARTEN, 0);
+
+ /* Drain the non-waiting TX disable before releasing the FIFO. */
+ synchronize_irq(port->irq);
}
static void uart_clps711x_set_termios(struct uart_port *port,
diff --git a/drivers/tty/serial/cpm_uart.c b/drivers/tty/serial/cpm_uart.c
index 39f54bb7b485..028a01fa02a9 100644
--- a/drivers/tty/serial/cpm_uart.c
+++ b/drivers/tty/serial/cpm_uart.c
@@ -214,6 +214,8 @@ static void cpm_uart_int_tx(struct uart_port *port)
{
pr_debug("CPM uart[%d]:TX INT\n", port->line);
+ guard(uart_port_lock_irqsave)(port);
+
cpm_uart_tx_pump(port);
}
diff --git a/drivers/tty/serial/dz.c b/drivers/tty/serial/dz.c
index 39d93e9c2d15..684691fed0f1 100644
--- a/drivers/tty/serial/dz.c
+++ b/drivers/tty/serial/dz.c
@@ -254,6 +254,8 @@ static inline void dz_transmit_chars(struct dz_mux *mux)
status = dz_in(dport, DZ_CSR);
dport = &mux->dport[LINE(status)];
+ guard(uart_port_lock_irqsave)(&dport->port);
+
tport = &dport->port.state->port;
if (dport->port.x_char) { /* XON/XOFF chars */
@@ -265,9 +267,7 @@ static inline void dz_transmit_chars(struct dz_mux *mux)
/* If nothing to do or stopped or hardware stopped. */
if (uart_tx_stopped(&dport->port) ||
!uart_fifo_get(&dport->port, &tmp)) {
- uart_port_lock(&dport->port);
dz_stop_tx(&dport->port);
- uart_port_unlock(&dport->port);
return;
}
@@ -281,11 +281,8 @@ static inline void dz_transmit_chars(struct dz_mux *mux)
uart_write_wakeup(&dport->port);
/* Are we are done. */
- if (kfifo_is_empty(&tport->xmit_fifo)) {
- uart_port_lock(&dport->port);
+ if (kfifo_is_empty(&tport->xmit_fifo))
dz_stop_tx(&dport->port);
- uart_port_unlock(&dport->port);
- }
}
/*
diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c
index c8575c965203..cb04571c3d0e 100644
--- a/drivers/tty/serial/fsl_lpuart.c
+++ b/drivers/tty/serial/fsl_lpuart.c
@@ -540,10 +540,10 @@ static void lpuart_dma_tx_complete(void *arg)
uart_xmit_advance(&sport->port, sport->dma_tx_bytes);
sport->dma_tx_in_progress = false;
- uart_port_unlock_irqrestore(&sport->port, flags);
if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
uart_write_wakeup(&sport->port);
+ uart_port_unlock_irqrestore(&sport->port, flags);
if (waitqueue_active(&sport->dma_wait)) {
wake_up(&sport->dma_wait);
diff --git a/drivers/tty/serial/jsm/jsm_cls.c b/drivers/tty/serial/jsm/jsm_cls.c
index 6e40792f92cf..9224bb57eaf3 100644
--- a/drivers/tty/serial/jsm/jsm_cls.c
+++ b/drivers/tty/serial/jsm/jsm_cls.c
@@ -571,7 +571,8 @@ static inline void cls_parse_isr(struct jsm_board *brd, uint port)
spin_lock_irqsave(&ch->ch_lock, flags);
ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
spin_unlock_irqrestore(&ch->ch_lock, flags);
- cls_copy_data_from_queue_to_uart(ch);
+ scoped_guard(uart_port_lock_irqsave, &ch->uart_port)
+ cls_copy_data_from_queue_to_uart(ch);
}
/*
@@ -581,7 +582,8 @@ static inline void cls_parse_isr(struct jsm_board *brd, uint port)
*/
/* Parse any modem signal changes */
- cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr));
+ scoped_guard(uart_port_lock_irqsave, &ch->uart_port)
+ cls_parse_modem(ch, readb(&ch->ch_cls_uart->msr));
}
}
diff --git a/drivers/tty/serial/jsm/jsm_neo.c b/drivers/tty/serial/jsm/jsm_neo.c
index e8e13bf056e2..cbd9af43c68b 100644
--- a/drivers/tty/serial/jsm/jsm_neo.c
+++ b/drivers/tty/serial/jsm/jsm_neo.c
@@ -740,7 +740,8 @@ static void neo_parse_isr(struct jsm_board *brd, u32 port)
spin_lock_irqsave(&ch->ch_lock, lock_flags);
ch->ch_flags |= (CH_TX_FIFO_EMPTY | CH_TX_FIFO_LWM);
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
- neo_copy_data_from_queue_to_uart(ch);
+ scoped_guard(uart_port_lock_irqsave, &ch->uart_port)
+ neo_copy_data_from_queue_to_uart(ch);
}
if (isr & UART_17158_IIR_XONXOFF) {
@@ -895,7 +896,8 @@ static inline void neo_parse_lsr(struct jsm_board *brd, u32 port)
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
/* Transfer data (if any) from Write Queue -> UART. */
- neo_copy_data_from_queue_to_uart(ch);
+ scoped_guard(uart_port_lock_irqsave, &ch->uart_port)
+ neo_copy_data_from_queue_to_uart(ch);
}
else if (linestatus & UART_17158_TX_AND_FIFO_CLR) {
spin_lock_irqsave(&ch->ch_lock, lock_flags);
@@ -903,7 +905,8 @@ static inline void neo_parse_lsr(struct jsm_board *brd, u32 port)
spin_unlock_irqrestore(&ch->ch_lock, lock_flags);
/* Transfer data (if any) from Write Queue -> UART. */
- neo_copy_data_from_queue_to_uart(ch);
+ scoped_guard(uart_port_lock_irqsave, &ch->uart_port)
+ neo_copy_data_from_queue_to_uart(ch);
}
}
diff --git a/drivers/tty/serial/lantiq.c b/drivers/tty/serial/lantiq.c
index 62cd9e0bb377..ed1d31791402 100644
--- a/drivers/tty/serial/lantiq.c
+++ b/drivers/tty/serial/lantiq.c
@@ -153,11 +153,12 @@ lqasc_start_tx(struct uart_port *port)
struct ltq_uart_port *ltq_port = to_ltq_uart_port(port);
u8 ch;
+ /* Drop the hardware lock before the helper notifies the line discipline. */
spin_lock_irqsave(<q_port->lock, flags);
- uart_port_tx(port, ch,
- lqasc_tx_ready(port),
- writeb(ch, port->membase + LTQ_ASC_TBUF));
- spin_unlock_irqrestore(<q_port->lock, flags);
+ uart_port_tx_limited(port, ch, port->fifosize,
+ lqasc_tx_ready(port),
+ writeb(ch, port->membase + LTQ_ASC_TBUF),
+ spin_unlock_irqrestore(<q_port->lock, flags));
return;
}
@@ -239,7 +240,8 @@ lqasc_tx_int(int irq, void *_port)
spin_lock_irqsave(<q_port->lock, flags);
__raw_writel(ASC_IRNCR_TIR, port->membase + LTQ_ASC_IRNCR);
spin_unlock_irqrestore(<q_port->lock, flags);
- lqasc_start_tx(port);
+ scoped_guard(uart_port_lock_irqsave, port)
+ lqasc_start_tx(port);
return IRQ_HANDLED;
}
diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c
index 3be30fb05513..ac44681bcb73 100644
--- a/drivers/tty/serial/ma35d1_serial.c
+++ b/drivers/tty/serial/ma35d1_serial.c
@@ -295,8 +295,10 @@ static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
if (isr & (MA35_ISR_RDA_IF | MA35_ISR_RXTO_IF))
receive_chars(up);
- if (isr & MA35_ISR_THRE_INT)
- transmit_chars(up);
+ if (isr & MA35_ISR_THRE_INT) {
+ scoped_guard(uart_port_lock_irqsave, port)
+ transmit_chars(up);
+ }
if (fsr & MA35_FSR_TX_OVER_IF)
serial_out(up, MA35_FSR_REG, MA35_FSR_TX_OVER_IF);
diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c
index f858a6669011..5a8817eb82ad 100644
--- a/drivers/tty/serial/max3100.c
+++ b/drivers/tty/serial/max3100.c
@@ -287,8 +287,10 @@ static void max3100_work(struct work_struct *w)
tty_flip_buffer_push(&s->port.state->port);
rxchars = 0;
}
- if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
- uart_write_wakeup(&s->port);
+ scoped_guard(uart_port_lock_irqsave, &s->port) {
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(&s->port);
+ }
} while (!s->force_end_work &&
!freezing(current) &&
diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
index a0ed70185555..d9ce6ba53c7f 100644
--- a/drivers/tty/serial/max310x.c
+++ b/drivers/tty/serial/max310x.c
@@ -950,8 +950,10 @@ static void max310x_handle_tx(struct uart_port *port)
uart_xmit_advance(port, to_send);
}
- if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
- uart_write_wakeup(port);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+ }
}
/*
@@ -1043,7 +1045,8 @@ static irqreturn_t max310x_port_irq(struct max310x_port *s, int portno)
if (ists & MAX310X_IRQ_CTS_BIT) {
lsr = max310x_port_read(port, MAX310X_LSR_IRQSTS_REG);
- uart_handle_cts_change(port, lsr & MAX310X_LSR_CTS_BIT);
+ scoped_guard(uart_port_lock_irqsave, port)
+ uart_handle_cts_change(port, lsr & MAX310X_LSR_CTS_BIT);
}
if (rxlen)
max310x_handle_rx(port, rxlen);
diff --git a/drivers/tty/serial/mux.c b/drivers/tty/serial/mux.c
index 3a77a7e5c7bc..8be5b3031749 100644
--- a/drivers/tty/serial/mux.c
+++ b/drivers/tty/serial/mux.c
@@ -189,11 +189,14 @@ static void mux_write(struct uart_port *port)
{
u8 ch;
- uart_port_tx_limited(port, ch,
- port->fifosize - UART_GET_FIFO_CNT(port),
- true,
- UART_PUT_CHAR(port, ch),
- mux_tx_done(port));
+ scoped_guard(uart_port_lock_irqsave, port) {
+ uart_port_tx_limited(port, ch,
+ port->fifosize - UART_GET_FIFO_CNT(port),
+ true,
+ UART_PUT_CHAR(port, ch),
+ ({}));
+ }
+ mux_tx_done(port);
}
/**
diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c
index 33ea496c93b2..7bc0d0d3760e 100644
--- a/drivers/tty/serial/mvebu-uart.c
+++ b/drivers/tty/serial/mvebu-uart.c
@@ -334,6 +334,8 @@ static void mvebu_uart_tx_chars(struct uart_port *port, unsigned int status)
{
u8 ch;
+ guard(uart_port_lock_irqsave)(port);
+
uart_port_tx_limited(port, ch, port->fifosize,
!(readl(port->membase + UART_STAT) & STAT_TX_FIFO_FUL),
writel(ch, port->membase + UART_TSH(port)),
diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
index 697318dbb146..6a310309b55d 100644
--- a/drivers/tty/serial/mxs-auart.c
+++ b/drivers/tty/serial/mxs-auart.c
@@ -521,6 +521,8 @@ static void dma_tx_callback(void *param)
dma_unmap_sg(s->dev, &s->tx_sgl, 1, DMA_TO_DEVICE);
+ guard(uart_port_lock_irqsave)(&s->port);
+
/* clear the bit used to serialize the DMA tx. */
clear_bit(MXS_AUART_DMA_TX_SYNC, &s->flags);
smp_mb__after_atomic();
diff --git a/drivers/tty/serial/sb1250-duart.c b/drivers/tty/serial/sb1250-duart.c
index b4e1b90e5960..ff3019e1631d 100644
--- a/drivers/tty/serial/sb1250-duart.c
+++ b/drivers/tty/serial/sb1250-duart.c
@@ -387,6 +387,9 @@ static void sbd_transmit_chars(struct sbd_port *sport)
unsigned int mask;
int stop_tx;
+ /* Serialize FIFO consumption and interrupt masking with start_tx(). */
+ guard(uart_port_lock_irqsave)(uport);
+
/* XON/XOFF chars. */
if (sport->port.x_char) {
write_sbdchn(sport, R_DUART_TX_HOLD, sport->port.x_char);
@@ -424,15 +427,19 @@ static void sbd_status_handle(struct sbd_port *sport)
delta = read_sbdshr(sport, R_DUART_INCHREG((uport->line) % 2));
delta >>= (uport->line) % 2;
- if (delta & (M_DUART_IN_PIN0_VAL << S_DUART_IN_PIN_CHNG))
- uart_handle_cts_change(uport, !(delta & M_DUART_IN_PIN0_VAL));
+ if (!(delta & ((M_DUART_IN_PIN2_VAL | M_DUART_IN_PIN0_VAL) <<
+ S_DUART_IN_PIN_CHNG)))
+ return;
- if (delta & (M_DUART_IN_PIN2_VAL << S_DUART_IN_PIN_CHNG))
- uport->icount.dsr++;
+ scoped_guard(uart_port_lock_irqsave, uport) {
+ if (delta & (M_DUART_IN_PIN0_VAL << S_DUART_IN_PIN_CHNG))
+ uart_handle_cts_change(uport, !(delta & M_DUART_IN_PIN0_VAL));
- if (delta & ((M_DUART_IN_PIN2_VAL | M_DUART_IN_PIN0_VAL) <<
- S_DUART_IN_PIN_CHNG))
- wake_up_interruptible(&uport->state->port.delta_msr_wait);
+ if (delta & (M_DUART_IN_PIN2_VAL << S_DUART_IN_PIN_CHNG))
+ uport->icount.dsr++;
+ }
+
+ wake_up_interruptible(&uport->state->port.delta_msr_wait);
}
static irqreturn_t sbd_interrupt(int irq, void *dev_id)
@@ -611,7 +618,7 @@ static void sbd_set_termios(struct uart_port *uport, struct ktermios *termios,
else
aux &= ~M_DUART_CTS_CHNG_ENA;
- uart_port_lock(uport);
+ guard(uart_port_lock_irqsave)(uport);
if (sport->tx_stopped)
command |= M_DUART_TX_DIS;
@@ -632,8 +639,6 @@ static void sbd_set_termios(struct uart_port *uport, struct ktermios *termios,
write_sbdchn(sport, R_DUART_AUXCTL_X, aux | oldaux);
write_sbdchn(sport, R_DUART_CMD, command);
-
- uart_port_unlock(uport);
}
diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c
index cb31e3aceb62..50845e64d633 100644
--- a/drivers/tty/serial/sccnxp.c
+++ b/drivers/tty/serial/sccnxp.c
@@ -10,6 +10,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
+#include <linux/find.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/console.h>
@@ -435,7 +436,7 @@ static void sccnxp_handle_rx(struct uart_port *port)
tty_flip_buffer_push(&port->state->port);
}
-static void sccnxp_handle_tx(struct uart_port *port)
+static bool sccnxp_handle_tx(struct uart_port *port)
{
u8 sr;
struct tty_port *tport = &port->state->port;
@@ -445,7 +446,7 @@ static void sccnxp_handle_tx(struct uart_port *port)
sccnxp_port_write(port, SCCNXP_THR_REG, port->x_char);
port->icount.tx++;
port->x_char = 0;
- return;
+ return false;
}
if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) {
@@ -457,7 +458,7 @@ static void sccnxp_handle_tx(struct uart_port *port)
if (s->chip->flags & SCCNXP_HAVE_IO)
sccnxp_set_bit(port, DIR_OP, 0);
}
- return;
+ return false;
}
while (1) {
@@ -473,13 +474,13 @@ static void sccnxp_handle_tx(struct uart_port *port)
sccnxp_port_write(port, SCCNXP_THR_REG, ch);
}
- if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
- uart_write_wakeup(port);
+ return kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS;
}
-static void sccnxp_handle_events(struct sccnxp_port *s)
+static unsigned long sccnxp_handle_events(struct sccnxp_port *s)
{
- int i;
+ unsigned long wakeup = 0;
+ unsigned int i;
u8 isr;
do {
@@ -491,20 +492,40 @@ static void sccnxp_handle_events(struct sccnxp_port *s)
for (i = 0; i < s->uart.nr; i++) {
if (s->opened[i] && (isr & ISR_RXRDY(i)))
sccnxp_handle_rx(&s->port[i]);
- if (s->opened[i] && (isr & ISR_TXRDY(i)))
- sccnxp_handle_tx(&s->port[i]);
+ if (s->opened[i] && (isr & ISR_TXRDY(i)) &&
+ sccnxp_handle_tx(&s->port[i]))
+ wakeup |= BIT(i);
}
} while (1);
+
+ return wakeup;
+}
+
+static void sccnxp_write_wakeup(struct sccnxp_port *s, unsigned long wakeup)
+{
+ unsigned int i;
+
+ /* UART callbacks take s->lock with the port lock already held. */
+ for_each_set_bit(i, &wakeup, s->uart.nr) {
+ struct uart_port *port = &s->port[i];
+
+ scoped_guard(uart_port_lock_irqsave, port) {
+ if (kfifo_len(&port->state->port.xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(port);
+ }
+ }
}
static void sccnxp_timer(struct timer_list *t)
{
struct sccnxp_port *s = timer_container_of(s, t, timer);
unsigned long flags;
+ unsigned long wakeup;
spin_lock_irqsave(&s->lock, flags);
- sccnxp_handle_events(s);
+ wakeup = sccnxp_handle_events(s);
spin_unlock_irqrestore(&s->lock, flags);
+ sccnxp_write_wakeup(s, wakeup);
mod_timer(&s->timer, jiffies + usecs_to_jiffies(s->pdata.poll_time_us));
}
@@ -513,10 +534,12 @@ static irqreturn_t sccnxp_ist(int irq, void *dev_id)
{
struct sccnxp_port *s = (struct sccnxp_port *)dev_id;
unsigned long flags;
+ unsigned long wakeup;
spin_lock_irqsave(&s->lock, flags);
- sccnxp_handle_events(s);
+ wakeup = sccnxp_handle_events(s);
spin_unlock_irqrestore(&s->lock, flags);
+ sccnxp_write_wakeup(s, wakeup);
return IRQ_HANDLED;
}
diff --git a/drivers/tty/serial/ucc_uart.c b/drivers/tty/serial/ucc_uart.c
index 6b7dcbbacd13..60f72c19d67c 100644
--- a/drivers/tty/serial/ucc_uart.c
+++ b/drivers/tty/serial/ucc_uart.c
@@ -570,8 +570,10 @@ static irqreturn_t qe_uart_int(int irq, void *data)
if (events & UCC_UART_UCCE_RX)
qe_uart_int_rx(qe_port);
- if (events & UCC_UART_UCCE_TX)
- qe_uart_tx_pump(qe_port);
+ if (events & UCC_UART_UCCE_TX) {
+ scoped_guard(uart_port_lock_irqsave, &qe_port->port)
+ qe_uart_tx_pump(qe_port);
+ }
return events ? IRQ_HANDLED : IRQ_NONE;
}
diff --git a/drivers/tty/serial/zs.c b/drivers/tty/serial/zs.c
index 8f92b4129a38..f1c9fec6d513 100644
--- a/drivers/tty/serial/zs.c
+++ b/drivers/tty/serial/zs.c
@@ -431,20 +431,23 @@ static void zs_stop_tx(struct uart_port *uport)
spin_unlock(&scc->zlock);
}
-static void zs_raw_transmit_chars(struct zs_port *);
+static bool zs_raw_transmit_chars(struct zs_port *);
static void zs_start_tx(struct uart_port *uport)
{
struct zs_port *zport = to_zport(uport);
struct zs_scc *scc = zport->scc;
+ bool wakeup = false;
spin_lock(&scc->zlock);
if (zport->tx_stopped) {
zs_transmit_drain(zport, 0);
zport->tx_stopped = 0;
- zs_raw_transmit_chars(zport);
+ wakeup = zs_raw_transmit_chars(zport);
}
spin_unlock(&scc->zlock);
+ if (wakeup)
+ uart_write_wakeup(uport);
}
static void zs_stop_rx(struct uart_port *uport)
@@ -603,51 +606,81 @@ static void zs_receive_chars(struct zs_port *zport)
tty_flip_buffer_push(&uport->state->port);
}
-static void zs_raw_transmit_chars(struct zs_port *zport)
+static bool zs_raw_transmit_chars(struct zs_port *zport)
{
struct tty_port *tport = &zport->port.state->port;
unsigned char ch;
+ bool wakeup;
+
+ /* A deferred console restart can race a pending TX interrupt. */
+ if (!(read_zsreg(zport, R0) & Tx_BUF_EMP))
+ return false;
/* XON/XOFF chars. */
if (zport->port.x_char) {
write_zsdata(zport, zport->port.x_char);
zport->port.icount.tx++;
zport->port.x_char = 0;
- return;
+ return false;
}
/* If nothing to do or stopped or hardware stopped. */
if (uart_tx_stopped(&zport->port) ||
!uart_fifo_get(&zport->port, &ch)) {
zs_raw_stop_tx(zport);
- return;
+ return false;
}
/* Send char. */
write_zsdata(zport, ch);
- if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
- uart_write_wakeup(&zport->port);
+ wakeup = kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS;
/* Are we are done? */
if (kfifo_is_empty(&tport->xmit_fifo))
zs_raw_stop_tx(zport);
+
+ return wakeup;
}
static void zs_transmit_chars(struct zs_port *zport)
{
struct zs_scc *scc = zport->scc;
+ bool wakeup;
+
+ guard(uart_port_lock_irqsave)(&zport->port);
spin_lock(&scc->zlock);
- zs_raw_transmit_chars(zport);
+ wakeup = zs_raw_transmit_chars(zport);
spin_unlock(&scc->zlock);
+ if (wakeup)
+ uart_write_wakeup(&zport->port);
+}
+
+static void zs_tx_work(struct work_struct *work)
+{
+ struct zs_port *zport = container_of(work, struct zs_port, tx_work);
+ struct uart_port *uport = &zport->port;
+ bool wakeup = false;
+
+ guard(uart_port_lock_irqsave)(uport);
+
+ if (!uport->state || !uport->state->port.xmit_buf)
+ return;
+
+ scoped_guard(spinlock, &zport->scc->zlock) {
+ if (!zport->tx_stopped)
+ wakeup = zs_raw_transmit_chars(zport);
+ }
+ if (wakeup)
+ uart_write_wakeup(uport);
}
static void zs_status_handle(struct zs_port *zport, struct zs_port *zport_a)
{
struct uart_port *uport = &zport->port;
struct zs_scc *scc = zport->scc;
- unsigned int delta;
+ unsigned int delta, mctrl;
u8 status, brk;
spin_lock(&scc->zlock);
@@ -670,18 +703,19 @@ static void zs_status_handle(struct zs_port *zport, struct zs_port *zport_a)
if (zport != zport_a) {
delta = zs_raw_xor_mctrl(zport);
+ mctrl = zport->mctrl;
spin_unlock(&scc->zlock);
- if (delta & TIOCM_CTS)
- uart_handle_cts_change(uport,
- zport->mctrl & TIOCM_CTS);
- if (delta & TIOCM_CAR)
- uart_handle_dcd_change(uport,
- zport->mctrl & TIOCM_CAR);
- if (delta & TIOCM_RNG)
- uport->icount.rng++;
- if (delta & TIOCM_DSR)
- uport->icount.dsr++;
+ scoped_guard(uart_port_lock_irqsave, uport) {
+ if (delta & TIOCM_CTS)
+ uart_handle_cts_change(uport, mctrl & TIOCM_CTS);
+ if (delta & TIOCM_CAR)
+ uart_handle_dcd_change(uport, mctrl & TIOCM_CAR);
+ if (delta & TIOCM_RNG)
+ uport->icount.rng++;
+ if (delta & TIOCM_DSR)
+ uport->icount.dsr++;
+ }
if (delta)
wake_up_interruptible(&uport->state->port.delta_msr_wait);
@@ -809,13 +843,19 @@ static void zs_shutdown(struct uart_port *uport)
unsigned long flags;
int irq_guard;
- spin_lock_irqsave(&scc->zlock, flags);
+ uart_port_lock_irqsave(uport, &flags);
+ spin_lock(&scc->zlock);
+ zs_raw_stop_tx(zport);
+ zport->regs[1] &= ~TxINT_ENAB;
+ write_zsreg(zport, R1, zport->regs[1]);
zport->regs[3] &= ~RxENABLE;
write_zsreg(zport, R5, zport->regs[5]);
write_zsreg(zport, R3, zport->regs[3]);
- spin_unlock_irqrestore(&scc->zlock, flags);
+ spin_unlock(&scc->zlock);
+ uart_port_unlock_irqrestore(uport, flags);
+ cancel_work_sync(&zport->tx_work);
irq_guard = atomic_add_return(-1, &scc->irq_guard);
if (!irq_guard)
@@ -1066,6 +1106,7 @@ static int __init zs_probe(struct platform_device *pdev)
zport->scc = &zs_sccs[chip];
zport->clk_mode = 16;
+ INIT_WORK(&zport->tx_work, zs_tx_work);
uport->dev = &pdev->dev;
uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_ZS_CONSOLE);
@@ -1098,8 +1139,10 @@ static void __exit zs_remove(struct platform_device *pdev)
struct zs_port *zport = &zs_sccs[chip].zport[side];
struct uart_port *uport = &zport->port;
- if (uport->dev)
+ if (uport->dev) {
uart_remove_one_port(&zs_reg, uport);
+ cancel_work_sync(&zport->tx_work);
+ }
}
}
@@ -1161,9 +1204,12 @@ static void zs_console_write(struct console *co, const char *s,
zport->regs[1] |= TxINT_ENAB;
write_zsreg(zport, R1, zport->regs[1]);
- /* Resume any transmission as the TxIP bit won't be set. */
+ /*
+ * TxIP won't be set. Defer the restart: console output may be
+ * called with the port lock held, and TX must acquire it first.
+ */
if (!zport->tx_stopped)
- zs_raw_transmit_chars(zport);
+ schedule_work(&zport->tx_work);
}
spin_unlock_irqrestore(&scc->zlock, flags);
}
diff --git a/drivers/tty/serial/zs.h b/drivers/tty/serial/zs.h
index e0d3c189b33f..0404294e4533 100644
--- a/drivers/tty/serial/zs.h
+++ b/drivers/tty/serial/zs.h
@@ -14,6 +14,8 @@
#ifdef __KERNEL__
+#include <linux/workqueue.h>
+
#define ZS_NUM_REGS 16
/*
@@ -22,6 +24,7 @@
struct zs_port {
struct zs_scc *scc; /* Containing SCC. */
struct uart_port port; /* Underlying UART. */
+ struct work_struct tx_work; /* Restart after console output. */
int clk_mode; /* May be 1, 16, 32, or 64. */
--
2.53.0
next prev parent reply other threads:[~2026-10-29 7:37 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 7:36 [PATCH v8 0/6] serial: 8250: serialize DMA cancellation with buffer reuse James Hilliard
2026-10-29 7:36 ` [PATCH v8 1/6] serial: 8250_omap: separate locked TX DMA completion processing James Hilliard
2026-10-29 7:36 ` James Hilliard [this message]
2026-10-29 14:17 ` [PATCH v8 2/6] serial: serialize asynchronous write wakeups with the port lock Andy Shevchenko
2026-10-29 17:13 ` James Hilliard
2026-10-29 7:36 ` [PATCH v8 3/6] serial: core: allow drivers to reserve flushed transmit storage James Hilliard
2026-10-29 7:37 ` [PATCH v8 4/6] serial: 8250: drain canceled TX DMA before reusing its buffer James Hilliard
2026-10-29 14:19 ` Andy Shevchenko
2026-10-29 17:15 ` James Hilliard
2026-10-30 9:33 ` Andy Shevchenko
2026-10-29 7:37 ` [PATCH v8 5/6] serial: 8250: retire canceled RX DMA before reusing the receive buffer James Hilliard
2026-10-29 14:22 ` Andy Shevchenko
2026-10-29 17:17 ` James Hilliard
2026-10-29 22:27 ` Raul E Rangel
2026-10-30 0:32 ` James Hilliard
2026-10-30 17:36 ` Raul E Rangel
2026-10-29 7:37 ` [PATCH v8 6/6] serial: 8250: retain DMA resources when termination fails James Hilliard
2026-10-29 14:23 ` Andy Shevchenko
2026-10-29 17:19 ` James Hilliard
2026-10-29 22:27 ` Raul E Rangel
2026-10-30 0:35 ` James Hilliard
2026-10-30 17:36 ` Raul E Rangel
2026-11-02 19:01 ` James Hilliard
2026-10-30 9:40 ` Andy Shevchenko
2026-10-30 17:09 ` James Hilliard
2026-10-30 17:53 ` Andy Shevchenko
2026-10-30 18:08 ` James Hilliard
2026-10-31 8:44 ` Andy Shevchenko
2026-10-31 18:00 ` 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=20261029-submit-serial-8250-dma-cancel-v1-v8-2-3fd56ba352ed@gmail.com \
--to=james.hilliard1@gmail.com \
--cc=Frank.Li@nxp.com \
--cc=alexandre.belloni@bootlin.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=bigeasy@linutronix.de \
--cc=claudiu.beznea@tuxon.dev \
--cc=festevam@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=heikki.krogerus@linux.intel.com \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=imx@lists.linux.dev \
--cc=jirislaby@kernel.org \
--cc=kernel@pengutronix.de \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=macro@orcam.me.uk \
--cc=nicolas.ferre@microchip.com \
--cc=pali@kernel.org \
--cc=richard.genoud@bootlin.com \
--cc=rrangel@chromium.org \
--cc=s.hauer@pengutronix.de \
--cc=schung@nuvoton.com \
--cc=timur@kernel.org \
--cc=tony@atomide.com \
--cc=ychuang3@nuvoton.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®