Message ID | 20250623071713.12814-1-syhuang3@nuvoton.com |
---|---|
Headers | show |
Series | USB: serial: nct_usb_serial: add support for Nuvoton USB adapter | expand |
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; > +}
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