[PATCH v1 3/4] usb: serial: mxuport: support serial interface mode configuration

Crescent Hsieh posted 4 patches 1 week, 3 days ago
[PATCH v1 3/4] usb: serial: mxuport: support serial interface mode configuration
Posted by Crescent Hsieh 1 week, 3 days ago
Add support for configuring the serial interface mode through
TIOCSRS485 and TIOCGRS485 using struct serial_rs485.

Sanitize the requested RS-485 settings and map them to the device
interface modes before issuing the vendor command to the firmware.

This allows userspace to switch between RS232, RS422, 2-wire RS485,
and 4-wire RS485, and to query the current per-port configuration.

Signed-off-by: Crescent Hsieh <crescentcy.hsieh@moxa.com>
---
 drivers/usb/serial/mxuport.c | 86 ++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/drivers/usb/serial/mxuport.c b/drivers/usb/serial/mxuport.c
index 4d29a431cefd..9a8bb4f02da3 100644
--- a/drivers/usb/serial/mxuport.c
+++ b/drivers/usb/serial/mxuport.c
@@ -183,6 +183,7 @@ struct mxuport_port {
 	u32 hold_reason;
 	u8 mcr_state;		/* Last MCR state */
 	u8 msr_state;		/* Last MSR state */
+	struct serial_rs485 rs485;
 	struct mutex mutex;	/* Protects mcr_state */
 	spinlock_t spinlock;	/* Protects msr_state */
 };
@@ -1348,6 +1349,90 @@ static int mxuport_open(struct tty_struct *tty, struct usb_serial_port *port)
 	return err;
 }
 
+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);
+		return;
+	}
+	rs485->flags &= (SER_RS485_ENABLED | SER_RS485_RX_DURING_TX);
+
+	memset(rs485->padding, 0, sizeof(rs485->padding));
+}
+
+static int mxuport_rs485_config(struct usb_serial_port *port,
+				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 *rs485)
+{
+	struct mxuport_port *mxport = usb_get_serial_port_data(port);
+
+	if (copy_to_user(rs485, &mxport->rs485, sizeof(mxport->rs485)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int mxuport_set_rs485_config(struct usb_serial_port *port,
+				    struct serial_rs485 __user *rs485_user)
+{
+	struct mxuport_port *mxport = usb_get_serial_port_data(port);
+	struct serial_rs485 rs485;
+	int ret;
+
+	if (copy_from_user(&rs485, rs485_user, sizeof(*rs485_user)))
+		return -EFAULT;
+
+	mxuport_sanitize_serial_rs485(&rs485);
+
+	ret = mxuport_rs485_config(port, &rs485);
+	if (!ret)
+		mxport->rs485 = rs485;
+
+	if (copy_to_user(rs485_user, &mxport->rs485, sizeof(mxport->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 *uarg = (void __user *)arg;
+
+	switch (cmd) {
+	case TIOCGRS485:
+		return mxuport_get_rs485_config(port, uarg);
+	case TIOCSRS485:
+		return mxuport_set_rs485_config(port, uarg);
+	}
+
+	return -ENOIOCTLCMD;
+}
+
 static void mxuport_close(struct usb_serial_port *port)
 {
 	struct usb_serial *serial = port->serial;
@@ -1421,6 +1506,7 @@ static struct usb_serial_driver mxuport_device = {
 	.calc_num_ports		= mxuport_calc_num_ports,
 	.open			= mxuport_open,
 	.close			= mxuport_close,
+	.ioctl			= mxuport_ioctl,
 	.set_termios		= mxuport_set_termios,
 	.break_ctl		= mxuport_break_ctl,
 	.tx_empty		= mxuport_tx_empty,
-- 
2.43.0