diff mbox series

[v6,1/7] serial: Do not hold the port lock when setting rx-during-tx GPIO

Message ID 20231225113524.8800-2-l.sanfilippo@kunbus.com
State Superseded
Headers show
Series Fixes and improvements for RS485 | expand

Commit Message

Lino Sanfilippo Dec. 25, 2023, 11:35 a.m. UTC
Both the imx and stm32 driver set the rx-during-tx GPIO in rs485_config().
Since this function is called with the port lock held, this can be an
problem in case that setting the GPIO line can sleep (e.g. if a GPIO
expander is used which is connected via SPI or I2C).

Avoid this issue by moving the GPIO setting outside of the port lock into
the serial core and thus making it a generic feature.

Also reset old GPIO settings in case that changing the RS485 configuration
failed.

Fixes: c54d48543689 ("serial: stm32: Add support for rs485 RX_DURING_TX output GPIO")
Fixes: ca530cfa968c ("serial: imx: Add support for RS485 RX_DURING_TX output GPIO")
Cc: Shawn Guo <shawnguo@kernel.org>
Cc: Sascha Hauer <s.hauer@pengutronix.de>
Cc: stable@vger.kernel.org
Reviewed-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Lino Sanfilippo <l.sanfilippo@kunbus.com>
---
 drivers/tty/serial/imx.c         |  4 ----
 drivers/tty/serial/serial_core.c | 26 ++++++++++++++++++++++++--
 drivers/tty/serial/stm32-usart.c |  5 +----
 3 files changed, 25 insertions(+), 10 deletions(-)

Comments

Lino Sanfilippo Dec. 29, 2023, 3:03 p.m. UTC | #1
Hi,

On 25.12.23 at 13:31, Maarten Brock wrote:
> Lino Sanfilippo wrote on 2023-12-25 12:35:
>> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
>> index f1348a509552..d155131f221d 100644
>> --- a/drivers/tty/serial/serial_core.c
>> +++ b/drivers/tty/serial/serial_core.c
>> @@ -1402,6 +1402,16 @@ static void uart_set_rs485_termination(struct
>> uart_port *port,
>>                   !!(rs485->flags & SER_RS485_TERMINATE_BUS));
>>  }
>>
>> +static void uart_set_rs485_rx_during_tx(struct uart_port *port,
>> +                    const struct serial_rs485 *rs485)
>> +{
>> +    if (!(rs485->flags & SER_RS485_ENABLED))
>> +        return;
>> +
>
> How about checking port->rs485_rx_during_tx_gpio here against NULL instead of
> before every call?
>

gpiod_set_value_cansleep() already checks for a NULL pointer, so doing this check
in the caller is not needed.

>> +    gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
>> +                 !!(rs485->flags & SER_RS485_RX_DURING_TX));
>> +}
>> +
>>  static int uart_rs485_config(struct uart_port *port)
>>  {
>>      struct serial_rs485 *rs485 = &port->rs485;
>> @@ -1413,12 +1423,17 @@ static int uart_rs485_config(struct uart_port *port)
>>
>>      uart_sanitize_serial_rs485(port, rs485);
>>      uart_set_rs485_termination(port, rs485);
>> +    uart_set_rs485_rx_during_tx(port, rs485);
>>
>>      uart_port_lock_irqsave(port, &flags);
>>      ret = port->rs485_config(port, NULL, rs485);
>>      uart_port_unlock_irqrestore(port, flags);
>> -    if (ret)
>> +    if (ret) {
>>          memset(rs485, 0, sizeof(*rs485));
>> +        /* unset GPIOs */
>> +        gpiod_set_value_cansleep(port->rs485_term_gpio, 0);
>> +        gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio, 0);
>> +    }
>>
>>      return ret;
>>  }
>> @@ -1457,6 +1472,7 @@ static int uart_set_rs485_config(struct
>> tty_struct *tty, struct uart_port *port,
>>          return ret;
>>      uart_sanitize_serial_rs485(port, &rs485);
>>      uart_set_rs485_termination(port, &rs485);
>> +    uart_set_rs485_rx_during_tx(port, &rs485);
>>
>>      uart_port_lock_irqsave(port, &flags);
>>      ret = port->rs485_config(port, &tty->termios, &rs485);
>> @@ -1468,8 +1484,14 @@ static int uart_set_rs485_config(struct
>> tty_struct *tty, struct uart_port *port,
>>              port->ops->set_mctrl(port, port->mctrl);
>>      }
>>      uart_port_unlock_irqrestore(port, flags);
>> -    if (ret)
>> +    if (ret) {
>> +        /* restore old GPIO settings */
>> +        gpiod_set_value_cansleep(port->rs485_term_gpio,
>> +            !!(port->rs485.flags & SER_RS485_TERMINATE_BUS));
>> +        gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
>> +            !!(port->rs485.flags & SER_RS485_RX_DURING_TX));
>
> This does not look like restoring.


Hmm. The rx-during-tx and terminate-bus GPIOs may have changed before the
drivers rs485_config() was called. If that function fails, the GPIOs
are set back to the values they had before (i.e what is still stored in
the ports serial_rs485 struct). So what is wrong with the term "restore"?

> Further this looks suspiciously like duplicated code

Since the added code consists of two one-liners I am not sure how to
decrease code duplication in this case. We could introduce wrapper functions (the only
ones we have so far to set the GPIOs are uart_set_rs485_termination() and
uart_set_rs485_rx_during_tx() which cannot be used here due to the initial
check for SER_RS485_ENABLED). But would that really help?


>
>>          return ret;
>> +    }
>>
>>      if (copy_to_user(rs485_user, &port->rs485, sizeof(port->rs485)))
>>          return -EFAULT;
>> diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c
>> index 3048620315d6..ec9a72a5bea9 100644
>> --- a/drivers/tty/serial/stm32-usart.c
>> +++ b/drivers/tty/serial/stm32-usart.c
>> @@ -226,10 +226,7 @@ static int stm32_usart_config_rs485(struct
>> uart_port *port, struct ktermios *ter
>>
>>      stm32_usart_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
>>
>> -    if (port->rs485_rx_during_tx_gpio)
>> -        gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
>> -                     !!(rs485conf->flags & SER_RS485_RX_DURING_TX));
>> -    else
>> +    if (!port->rs485_rx_during_tx_gpio)
>
> Should the ! be there?
>

