USB: serial: add Moxa UPORT 11x0 driver
Subsystems (2)
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
@@ -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
@@ -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
Johan Hovold
maintainerQuoted 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_tiserial driver.
Quoted 6 lines
Signed-off-by Mathieu OTHACEHE <[email protected]>---
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
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
Johan Hovold
maintainerQuoted 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
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
@@ -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
@@ -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
Johan Hovold
maintainerQuoted 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_tiserial driver.Signed-off-by Mathieu OTHACEHE <[email protected]>---
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
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