Skip to contentSkip to conversation

USB: serial: add Moxa UPORT 11x0 driver

PATCH Superseded Mathieu OTHACEHE 3 participants 7 messages Dec 6, 2015 – Jan 3, 2016

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH v5 1/1 USB: serial: add Moxa UPORT 11x0 driver

Add a driver which supports :

- UPort 1110 : 1 port RS-232 USB to Serial Hub.
- UPort 1130 : 1 port RS-422/485 USB to Serial Hub.
- UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
- UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub.
- UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.

This driver is based on GPL MOXA driver written by Hen Huang and available on MOXA website. The original driver was based on io_ti serial driver.

---

Hi Johan,

Thanks for your patience, here is v5,

Mathieu

Changes in v2:
* Use generic implementation for read/write code
* Use TIOCSRS485/TIOCGRS485 instead of moxa specific ioctl
* Other problems pointed out by Johan review

Changes in v3:
* Drop unused defines
* Fix style problems
* Remove port identification code since this driver is for one-port devices only
* Fix debug messages
* Use a mutex to protect shadow_mc
* Use usb_serial_generic_open and close
* Other problems pointed out by Johan review

Changes in v4:
* mxu1_startup return -ENODEV on purpose if the firmware was downloaded. It avoids port_probe to fail since the device is reset.

Changes in v5:
* Drop debug messages
* Drop useless fields from mxu1_port and mxu1_device structures
* Return supported bits to userspace in function mxu1_ioctl_set_rs485
* Other problems pointed out by Johan review

 drivers/usb/serial/Kconfig   |   16 +
 drivers/usb/serial/Makefile  |    1 +
 drivers/usb/serial/mxu11x0.c | 1069 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1086 insertions(+)
 create mode 100644 drivers/usb/serial/mxu11x0.c
drivers/usb/serial/Kconfig +16 -0
index 56ecb8b..f612dda 100644
@@ -475,6 +475,22 @@ config USB_SERIAL_MOS7840
 	  To compile this driver as a module, choose M here: the
 	  module will be called mos7840.  If unsure, choose N.
 
+config USB_SERIAL_MXUPORT11
+	tristate "USB Moxa UPORT 11x0 Serial Driver"
+	---help---
+	  Say Y here if you want to use a MOXA UPort 11x0 Serial hub.
+
+	  This driver supports:
+
+	  - UPort 1110  : 1 port RS-232 USB to Serial Hub.
+	  - UPort 1130  : 1 port RS-422/485 USB to Serial Hub.
+	  - UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
+	  - UPort 1150  : 1 port RS-232/422/485 USB to Serial Hub.
+	  - UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxu11x0.
+
 config USB_SERIAL_MXUPORT
 	tristate "USB Moxa UPORT Serial Driver"
 	---help---
drivers/usb/serial/Makefile +1 -0
index 349d9df..f3fa5e5 100644
@@ -38,6 +38,7 @@ obj-$(CONFIG_USB_SERIAL_METRO)			+= metro-usb.o
 obj-$(CONFIG_USB_SERIAL_MOS7720)		+= mos7720.o
 obj-$(CONFIG_USB_SERIAL_MOS7840)		+= mos7840.o
 obj-$(CONFIG_USB_SERIAL_MXUPORT)		+= mxuport.o
+obj-$(CONFIG_USB_SERIAL_MXUPORT11)		+= mxu11x0.o
 obj-$(CONFIG_USB_SERIAL_NAVMAN)			+= navman.o
 obj-$(CONFIG_USB_SERIAL_OMNINET)		+= omninet.o
 obj-$(CONFIG_USB_SERIAL_OPTICON)		+= opticon.o
drivers/usb/serial/mxu11x0.c new file +1069 -0

1,069 lines not on this page · raw message

Signature · 1 line
2.6.3
maintainer
#1 lore raw
Quoted 11 lines · Mathieu OTHACEHE

Add a driver which supports :

- UPort 1110 : 1 port RS-232 USB to Serial Hub.
- UPort 1130 : 1 port RS-422/485 USB to Serial Hub.
- UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
- UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub.
- UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.

This driver is based on GPL MOXA driver written by Hen Huang and available on MOXA website. The original driver was based on io_ti serial driver.

Quoted 6 lines

---

Hi Johan,

Thanks for your patience, here is v5,

Thanks for the update.

The code itself is in a state ready to be merged, but I have some concerns about the transceiver-mode interface and also a question about firmware availability.

drivers/usb/serial/mxu11x0.c line 135

Quoted 4 lines
drivers/usb/serial/mxu11x0.c new file +4 -0
+/* Operation modes */
+#define MXU1_UART_232				0x00
+#define MXU1_UART_485_RECEIVER_DISABLED		0x01
+#define MXU1_UART_485_RECEIVER_ENABLED		0x02

