|
| 1 | +From: Tony Lindgren < [email protected]> |
| 2 | +Date: Thu, 11 Apr 2024 08:58:45 +0300 |
| 3 | +Subject: [PATCH] serial: core: Fix missing shutdown and startup for serial |
| 4 | + base port |
| 5 | + |
| 6 | +We are seeing start_tx being called after port shutdown as noted by Jiri. |
| 7 | +This happens because we are missing the startup and shutdown related |
| 8 | +functions for the serial base port. |
| 9 | + |
| 10 | +Let's fix the issue by adding startup and shutdown functions for the |
| 11 | +serial base port to block tx flushing for the serial base port when the |
| 12 | +port is not in use. |
| 13 | + |
| 14 | +Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM") |
| 15 | + |
| 16 | +Reported-by: Jiri Slaby < [email protected]> |
| 17 | +Signed-off-by: Tony Lindgren < [email protected]> |
| 18 | +Link: https://lore.kernel.org/r/ [email protected] |
| 19 | +Signed-off-by: Greg Kroah-Hartman < [email protected]> |
| 20 | +Origin: next-20240415, commit:1aa4ad4eb695bac1b0a7ba542a16d6833c9c8dd8 |
| 21 | +--- |
| 22 | + drivers/tty/serial/serial_base.h | 4 ++++ |
| 23 | + drivers/tty/serial/serial_core.c | 20 +++++++++++++++++--- |
| 24 | + drivers/tty/serial/serial_port.c | 34 ++++++++++++++++++++++++++++++++++ |
| 25 | + 3 files changed, 55 insertions(+), 3 deletions(-) |
| 26 | + |
| 27 | +diff --git a/drivers/tty/serial/serial_base.h b/drivers/tty/serial/serial_base.h |
| 28 | +index c74c548f0db6..b6c38d2edfd4 100644 |
| 29 | +--- a/drivers/tty/serial/serial_base.h |
| 30 | ++++ b/drivers/tty/serial/serial_base.h |
| 31 | +@@ -22,6 +22,7 @@ struct serial_ctrl_device { |
| 32 | + struct serial_port_device { |
| 33 | + struct device dev; |
| 34 | + struct uart_port *port; |
| 35 | ++ unsigned int tx_enabled:1; |
| 36 | + }; |
| 37 | + |
| 38 | + int serial_base_ctrl_init(void); |
| 39 | +@@ -30,6 +31,9 @@ void serial_base_ctrl_exit(void); |
| 40 | + int serial_base_port_init(void); |
| 41 | + void serial_base_port_exit(void); |
| 42 | + |
| 43 | ++void serial_base_port_startup(struct uart_port *port); |
| 44 | ++void serial_base_port_shutdown(struct uart_port *port); |
| 45 | ++ |
| 46 | + int serial_base_driver_register(struct device_driver *driver); |
| 47 | + void serial_base_driver_unregister(struct device_driver *driver); |
| 48 | + |
| 49 | +diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c |
| 50 | +index 15664cda4fcd..fa9bc6f5035a 100644 |
| 51 | +--- a/drivers/tty/serial/serial_core.c |
| 52 | ++++ b/drivers/tty/serial/serial_core.c |
| 53 | +@@ -323,16 +323,26 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state, |
| 54 | + bool init_hw) |
| 55 | + { |
| 56 | + struct tty_port *port = &state->port; |
| 57 | ++ struct uart_port *uport; |
| 58 | + int retval; |
| 59 | + |
| 60 | + if (tty_port_initialized(port)) |
| 61 | +- return 0; |
| 62 | ++ goto out_base_port_startup; |
| 63 | + |
| 64 | + retval = uart_port_startup(tty, state, init_hw); |
| 65 | +- if (retval) |
| 66 | ++ if (retval) { |
| 67 | + set_bit(TTY_IO_ERROR, &tty->flags); |
| 68 | ++ return retval; |
| 69 | ++ } |
| 70 | + |
| 71 | +- return retval; |
| 72 | ++out_base_port_startup: |
| 73 | ++ uport = uart_port_check(state); |
| 74 | ++ if (!uport) |
| 75 | ++ return -EIO; |
| 76 | ++ |
| 77 | ++ serial_base_port_startup(uport); |
| 78 | ++ |
| 79 | ++ return 0; |
| 80 | + } |
| 81 | + |
| 82 | + /* |
| 83 | +@@ -355,6 +365,9 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state) |
| 84 | + if (tty) |
| 85 | + set_bit(TTY_IO_ERROR, &tty->flags); |
| 86 | + |
| 87 | ++ if (uport) |
| 88 | ++ serial_base_port_shutdown(uport); |
| 89 | ++ |
| 90 | + if (tty_port_initialized(port)) { |
| 91 | + tty_port_set_initialized(port, false); |
| 92 | + |
| 93 | +@@ -1775,6 +1788,7 @@ static void uart_tty_port_shutdown(struct tty_port *port) |
| 94 | + uport->ops->stop_rx(uport); |
| 95 | + uart_port_unlock_irq(uport); |
| 96 | + |
| 97 | ++ serial_base_port_shutdown(uport); |
| 98 | + uart_port_shutdown(port); |
| 99 | + |
| 100 | + /* |
| 101 | +diff --git a/drivers/tty/serial/serial_port.c b/drivers/tty/serial/serial_port.c |
| 102 | +index 72b6f4f326e2..7d51e66ec88b 100644 |
| 103 | +--- a/drivers/tty/serial/serial_port.c |
| 104 | ++++ b/drivers/tty/serial/serial_port.c |
| 105 | +@@ -36,8 +36,12 @@ static int serial_port_runtime_resume(struct device *dev) |
| 106 | + |
| 107 | + /* Flush any pending TX for the port */ |
| 108 | + uart_port_lock_irqsave(port, &flags); |
| 109 | ++ if (!port_dev->tx_enabled) |
| 110 | ++ goto unlock; |
| 111 | + if (__serial_port_busy(port)) |
| 112 | + port->ops->start_tx(port); |
| 113 | ++ |
| 114 | ++unlock: |
| 115 | + uart_port_unlock_irqrestore(port, flags); |
| 116 | + |
| 117 | + out: |
| 118 | +@@ -57,6 +61,11 @@ static int serial_port_runtime_suspend(struct device *dev) |
| 119 | + return 0; |
| 120 | + |
| 121 | + uart_port_lock_irqsave(port, &flags); |
| 122 | ++ if (!port_dev->tx_enabled) { |
| 123 | ++ uart_port_unlock_irqrestore(port, flags); |
| 124 | ++ return 0; |
| 125 | ++ } |
| 126 | ++ |
| 127 | + busy = __serial_port_busy(port); |
| 128 | + if (busy) |
| 129 | + port->ops->start_tx(port); |
| 130 | +@@ -68,6 +77,31 @@ static int serial_port_runtime_suspend(struct device *dev) |
| 131 | + return busy ? -EBUSY : 0; |
| 132 | + } |
| 133 | + |
| 134 | ++static void serial_base_port_set_tx(struct uart_port *port, |
| 135 | ++ struct serial_port_device *port_dev, |
| 136 | ++ bool enabled) |
| 137 | ++{ |
| 138 | ++ unsigned long flags; |
| 139 | ++ |
| 140 | ++ uart_port_lock_irqsave(port, &flags); |
| 141 | ++ port_dev->tx_enabled = enabled; |
| 142 | ++ uart_port_unlock_irqrestore(port, flags); |
| 143 | ++} |
| 144 | ++ |
| 145 | ++void serial_base_port_startup(struct uart_port *port) |
| 146 | ++{ |
| 147 | ++ struct serial_port_device *port_dev = port->port_dev; |
| 148 | ++ |
| 149 | ++ serial_base_port_set_tx(port, port_dev, true); |
| 150 | ++} |
| 151 | ++ |
| 152 | ++void serial_base_port_shutdown(struct uart_port *port) |
| 153 | ++{ |
| 154 | ++ struct serial_port_device *port_dev = port->port_dev; |
| 155 | ++ |
| 156 | ++ serial_base_port_set_tx(port, port_dev, false); |
| 157 | ++} |
| 158 | ++ |
| 159 | + static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm, |
| 160 | + serial_port_runtime_suspend, |
| 161 | + serial_port_runtime_resume, NULL); |
0 commit comments