Message ID | 20220622154659.8710-3-LinoSanfilippo@gmx.de (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Fixes and cleanup for RS485 | expand |
On Wed, Jun 22, 2022 at 05:46:53PM +0200, Lino Sanfilippo wrote: > From: Lino Sanfilippo <l.sanfilippo@kunbus.com> > > In serial8250_em485_config() the termination GPIO is set with the uart_port > spinlock held. This is an issue if setting the GPIO line can sleep (e.g. > since the concerning GPIO expander is connected via SPI or I2C). > > Fix this by setting the termination line outside of the uart_port spinlock > in the serial core. This doesn't describe that this patch is actually changing GPIO to support sleep mode. So, it doesn't fix anything. Please rephrase the commit message accordingly. > This also makes setting the termination GPIO generic for all uart drivers. UART
On 22.06.22 at 19:06, Andy Shevchenko wrote: > On Wed, Jun 22, 2022 at 05:46:53PM +0200, Lino Sanfilippo wrote: >> From: Lino Sanfilippo <l.sanfilippo@kunbus.com> >> >> In serial8250_em485_config() the termination GPIO is set with the uart_port >> spinlock held. This is an issue if setting the GPIO line can sleep (e.g. >> since the concerning GPIO expander is connected via SPI or I2C). >> >> Fix this by setting the termination line outside of the uart_port spinlock >> in the serial core. > > This doesn't describe that this patch is actually changing GPIO to support > sleep mode. So, it doesn't fix anything. Please rephrase the commit message > accordingly. Good point, I will adjust the commit message in the next version. >> This also makes setting the termination GPIO generic for all uart drivers. > > UART > Right, upper letters should be used. Thanks a lot for the review! Regards, Lino
On Wed, 22 Jun 2022, Lino Sanfilippo wrote: > From: Lino Sanfilippo <l.sanfilippo@kunbus.com> > > In serial8250_em485_config() the termination GPIO is set with the uart_port > spinlock held. This is an issue if setting the GPIO line can sleep (e.g. > since the concerning GPIO expander is connected via SPI or I2C). > > Fix this by setting the termination line outside of the uart_port spinlock > in the serial core. > > This also makes setting the termination GPIO generic for all uart drivers. > > Signed-off-by: Lino Sanfilippo <l.sanfilippo@kunbus.com> > --- > drivers/tty/serial/8250/8250_port.c | 3 --- > drivers/tty/serial/serial_core.c | 12 ++++++++++++ > 2 files changed, 12 insertions(+), 3 deletions(-) > > diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c > index 3e3d784aa628..5245c179cc51 100644 > --- a/drivers/tty/serial/8250/8250_port.c > +++ b/drivers/tty/serial/8250/8250_port.c > @@ -675,9 +675,6 @@ int serial8250_em485_config(struct uart_port *port, struct serial_rs485 *rs485) > rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; > } > > - gpiod_set_value(port->rs485_term_gpio, > - rs485->flags & SER_RS485_TERMINATE_BUS); > - > /* > * Both serial8250_em485_init() and serial8250_em485_destroy() > * are idempotent. > diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c > index 015f4e1da647..b387376e6fa2 100644 > --- a/drivers/tty/serial/serial_core.c > +++ b/drivers/tty/serial/serial_core.c > @@ -1352,12 +1352,23 @@ static void uart_sanitize_serial_rs485(struct uart_port *port, struct serial_rs4 > memset(rs485->padding, 0, sizeof(rs485->padding)); > } > > +static void uart_set_rs485_termination(struct uart_port *port, > + const struct serial_rs485 *rs485) > +{ > + if (!port->rs485_term_gpio || !(rs485->flags & SER_RS485_ENABLED)) > + return; > + > + gpiod_set_value_cansleep(port->rs485_term_gpio, > + !!(rs485->flags & SER_RS485_TERMINATE_BUS)); > +} > + > int uart_rs485_config(struct uart_port *port) > { > struct serial_rs485 *rs485 = &port->rs485; > int ret; > > uart_sanitize_serial_rs485(port, rs485); > + uart_set_rs485_termination(port, rs485); > > ret = port->rs485_config(port, rs485); > if (ret) > @@ -1400,6 +1411,7 @@ static int uart_set_rs485_config(struct uart_port *port, > if (ret) > return ret; > uart_sanitize_serial_rs485(port, &rs485); > + uart_set_rs485_termination(port, &rs485); > > spin_lock_irqsave(&port->lock, flags); > ret = port->rs485_config(port, &rs485); When port->rs485_config(port, &rs485) returns non-zero, the input got partially applied?
On 25.06.22 at 21:58, Lukas Wunner wrote: > On Wed, Jun 22, 2022 at 05:46:53PM +0200, Lino Sanfilippo wrote: >> From: Lino Sanfilippo <l.sanfilippo@kunbus.com> >> >> In serial8250_em485_config() the termination GPIO is set with the uart_port >> spinlock held. This is an issue if setting the GPIO line can sleep (e.g. >> since the concerning GPIO expander is connected via SPI or I2C). >> >> Fix this by setting the termination line outside of the uart_port spinlock >> in the serial core. > [...] >> --- a/drivers/tty/serial/serial_core.c >> +++ b/drivers/tty/serial/serial_core.c >> @@ -1400,6 +1411,7 @@ static int uart_set_rs485_config(struct uart_port *port, >> if (ret) >> return ret; >> uart_sanitize_serial_rs485(port, &rs485); >> + uart_set_rs485_termination(port, &rs485); >> >> spin_lock_irqsave(&port->lock, flags); >> ret = port->rs485_config(port, &rs485); > > That's one way to solve the issue. Another would be to push > acquisition of the port spinlock down into drivers. > > I think in most drivers we don't need to take the port spinlock at all > or only for a few specific register accesses. So taking the lock here > in the midlayer is likely unwarranted. However, changing that requires > going through every single driver's ->rs485_config() callback and > checking whether it needs the lock or not. As a first step its sufficient to take the lock in each drivers rs485_config() function and remove it from uart_set_rs485_config(). Then after time sort out the drivers that dont require the lock and remove it from their function. However the point of this patch was also to generalize the handling of the termination GPIO, so I would still see this placed in uart_set_rs485_config(). Regards, Lino
Hi, On 25.06.22 at 12:40, Ilpo Järvinen wrote: >> + >> int uart_rs485_config(struct uart_port *port) >> { >> struct serial_rs485 *rs485 = &port->rs485; >> int ret; >> >> uart_sanitize_serial_rs485(port, rs485); >> + uart_set_rs485_termination(port, rs485); >> >> ret = port->rs485_config(port, rs485); >> if (ret) >> @@ -1400,6 +1411,7 @@ static int uart_set_rs485_config(struct uart_port *port, >> if (ret) >> return ret; >> uart_sanitize_serial_rs485(port, &rs485); >> + uart_set_rs485_termination(port, &rs485); >> >> spin_lock_irqsave(&port->lock, flags); >> ret = port->rs485_config(port, &rs485); > > When port->rs485_config(port, &rs485) returns non-zero, the input got > partially applied? > > The thing is we dont know what the state of the termination GPIO (asserted or deasserted) was before we set it and port->rs485_config() failed, so we cannot restore it. We could read the GPIO before we change it but AFAIK this is unsafe since it is an output pin. Maybe add a boolean variable "rs485_termination_gpio_asserted" to uart_port to track the current state? Regards, Lino
On Sun, 26 Jun 2022, Lino Sanfilippo wrote: > On 25.06.22 at 21:58, Lukas Wunner wrote: > > On Wed, Jun 22, 2022 at 05:46:53PM +0200, Lino Sanfilippo wrote: > >> From: Lino Sanfilippo <l.sanfilippo@kunbus.com> > >> > >> In serial8250_em485_config() the termination GPIO is set with the uart_port > >> spinlock held. This is an issue if setting the GPIO line can sleep (e.g. > >> since the concerning GPIO expander is connected via SPI or I2C). > >> > >> Fix this by setting the termination line outside of the uart_port spinlock > >> in the serial core. > > [...] > >> --- a/drivers/tty/serial/serial_core.c > >> +++ b/drivers/tty/serial/serial_core.c > >> @@ -1400,6 +1411,7 @@ static int uart_set_rs485_config(struct uart_port *port, > >> if (ret) > >> return ret; > >> uart_sanitize_serial_rs485(port, &rs485); > >> + uart_set_rs485_termination(port, &rs485); > >> > >> spin_lock_irqsave(&port->lock, flags); > >> ret = port->rs485_config(port, &rs485); > > > > That's one way to solve the issue. Another would be to push > > acquisition of the port spinlock down into drivers. > > > > I think in most drivers we don't need to take the port spinlock at all > > or only for a few specific register accesses. So taking the lock here > > in the midlayer is likely unwarranted. However, changing that requires > > going through every single driver's ->rs485_config() callback and > > checking whether it needs the lock or not. > > As a first step its sufficient to take the lock in each drivers rs485_config() > function and remove it from uart_set_rs485_config(). Then after time sort out > the drivers that dont require the lock and remove it from their function. > > However the point of this patch was also to generalize the handling of the > termination GPIO, so I would still see this placed in uart_set_rs485_config(). Additional thing to consider is that core currently handles also the port->rs485 assignment under spinlock when ->rs485_config() was successful. TIOCSRS485 ioctl calls are synchronized by other primitives wrt. each other (port mutex and now also termios_rwsem) but drivers probably would like to see consistent rs485 which can only be realized by holding port->lock.
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 3e3d784aa628..5245c179cc51 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -675,9 +675,6 @@ int serial8250_em485_config(struct uart_port *port, struct serial_rs485 *rs485) rs485->flags &= ~SER_RS485_RTS_AFTER_SEND; } - gpiod_set_value(port->rs485_term_gpio, - rs485->flags & SER_RS485_TERMINATE_BUS); - /* * Both serial8250_em485_init() and serial8250_em485_destroy() * are idempotent. diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 015f4e1da647..b387376e6fa2 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1352,12 +1352,23 @@ static void uart_sanitize_serial_rs485(struct uart_port *port, struct serial_rs4 memset(rs485->padding, 0, sizeof(rs485->padding)); } +static void uart_set_rs485_termination(struct uart_port *port, + const struct serial_rs485 *rs485) +{ + if (!port->rs485_term_gpio || !(rs485->flags & SER_RS485_ENABLED)) + return; + + gpiod_set_value_cansleep(port->rs485_term_gpio, + !!(rs485->flags & SER_RS485_TERMINATE_BUS)); +} + int uart_rs485_config(struct uart_port *port) { struct serial_rs485 *rs485 = &port->rs485; int ret; uart_sanitize_serial_rs485(port, rs485); + uart_set_rs485_termination(port, rs485); ret = port->rs485_config(port, rs485); if (ret) @@ -1400,6 +1411,7 @@ static int uart_set_rs485_config(struct uart_port *port, if (ret) return ret; uart_sanitize_serial_rs485(port, &rs485); + uart_set_rs485_termination(port, &rs485); spin_lock_irqsave(&port->lock, flags); ret = port->rs485_config(port, &rs485);