These modes correspond to RS-232, RS-485-2W and RS-422/RS-485-4W.

It is known that the TIOCSRS485-interface is not generic enough as it is essentially limited to switching between full and half-duplex mode. There has been been discussions about extending it, see for example this thread from 2011:

	https://lkml.kernel.org/r/[email protected]

However, the TIOCSRS485-ioctl might not even be the right interface for changing transceiver modes, and instead a sysfs-based usb-serial-specific interface could be more appropriate for example.

This is a can that we've kicked down the road for a very long time now, and we really need to reach some sort of conclusion on this. There are a few usb-serial drivers in mainline for devices that supports other modes than RS232 and there are out-of-tree drivers that have cooked up their own interfaces (e.g. Moxa).

Before we have resolved this, we could merge this driver without support for switching modes as we did for mxuport. If you prefer this, then just set RS232 for 1110 and 1150, pick a good default mode for 1130 and drop the RS485 ioctls for now.

Quoted 101 lines
drivers/usb/serial/mxu11x0.c new file +99 -0
+/* Supported operation types */
+#define MXU1_TYPE_RS232				BIT(0)
+#define MXU1_TYPE_RS422				BIT(1)
+#define MXU1_TYPE_RS485				BIT(2)
 
+/* Config struct */
+struct mxu1_uart_config {
+	__be16	wBaudRate;
+	__be16	wFlags;
+	u8	bDataBits;
+	u8	bParity;
+	u8	bStopBits;
+	char	cXon;
+	char	cXoff;
+	u8	bUartMode;
+} __packed;
 
+static int mxu1_port_probe(struct usb_serial_port *port)
+{
+	struct mxu1_port *mxport;
+	struct mxu1_device *mxdev;
+	struct urb *urb;
+
+	mxport = kzalloc(sizeof(struct mxu1_port), GFP_KERNEL);
+	if (!mxport)
+		return -ENOMEM;
+
+	spin_lock_init(&mxport->spinlock);
+	mutex_init(&mxport->mutex);
+
+	mxdev = usb_get_serial_data(port->serial);
+
+	urb = port->interrupt_in_urb;
+	if (!urb) {
+		dev_err(&port->dev, "%s - no interrupt urb\n", __func__);
+		return -EINVAL;
+	}
+
+	switch (mxdev->mxd_model) {
+	case MXU1_1110_PRODUCT_ID:
+		mxport->uart_types = MXU1_TYPE_RS232;
+		mxport->uart_mode = MXU1_UART_232;
+		break;
+	case MXU1_1130_PRODUCT_ID:
+	case MXU1_1131_PRODUCT_ID:
+		mxport->uart_types = MXU1_TYPE_RS422 | MXU1_TYPE_RS485;
+		mxport->uart_mode = MXU1_UART_485_RECEIVER_DISABLED;
+		break;
+	case MXU1_1150_PRODUCT_ID:
+	case MXU1_1151_PRODUCT_ID:
+		mxport->uart_types =
+			MXU1_TYPE_RS232 | MXU1_TYPE_RS422 | MXU1_TYPE_RS485;
+		mxport->uart_mode = MXU1_UART_232;
+		break;
+	}
+
+	usb_set_serial_port_data(port, mxport);
+
+	port->port.closing_wait =
+			msecs_to_jiffies(MXU1_DEFAULT_CLOSING_WAIT * 10);
+	port->port.drain_delay = 1;
+
+	return 0;
+}
+
+static int mxu1_startup(struct usb_serial *serial)
+{
+	struct mxu1_device *mxdev;
+	struct usb_device *dev = serial->dev;
+	struct usb_host_interface *cur_altsetting;
+	char fw_name[32];
+	const struct firmware *fw_p = NULL;
+	int err;
+	int status = 0;
+
+	dev_dbg(&serial->interface->dev, "%s - product 0x%04X, num configurations %d, configuration value %d\n",
+		__func__, le16_to_cpu(dev->descriptor.idProduct),
+		dev->descriptor.bNumConfigurations,
+		dev->actconfig->desc.bConfigurationValue);
+
+	/* create device structure */
+	mxdev = kzalloc(sizeof(struct mxu1_device), GFP_KERNEL);
+	if (!mxdev)
+		return -ENOMEM;
+
+	usb_set_serial_data(serial, mxdev);
+
+	mxdev->mxd_model = le16_to_cpu(dev->descriptor.idProduct);
+
+	cur_altsetting = serial->interface->cur_altsetting;
+
+	/* if we have only 1 configuration, download firmware */
+	if (cur_altsetting->desc.bNumEndpoints == 1) {
+
+		snprintf(fw_name,
+			 sizeof(fw_name),
+			 "moxa/moxa-%04x.fw",
+			 mxdev->mxd_model);
+
+		err = request_firmware(&fw_p, fw_name, &serial->interface->dev);
+		if (err) {

drivers/usb/serial/mxu11x0.c line 431

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+			dev_err(&serial->interface->dev, "failed to request firmware: %d\n",
+				err);
+			kfree(mxdev);
+			return err;
+		}
+
+		err = mxu1_download_firmware(serial, fw_p);
+		if (err) {
+			kfree(mxdev);
+			release_firmware(fw_p);

Please release the firmware before the state container to reverse allocation.

Quoted 145 lines
drivers/usb/serial/mxu11x0.c new file +144 -0
+			return err;
+		}
+
+		status = -ENODEV;
+		release_firmware(fw_p);
+	}
+
+	return status;
+}
 
