Skip to contentSkip to conversation

USB: serial: cp210x: add support for line-status events

PATCH In review Johan Hovold 2 participants 8 messages Jul 13, 2020 – Jul 14, 2020

Subsystems (2)
lore
Share & tools
lore

Johan Hovold

author maintainer
#0 lore raw

cover USB: serial: cp210x: add support for line-status events

This series add supports for line-status events that specifically can be used to detect parity errors.

The implementation relies on the device's event-insertion mode, which can also be used to detect line-breaks and modem status changes. But as this doesn't seem to work as expected on CP2102, support has been left unimplemented for now.

Included are also a couple of cleanups.

Johan

Changes since RFC [1]:
- drop sysrq support which relies on break detection
- use 0xec (instead of 0xff) as escape character

[1] https://lore.kernel.org/r/20200703150104.GE3453@localhost

Johan Hovold (5):
USB: serial: cp210x: disable interface on errors in open
USB: serial: cp210x: add support for line-status events
USB: serial: cp210x: add support for TIOCGICOUNT
USB: serial: cp210x: drop unnecessary packed attributes
USB: serial: cp210x: use in-kernel types in port data

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

PATCH 1/5 USB: serial: cp210x: disable interface on errors in open

Try to disable the serial interface in the unlikely event that generic open() fails.

---
 drivers/usb/serial/cp210x.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)
drivers/usb/serial/cp210x.c +10 -1
index a90801ef0055..c01c7863dd1a 100644
@@ -824,7 +824,16 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
 	if (tty)
 		cp210x_change_speed(tty, port, NULL);
 
-	return usb_serial_generic_open(tty, port);
+	result = usb_serial_generic_open(tty, port);
+	if (result)
+		goto err_disable;
+
+	return 0;
+
+err_disable:
+	cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+
+	return result;
 }
 
 static void cp210x_close(struct usb_serial_port *port)
Signature · 1 line
2.26.2
author maintainer
#2 lore raw

PATCH 2/5 USB: serial: cp210x: add support for line-status events

In reply to Johan Hovold

Add support for line-status events that specifically can be used to detect and report parity errors.

Enable the device's event-insertion mode whenever input-parity checking is requested. This will insert line and modem status events into the data stream.

Note that modem-status changes appear to be buffered until a character is received (at least on CP2102) and support is therefore left unimplemented.

On at least one type of these chips (CP2102), line breaks are not reported as expected either (regardless of whether SERIAL_BREAK_CHAR is set) so do not enable event-mode when !IGNBRK is requested for now.

---
 drivers/usb/serial/cp210x.c | 189 +++++++++++++++++++++++++++++++++++-
 1 file changed, 186 insertions(+), 3 deletions(-)
drivers/usb/serial/cp210x.c +186 -3
index c01c7863dd1a..02e4acb2823b 100644
@@ -50,6 +50,9 @@ static void cp210x_release(struct usb_serial *);
 static int cp210x_port_probe(struct usb_serial_port *);
 static int cp210x_port_remove(struct usb_serial_port *);
 static void cp210x_dtr_rts(struct usb_serial_port *p, int on);
