[PATCH v1 23/31] serial: 8250_mxpcie: defer uart_write_wakeup to workqueue

Crescent Hsieh posted 31 patches 1 day, 11 hours ago
[PATCH v1 23/31] serial: 8250_mxpcie: defer uart_write_wakeup to workqueue
Posted by Crescent Hsieh 1 day, 11 hours ago
When the TX FIFO drops below WAKEUP_CHARS, mxpcie used to call
uart_write_wakeup() directly from the interrupt path. Move this into a
per-port work item so we avoid doing TTY wakeups in interrupt context and
reduce IRQ-side work.

Changes:
- Add per-port state (event_flags, work, cached uport pointer).
- In tx_chars(), set a TXLOW event and schedule the per-port work instead
  of calling uart_write_wakeup() directly.
- The work handler test-and-clear the TXLOW bit and calls
  uart_write_wakeup().

This keeps IRQ handlers lightweight and avoids potential locking or RT
latency issues while preserving existing behavior.

Note: removal path must cancel pending works before unregistering ports.

Signed-off-by: Crescent Hsieh <crescentcy.hsieh@moxa.com>
---
 drivers/tty/serial/8250/8250_mxpcie.c | 32 +++++++++++++++++++++++----
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c
index 04b9c9ff5cbf..a0deb464a318 100644
--- a/drivers/tty/serial/8250/8250_mxpcie.c
+++ b/drivers/tty/serial/8250/8250_mxpcie.c
@@ -93,9 +93,14 @@
 #define MOXA_EVEN_RS_MASK	GENMASK(3, 0)
 #define MOXA_ODD_RS_MASK	GENMASK(7, 4)
 
+#define	MOXA_EVENT_TXLOW	BIT(0)
+
 struct mxpcie8250_port {
 	int line;
+	unsigned long event_flags;
 	u8 rx_trig_level;
+	struct uart_port *uport;
+	struct work_struct work;
 };
 
 struct mxpcie8250 {
@@ -332,6 +337,8 @@ static void mxpcie8250_tx_chars(struct uart_8250_port *up)
 {
 	struct uart_port *port = &up->port;
 	struct tty_port *tport = &port->state->port;
+	struct pci_dev *pdev = to_pci_dev(port->dev);
+	struct mxpcie8250 *priv = pci_get_drvdata(pdev);
 	unsigned int count, i;
 	unsigned char c;
 
@@ -352,9 +359,10 @@ static void mxpcie8250_tx_chars(struct uart_8250_port *up)
 
 		*(port->membase + MOXA_PUART_TX_FIFO_MEM + i) = c;
 	}
-	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
-		uart_write_wakeup(port);
-
+	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS) {
+		if (!test_and_set_bit(MOXA_EVENT_TXLOW, &priv->port[port->port_id].event_flags))
+			schedule_work(&priv->port[port->port_id].work);
+	}
 	if (kfifo_is_empty(&tport->xmit_fifo) && !(up->capabilities & UART_CAP_RPM))
 		port->ops->stop_tx(port);
 }
@@ -473,6 +481,14 @@ static int mxpcie8250_get_rxtrig(struct uart_port *port)
 	return rx_trig_byte;
 }
 
+static void mxpcie8250_work_handler(struct work_struct *work)
+{
+	struct mxpcie8250_port *priv_port = container_of(work, struct mxpcie8250_port, work);
+
+	if (test_and_clear_bit(MOXA_EVENT_TXLOW, &priv_port->event_flags))
+		uart_write_wakeup(priv_port->uport);
+}
+
 static int mxpcie8250_init(struct pci_dev *pdev)
 {
 	resource_size_t iobar_addr = pci_resource_start(pdev, 2);
@@ -533,6 +549,7 @@ static int mxpcie8250_setup(struct pci_dev *pdev,
 static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 {
 	struct uart_8250_port up;
+	struct uart_8250_port *new_port;
 	struct mxpcie8250 *priv;
 	unsigned int i, num_ports;
 	int ret;
@@ -590,7 +607,12 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id
 				up.port.iotype, priv->port[i].line);
 			break;
 		}
+		new_port = serial8250_get_port(priv->port[i].line);
+
 		priv->port[i].rx_trig_level = 96;
+		priv->port[i].uport = &new_port->port;
+
+		INIT_WORK(&priv->port[i].work, mxpcie8250_work_handler);
 	}
 	pci_set_drvdata(pdev, priv);
 
@@ -602,8 +624,10 @@ static void mxpcie8250_remove(struct pci_dev *pdev)
 	struct mxpcie8250 *priv = pci_get_drvdata(pdev);
 	unsigned int i;
 
-	for (i = 0; i < priv->num_ports; i++)
+	for (i = 0; i < priv->num_ports; i++) {
+		cancel_work_sync(&priv->port[i].work);
 		serial8250_unregister_port(priv->port[i].line);
+	}
 }
 
 static const struct pci_device_id mxpcie8250_pci_ids[] = {
-- 
2.45.2
Re: [PATCH v1 23/31] serial: 8250_mxpcie: defer uart_write_wakeup to workqueue
Posted by Andy Shevchenko 20 hours ago
On Sun, Nov 30, 2025 at 12:45 PM Crescent Hsieh
<crescentcy.hsieh@moxa.com> wrote:
>
> When the TX FIFO drops below WAKEUP_CHARS, mxpcie used to call
> uart_write_wakeup() directly from the interrupt path. Move this into a
> per-port work item so we avoid doing TTY wakeups in interrupt context and
> reduce IRQ-side work.
>
> Changes:
> - Add per-port state (event_flags, work, cached uport pointer).
> - In tx_chars(), set a TXLOW event and schedule the per-port work instead
>   of calling uart_write_wakeup() directly.
> - The work handler test-and-clear the TXLOW bit and calls
>   uart_write_wakeup().
>
> This keeps IRQ handlers lightweight and avoids potential locking or RT
> latency issues while preserving existing behavior.
>
> Note: removal path must cancel pending works before unregistering ports.

...

> +       struct pci_dev *pdev = to_pci_dev(port->dev);
> +       struct mxpcie8250 *priv = pci_get_drvdata(pdev);

dev_get_drvdata();

-- 
With Best Regards,
Andy Shevchenko