+static void mxu1_set_termios(struct tty_struct *tty,
+			     struct usb_serial_port *port,
+			     struct ktermios *old_termios)
+{
+	struct mxu1_port *mxport = usb_get_serial_port_data(port);
+	struct mxu1_uart_config *config;
+	tcflag_t cflag, iflag;
+	speed_t baud;
+	int status;
+	unsigned int mcr;
+
+	cflag = tty->termios.c_cflag;
+	iflag = tty->termios.c_iflag;
+
+	if (old_termios &&
+	    !tty_termios_hw_change(&tty->termios, old_termios) &&
+	    tty->termios.c_iflag == old_termios->c_iflag) {
+		dev_dbg(&port->dev, "%s - nothing to change\n", __func__);
+		return;
+	}
+
+	dev_dbg(&port->dev,
+		"%s - clfag %08x, iflag %08x\n", __func__, cflag, iflag);
+
+	if (old_termios) {
+		dev_dbg(&port->dev, "%s - old clfag %08x, old iflag %08x\n",
+			__func__,
+			old_termios->c_cflag,
+			old_termios->c_iflag);
+	}
+
+	config = kzalloc(sizeof(*config), GFP_KERNEL);
+	if (!config)
+		return;
+
+	/* these flags must be set */
+	config->wFlags |= MXU1_UART_ENABLE_MS_INTS;
+	config->wFlags |= MXU1_UART_ENABLE_AUTO_START_DMA;
+	if (mxport->send_break)
+		config->wFlags |= MXU1_UART_SEND_BREAK_SIGNAL;
+	config->bUartMode = mxport->uart_mode;
+
+	switch (C_CSIZE(tty)) {
+	case CS5:
+		config->bDataBits = MXU1_UART_5_DATA_BITS;
+		break;
+	case CS6:
+		config->bDataBits = MXU1_UART_6_DATA_BITS;
+		break;
+	case CS7:
+		config->bDataBits = MXU1_UART_7_DATA_BITS;
+		break;
+	default:
+	case CS8:
+		config->bDataBits = MXU1_UART_8_DATA_BITS;
+		break;
+	}
+
+	if (C_PARENB(tty)) {
+		config->wFlags |= MXU1_UART_ENABLE_PARITY_CHECKING;
+		if (C_CMSPAR(tty)) {
+			if (C_PARODD(tty))
+				config->bParity = MXU1_UART_MARK_PARITY;
+			else
+				config->bParity = MXU1_UART_SPACE_PARITY;
+		} else {
+			if (C_PARODD(tty))
+				config->bParity = MXU1_UART_ODD_PARITY;
+			else
+				config->bParity = MXU1_UART_EVEN_PARITY;
+		}
+	} else {
+		config->bParity = MXU1_UART_NO_PARITY;
+	}
+
+	if (C_CSTOPB(tty))
+		config->bStopBits = MXU1_UART_2_STOP_BITS;
+	else
+		config->bStopBits = MXU1_UART_1_STOP_BITS;
+
+	if (C_CRTSCTS(tty)) {
+		/* RTS flow control must be off to drop RTS for baud rate B0 */
+		if (C_BAUD(tty) != B0)
+			config->wFlags |= MXU1_UART_ENABLE_RTS_IN;
+		config->wFlags |= MXU1_UART_ENABLE_CTS_OUT;
+	}
+
+	if (I_IXOFF(tty) || I_IXON(tty)) {
+		config->cXon  = START_CHAR(tty);
+		config->cXoff = STOP_CHAR(tty);
+
+		if (I_IXOFF(tty))
+			config->wFlags |= MXU1_UART_ENABLE_X_IN;
+
+		if (I_IXON(tty))
+			config->wFlags |= MXU1_UART_ENABLE_X_OUT;
+	}
+
+	baud = tty_get_baud_rate(tty);
+	if (!baud)
+		baud = 9600;
+	config->wBaudRate = MXU1_BAUD_BASE / baud;
+
+	dev_dbg(&port->dev, "%s - BaudRate=%d, wBaudRate=%d, wFlags=0x%04X, bDataBits=%d, bParity=%d, bStopBits=%d, cXon=%d, cXoff=%d, bUartMode=%d\n",
+		__func__, baud, config->wBaudRate, config->wFlags,
+		config->bDataBits, config->bParity, config->bStopBits,
+		config->cXon, config->cXoff, config->bUartMode);
+
+	cpu_to_be16s(&config->wBaudRate);
+	cpu_to_be16s(&config->wFlags);
+
+	status = mxu1_send_ctrl_data_urb(port->serial, MXU1_SET_CONFIG, 0,
+					 MXU1_UART1_PORT, config,
+					 sizeof(*config));
+	if (status)
+		dev_err(&port->dev, "cannot set config: %d\n", status);
+
+	mutex_lock(&mxport->mutex);
+	mcr = mxport->mcr;
+
+	if (C_BAUD(tty) == B0)
+		mcr &= ~(MXU1_MCR_DTR | MXU1_MCR_RTS);
+	else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+		mcr |= ~(MXU1_MCR_DTR | MXU1_MCR_RTS);
+
+	status = mxu1_set_mcr(port, mcr);
+	if (status)
+		dev_err(&port->dev, "cannot set modem control: %d\n", status);
+	else
+		mxport->mcr = mcr;
+
+	mutex_unlock(&mxport->mutex);
+
+	kfree(config);
+}

