lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250603032057.5174-2-syhuang3@nuvoton.com>
Date: Tue,  3 Jun 2025 11:20:57 +0800
From: hsyemail2@...il.com
To: Johan Hovold <johan@...nel.org>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Cc: linux-kernel@...r.kernel.org,
	linux-usb@...r.kernel.org,
	Sheng-Yuan Huang <syhuang3@...oton.com>
Subject: [PATCH v1 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter

From: Sheng-Yuan Huang <syhuang3@...oton.com>

Add support for the Nuvoton USB-to-serial adapter, which provides
multiple serial ports over a single USB interface.

The device exposes one control endpoint, one bulk-in endpoint, and
one bulk-out endpoint for data transfer. Port status is reported via
an interrupt-in or bulk-in endpoint, depending on device configuration.

This driver implements basic TTY operations.

Signed-off-by: Sheng-Yuan Huang <syhuang3@...oton.com>
---
 drivers/usb/serial/nct_usb_serial.c | 1523 +++++++++++++++++++++++++++
 1 file changed, 1523 insertions(+)
 create mode 100644 drivers/usb/serial/nct_usb_serial.c

diff --git a/drivers/usb/serial/nct_usb_serial.c b/drivers/usb/serial/nct_usb_serial.c
new file mode 100644
index 000000000000..424c604229b3
--- /dev/null
+++ b/drivers/usb/serial/nct_usb_serial.c
@@ -0,0 +1,1523 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024-2025 Nuvoton Corp.
+ * Copyright (C) 2024-2025 Sheng-Yuan Huang <syhuang3@...oton.com>
+ *
+ * Nuvoton USB to serial adapter driver
+ *
+ * This device interface consists of one control endpoint for configuration,
+ * one bulk-out endpoint used for transmitting data for all serial ports,
+ * and one bulk-in endpoint for receiving data from all serial ports.
+ * The status of the ports may be reported via either an interrupt endpoint
+ * or the bulk-in endpoint, depending on the device configuration.
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/serial.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/version.h>
+
+#define NCT_VENDOR_ID			0x0416
+#define NCT_PRODUCT_ID			0x200B
+#define NCT_USB_CLASS			0xFF
+#define NCT_USB_SUBCLASS		0x0
+#define NCT_USB_PROTOCOL		0x1
+
+#define NCT_MAX_VENDOR_READ_SIZE	8
+
+static const struct usb_device_id id_table[] = {
+	{USB_DEVICE_AND_INTERFACE_INFO(NCT_VENDOR_ID, NCT_PRODUCT_ID, NCT_USB_CLASS,
+				       NCT_USB_SUBCLASS, NCT_USB_PROTOCOL)},
+	{} /* Terminating entry */
+};
+
+#define NCT_DRVNAME "nct_mtuart"
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+#define NCT_MAX_SEND_BULK_SIZE 128
+#define NCT_EMPTY_PORT	0xFF	/* The port does not exist in FW (For device status) */
+
+/*
+ * The max loop count when disconnecting for the
+ * send-work
+ */
+#define NCT_DISCONN_QUEUE_LOOP_CNT	10
+
+/* Hardware configure */
+#define NCT_MAX_NUM_COM_DEVICES		8
+#define NCT_MAX_PACKAGE_SIZE		4096 /* The max size of one writing package */
+#define NCT_MAX_BULK_IN_SIZE		512
+#define NCT_MAX_BULK_OUT_SIZE		512
+
+#define NCT_DEFAULT_BAUD		14 /* 115200 */
+static const unsigned int NCT_BAUD_SUP[] = {
+	/* It should be the same as FW's baud-rate table */
+	B0,	B50,	B75,	B150,	B300,	B600,	B1200,
+	B1800,	B2400,	B4800,	B9600,	B19200,	B38400,	B57600,
+	B115200, B230400, B460800, B921600, B1500000
+};
+
+/* USB request */
+#define NCT_VENDOR_COM_READ_REQUEST_TYPE	0xc0
+#define NCT_VENDOR_COM_WRITE_REQUEST_TYPE	0x40
+#define NCT_VENDOR_COM_READ_REQUEST		0x01
+#define NCT_VENDOR_COM_WRITE_REQUEST		0x01
+/* Index definition */
+enum {
+	NCT_VCOM_INDEX_0 = 0,
+	NCT_VCOM_INDEX_1,
+	NCT_VCOM_INDEX_2,
+	NCT_VCOM_INDEX_3,
+	NCT_VCOM_INDEX_4,
+	NCT_VCOM_INDEX_5,
+	NCT_VCOM_INDEX_GLOBAL = 0xF,
+};
+
+/* Command */
+enum {
+	NCT_VCOM_GET_NUM_PORTS = 0,
+	NCT_VCOM_GET_PORTS_SUPPORT,
+	NCT_VCOM_GET_BAUD,
+	NCT_VCOM_SET_INIT,
+	NCT_VCOM_SET_CONFIG,
+	NCT_VCOM_SET_BAUD,
+	NCT_VCOM_SET_HCR,
+	NCT_VCOM_SET_OPEN_PORT,
+	NCT_VCOM_SET_CLOSE_PORT,
+	NCT_VCOM_SILENT,
+	/* Use bulk-in status instead of interrupt-in status */
+	NCT_VCON_SET_BULK_IN_STATUS,
+};
+
+union nct_vendor_cmd {
+	struct pkg0 {
+		u16 index:4;
+		u16 cmd:8;
+	} p;
+	u16 val;
+} __packed;
+
+#define NCT_HDR_MAGIC		0xA5
+#define NCT_HDR_MAGIC2		0x5A
+#define NCT_HDR_MAGIC_STATUS	0x5B
+
+struct nct_packet_header {
+	unsigned int magic:8;
+	unsigned int magic2:8;
+	unsigned int idx:4;
+	unsigned int len:12;
+} __packed;
+
+/* The definitions are for the feilds of nct_ctrl_msg */
+#define NCT_VCOM_1_STOP_BIT		0
+#define NCT_VCOM_2_STOP_BITS		1
+#define NCT_VCOM_PARITY_NONE		0
+#define NCT_VCOM_PARITY_ODD		1
+#define NCT_VCOM_PARITY_EVEN		2
+#define NCT_VCOM_DL5			0
+#define NCT_VCOM_DL6			1
+#define NCT_VCOM_DL7			2
+#define NCT_VCOM_DL8			3
+#define NCT_VCOM_DISABLE_FLOW_CTRL	0
+#define NCT_VCOM_XOFF			1
+#define NCT_VCOM_RTS_CTS		2
+union nct_ctrl_msg {
+	struct pkg1 {
+		u16 stop_bit:1;
+		u16 parity:2;
+		u16 data_len:2;
+		u16 flow:2;
+		u16 spd:5;
+		u16 reserved:4;
+	} p;
+	u16 val;
+} __packed;
+
+#define NCT_USR_RDR	0x01
+#define NCT_USR_ORR	0x02
+#define NCT_USR_PBER	0x04
+#define NCT_USR_NSER	0x08
+#define NCT_USR_SBD	0x10
+#define NCT_USR_TBRE	0x20
+#define NCT_USR_TSRE	0x40
+#define NCT_USR_RFEI	0x80
+#define NCT_HSR_TCTS	0x01
+#define NCT_HSR_TDSR	0x02
+#define NCT_HSR_FERI	0x04
+#define NCT_HSR_TDCD	0x08
+#define NCT_HSR_CTS	0x10
+#define NCT_HSR_DSR	0x20
+#define NCT_HSR_RI	0x40
+#define NCT_HSR_DCD	0x80
+#define NCT_HCR_DTR	0x01
+#define NCT_HCR_RTS	0x02
+#define NCT_UART_STATE_MSR_MASK (NCT_HSR_TCTS | NCT_HSR_TDSR | NCT_HSR_TDCD | NCT_HSR_DCD)
+struct nct_port_status {
+	u8 index;
+	u8 usr;
+	u8 hsr;
+	u8 hcr;
+};
+
+struct nct_serial {
+	spinlock_t serial_lock; /* Protects the private data in structure 'usb_serial' */
+	bool device_init;
+
+	/* Reading data information */
+	struct nct_tty_port *cur_port;
+	int cur_len;
+
+	bool status_trans_mode;
+	u8 en_device_mask;
+	u8 last_assigned_hw_idx;
+	struct usb_endpoint_descriptor *bulk_out_ep;
+};
+
+struct nct_tty_port {
+	union nct_ctrl_msg msg;
+
+	unsigned long sysrq; /* Sysrq timeout */
+	u8 hw_idx;
+	u8 usr;
+	u8 hsr;
+	u8 hcr;
+	/*
+	 * Flow control - stop writing data to device.
+	 * 0:Write enalbe, 1:Stop writing
+	 */
+	bool flow_stop_wrt;
+
+	spinlock_t port_lock; /* Protects the port data */
+	bool write_urb_in_use;
+};
+
+/* Functions */
+
+/* Read from USB control pipe */
+static int nct_vendor_read(struct usb_interface *intf, union nct_vendor_cmd cmd,
+			   unsigned char *buf, int size)
+{
+	struct device *dev = &intf->dev;
+	struct usb_device *udev = interface_to_usbdev(intf);
+	u8 *tmp_buf;
+	int res;
+
+	tmp_buf = kmalloc(NCT_MAX_VENDOR_READ_SIZE, GFP_KERNEL);
+	if (!tmp_buf)
+		return -ENOMEM;
+
+	if (size > NCT_MAX_VENDOR_READ_SIZE)
+		dev_err(dev, NCT_DRVNAME ": %s - failed to read [%04x]: over size %d\n",
+			__func__, cmd.p.cmd, size);
+
+	res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			      NCT_VENDOR_COM_READ_REQUEST,
+			      NCT_VENDOR_COM_READ_REQUEST_TYPE,
+			      cmd.val,
+			      intf->cur_altsetting->desc.bInterfaceNumber,
+			      tmp_buf, size, 100);
+
+	if (res < 0) {
+		dev_err(dev, NCT_DRVNAME ": %s - failed to read [%04x]: %d\n", __func__,
+			cmd.p.cmd, res);
+
+		kfree(tmp_buf);
+		return res;
+	}
+	memcpy(buf, tmp_buf, res);
+	kfree(tmp_buf);
+
+	return res;
+}
+
+static int nct_vendor_write(struct usb_interface *intf, union nct_vendor_cmd cmd, u16 val)
+{
+	struct device *dev = &intf->dev;
+	struct usb_device *udev = interface_to_usbdev(intf);
+	int res;
+	u8 *buf_val;
+
+	buf_val = kmalloc(2, GFP_KERNEL);
+	if (!buf_val)
+		return -ENOMEM;
+
+	/* Copy data to the buffer for sending */
+	buf_val[0] = val & 0xff;
+	buf_val[1] = (val >> 8) & 0xff;
+
+	res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+			      NCT_VENDOR_COM_WRITE_REQUEST,
+			      NCT_VENDOR_COM_WRITE_REQUEST_TYPE,
+			      cmd.val,
+			      intf->cur_altsetting->desc.bInterfaceNumber,
+			      buf_val,
+			      2,
+			      100);
+	kfree(buf_val);
+	if (res < 0)
+		dev_err(dev, NCT_DRVNAME ": %s - failed to write [%04x]: %d\n",
+			__func__, cmd.p.cmd, res);
+	else
+		res = 0; /* Set to 0 to align with the design. */
+
+	return res;
+}
+
+static u16 nct_set_baud(struct usb_interface *intf, u16 index, unsigned int cflag)
+{
+	union nct_ctrl_msg msg;
+	union nct_vendor_cmd cmd;
+	u16 i;
+
+	msg.val = 0;
+	cmd.p.cmd = NCT_VCOM_SET_BAUD;
+	msg.p.spd = NCT_DEFAULT_BAUD;
+	cmd.p.index = index;
+	dev_dbg(&intf->dev, NCT_DRVNAME ": %s tty baud: 0x%X\n", __func__,
+		(cflag & CBAUD));
+	for (i = 0; i < ARRAY_SIZE(NCT_BAUD_SUP); i++) {
+		if ((cflag & CBAUD) == NCT_BAUD_SUP[i]) {
+			msg.p.spd = i;
+			dev_dbg(&intf->dev,
+				NCT_DRVNAME ": %s index %d set baud: NCT_BAUD_SUP[%d]=%d\n",
+				__func__, cmd.p.index, msg.p.spd, NCT_BAUD_SUP[i]);
+			if (nct_vendor_write(intf, cmd, msg.val))
+				dev_err(&intf->dev,
+					NCT_DRVNAME ": %s - Set index: %d speed error\n",
+					__func__, cmd.p.index);
+
+			break;
+		}
+	}
+
+	return msg.p.spd;
+}
+
+static void nct_serial_set_termios(struct tty_struct *tty,
+				   struct usb_serial_port *port,
+				   const struct ktermios *old)
+{
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct usb_interface *intf = serial->interface;
+	struct ktermios *termios = &tty->termios;
+	union nct_ctrl_msg msg;
+	union nct_vendor_cmd cmd;
+	unsigned int cflag = termios->c_cflag;
+	int ret;
+	speed_t baud;
+
+	baud = tty_get_baud_rate(tty);
+	cmd.p.index = (u16)tport->hw_idx;
+	cmd.p.cmd = NCT_VCOM_SET_CONFIG;
+	msg.val = 0;
+	msg.p.stop_bit =
+	    (cflag & CSTOPB) ? (NCT_VCOM_2_STOP_BITS) : (NCT_VCOM_1_STOP_BIT);
+	if (cflag & PARENB)
+		msg.p.parity = (cflag & PARODD) ? (NCT_VCOM_PARITY_ODD) :
+						  (NCT_VCOM_PARITY_EVEN);
+	else
+		msg.p.parity = NCT_VCOM_PARITY_NONE;
+
+	switch (cflag & CSIZE) {
+	case CS5:
+		msg.p.data_len = NCT_VCOM_DL5;
+		break;
+	case CS6:
+		msg.p.data_len = NCT_VCOM_DL6;
+		break;
+	case CS7:
+		msg.p.data_len = NCT_VCOM_DL7;
+		break;
+	default:
+	case CS8:
+		msg.p.data_len = NCT_VCOM_DL8;
+		break;
+	}
+	if (C_CRTSCTS(tty)) {
+		msg.p.flow = NCT_VCOM_RTS_CTS;
+		/* Flow control - Set flag of RTSCTS */
+		tty_port_set_cts_flow(tty->port, true);
+
+	} else if (I_IXON(tty)) {
+		msg.p.flow = NCT_VCOM_XOFF;
+		/* Flow control - Clear flag of RTSCTS */
+		tty_port_set_cts_flow(tty->port, false);
+	} else {
+		msg.p.flow = NCT_VCOM_DISABLE_FLOW_CTRL;
+		/* Flow control - Clear flag of RTSCTS */
+		tty_port_set_cts_flow(tty->port, false);
+	}
+	ret = nct_vendor_write(intf, cmd, msg.val);
+	if (ret)
+		dev_err(&intf->dev,
+			NCT_DRVNAME ": %s - Set index: %d set configure error\n",
+			__func__, cmd.p.index);
+
+	tport->msg.val = msg.val;
+
+	/*
+	 * Set baud if speed changed
+	 * Note: 'nct_set_baud()' also send the speed to the FW
+	 */
+	if (!old ||
+	    old->c_cflag != termios->c_cflag ||
+	    old->c_ispeed != termios->c_ispeed ||
+	    old->c_ospeed != termios->c_ospeed)
+		tport->msg.p.spd = nct_set_baud(intf, cmd.p.index, cflag);
+
+	tty_encode_baud_rate(tty, baud, baud);
+}
+
+static int nct_serial_break(struct tty_struct *tty, int break_state)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct usb_interface *intf = serial->interface;
+	union nct_vendor_cmd cmd;
+
+	cmd.p.index = tport->hw_idx;
+	cmd.p.cmd = NCT_VCOM_SILENT;
+
+	return nct_vendor_write(intf, cmd, 0);
+}
+
+static int nct_tiocmset_helper(struct tty_struct *tty, unsigned int set,
+			       unsigned int clear)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct usb_interface *intf = serial->interface;
+	union nct_ctrl_msg msg;
+	union nct_vendor_cmd cmd;
+	unsigned long flags;
+	u8 hcr = 0;
+
+	if (set & TIOCM_RTS)
+		hcr |= NCT_HCR_RTS;
+	if (set & TIOCM_DTR)
+		hcr |= NCT_HCR_DTR;
+	if (clear & TIOCM_RTS)
+		hcr &= ~NCT_HCR_RTS;
+	if (clear & TIOCM_DTR)
+		hcr &= ~NCT_HCR_DTR;
+	cmd.p.index = (u16)tport->hw_idx;
+	cmd.p.cmd = NCT_VCOM_SET_HCR;
+	msg.val = (u16)hcr;
+	spin_lock_irqsave(&tport->port_lock, flags);
+	tport->hcr = hcr;
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	dev_dbg(&intf->dev,
+		NCT_DRVNAME ": %s: index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n",
+		__func__, cmd.p.index, cmd.p.cmd, msg.val, hcr & NCT_HCR_RTS,
+		hcr & NCT_HCR_DTR);
+
+	return nct_vendor_write(intf, cmd, msg.val);
+}
+
+static int nct_serial_tiocmget(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct usb_interface *intf = serial->interface;
+
+	unsigned long flags;
+	unsigned int res;
+	u8 hcr, hsr;
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+	hcr = tport->hcr;
+	hsr = tport->hsr;
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+	res = ((hcr & NCT_HCR_DTR) ? TIOCM_DTR : 0) |
+		((hcr & NCT_HCR_RTS) ? TIOCM_RTS : 0) |
+		((hsr & NCT_HSR_CTS) ? TIOCM_CTS : 0) |
+		((hsr & NCT_HSR_DSR) ? TIOCM_DSR : 0) |
+		((hsr & NCT_HSR_TDCD) ? TIOCM_RI : 0) |
+		((hsr & NCT_HSR_DCD) ? TIOCM_CD : 0);
+
+	dev_dbg(&intf->dev, NCT_DRVNAME ": DTR/RTS/CTS/DSR=%X,%X,%X,%X\n",
+		(hcr & NCT_HCR_DTR), (hcr & NCT_HCR_RTS),
+		(hsr & NCT_HSR_CTS), (hsr & NCT_HSR_DSR));
+
+	return res;
+}
+
+static int nct_serial_tiocmset(struct tty_struct *tty, unsigned int set,
+			       unsigned int clear)
+{
+	return nct_tiocmset_helper(tty, set, clear);
+}
+
+static void nct_rx_throttle(struct tty_struct *tty)
+{
+	unsigned int set;
+	unsigned int clear = 0;
+
+	/* If we are implementing RTS/CTS, control that line */
+	if (C_CRTSCTS(tty)) {
+		set = 0;
+		clear = TIOCM_RTS;
+		nct_tiocmset_helper(tty, set, clear);
+	}
+}
+
+static void nct_rx_unthrottle(struct tty_struct *tty)
+{
+	unsigned int set;
+	unsigned int clear = 0;
+
+	/* If we are implementing RTS/CTS, control that line */
+	if (C_CRTSCTS(tty)) {
+		set = 0;
+		set |= TIOCM_RTS;
+		nct_tiocmset_helper(tty, set, clear);
+	}
+}
+
+static int nct_serial_write_data(struct tty_struct *tty, struct usb_serial_port *port,
+				 const unsigned char *buf, int count)
+{
+	int ret;
+	unsigned long flags;
+	struct nct_packet_header hdr;
+	int wr_len;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+
+	wr_len = min((unsigned int)count, NCT_MAX_SEND_BULK_SIZE - sizeof(hdr));
+
+	if (!wr_len)
+		return 0;
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+
+	if (tport->write_urb_in_use) {
+		spin_unlock_irqrestore(&tport->port_lock, flags);
+		return 0;
+	}
+
+	/* Fill header */
+	hdr.magic = NCT_HDR_MAGIC;
+	hdr.magic2 = NCT_HDR_MAGIC2;
+	hdr.idx = tport->hw_idx; /* The 'hw_idx' is based on 1 */
+
+	/* Copy data */
+	memcpy(port->write_urb->transfer_buffer + sizeof(hdr),
+	       (const void *)buf, wr_len);
+
+	hdr.len = wr_len; /* File filed 'len' of header */
+
+	/* Filled urb data */
+	memcpy(port->write_urb->transfer_buffer, (const void *)&hdr,
+	       sizeof(hdr)); /* Copy header after filling all other fields */
+
+	/* Set urb length(Total length) */
+	port->write_urb->transfer_buffer_length = wr_len + sizeof(hdr);
+
+	port->write_urb->transfer_flags |= URB_ZERO_PACKET;
+
+	ret = usb_submit_urb(port->write_urb, GFP_ATOMIC);
+	if (ret < 0) {
+		dev_err(&port->dev,
+			NCT_DRVNAME ": %s: usb_submit_urb failed, ret=%d, hw_idx=%d\n",
+			__func__, ret, tport->hw_idx);
+	} else {
+		tport->write_urb_in_use = true; /* Set it as busy */
+		ret = wr_len + sizeof(hdr);
+	}
+
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	if (ret > sizeof(hdr))
+		ret = ret - sizeof(hdr);
+
+	dev_dbg(&port->dev, NCT_DRVNAME ": %s: returning %d\n", __func__, ret);
+	return ret;
+}
+
+static int nct_serial_write(struct tty_struct *tty, struct usb_serial_port *port,
+			    const unsigned char *buf, int count)
+{
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+
+	if (!port) {
+		pr_err(NCT_DRVNAME ": %s: port is NULL!\n", __func__);
+		return -EIO;
+	}
+	if (!port->write_urb) {
+		dev_err(&port->dev, NCT_DRVNAME ": %s: write_urb not initialized!\n",
+			__func__);
+		return -EIO;
+	}
+	if (!port->write_urb->transfer_buffer) {
+		dev_err(&port->dev, NCT_DRVNAME ": %s: transfer_buffer not initialized!\n",
+			__func__);
+		return -EIO;
+	}
+
+	/* Flow control */
+	if (tty_port_cts_enabled(tty->port))
+		if (tport->flow_stop_wrt)
+			return 0;
+
+	return nct_serial_write_data(tty, port, buf, count);
+}
+
+static void nct_write_bulk_callback(struct urb *urb)
+{
+	struct usb_serial_port *port = urb->context;
+	struct nct_tty_port *tport;
+	unsigned long flags;
+	int status = urb->status;
+
+	/* Port and serial sanity check */
+	if (!port) {
+		pr_err(NCT_DRVNAME ": %s: port is NULL, status=%d\n",
+		       __func__, status);
+		return;
+	}
+
+	tport = usb_get_serial_port_data(port);
+	if  (!tport) {
+		dev_err(&port->dev, NCT_DRVNAME ": %s: port->private is NULL, status=%d\n",
+			__func__, status);
+		return;
+	}
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+	tport->write_urb_in_use = false;
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	tty_port_tty_wakeup(&port->port);
+}
+
+static unsigned int nct_write_room(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	unsigned long flags;
+	unsigned int room;
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+	if (tport->write_urb_in_use)
+		room = 0;
+	else
+		room = NCT_MAX_SEND_BULK_SIZE - sizeof(struct nct_packet_header);
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	dev_dbg(&port->dev, NCT_DRVNAME ": %s: port=%d, room=%u\n", __func__,
+		tport->hw_idx, room);
+	return room;
+}
+
+static unsigned int nct_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	unsigned long flags;
+	unsigned int chars;
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+	if (tport->write_urb_in_use)
+		chars = NCT_MAX_SEND_BULK_SIZE - sizeof(struct nct_packet_header);
+	else
+		chars = 0;
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	dev_dbg(&port->dev, NCT_DRVNAME ": %s: port=%d, chars=%d\n", __func__,
+		tport->hw_idx, chars);
+	return chars;
+}
+
+/*
+ *  Starts reads urb on all ports. It is to avoid potential issues caused by
+ *  multiple ports being opened almost simultaneously.
+ *  It must be called AFTER startup, with urbs initialized.
+ *  Returns 0 if successful, non-zero error otherwise.
+ */
+static int nct_startup_device(struct usb_serial *serial)
+{
+	int ret = 0;
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	struct usb_serial_port *port;
+	unsigned long flags;
+
+	/* Be sure this happens exactly once */
+	spin_lock_irqsave(&serial_priv->serial_lock, flags);
+
+	if (serial_priv->device_init) {
+		spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+		return 0;
+	}
+	serial_priv->device_init = true;
+	spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+
+	/* Start reading from bulk in endpoint */
+	port = serial->port[0];
+	if (!port->read_urb)
+		dev_dbg(&port->dev, NCT_DRVNAME ": %s: port->read_urb is null, index=%d\n",
+			__func__, 0);
+
+	ret = usb_submit_urb(port->read_urb, GFP_KERNEL);
+	if (ret)
+		dev_err(&port->dev,
+			NCT_DRVNAME ": %s: usb_submit_urb failed, ret=%d, port=%d\n",
+			__func__, ret, 0);
+
+	/* For getting status from interrupt-in */
+	if (!serial_priv->status_trans_mode) {
+		/* Start reading from interrupt pipe */
+		port = serial->port[0];
+		ret = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+		if (ret)
+			dev_err(&port->dev,
+				NCT_DRVNAME ": %s: usb_submit_urb(intr) failed, ret=%d, port=%d\n",
+				__func__, ret, 0);
+	}
+	return ret;
+}
+
+static void nct_serial_port_end(struct usb_serial_port *port)
+{
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct usb_interface *intf = serial->interface;
+	union nct_ctrl_msg msg;
+	union nct_vendor_cmd cmd;
+
+	/* Send 'Close Port' to the device */
+	cmd.p.index = (u16)tport->hw_idx;
+	cmd.p.cmd = NCT_VCOM_SET_CLOSE_PORT;
+	msg.val = 0;
+	if (!intf) {
+		pr_err(NCT_DRVNAME ": %s: No intf => do not send 'close' event\n",
+		       __func__);
+		return;
+	}
+	nct_vendor_write(intf, cmd, msg.val);
+}
+
+static int nct_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	union nct_vendor_cmd cmd;
+	union nct_ctrl_msg msg;
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	struct usb_serial *serial = port->serial;
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	struct usb_interface *intf = serial->interface;
+
+	if (!port->serial)
+		return -ENXIO;
+
+	/* Allocate write_urb */
+	if (!port->write_urb) {
+		port->write_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!port->write_urb) {
+			dev_err(&port->dev, NCT_DRVNAME ": %s: Failed to allocate write URB\n",
+				__func__);
+			return -ENOMEM;
+		}
+	}
+
+	/* Allocate bulk_out_buffer */
+	port->write_urb->transfer_buffer = kmalloc(NCT_MAX_SEND_BULK_SIZE, GFP_KERNEL);
+	if (!port->write_urb->transfer_buffer) {
+		usb_free_urb(port->write_urb);
+		port->write_urb = NULL;
+		return -ENOMEM;
+	}
+
+	/* Clear(init) buffer */
+	memset(port->write_urb->transfer_buffer, 0, NCT_MAX_SEND_BULK_SIZE);
+
+	/* Set write_urb */
+	usb_fill_bulk_urb(port->write_urb, serial->dev,
+			  usb_sndbulkpipe(serial->dev, serial_priv->bulk_out_ep->bEndpointAddress),
+			  port->write_urb->transfer_buffer, NCT_MAX_SEND_BULK_SIZE,
+			  nct_write_bulk_callback, port);
+
+	/* Be sure the device is started up */
+	if (nct_startup_device(port->serial) != 0)
+		return -ENXIO;
+
+	cmd.p.index = (u16)tport->hw_idx;
+	cmd.p.cmd = NCT_VCOM_SET_OPEN_PORT;
+	msg.val = 0;
+	nct_vendor_write(intf, cmd, msg.val);
+	/*
+	 * Delay 1ms for firmware to configure hardware after opening the port.
+	 * (Especially at high speed)
+	 */
+	usleep_range(1000, 2000);
+	return 0;
+}
+
+static void nct_close(struct usb_serial_port *port)
+{
+	struct nct_tty_port *tport = usb_get_serial_port_data(port);
+	unsigned long flags;
+
+	mutex_lock(&port->serial->disc_mutex);
+	/* If disconnected, don't send the close-command to the firmware */
+	if (port->serial->disconnected)
+		goto exit;
+
+	nct_serial_port_end(port);
+
+exit:
+	/* Shutdown any outstanding bulk writes */
+	usb_kill_urb(port->write_urb);
+
+	/* Free transfer_buffer */
+	kfree(port->write_urb->transfer_buffer);
+	port->write_urb->transfer_buffer = NULL;
+
+	if (tport) {
+		spin_lock_irqsave(&tport->port_lock, flags);
+		tport->write_urb_in_use = false;
+		spin_unlock_irqrestore(&tport->port_lock, flags);
+	}
+
+	mutex_unlock(&port->serial->disc_mutex);
+}
+
+static void nct_update_status(struct usb_serial *serial, unsigned char *data)
+{
+	struct nct_port_status *nps = (struct nct_port_status *)data;
+	struct usb_interface *intf = serial->interface;
+	struct nct_tty_port *tport;
+	struct tty_struct *tty;
+	struct usb_serial_port *port;
+	unsigned long flags;
+	bool found;
+	int i;
+
+	if (nps->index >= NCT_MAX_NUM_COM_DEVICES) {
+		if (nps->index != NCT_EMPTY_PORT) /* Un-used port */
+			dev_warn(&intf->dev,
+				 NCT_DRVNAME ": %s: Receive wrong H/W index\n", __func__);
+		return;
+	}
+	if (!(nps->hsr & (NCT_UART_STATE_MSR_MASK | NCT_HSR_CTS)))
+		return; /* No any state changed. */
+	tport = NULL;
+	found = false;
+	for (i = 0; i < serial->type->num_ports; i++) {
+		port = serial->port[i];
+
+		if (!port) {
+			dev_err(&intf->dev, NCT_DRVNAME ": %s: port[%d] is NULL\n",
+				__func__, i);
+			continue;
+		}
+
+		tport = usb_get_serial_port_data(port);
+
+		if (!tport) {
+			dev_err(&intf->dev,
+				NCT_DRVNAME ": %s: Get NULL port data for port[%d]\n",
+				__func__, i);
+			continue;
+		}
+
+		if (tport->hw_idx == nps->index) {
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		dev_err(&intf->dev,
+			NCT_DRVNAME ": %s: Decode serial packet index failed.\n",
+			__func__);
+		return;
+	}
+
+	if (!tport) {
+		dev_err(&intf->dev,
+			NCT_DRVNAME ": %s: Decode serial packet index failed.\n",
+			__func__);
+		return;
+	}
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+	tport->usr = nps->usr;
+	tport->hsr = nps->hsr;
+	tport->hcr = nps->hcr;
+	tport->sysrq = (tport->sysrq & ~0x01) | (-(nps->usr & NCT_USR_SBD) & 0x01);
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	if (serial->disconnected) {
+		dev_err(&intf->dev,
+			NCT_DRVNAME ": %s: Device disconnected, skipping update_status\n",
+			__func__);
+		return;
+	}
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return; /* The port has been closed. */
+
+	if (nps->hsr & NCT_UART_STATE_MSR_MASK) {
+		if (nps->hsr & NCT_HSR_DCD) {
+			if (tty) {
+				struct tty_ldisc *ld = tty_ldisc_ref(tty);
+
+				if (ld) {
+					if (ld->ops->dcd_change)
+						ld->ops->dcd_change(tty, 0x01);
+					tty_ldisc_deref(ld);
+				}
+				wake_up_interruptible(&tty->port->open_wait);
+			}
+		}
+	}
+
+	/* Flow control */
+	if (tty_port_cts_enabled(&port->port)) {
+		if ((nps->hsr & NCT_HSR_CTS)) {
+			if (tport->flow_stop_wrt)
+				tport->flow_stop_wrt = false;
+		} else {
+			tport->flow_stop_wrt = true;
+		}
+	}
+
+	tty_kref_put(tty);
+}
+
+static void nct_usb_serial_read(struct urb *urb)
+{
+	struct usb_serial_port *port =  urb->context;
+	struct usb_serial *serial = port->serial;
+	struct usb_interface *intf = serial->interface;
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	struct nct_tty_port *tport;
+	struct nct_packet_header *hdr = NULL;
+	unsigned char *data = urb->transfer_buffer;
+	int i, j;
+	int actual_len = urb->actual_length;
+	int len = 0;
+	struct nct_port_status *nps;
+	unsigned long flags;
+
+	if (!urb->actual_length)
+		return;
+
+again:
+	spin_lock_irqsave(&serial_priv->serial_lock, flags);
+	tport = serial_priv->cur_port;
+	if (!tport) {
+		/*
+		 * Handle a new data package (i.e., it is not
+		 * the remaining data without a header).
+		 * The package does not need to be combined this time.
+		 */
+
+		for (i = 0; i < urb->actual_length; i++) {
+			hdr = (struct nct_packet_header *)data;
+			/* Decode the header */
+
+			if (serial_priv->status_trans_mode) {
+				/*
+				 * Status data is also transmitted via bulk-in
+				 * pipe.
+				 */
+				if (hdr->magic == NCT_HDR_MAGIC &&
+				    hdr->magic2 == NCT_HDR_MAGIC_STATUS &&
+				    hdr->len == 24 && actual_len >= 28) {
+					/*
+					 * Notice: actual_len will be decreased,
+					 * it is equal to urb->actual_length
+					 * only at the beginning.
+					 */
+
+					/*
+					 * Status report.
+					 * It should be a standalone package in
+					 * one URB
+					 */
+					data += sizeof(struct nct_packet_header);
+					actual_len -=
+						sizeof(struct nct_packet_header);
+
+					nps = (struct nct_port_status *)data;
+
+					for (j = 0; j < actual_len - 4; j++) {
+						nct_update_status(serial,
+								  (unsigned char *)nps);
+						nps++;
+					}
+
+					spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+					return;
+				}
+			}
+
+			if (hdr->magic == NCT_HDR_MAGIC &&
+			    hdr->magic2 == NCT_HDR_MAGIC2 &&
+			    hdr->idx <= NCT_MAX_NUM_COM_DEVICES &&
+			    hdr->len <= 512)
+				break;
+
+			data++;
+			actual_len--;
+			if (!actual_len) {
+				dev_err(&intf->dev, NCT_DRVNAME
+					": %s: Decode serial packet size failed.\n", __func__);
+				spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+				return;
+			}
+		}
+		/*
+		 * Search tty port
+		 * Search the tty device by the idx in header, and check if
+		 * it is registered or opened.
+		 * If it is, record them. The record will be used later for
+		 * 2 purposes:
+		 *   (1) If the current data package is incomplete, the following
+		 *       incoming data will not include a header.
+		 *   (2) To determine which device will be used for transmission.
+		 */
+		tport = NULL;
+		for (i = 0; i < serial->type->num_ports; i++) {
+			port = serial->port[i];
+			tport = usb_get_serial_port_data(port);
+			if (tport->hw_idx != hdr->idx)
+				continue;
+
+			break;
+		}
+		if (!tport) {
+			dev_err(&intf->dev,
+				NCT_DRVNAME ": %s: Decode serial packet index failed.\n",
+				__func__);
+			spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+			return;
+		}
+		/*
+		 * Calculate the data length.
+		 * Then, check if the length specified in the header matches
+		 * the data length. If not, it indicates that the data we
+		 * received spans across two (or more) packets.
+		 */
+		actual_len -= sizeof(struct nct_packet_header);
+		data += sizeof(struct nct_packet_header);
+		/* actual_len: the data length of the data we got this time */
+		if (hdr->len > actual_len) {
+			/*
+			 * It means the length specified in the header (the
+			 * custom header) is greater than the length of the
+			 * data we received.
+			 * Therefore, the data we received this time does not
+			 * span across another packet (i.e. no new header).
+			 */
+			len = actual_len;
+			/*
+			 * cur_len: Record how many data does not handle yet
+			 */
+			serial_priv->cur_len = hdr->len - len;
+			/*
+			 * Record the current port. When we got remained data of
+			 * the package next time
+			 */
+			serial_priv->cur_port = tport;
+		} else {
+			/*
+			 * The data we got crosses packages(not belong
+			 * to the same header). We only handle data by
+			 * the length in header. And we will handle
+			 * another package when 'goto "again" '.
+			 */
+			len = hdr->len;
+		}
+	} else { /*  Handling the remained data which crosses package */
+		if (serial_priv->cur_len > actual_len) {
+			/*
+			 * The unhandled part of the data exceeds the data we
+			 * received this time. We only handle the data we
+			 * have, expecting more data to be received later.
+			 */
+			len = actual_len;
+		} else {
+			/*
+			 * This means the package has been fully handled.
+			 * Clear 'cur_port' as no additional data needs to be
+			 * attached to the current package.
+			 */
+			len = serial_priv->cur_len;
+			serial_priv->cur_port = NULL;
+		}
+		serial_priv->cur_len -= len;
+	}
+	spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+	/*
+	 * The per character mucking around with sysrq path it too slow for
+	 * stuff like 3G modems, so shortcircuit it in the 99.9999999% of
+	 * cases where the USB serial is not a console anyway.
+	 */
+	if (tport->sysrq) {
+		for (i = 0; i < len; i++, data++)
+			tty_insert_flip_char(&port->port, *data, TTY_NORMAL);
+	} else {
+		tty_insert_flip_string(&port->port, data, len);
+		data += len;
+	}
+	/*
+	 * Send data to the tty device (according to the port identified above).
+	 */
+	tty_flip_buffer_push(&port->port);
+	actual_len -= len;
+
+	/*
+	 * It means that the data we received this time contains two or
+	 * more data packages, so it needs to continue processing the next
+	 * data packages.
+	 */
+	if (actual_len > 0)
+		goto again;
+}
+
+static void nct_process_read_bulk(struct urb *urb)
+{
+	struct usb_serial_port *port =  urb->context;
+	bool stopped = false;
+	int status = urb->status;
+	int ret;
+
+	switch (status) {
+	case 0:
+		nct_usb_serial_read(urb);
+		break;
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		dev_dbg(&port->dev, NCT_DRVNAME ": %s - urb stopped: %d\n",
+			__func__, status);
+		stopped = true;
+		break;
+	case -EPIPE:
+		dev_dbg(&port->dev, NCT_DRVNAME ": %s - urb stopped: %d\n",
+			__func__, status);
+		stopped = true;
+		break;
+	case -ETIME:
+		dev_dbg(&port->dev, NCT_DRVNAME ": %s - urb ETIME t: %d\n",
+			__func__, status);
+		break;
+	case -ETIMEDOUT:
+		dev_dbg(&port->dev, NCT_DRVNAME ": %s - urb ETIMEDOUT t: %d\n",
+			__func__, status);
+		break;
+	default:
+		dev_dbg(&port->dev, NCT_DRVNAME ": %s - nonzero urb status: %d\n",
+			__func__, status);
+		break;
+	}
+
+	if (stopped)
+		return;
+
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret != 0 && ret != -EPERM)
+		dev_err(&port->dev,
+			NCT_DRVNAME ": %s: failed resubmitting urb, ret=%d\n",
+			__func__, ret);
+}
+
+static void nct_read_bulk_callback(struct urb *urb)
+{
+	struct usb_serial_port *port = urb->context;
+	struct nct_serial *serial_priv;
+
+	/* Port sanity check, do not resubmit if port is not valid */
+	if (urb->status == -ESHUTDOWN)
+		return;
+
+	if (!port) {
+		pr_err(NCT_DRVNAME ": %s: port or serial is NULL, status=%d\n",
+		       __func__, urb->status);
+		return;
+	}
+
+	if (!port->serial) {
+		dev_err(&port->dev, NCT_DRVNAME ": %s: serial is NULL, status=%d\n",
+			__func__, urb->status);
+		return;
+	}
+
+	if (port->serial->disconnected)
+		return;
+
+	serial_priv = usb_get_serial_port_data(port);
+	if (!serial_priv) {
+		dev_err(&port->dev, NCT_DRVNAME ": %s: port->private is NULL, status=%d\n",
+			__func__, urb->status);
+		return;
+	}
+
+	if (!port->serial) {
+		dev_err(&port->dev, NCT_DRVNAME ": %s: serial is NULL, status=%d\n",
+			__func__, urb->status);
+		return;
+	}
+
+	serial_priv = usb_get_serial_data(port->serial);
+	if (!serial_priv) {
+		dev_err(&port->dev,
+			NCT_DRVNAME ": %s: serial->private is NULL, status=%d\n",
+			__func__, urb->status);
+		return;
+	}
+
+	/* Processing data */
+	nct_process_read_bulk(urb);
+}
+
+static int nct_usb_attach(struct usb_serial *serial)
+{
+	return 0;
+}
+
+static int nct_calc_num_ports(struct usb_serial *serial,
+			      struct usb_serial_endpoints *epds)
+{
+	union nct_vendor_cmd cmd;
+	u8 buf[8];
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	struct usb_interface *intf = serial->interface;
+	int ret;
+	int i;
+	int num_ports;
+
+	//Send init command
+	cmd.p.index = NCT_VCOM_INDEX_GLOBAL;
+	cmd.p.cmd = NCT_VCOM_SET_INIT;
+	ret = nct_vendor_write(intf, cmd, 0);
+	if (ret) {
+		dev_err(&intf->dev,
+			NCT_DRVNAME ": %s - Set COM init error\n",
+			__func__);
+		return ret;
+	}
+
+	/* Get ports' index supported by the device(/FW) */
+	cmd.p.index = NCT_VCOM_INDEX_GLOBAL;
+	cmd.p.cmd = NCT_VCOM_GET_PORTS_SUPPORT;
+	ret = nct_vendor_read(intf, cmd, buf, 1);
+	if (ret != 1) {
+		dev_err(&intf->dev,
+			NCT_DRVNAME ": %s - Get COM port index error\n",
+			__func__);
+		return 0;
+	}
+	serial_priv->en_device_mask = buf[0];
+	serial_priv->last_assigned_hw_idx = 0; /* Note: hw_idx is based on 1 */
+	dev_info(&intf->dev, NCT_DRVNAME ": %s Enabled devices mask:%X\n",
+		 __func__, buf[0]);
+
+	for (i = 0, num_ports = 0; i < NCT_MAX_NUM_COM_DEVICES; i++) {
+		if ((buf[0] & (1 << i)) == 0)
+			continue;	/* The port is disabled */
+
+		num_ports++;
+	}
+
+	return num_ports;
+}
+
+static int nct_probe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+	struct nct_serial *serial_priv;
+	int i;
+	struct usb_endpoint_descriptor *endpoint;
+	struct usb_interface *intf = serial->interface;
+	struct usb_host_interface *iface_desc;
+
+	serial_priv = kzalloc(sizeof(*serial_priv), GFP_KERNEL);
+	if (!serial_priv)
+		return -ENOMEM;
+
+	spin_lock_init(&serial_priv->serial_lock);
+	usb_set_serial_data(serial, serial_priv);
+
+	iface_desc = intf->cur_altsetting;
+
+	/* For bulk-out */
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		if (usb_endpoint_is_bulk_out(endpoint)) {
+			serial_priv->bulk_out_ep = endpoint;
+			break;
+		}
+	}
+
+	/*
+	 * Initialize the mode as 'Status data is transmitted via
+	 * bulk-in pipe'.
+	 */
+	serial_priv->status_trans_mode = true;
+	serial->type->num_interrupt_in = 0;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+		endpoint = &iface_desc->endpoint[i].desc;
+
+		if (usb_endpoint_is_int_in(endpoint)) {
+			/* Status data is transmitted via interrupt-in pipe. */
+			serial_priv->status_trans_mode = false;
+			serial->type->num_interrupt_in = 1;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int nct_port_init(struct usb_serial_port *port, unsigned int port_num)
+{
+	struct nct_tty_port *tport;
+	struct usb_serial *serial = port->serial;
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	unsigned long flags;
+	int i;
+
+	tport = kzalloc(sizeof(*tport), GFP_KERNEL);
+	if (!tport)
+		return -ENOMEM;
+
+	/* Assigned the hw_idx */
+	spin_lock_init(&tport->port_lock);
+
+	spin_lock_irqsave(&tport->port_lock, flags);
+	for (i = serial_priv->last_assigned_hw_idx + 1; i < NCT_MAX_NUM_COM_DEVICES; i++) {
+		if ((serial_priv->en_device_mask & (1 << i)) == 0)
+			continue;	/* the port is disabled */
+
+		tport->hw_idx = i;
+		serial_priv->last_assigned_hw_idx = i;
+		break;
+	}
+	spin_unlock_irqrestore(&tport->port_lock, flags);
+
+	usb_set_serial_port_data(port, tport);
+
+	return 0;
+}
+
+static void nct_interrupt_in_callback(struct urb *urb)
+{
+	struct usb_serial_port *port = urb->context;
+	int status = urb->status;
+	struct usb_serial *serial = port->serial;
+
+	unsigned char *data = urb->transfer_buffer;
+	int retval;
+	int i;
+	int actual_len = urb->actual_length;
+	struct nct_port_status *nps;
+
+	switch (status) {
+	case 0:
+		/* Success */
+		if ((actual_len % 4) != 0)
+			return;
+
+		nps = (struct nct_port_status *)data;
+
+		for (i = 0; i < (actual_len / 4); i++) {
+			nct_update_status(serial, (unsigned char *)nps);
+			nps++;
+		}
+		break;
+
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* This urb is terminated, clean up */
+		return;
+	default:
+		break;
+	}
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		dev_err(&port->dev,
+			NCT_DRVNAME ": %s: Submit intr URB failed, ret=%d\n",
+			__func__, retval);
+}
+
+static void nct_disconnect(struct usb_serial *serial)
+{
+	int i;
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	unsigned long flags;
+
+	/* Reset status */
+	spin_lock_irqsave(&serial_priv->serial_lock, flags);
+	serial_priv->device_init = false;
+	serial_priv->cur_port = NULL;
+	serial_priv->cur_len = 0;
+	spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+
+	/* Stop reads and writes on all ports */
+	for (i = 0; i < serial->type->num_ports; i++) {
+		if (!serial->port[i])
+			continue;
+		usb_kill_urb(serial->port[i]->read_urb);
+		usb_kill_urb(serial->port[i]->write_urb);
+	}
+	if (!serial_priv->status_trans_mode)
+		if (serial->port[0] && serial->port[0]->interrupt_in_urb)
+			usb_kill_urb(serial->port[0]->interrupt_in_urb);
+}
+
+static int nct_port_probe(struct usb_serial_port *port)
+{
+	return nct_port_init(port, port->port_number);
+}
+
+static void nct_port_remove(struct usb_serial_port *port)
+{
+	struct nct_tty_port *priv;
+
+	priv = usb_get_serial_port_data(port);
+	kfree(priv);
+}
+
+static void nct_release(struct usb_serial *serial)
+{
+	struct nct_serial *serial_priv;
+
+	serial_priv = usb_get_serial_data(serial);
+	kfree(serial_priv);
+}
+
+static int nct_suspend(struct usb_serial *serial, pm_message_t message)
+{
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	int i;
+
+	/* Stop all URBs */
+	for (i = 0; i < serial->type->num_ports; i++) {
+		if (serial->port[i]) {
+			usb_kill_urb(serial->port[i]->read_urb);
+			usb_kill_urb(serial->port[i]->write_urb);
+		}
+	}
+
+	if (!serial_priv->status_trans_mode)
+		if (serial->port[0] && serial->port[0]->interrupt_in_urb)
+			usb_kill_urb(serial->port[0]->interrupt_in_urb);
+
+	return 0;
+}
+
+static int nct_resume(struct usb_serial *serial)
+{
+	struct nct_serial *serial_priv = usb_get_serial_data(serial);
+	struct usb_interface *intf = serial->interface;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	unsigned long flags;
+	int i, ret = 0;
+
+	/* Reacquire endpoint descriptors */
+	iface_desc = intf->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		if (usb_endpoint_is_bulk_out(endpoint)) {
+			serial_priv->bulk_out_ep = endpoint;
+			break;
+		}
+	}
+
+	/* Reset driver internal state */
+	spin_lock_irqsave(&serial_priv->serial_lock, flags);
+	serial_priv->cur_port = NULL;
+	serial_priv->cur_len = 0;
+	spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+
+	/* Resubmit URBs */
+	if (serial->port[0] && serial->port[0]->read_urb) {
+		ret = usb_submit_urb(serial->port[0]->read_urb, GFP_KERNEL);
+		if (ret)
+			dev_err(&intf->dev,
+				NCT_DRVNAME ": %s: Submit read URB failed, ret=%d\n",
+				__func__, ret);
+	}
+
+	if (!serial_priv->status_trans_mode) {
+		if (serial->port[0] && serial->port[0]->interrupt_in_urb) {
+			ret = usb_submit_urb(serial->port[0]->interrupt_in_urb,
+					     GFP_KERNEL);
+			if (ret)
+				dev_err(&intf->dev,
+					NCT_DRVNAME ": %s: Submit interrupt URB failed, ret=%d\n",
+					__func__, ret);
+		}
+	}
+
+	/* Restore status flags */
+	spin_lock_irqsave(&serial_priv->serial_lock, flags);
+	serial_priv->device_init = true;  /* Reset initialization flag */
+	spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
+
+	return 0;
+}
+
+static struct usb_serial_driver nct_usb_serial_device = {
+	.driver	= {
+		.name =		NCT_DRVNAME,
+	},
+	.description =		"Nuvoton USB to serial adapter",
+	.id_table =		id_table,
+	.num_ports =		6,
+	.num_bulk_in =		1,
+	.num_bulk_out =		1,
+	.open =			nct_open,
+	.close =		nct_close,
+	.write =		nct_serial_write,
+	.write_room =		nct_write_room,
+	.write_bulk_callback =	nct_write_bulk_callback,
+	.read_bulk_callback =	nct_read_bulk_callback,
+	.read_int_callback =	nct_interrupt_in_callback,
+	.chars_in_buffer =	nct_chars_in_buffer,
+	.throttle =		nct_rx_throttle,
+	.unthrottle =		nct_rx_unthrottle,
+	.probe =		nct_probe,
+	.calc_num_ports =	nct_calc_num_ports,
+	.set_termios =		nct_serial_set_termios,
+	.break_ctl =		nct_serial_break,
+	.tiocmget =		nct_serial_tiocmget,
+	.tiocmset =		nct_serial_tiocmset,
+	.attach	=		nct_usb_attach,
+	.disconnect =		nct_disconnect,
+	.release =		nct_release,
+	.port_probe =		nct_port_probe,
+	.port_remove =		nct_port_remove,
+	.suspend =		nct_suspend,
+	.resume =		nct_resume,
+};
+
+static struct usb_serial_driver * const nct_serial_drivers[] = {
+	&nct_usb_serial_device, NULL
+};
+
+module_usb_serial_driver(nct_serial_drivers, id_table);
+MODULE_DESCRIPTION("Nuvoton USB to serial adaptor driver");
+MODULE_AUTHOR("Sheng-Yuan Huang <syhuang3@...oton.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.43.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