[PATCH v3 6/6] USB: serial: mxuport: support RS485 mode configuration

Crescent Hsieh <[email protected]>
Newsgroups org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Add support for TIOCSRS485 and TIOCGRS485 so that userspace can
configure the serial interface mode using struct serial_rs485.

Map the serial_rs485 flags to the device interface modes as follows:

  - RS232                  = no flags set
  - RS422                  = SER_RS485_ENABLED | SER_RS485_MODE_RS422
  - RS485_2W (half-duplex) = SER_RS485_ENABLED
  - RS485_4W (full-duplex) = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX

Unsupported flags and fields are cleared before applying the
configuration. Cache the sanitized per-port configuration and serialize
access with the per-port mutex.

Signed-off-by: Crescent Hsieh <[email protected]>
---
 drivers/usb/serial/mxuport.c | 98 +++++++++++++++++++++++++++++++++++-
 1 file changed, 97 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c
index 96dc85defb0b..319704e64528 100644
--- a/drivers/usb/serial/mxuport.c
+++ b/drivers/usb/serial/mxuport.c
@@ -208,7 +208,8 @@ struct mxuport_port {
 	u8 hold_reason;
 	u8 mcr_state;		/* Last MCR state */
 	u8 msr_state;		/* Last MSR state */
-	struct mutex mutex;	/* Protects mcr_state */
+	struct serial_rs485 rs485;
+	struct mutex mutex;	/* Protects per-port control state */
 	spinlock_t spinlock;	/* Protects msr_state */
 };
 
@@ -870,6 +871,100 @@ static int mxuport_tiocmget(struct tty_struct *tty)
 	return result;
 }
 
+static void mxuport_sanitize_serial_rs485(struct serial_rs485 *rs485)
+{
+	if (!(rs485->flags & SER_RS485_ENABLED)) {
+		memset(rs485, 0, sizeof(*rs485));
+		return;
+	}
+
+	if (rs485->flags & SER_RS485_MODE_RS422)
+		rs485->flags &= SER_RS485_ENABLED | SER_RS485_MODE_RS422;
+	else
+		rs485->flags &= SER_RS485_ENABLED | SER_RS485_RX_DURING_TX;
+
+	rs485->delay_rts_before_send = 0;
+	rs485->delay_rts_after_send = 0;
+	memset(rs485->padding, 0, sizeof(rs485->padding));
+}
+
+static int mxuport_rs485_config(struct usb_serial_port *port,
+				const struct serial_rs485 *rs485)
+{
+	struct usb_serial *serial = port->serial;
+	u16 mode = MX_INT_RS232;
+
+	if (rs485->flags & SER_RS485_ENABLED) {
+		if (rs485->flags & SER_RS485_MODE_RS422)
+			mode = MX_INT_RS422;
+		else if (rs485->flags & SER_RS485_RX_DURING_TX)
+			mode = MX_INT_4W_RS485;
+		else
+			mode = MX_INT_2W_RS485;
+	}
+
+	return mxuport_send_ctrl_urb(serial, RQ_VENDOR_SET_INTERFACE, mode,
+				     port->port_number);
+}
+
+static int mxuport_get_rs485_config(struct usb_serial_port *port,
+				    struct serial_rs485 __user *argp)
+{
+	struct mxuport_port *mxport = usb_get_serial_port_data(port);
+	struct serial_rs485 rs485;
+
+	mutex_lock(&mxport->mutex);
+	rs485 = mxport->rs485;
+	mutex_unlock(&mxport->mutex);
+
+	if (copy_to_user(argp, &rs485, sizeof(rs485)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int mxuport_set_rs485_config(struct usb_serial_port *port,
+				    struct serial_rs485 __user *argp)
+{
+	struct mxuport_port *mxport = usb_get_serial_port_data(port);
+	struct serial_rs485 rs485;
+	int err;
+
+	if (copy_from_user(&rs485, argp, sizeof(rs485)))
+		return -EFAULT;
+
+	mxuport_sanitize_serial_rs485(&rs485);
+
+	mutex_lock(&mxport->mutex);
+	err = mxuport_rs485_config(port, &rs485);
+	if (!err)
+		mxport->rs485 = rs485;
+	mutex_unlock(&mxport->mutex);
+	if (err)
+		return err;
+
+	if (copy_to_user(argp, &rs485, sizeof(rs485)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int mxuport_ioctl(struct tty_struct *tty, unsigned int cmd,
+			 unsigned long arg)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case TIOCGRS485:
+		return mxuport_get_rs485_config(port, argp);
+	case TIOCSRS485:
+		return mxuport_set_rs485_config(port, argp);
+	}
+
+	return -ENOIOCTLCMD;
+}
+
 static int mxuport_set_termios_flow(struct tty_struct *tty,
 				    const struct ktermios *old_termios,
 				    struct usb_serial_port *port,
@@ -1503,6 +1598,7 @@ static struct usb_serial_driver mxuport_device = {
 	.set_termios		= mxuport_set_termios,
 	.break_ctl		= mxuport_break_ctl,
 	.tx_empty		= mxuport_tx_empty,
+	.ioctl			= mxuport_ioctl,
 	.tiocmiwait		= usb_serial_generic_tiocmiwait,
 	.get_icount		= usb_serial_generic_get_icount,
 	.throttle		= mxuport_throttle,
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.