2023-12-25 11:36:30

by Lino Sanfilippo

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

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 <[email protected]>
Cc: Sascha Hauer <[email protected]>
Cc: [email protected]
Reviewed-by: Hugo Villeneuve <[email protected]>
Reviewed-by: Ilpo Järvinen <[email protected]>
Signed-off-by: Lino Sanfilippo <[email protected]>
---
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(-)

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) {
--
2.43.0


2023-12-25 12:31:42

by Maarten Brock

[permalink] [raw]
Subject: Re: [PATCH v6 1/7] serial: Do not hold the port lock when setting rx-during-tx GPIO

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(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.
Further this looks suspiciously like duplicated code.

> 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?

> rs485conf->flags |= SER_RS485_RX_DURING_TX;
>
> if (rs485conf->flags & SER_RS485_ENABLED) {

Kind Regards
Maarten Brock


2023-12-29 15:05:22

by Lino Sanfilippo

[permalink] [raw]
Subject: Re: [PATCH v6 1/7] serial: Do not hold the port lock when setting rx-during-tx GPIO


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

2024-01-02 16:24:37

by Maarten Brock

[permalink] [raw]
Subject: Re: [PATCH v6 1/7] serial: Do not hold the port lock when setting rx-during-tx GPIO

Lino Sanfilippo wrote on 2023-12-29 16:03:
> 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
>>> +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.

Ah, sorry, you're right.

>>> +    gpiod_set_value_cansleep(port->rs485_rx_during_tx_gpio,
>>> +                 !!(rs485->flags & SER_RS485_RX_DURING_TX));
>>> +}
>>> +
>>> @@ -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"?

Oops, I missed that too that port-rs485 is not updated in this case.

Kind Regards,
Maarten Brock