Skip to contentSkip to conversation

USB: serial: cp210x: modem-control fixes

PATCH New Johan Hovold 1 participant 8 messages Jan 25, 2021 – Jan 26, 2021

Subsystems (2)
lore
Share & tools
lore

Johan Hovold

author maintainer
#0 lore raw

cover USB: serial: cp210x: modem-control fixes

This series fixes the modem-control handling and its interaction with hardware flow control in the cp210x driver.

Johan

Johan Hovold (7):
USB: serial: cp210x: suppress modem-control errors
USB: serial: cp210x: fix modem-control handling
USB: serial: cp210x: drop shift macros
USB: serial: cp210x: clean up flow-control debug message
USB: serial: cp210x: clean up printk zero padding
USB: serial: cp210x: fix RTS handling
USB: serial: cp210x: clean up auto-RTS handling

 drivers/usb/serial/cp210x.c | 113 ++++++++++++++++++++++++++++--------
 1 file changed, 89 insertions(+), 24 deletions(-)
Signature · 1 line
2.26.2
author maintainer
#1 lore raw

PATCH 1/7 USB: serial: cp210x: suppress modem-control errors

The CP210X_SET_MHS request cannot be used to control RTS when hardware flow control (auto-RTS) is enabled and instead returns an error which is currently logged as:

	cp210x ttyUSB0: failed set request 0x7 status: -32

when opening and closing a port (and on TIOCMSET requests).

Add a crtscts flag to keep track of the hardware flow-control setting and use it to suppress any request to change RTS when auto-RTS is enabled.

Note that RTS is still deasserted when disabling the UART as part of close.

---
 drivers/usb/serial/cp210x.c | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)
drivers/usb/serial/cp210x.c +30 -3
index d813a052738f..7e4a09b42c99 100644
@@ -21,6 +21,7 @@
 #include <linux/usb/serial.h>
 #include <linux/gpio/driver.h>
 #include <linux/bitops.h>
+#include <linux/mutex.h>
 
 #define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver"
 
@@ -264,7 +265,10 @@ struct cp210x_port_private {
 	u8			bInterfaceNumber;
 	bool			event_mode;
 	enum cp210x_event_state event_state;
-	u8 lsr;
+	u8			lsr;
+
+	struct mutex		mutex;
+	bool			crtscts;
 };
 
 static struct usb_serial_driver cp210x_device = {
@@ -1117,6 +1121,7 @@ static bool cp210x_termios_change(const struct ktermios *a, const struct ktermio
 static void cp210x_set_flow_control(struct tty_struct *tty,
 		struct usb_serial_port *port, struct ktermios *old_termios)
 {
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
 	struct cp210x_special_chars chars;
 	struct cp210x_flow_ctl flow_ctl;
 	u32 flow_repl;
@@ -1143,10 +1148,12 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 			return;
 	}
 
+	mutex_lock(&port_priv->mutex);
+
 	ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl,
 			sizeof(flow_ctl));
 	if (ret)
-		return;
+		goto out_unlock;
 
 	ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake);
 	flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace);
@@ -1161,10 +1168,12 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 		ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
 		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
 		flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL);
+		port_priv->crtscts = true;
 	} else {
 		ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
 		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
 		flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE);
+		port_priv->crtscts = false;
 	}
 
 	if (I_IXOFF(tty))
@@ -1188,6 +1197,8 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 
 	cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl,
 			sizeof(flow_ctl));
