[PATCH v5 2/5] serial: serialize asynchronous write wakeups with the port lock

From: James Hilliard

Date: Sat Oct 24 2026 - 02:41:32 EST


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.

SCCNXP handles interrupts under a controller lock which its UART callbacks
acquire with the port lock already held. Collect the ports needing wakeups
while servicing the controller, then release that lock before 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.

Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/tty/serial/21285.c | 2 ++
drivers/tty/serial/atmel_serial.c | 2 ++
drivers/tty/serial/clps711x.c | 6 +++--
drivers/tty/serial/cpm_uart.c | 2 ++
drivers/tty/serial/fsl_lpuart.c | 2 +-
drivers/tty/serial/jsm/jsm_cls.c | 3 ++-
drivers/tty/serial/jsm/jsm_neo.c | 9 +++++---
drivers/tty/serial/lantiq.c | 3 ++-
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 | 10 +++++----
drivers/tty/serial/sccnxp.c | 45 +++++++++++++++++++++++++++++---------
drivers/tty/serial/ucc_uart.c | 6 +++--
17 files changed, 92 insertions(+), 36 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..fb12cef3f719 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++;
diff --git a/drivers/tty/serial/clps711x.c b/drivers/tty/serial/clps711x.c
index 83186bf50002..054b9d8359de 100644
--- a/drivers/tty/serial/clps711x.c
+++ b/drivers/tty/serial/clps711x.c
@@ -174,8 +174,10 @@ static irqreturn_t uart_clps711x_int_tx(int irq, void *dev_id)
break;
}

- 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);
+ }

return IRQ_HANDLED;
}
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/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..7720796331c9 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);
}

/*
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..0d7e06c7d790 100644
--- a/drivers/tty/serial/lantiq.c
+++ b/drivers/tty/serial/lantiq.c
@@ -239,7 +239,8 @@ lqasc_tx_int(int irq, void *_port)
spin_lock_irqsave(&ltq_port->lock, flags);
__raw_writel(ASC_IRNCR_TIR, port->membase + LTQ_ASC_IRNCR);
spin_unlock_irqrestore(&ltq_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..b15f76acd06b 100644
--- a/drivers/tty/serial/sb1250-duart.c
+++ b/drivers/tty/serial/sb1250-duart.c
@@ -454,10 +454,12 @@ static irqreturn_t sbd_interrupt(int irq, void *dev_id)

if (intstat & M_DUART_ISR_RX)
sbd_receive_chars(sport);
- if (intstat & M_DUART_ISR_IN)
- sbd_status_handle(sport);
- if (intstat & M_DUART_ISR_TX)
- sbd_transmit_chars(sport);
+ scoped_guard(uart_port_lock_irqsave, uport) {
+ if (intstat & M_DUART_ISR_IN)
+ sbd_status_handle(sport);
+ if (intstat & M_DUART_ISR_TX)
+ sbd_transmit_chars(sport);
+ }

status = IRQ_HANDLED;
}
diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c
index cb31e3aceb62..f89efc4a2d77 100644
--- a/drivers/tty/serial/sccnxp.c
+++ b/drivers/tty/serial/sccnxp.c
@@ -435,7 +435,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 +445,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 +457,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,12 +473,12 @@ 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 int sccnxp_handle_events(struct sccnxp_port *s)
{
+ unsigned int wakeup = 0;
int i;
u8 isr;

@@ -491,20 +491,43 @@ 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 int wakeup)
+{
+ int i;
+
+ /* UART callbacks take s->lock with the port lock already held. */
+ for (i = 0; i < s->uart.nr; i++) {
+ struct uart_port *port = &s->port[i];
+
+ if (!(wakeup & BIT(i)))
+ continue;
+
+ 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 int 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 +536,12 @@ static irqreturn_t sccnxp_ist(int irq, void *dev_id)
{
struct sccnxp_port *s = (struct sccnxp_port *)dev_id;
unsigned long flags;
+ unsigned int 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;
}

--
2.53.0