[PATCH v3 1/2] USB: serial: ftdi_sio: retry transient errors on chip-side control transfers
From: Chinna Mopurigari Naveen Kumar Reddy
Date: Tue Jun 23 2026 - 04:04:14 EST
usb_control_msg() can return -ETIMEDOUT, -EPIPE or -EPROTO on a
functioning device when the host controller is momentarily unable to
complete the transfer. These are transient conditions that surface
under heavy USB bus load -- for example when several high-baud FTDI
channels share a busy host controller -- and a short retry generally
succeeds.
When such an error happens during a one-shot userspace operation such
as a sysfs write to /sys/bus/usb-serial/devices/ttyUSBx/latency_timer,
the write fails back to userspace with -EIO and the chip's per-channel
latency timer register is left unchanged, even though the device is
healthy and the next attempt would have worked.
Introduce a small helper, ftdi_send_request(), that wraps
usb_control_msg() with up to FTDI_CONTROL_RETRIES attempts on these
documented transient errno values, separated by
FTDI_CONTROL_RETRY_DELAY_MS. Non-transient errors are returned
immediately, as before. Each retry is logged with dev_warn() so the
underlying bus condition stays visible in dmesg.
Convert write_latency_timer() to use the helper. Only this site is
converted, as it is the one where a transient failure has a directly
userspace-visible effect; other chip-side control transfers are left
unchanged.
Signed-off-by: Chinna Mopurigari Naveen Kumar Reddy <naveen.reddy@xxxxxxxxxxxx>
---
drivers/usb/serial/ftdi_sio.c | 48 ++++++++++++++++++++++++++++++-----
1 file changed, 41 insertions(+), 7 deletions(-)
diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c
index af14548fa03d..7aaa7fc1be71 100644
--- a/drivers/usb/serial/ftdi_sio.c
+++ b/drivers/usb/serial/ftdi_sio.c
@@ -30,6 +30,7 @@
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
+#include <linux/delay.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
@@ -1363,10 +1364,46 @@ static int change_speed(struct tty_struct *tty, struct usb_serial_port *port)
return rv;
}
+/*
+ * Send a chip-side control request, retrying transient bus errors.
+ *
+ * On a healthy device, usb_control_msg() can still return -ETIMEDOUT,
+ * -EPIPE or -EPROTO when the host controller is under heavy load --
+ * for example multiple high-baud FTDI channels sharing a host
+ * controller with limited DMA-channel fairness. Failing a single
+ * one-shot configuration (e.g. a sysfs latency_timer write) to the
+ * caller as -EIO in that situation is unhelpful: the next attempt
+ * usually succeeds. Retry a small number of times before giving up.
+ */
+#define FTDI_CONTROL_RETRIES 3
+#define FTDI_CONTROL_RETRY_DELAY_MS 2
+
+static int ftdi_send_request(struct usb_serial_port *port, u8 request,
+ u8 requesttype, u16 value, u16 index)
+{
+ struct usb_device *udev = port->serial->dev;
+ int attempts;
+ int rv = -EIO;
+
+ for (attempts = 0; attempts < FTDI_CONTROL_RETRIES; ++attempts) {
+ rv = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ request, requesttype, value, index,
+ NULL, 0, WDR_TIMEOUT);
+ if (rv >= 0)
+ return rv;
+ if (rv != -ETIMEDOUT && rv != -EPIPE && rv != -EPROTO)
+ return rv;
+ dev_warn(&port->dev,
+ "control msg req 0x%02x attempt %d returned %d, retrying\n",
+ request, attempts + 1, rv);
+ msleep(FTDI_CONTROL_RETRY_DELAY_MS);
+ }
+ return rv;
+}
+
static int write_latency_timer(struct usb_serial_port *port)
{
struct ftdi_private *priv = usb_get_serial_port_data(port);
- struct usb_device *udev = port->serial->dev;
int rv;
int l = priv->latency;
@@ -1378,12 +1415,9 @@ static int write_latency_timer(struct usb_serial_port *port)
dev_dbg(&port->dev, "%s: setting latency timer = %i\n", __func__, l);
- rv = usb_control_msg(udev,
- usb_sndctrlpipe(udev, 0),
- FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
- FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
- l, priv->channel,
- NULL, 0, WDR_TIMEOUT);
+ rv = ftdi_send_request(port, FTDI_SIO_SET_LATENCY_TIMER_REQUEST,
+ FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE,
+ l, priv->channel);
if (rv < 0)
dev_err(&port->dev, "Unable to write latency timer: %i\n", rv);
return rv;
--
2.43.0