+out_unlock:
+	mutex_unlock(&port_priv->mutex);
 }
 
 static void cp210x_set_termios(struct tty_struct *tty,
@@ -1272,7 +1283,9 @@ static int cp210x_tiocmset(struct tty_struct *tty,
 static int cp210x_tiocmset_port(struct usb_serial_port *port,
 		unsigned int set, unsigned int clear)
 {
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
 	u16 control = 0;
+	int ret;
 
 	if (set & TIOCM_RTS) {
 		control |= CONTROL_RTS;
@@ -1291,9 +1304,22 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,
 		control |= CONTROL_WRITE_DTR;
 	}
 
+	mutex_lock(&port_priv->mutex);
+
+	/*
+	 * SET_MHS cannot be used to control RTS when auto-RTS is enabled.
+	 * Note that RTS is still deasserted when disabling the UART on close.
+	 */
+	if (port_priv->crtscts)
+		control &= ~CONTROL_WRITE_RTS;
+
 	dev_dbg(&port->dev, "%s - control = 0x%.4x\n", __func__, control);
 
-	return cp210x_write_u16_reg(port, CP210X_SET_MHS, control);
+	ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control);
+
+	mutex_unlock(&port_priv->mutex);
+
+	return ret;
 }
 
 static void cp210x_dtr_rts(struct usb_serial_port *port, int on)
@@ -1770,6 +1796,7 @@ static int cp210x_port_probe(struct usb_serial_port *port)
 		return -ENOMEM;
 
 	port_priv->bInterfaceNumber = cp210x_interface_num(serial);
+	mutex_init(&port_priv->mutex);
 
 	usb_set_serial_port_data(port, port_priv);
 
Signature · 1 line
2.26.2
author maintainer
#2 lore raw

PATCH 2/7 USB: serial: cp210x: fix modem-control handling

In reply to Johan Hovold

The vendor request used to set the flow-control settings also sets the state of the modem-control lines.

Add state variables to keep track of the modem-control lines to avoid always asserting the lines whenever the flow-control settings are updated.

This specifically also avoids asserting DTR/RTS when opening a port with the line speed set to B0.

---
 drivers/usb/serial/cp210x.c | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)
drivers/usb/serial/cp210x.c +16 -4
index 7e4a09b42c99..9378b4bba34b 100644
@@ -269,6 +269,8 @@ struct cp210x_port_private {
 
 	struct mutex		mutex;
 	bool			crtscts;
+	bool			dtr;
+	bool			rts;
 };
 
 static struct usb_serial_driver cp210x_device = {
@@ -1162,7 +1164,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 	ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE;
 	ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY;
 	ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
-	ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE);
+	if (port_priv->dtr)
+		ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE);
+	else
+		ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_INACTIVE);
 
 	if (C_CRTSCTS(tty)) {
 		ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
@@ -1172,7 +1177,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 	} else {
 		ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
 		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
-		flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE);
+		if (port_priv->rts)
+			flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE);
+		else
+			flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_INACTIVE);
 		port_priv->crtscts = false;
 	}
 
@@ -1287,25 +1295,29 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,
 	u16 control = 0;
 	int ret;
 
+	mutex_lock(&port_priv->mutex);
+
 	if (set & TIOCM_RTS) {
+		port_priv->rts = true;
 		control |= CONTROL_RTS;
 		control |= CONTROL_WRITE_RTS;
 	}
 	if (set & TIOCM_DTR) {
+		port_priv->dtr = true;
 		control |= CONTROL_DTR;
 		control |= CONTROL_WRITE_DTR;
 	}
 	if (clear & TIOCM_RTS) {
+		port_priv->rts = false;
 		control &= ~CONTROL_RTS;
 		control |= CONTROL_WRITE_RTS;
 	}
 	if (clear & TIOCM_DTR) {
+		port_priv->dtr = false;
 		control &= ~CONTROL_DTR;
 		control |= CONTROL_WRITE_DTR;
 	}
 
-	mutex_lock(&port_priv->mutex);
-
 	/*
 	 * SET_MHS cannot be used to control RTS when auto-RTS is enabled.
 	 * Note that RTS is still deasserted when disabling the UART on close.
Signature · 1 line
2.26.2
author maintainer
#3 lore raw

PATCH 3/7 USB: serial: cp210x: drop shift macros

In reply to Johan Hovold

Drop the macros used to shift the flow-control settings to make the code more readable for consistency with the other requests.

---
 drivers/usb/serial/cp210x.c | 28 +++++++++++-----------------
 1 file changed, 11 insertions(+), 17 deletions(-)
drivers/usb/serial/cp210x.c +11 -17
index 9378b4bba34b..aa874641374a 100644
@@ -449,17 +449,14 @@ struct cp210x_flow_ctl {
 
 /* cp210x_flow_ctl::ulControlHandshake */
 #define CP210X_SERIAL_DTR_MASK		GENMASK(1, 0)
-#define CP210X_SERIAL_DTR_SHIFT(_mode)	(_mode)
+#define CP210X_SERIAL_DTR_INACTIVE	(0 << 0)
+#define CP210X_SERIAL_DTR_ACTIVE	(1 << 0)
+#define CP210X_SERIAL_DTR_FLOW_CTL	(2 << 0)
 #define CP210X_SERIAL_CTS_HANDSHAKE	BIT(3)
 #define CP210X_SERIAL_DSR_HANDSHAKE	BIT(4)
 #define CP210X_SERIAL_DCD_HANDSHAKE	BIT(5)
 #define CP210X_SERIAL_DSR_SENSITIVITY	BIT(6)
 