+static void cp210x_process_read_urb(struct urb *urb);
+static void cp210x_enable_event_mode(struct usb_serial_port *port);
+static void cp210x_disable_event_mode(struct usb_serial_port *port);
 
 static const struct usb_device_id id_table[] = {
 	{ USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */
@@ -253,9 +256,21 @@ struct cp210x_serial_private {
 	bool			use_actual_rate;
 };
 
+enum cp210x_event_state {
+	ES_DATA,
+	ES_ESCAPE,
+	ES_LSR,
+	ES_LSR_DATA_0,
+	ES_LSR_DATA_1,
+	ES_MSR
+};
+
 struct cp210x_port_private {
 	__u8			bInterfaceNumber;
 	bool			has_swapped_line_ctl;
+	bool			event_mode;
+	enum cp210x_event_state event_state;
+	u8 lsr;
 };
 
 static struct usb_serial_driver cp210x_device = {
@@ -281,7 +296,8 @@ static struct usb_serial_driver cp210x_device = {
 	.release		= cp210x_release,
 	.port_probe		= cp210x_port_probe,
 	.port_remove		= cp210x_port_remove,
-	.dtr_rts		= cp210x_dtr_rts
+	.dtr_rts		= cp210x_dtr_rts,
+	.process_read_urb	= cp210x_process_read_urb,
 };
 
 static struct usb_serial_driver * const serial_drivers[] = {
@@ -403,6 +419,15 @@ struct cp210x_comm_status {
  */
 #define PURGE_ALL		0x000f
 
+/* CP210X_EMBED_EVENTS */
+#define CP210X_ESCCHAR		0xec
+
+#define CP210X_LSR_OVERRUN	BIT(1)
+#define CP210X_LSR_PARITY	BIT(2)
+#define CP210X_LSR_FRAME	BIT(3)
+#define CP210X_LSR_BREAK	BIT(4)
+
+
 /* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */
 struct cp210x_flow_ctl {
 	__le32	ulControlHandshake;
@@ -809,6 +834,7 @@ static int cp210x_get_line_ctl(struct usb_serial_port *port, u16 *ctl)
 
 static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
 {
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
 	int result;
 
 	result = cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_ENABLE);
@@ -820,10 +846,14 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
 	/* Configure the termios structure */
 	cp210x_get_termios(tty, port);
 
-	/* The baud rate must be initialised on cp2104 */
-	if (tty)
+	if (tty) {
+		/* The baud rate must be initialised on cp2104 */
 		cp210x_change_speed(tty, port, NULL);
 
+		if (I_INPCK(tty))
+			cp210x_enable_event_mode(port);
+	}
+
 	result = usb_serial_generic_open(tty, port);
 	if (result)
 		goto err_disable;
@@ -832,18 +862,128 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
 
 err_disable:
 	cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+	port_priv->event_mode = false;
 
 	return result;
 }
 
 static void cp210x_close(struct usb_serial_port *port)
 {
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
 	usb_serial_generic_close(port);
 
 	/* Clear both queues; cp2108 needs this to avoid an occasional hang */
 	cp210x_write_u16_reg(port, CP210X_PURGE, PURGE_ALL);
 
 	cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE);
+
+	/* Disabling the interface disables event-insertion mode. */
+	port_priv->event_mode = false;
+}
+
+static void cp210x_process_lsr(struct usb_serial_port *port, unsigned char lsr, char *flag)
+{
+	if (lsr & CP210X_LSR_BREAK) {
+		port->icount.brk++;
+		*flag = TTY_BREAK;
+	} else if (lsr & CP210X_LSR_PARITY) {
+		port->icount.parity++;
+		*flag = TTY_PARITY;
+	} else if (lsr & CP210X_LSR_FRAME) {
+		port->icount.frame++;
+		*flag = TTY_FRAME;
+	}
+
+	if (lsr & CP210X_LSR_OVERRUN) {
+		port->icount.overrun++;
+		tty_insert_flip_char(&port->port, 0, TTY_OVERRUN);
+	}
+}
+
+static bool cp210x_process_char(struct usb_serial_port *port, unsigned char *ch, char *flag)
+{
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+
+	switch (port_priv->event_state) {
+	case ES_DATA:
+		if (*ch == CP210X_ESCCHAR) {
+			port_priv->event_state = ES_ESCAPE;
+			break;
+		}
+		return false;
+	case ES_ESCAPE:
+		switch (*ch) {
+		case 0:
+			dev_dbg(&port->dev, "%s - escape char\n", __func__);
+			*ch = CP210X_ESCCHAR;
+			port_priv->event_state = ES_DATA;
+			return false;
+		case 1:
+			port_priv->event_state = ES_LSR_DATA_0;
+			break;
+		case 2:
+			port_priv->event_state = ES_LSR;
+			break;
+		case 3:
+			port_priv->event_state = ES_MSR;
+			break;
+		default:
+			dev_err(&port->dev, "malformed event 0x%02x\n", *ch);
+			port_priv->event_state = ES_DATA;
+			break;
+		}
+		break;
+	case ES_LSR_DATA_0:
+		port_priv->lsr = *ch;
+		port_priv->event_state = ES_LSR_DATA_1;
+		break;
+	case ES_LSR_DATA_1:
+		dev_dbg(&port->dev, "%s - lsr = 0x%02x, data = 0x%02x\n",
+				__func__, port_priv->lsr, *ch);
+		cp210x_process_lsr(port, port_priv->lsr, flag);
+		port_priv->event_state = ES_DATA;
+		return false;
+	case ES_LSR:
+		dev_dbg(&port->dev, "%s - lsr = 0x%02x\n", __func__, *ch);
+		port_priv->lsr = *ch;
+		cp210x_process_lsr(port, port_priv->lsr, flag);
+		port_priv->event_state = ES_DATA;
+		break;
+	case ES_MSR:
+		dev_dbg(&port->dev, "%s - msr = 0x%02x\n", __func__, *ch);
+		/* unimplemented */
+		port_priv->event_state = ES_DATA;
+		break;
+	}
+
+	return true;
+}
+
+static void cp210x_process_read_urb(struct urb *urb)
+{
+	struct usb_serial_port *port = urb->context;
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+	unsigned char *ch = urb->transfer_buffer;
+	char flag;
+	int i;
+
+	if (!urb->actual_length)
+		return;
+
+	if (port_priv->event_mode) {
+		for (i = 0; i < urb->actual_length; i++, ch++) {
+			flag = TTY_NORMAL;
+
+			if (cp210x_process_char(port, ch, &flag))
+				continue;
+
+			tty_insert_flip_char(&port->port, *ch, flag);
+		}
+	} else {
+		tty_insert_flip_string(&port->port, ch, urb->actual_length);
+	}
+	tty_flip_buffer_push(&port->port);
 }
 
 /*
@@ -1176,6 +1316,41 @@ static void cp210x_change_speed(struct tty_struct *tty,
 	tty_encode_baud_rate(tty, baud, baud);
 }
 
+static void cp210x_enable_event_mode(struct usb_serial_port *port)
+{
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+	int ret;
+
+	if (port_priv->event_mode)
+		return;
+
+	port_priv->event_state = ES_DATA;
+	port_priv->event_mode = true;
+
+	ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, CP210X_ESCCHAR);
+	if (ret) {
+		dev_err(&port->dev, "failed to enable events: %d\n", ret);
+		port_priv->event_mode = false;
+	}
+}
+
+static void cp210x_disable_event_mode(struct usb_serial_port *port)
+{
+	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
+	int ret;
+
+	if (!port_priv->event_mode)
+		return;
+
+	ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, 0);
+	if (ret) {
+		dev_err(&port->dev, "failed to disable events: %d\n", ret);
+		return;
+	}
+
+	port_priv->event_mode = false;
+}
+
 static void cp210x_set_termios(struct tty_struct *tty,
 		struct usb_serial_port *port, struct ktermios *old_termios)
 {
@@ -1298,6 +1473,14 @@ static void cp210x_set_termios(struct tty_struct *tty,
 				sizeof(flow_ctl));
 	}
 
+	/*
+	 * Enable event-insertion mode only if input parity checking is
+	 * enabled for now.
+	 */
+	if (I_INPCK(tty))
+		cp210x_enable_event_mode(port);
+	else
+		cp210x_disable_event_mode(port);
 }
 
 static int cp210x_tiocmset(struct tty_struct *tty,
Signature · 1 line
2.26.2
author maintainer
#3 lore raw

PATCH 3/5 USB: serial: cp210x: add support for TIOCGICOUNT

In reply to Johan Hovold

Enable TIOCGICOUNT to allow reading out the (unused) interrupt counters and error statistics.

Note that modem-status events are currently left unimplemented as they appear to be buffered on at least CP2102 and therefore cannot be used to implement TIOCMIWAIT.

---
 drivers/usb/serial/cp210x.c | 1 +
 1 file changed, 1 insertion(+)
drivers/usb/serial/cp210x.c +1 -0
index 02e4acb2823b..3a65be4a0ec0 100644
@@ -291,6 +291,7 @@ static struct usb_serial_driver cp210x_device = {
 	.unthrottle		= usb_serial_generic_unthrottle,
 	.tiocmget		= cp210x_tiocmget,
 	.tiocmset		= cp210x_tiocmset,
+	.get_icount		= usb_serial_generic_get_icount,
 	.attach			= cp210x_attach,
 	.disconnect		= cp210x_disconnect,
 	.release		= cp210x_release,
Signature · 1 line
2.26.2
author maintainer
#4 lore raw

PATCH 4/5 USB: serial: cp210x: drop unnecessary packed attributes

In reply to Johan Hovold

Drop unnecessary packed attributes from structs without padding.

---
 drivers/usb/serial/cp210x.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/usb/serial/cp210x.c +3 -3
index 3a65be4a0ec0..09445b7a8f64 100644
@@ -435,7 +435,7 @@ struct cp210x_flow_ctl {
 	__le32	ulFlowReplace;
 	__le32	ulXonLimit;
 	__le32	ulXoffLimit;
-} __packed;
+};
 
 /* cp210x_flow_ctl::ulControlHandshake */
 #define CP210X_SERIAL_DTR_MASK		GENMASK(1, 0)
@@ -469,7 +469,7 @@ struct cp210x_flow_ctl {
 struct cp210x_pin_mode {
 	u8	eci;
 	u8	sci;
-} __packed;
+};
 
 #define CP210X_PIN_MODE_MODEM		0
 #define CP210X_PIN_MODE_GPIO		BIT(0)
@@ -532,7 +532,7 @@ struct cp210x_single_port_config {
 struct cp210x_gpio_write {
 	u8	mask;
 	u8	state;
-} __packed;
+};
 
 /*
  * Helper to get interface number when we only have struct usb_serial.
Signature · 1 line
2.26.2
author maintainer
#5 lore raw

PATCH 5/5 USB: serial: cp210x: use in-kernel types in port data

In reply to Johan Hovold

The port data is not exported to user space so use the in-kernel u8 type.

---
 drivers/usb/serial/cp210x.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/usb/serial/cp210x.c +1 -1
index 09445b7a8f64..d0c05aa8a0d6 100644
@@ -266,7 +266,7 @@ enum cp210x_event_state {
 };
 
 struct cp210x_port_private {
-	__u8			bInterfaceNumber;
+	u8			bInterfaceNumber;
 	bool			has_swapped_line_ctl;
 	bool			event_mode;
 	enum cp210x_event_state event_state;
Signature · 1 line
2.26.2
maintainer Reviewed-by on the v1 series
#6 lore raw
In reply to Johan Hovold
Quoted 10 lines · Johan Hovold

This series add supports for line-status events that specifically can be used to detect parity errors.

The implementation relies on the device's event-insertion mode, which can also be used to detect line-breaks and modem status changes. But as this doesn't seem to work as expected on CP2102, support has been left unimplemented for now.

Included are also a couple of cleanups.

Quoted 7 lines

Johan

Changes since RFC [1]:
- drop sysrq support which relies on break detection
- use 0xec (instead of 0xff) as escape character

[1] https://lore.kernel.org/r/20200703150104.GE3453@localhost

author maintainer
#7 lore raw
Quoted 13 lines · Greg Kroah-Hartman
Quoted 12 lines · Johan Hovold

This series add supports for line-status events that specifically can be used to detect parity errors.

The implementation relies on the device's event-insertion mode, which can also be used to detect line-breaks and modem status changes. But as this doesn't seem to work as expected on CP2102, support has been left unimplemented for now.

Included are also a couple of cleanups.

Johan

Quoted 8 lines
Quoted 5 lines

Changes since RFC [1]:
- drop sysrq support which relies on break detection
- use 0xec (instead of 0xff) as escape character

[1] https://lore.kernel.org/r/20200703150104.GE3453@localhost

Thanks. Now applied.

Johan