drivers/usb/serial/mxu11x0.c line 630

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+
+static int mxu1_ioctl_get_rs485(struct mxu1_port *mxport,
+				struct serial_rs485 __user *rs485)
+{
+	struct serial_rs485 aux;
+
+	memset(&aux, 0, sizeof(aux));
+
+	if (mxport->uart_mode == MXU1_UART_485_RECEIVER_ENABLED)
+		aux.flags = SER_RS485_ENABLED;

With the current ioctl it's not possible to discern RS422 from RS232 mode.

Quoted 22 lines
drivers/usb/serial/mxu11x0.c new file +22 -0
+
+	if (copy_to_user(rs485, &aux, sizeof(aux)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int mxu1_ioctl_set_rs485(struct usb_serial_port *port,
+				struct mxu1_port *mxport,
+				struct serial_rs485 __user *rs485_user)
+{
+	struct serial_rs485 rs485;
+	int status = 0;
+
+	if (copy_from_user(&rs485, rs485_user, sizeof(*rs485_user)))
+		return -EFAULT;
+
+	if (mxport->uart_types & (MXU1_TYPE_RS422 | MXU1_TYPE_RS485)) {
+
+		if (rs485.flags & SER_RS485_ENABLED) {
+			mxport->uart_mode = MXU1_UART_485_RECEIVER_ENABLED;
+		} else {

drivers/usb/serial/mxu11x0.c line 662

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+			if (mxport->uart_types & MXU1_TYPE_RS232)
+				mxport->uart_mode = MXU1_UART_232;
+			else
+				mxport->uart_mode =
+						MXU1_UART_485_RECEIVER_DISABLED;
+		}
+	} else {
+		dev_err(&port->dev, "RS485 not supported by this device\n");
+		status = -EINVAL;
+	}

And there's currently no way to set RS485-2W for 1150 using this interface.

Quoted 18 lines
drivers/usb/serial/mxu11x0.c new file +17 -0
+
+	memset(&rs485, 0, sizeof(rs485));
+	if (mxport->uart_mode & MXU1_UART_485_RECEIVER_ENABLED)
+		rs485.flags = SER_RS485_ENABLED;
+
+	if (copy_to_user(rs485_user, &rs485, sizeof(rs485)))
+		return -EFAULT;
+
+	return status;
+}
 
+static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	struct mxu1_port *mxport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct urb *urb;
+	int status;
+	u16 open_settings;

drivers/usb/serial/mxu11x0.c

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+
+	open_settings = (MXU1_PIPE_MODE_CONTINUOUS |
+			 MXU1_PIPE_TIMEOUT_ENABLE |
+			 (MXU1_TRANSFER_TIMEOUT << 2));
+
+	mxport->msr = 0;
+
+	dev_dbg(&port->dev, "%s - start interrupt in urb\n", __func__);
+	urb = port->interrupt_in_urb;
+	status = usb_submit_urb(urb, GFP_KERNEL);

Just use port->interrupt_in_urb directly here and drop the urb pointer.

Quoted 76 lines
drivers/usb/serial/mxu11x0.c new file +76 -0
+	if (status) {
+		dev_err(&port->dev, "failed to submit interrupt urb: %d\n",
+			status);
+		return status;
+	}
+
+	if (tty)
+		mxu1_set_termios(tty, port, NULL);
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT,
+				    open_settings, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send open command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT,
+				    0, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send start command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT,
+				    MXU1_PURGE_INPUT, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot clear input buffers: %d\n",
+			__func__, status);
+
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT,
+				    MXU1_PURGE_OUTPUT, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot clear output buffers: %d\n",
+			__func__, status);
+
+		goto unlink_int_urb;
+	}
+
+	/*
+	 * reset the data toggle on the bulk endpoints to work around bug in
+	 * host controllers where things get out of sync some times
+	 */
+	usb_clear_halt(serial->dev, port->write_urb->pipe);
+	usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+	if (tty)
+		mxu1_set_termios(tty, port, NULL);
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT,
+				    open_settings, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send open command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT,
+				    0, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send start command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	status = usb_serial_generic_open(tty, port);
+	if (status) {
+		dev_err(&port->dev, "%s - submit read urb failed: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+

drivers/usb/serial/mxu11x0.c line 940

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +9 -0
+	return status;
+
+unlink_int_urb:
+	usb_kill_urb(port->interrupt_in_urb);
+
+	return status;
+}
 
+static void mxu1_handle_new_msr(struct usb_serial_port *port,
+				struct mxu1_port *mxport, u8 msr)

Just pass the usb-serial port and use usb_get_serial_port_data to access the state container.

Quoted 26 lines
drivers/usb/serial/mxu11x0.c new file +26 -0
+{
+	struct async_icount *icount;
+	unsigned long flags;
+
+	dev_dbg(&port->dev, "%s - msr 0x%02X\n", __func__, msr);
+
+	spin_lock_irqsave(&mxport->spinlock, flags);
+	mxport->msr = msr & MXU1_MSR_MASK;
+	spin_unlock_irqrestore(&mxport->spinlock, flags);
+
+	if (msr & MXU1_MSR_DELTA_MASK) {
+		icount = &port->icount;
+		if (msr & MXU1_MSR_DELTA_CTS)
+			icount->cts++;
+		if (msr & MXU1_MSR_DELTA_DSR)
+			icount->dsr++;
+		if (msr & MXU1_MSR_DELTA_CD)
+			icount->dcd++;
+		if (msr & MXU1_MSR_DELTA_RI)
+			icount->rng++;
+
+		wake_up_interruptible(&port->port.delta_msr_wait);
+	}
+}
+
+static void mxu1_interrupt_callback(struct urb *urb)

drivers/usb/serial/mxu11x0.c line 976

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+{
+	struct usb_serial_port *port = urb->context;
+	struct mxu1_port *mxport;
+	unsigned char *data = urb->transfer_buffer;
+	int length = urb->actual_length;
+	int function;
+	int status;
+	u8 msr;
+
+	mxport = usb_get_serial_port_data(port);

Then you can drop this too.

drivers/usb/serial/mxu11x0.c line 1069

Quoted 8 lines
drivers/usb/serial/mxu11x0.c new file +8 -0
+MODULE_AUTHOR("Mathieu Othacehe <[email protected]>");
+MODULE_DESCRIPTION("MOXA UPort 11x0 USB to Serial Hub Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("moxa/moxa-1110.fw");
+MODULE_FIRMWARE("moxa/moxa-1130.fw");
+MODULE_FIRMWARE("moxa/moxa-1131.fw");
+MODULE_FIRMWARE("moxa/moxa-1150.fw");
+MODULE_FIRMWARE("moxa/moxa-1151.fw");

Where can I find these firmware images?

Thanks,
Johan

#2 lore raw
Quoted 5 lines
	https://lkml.kernel.org/r/[email protected]

However, the TIOCSRS485-ioctl might not even be the right interface for changing transceiver modes, and instead a sysfs-based usb-serial-specific interface could be more appropriate for example.

sysfs works out very badly if modes can be configured routinely at runtime. In particular the sysfs interface is completely disconnected from the vhangup() and security model of the tty interface.

If its the kind if thing just administratively configured at boot then it's a great interface, just be careful about who has access to it.

Otherwise the traditional interface for that level of configuration has probably been termiox, which we do have hooks for but don't really make much use of.

Alan

maintainer
#3 lore raw
Quoted 6 lines · One Thousand Gnomes
Quoted 5 lines
	https://lkml.kernel.org/r/[email protected]

However, the TIOCSRS485-ioctl might not even be the right interface for changing transceiver modes, and instead a sysfs-based usb-serial-specific interface could be more appropriate for example.

Quoted 6 lines

sysfs works out very badly if modes can be configured routinely at runtime. In particular the sysfs interface is completely disconnected from the vhangup() and security model of the tty interface.

If its the kind if thing just administratively configured at boot then it's a great interface, just be careful about who has access to it.

I think this is something that you only want to configure once (e.g. at boot) and before even opening the tty device as this can change the functions of the pins (e.g. DCD becoming TxD-).

If a generic usb-serial-specific interface for this makes sense, I think we should simply prevent mode changes while the port is in use.

Johan

#4 lore raw

PATCH v6 1/1 USB: serial: add Moxa UPORT 11x0 driver

Add a driver which supports :

- UPort 1110 : 1 port RS-232 USB to Serial Hub.
- UPort 1130 : 1 port RS-422/485 USB to Serial Hub.
- UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
- UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub.
- UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.

This driver is based on GPL MOXA driver written by Hen Huang and available on MOXA website. The original driver was based on io_ti serial driver.

---

Hi Johan,

Here is the v6 of the driver.

I understand the problems with the TIOCSRS485/TIOCGRS485 ioctls in this driver. For now, I prefer dropping mode switching support from the driver as you suggested.

So UPORT 1110 and 1150 only support RS232 and UPORT 1130 only support RS-485-2W. If a new interface is developped, I'll restore mode switching code.

About firmware images, I just sent a patch to linux-firmware. Here is the link :
https://lkml.org/lkml/2015/12/28/239

Thanks,
Mathieu

Changes in v2:
* Use generic implementation for read/write code
* Use TIOCSRS485/TIOCGRS485 instead of moxa specific ioctl
* Other problems pointed out by Johan review

Changes in v3:
* Drop unused defines
* Fix style problems
* Remove port identification code since this driver is for one-port devices only
* Fix debug messages
* Use a mutex to protect shadow_mc
* Use usb_serial_generic_open and close
* Other problems pointed out by Johan review

Changes in v4:
* mxu1_startup return -ENODEV on purpose if the firmware was downloaded. It avoids port_probe to fail since the device is reset.

Changes in v5:
* Drop debug messages
* Drop useless fields from mxu1_port and mxu1_device structures
* Return supported bits to userspace in function mxu1_ioctl_set_rs485
* Other problems pointed out by Johan review

Changes in v6:
* Drop RS422/RS485 ioctl mode switching
* Other problems pointed out by Johan review

 drivers/usb/serial/Kconfig   |  16 +
 drivers/usb/serial/Makefile  |   1 +
 drivers/usb/serial/mxu11x0.c | 995 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1012 insertions(+)
 create mode 100644 drivers/usb/serial/mxu11x0.c
drivers/usb/serial/Kconfig +16 -0
index 56ecb8b..f612dda 100644
@@ -475,6 +475,22 @@ config USB_SERIAL_MOS7840
 	  To compile this driver as a module, choose M here: the
 	  module will be called mos7840.  If unsure, choose N.
 
+config USB_SERIAL_MXUPORT11
+	tristate "USB Moxa UPORT 11x0 Serial Driver"
+	---help---
+	  Say Y here if you want to use a MOXA UPort 11x0 Serial hub.
+
+	  This driver supports:
+
+	  - UPort 1110  : 1 port RS-232 USB to Serial Hub.
+	  - UPort 1130  : 1 port RS-422/485 USB to Serial Hub.
+	  - UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
+	  - UPort 1150  : 1 port RS-232/422/485 USB to Serial Hub.
+	  - UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mxu11x0.
+
 config USB_SERIAL_MXUPORT
 	tristate "USB Moxa UPORT Serial Driver"
 	---help---
drivers/usb/serial/Makefile +1 -0
index 349d9df..f3fa5e5 100644
@@ -38,6 +38,7 @@ obj-$(CONFIG_USB_SERIAL_METRO)			+= metro-usb.o
 obj-$(CONFIG_USB_SERIAL_MOS7720)		+= mos7720.o
 obj-$(CONFIG_USB_SERIAL_MOS7840)		+= mos7840.o
 obj-$(CONFIG_USB_SERIAL_MXUPORT)		+= mxuport.o
+obj-$(CONFIG_USB_SERIAL_MXUPORT11)		+= mxu11x0.o
 obj-$(CONFIG_USB_SERIAL_NAVMAN)			+= navman.o
 obj-$(CONFIG_USB_SERIAL_OMNINET)		+= omninet.o
 obj-$(CONFIG_USB_SERIAL_OPTICON)		+= opticon.o
drivers/usb/serial/mxu11x0.c new file +995 -0

995 lines not on this page · raw message

Signature · 1 line
2.6.4
maintainer
#5 lore raw
Quoted 18 lines · Mathieu OTHACEHE

Add a driver which supports :

- UPort 1110 : 1 port RS-232 USB to Serial Hub.
- UPort 1130 : 1 port RS-422/485 USB to Serial Hub.
- UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
- UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub.
- UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.

This driver is based on GPL MOXA driver written by Hen Huang and available on MOXA website. The original driver was based on io_ti serial driver.

---

Hi Johan,

Here is the v6 of the driver.

Quoted 8 lines

I understand the problems with the TIOCSRS485/TIOCGRS485 ioctls in this driver.
For now, I prefer dropping mode switching support from the driver as you suggested.

So UPORT 1110 and 1150 only support RS232 and UPORT 1130 only support RS-485-2W.
If a new interface is developped, I'll restore mode switching code.

Sounds good.

Quoted 2 lines

About firmware images, I just sent a patch to linux-firmware. Here is the link :
https://lkml.org/lkml/2015/12/28/239

Thanks, I've done some quick tests using a 1150 now. When looking through the code one last time I found a few issues that I fixed up. I'll submit patches for these to the USB list.

But there are couple of things you could consider to do as follow ups as well. Details below.

Quoted 7 lines
drivers/usb/serial/mxu11x0.c new file +7 -0
+static int mxu1_port_probe(struct usb_serial_port *port)
+{
+	struct mxu1_port *mxport;
+	struct mxu1_device *mxdev;
+	struct urb *urb;
+
+	mxport = kzalloc(sizeof(struct mxu1_port), GFP_KERNEL);

drivers/usb/serial/mxu11x0.c line 348

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+	if (!mxport)
+		return -ENOMEM;
+
+	spin_lock_init(&mxport->spinlock);
+	mutex_init(&mxport->mutex);
+
+	mxdev = usb_get_serial_data(port->serial);
+
+	urb = port->interrupt_in_urb;
+	if (!urb) {

You are leaking the port private data here (fixed).

Quoted 66 lines
drivers/usb/serial/mxu11x0.c new file +66 -0
+		dev_err(&port->dev, "%s - no interrupt urb\n", __func__);
+		return -EINVAL;
+	}
+
+	switch (mxdev->mxd_model) {
+	case MXU1_1110_PRODUCT_ID:
+	case MXU1_1150_PRODUCT_ID:
+	case MXU1_1151_PRODUCT_ID:
+		mxport->uart_mode = MXU1_UART_232;
+		break;
+	case MXU1_1130_PRODUCT_ID:
+	case MXU1_1131_PRODUCT_ID:
+		mxport->uart_mode = MXU1_UART_485_RECEIVER_DISABLED;
+		break;
+	}
+
+	usb_set_serial_port_data(port, mxport);
+
+	port->port.closing_wait =
+			msecs_to_jiffies(MXU1_DEFAULT_CLOSING_WAIT * 10);
+	port->port.drain_delay = 1;
+
+	return 0;
+}
+
+static int mxu1_startup(struct usb_serial *serial)
+{
+	struct mxu1_device *mxdev;
+	struct usb_device *dev = serial->dev;
+	struct usb_host_interface *cur_altsetting;
+	char fw_name[32];
+	const struct firmware *fw_p = NULL;
+	int err;
+	int status = 0;
+
+	dev_dbg(&serial->interface->dev, "%s - product 0x%04X, num configurations %d, configuration value %d\n",
+		__func__, le16_to_cpu(dev->descriptor.idProduct),
+		dev->descriptor.bNumConfigurations,
+		dev->actconfig->desc.bConfigurationValue);
+
+	/* create device structure */
+	mxdev = kzalloc(sizeof(struct mxu1_device), GFP_KERNEL);
+	if (!mxdev)
+		return -ENOMEM;
+
+	usb_set_serial_data(serial, mxdev);
+
+	mxdev->mxd_model = le16_to_cpu(dev->descriptor.idProduct);
+
+	cur_altsetting = serial->interface->cur_altsetting;
+
+	/* if we have only 1 configuration, download firmware */
+	if (cur_altsetting->desc.bNumEndpoints == 1) {
+
+		snprintf(fw_name,
+			 sizeof(fw_name),
+			 "moxa/moxa-%04x.fw",
+			 mxdev->mxd_model);
+
+		err = request_firmware(&fw_p, fw_name, &serial->interface->dev);
+		if (err) {
+			dev_err(&serial->interface->dev, "failed to request firmware: %d\n",
+				err);
+			kfree(mxdev);
+			return err;
+		}

drivers/usb/serial/mxu11x0.c line 424

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+
+		err = mxu1_download_firmware(serial, fw_p);
+		if (err) {
+			release_firmware(fw_p);
+			kfree(mxdev);
+			return err;
+		}
+
+		status = -ENODEV;
+		release_firmware(fw_p);

And you're leaking the interface private data here as well (also fixed).

What you could consider doing as a follow up it so move both the interrupt-in urb test in port_probe and the firmware download to a new probe callback. That way you avoid the unnecessary memory allocations done by core before attach is called, and you can verify and refuse to bind to a device in case the expected endpoints are missing.

drivers/usb/serial/mxu11x0.c line 597

Quoted 9 lines
drivers/usb/serial/mxu11x0.c new file +8 -0
+	}
+
+	return status;
+}
 
+	if (C_BAUD(tty) == B0)
+		mcr &= ~(MXU1_MCR_DTR | MXU1_MCR_RTS);
+	else if (old_termios && (old_termios->c_cflag & CBAUD) == B0)
+		mcr |= ~(MXU1_MCR_DTR | MXU1_MCR_RTS);

This should have been

	mcr |= MXU1_MCR_DTR | MXU1_MCR_RTS;

Also fixed.

Quoted 73 lines
drivers/usb/serial/mxu11x0.c new file +73 -0
+static int mxu1_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	struct mxu1_port *mxport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	int status;
+	u16 open_settings;
+
+	open_settings = (MXU1_PIPE_MODE_CONTINUOUS |
+			 MXU1_PIPE_TIMEOUT_ENABLE |
+			 (MXU1_TRANSFER_TIMEOUT << 2));
+
+	mxport->msr = 0;
+
+	dev_dbg(&port->dev, "%s - start interrupt in urb\n", __func__);
+	status = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+	if (status) {
+		dev_err(&port->dev, "failed to submit interrupt urb: %d\n",
+			status);
+		return status;
+	}
+
+	if (tty)
+		mxu1_set_termios(tty, port, NULL);
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT,
+				    open_settings, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send open command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT,
+				    0, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send start command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT,
+				    MXU1_PURGE_INPUT, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot clear input buffers: %d\n",
+			__func__, status);
+
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_PURGE_PORT,
+				    MXU1_PURGE_OUTPUT, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot clear output buffers: %d\n",
+			__func__, status);
+
+		goto unlink_int_urb;
+	}
+
+	/*
+	 * reset the data toggle on the bulk endpoints to work around bug in
+	 * host controllers where things get out of sync some times
+	 */
+	usb_clear_halt(serial->dev, port->write_urb->pipe);
+	usb_clear_halt(serial->dev, port->read_urb->pipe);
+
+	if (tty)
+		mxu1_set_termios(tty, port, NULL);
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_OPEN_PORT,
+				    open_settings, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send open command: %d\n",
+			__func__, status);