-/* values for cp210x_flow_ctl::ulControlHandshake::CP210X_SERIAL_DTR_MASK */
-#define CP210X_SERIAL_DTR_INACTIVE	0
-#define CP210X_SERIAL_DTR_ACTIVE	1
-#define CP210X_SERIAL_DTR_FLOW_CTL	2
-
 /* cp210x_flow_ctl::ulFlowReplace */
 #define CP210X_SERIAL_AUTO_TRANSMIT	BIT(0)
 #define CP210X_SERIAL_AUTO_RECEIVE	BIT(1)
@@ -467,14 +464,11 @@ struct cp210x_flow_ctl {
 #define CP210X_SERIAL_NULL_STRIPPING	BIT(3)
 #define CP210X_SERIAL_BREAK_CHAR	BIT(4)
 #define CP210X_SERIAL_RTS_MASK		GENMASK(7, 6)
-#define CP210X_SERIAL_RTS_SHIFT(_mode)	(_mode << 6)
+#define CP210X_SERIAL_RTS_INACTIVE	(0 << 6)
+#define CP210X_SERIAL_RTS_ACTIVE	(1 << 6)
+#define CP210X_SERIAL_RTS_FLOW_CTL	(2 << 6)
 #define CP210X_SERIAL_XOFF_CONTINUE	BIT(31)
 
-/* values for cp210x_flow_ctl::ulFlowReplace::CP210X_SERIAL_RTS_MASK */
-#define CP210X_SERIAL_RTS_INACTIVE	0
-#define CP210X_SERIAL_RTS_ACTIVE	1
-#define CP210X_SERIAL_RTS_FLOW_CTL	2
-
 /* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */
 struct cp210x_pin_mode {
 	u8	eci;
@@ -1165,22 +1159,22 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 	ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY;
 	ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
 	if (port_priv->dtr)
-		ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_ACTIVE);
+		ctl_hs |= CP210X_SERIAL_DTR_ACTIVE;
 	else
-		ctl_hs |= CP210X_SERIAL_DTR_SHIFT(CP210X_SERIAL_DTR_INACTIVE);
+		ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
 
 	if (C_CRTSCTS(tty)) {
 		ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
 		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
-		flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL);
+		flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
 		port_priv->crtscts = true;
 	} else {
 		ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
 		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
 		if (port_priv->rts)
-			flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_ACTIVE);
+			flow_repl |= CP210X_SERIAL_RTS_ACTIVE;
 		else
-			flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_INACTIVE);
+			flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
 		port_priv->crtscts = false;
 	}
 
Signature · 1 line
2.26.2
author maintainer
#4 lore raw

PATCH 4/7 USB: serial: cp210x: clean up flow-control debug message

In reply to Johan Hovold

Shorten the flow-control debug message by abbreviating the field names and reducing the value width to two characters. The latter improves readability since all but the least significant byte will almost always be zero anyway.

---
 drivers/usb/serial/cp210x.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/usb/serial/cp210x.c +2 -2
index aa874641374a..36ae44818c13 100644
@@ -1191,8 +1191,8 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 	flow_ctl.ulXonLimit = cpu_to_le32(128);
 	flow_ctl.ulXoffLimit = cpu_to_le32(128);
 
-	dev_dbg(&port->dev, "%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n",
-			__func__, ctl_hs, flow_repl);
+	dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n", __func__,
+			ctl_hs, flow_repl);
 
 	flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs);
 	flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl);
Signature · 1 line
2.26.2
author maintainer
#5 lore raw

PATCH 5/7 USB: serial: cp210x: clean up printk zero padding

In reply to Johan Hovold

Use the 0-flag and a field width to specify zero-padding consistently in printk messages.

---
 drivers/usb/serial/cp210x.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/usb/serial/cp210x.c +2 -2
index 36ae44818c13..4ba3fb096bf1 100644
@@ -1319,7 +1319,7 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,
 	if (port_priv->crtscts)
 		control &= ~CONTROL_WRITE_RTS;
 
-	dev_dbg(&port->dev, "%s - control = 0x%.4x\n", __func__, control);
+	dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control);
 
 	ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control);
 
@@ -1353,7 +1353,7 @@ static int cp210x_tiocmget(struct tty_struct *tty)
 		|((control & CONTROL_RING)? TIOCM_RI  : 0)
 		|((control & CONTROL_DCD) ? TIOCM_CD  : 0);
 
