serial: core: Fix missing shutdown and startup for serial base port
authorTony Lindgren <tony@atomide.com>
Thu, 11 Apr 2024 05:58:45 +0000 (08:58 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 11 Apr 2024 12:21:11 +0000 (14:21 +0200)
We are seeing start_tx being called after port shutdown as noted by Jiri.
This happens because we are missing the startup and shutdown related
functions for the serial base port.

Let's fix the issue by adding startup and shutdown functions for the
serial base port to block tx flushing for the serial base port when the
port is not in use.

Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM")
Cc: stable <stable@kernel.org>
Reported-by: Jiri Slaby <jirislaby@kernel.org>
Signed-off-by: Tony Lindgren <tony@atomide.com>
Link: https://lore.kernel.org/r/20240411055848.38190-1-tony@atomide.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/serial/serial_base.h
drivers/tty/serial/serial_core.c
drivers/tty/serial/serial_port.c

index c74c548f0db62ae97ffdfbe33390bac50c412862..b6c38d2edfd401a79a937c0d149cbbb77ee3b342 100644 (file)
@@ -22,6 +22,7 @@ struct serial_ctrl_device {
 struct serial_port_device {
        struct device dev;
        struct uart_port *port;
+       unsigned int tx_enabled:1;
 };
 
 int serial_base_ctrl_init(void);
@@ -30,6 +31,9 @@ void serial_base_ctrl_exit(void);
 int serial_base_port_init(void);
 void serial_base_port_exit(void);
 
+void serial_base_port_startup(struct uart_port *port);
+void serial_base_port_shutdown(struct uart_port *port);
+
 int serial_base_driver_register(struct device_driver *driver);
 void serial_base_driver_unregister(struct device_driver *driver);
 
index 2247efe9725032762779a6daf23ef67831b95bc1..c476d884356dbda13ad2183a2d7218aa88ae3325 100644 (file)
@@ -323,16 +323,26 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state,
                        bool init_hw)
 {
        struct tty_port *port = &state->port;
+       struct uart_port *uport;
        int retval;
 
        if (tty_port_initialized(port))
-               return 0;
+               goto out_base_port_startup;
 
        retval = uart_port_startup(tty, state, init_hw);
-       if (retval)
+       if (retval) {
                set_bit(TTY_IO_ERROR, &tty->flags);
+               return retval;
+       }
 
-       return retval;
+out_base_port_startup:
+       uport = uart_port_check(state);
+       if (!uport)
+               return -EIO;
+
+       serial_base_port_startup(uport);
+
+       return 0;
 }
 
 /*
@@ -355,6 +365,9 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
        if (tty)
                set_bit(TTY_IO_ERROR, &tty->flags);
 
+       if (uport)
+               serial_base_port_shutdown(uport);
+
        if (tty_port_initialized(port)) {
                tty_port_set_initialized(port, false);
 
@@ -1775,6 +1788,7 @@ static void uart_tty_port_shutdown(struct tty_port *port)
        uport->ops->stop_rx(uport);
        uart_port_unlock_irq(uport);
 
+       serial_base_port_shutdown(uport);
        uart_port_shutdown(port);
 
        /*
index 22b9eeb23e68adb2cfd949ff20c18816bb78b1cb..7e3a1c7b097c3cce20c7fe9f801c1f3dfa76f304 100644 (file)
@@ -39,8 +39,12 @@ static int serial_port_runtime_resume(struct device *dev)
 
        /* Flush any pending TX for the port */
        uart_port_lock_irqsave(port, &flags);
+       if (!port_dev->tx_enabled)
+               goto unlock;
        if (__serial_port_busy(port))
                port->ops->start_tx(port);
+
+unlock:
        uart_port_unlock_irqrestore(port, flags);
 
 out:
@@ -60,6 +64,11 @@ static int serial_port_runtime_suspend(struct device *dev)
                return 0;
 
        uart_port_lock_irqsave(port, &flags);
+       if (!port_dev->tx_enabled) {
+               uart_port_unlock_irqrestore(port, flags);
+               return 0;
+       }
+
        busy = __serial_port_busy(port);
        if (busy)
                port->ops->start_tx(port);
@@ -71,6 +80,31 @@ static int serial_port_runtime_suspend(struct device *dev)
        return busy ? -EBUSY : 0;
 }
 
+static void serial_base_port_set_tx(struct uart_port *port,
+                                   struct serial_port_device *port_dev,
+                                   bool enabled)
+{
+       unsigned long flags;
+
+       uart_port_lock_irqsave(port, &flags);
+       port_dev->tx_enabled = enabled;
+       uart_port_unlock_irqrestore(port, flags);
+}
+
+void serial_base_port_startup(struct uart_port *port)
+{
+       struct serial_port_device *port_dev = port->port_dev;
+
+       serial_base_port_set_tx(port, port_dev, true);
+}
+
+void serial_base_port_shutdown(struct uart_port *port)
+{
+       struct serial_port_device *port_dev = port->port_dev;
+
+       serial_base_port_set_tx(port, port_dev, false);
+}
+
 static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm,
                                 serial_port_runtime_suspend,
                                 serial_port_runtime_resume, NULL);