drivers/usb/serial/mxu11x0.c line 835

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+		goto unlink_int_urb;
+	}
+
+	status = mxu1_send_ctrl_urb(serial, MXU1_START_PORT,
+				    0, MXU1_UART1_PORT);
+	if (status) {
+		dev_err(&port->dev, "%s - cannot send start command: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}

I noticed that you send OPEN and START twice during probe -- is that really necessary?

Quoted 17 lines
drivers/usb/serial/mxu11x0.c new file +17 -0
+	status = usb_serial_generic_open(tty, port);
+	if (status) {
+		dev_err(&port->dev, "%s - submit read urb failed: %d\n",
+			__func__, status);
+		goto unlink_int_urb;
+	}
+
+	return status;
+
+unlink_int_urb:
+	usb_kill_urb(port->interrupt_in_urb);
+
+	return status;
+}
+
+static void mxu1_close(struct usb_serial_port *port)
+{

drivers/usb/serial/mxu11x0.c line 863

Quoted 10 lines
drivers/usb/serial/mxu11x0.c new file +10 -0
+	int status;
+
+	usb_serial_generic_close(port);
+	usb_kill_urb(port->interrupt_in_urb);
+
+	status = mxu1_send_ctrl_urb(port->serial, MXU1_CLOSE_PORT,
+				    0, MXU1_UART1_PORT);
+	if (status)
+		dev_err(&port->dev, "failed to send close port command: %d\n",
+			status);

And should there not be a matching STOP request at close?

drivers/usb/serial/mxu11x0.c line 963

Quoted 4 lines
drivers/usb/serial/mxu11x0.c new file +4 -0
+static struct usb_serial_driver mxuport11_device = {
+	.driver = {
+		.owner		= THIS_MODULE,
+		.name		= "mxuport11",

I also decided to rename the usb-serial driver mxu11x0 to match the module and USB driver name.

I have applied the patch now and will post my fixes and a couple of clean ups to the list.

Thanks,
Johan

#6 lore raw

Hi Johan,

Thanks for merging !
About the follow ups, I have tested the driver on uport 1110 without double OPEN_PORT/START_PORT commands. It is still working fine so, I guess it can be removed.

Plus, I sniffed the Windows USB driver for uport 1110 and the commands sent are : OPEN_PORT, SET_CONFIG, START_PORT, CLOSE_PORT. The STOP_PORT command isn't used. I add it anyway to close callback and it doesn't modify driver's behaviour.

I will post a patch serie cleaning up control commands and adding a probe callback as you suggested.

Thanks,
Mathieu