-	dev_dbg(&port->dev, "%s - control = 0x%.2x\n", __func__, control);
+	dev_dbg(&port->dev, "%s - control = 0x%02x\n", __func__, control);
 
 	return result;
 }
Signature · 1 line
2.26.2
author maintainer
#6 lore raw

PATCH 6/7 USB: serial: cp210x: fix RTS handling

In reply to Johan Hovold

Clearing TIOCM_RTS should always deassert RTS and setting the same bit should enable auto-RTS if hardware flow control is enabled.

This allows user space to throttle input directly at the source also when hardware-assisted flow control is enabled and makes dtr_rts() always deassert both lines during close (when HUPCL is set).

---
 drivers/usb/serial/cp210x.c | 47 +++++++++++++++++++++++++++++++------
 1 file changed, 40 insertions(+), 7 deletions(-)
drivers/usb/serial/cp210x.c +40 -7
index 4ba3fb096bf1..f00b736f3cd3 100644
@@ -1166,7 +1166,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 	if (C_CRTSCTS(tty)) {
 		ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
 		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
-		flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
+		if (port_priv->rts)
+			flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
+		else
+			flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
 		port_priv->crtscts = true;
 	} else {
 		ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
@@ -1286,6 +1289,8 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,
 		unsigned int set, unsigned int clear)
 {
 	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+	struct cp210x_flow_ctl flow_ctl;
+	u32 ctl_hs, flow_repl;
 	u16 control = 0;
 	int ret;
 
@@ -1313,16 +1318,44 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,
 	}
 
 	/*
-	 * SET_MHS cannot be used to control RTS when auto-RTS is enabled.
-	 * Note that RTS is still deasserted when disabling the UART on close.
+	 * Use SET_FLOW to set DTR and enable/disable auto-RTS when hardware
+	 * flow control is enabled.
 	 */
-	if (port_priv->crtscts)
-		control &= ~CONTROL_WRITE_RTS;
+	if (port_priv->crtscts && control & CONTROL_WRITE_RTS) {
+		ret = cp210x_read_reg_block(port, CP210X_GET_FLOW, &flow_ctl,
+				sizeof(flow_ctl));
+		if (ret)
+			goto out_unlock;
 
-	dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control);
+		ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake);
+		flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace);
 
-	ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control);
+		ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
+		if (port_priv->dtr)
+			ctl_hs |= CP210X_SERIAL_DTR_ACTIVE;
+		else
+			ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
 
+		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
+		if (port_priv->rts)
+			flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
+		else
+			flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
+
+		flow_ctl.ulControlHandshake = cpu_to_le32(ctl_hs);
+		flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl);
+
+		dev_dbg(&port->dev, "%s - ctrl = 0x%02x, flow = 0x%02x\n",
+				__func__, ctl_hs, flow_repl);
+
+		ret = cp210x_write_reg_block(port, CP210X_SET_FLOW, &flow_ctl,
+				sizeof(flow_ctl));
+	} else {
+		dev_dbg(&port->dev, "%s - control = 0x%04x\n", __func__, control);
+
+		ret = cp210x_write_u16_reg(port, CP210X_SET_MHS, control);
+	}
+out_unlock:
 	mutex_unlock(&port_priv->mutex);
 
 	return ret;
Signature · 1 line
2.26.2
author maintainer
#7 lore raw

PATCH 7/7 USB: serial: cp210x: clean up auto-RTS handling

In reply to Johan Hovold

Clear the RTS bits of the flow-control request before determining the new value when updating the settings.

---
 drivers/usb/serial/cp210x.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/usb/serial/cp210x.c +1 -2
index f00b736f3cd3..cc4f63a06f9e 100644
@@ -1163,9 +1163,9 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 	else
 		ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
 
+	flow_repl &= ~CP210X_SERIAL_RTS_MASK;
 	if (C_CRTSCTS(tty)) {
 		ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
-		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
 		if (port_priv->rts)
 			flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
 		else
@@ -1173,7 +1173,6 @@ static void cp210x_set_flow_control(struct tty_struct *tty,
 		port_priv->crtscts = true;
 	} else {
 		ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
-		flow_repl &= ~CP210X_SERIAL_RTS_MASK;
 		if (port_priv->rts)
 			flow_repl |= CP210X_SERIAL_RTS_ACTIVE;
 		else
Signature · 1 line
2.26.2