Thats a good point, the "else" seems indeed to be wrong. It has been introduced
with the code that added the GPIO support (c54d48543689 "serial: stm32: Add support for rs485 RX_DURING_TX output GPIO")

I will fix it in the next version of this patch, thanks.


>>          rs485conf->flags |= SER_RS485_RX_DURING_TX;
>>
>>      if (rs485conf->flags & SER_RS485_ENABLED) {
>
> Kind Regards
> Maarten Brock
>

Thanks a lot for the review.

BR,
Lino
diff mbox series

Patch

diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index 708b9852a575..9cffeb23112b 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -1943,10 +1943,6 @@  static int imx_uart_rs485_config(struct uart_port *port, struct ktermios *termio
 	    rs485conf->flags & SER_RS485_RX_DURING_TX)
 		imx_uart_start_rx(port);
 
-	if (port->rs485_rx_during_tx_gpio)
-		gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
-					 !!(rs485conf->flags & SER_RS485_RX_DURING_TX));
-
 	return 0;
 }
 
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index f1348a509552..d155131f221d 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -1402,6 +1402,16 @@  static void uart_set_rs485_termination(struct uart_port *port,
 				 !!(rs485->flags & SER_RS485_TERMINATE_BUS));
 }
 
+static void uart_set_rs485_rx_during_tx(struct uart_port *port,
+					const struct serial_rs485 *rs485)
+{
+	if (!(rs485->flags & SER_RS485_ENABLED))
+		return;
+
+	gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
+				 !!(rs485->flags & SER_RS485_RX_DURING_TX));
+}
+
 static int uart_rs485_config(struct uart_port *port)
 {
 	struct serial_rs485 *rs485 = &port->rs485;
@@ -1413,12 +1423,17 @@  static int uart_rs485_config(struct uart_port *port)
 
 	uart_sanitize_serial_rs485(port, rs485);
 	uart_set_rs485_termination(port, rs485);
+	uart_set_rs485_rx_during_tx(port, rs485);
 
 	uart_port_lock_irqsave(port, &flags);
 	ret = port->rs485_config(port, NULL, rs485);
 	uart_port_unlock_irqrestore(port, flags);
-	if (ret)
+	if (ret) {
 		memset(rs485, 0, sizeof(*rs485));
+		/* unset GPIOs */
+		gpiod_set_value_cansleep(port->rs485_term_gpio, 0);
+		gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio, 0);
+	}
 
 	return ret;
 }
@@ -1457,6 +1472,7 @@  static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
 		return ret;
 	uart_sanitize_serial_rs485(port, &rs485);
 	uart_set_rs485_termination(port, &rs485);
+	uart_set_rs485_rx_during_tx(port, &rs485);
 
 	uart_port_lock_irqsave(port, &flags);
 	ret = port->rs485_config(port, &tty->termios, &rs485);
@@ -1468,8 +1484,14 @@  static int uart_set_rs485_config(struct tty_struct *tty, struct uart_port *port,
 			port->ops->set_mctrl(port, port->mctrl);
 	}
 	uart_port_unlock_irqrestore(port, flags);
-	if (ret)
+	if (ret) {
+		/* restore old GPIO settings */
+		gpiod_set_value_cansleep(port->rs485_term_gpio,
+			!!(port->rs485.flags & SER_RS485_TERMINATE_BUS));
+		gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
+			!!(port->rs485.flags & SER_RS485_RX_DURING_TX));
 		return ret;
+	}
 
 	if (copy_to_user(rs485_user, &port->rs485, sizeof(port->rs485)))
 		return -EFAULT;
diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c
index 3048620315d6..ec9a72a5bea9 100644
--- a/drivers/tty/serial/stm32-usart.c
+++ b/drivers/tty/serial/stm32-usart.c
@@ -226,10 +226,7 @@  static int stm32_usart_config_rs485(struct uart_port *port, struct ktermios *ter
 
 	stm32_usart_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit));
 
-	if (port->rs485_rx_during_tx_gpio)
-		gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
-					 !!(rs485conf->flags & SER_RS485_RX_DURING_TX));
-	else
+	if (!port->rs485_rx_during_tx_gpio)
 		rs485conf->flags |= SER_RS485_RX_DURING_TX;
 
 	if (rs485conf->flags & SER_RS485_ENABLED) {