From 2ed12514074d06e19fca473eb7e24e9245393db2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 29 Jun 2026 14:37:33 +0200 Subject: [PATCH 01/19] USB: serial: digi_acceleport: do not log stopping of urbs as errors Stopping an urb is not an error and should not be logged as such. Demote the dev_err() in the read bulk completion handler to dev_dbg() when an urb is being unlinked on disconnect. Note that this will become more of an issue when the urbs are stopped every time a port is closed. This issue was flagged by Sashiko when reviewing the upcoming change. Link: https://sashiko.dev/#/patchset/20260623150826.314727-1-johan%40kernel.org?part=2 Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index dea039163661..fbb3609ac014 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1355,7 +1355,17 @@ static void digi_read_bulk_callback(struct urb *urb) } /* do not resubmit urb if it has any status error */ - if (status) { + switch (status) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&port->dev, + "%s: nonzero read bulk status: status=%d, port=%d\n", + __func__, status, priv->dp_port_num); + return; + default: dev_err(&port->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); From 54ad7212195812e76bf33f561009a8e025bbfbd1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:16 +0200 Subject: [PATCH 02/19] USB: serial: digi_acceleport: fix port registration order The driver submits the read urbs for all ports when the first port is opened, which could happen before the other ports have been probed and their private data set up. If such an urb completes before the port has been probed, the completion handler will not resubmit it, thus preventing any further reads. Fix the ordering issue by not submitting the port read urbs until the port is opened. This also avoids wasting resources (e.g. power) when ports are not in use. Note that the port write urbs are already stopped on close (unless unbinding, but they are also stopped by core on disconnect). Fixes: fb44ff854e14 ("USB: digi_acceleport: fix port-data memory leak") Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 48 +++++++++++----------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index fbb3609ac014..f340c111f171 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1076,7 +1076,6 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); struct ktermios not_termios; - int throttled; /* be sure the device is started up */ if (digi_startup_device(port->serial) != 0) @@ -1106,17 +1105,14 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) } spin_lock_irq(&priv->dp_port_lock); - throttled = priv->dp_throttle_restart; priv->dp_throttled = 0; priv->dp_throttle_restart = 0; spin_unlock_irq(&priv->dp_port_lock); - if (throttled) { - ret = usb_submit_urb(port->read_urb, GFP_KERNEL); - if (ret) { - dev_err(&port->dev, "failed to submit read urb: %d\n", ret); - return ret; - } + ret = usb_submit_urb(port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&port->dev, "failed to submit read urb: %d\n", ret); + return ret; } return 0; @@ -1130,6 +1126,8 @@ static void digi_close(struct usb_serial_port *port) unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); + usb_kill_urb(port->read_urb); + mutex_lock(&port->serial->disc_mutex); /* if disconnected, just clear flags */ if (port->serial->disconnected) @@ -1192,15 +1190,15 @@ static void digi_close(struct usb_serial_port *port) /* * Digi Startup Device * - * Starts reads on all ports. Must be called AFTER startup, with + * Starts read on the OOB port. Must be called AFTER startup, with * urbs initialized. Returns 0 if successful, non-zero error otherwise. */ static int digi_startup_device(struct usb_serial *serial) { - int i, ret = 0; struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *port; + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + int ret; /* be sure this happens exactly once */ spin_lock(&serial_priv->ds_serial_lock); @@ -1211,19 +1209,13 @@ static int digi_startup_device(struct usb_serial *serial) serial_priv->ds_device_started = 1; spin_unlock(&serial_priv->ds_serial_lock); - /* start reading from each bulk in endpoint for the device */ - /* set USB_DISABLE_SPD flag for write bulk urbs */ - for (i = 0; i < serial->type->num_ports + 1; i++) { - port = serial->port[i]; - ret = usb_submit_urb(port->read_urb, GFP_KERNEL); - if (ret != 0) { - dev_err(&port->dev, - "%s: usb_submit_urb failed, ret=%d, port=%d\n", - __func__, ret, i); - break; - } + ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ret); + return ret; } - return ret; + + return 0; } static int digi_port_init(struct usb_serial_port *port, unsigned port_num) @@ -1294,13 +1286,11 @@ static int digi_startup(struct usb_serial *serial) static void digi_disconnect(struct usb_serial *serial) { - int i; + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; - /* stop reads and writes on all ports */ - for (i = 0; i < serial->type->num_ports + 1; i++) { - usb_kill_urb(serial->port[i]->read_urb); - usb_kill_urb(serial->port[i]->write_urb); - } + usb_kill_urb(oob_port->read_urb); + usb_kill_urb(oob_port->write_urb); } From 9da927878069208d2f581aa703c45ab013eca685 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:17 +0200 Subject: [PATCH 03/19] USB: serial: digi_acceleport: drop unused wait queue Drop the close wait queue which has not been used since commit 335f8514f200 ("tty: Bring the usb tty port structure into more use"). Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index f340c111f171..b14a8c33d11d 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -194,7 +194,6 @@ struct digi_port { int dp_throttled; int dp_throttle_restart; wait_queue_head_t dp_flush_wait; - wait_queue_head_t dp_close_wait; /* wait queue for close */ wait_queue_head_t write_wait; struct usb_serial_port *dp_port; }; @@ -1181,7 +1180,6 @@ static void digi_close(struct usb_serial_port *port) exit: spin_lock_irq(&priv->dp_port_lock); priv->dp_write_urb_in_use = 0; - wake_up_interruptible(&priv->dp_close_wait); spin_unlock_irq(&priv->dp_port_lock); mutex_unlock(&port->serial->disc_mutex); } @@ -1230,7 +1228,6 @@ static int digi_port_init(struct usb_serial_port *port, unsigned port_num) priv->dp_port_num = port_num; init_waitqueue_head(&priv->dp_transmit_idle_wait); init_waitqueue_head(&priv->dp_flush_wait); - init_waitqueue_head(&priv->dp_close_wait); init_waitqueue_head(&priv->write_wait); priv->dp_port = port; From 5d17fbd6296db34d84aef771c87387c86eae7d2b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:18 +0200 Subject: [PATCH 04/19] USB: serial: digi_acceleport: always stop write urb on close Explicitly stop the write urb on close() also if the device is being unbound instead of relying on core to do it after returning. Note that the dp_write_urb_in_use flag is cleared by the completion handler. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index b14a8c33d11d..1b858b0cc84b 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1128,7 +1128,6 @@ static void digi_close(struct usb_serial_port *port) usb_kill_urb(port->read_urb); mutex_lock(&port->serial->disc_mutex); - /* if disconnected, just clear flags */ if (port->serial->disconnected) goto exit; @@ -1174,14 +1173,11 @@ static void digi_close(struct usb_serial_port *port) TASK_INTERRUPTIBLE); schedule_timeout(DIGI_CLOSE_TIMEOUT); finish_wait(&priv->dp_flush_wait, &wait); +exit: + mutex_unlock(&port->serial->disc_mutex); /* shutdown any outstanding bulk writes */ usb_kill_urb(port->write_urb); -exit: - spin_lock_irq(&priv->dp_port_lock); - priv->dp_write_urb_in_use = 0; - spin_unlock_irq(&priv->dp_port_lock); - mutex_unlock(&port->serial->disc_mutex); } From 1c44f3b971ffa979191f83c98823e1f83f169208 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:19 +0200 Subject: [PATCH 05/19] USB: serial: digi_acceleport: add oob port helper Add a helper function for retrieving the OOB port to replace two convoluted expressions. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 1b858b0cc84b..487816470ceb 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -350,6 +350,13 @@ __releases(lock) return timeout; } +static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + + return serial_priv->ds_oob_port; +} + /* * Digi Write OOB Command * @@ -366,7 +373,7 @@ static int digi_write_oob_command(struct usb_serial_port *port, { int ret = 0; int len; - struct usb_serial_port *oob_port = (struct usb_serial_port *)((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned long flags; @@ -511,7 +518,7 @@ static int digi_set_modem_signals(struct usb_serial_port *port, int ret; struct digi_port *port_priv = usb_get_serial_port_data(port); - struct usb_serial_port *oob_port = (struct usb_serial_port *) ((struct digi_serial *)(usb_get_serial_data(port->serial)))->ds_oob_port; + struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned char *data = oob_port->write_urb->transfer_buffer; unsigned long flags; From dc2723aab475ebdfa51608670617ef51f71b94e1 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:20 +0200 Subject: [PATCH 06/19] USB: serial: digi_acceleport: clean up declarations and whitespace Clean up the driver by moving some declarations to approximate reverse xmas style and removing some stray newlines (and adding a few for readability). While at it, also replace two spaces before tabs in the driver structs. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 88 +++++++++------------------- 1 file changed, 29 insertions(+), 59 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 487816470ceb..21f15e19fae3 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -271,7 +271,7 @@ static struct usb_serial_driver digi_acceleport_2_device = { .dtr_rts = digi_dtr_rts, .write = digi_write, .write_room = digi_write_room, - .write_bulk_callback = digi_write_bulk_callback, + .write_bulk_callback = digi_write_bulk_callback, .read_bulk_callback = digi_read_bulk_callback, .chars_in_buffer = digi_chars_in_buffer, .throttle = digi_rx_throttle, @@ -300,7 +300,7 @@ static struct usb_serial_driver digi_acceleport_4_device = { .close = digi_close, .write = digi_write, .write_room = digi_write_room, - .write_bulk_callback = digi_write_bulk_callback, + .write_bulk_callback = digi_write_bulk_callback, .read_bulk_callback = digi_read_bulk_callback, .chars_in_buffer = digi_chars_in_buffer, .throttle = digi_rx_throttle, @@ -334,7 +334,6 @@ static struct usb_serial_driver * const serial_drivers[] = { * interruptible_sleep_on_timeout is deprecated and has been replaced * with the equivalent code. */ - static long cond_wait_interruptible_timeout_irqrestore( wait_queue_head_t *q, long timeout, spinlock_t *lock, unsigned long flags) @@ -367,15 +366,14 @@ static struct usb_serial_port *digi_get_oob_port(struct usb_serial *serial) * the interruptible flag is true, or a negative error * returned by usb_submit_urb. */ - static int digi_write_oob_command(struct usb_serial_port *port, unsigned char *buf, int count, int interruptible) { - int ret = 0; - int len; struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned long flags; + int ret = 0; + int len; dev_dbg(&port->dev, "digi_write_oob_command: TOP: port=%d, count=%d\n", @@ -412,10 +410,8 @@ static int digi_write_oob_command(struct usb_serial_port *port, dev_err(&port->dev, "%s: usb_submit_urb failed, ret=%d\n", __func__, ret); return ret; - } - /* * Digi Write In Band Command * @@ -427,16 +423,15 @@ static int digi_write_oob_command(struct usb_serial_port *port, * timeout ticks. Returns 0 if successful, or a negative * error returned by digi_write. */ - static int digi_write_inb_command(struct usb_serial_port *port, unsigned char *buf, int count, unsigned long timeout) { - int ret = 0; - int len; struct digi_port *priv = usb_get_serial_port_data(port); unsigned char *data = port->write_urb->transfer_buffer; unsigned long expire; unsigned long flags; + int ret = 0; + int len; dev_dbg(&port->dev, "digi_write_inb_command: TOP: port=%d, count=%d\n", priv->dp_port_num, count); @@ -490,7 +485,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, count -= len; buf += len; } - } spin_unlock_irqrestore(&priv->dp_port_lock, flags); @@ -501,7 +495,6 @@ static int digi_write_inb_command(struct usb_serial_port *port, return ret; } - /* * Digi Set Modem Signals * @@ -511,17 +504,15 @@ static int digi_write_inb_command(struct usb_serial_port *port, * -EINTR if interrupted while sleeping, or a non-zero error * returned by usb_submit_urb. */ - static int digi_set_modem_signals(struct usb_serial_port *port, unsigned int modem_signals, int interruptible) { - - int ret; struct digi_port *port_priv = usb_get_serial_port_data(port); struct usb_serial_port *oob_port = digi_get_oob_port(port->serial); struct digi_port *oob_priv = usb_get_serial_port_data(oob_port); unsigned char *data = oob_port->write_urb->transfer_buffer; unsigned long flags; + int ret; dev_dbg(&port->dev, "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x\n", @@ -579,14 +570,13 @@ static int digi_set_modem_signals(struct usb_serial_port *port, * is only called from close, and only one process can be in close on a * port at a time, so its ok. */ - static int digi_transmit_idle(struct usb_serial_port *port, unsigned long timeout) { - int ret; - unsigned char buf[2]; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[2]; unsigned long flags; + int ret; spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_transmit_idle = 0; @@ -613,16 +603,15 @@ static int digi_transmit_idle(struct usb_serial_port *port, } priv->dp_transmit_idle = 0; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return 0; - } - static void digi_rx_throttle(struct tty_struct *tty) { - unsigned long flags; struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; /* stop receiving characters by not resubmitting the read urb */ spin_lock_irqsave(&priv->dp_port_lock, flags); @@ -631,13 +620,12 @@ static void digi_rx_throttle(struct tty_struct *tty) spin_unlock_irqrestore(&priv->dp_port_lock, flags); } - static void digi_rx_unthrottle(struct tty_struct *tty) { - int ret = 0; - unsigned long flags; struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); + unsigned long flags; + int ret = 0; spin_lock_irqsave(&priv->dp_port_lock, flags); @@ -657,7 +645,6 @@ static void digi_rx_unthrottle(struct tty_struct *tty) __func__, ret, priv->dp_port_num); } - static void digi_set_termios(struct tty_struct *tty, struct usb_serial_port *port, const struct ktermios *old_termios) @@ -768,7 +755,6 @@ static void digi_set_termios(struct tty_struct *tty, /* set stop bits */ if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) { - if ((cflag & CSTOPB)) arg = DIGI_STOP_BITS_2; else @@ -778,7 +764,6 @@ static void digi_set_termios(struct tty_struct *tty, buf[i++] = priv->dp_port_num; buf[i++] = arg; buf[i++] = 0; - } /* set input flow control */ @@ -847,7 +832,6 @@ static void digi_set_termios(struct tty_struct *tty, tty_encode_baud_rate(tty, baud, baud); } - static int digi_break_ctl(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; @@ -861,43 +845,41 @@ static int digi_break_ctl(struct tty_struct *tty, int break_state) return digi_write_inb_command(port, buf, 4, 0); } - static int digi_tiocmget(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - unsigned int val; unsigned long flags; + unsigned int val; spin_lock_irqsave(&priv->dp_port_lock, flags); val = priv->dp_modem_signals; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return val; } - static int digi_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - unsigned int val; unsigned long flags; + unsigned int val; spin_lock_irqsave(&priv->dp_port_lock, flags); val = (priv->dp_modem_signals & ~clear) | set; spin_unlock_irqrestore(&priv->dp_port_lock, flags); + return digi_set_modem_signals(port, val, 1); } - static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, const unsigned char *buf, int count) { - - int ret, data_len, new_len; struct digi_port *priv = usb_get_serial_port_data(port); unsigned char *data = port->write_urb->transfer_buffer; + int ret, data_len, new_len; unsigned long flags; dev_dbg(&port->dev, "digi_write: TOP: port=%d, count=%d\n", @@ -960,21 +942,20 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, "%s: usb_submit_urb failed, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); dev_dbg(&port->dev, "digi_write: returning %d\n", ret); - return ret; + return ret; } static void digi_write_bulk_callback(struct urb *urb) { - struct usb_serial_port *port = urb->context; struct usb_serial *serial; struct digi_port *priv; struct digi_serial *serial_priv; - unsigned long flags; - int ret = 0; int status = urb->status; + unsigned long flags; bool wakeup; + int ret = 0; /* port and serial sanity check */ if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { @@ -1047,8 +1028,8 @@ static unsigned int digi_write_room(struct tty_struct *tty) spin_unlock_irqrestore(&priv->dp_port_lock, flags); dev_dbg(&port->dev, "digi_write_room: port=%d, room=%u\n", priv->dp_port_num, room); - return room; + return room; } static unsigned int digi_chars_in_buffer(struct tty_struct *tty) @@ -1078,10 +1059,10 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) { - int ret; - unsigned char buf[32]; struct digi_port *priv = usb_get_serial_port_data(port); struct ktermios not_termios; + unsigned char buf[32]; + int ret; /* be sure the device is started up */ if (digi_startup_device(port->serial) != 0) @@ -1124,13 +1105,12 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) return 0; } - static void digi_close(struct usb_serial_port *port) { + struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char buf[32]; DEFINE_WAIT(wait); int ret; - unsigned char buf[32]; - struct digi_port *priv = usb_get_serial_port_data(port); usb_kill_urb(port->read_urb); @@ -1187,14 +1167,12 @@ static void digi_close(struct usb_serial_port *port) usb_kill_urb(port->write_urb); } - /* * Digi Startup Device * * Starts read on the OOB port. Must be called AFTER startup, with * urbs initialized. Returns 0 if successful, non-zero error otherwise. */ - static int digi_startup_device(struct usb_serial *serial) { struct digi_serial *serial_priv = usb_get_serial_data(serial); @@ -1283,7 +1261,6 @@ static int digi_startup(struct usb_serial *serial) return 0; } - static void digi_disconnect(struct usb_serial *serial) { struct digi_serial *serial_priv = usb_get_serial_data(serial); @@ -1293,7 +1270,6 @@ static void digi_disconnect(struct usb_serial *serial) usb_kill_urb(oob_port->write_urb); } - static void digi_release(struct usb_serial *serial) { struct digi_serial *serial_priv; @@ -1325,8 +1301,8 @@ static void digi_read_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct digi_port *priv; struct digi_serial *serial_priv; - int ret; int status = urb->status; + int ret; /* port sanity check, do not resubmit if port is not valid */ if (port == NULL) @@ -1378,7 +1354,6 @@ static void digi_read_bulk_callback(struct urb *urb) "%s: failed resubmitting urb, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); } - } /* @@ -1390,7 +1365,6 @@ static void digi_read_bulk_callback(struct urb *urb) * It returns 0 if successful, 1 if successful but the port is * throttled, and -1 if the sanity checks failed. */ - static int digi_read_inb_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; @@ -1468,10 +1442,8 @@ static int digi_read_inb_callback(struct urb *urb) dev_dbg(&port->dev, "%s: unknown opcode: %d\n", __func__, opcode); return throttled ? 1 : 0; - } - /* * Digi Read OOB Callback * @@ -1480,10 +1452,8 @@ static int digi_read_inb_callback(struct urb *urb) * the port->serial is valid. It returns 0 if successful, and * -1 if the sanity checks failed. */ - static int digi_read_oob_callback(struct urb *urb) { - struct usb_serial_port *port = urb->context; struct usb_serial *serial = port->serial; struct tty_struct *tty; @@ -1491,8 +1461,8 @@ static int digi_read_oob_callback(struct urb *urb) unsigned char *buf = urb->transfer_buffer; int opcode, line, status, val; unsigned long flags; - int i; unsigned int rts; + int i; if (urb->actual_length < 4) return -1; @@ -1562,8 +1532,8 @@ static int digi_read_oob_callback(struct urb *urb) } tty_kref_put(tty); } - return 0; + return 0; } module_usb_serial_driver(serial_drivers, id_table_combined); From 58ef164543a4b7b0d4b704989fc580d1260fc058 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:21 +0200 Subject: [PATCH 07/19] USB: serial: digi_acceleport: drop redundant driver data sanity checks The urb context pointer does not change while an urb is in flight so there is never a need to check for NULL on completion. The port driver data is not freed until the port is unbound at which point all I/O for that port has been stopped (and I/O is no longer started for a port that has not yet been probed). The device driver data is not freed until after the driver has been unbound and at which point all I/O has also ceased. Drop the redundant, overly defensive (and still incomplete) sanity checks from the completion callbacks. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 40 +++------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 21f15e19fae3..5139e1a35669 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -949,28 +949,12 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, static void digi_write_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct usb_serial *serial; - struct digi_port *priv; - struct digi_serial *serial_priv; - int status = urb->status; + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv = usb_get_serial_port_data(port); unsigned long flags; bool wakeup; int ret = 0; - /* port and serial sanity check */ - if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { - pr_err("%s: port or port->private is NULL, status=%d\n", - __func__, status); - return; - } - serial = port->serial; - if (serial == NULL || (serial_priv = usb_get_serial_data(serial)) == NULL) { - dev_err(&port->dev, - "%s: serial or serial->private is NULL, status=%d\n", - __func__, status); - return; - } - /* handle oob callback */ if (priv->dp_port_num == serial_priv->ds_oob_port_num) { dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); @@ -1299,27 +1283,11 @@ static void digi_port_remove(struct usb_serial_port *port) static void digi_read_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct digi_port *priv; - struct digi_serial *serial_priv; + struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct digi_port *priv = usb_get_serial_port_data(port); int status = urb->status; int ret; - /* port sanity check, do not resubmit if port is not valid */ - if (port == NULL) - return; - priv = usb_get_serial_port_data(port); - if (priv == NULL) { - dev_err(&port->dev, "%s: port->private is NULL, status=%d\n", - __func__, status); - return; - } - if (port->serial == NULL || - (serial_priv = usb_get_serial_data(port->serial)) == NULL) { - dev_err(&port->dev, "%s: serial is bad or serial->private " - "is NULL, status=%d\n", __func__, status); - return; - } - /* do not resubmit urb if it has any status error */ switch (status) { case 0: From 747e057e55308f07198c1cafb01c34185b5f2941 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:22 +0200 Subject: [PATCH 08/19] USB: serial: digi_acceleport: stop OOB I/O when not in use The driver submits the OOB read urb on first open of a port and does not stop it until the device is disconnected. Add an open counter and submit the urb on first open and stop it on last close to avoid wasting resources (e.g. power) when the device is not in use. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 100 ++++++++++++++------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 5139e1a35669..afaa62e257d0 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -176,10 +176,10 @@ /* Structures */ struct digi_serial { - spinlock_t ds_serial_lock; + struct mutex open_mutex; struct usb_serial_port *ds_oob_port; /* out-of-band port */ int ds_oob_port_num; /* index of out-of-band port */ - int ds_device_started; + int open_count; }; struct digi_port { @@ -226,9 +226,7 @@ static unsigned int digi_chars_in_buffer(struct tty_struct *tty); static int digi_open(struct tty_struct *tty, struct usb_serial_port *port); static void digi_close(struct usb_serial_port *port); static void digi_dtr_rts(struct usb_serial_port *port, int on); -static int digi_startup_device(struct usb_serial *serial); static int digi_startup(struct usb_serial *serial); -static void digi_disconnect(struct usb_serial *serial); static void digi_release(struct usb_serial *serial); static int digi_port_probe(struct usb_serial_port *port); static void digi_port_remove(struct usb_serial_port *port); @@ -281,7 +279,6 @@ static struct usb_serial_driver digi_acceleport_2_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .disconnect = digi_disconnect, .release = digi_release, .port_probe = digi_port_probe, .port_remove = digi_port_remove, @@ -310,7 +307,6 @@ static struct usb_serial_driver digi_acceleport_4_device = { .tiocmget = digi_tiocmget, .tiocmset = digi_tiocmset, .attach = digi_startup, - .disconnect = digi_disconnect, .release = digi_release, .port_probe = digi_port_probe, .port_remove = digi_port_remove, @@ -1041,6 +1037,43 @@ static void digi_dtr_rts(struct usb_serial_port *port, int on) digi_set_modem_signals(port, on * (TIOCM_DTR | TIOCM_RTS), 1); } +static int digi_open_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + int ret = 0; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count++ == 0) { + ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); + if (ret) { + dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", + ret); + serial_priv->open_count--; + } + } + + mutex_unlock(&serial_priv->open_mutex); + + return ret; +} + +static void digi_close_oob_port(struct usb_serial *serial) +{ + struct digi_serial *serial_priv = usb_get_serial_data(serial); + struct usb_serial_port *oob_port = serial_priv->ds_oob_port; + + mutex_lock(&serial_priv->open_mutex); + + if (serial_priv->open_count-- == 1) { + usb_kill_urb(oob_port->read_urb); + usb_kill_urb(oob_port->write_urb); + } + + mutex_unlock(&serial_priv->open_mutex); +} + static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) { struct digi_port *priv = usb_get_serial_port_data(port); @@ -1048,9 +1081,9 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) unsigned char buf[32]; int ret; - /* be sure the device is started up */ - if (digi_startup_device(port->serial) != 0) - return -ENXIO; + ret = digi_open_oob_port(port->serial); + if (ret) + return ret; /* read modem signals automatically whenever they change */ buf[0] = DIGI_CMD_READ_INPUT_SIGNALS; @@ -1083,10 +1116,15 @@ static int digi_open(struct tty_struct *tty, struct usb_serial_port *port) ret = usb_submit_urb(port->read_urb, GFP_KERNEL); if (ret) { dev_err(&port->dev, "failed to submit read urb: %d\n", ret); - return ret; + goto err_close_oob; } return 0; + +err_close_oob: + digi_close_oob_port(port->serial); + + return ret; } static void digi_close(struct usb_serial_port *port) @@ -1149,36 +1187,8 @@ static void digi_close(struct usb_serial_port *port) /* shutdown any outstanding bulk writes */ usb_kill_urb(port->write_urb); -} -/* - * Digi Startup Device - * - * Starts read on the OOB port. Must be called AFTER startup, with - * urbs initialized. Returns 0 if successful, non-zero error otherwise. - */ -static int digi_startup_device(struct usb_serial *serial) -{ - struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *oob_port = serial_priv->ds_oob_port; - int ret; - - /* be sure this happens exactly once */ - spin_lock(&serial_priv->ds_serial_lock); - if (serial_priv->ds_device_started) { - spin_unlock(&serial_priv->ds_serial_lock); - return 0; - } - serial_priv->ds_device_started = 1; - spin_unlock(&serial_priv->ds_serial_lock); - - ret = usb_submit_urb(oob_port->read_urb, GFP_KERNEL); - if (ret) { - dev_err(&serial->interface->dev, "failed to submit OOB read urb: %d\n", ret); - return ret; - } - - return 0; + digi_close_oob_port(port->serial); } static int digi_port_init(struct usb_serial_port *port, unsigned port_num) @@ -1229,7 +1239,8 @@ static int digi_startup(struct usb_serial *serial) if (!serial_priv) return -ENOMEM; - spin_lock_init(&serial_priv->ds_serial_lock); + mutex_init(&serial_priv->open_mutex); + serial_priv->ds_oob_port_num = oob_port_num; serial_priv->ds_oob_port = serial->port[oob_port_num]; @@ -1245,15 +1256,6 @@ static int digi_startup(struct usb_serial *serial) return 0; } -static void digi_disconnect(struct usb_serial *serial) -{ - struct digi_serial *serial_priv = usb_get_serial_data(serial); - struct usb_serial_port *oob_port = serial_priv->ds_oob_port; - - usb_kill_urb(oob_port->read_urb); - usb_kill_urb(oob_port->write_urb); -} - static void digi_release(struct usb_serial *serial) { struct digi_serial *serial_priv; From 6f04e550a6247acf57fc736e346d4479c92bb70d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:23 +0200 Subject: [PATCH 09/19] USB: serial: digi_acceleport: drop unused in-buf define Drop the in-buf size define which has not been used since the port buffers were removed by commit 5fea2a4dabdf ("USB: digi_acceleport further buffer clean up"). Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index afaa62e257d0..8d320cce86fa 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -32,10 +32,6 @@ /* so we can be sure to send the full buffer in one urb */ #define DIGI_OUT_BUF_SIZE 8 -/* port input buffer length -- must be >= transfer buffer length - 3 */ -/* so we can be sure to hold at least one full buffer from one urb */ -#define DIGI_IN_BUF_SIZE 64 - /* retry timeout while sleeping */ #define DIGI_RETRY_TIMEOUT (HZ/10) From 6016799d33f27648ed41c82c3a0e1ac8e025b18b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:24 +0200 Subject: [PATCH 10/19] USB: serial: digi_acceleport: clean up xfer buf length expression Add the missing space around operators in transfer-buffer length expressions to make the code more readable. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 8d320cce86fa..0f6127c05998 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -452,7 +452,7 @@ static int digi_write_inb_command(struct usb_serial_port *port, /* len must be a multiple of 4 and small enough to */ /* guarantee the write will send buffered data first, */ /* so commands are in order with data and not split */ - len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); if (len > 4) len &= ~3; @@ -878,7 +878,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, priv->dp_port_num, count); /* copy user data (which can sleep) before getting spin lock */ - count = min(count, port->bulk_out_size-2); + count = min(count, port->bulk_out_size - 2); count = min(64, count); /* be sure only one write proceeds at a time */ @@ -900,7 +900,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, /* allow space for any buffered data and for new data, up to */ /* transfer buffer size - 2 (for command and length bytes) */ - new_len = min(count, port->bulk_out_size-2-priv->dp_out_buf_len); + new_len = min(count, port->bulk_out_size - 2 - priv->dp_out_buf_len); data_len = new_len + priv->dp_out_buf_len; if (data_len == 0) { @@ -908,7 +908,7 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, return 0; } - port->write_urb->transfer_buffer_length = data_len+2; + port->write_urb->transfer_buffer_length = data_len + 2; *data++ = DIGI_CMD_SEND_DATA; *data++ = data_len; From cc94b7b0cd203497bc89c520bd9a42724a335849 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:25 +0200 Subject: [PATCH 11/19] USB: serial: digi_acceleport: clean up write completion Clean up the write completion handler by adding a temporary variable for the transfer buffer and using the pre-existing urb pointer while dropping some redundant casts. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 0f6127c05998..f67bca769484 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -943,6 +943,7 @@ static void digi_write_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct digi_serial *serial_priv = usb_get_serial_data(port->serial); struct digi_port *priv = usb_get_serial_port_data(port); + unsigned char *data = urb->transfer_buffer; unsigned long flags; bool wakeup; int ret = 0; @@ -962,15 +963,13 @@ static void digi_write_bulk_callback(struct urb *urb) spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; if (priv->dp_out_buf_len > 0) { - *((unsigned char *)(port->write_urb->transfer_buffer)) - = (unsigned char)DIGI_CMD_SEND_DATA; - *((unsigned char *)(port->write_urb->transfer_buffer) + 1) - = (unsigned char)priv->dp_out_buf_len; - port->write_urb->transfer_buffer_length = - priv->dp_out_buf_len + 2; - memcpy(port->write_urb->transfer_buffer + 2, priv->dp_out_buf, - priv->dp_out_buf_len); - ret = usb_submit_urb(port->write_urb, GFP_ATOMIC); + data[0] = DIGI_CMD_SEND_DATA; + data[1] = priv->dp_out_buf_len; + memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); + + urb->transfer_buffer_length = priv->dp_out_buf_len + 2; + + ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret == 0) { priv->dp_write_urb_in_use = 1; priv->dp_out_buf_len = 0; From fdb85e08aa7a935eae6a60c5d721b34106d8a73a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:08:26 +0200 Subject: [PATCH 12/19] USB: serial: digi_acceleport: clean up inb command submission Clean up the inb command handling a bit by removing an unnecessary line break and moving the assignment operator before breaking another long expression. Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index f67bca769484..efaafaf728f8 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -460,11 +460,10 @@ static int digi_write_inb_command(struct usb_serial_port *port, if (priv->dp_out_buf_len > 0) { data[0] = DIGI_CMD_SEND_DATA; data[1] = priv->dp_out_buf_len; - memcpy(data + 2, priv->dp_out_buf, - priv->dp_out_buf_len); + memcpy(data + 2, priv->dp_out_buf, priv->dp_out_buf_len); memcpy(data + 2 + priv->dp_out_buf_len, buf, len); - port->write_urb->transfer_buffer_length - = priv->dp_out_buf_len + 2 + len; + port->write_urb->transfer_buffer_length = + priv->dp_out_buf_len + 2 + len; } else { memcpy(data, buf, len); port->write_urb->transfer_buffer_length = len; From c3c1852355c8ee171dcc71f71a328d672a604179 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 29 Jun 2026 14:40:37 +0200 Subject: [PATCH 13/19] USB: serial: digi_acceleport: fix oob port dev_printk() The OOB port is not registered with driver core and does not have a name. Use the USB interface with dev_printk() that may involve the OOB port to avoid log entries with no driver and a "null" device name. Fixes: f9dfbebb8b39 ("USB: serial: digi_acceleport.c: remove dbg() usage") Fixes: 194343d9364e ("USB: remove use of err() in drivers/usb/serial") Signed-off-by: Johan Hovold --- drivers/usb/serial/digi_acceleport.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index efaafaf728f8..e6d14f7d106d 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -949,7 +949,6 @@ static void digi_write_bulk_callback(struct urb *urb) /* handle oob callback */ if (priv->dp_port_num == serial_priv->ds_oob_port_num) { - dev_dbg(&port->dev, "digi_write_bulk_callback: oob callback\n"); spin_lock_irqsave(&priv->dp_port_lock, flags); priv->dp_write_urb_in_use = 0; wake_up_interruptible(&priv->write_wait); @@ -1279,7 +1278,8 @@ static void digi_port_remove(struct usb_serial_port *port) static void digi_read_bulk_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; - struct digi_serial *serial_priv = usb_get_serial_data(port->serial); + struct usb_serial *serial = port->serial; + struct digi_serial *serial_priv = usb_get_serial_data(serial); struct digi_port *priv = usb_get_serial_port_data(port); int status = urb->status; int ret; @@ -1291,12 +1291,12 @@ static void digi_read_bulk_callback(struct urb *urb) case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: - dev_dbg(&port->dev, + dev_err(&serial->interface->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); return; default: - dev_err(&port->dev, + dev_err(&serial->interface->dev, "%s: nonzero read bulk status: status=%d, port=%d\n", __func__, status, priv->dp_port_num); return; @@ -1314,7 +1314,7 @@ static void digi_read_bulk_callback(struct urb *urb) /* continue read */ ret = usb_submit_urb(urb, GFP_ATOMIC); if (ret != 0 && ret != -EPERM) { - dev_err(&port->dev, + dev_err(&serial->interface->dev, "%s: failed resubmitting urb, ret=%d, port=%d\n", __func__, ret, priv->dp_port_num); } @@ -1438,7 +1438,8 @@ static int digi_read_oob_callback(struct urb *urb) status = buf[i + 2]; val = buf[i + 3]; - dev_dbg(&port->dev, "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", + dev_dbg(&serial->interface->dev, + "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d\n", opcode, line, status, val); if (status != 0 || line >= serial->type->num_ports) From c41d491929bbb2e3fcd9f0233e563b1f6e1120a7 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:21:46 +0200 Subject: [PATCH 14/19] USB: serial: metro-usb: replace unnecessary atomic allocation The unthrottle callback is allowed to sleep so pass the correct GFP flag to usb_submit_urb() to avoid unnecessary atomic allocations. Signed-off-by: Johan Hovold --- drivers/usb/serial/metro-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index 35473544f1c8..f42ad5dec35e 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -329,7 +329,7 @@ static void metrousb_unthrottle(struct tty_struct *tty) spin_unlock_irqrestore(&metro_priv->lock, flags); /* Submit the urb to read from the port. */ - result = usb_submit_urb(port->interrupt_in_urb, GFP_ATOMIC); + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); if (result) dev_err(&port->dev, "failed submitting interrupt in urb error code=%d\n", From 563cd5aacd759376e7a6af63ca5fbe7237a3b9de Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:21:47 +0200 Subject: [PATCH 15/19] USB: serial: metro-usb: fix unthrottle race If the completion handler races with unthrottle() both functions may try to resubmit the same interrupt-in urb, but at most one will succeed. Fix the unthrottle logic using a throttle-requested flag so that only one attempt to resubmit the urb is made to avoid logging an error. Fixes: 43d186fe992d ("USB: serial: add metro-usb driver to the tree") Signed-off-by: Johan Hovold --- drivers/usb/serial/metro-usb.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index f42ad5dec35e..8458713277f4 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -36,6 +36,7 @@ struct metrousb_private { spinlock_t lock; int throttled; + int throttle_req; unsigned long control_state; }; @@ -143,7 +144,10 @@ static void metrousb_read_int_callback(struct urb *urb) /* Set any port variables. */ spin_lock_irqsave(&metro_priv->lock, flags); - throttled = metro_priv->throttled; + if (metro_priv->throttle_req) { + metro_priv->throttled = 1; + throttled = 1; + } spin_unlock_irqrestore(&metro_priv->lock, flags); if (throttled) @@ -175,6 +179,7 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) spin_lock_irqsave(&metro_priv->lock, flags); metro_priv->control_state = 0; metro_priv->throttled = 0; + metro_priv->throttle_req = 0; spin_unlock_irqrestore(&metro_priv->lock, flags); /* Clear the urb pipe. */ @@ -269,7 +274,7 @@ static void metrousb_throttle(struct tty_struct *tty) /* Set the private information for the port to stop reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); - metro_priv->throttled = 1; + metro_priv->throttle_req = 1; spin_unlock_irqrestore(&metro_priv->lock, flags); } @@ -321,19 +326,23 @@ static void metrousb_unthrottle(struct tty_struct *tty) struct usb_serial_port *port = tty->driver_data; struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; + int throttled; int result = 0; /* Set the private information for the port to resume reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); + throttled = metro_priv->throttled; metro_priv->throttled = 0; + metro_priv->throttle_req = 0; spin_unlock_irqrestore(&metro_priv->lock, flags); - /* Submit the urb to read from the port. */ - result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); - if (result) - dev_err(&port->dev, - "failed submitting interrupt in urb error code=%d\n", - result); + if (throttled) { + result = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (result) { + dev_err(&port->dev, "failed to submit interrupt in urb: %d\n", + result); + } + } } static struct usb_serial_driver metrousb_device = { From 79c6baf62ee4aa0a18ef8a61597158125745f171 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 23 Jun 2026 17:21:48 +0200 Subject: [PATCH 16/19] USB: serial: metro-usb: drop redundant initialisations Three functions are initialising their return value variables at declaration only to later assign them unconditionally. Signed-off-by: Johan Hovold --- drivers/usb/serial/metro-usb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index 8458713277f4..22c5f071f116 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -109,7 +109,7 @@ static void metrousb_read_int_callback(struct urb *urb) unsigned char *data = urb->transfer_buffer; unsigned long flags; int throttled = 0; - int result = 0; + int result; dev_dbg(&port->dev, "%s\n", __func__); @@ -173,7 +173,7 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) struct usb_serial *serial = port->serial; struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; - int result = 0; + int result; /* Set the private data information for the port. */ spin_lock_irqsave(&metro_priv->lock, flags); @@ -327,7 +327,7 @@ static void metrousb_unthrottle(struct tty_struct *tty) struct metrousb_private *metro_priv = usb_get_serial_port_data(port); unsigned long flags; int throttled; - int result = 0; + int result; /* Set the private information for the port to resume reading data. */ spin_lock_irqsave(&metro_priv->lock, flags); From 091738d09786ae4da789b5297cb4dae3024d1c13 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 29 Jun 2026 14:46:28 +0200 Subject: [PATCH 17/19] USB: serial: keyspan_pda: drop unused driver data usb-serial pointer The driver data usb-serial pointer is unused since commit 66c32e483355 ("USB: serial: keyspan_pda: drop redundant usb-serial pointer"), which apparently failed to remove the pointer as intended. Signed-off-by: Johan Hovold --- drivers/usb/serial/keyspan_pda.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index f05bcce60600..e8755d126244 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -31,7 +31,6 @@ struct keyspan_pda_private { int tx_room; struct work_struct unthrottle_work; - struct usb_serial *serial; struct usb_serial_port *port; }; From 885d802f544ca7bfa8f3984d94233cce715bb6b3 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Sun, 26 Jul 2026 00:27:51 +0800 Subject: [PATCH 18/19] USB: serial: option: fix slab OOB read in interrupt URB callback The interrupt URB buffer is allocated in setup_port_interrupt_in() based on the endpoint's wMaxPacketSize: buffer_size = usb_endpoint_maxp(epd); port->interrupt_in_buffer = kmalloc(buffer_size, GFP_KERNEL); When a USB device declares wMaxPacketSize = 8 on its interrupt IN endpoint, the buffer is allocated from kmalloc-8 cache (exactly 8 bytes). If the device sends a short packet (actual_length < wMaxPacketSize), the URB completes with status == 0 and the callback proceeds to read: data[sizeof(struct usb_ctrlrequest)] which evaluates to data[8], accessing 1 byte beyond the allocated 8-byte buffer. This results in a slab out-of-bounds read. Fix this by adding the missing bounds check: first verify that the actual length is large enough to contain the struct usb_ctrlrequest header before accessing req_pkt->bRequestType and req_pkt->bRequest, and then verify that there is an additional byte for the modem signal state before reading data[sizeof(struct usb_ctrlrequest)] inside the conditional. Use sizeof(*req_pkt) instead of sizeof(struct usb_ctrlrequest) for consistency. Assisted-by: Claude:deepseek-v4-pro Signed-off-by: Jiale Yao Fixes: 58cfe9113e48 ("[PATCH] USB: add Option Card driver") Cc: stable@vger.kernel.org # v2.6.12 [ johan: use dev_err(); split signals declaration and initialisation ] Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 7275f4e7f569..fd8f96294199 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2688,12 +2688,26 @@ static void option_instat_callback(struct urb *urb) dev_dbg(dev, "%s: NULL req_pkt\n", __func__); return; } + + if (urb->actual_length < sizeof(*req_pkt)) { + dev_err(dev, "%s: short packet: %u bytes\n", __func__, + urb->actual_length); + return; + } + if ((req_pkt->bRequestType == 0xA1) && (req_pkt->bRequest == 0x20)) { + unsigned char signals; int old_dcd_state; - unsigned char signals = *((unsigned char *) - urb->transfer_buffer + - sizeof(struct usb_ctrlrequest)); + + if (urb->actual_length < sizeof(*req_pkt) + 1) { + dev_err(dev, "%s: short interrupt transfer: %u bytes\n", + __func__, urb->actual_length); + return; + } + + signals = *((unsigned char *)urb->transfer_buffer + + sizeof(*req_pkt)); dev_dbg(dev, "%s: signal x%x\n", __func__, signals); From 15734de99517b0c81a1a5a3bccaff4593ef8d953 Mon Sep 17 00:00:00 2001 From: Charles Yeh Date: Tue, 21 Jul 2026 19:24:40 +0800 Subject: [PATCH 19/19] USB: serial: pl2303: add support for PL256X multi-port devices Prolific PL256X devices are multi-port USB-to-UART controllers, including the PL2533, PL2543 and PL2565 variants. These devices use vendor requests that differ from those used by the existing TYPE_HX and TYPE_HXN devices. They also require a separate UART reset request and use a port-specific register for configuring flow control. Add a new TYPE_MP device type and select the appropriate vendor requests, reset operation and flow-control register for PL256X devices. Store the USB interface number so that requests can be directed to the corresponding UART port. Detect the supported PL256X variants using bcdDevice before issuing any legacy vendor requests, as PL256X devices do not accept those requests. PL256X devices support baud rates up to 24 Mbps and do not use divisor encoding. Signed-off-by: Charles Yeh Link: https://lore.kernel.org/all/CAAZvQQ6O4p35Xs2hVYaoJxD4D7U0YonsdweuPh6W8RQVhvoUNw@mail.gmail.com/ Signed-off-by: Johan Hovold --- drivers/usb/serial/pl2303.c | 108 +++++++++++++++++++++++++++++++++--- drivers/usb/serial/pl2303.h | 2 +- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 0bcbdcea52af..bf42a545c20f 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GL) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GE) }, { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GS) }, + { USB_DEVICE(PL2303_VENDOR_ID, PL256X_PRODUCT_ID_4P) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) }, { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) }, { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID), @@ -141,10 +142,15 @@ MODULE_DEVICE_TABLE(usb, id_table); #define VENDOR_WRITE_REQUEST_TYPE 0x40 #define VENDOR_WRITE_REQUEST 0x01 #define VENDOR_WRITE_NREQUEST 0x80 +#define VENDOR_WRITE_MPREQUEST 0x80 #define VENDOR_READ_REQUEST_TYPE 0xc0 #define VENDOR_READ_REQUEST 0x01 #define VENDOR_READ_NREQUEST 0x81 +#define VENDOR_READ_MPREQUEST 0x80 + +#define PL256X_RESET_REQUEST_TYPE 0x40 +#define PL256X_RESET_REQUEST 0x96 #define UART_STATE_INDEX 8 #define UART_STATE_MSR_MASK 0x8b @@ -172,6 +178,16 @@ MODULE_DEVICE_TABLE(usb, id_table); #define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18 #define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c +#define PL256X_PORT_A_FLOWCTRL_REG 0xc005 +#define PL256X_PORT_B_FLOWCTRL_REG 0xd005 +#define PL256X_PORT_C_FLOWCTRL_REG 0xe005 +#define PL256X_PORT_D_FLOWCTRL_REG 0xf005 + +#define PL256X_FLOWCTRL_MASK 0x43 +#define PL256X_FLOWCTRL_XON_XOFF 0x40 +#define PL256X_FLOWCTRL_RTS_CTS 0x03 +#define PL256X_FLOWCTRL_NONE 0x00 + static int pl2303_set_break(struct usb_serial_port *port, bool enable); enum pl2303_type { @@ -181,6 +197,7 @@ enum pl2303_type { TYPE_TB, TYPE_HXD, TYPE_HXN, + TYPE_MP, TYPE_COUNT }; @@ -196,6 +213,8 @@ struct pl2303_type_data { struct pl2303_serial_private { const struct pl2303_type_data *type; unsigned long quirks; + u16 interface_num; + u16 flowctrl_reg; }; struct pl2303_private { @@ -236,8 +255,30 @@ static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = { .max_baud_rate = 12000000, .no_divisors = true, }, + [TYPE_MP] = { + .name = "MP", + .max_baud_rate = 24000000, + .no_divisors = true, + }, }; +static int pl256x_uart_reset(struct usb_serial *serial) +{ + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); + struct device *dev = &serial->interface->dev; + int res; + + res = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), + PL256X_RESET_REQUEST, PL256X_RESET_REQUEST_TYPE, + 0, spriv->interface_num, NULL, 0, 100); + if (res) { + dev_err(dev, "failed to reset device: %d\n", res); + return res; + } + + return 0; +} + static int pl2303_vendor_read(struct usb_serial *serial, u16 value, unsigned char buf[1]) { @@ -248,6 +289,8 @@ static int pl2303_vendor_read(struct usb_serial *serial, u16 value, if (spriv->type == &pl2303_type_data[TYPE_HXN]) request = VENDOR_READ_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_READ_MPREQUEST; else request = VENDOR_READ_REQUEST; @@ -279,6 +322,8 @@ static int pl2303_vendor_write(struct usb_serial *serial, u16 value, u16 index) if (spriv->type == &pl2303_type_data[TYPE_HXN]) request = VENDOR_WRITE_NREQUEST; + else if (spriv->type == &pl2303_type_data[TYPE_MP]) + request = VENDOR_WRITE_MPREQUEST; else request = VENDOR_WRITE_REQUEST; @@ -304,10 +349,12 @@ static int pl2303_update_reg(struct usb_serial *serial, u8 reg, u8 mask, u8 val) if (!buf) return -ENOMEM; - if (spriv->type == &pl2303_type_data[TYPE_HXN]) + if (spriv->type == &pl2303_type_data[TYPE_HXN] || + spriv->type == &pl2303_type_data[TYPE_MP]) { ret = pl2303_vendor_read(serial, reg, buf); - else + } else { ret = pl2303_vendor_read(serial, reg | 0x80, buf); + } if (ret) goto out_free; @@ -458,6 +505,14 @@ static int pl2303_detect_type(struct usb_serial *serial) case 0x905: /* GT-2AB */ case 0x1005: /* GC-Q20 */ return TYPE_HXN; + case 0x3302: /* PL2533 VC 2 Port */ + case 0x3304: /* PL2533 VC 4 Port */ + case 0x4302: /* PL2543 VC 2 Port */ + case 0x4304: /* PL2543 VC 4 Port */ + case 0x6502: /* PL2565 VC 2 Port */ + case 0x6504: /* PL2565 VC 4 Port */ + case 0x6506: /* PL2565 VC 4 Port QFN64 package */ + return TYPE_MP; } break; } @@ -491,6 +546,7 @@ static int pl2303_startup(struct usb_serial *serial) struct pl2303_serial_private *spriv; enum pl2303_type type; unsigned char *buf; + unsigned int ifnum; int ret; ret = pl2303_detect_type(serial); @@ -500,20 +556,43 @@ static int pl2303_startup(struct usb_serial *serial) type = ret; dev_dbg(&serial->interface->dev, "device type: %s\n", pl2303_type_data[type].name); + ifnum = serial->interface->altsetting->desc.bInterfaceNumber; + spriv = kzalloc_obj(*spriv); if (!spriv) return -ENOMEM; + if (type == TYPE_MP) { + switch (ifnum) { + case 0: + spriv->flowctrl_reg = PL256X_PORT_A_FLOWCTRL_REG; + break; + case 1: + spriv->flowctrl_reg = PL256X_PORT_B_FLOWCTRL_REG; + break; + case 2: + spriv->flowctrl_reg = PL256X_PORT_C_FLOWCTRL_REG; + break; + case 3: + spriv->flowctrl_reg = PL256X_PORT_D_FLOWCTRL_REG; + break; + default: + kfree(spriv); + return -ENODEV; + } + } + spriv->type = &pl2303_type_data[type]; spriv->quirks = (unsigned long)usb_get_serial_data(serial); spriv->quirks |= spriv->type->quirks; + spriv->interface_num = ifnum; if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE; usb_set_serial_data(serial, spriv); - if (type != TYPE_HXN) { + if (type != TYPE_HXN && type != TYPE_MP) { buf = kmalloc(1, GFP_KERNEL); if (!buf) { kfree(spriv); @@ -575,13 +654,15 @@ static void pl2303_port_remove(struct usb_serial_port *port) static int pl2303_set_control_lines(struct usb_serial_port *port, u8 value) { struct usb_device *dev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); int retval; dev_dbg(&port->dev, "%s - %02x\n", __func__, value); retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CONTROL_REQUEST, SET_CONTROL_REQUEST_TYPE, - value, 0, NULL, 0, 100); + value, spriv->interface_num, NULL, 0, 100); if (retval) dev_err(&port->dev, "%s - failed: %d\n", __func__, retval); @@ -761,7 +842,7 @@ static int pl2303_get_line_request(struct usb_serial_port *port, ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, - 0, 0, buf, 7, 100); + 0, spriv->interface_num, buf, 7, 100); if (ret != 7) { dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); @@ -780,11 +861,13 @@ static int pl2303_set_line_request(struct usb_serial_port *port, unsigned char buf[7]) { struct usb_device *udev = port->serial->dev; + struct usb_serial *serial = port->serial; + struct pl2303_serial_private *spriv = usb_get_serial_data(serial); int ret; ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, - 0, 0, buf, 7, 100); + 0, spriv->interface_num, buf, 7, 100); if (ret < 0) { dev_err(&port->dev, "%s - failed: %d\n", __func__, ret); return ret; @@ -939,6 +1022,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_RTS_CTS); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_RTS_CTS); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60); } @@ -947,6 +1033,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_XON_XOFF); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_XON_XOFF); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0xc0); } @@ -955,6 +1044,9 @@ static void pl2303_set_termios(struct tty_struct *tty, pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG, PL2303_HXN_FLOWCTRL_MASK, PL2303_HXN_FLOWCTRL_NONE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl2303_vendor_write(serial, spriv->flowctrl_reg, + PL256X_FLOWCTRL_NONE); } else { pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0); } @@ -1002,6 +1094,8 @@ static int pl2303_open(struct tty_struct *tty, struct usb_serial_port *port) pl2303_vendor_write(serial, PL2303_HXN_RESET_REG, PL2303_HXN_RESET_UPSTREAM_PIPE | PL2303_HXN_RESET_DOWNSTREAM_PIPE); + } else if (spriv->type == &pl2303_type_data[TYPE_MP]) { + pl256x_uart_reset(serial); } else { pl2303_vendor_write(serial, 8, 0); pl2303_vendor_write(serial, 9, 0); @@ -1112,7 +1206,7 @@ static int pl2303_set_break(struct usb_serial_port *port, bool enable) result = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), BREAK_REQUEST, BREAK_REQUEST_TYPE, state, - 0, NULL, 0, 100); + spriv->interface_num, NULL, 0, 100); if (result) { dev_err(&port->dev, "error sending break = %d\n", result); return result; diff --git a/drivers/usb/serial/pl2303.h b/drivers/usb/serial/pl2303.h index d60eda7f6eda..8eb1e7b5d3ec 100644 --- a/drivers/usb/serial/pl2303.h +++ b/drivers/usb/serial/pl2303.h @@ -26,7 +26,7 @@ #define PL2303_PRODUCT_ID_HCR331 0x331a #define PL2303_PRODUCT_ID_MOTOROLA 0x0307 #define PL2303_PRODUCT_ID_ZTEK 0xe1f1 - +#define PL256X_PRODUCT_ID_4P 0x2533 #define ATEN_VENDOR_ID 0x0557 #define ATEN_VENDOR_ID2 0x0547