Linux USB
 help / color / mirror / Atom feed
From: Julian Oes <julian@oes.ch>
To: johan@kernel.org
Cc: gregkh@linuxfoundation.org, linux-usb@vger.kernel.org,
	linux-kernel@vger.kernel.org, Julian Oes <julian@oes.ch>
Subject: [PATCH] USB: serial: generic: recover from bulk-in endpoint stall
Date: Wed, 13 May 2026 14:37:28 +1200	[thread overview]
Message-ID: <20260513023728.55557-1-julian@oes.ch> (raw)

A USB-serial device can go permanently silent when its bulk-in
endpoint gets stalled (-EPIPE).

In my case, disconnecting a CDC ACM device causes my USB hub to stall
the bulk-in endpoint of an FTDI adapter on a sibling port: the FTDI's
bulk-in URB completes with -EPIPE and no further data arrives.
Recovery requires userspace to close and reopen the tty.

usb_serial_generic_read_bulk_callback() treats -EPIPE as fatal and
abandons the URB. My assumption is that for a halted bulk endpoint,
recovery is CLEAR_FEATURE(ENDPOINT_HALT) followed by resubmission.

The fix follows the pattern used in cdc-acm: On -EPIPE, defer recovery
to the existing per-port work, which clears the endpoint halt and
resubmits the read URBs.

With this patch, data from the FTDI adapter keeps arriving after the
unrelated CDC ACM device is unplugged.

Fixes: fc11efe2800f ("USB: serial: continue to read on errors")
Signed-off-by: Julian Oes <julian@oes.ch>
Tested-by: Julian Oes <julian@oes.ch>

---

Reproducer hardware:
- CDC ACM device: PX4 Pixhawk 6X (NuttX, 3185:0035)
- FTDI device: SiK telemetry radio (FTDI FT231X, 0403:6015)
- Hub: j5create JUH377 7-port hub.
       https://info.j5create.com/products/juh377

Tested on Linux 7.1-rc3 on Linux Mint 22.3 x86_64.

Debugging and patch authoring were assisted by AI.

usbmon traces of broken and fixed behaviour available on request.
---
 drivers/usb/serial/generic.c    | 12 ++++++++----
 drivers/usb/serial/usb-serial.c | 13 +++++++++++++
 include/linux/usb/serial.h      |  1 +
 3 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c
index 757f0a586ddb..7f58ea55507b 100644
--- a/drivers/usb/serial/generic.c
+++ b/drivers/usb/serial/generic.c
@@ -371,6 +371,7 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
 	struct usb_serial_port *port = urb->context;
 	unsigned char *data = urb->transfer_buffer;
 	bool stopped = false;
+	bool stalled = false;
 	int status = urb->status;
 	int i;
 
@@ -395,9 +396,10 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
 		stopped = true;
 		break;
 	case -EPIPE:
-		dev_err(&port->dev, "%s - urb stopped: %d\n",
-							__func__, status);
-		stopped = true;
+		dev_err(&port->dev, "%s - urb stalled: %d\n",
+			__func__, status);
+		set_bit(USB_SERIAL_RX_STALLED, &port->flags);
+		stalled = true;
 		break;
 	default:
 		dev_dbg(&port->dev, "%s - nonzero urb status: %d\n",
@@ -420,7 +422,9 @@ void usb_serial_generic_read_bulk_callback(struct urb *urb)
 	 */
 	smp_mb__after_atomic();
 
-	if (stopped)
+	if (stalled)
+		schedule_work(&port->work);
+	if (stopped || stalled)
 		return;
 
 	if (test_bit(USB_SERIAL_THROTTLED, &port->flags))
diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c
index 0e072fd87c3d..383ff1d2fc35 100644
--- a/drivers/usb/serial/usb-serial.c
+++ b/drivers/usb/serial/usb-serial.c
@@ -629,6 +629,19 @@ static void usb_serial_port_work(struct work_struct *work)
 	struct usb_serial_port *port =
 		container_of(work, struct usb_serial_port, work);
 
+	if (test_and_clear_bit(USB_SERIAL_RX_STALLED, &port->flags)) {
+		int i;
+
+		for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i)
+			usb_kill_urb(port->read_urbs[i]);
+
+		usb_clear_halt(port->serial->dev,
+			       usb_rcvbulkpipe(port->serial->dev,
+					       port->bulk_in_endpointAddress));
+
+		usb_serial_generic_submit_read_urbs(port, GFP_KERNEL);
+	}
+
 	tty_port_tty_wakeup(&port->port);
 }
 
diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h
index 75b2b763f1ba..64dadb2cb7ea 100644
--- a/include/linux/usb/serial.h
+++ b/include/linux/usb/serial.h
@@ -20,6 +20,7 @@
 /* USB serial flags */
 #define USB_SERIAL_WRITE_BUSY	0
 #define USB_SERIAL_THROTTLED	1
+#define USB_SERIAL_RX_STALLED	2
 
 /**
  * usb_serial_port: structure for the specific ports of a device.
-- 
2.43.0


                 reply	other threads:[~2026-05-13  2:38 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260513023728.55557-1-julian@oes.ch \
    --to=julian@oes.ch \
    --cc=gregkh@linuxfoundation.org \
    --cc=johan@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox