* [PATCH v1 0/1] USB: serial: add support for Nuvoton USB adapter @ 2025-06-03 3:20 hsyemail2 2025-06-03 3:20 ` [PATCH v1 1/1] USB: serial: nct_usb_serial: " hsyemail2 0 siblings, 1 reply; 16+ messages in thread From: hsyemail2 @ 2025-06-03 3:20 UTC (permalink / raw) To: Johan Hovold, Greg Kroah-Hartman Cc: linux-kernel, linux-usb, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.com> Hi all, This patch adds a new driver for Nuvoton USB-to-serial adapters in drivers/usb/serial. The driver supports multiple virtual serial ports over a single USB interface. It configures the device using vendor-specific control messages, and handles data transmission via bulk endpoints. Port status is reported either through an interrupt-in or a bulk-in endpoint, depending on the hardware configuration. Any feedback or suggestions would be greatly appreciated. Best regards, Sheng-Yuan Huang Sheng-Yuan Huang (1): USB: serial: nct_usb_serial: add support for Nuvoton USB adapter drivers/usb/serial/nct_usb_serial.c | 1523 +++++++++++++++++++++++++++ 1 file changed, 1523 insertions(+) create mode 100644 drivers/usb/serial/nct_usb_serial.c -- 2.43.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v1 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-03 3:20 [PATCH v1 0/1] USB: serial: add support for Nuvoton USB adapter hsyemail2 @ 2025-06-03 3:20 ` hsyemail2 2025-06-03 4:19 ` Greg Kroah-Hartman 2025-06-03 11:57 ` [PATCH v1 " Oliver Neukum 0 siblings, 2 replies; 16+ messages in thread From: hsyemail2 @ 2025-06-03 3:20 UTC (permalink / raw) To: Johan Hovold, Greg Kroah-Hartman Cc: linux-kernel, linux-usb, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.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@nuvoton.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@nuvoton.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@nuvoton.com>"); +MODULE_LICENSE("GPL v2"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v1 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-03 3:20 ` [PATCH v1 1/1] USB: serial: nct_usb_serial: " hsyemail2 @ 2025-06-03 4:19 ` Greg Kroah-Hartman 2025-06-04 2:51 ` [PATCH v2 0/1] " hsyemail2 2025-06-03 11:57 ` [PATCH v1 " Oliver Neukum 1 sibling, 1 reply; 16+ messages in thread From: Greg Kroah-Hartman @ 2025-06-03 4:19 UTC (permalink / raw) To: hsyemail2; +Cc: Johan Hovold, linux-kernel, linux-usb, Sheng-Yuan Huang At quick glance, this looks good, but one thing overall stood out: On Tue, Jun 03, 2025 at 11:20:57AM +0800, hsyemail2@gmail.com wrote: > + 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); When using dev_*() functions, you do not need a "driver prefix" here like NCT_DRVNAME as it already is in the string that is sent to the kernel log. So that all can be removed from all of these. Also: > + dev_dbg(&intf->dev, NCT_DRVNAME ": %s tty baud: 0x%X\n", __func__, > + (cflag & CBAUD)); The __func__ is already in the dev_dbg() output, so no need to repeat it again please. This should be done for all of the calls like this in the driver. > + dev_info(&intf->dev, NCT_DRVNAME ": %s Enabled devices mask:%X\n", > + __func__, buf[0]); When drivers are working properly, they are quiet, so no need for this line at all. thanks, greg k-h ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 0/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-03 4:19 ` Greg Kroah-Hartman @ 2025-06-04 2:51 ` hsyemail2 2025-06-04 2:51 ` [PATCH v2 1/1] " hsyemail2 0 siblings, 1 reply; 16+ messages in thread From: hsyemail2 @ 2025-06-04 2:51 UTC (permalink / raw) To: johan, gregkh; +Cc: linux-usb, linux-kernel, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.com> This is the v2 of the patch to add support for the Nuvoton USB-to-serial adapter. Changes since v1: - Removed redundant driver name prefixes (NCT_DRVNAME) in dev_*() messages. - Removed unnecessary __func__ usage in dev_dbg(). - Dropped verbose dev_info() message to keep the driver quiet when working properly. I sincerely thank Greg Kroah-Hartman for his detailed and patient review. Best regards, Sheng-Yuan Huang Sheng-Yuan Huang (1): USB: serial: nct_usb_serial: add support for Nuvoton USB adapter drivers/usb/serial/nct_usb_serial.c | 1480 +++++++++++++++++++++++++++ 1 file changed, 1480 insertions(+) create mode 100644 drivers/usb/serial/nct_usb_serial.c -- 2.43.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-04 2:51 ` [PATCH v2 0/1] " hsyemail2 @ 2025-06-04 2:51 ` hsyemail2 2025-06-19 9:40 ` Greg KH 0 siblings, 1 reply; 16+ messages in thread From: hsyemail2 @ 2025-06-04 2:51 UTC (permalink / raw) To: johan, gregkh; +Cc: linux-usb, linux-kernel, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.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@nuvoton.com> --- drivers/usb/serial/nct_usb_serial.c | 1480 +++++++++++++++++++++++++++ 1 file changed, 1480 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..b17b14c64e4f --- /dev/null +++ b/drivers/usb/serial/nct_usb_serial.c @@ -0,0 +1,1480 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024-2025 Nuvoton Corp. + * Copyright (C) 2024-2025 Sheng-Yuan Huang <syhuang3@nuvoton.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, "%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, "%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, "%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, "tty baud: 0x%X\n", (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, + "index %d set baud: NCT_BAUD_SUP[%d]=%d\n", + cmd.p.index, msg.p.spd, NCT_BAUD_SUP[i]); + if (nct_vendor_write(intf, cmd, msg.val)) + dev_err(&intf->dev, + "%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, + "%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, + "index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n", + 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, "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, + "%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, "returning %d\n", 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("%s: port is NULL!\n", __func__); + return -EIO; + } + if (!port->write_urb) { + dev_err(&port->dev, "%s: write_urb not initialized!\n", __func__); + return -EIO; + } + if (!port->write_urb->transfer_buffer) { + dev_err(&port->dev, "%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("%s: port is NULL, status=%d\n", __func__, status); + return; + } + + tport = usb_get_serial_port_data(port); + if (!tport) { + dev_err(&port->dev, "%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, "port=%d, room=%u\n", 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, "port=%d, chars=%d\n", 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, "port->read_urb is null, index=%d\n", 0); + + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) + dev_err(&port->dev, + "%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, + "%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("%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, "%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, "%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, "%s: port[%d] is NULL\n", __func__, i); + continue; + } + + tport = usb_get_serial_port_data(port); + + if (!tport) { + dev_err(&intf->dev, "%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, "%s: Decode serial packet index failed.\n", __func__); + return; + } + + if (!tport) { + dev_err(&intf->dev, "%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, "%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, "%s: Decode 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, "%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, "urb stopped: %d\n", status); + stopped = true; + break; + case -EPIPE: + dev_dbg(&port->dev, "urb stopped: %d\n", status); + stopped = true; + break; + case -ETIME: + dev_dbg(&port->dev, "urb ETIME t: %d\n", status); + break; + case -ETIMEDOUT: + dev_dbg(&port->dev, "urb ETIMEDOUT t: %d\n", status); + break; + default: + dev_dbg(&port->dev, "nonzero urb status: %d\n", status); + break; + } + + if (stopped) + return; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) + dev_err(&port->dev, "%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("%s: port or serial is NULL, status=%d\n", __func__, urb->status); + return; + } + + if (!port->serial) { + dev_err(&port->dev, "%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, "%s: port->private is NULL, status=%d\n", + __func__, urb->status); + return; + } + + if (!port->serial) { + dev_err(&port->dev, "%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, + "%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, "%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, "%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_dbg(&intf->dev, "Enabled devices mask:%X\n", 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, "%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, "%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, "%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@nuvoton.com>"); +MODULE_LICENSE("GPL v2"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-04 2:51 ` [PATCH v2 1/1] " hsyemail2 @ 2025-06-19 9:40 ` Greg KH 2025-06-20 1:16 ` Sheng-Yuan Huang 0 siblings, 1 reply; 16+ messages in thread From: Greg KH @ 2025-06-19 9:40 UTC (permalink / raw) To: hsyemail2; +Cc: johan, linux-usb, linux-kernel, Sheng-Yuan Huang On Wed, Jun 04, 2025 at 10:51:54AM +0800, hsyemail2@gmail.com wrote: > From: Sheng-Yuan Huang <syhuang3@nuvoton.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@nuvoton.com> > --- > drivers/usb/serial/nct_usb_serial.c | 1480 +++++++++++++++++++++++++++ > 1 file changed, 1480 insertions(+) > create mode 100644 drivers/usb/serial/nct_usb_serial.c This patch does not actually allow the .c file to be built, so how was this tested? confused, greg k-h ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-19 9:40 ` Greg KH @ 2025-06-20 1:16 ` Sheng-Yuan Huang 0 siblings, 0 replies; 16+ messages in thread From: Sheng-Yuan Huang @ 2025-06-20 1:16 UTC (permalink / raw) To: Greg KH; +Cc: johan, linux-usb, linux-kernel, Sheng-Yuan Huang Hi Greg, My apologies for the incomplete patch submission. This is my first time submitting a driver, and I'm still getting familiar with git console operations. A procedural error on my part led to the patch not including the necessary Kconfig and Makefile changes. I truly appreciate you pointing out this mistake promptly, which allows me to correct it early. I've learned from this, and the next version of the patch will correctly incorporate all required files. Thank you for your understanding, and I apologize again for any inconvenience caused. Best regards, Sheng-Yuan Huang Greg KH <gregkh@linuxfoundation.org> 於 2025年6月19日 週四 下午5:40寫道: > > On Wed, Jun 04, 2025 at 10:51:54AM +0800, hsyemail2@gmail.com wrote: > > From: Sheng-Yuan Huang <syhuang3@nuvoton.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@nuvoton.com> > > --- > > drivers/usb/serial/nct_usb_serial.c | 1480 +++++++++++++++++++++++++++ > > 1 file changed, 1480 insertions(+) > > create mode 100644 drivers/usb/serial/nct_usb_serial.c > > This patch does not actually allow the .c file to be built, so how was > this tested? > > confused, > > greg k-h ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v1 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-03 3:20 ` [PATCH v1 1/1] USB: serial: nct_usb_serial: " hsyemail2 2025-06-03 4:19 ` Greg Kroah-Hartman @ 2025-06-03 11:57 ` Oliver Neukum 2025-06-05 1:15 ` 逸曉塵 2025-06-23 7:17 ` [PATCH v3 0/1] " hsyemail2 1 sibling, 2 replies; 16+ messages in thread From: Oliver Neukum @ 2025-06-03 11:57 UTC (permalink / raw) To: hsyemail2, Johan Hovold, Greg Kroah-Hartman Cc: linux-kernel, linux-usb, Sheng-Yuan Huang Hi, On 03.06.25 05:20, hsyemail2@gmail.com wrote: > From: Sheng-Yuan Huang <syhuang3@nuvoton.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. I am afraid there are a few issue that will not to be addressed before this can be merged. > This driver implements basic TTY operations. > > Signed-off-by: Sheng-Yuan Huang <syhuang3@nuvoton.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 > +/* 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, > +}; What use is this? A number is a number. > +/* 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, > +}; No. This is an abuse of enumeration. These are just commands that happen to use the number space consecutively. These need to be defines. > +union nct_vendor_cmd { > + struct pkg0 { > + u16 index:4; > + u16 cmd:8; > + } p; > + u16 val; > +} __packed; This definition is an endianness bug waiting to happen. If this goes over the wire, it has a defined endianness, which needs to be declared. > +#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; Again endianness. > +/* 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; At the risk of repeating myself: endianness > + > +/* 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); And you just go on and overwrite kernel memory? If you test for plausibility, do something with the result. > +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; Why is this u8* ? It should be le16* > + 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; We have macros for that. > +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; > + } If nothing matches, you do nothing? > + } > + > + return msg.p.spd; So errors are ignored? > +static int nct_serial_tiocmset(struct tty_struct *tty, unsigned int set, > + unsigned int clear) > +{ > + return nct_tiocmset_helper(tty, set, clear); > +} Why? Does this function do anything useful? > +static void nct_rx_throttle(struct tty_struct *tty) > +{ > + unsigned int set; > + unsigned int clear = 0; Why? > + > + /* 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; Why? > + /* 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 */ Endianness. > + > + /* Copy data */ > + memcpy(port->write_urb->transfer_buffer + sizeof(hdr), > + (const void *)buf, wr_len); > + > + hdr.len = wr_len; /* File filed 'len' of header */ Endiannes > +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); Error handling? > + > + /* 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; Endianness > +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) { Endianness > + /* > + * 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; Endianness Regards Oliver ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v1 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-03 11:57 ` [PATCH v1 " Oliver Neukum @ 2025-06-05 1:15 ` 逸曉塵 2025-06-23 7:17 ` [PATCH v3 0/1] " hsyemail2 1 sibling, 0 replies; 16+ messages in thread From: 逸曉塵 @ 2025-06-05 1:15 UTC (permalink / raw) To: Oliver Neukum Cc: Johan Hovold, Greg Kroah-Hartman, linux-kernel, linux-usb, Sheng-Yuan Huang Hi Oliver Neukum, Appreciate the excellent suggestions! I'm in the process of revising the code based on your input. It'll take me a little while to thoroughly address everything, but I'll post an update here when I have something new to share. Thank you very much. Oliver Neukum <oneukum@suse.com> 於 2025年6月3日 週二 下午7:57寫道: > > Hi, > > On 03.06.25 05:20, hsyemail2@gmail.com wrote: > > From: Sheng-Yuan Huang <syhuang3@nuvoton.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. > > I am afraid there are a few issue that will not to be addressed > before this can be merged. > > > This driver implements basic TTY operations. > > > > Signed-off-by: Sheng-Yuan Huang <syhuang3@nuvoton.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 > > > +/* 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, > > +}; > > What use is this? A number is a number. > > > +/* 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, > > +}; > > No. This is an abuse of enumeration. These are just commands that > happen to use the number space consecutively. These need to be > defines. > > > +union nct_vendor_cmd { > > + struct pkg0 { > > + u16 index:4; > > + u16 cmd:8; > > + } p; > > + u16 val; > > +} __packed; > > This definition is an endianness bug waiting to happen. > If this goes over the wire, it has a defined endianness, > which needs to be declared. > > > +#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; > > Again endianness. > > > +/* 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; > > At the risk of repeating myself: endianness > > > + > > +/* 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); > > And you just go on and overwrite kernel memory? > If you test for plausibility, do something with the result. > > > > +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; > > Why is this u8* ? > It should be le16* > > > + 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; > > We have macros for that. > > > +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; > > + } > > If nothing matches, you do nothing? > > + } > > + > > + return msg.p.spd; > > So errors are ignored? > > > > +static int nct_serial_tiocmset(struct tty_struct *tty, unsigned int set, > > + unsigned int clear) > > +{ > > + return nct_tiocmset_helper(tty, set, clear); > > +} > > Why? Does this function do anything useful? > > > +static void nct_rx_throttle(struct tty_struct *tty) > > +{ > > + unsigned int set; > > + unsigned int clear = 0; > > Why? > > > + > > + /* 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; > > Why? > > > + /* 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 */ > > Endianness. > > > + > > + /* Copy data */ > > + memcpy(port->write_urb->transfer_buffer + sizeof(hdr), > > + (const void *)buf, wr_len); > > + > > + hdr.len = wr_len; /* File filed 'len' of header */ > > Endiannes > > > +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); > > Error handling? > > + > > + /* 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; > > Endianness > > > > +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) { > > Endianness > > > + /* > > + * 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; > > Endianness > > Regards > Oliver > ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 0/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-03 11:57 ` [PATCH v1 " Oliver Neukum 2025-06-05 1:15 ` 逸曉塵 @ 2025-06-23 7:17 ` hsyemail2 2025-06-23 7:17 ` [PATCH v3 1/1] " hsyemail2 1 sibling, 1 reply; 16+ messages in thread From: hsyemail2 @ 2025-06-23 7:17 UTC (permalink / raw) To: Johan Hovold, Greg Kroah-Hartman Cc: linux-usb, linux-kernel, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.com> Hi everyone, This patchset is a revised version of the previous submission and includes changes based on Oliver's comments. Thank you for the detailed review and valuable suggestions. The main updates are listed below: - All endianness concerns have been addressed by removing bitfields and using explicit byte order conversions. - Command definitions have been changed from enums to "#define" constants as requested. - Error handling has been improved, including proper checks in nct_vendor_read() and nct_set_baud(). - Cleaned up unnecessary code and improved function structure in several places, such as nct_vendor_write(), nct_rx_throttle(), and nct_rx_unthrottle(). Sheng-Yuan Huang (1): USB: serial: nct_usb_serial: add support for Nuvoton USB adapter drivers/usb/serial/Kconfig | 10 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/nct_usb_serial.c | 1553 +++++++++++++++++++++++++++ 3 files changed, 1564 insertions(+) create mode 100644 drivers/usb/serial/nct_usb_serial.c -- 2.43.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-23 7:17 ` [PATCH v3 0/1] " hsyemail2 @ 2025-06-23 7:17 ` hsyemail2 2025-06-23 10:32 ` Oliver Neukum 2025-06-27 7:13 ` [PATCH v3 " kernel test robot 0 siblings, 2 replies; 16+ messages in thread From: hsyemail2 @ 2025-06-23 7:17 UTC (permalink / raw) To: Johan Hovold, Greg Kroah-Hartman Cc: linux-usb, linux-kernel, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.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@nuvoton.com> --- drivers/usb/serial/Kconfig | 10 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/nct_usb_serial.c | 1553 +++++++++++++++++++++++++++ 3 files changed, 1564 insertions(+) create mode 100644 drivers/usb/serial/nct_usb_serial.c diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index ef8d1c73c754..3c4871de56f4 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -112,6 +112,16 @@ config USB_SERIAL_CH341 To compile this driver as a module, choose M here: the module will be called ch341. +config USB_SERIAL_NUV_MULTI_UART + tristate "USB Nuvoton Multi-Ports Serial Driver" + depends on USB_SERIAL + help + Say Y here if you want to use a Nuvoton Multi-Ports USB to + serial converter device + + To compile this driver as a module, choose M here: the + module will be called nct_usb_serial. + config USB_SERIAL_WHITEHEAT tristate "USB ConnectTech WhiteHEAT Serial Driver" select USB_EZUSB_FX2 diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index c7bb1a88173e..c07919a52076 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -41,6 +41,7 @@ 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_NAVMAN) += navman.o +obj-$(CONFIG_USB_SERIAL_NUV_MULTI_UART) += nct_usb_serial.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o obj-$(CONFIG_USB_SERIAL_OPTION) += option.o diff --git a/drivers/usb/serial/nct_usb_serial.c b/drivers/usb/serial/nct_usb_serial.c new file mode 100644 index 000000000000..931787d708c0 --- /dev/null +++ b/drivers/usb/serial/nct_usb_serial.c @@ -0,0 +1,1553 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024-2025 Nuvoton Corp. + * Copyright (C) 2024-2025 Sheng-Yuan Huang <syhuang3@nuvoton.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 */ +#define NCT_VCOM_INDEX_0 0 +#define NCT_VCOM_INDEX_1 1 +#define NCT_VCOM_INDEX_2 2 +#define NCT_VCOM_INDEX_3 3 +#define NCT_VCOM_INDEX_4 4 +#define NCT_VCOM_INDEX_5 5 +#define NCT_VCOM_INDEX_GLOBAL 0xF + +/* Command */ +#define NCT_VCOM_GET_NUM_PORTS 0 +#define NCT_VCOM_GET_PORTS_SUPPORT 1 +#define NCT_VCOM_GET_BAUD 2 +#define NCT_VCOM_SET_INIT 3 +#define NCT_VCOM_SET_CONFIG 4 +#define NCT_VCOM_SET_BAUD 5 +#define NCT_VCOM_SET_HCR 6 +#define NCT_VCOM_SET_OPEN_PORT 7 +#define NCT_VCOM_SET_CLOSE_PORT 8 +#define NCT_VCOM_SILENT 9 +/* Use bulk-in status instead of interrupt-in status */ +#define NCT_VCON_SET_BULK_IN_STATUS 10 + +struct nct_vendor_cmd { + __le16 val; /* bits[3:0]: index, bits[11:4]: cmd, bits[15:12]: reserved */ +}; + +#define NCT_CMD_INDEX_MASK 0x000F +#define NCT_CMD_CMD_MASK 0x0FF0 +#define NCT_CMD_CMD_SHIFT 4 + +static inline __le16 nct_build_cmd(__u8 cmd_code, __u8 index) +{ + return cpu_to_le16((cmd_code << NCT_CMD_CMD_SHIFT) | (index & NCT_CMD_INDEX_MASK)); +} + +static inline __u8 nct_get_cmd_index(__le16 val) +{ + return le16_to_cpu(val) & NCT_CMD_INDEX_MASK; +} + +static inline __u8 nct_get_cmd_cmd(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CMD_CMD_MASK) >> NCT_CMD_CMD_SHIFT; +} + +#define NCT_HDR_MAGIC 0xA5 +#define NCT_HDR_MAGIC2 0x5A +#define NCT_HDR_MAGIC_STATUS 0x5B + +struct nct_packet_header { + __u8 magic; + __u8 magic2; + __le16 len_and_idx; /* bits[3:0]: idx, bits[15:4]: len */ +} __packed; + +#define NCT_HDR_IDX_MASK 0x000F +#define NCT_HDR_LEN_MASK 0xFFF0 +#define NCT_HDR_LEN_SHIFT 4 + +static inline void nct_set_hdr_idx_len(struct nct_packet_header *hdr, __u8 idx, __u16 len) +{ + hdr->len_and_idx = cpu_to_le16((len << NCT_HDR_LEN_SHIFT) | (idx & NCT_HDR_IDX_MASK)); +} + +static inline __u8 nct_get_hdr_idx(const struct nct_packet_header *hdr) +{ + return le16_to_cpu(hdr->len_and_idx) & NCT_HDR_IDX_MASK; +} + +static inline __u16 nct_get_hdr_len(const struct nct_packet_header *hdr) +{ + return (le16_to_cpu(hdr->len_and_idx) & NCT_HDR_LEN_MASK) >> NCT_HDR_LEN_SHIFT; +} + +/* The definitions are for the fields 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 + +struct nct_ctrl_msg { + __le16 val; +}; + +#define NCT_CTRL_STOP_BIT_MASK 0x0001 +#define NCT_CTRL_PARITY_MASK 0x0006 +#define NCT_CTRL_PARITY_SHIFT 1 +#define NCT_CTRL_DATA_LEN_MASK 0x0018 +#define NCT_CTRL_DATA_LEN_SHIFT 3 +#define NCT_CTRL_FLOW_MASK 0x0060 +#define NCT_CTRL_FLOW_SHIFT 5 +#define NCT_CTRL_SPD_MASK 0x0F80 +#define NCT_CTRL_SPD_SHIFT 7 +#define NCT_CTRL_RESERVED_MASK 0xF000 +#define NCT_CTRL_RESERVED_SHIFT 12 + +static inline __le16 nct_build_ctrl_msg(__u8 stop_bit, __u8 parity, __u8 data_len, + __u8 flow, __u8 spd) +{ + __u16 val = 0; + + val |= (stop_bit & 0x01); + val |= ((parity & 0x03) << NCT_CTRL_PARITY_SHIFT); + val |= ((data_len & 0x03) << NCT_CTRL_DATA_LEN_SHIFT); + val |= ((flow & 0x03) << NCT_CTRL_FLOW_SHIFT); + val |= ((spd & 0x1F) << NCT_CTRL_SPD_SHIFT); + + return cpu_to_le16(val); +} + +static inline __u8 nct_get_ctrl_stop_bit(__le16 val) +{ + return le16_to_cpu(val) & NCT_CTRL_STOP_BIT_MASK; +} + +static inline __u8 nct_get_ctrl_parity(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_PARITY_MASK) >> NCT_CTRL_PARITY_SHIFT; +} + +static inline __u8 nct_get_ctrl_data_len(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_DATA_LEN_MASK) >> NCT_CTRL_DATA_LEN_SHIFT; +} + +static inline __u8 nct_get_ctrl_flow(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_FLOW_MASK) >> NCT_CTRL_FLOW_SHIFT; +} + +static inline __u8 nct_get_ctrl_spd(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_SPD_MASK) >> NCT_CTRL_SPD_SHIFT; +} + +static inline __le16 nct_set_ctrl_spd(__le16 val, __u8 spd) +{ + __u16 cpu_val = le16_to_cpu(val); + + cpu_val = (cpu_val & ~NCT_CTRL_SPD_MASK) | ((spd & 0x1F) << NCT_CTRL_SPD_SHIFT); + return cpu_to_le16(cpu_val); +} + +#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 { + unsigned long sysrq; /* Sysrq timeout */ + u8 hw_idx; + u8 usr; + u8 hsr; + u8 hcr; + /* + * Flow control - stop writing data to device. + * 0: Write enable, 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, struct 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, "%s - failed to read [%04x]: over size %d\n", + __func__, nct_get_cmd_cmd(cmd.val), size); + kfree(tmp_buf); + return -EINVAL; + } + + res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + NCT_VENDOR_COM_READ_REQUEST, + NCT_VENDOR_COM_READ_REQUEST_TYPE, + le16_to_cpu(cmd.val), + intf->cur_altsetting->desc.bInterfaceNumber, + tmp_buf, size, 100); + + if (res < 0) { + dev_err(dev, "%s - failed to read [%04x]: %d\n", __func__, + nct_get_cmd_cmd(cmd.val), 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, struct nct_vendor_cmd cmd, __u16 val) +{ + struct device *dev = &intf->dev; + struct usb_device *udev = interface_to_usbdev(intf); + int res; + __le16 *buf_val; + + buf_val = kmalloc(2, GFP_KERNEL); + if (!buf_val) + return -ENOMEM; + + *buf_val = cpu_to_le16(val); + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + NCT_VENDOR_COM_WRITE_REQUEST, + NCT_VENDOR_COM_WRITE_REQUEST_TYPE, + le16_to_cpu(cmd.val), + intf->cur_altsetting->desc.bInterfaceNumber, + buf_val, + 2, + 100); + kfree(buf_val); + if (res < 0) + dev_err(dev, "%s - failed to write [%04x]: %d\n", __func__, + nct_get_cmd_cmd(cmd.val), 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, bool *found) +{ + struct nct_vendor_cmd cmd; + struct nct_ctrl_msg msg; + u16 i; + u8 spd = NCT_DEFAULT_BAUD; + + *found = false; + cmd.val = nct_build_cmd(NCT_VCOM_SET_BAUD, index); + dev_dbg(&intf->dev, "tty baud: 0x%X\n", (cflag & CBAUD)); + for (i = 0; i < ARRAY_SIZE(NCT_BAUD_SUP); i++) { + if ((cflag & CBAUD) == NCT_BAUD_SUP[i]) { + spd = i; + dev_dbg(&intf->dev, "index %d set baud: NCT_BAUD_SUP[%d]=%d\n", + index, spd, NCT_BAUD_SUP[i]); + /* + * Create control message + * Note: The NCT_VCOM_SET_BAUD only set the baud rate + */ + msg.val = nct_build_ctrl_msg(0, 0, 0, 0, spd); + if (nct_vendor_write(intf, cmd, le16_to_cpu(msg.val))) + dev_err(&intf->dev, "%s - Set index: %d speed error\n", + __func__, index); + else + *found = true; + + break; + } + } + + if (!*found) + dev_warn(&intf->dev, "Unsupported baud rate 0x%X requested\n", (cflag & CBAUD)); + + return 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; + struct nct_ctrl_msg msg; + struct nct_vendor_cmd cmd; + unsigned int cflag = termios->c_cflag; + int ret; + speed_t baud, old_baud; + u8 stop_bit, parity, data_len, flow, spd = 0; + bool baud_found; + + baud_found = false; + baud = tty_get_baud_rate(tty); + if (old && tty_termios_baud_rate(old)) + old_baud = tty_termios_baud_rate(old); + else + old_baud = NCT_BAUD_SUP[NCT_DEFAULT_BAUD]; + + cmd.val = nct_build_cmd(NCT_VCOM_SET_CONFIG, tport->hw_idx); + + /* Set stop bit */ + stop_bit = (cflag & CSTOPB) ? (NCT_VCOM_2_STOP_BITS) : (NCT_VCOM_1_STOP_BIT); + + /* Set parity */ + if (cflag & PARENB) + parity = (cflag & PARODD) ? NCT_VCOM_PARITY_ODD : NCT_VCOM_PARITY_EVEN; + else + parity = NCT_VCOM_PARITY_NONE; + + /* Set data bit length */ + switch (cflag & CSIZE) { + case CS5: + data_len = NCT_VCOM_DL5; + break; + case CS6: + data_len = NCT_VCOM_DL6; + break; + case CS7: + data_len = NCT_VCOM_DL7; + break; + default: + case CS8: + data_len = NCT_VCOM_DL8; + break; + } + + /* Set flow control */ + if (C_CRTSCTS(tty)) { + flow = NCT_VCOM_RTS_CTS; + /* Flow control - Set flag of RTSCTS */ + tty_port_set_cts_flow(tty->port, true); + } else if (I_IXON(tty)) { + flow = NCT_VCOM_XOFF; + /* Flow control - Clear flag of RTSCTS */ + tty_port_set_cts_flow(tty->port, false); + } else { + flow = NCT_VCOM_DISABLE_FLOW_CTRL; + /* Flow control - Clear flag of RTSCTS */ + tty_port_set_cts_flow(tty->port, false); + } + + /* Create control message */ + msg.val = nct_build_ctrl_msg(stop_bit, parity, data_len, flow, spd); + + ret = nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); + if (ret) + dev_err(&intf->dev, + "%s - Set index: %d set configure error\n", + __func__, nct_get_cmd_index(cmd.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) { + spd = nct_set_baud(intf, tport->hw_idx, cflag, &baud_found); + } + + if (baud_found) + tty_encode_baud_rate(tty, baud, baud); + else + tty_encode_baud_rate(tty, old_baud, old_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; + struct nct_vendor_cmd cmd; + + cmd.val = nct_build_cmd(NCT_VCOM_SILENT, tport->hw_idx); + + 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; + struct nct_ctrl_msg msg; + struct 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.val = nct_build_cmd(NCT_VCOM_SET_HCR, tport->hw_idx); + msg.val = cpu_to_le16(hcr); + + spin_lock_irqsave(&tport->port_lock, flags); + tport->hcr = hcr; + spin_unlock_irqrestore(&tport->port_lock, flags); + + dev_dbg(&intf->dev, + "index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n", + nct_get_cmd_index(cmd.val), nct_get_cmd_cmd(cmd.val), + le16_to_cpu(msg.val), hcr & NCT_HCR_RTS, hcr & NCT_HCR_DTR); + + return nct_vendor_write(intf, cmd, le16_to_cpu(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, "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 void nct_rx_throttle(struct tty_struct *tty) +{ + /* Handle RTS line for RTS/CTS flow control */ + if (C_CRTSCTS(tty)) + nct_tiocmset_helper(tty, 0, TIOCM_RTS); +} + +static void nct_rx_unthrottle(struct tty_struct *tty) +{ + /* Handle RTS line for RTS/CTS flow control */ + if (C_CRTSCTS(tty)) + nct_tiocmset_helper(tty, TIOCM_RTS, 0); +} + +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; + nct_set_hdr_idx_len(&hdr, tport->hw_idx, wr_len); /* The 'hw_idx' is based on 1 */ + + /* Copy data */ + memcpy(port->write_urb->transfer_buffer + sizeof(hdr), + (const void *)buf, wr_len); + + /* 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, + "%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, "returning %d\n", 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("%s: port is NULL!\n", __func__); + return -EIO; + } + if (!port->write_urb) { + dev_err(&port->dev, "%s: write_urb not initialized!\n", __func__); + return -EIO; + } + if (!port->write_urb->transfer_buffer) { + dev_err(&port->dev, "%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("%s: port is NULL, status=%d\n", __func__, status); + return; + } + + tport = usb_get_serial_port_data(port); + if (!tport) { + dev_err(&port->dev, "%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, "port=%d, room=%u\n", 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, "port=%d, chars=%d\n", 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, "port->read_urb is null, index=%d\n", 0); + + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) + dev_err(&port->dev, + "%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, + "%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; + struct nct_ctrl_msg msg; + struct nct_vendor_cmd cmd; + + /* Send 'Close Port' to the device */ + cmd.val = nct_build_cmd(NCT_VCOM_SET_CLOSE_PORT, tport->hw_idx); + msg.val = cpu_to_le16(0); + if (!intf) { + pr_err("%s: No intf => do not send 'close' event\n", __func__); + return; + } + nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); +} + +static int nct_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct nct_vendor_cmd cmd; + struct 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, "%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.val = nct_build_cmd(NCT_VCOM_SET_OPEN_PORT, tport->hw_idx); + msg.val = cpu_to_le16(0); + nct_vendor_write(intf, cmd, le16_to_cpu(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, "%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, "%s: port[%d] is NULL\n", __func__, i); + continue; + } + + tport = usb_get_serial_port_data(port); + + if (!tport) { + dev_err(&intf->dev, "%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, "%s: Decode serial packet index failed.\n", __func__); + return; + } + + if (!tport) { + dev_err(&intf->dev, "%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, "%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 && + nct_get_hdr_len(hdr) == 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 && + nct_get_hdr_idx(hdr) <= NCT_MAX_NUM_COM_DEVICES && + nct_get_hdr_len(hdr) <= 512) + break; + + data++; + actual_len--; + if (!actual_len) { + dev_err(&intf->dev, "%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 != nct_get_hdr_idx(hdr)) + continue; + + break; + } + if (!tport) { + dev_err(&intf->dev, "%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 (nct_get_hdr_len(hdr) > 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 = nct_get_hdr_len(hdr) - 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 = nct_get_hdr_len(hdr); + } + } 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 sysrq handling is too slow for fast devices, + * so we bypass it in the vast majority of cases where the USB serial is + * not a console. + */ + 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, "urb stopped: %d\n", status); + stopped = true; + break; + case -EPIPE: + dev_dbg(&port->dev, "urb stopped: %d\n", status); + stopped = true; + break; + case -ETIME: + dev_dbg(&port->dev, "urb ETIME t: %d\n", status); + break; + case -ETIMEDOUT: + dev_dbg(&port->dev, "urb ETIMEDOUT t: %d\n", status); + break; + default: + dev_dbg(&port->dev, "nonzero urb status: %d\n", status); + break; + } + + if (stopped) + return; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) + dev_err(&port->dev, "%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("%s: port or serial is NULL, status=%d\n", __func__, urb->status); + return; + } + + if (!port->serial) { + dev_err(&port->dev, "%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, "%s: port->private is NULL, status=%d\n", + __func__, urb->status); + return; + } + + if (!port->serial) { + dev_err(&port->dev, "%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, + "%s: serial->private is NULL, status=%d\n", + __func__, urb->status); + return; + } + + /* Processing data */ + nct_process_read_bulk(urb); +} + +static int nct_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct 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.val = nct_build_cmd(NCT_VCOM_SET_INIT, NCT_VCOM_INDEX_GLOBAL); + ret = nct_vendor_write(intf, cmd, 0); + if (ret) { + dev_err(&intf->dev, "%s - Set COM init error\n", __func__); + return ret; + } + + /* Get ports' index supported by the device(/FW) */ + cmd.val = nct_build_cmd(NCT_VCOM_GET_PORTS_SUPPORT, NCT_VCOM_INDEX_GLOBAL); + ret = nct_vendor_read(intf, cmd, buf, 1); + if (ret != 1) { + dev_err(&intf->dev, "%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_dbg(&intf->dev, "Enabled devices mask:%X\n", 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, "%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, "%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, "%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_tiocmset_helper, + .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@nuvoton.com>"); +MODULE_LICENSE("GPL v2"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-23 7:17 ` [PATCH v3 1/1] " hsyemail2 @ 2025-06-23 10:32 ` Oliver Neukum 2025-06-27 0:59 ` Sheng-Yuan Huang 2025-06-27 7:13 ` [PATCH v3 " kernel test robot 1 sibling, 1 reply; 16+ messages in thread From: Oliver Neukum @ 2025-06-23 10:32 UTC (permalink / raw) To: hsyemail2, Johan Hovold, Greg Kroah-Hartman Cc: linux-usb, linux-kernel, Sheng-Yuan Huang Hi, On 23.06.25 09:17, hsyemail2@gmail.com wrote: > From: Sheng-Yuan Huang <syhuang3@nuvoton.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. A few issues left I am afraid. Comments on them inline. Regards Oliver > +/* Index definition */ > +#define NCT_VCOM_INDEX_0 0 > +#define NCT_VCOM_INDEX_1 1 > +#define NCT_VCOM_INDEX_2 2 > +#define NCT_VCOM_INDEX_3 3 > +#define NCT_VCOM_INDEX_4 4 > +#define NCT_VCOM_INDEX_5 5 Why? These make no sense. > +#define NCT_VCOM_INDEX_GLOBAL 0xF > + > +/* Command */ > +#define NCT_VCOM_GET_NUM_PORTS 0 > +#define NCT_VCOM_GET_PORTS_SUPPORT 1 > +#define NCT_VCOM_GET_BAUD 2 > +#define NCT_VCOM_SET_INIT 3 > +#define NCT_VCOM_SET_CONFIG 4 > +#define NCT_VCOM_SET_BAUD 5 > +#define NCT_VCOM_SET_HCR 6 > +#define NCT_VCOM_SET_OPEN_PORT 7 > +#define NCT_VCOM_SET_CLOSE_PORT 8 > +#define NCT_VCOM_SILENT 9 > +/* Use bulk-in status instead of interrupt-in status */ > +#define NCT_VCON_SET_BULK_IN_STATUS 10 > + > +struct nct_vendor_cmd { > + __le16 val; /* bits[3:0]: index, bits[11:4]: cmd, bits[15:12]: reserved */ > +}; > + > +#define NCT_CMD_INDEX_MASK 0x000F > +#define NCT_CMD_CMD_MASK 0x0FF0 > +#define NCT_CMD_CMD_SHIFT 4 > + > +static inline __le16 nct_build_cmd(__u8 cmd_code, __u8 index) > +{ > + return cpu_to_le16((cmd_code << NCT_CMD_CMD_SHIFT) | (index & NCT_CMD_INDEX_MASK)); This may be picking nits, but it seems to me that cmd_code is u8. Hence cmd_code << NCT_CMD_CMD_SHIFT) would also be u8 and the operation may overflow. You better cast cmd_code to u16. > +static u16 nct_set_baud(struct usb_interface *intf, u16 index, unsigned int cflag, bool *found) > +{ > + struct nct_vendor_cmd cmd; > + struct nct_ctrl_msg msg; > + u16 i; > + u8 spd = NCT_DEFAULT_BAUD; > + > + *found = false; > + cmd.val = nct_build_cmd(NCT_VCOM_SET_BAUD, index); > + dev_dbg(&intf->dev, "tty baud: 0x%X\n", (cflag & CBAUD)); > + for (i = 0; i < ARRAY_SIZE(NCT_BAUD_SUP); i++) { > + if ((cflag & CBAUD) == NCT_BAUD_SUP[i]) { > + spd = i; > + dev_dbg(&intf->dev, "index %d set baud: NCT_BAUD_SUP[%d]=%d\n", > + index, spd, NCT_BAUD_SUP[i]); > + /* > + * Create control message > + * Note: The NCT_VCOM_SET_BAUD only set the baud rate > + */ > + msg.val = nct_build_ctrl_msg(0, 0, 0, 0, spd); > + if (nct_vendor_write(intf, cmd, le16_to_cpu(msg.val))) > + dev_err(&intf->dev, "%s - Set index: %d speed error\n", > + __func__, index); > + else > + *found = true; > + > + break; > + } > + } > + > + if (!*found) > + dev_warn(&intf->dev, "Unsupported baud rate 0x%X requested\n", (cflag & CBAUD)); This is problematic. There are two reasons for this to trigger 1. no match 2. IO error in nct_vendor_write() If the second case happens you nevertheless claim the first cause I'd just drop the warning. Better nothing than something misleading. > + > + return spd; > +} > +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; > + struct nct_ctrl_msg msg; > + struct 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.val = nct_build_cmd(NCT_VCOM_SET_HCR, tport->hw_idx); > + msg.val = cpu_to_le16(hcr); > + > + spin_lock_irqsave(&tport->port_lock, flags); No need for irqsave. A simple irq version will do. Using irqsave is misleading, because we know that this function can sleep. > + tport->hcr = hcr; > + spin_unlock_irqrestore(&tport->port_lock, flags); > + > + dev_dbg(&intf->dev, > + "index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n", > + nct_get_cmd_index(cmd.val), nct_get_cmd_cmd(cmd.val), > + le16_to_cpu(msg.val), hcr & NCT_HCR_RTS, hcr & NCT_HCR_DTR); > + > + return nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); > +} > +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); > + [..] > + /* Fill header */ > + hdr.magic = NCT_HDR_MAGIC; > + hdr.magic2 = NCT_HDR_MAGIC2; > + nct_set_hdr_idx_len(&hdr, tport->hw_idx, wr_len); /* The 'hw_idx' is based on 1 */ > + > + /* Copy data */ > + memcpy(port->write_urb->transfer_buffer + sizeof(hdr), > + (const void *)buf, wr_len); > + > + /* Filled urb data */ > + memcpy(port->write_urb->transfer_buffer, (const void *)&hdr, > + sizeof(hdr)); /* Copy header after filling all other fields */ You are copying the header in unconditionally ... > + > + /* 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, > + "%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); ... and here you check? This needs an explanation. A very good explanation. > + > + dev_dbg(&port->dev, "returning %d\n", 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("%s: port is NULL!\n", __func__); > + return -EIO; > + } > + if (!port->write_urb) { > + dev_err(&port->dev, "%s: write_urb not initialized!\n", __func__); > + return -EIO; > + } > + if (!port->write_urb->transfer_buffer) { > + dev_err(&port->dev, "%s: transfer_buffer not initialized!\n", __func__); > + return -EIO; > + } Can these errors really happen? > + > + /* 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 int nct_open(struct tty_struct *tty, struct usb_serial_port *port) > +{ > + struct nct_vendor_cmd cmd; > + struct 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, "%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); Can use kzalloc() > + 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.val = nct_build_cmd(NCT_VCOM_SET_OPEN_PORT, tport->hw_idx); > + msg.val = cpu_to_le16(0); > + nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); This can fail. > + /* > + * 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) We are disconnected ... > + goto exit; > + > + nct_serial_port_end(port); > + > +exit: > + /* Shutdown any outstanding bulk writes */ > + usb_kill_urb(port->write_urb); ... so this is either useless, or a bug has already happened. > + > + /* 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_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; How do you know that there is enough data for struct nct_packet_header left? > + /* 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 && > + nct_get_hdr_len(hdr) == 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 && > + nct_get_hdr_idx(hdr) <= NCT_MAX_NUM_COM_DEVICES && > + nct_get_hdr_len(hdr) <= 512) > + break; > + > + data++; > + actual_len--; > + if (!actual_len) { > + dev_err(&intf->dev, "%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 != nct_get_hdr_idx(hdr)) > + continue; > + > + break; > + } > + if (!tport) { > + dev_err(&intf->dev, "%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 (nct_get_hdr_len(hdr) > 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 = nct_get_hdr_len(hdr) - 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 = nct_get_hdr_len(hdr); > + } > + } 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 sysrq handling is too slow for fast devices, > + * so we bypass it in the vast majority of cases where the USB serial is > + * not a console. > + */ > + 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; > +} ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-23 10:32 ` Oliver Neukum @ 2025-06-27 0:59 ` Sheng-Yuan Huang 2025-08-20 8:07 ` [PATCH v4 0/1] " hsyemail2 0 siblings, 1 reply; 16+ messages in thread From: Sheng-Yuan Huang @ 2025-06-27 0:59 UTC (permalink / raw) To: Oliver Neukum Cc: Johan Hovold, Greg Kroah-Hartman, linux-usb, linux-kernel, Sheng-Yuan Huang Hi Oliver, Thank you very much for taking the time to review my patch and provide your valuable suggestions. I have carefully reviewed all the issues you pointed out. Due to some ongoing tasks on my side, I may need a bit more time before I can work on the fixes. I will address the issues as soon as I am able. Thank you again for your understanding and help. Best regards, Sheng-Yuan Huang Oliver Neukum <oneukum@suse.com> 於 2025年6月23日 週一 下午6:32寫道: > > Hi, > > On 23.06.25 09:17, hsyemail2@gmail.com wrote: > > From: Sheng-Yuan Huang <syhuang3@nuvoton.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. > > A few issues left I am afraid. > Comments on them inline. > > Regards > Oliver > > > +/* Index definition */ > > +#define NCT_VCOM_INDEX_0 0 > > +#define NCT_VCOM_INDEX_1 1 > > +#define NCT_VCOM_INDEX_2 2 > > +#define NCT_VCOM_INDEX_3 3 > > +#define NCT_VCOM_INDEX_4 4 > > +#define NCT_VCOM_INDEX_5 5 > > Why? These make no sense. > > > +#define NCT_VCOM_INDEX_GLOBAL 0xF > > + > > +/* Command */ > > +#define NCT_VCOM_GET_NUM_PORTS 0 > > +#define NCT_VCOM_GET_PORTS_SUPPORT 1 > > +#define NCT_VCOM_GET_BAUD 2 > > +#define NCT_VCOM_SET_INIT 3 > > +#define NCT_VCOM_SET_CONFIG 4 > > +#define NCT_VCOM_SET_BAUD 5 > > +#define NCT_VCOM_SET_HCR 6 > > +#define NCT_VCOM_SET_OPEN_PORT 7 > > +#define NCT_VCOM_SET_CLOSE_PORT 8 > > +#define NCT_VCOM_SILENT 9 > > +/* Use bulk-in status instead of interrupt-in status */ > > +#define NCT_VCON_SET_BULK_IN_STATUS 10 > > + > > +struct nct_vendor_cmd { > > + __le16 val; /* bits[3:0]: index, bits[11:4]: cmd, bits[15:12]: reserved */ > > +}; > > + > > +#define NCT_CMD_INDEX_MASK 0x000F > > +#define NCT_CMD_CMD_MASK 0x0FF0 > > +#define NCT_CMD_CMD_SHIFT 4 > > + > > +static inline __le16 nct_build_cmd(__u8 cmd_code, __u8 index) > > +{ > > + return cpu_to_le16((cmd_code << NCT_CMD_CMD_SHIFT) | (index & NCT_CMD_INDEX_MASK)); > > This may be picking nits, but it seems to me that cmd_code is u8. > Hence cmd_code << NCT_CMD_CMD_SHIFT) would also be u8 and the operation > may overflow. You better cast cmd_code to u16. > > > +static u16 nct_set_baud(struct usb_interface *intf, u16 index, unsigned int cflag, bool *found) > > +{ > > + struct nct_vendor_cmd cmd; > > + struct nct_ctrl_msg msg; > > + u16 i; > > + u8 spd = NCT_DEFAULT_BAUD; > > + > > + *found = false; > > + cmd.val = nct_build_cmd(NCT_VCOM_SET_BAUD, index); > > + dev_dbg(&intf->dev, "tty baud: 0x%X\n", (cflag & CBAUD)); > > + for (i = 0; i < ARRAY_SIZE(NCT_BAUD_SUP); i++) { > > + if ((cflag & CBAUD) == NCT_BAUD_SUP[i]) { > > + spd = i; > > + dev_dbg(&intf->dev, "index %d set baud: NCT_BAUD_SUP[%d]=%d\n", > > + index, spd, NCT_BAUD_SUP[i]); > > + /* > > + * Create control message > > + * Note: The NCT_VCOM_SET_BAUD only set the baud rate > > + */ > > + msg.val = nct_build_ctrl_msg(0, 0, 0, 0, spd); > > + if (nct_vendor_write(intf, cmd, le16_to_cpu(msg.val))) > > + dev_err(&intf->dev, "%s - Set index: %d speed error\n", > > + __func__, index); > > + else > > + *found = true; > > + > > + break; > > + } > > + } > > + > > + if (!*found) > > + dev_warn(&intf->dev, "Unsupported baud rate 0x%X requested\n", (cflag & CBAUD)); > > This is problematic. There are two reasons for this to trigger > > 1. no match > 2. IO error in nct_vendor_write() > > If the second case happens you nevertheless claim the first cause > I'd just drop the warning. Better nothing than something misleading. > > > + > > + return spd; > > +} > > > > +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; > > + struct nct_ctrl_msg msg; > > + struct 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.val = nct_build_cmd(NCT_VCOM_SET_HCR, tport->hw_idx); > > + msg.val = cpu_to_le16(hcr); > > + > > + spin_lock_irqsave(&tport->port_lock, flags); > > No need for irqsave. A simple irq version will do. > Using irqsave is misleading, because we know that this > function can sleep. > > > + tport->hcr = hcr; > > + spin_unlock_irqrestore(&tport->port_lock, flags); > > + > > + dev_dbg(&intf->dev, > > + "index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n", > > + nct_get_cmd_index(cmd.val), nct_get_cmd_cmd(cmd.val), > > + le16_to_cpu(msg.val), hcr & NCT_HCR_RTS, hcr & NCT_HCR_DTR); > > + > > + return nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); > > +} > > > > +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); > > + > > [..] > > > + /* Fill header */ > > + hdr.magic = NCT_HDR_MAGIC; > > + hdr.magic2 = NCT_HDR_MAGIC2; > > + nct_set_hdr_idx_len(&hdr, tport->hw_idx, wr_len); /* The 'hw_idx' is based on 1 */ > > + > > + /* Copy data */ > > + memcpy(port->write_urb->transfer_buffer + sizeof(hdr), > > + (const void *)buf, wr_len); > > + > > + /* Filled urb data */ > > + memcpy(port->write_urb->transfer_buffer, (const void *)&hdr, > > + sizeof(hdr)); /* Copy header after filling all other fields */ > > You are copying the header in unconditionally ... > > > + > > + /* 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, > > + "%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); > > ... and here you check? > > This needs an explanation. A very good explanation. > > > + > > + dev_dbg(&port->dev, "returning %d\n", 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("%s: port is NULL!\n", __func__); > > + return -EIO; > > + } > > + if (!port->write_urb) { > > + dev_err(&port->dev, "%s: write_urb not initialized!\n", __func__); > > + return -EIO; > > + } > > + if (!port->write_urb->transfer_buffer) { > > + dev_err(&port->dev, "%s: transfer_buffer not initialized!\n", __func__); > > + return -EIO; > > + } > > Can these errors really happen? > > > + > > + /* 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 int nct_open(struct tty_struct *tty, struct usb_serial_port *port) > > +{ > > + struct nct_vendor_cmd cmd; > > + struct 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, "%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); > > Can use kzalloc() > > > + 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.val = nct_build_cmd(NCT_VCOM_SET_OPEN_PORT, tport->hw_idx); > > + msg.val = cpu_to_le16(0); > > + nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); > > This can fail. > > > + /* > > + * 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) > > We are disconnected ... > > + goto exit; > > + > > + nct_serial_port_end(port); > > + > > +exit: > > + /* Shutdown any outstanding bulk writes */ > > + usb_kill_urb(port->write_urb); > > ... so this is either useless, or a bug has already happened. > > > + > > + /* 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_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; > > How do you know that there is enough data for struct nct_packet_header > left? > > > + /* 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 && > > + nct_get_hdr_len(hdr) == 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 && > > + nct_get_hdr_idx(hdr) <= NCT_MAX_NUM_COM_DEVICES && > > + nct_get_hdr_len(hdr) <= 512) > > + break; > > + > > + data++; > > + actual_len--; > > + if (!actual_len) { > > + dev_err(&intf->dev, "%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 != nct_get_hdr_idx(hdr)) > > + continue; > > + > > + break; > > + } > > + if (!tport) { > > + dev_err(&intf->dev, "%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 (nct_get_hdr_len(hdr) > 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 = nct_get_hdr_len(hdr) - 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 = nct_get_hdr_len(hdr); > > + } > > + } 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 sysrq handling is too slow for fast devices, > > + * so we bypass it in the vast majority of cases where the USB serial is > > + * not a console. > > + */ > > + 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; > > +} ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 0/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-27 0:59 ` Sheng-Yuan Huang @ 2025-08-20 8:07 ` hsyemail2 2025-08-20 8:07 ` [PATCH v4 1/1] " hsyemail2 0 siblings, 1 reply; 16+ messages in thread From: hsyemail2 @ 2025-08-20 8:07 UTC (permalink / raw) To: Oliver Neukum, Johan Hovold, Greg Kroah-Hartman Cc: linux-usb, linux-kernel, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.com> Hi everyone, This patch is a revised version of the previous submission and includes changes based on comments from Oliver. I apologize for the delay in following up, as I’ve been tied up with some ongoing work. As requested, I’ve included a detailed explanation at the end of this cover letter regarding the payload length handling logic. Thank you to Oliver for the valuable feedback and suggestions provided in the previous review. The main changes are as follows: - Remove unnecessary index macros - Add explicit cast to avoid overflow in command code bit-shifting - Remove misleading baud rate warning (nct_set_baud()) - Use correct spinlock variant for sleepable context (nct_tiocmset_helper(), nct_serial_tiocmget(), and nct_close()) - Clarify and simplify payload length return logic (nct_serial_write_data()) - Remove NULL check for port and for write_urb/transfer_buffer (nct_serial_write()) - Use kzalloc for buffer allocation (nct_open()) - Add error handling for nct_vendor_write() calls (nct_open()) - Remove usb_kill_urb() (nct_close()) - Add buffer size check before accessing header (nct_usb_serial_read()) Regarding the handling of the payload length returned to the upper layer in 'nct_serial_write_data()', as requested by Oliver: Previously, I checked whether 'usb_submit_urb()' succeeded and only subtracted the header size from the total length if the submission was successful, since the upper layer only needs the payload length. After considering your feedback, I modified the code so that the return value is now set directly based on the result of 'usb_submit_urb()'. The functionality remains the same, but I hope this version is more straightforward. If you have any further suggestions or concerns, I would sincerely appreciate your feedback. Best regards, Sheng-Yuan Huang Sheng-Yuan Huang (1): USB: serial: nct_usb_serial: add support for Nuvoton USB adapter drivers/usb/serial/Kconfig | 10 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/nct_usb_serial.c | 1532 +++++++++++++++++++++++++++ 3 files changed, 1543 insertions(+) create mode 100644 drivers/usb/serial/nct_usb_serial.c -- 2.43.0 ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v4 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-08-20 8:07 ` [PATCH v4 0/1] " hsyemail2 @ 2025-08-20 8:07 ` hsyemail2 0 siblings, 0 replies; 16+ messages in thread From: hsyemail2 @ 2025-08-20 8:07 UTC (permalink / raw) To: Oliver Neukum, Johan Hovold, Greg Kroah-Hartman Cc: linux-usb, linux-kernel, Sheng-Yuan Huang From: Sheng-Yuan Huang <syhuang3@nuvoton.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@nuvoton.com> --- drivers/usb/serial/Kconfig | 10 + drivers/usb/serial/Makefile | 1 + drivers/usb/serial/nct_usb_serial.c | 1532 +++++++++++++++++++++++++++ 3 files changed, 1543 insertions(+) create mode 100644 drivers/usb/serial/nct_usb_serial.c diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index ef8d1c73c754..3c4871de56f4 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -112,6 +112,16 @@ config USB_SERIAL_CH341 To compile this driver as a module, choose M here: the module will be called ch341. +config USB_SERIAL_NUV_MULTI_UART + tristate "USB Nuvoton Multi-Ports Serial Driver" + depends on USB_SERIAL + help + Say Y here if you want to use a Nuvoton Multi-Ports USB to + serial converter device + + To compile this driver as a module, choose M here: the + module will be called nct_usb_serial. + config USB_SERIAL_WHITEHEAT tristate "USB ConnectTech WhiteHEAT Serial Driver" select USB_EZUSB_FX2 diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index c7bb1a88173e..c07919a52076 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -41,6 +41,7 @@ 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_NAVMAN) += navman.o +obj-$(CONFIG_USB_SERIAL_NUV_MULTI_UART) += nct_usb_serial.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o obj-$(CONFIG_USB_SERIAL_OPTION) += option.o diff --git a/drivers/usb/serial/nct_usb_serial.c b/drivers/usb/serial/nct_usb_serial.c new file mode 100644 index 000000000000..da6553e01cbd --- /dev/null +++ b/drivers/usb/serial/nct_usb_serial.c @@ -0,0 +1,1532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024-2025 Nuvoton Corp. + * Copyright (C) 2024-2025 Sheng-Yuan Huang <syhuang3@nuvoton.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> + +#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 */ +#define NCT_VCOM_INDEX_GLOBAL 0xF + +/* Command */ +#define NCT_VCOM_GET_NUM_PORTS 0 +#define NCT_VCOM_GET_PORTS_SUPPORT 1 +#define NCT_VCOM_GET_BAUD 2 +#define NCT_VCOM_SET_INIT 3 +#define NCT_VCOM_SET_CONFIG 4 +#define NCT_VCOM_SET_BAUD 5 +#define NCT_VCOM_SET_HCR 6 +#define NCT_VCOM_SET_OPEN_PORT 7 +#define NCT_VCOM_SET_CLOSE_PORT 8 +#define NCT_VCOM_SILENT 9 +/* Use bulk-in status instead of interrupt-in status */ +#define NCT_VCON_SET_BULK_IN_STATUS 10 + +struct nct_vendor_cmd { + __le16 val; /* bits[3:0]: index, bits[11:4]: cmd, bits[15:12]: reserved */ +}; + +#define NCT_CMD_INDEX_MASK 0x000F +#define NCT_CMD_CMD_MASK 0x0FF0 +#define NCT_CMD_CMD_SHIFT 4 + +static inline __le16 nct_build_cmd(__u8 cmd_code, __u8 index) +{ + return cpu_to_le16(((__u16)cmd_code << NCT_CMD_CMD_SHIFT) | (index & NCT_CMD_INDEX_MASK)); +} + +static inline __u8 nct_get_cmd_index(__le16 val) +{ + return le16_to_cpu(val) & NCT_CMD_INDEX_MASK; +} + +static inline __u8 nct_get_cmd_cmd(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CMD_CMD_MASK) >> NCT_CMD_CMD_SHIFT; +} + +#define NCT_HDR_MAGIC 0xA5 +#define NCT_HDR_MAGIC2 0x5A +#define NCT_HDR_MAGIC_STATUS 0x5B + +struct nct_packet_header { + __u8 magic; + __u8 magic2; + __le16 len_and_idx; /* bits[3:0]: idx, bits[15:4]: len */ +} __packed; + +#define NCT_HDR_IDX_MASK 0x000F +#define NCT_HDR_LEN_MASK 0xFFF0 +#define NCT_HDR_LEN_SHIFT 4 + +static inline void nct_set_hdr_idx_len(struct nct_packet_header *hdr, __u8 idx, __u16 len) +{ + hdr->len_and_idx = cpu_to_le16((len << NCT_HDR_LEN_SHIFT) | (idx & NCT_HDR_IDX_MASK)); +} + +static inline __u8 nct_get_hdr_idx(const struct nct_packet_header *hdr) +{ + return le16_to_cpu(hdr->len_and_idx) & NCT_HDR_IDX_MASK; +} + +static inline __u16 nct_get_hdr_len(const struct nct_packet_header *hdr) +{ + return (le16_to_cpu(hdr->len_and_idx) & NCT_HDR_LEN_MASK) >> NCT_HDR_LEN_SHIFT; +} + +/* The definitions are for the fields 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 + +struct nct_ctrl_msg { + __le16 val; +}; + +#define NCT_CTRL_STOP_BIT_MASK 0x0001 +#define NCT_CTRL_PARITY_MASK 0x0006 +#define NCT_CTRL_PARITY_SHIFT 1 +#define NCT_CTRL_DATA_LEN_MASK 0x0018 +#define NCT_CTRL_DATA_LEN_SHIFT 3 +#define NCT_CTRL_FLOW_MASK 0x0060 +#define NCT_CTRL_FLOW_SHIFT 5 +#define NCT_CTRL_SPD_MASK 0x0F80 +#define NCT_CTRL_SPD_SHIFT 7 +#define NCT_CTRL_RESERVED_MASK 0xF000 +#define NCT_CTRL_RESERVED_SHIFT 12 + +static inline __le16 nct_build_ctrl_msg(__u8 stop_bit, __u8 parity, __u8 data_len, + __u8 flow, __u8 spd) +{ + __u16 val = 0; + + val |= (stop_bit & 0x01); + val |= ((parity & 0x03) << NCT_CTRL_PARITY_SHIFT); + val |= ((data_len & 0x03) << NCT_CTRL_DATA_LEN_SHIFT); + val |= ((flow & 0x03) << NCT_CTRL_FLOW_SHIFT); + val |= ((spd & 0x1F) << NCT_CTRL_SPD_SHIFT); + + return cpu_to_le16(val); +} + +static inline __u8 nct_get_ctrl_stop_bit(__le16 val) +{ + return le16_to_cpu(val) & NCT_CTRL_STOP_BIT_MASK; +} + +static inline __u8 nct_get_ctrl_parity(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_PARITY_MASK) >> NCT_CTRL_PARITY_SHIFT; +} + +static inline __u8 nct_get_ctrl_data_len(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_DATA_LEN_MASK) >> NCT_CTRL_DATA_LEN_SHIFT; +} + +static inline __u8 nct_get_ctrl_flow(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_FLOW_MASK) >> NCT_CTRL_FLOW_SHIFT; +} + +static inline __u8 nct_get_ctrl_spd(__le16 val) +{ + return (le16_to_cpu(val) & NCT_CTRL_SPD_MASK) >> NCT_CTRL_SPD_SHIFT; +} + +static inline __le16 nct_set_ctrl_spd(__le16 val, __u8 spd) +{ + __u16 cpu_val = le16_to_cpu(val); + + cpu_val = (cpu_val & ~NCT_CTRL_SPD_MASK) | ((spd & 0x1F) << NCT_CTRL_SPD_SHIFT); + return cpu_to_le16(cpu_val); +} + +#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 { + unsigned long sysrq; /* Sysrq timeout */ + u8 hw_idx; + u8 usr; + u8 hsr; + u8 hcr; + /* + * Flow control - stop writing data to device. + * 0: Write enable, 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, struct 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, "%s - failed to read [%04x]: over size %d\n", + __func__, nct_get_cmd_cmd(cmd.val), size); + kfree(tmp_buf); + return -EINVAL; + } + + res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + NCT_VENDOR_COM_READ_REQUEST, + NCT_VENDOR_COM_READ_REQUEST_TYPE, + le16_to_cpu(cmd.val), + intf->cur_altsetting->desc.bInterfaceNumber, + tmp_buf, size, 100); + + if (res < 0) { + dev_err(dev, "%s - failed to read [%04x]: %d\n", __func__, + nct_get_cmd_cmd(cmd.val), 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, struct nct_vendor_cmd cmd, __u16 val) +{ + struct device *dev = &intf->dev; + struct usb_device *udev = interface_to_usbdev(intf); + int res; + __le16 *buf_val; + + buf_val = kmalloc(2, GFP_KERNEL); + if (!buf_val) + return -ENOMEM; + + *buf_val = cpu_to_le16(val); + + res = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + NCT_VENDOR_COM_WRITE_REQUEST, + NCT_VENDOR_COM_WRITE_REQUEST_TYPE, + le16_to_cpu(cmd.val), + intf->cur_altsetting->desc.bInterfaceNumber, + buf_val, + 2, + 100); + kfree(buf_val); + if (res < 0) + dev_err(dev, "%s - failed to write [%04x]: %d\n", __func__, + nct_get_cmd_cmd(cmd.val), 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, bool *found) +{ + struct nct_vendor_cmd cmd; + struct nct_ctrl_msg msg; + u16 i; + u8 spd = NCT_DEFAULT_BAUD; + + *found = false; + cmd.val = nct_build_cmd(NCT_VCOM_SET_BAUD, index); + dev_dbg(&intf->dev, "tty baud: 0x%X\n", (cflag & CBAUD)); + for (i = 0; i < ARRAY_SIZE(NCT_BAUD_SUP); i++) { + if ((cflag & CBAUD) == NCT_BAUD_SUP[i]) { + spd = i; + dev_dbg(&intf->dev, "index %d set baud: NCT_BAUD_SUP[%d]=%d\n", + index, spd, NCT_BAUD_SUP[i]); + /* + * Create control message + * Note: The NCT_VCOM_SET_BAUD only set the baud rate + */ + msg.val = nct_build_ctrl_msg(0, 0, 0, 0, spd); + if (nct_vendor_write(intf, cmd, le16_to_cpu(msg.val))) + dev_err(&intf->dev, "%s - Set index: %d speed error\n", + __func__, index); + else + *found = true; + + break; + } + } + + return 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; + struct nct_ctrl_msg msg; + struct nct_vendor_cmd cmd; + unsigned int cflag = termios->c_cflag; + int ret; + speed_t baud, old_baud; + u8 stop_bit, parity, data_len, flow, spd = 0; + bool baud_found; + + baud_found = false; + baud = tty_get_baud_rate(tty); + if (old && tty_termios_baud_rate(old)) + old_baud = tty_termios_baud_rate(old); + else + old_baud = NCT_BAUD_SUP[NCT_DEFAULT_BAUD]; + + cmd.val = nct_build_cmd(NCT_VCOM_SET_CONFIG, tport->hw_idx); + + /* Set stop bit */ + stop_bit = (cflag & CSTOPB) ? (NCT_VCOM_2_STOP_BITS) : (NCT_VCOM_1_STOP_BIT); + + /* Set parity */ + if (cflag & PARENB) + parity = (cflag & PARODD) ? NCT_VCOM_PARITY_ODD : NCT_VCOM_PARITY_EVEN; + else + parity = NCT_VCOM_PARITY_NONE; + + /* Set data bit length */ + switch (cflag & CSIZE) { + case CS5: + data_len = NCT_VCOM_DL5; + break; + case CS6: + data_len = NCT_VCOM_DL6; + break; + case CS7: + data_len = NCT_VCOM_DL7; + break; + default: + case CS8: + data_len = NCT_VCOM_DL8; + break; + } + + /* Set flow control */ + if (C_CRTSCTS(tty)) { + flow = NCT_VCOM_RTS_CTS; + /* Flow control - Set flag of RTSCTS */ + tty_port_set_cts_flow(tty->port, true); + } else if (I_IXON(tty)) { + flow = NCT_VCOM_XOFF; + /* Flow control - Clear flag of RTSCTS */ + tty_port_set_cts_flow(tty->port, false); + } else { + flow = NCT_VCOM_DISABLE_FLOW_CTRL; + /* Flow control - Clear flag of RTSCTS */ + tty_port_set_cts_flow(tty->port, false); + } + + /* Create control message */ + msg.val = nct_build_ctrl_msg(stop_bit, parity, data_len, flow, spd); + + ret = nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); + if (ret) + dev_err(&intf->dev, + "%s - Set index: %d set configure error\n", + __func__, nct_get_cmd_index(cmd.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) { + spd = nct_set_baud(intf, tport->hw_idx, cflag, &baud_found); + } + + if (baud_found) + tty_encode_baud_rate(tty, baud, baud); + else + tty_encode_baud_rate(tty, old_baud, old_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; + struct nct_vendor_cmd cmd; + + cmd.val = nct_build_cmd(NCT_VCOM_SILENT, tport->hw_idx); + + 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; + struct nct_ctrl_msg msg; + struct nct_vendor_cmd cmd; + 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.val = nct_build_cmd(NCT_VCOM_SET_HCR, tport->hw_idx); + msg.val = cpu_to_le16(hcr); + + spin_lock_irq(&tport->port_lock); + tport->hcr = hcr; + spin_unlock_irq(&tport->port_lock); + + dev_dbg(&intf->dev, + "index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n", + nct_get_cmd_index(cmd.val), nct_get_cmd_cmd(cmd.val), + le16_to_cpu(msg.val), hcr & NCT_HCR_RTS, hcr & NCT_HCR_DTR); + + return nct_vendor_write(intf, cmd, le16_to_cpu(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 int res; + u8 hcr, hsr; + + spin_lock_irq(&tport->port_lock); + hcr = tport->hcr; + hsr = tport->hsr; + spin_unlock_irq(&tport->port_lock); + 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, "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 void nct_rx_throttle(struct tty_struct *tty) +{ + /* Handle RTS line for RTS/CTS flow control */ + if (C_CRTSCTS(tty)) + nct_tiocmset_helper(tty, 0, TIOCM_RTS); +} + +static void nct_rx_unthrottle(struct tty_struct *tty) +{ + /* Handle RTS line for RTS/CTS flow control */ + if (C_CRTSCTS(tty)) + nct_tiocmset_helper(tty, TIOCM_RTS, 0); +} + +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; + nct_set_hdr_idx_len(&hdr, tport->hw_idx, wr_len); /* The 'hw_idx' is based on 1 */ + + /* Copy data */ + memcpy(port->write_urb->transfer_buffer + sizeof(hdr), + (const void *)buf, wr_len); + + /* 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, + "%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; + } + + spin_unlock_irqrestore(&tport->port_lock, flags); + + dev_dbg(&port->dev, "returning %d\n", 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); + + /* 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("%s: port is NULL, status=%d\n", __func__, status); + return; + } + + tport = usb_get_serial_port_data(port); + if (!tport) { + dev_err(&port->dev, "%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, "port=%d, room=%u\n", 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, "port=%d, chars=%d\n", 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, "port->read_urb is null, index=%d\n", 0); + + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) + dev_err(&port->dev, + "%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, + "%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; + struct nct_ctrl_msg msg; + struct nct_vendor_cmd cmd; + + /* Send 'Close Port' to the device */ + cmd.val = nct_build_cmd(NCT_VCOM_SET_CLOSE_PORT, tport->hw_idx); + msg.val = cpu_to_le16(0); + if (!intf) { + pr_err("%s: No intf => do not send 'close' event\n", __func__); + return; + } + nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); +} + +static int nct_open(struct tty_struct *tty, struct usb_serial_port *port) +{ + struct nct_vendor_cmd cmd; + struct 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; + int ret; + + 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, "%s: Failed to allocate write URB\n", __func__); + return -ENOMEM; + } + } + + /* Allocate bulk_out_buffer */ + port->write_urb->transfer_buffer = kzalloc(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; + } + + /* 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.val = nct_build_cmd(NCT_VCOM_SET_OPEN_PORT, tport->hw_idx); + msg.val = cpu_to_le16(0); + ret = nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)); + if (ret) { + dev_err(&port->dev, "%s: Failed to open port: %d\n", __func__, ret); + kfree(port->write_urb->transfer_buffer); + usb_free_urb(port->write_urb); + port->write_urb = NULL; + return ret; + } + + /* + * 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); + + 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: + /* Free transfer_buffer */ + kfree(port->write_urb->transfer_buffer); + port->write_urb->transfer_buffer = NULL; + + if (tport) { + spin_lock_irq(&tport->port_lock); + tport->write_urb_in_use = false; + spin_unlock_irq(&tport->port_lock); + } + + 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, "%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, "%s: port[%d] is NULL\n", __func__, i); + continue; + } + + tport = usb_get_serial_port_data(port); + + if (!tport) { + dev_err(&intf->dev, "%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, "%s: Decode serial packet index failed.\n", __func__); + return; + } + + if (!tport) { + dev_err(&intf->dev, "%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, "%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++) { + if (i + sizeof(struct nct_packet_header) > urb->actual_length) { + /* Not enough data for header */ + spin_unlock_irqrestore(&serial_priv->serial_lock, flags); + return; + } + + 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 && + nct_get_hdr_len(hdr) == 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 && + nct_get_hdr_idx(hdr) <= NCT_MAX_NUM_COM_DEVICES && + nct_get_hdr_len(hdr) <= 512) + break; + + data++; + actual_len--; + if (!actual_len) { + dev_err(&intf->dev, "%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 != nct_get_hdr_idx(hdr)) + continue; + + break; + } + if (!tport) { + dev_err(&intf->dev, "%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 (nct_get_hdr_len(hdr) > 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 = nct_get_hdr_len(hdr) - 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 = nct_get_hdr_len(hdr); + } + } 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 sysrq handling is too slow for fast devices, + * so we bypass it in the vast majority of cases where the USB serial is + * not a console. + */ + 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, "urb stopped: %d\n", status); + stopped = true; + break; + case -EPIPE: + dev_dbg(&port->dev, "urb stopped: %d\n", status); + stopped = true; + break; + case -ETIME: + dev_dbg(&port->dev, "urb ETIME t: %d\n", status); + break; + case -ETIMEDOUT: + dev_dbg(&port->dev, "urb ETIMEDOUT t: %d\n", status); + break; + default: + dev_dbg(&port->dev, "nonzero urb status: %d\n", status); + break; + } + + if (stopped) + return; + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret != 0 && ret != -EPERM) + dev_err(&port->dev, "%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("%s: port or serial is NULL, status=%d\n", __func__, urb->status); + return; + } + + if (!port->serial) { + dev_err(&port->dev, "%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, "%s: port->private is NULL, status=%d\n", + __func__, urb->status); + return; + } + + if (!port->serial) { + dev_err(&port->dev, "%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, + "%s: serial->private is NULL, status=%d\n", + __func__, urb->status); + return; + } + + /* Processing data */ + nct_process_read_bulk(urb); +} + +static int nct_calc_num_ports(struct usb_serial *serial, + struct usb_serial_endpoints *epds) +{ + struct 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.val = nct_build_cmd(NCT_VCOM_SET_INIT, NCT_VCOM_INDEX_GLOBAL); + ret = nct_vendor_write(intf, cmd, 0); + if (ret) { + dev_err(&intf->dev, "%s - Set COM init error\n", __func__); + return ret; + } + + /* Get ports' index supported by the device(/FW) */ + cmd.val = nct_build_cmd(NCT_VCOM_GET_PORTS_SUPPORT, NCT_VCOM_INDEX_GLOBAL); + ret = nct_vendor_read(intf, cmd, buf, 1); + if (ret != 1) { + dev_err(&intf->dev, "%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_dbg(&intf->dev, "Enabled devices mask:%X\n", 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, "%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, "%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, "%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_tiocmset_helper, + .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@nuvoton.com>"); +MODULE_LICENSE("GPL v2"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v3 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter 2025-06-23 7:17 ` [PATCH v3 1/1] " hsyemail2 2025-06-23 10:32 ` Oliver Neukum @ 2025-06-27 7:13 ` kernel test robot 1 sibling, 0 replies; 16+ messages in thread From: kernel test robot @ 2025-06-27 7:13 UTC (permalink / raw) To: hsyemail2, Johan Hovold, Greg Kroah-Hartman Cc: oe-kbuild-all, linux-usb, linux-kernel, Sheng-Yuan Huang Hi, kernel test robot noticed the following build warnings: [auto build test WARNING on johan-usb-serial/usb-next] [also build test WARNING on johan-usb-serial/usb-linus usb/usb-testing usb/usb-next usb/usb-linus tty/tty-testing tty/tty-next tty/tty-linus linus/master v6.16-rc3 next-20250626] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/hsyemail2-gmail-com/USB-serial-nct_usb_serial-add-support-for-Nuvoton-USB-adapter/20250623-151955 base: https://git.kernel.org/pub/scm/linux/kernel/git/johan/usb-serial.git usb-next patch link: https://lore.kernel.org/r/20250623071713.12814-2-syhuang3%40nuvoton.com patch subject: [PATCH v3 1/1] USB: serial: nct_usb_serial: add support for Nuvoton USB adapter reproduce: (https://download.01.org/0day-ci/archive/20250627/202506271430.ZtD5WXck-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202506271430.ZtD5WXck-lkp@intel.com/ versioncheck warnings: (new ones prefixed by >>) INFO PATH=/opt/cross/rustc-1.78.0-bindgen-0.65.1/cargo/bin:/opt/cross/clang-20/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /usr/bin/timeout -k 100 3h /usr/bin/make KCFLAGS= -fno-crash-diagnostics -Wno-error=return-type -Wreturn-type -funsigned-char -Wundef W=1 --keep-going LLVM=1 -j32 ARCH=x86_64 versioncheck find ./* \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS -o -name .pc -o -name .hg -o -name .git \) -prune -o \ -name '*.[hcS]' -type f -print | sort \ | xargs perl -w ./scripts/checkversion.pl ./drivers/media/i2c/ov02c10.c: 12 linux/version.h not needed. >> ./drivers/usb/serial/nct_usb_serial.c: 29 linux/version.h not needed. ./samples/bpf/spintest.bpf.c: 8 linux/version.h not needed. ./tools/lib/bpf/bpf_helpers.h: 432: need linux/version.h ./tools/testing/selftests/bpf/progs/dev_cgroup.c: 9 linux/version.h not needed. ./tools/testing/selftests/bpf/progs/netcnt_prog.c: 3 linux/version.h not needed. ./tools/testing/selftests/bpf/progs/test_map_lock.c: 4 linux/version.h not needed. ./tools/testing/selftests/bpf/progs/test_send_signal_kern.c: 4 linux/version.h not needed. ./tools/testing/selftests/bpf/progs/test_spin_lock.c: 4 linux/version.h not needed. ./tools/testing/selftests/bpf/progs/test_tcp_estats.c: 37 linux/version.h not needed. ./tools/testing/selftests/wireguard/qemu/init.c: 27 linux/version.h not needed. -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2025-08-20 8:11 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-06-03 3:20 [PATCH v1 0/1] USB: serial: add support for Nuvoton USB adapter hsyemail2 2025-06-03 3:20 ` [PATCH v1 1/1] USB: serial: nct_usb_serial: " hsyemail2 2025-06-03 4:19 ` Greg Kroah-Hartman 2025-06-04 2:51 ` [PATCH v2 0/1] " hsyemail2 2025-06-04 2:51 ` [PATCH v2 1/1] " hsyemail2 2025-06-19 9:40 ` Greg KH 2025-06-20 1:16 ` Sheng-Yuan Huang 2025-06-03 11:57 ` [PATCH v1 " Oliver Neukum 2025-06-05 1:15 ` 逸曉塵 2025-06-23 7:17 ` [PATCH v3 0/1] " hsyemail2 2025-06-23 7:17 ` [PATCH v3 1/1] " hsyemail2 2025-06-23 10:32 ` Oliver Neukum 2025-06-27 0:59 ` Sheng-Yuan Huang 2025-08-20 8:07 ` [PATCH v4 0/1] " hsyemail2 2025-08-20 8:07 ` [PATCH v4 1/1] " hsyemail2 2025-06-27 7:13 ` [PATCH v3 " kernel test robot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).