Linux USB
 help / color / mirror / Atom feed
* [PATCH] usbcore: Add quirk for 255-bytes initial config read
@ 2026-06-19  9:59 Nikhil Solanke
  2026-06-19 19:46 ` Alan Stern
  0 siblings, 1 reply; 4+ messages in thread
From: Nikhil Solanke @ 2026-06-19  9:59 UTC (permalink / raw)
  To: linux-usb
  Cc: gregkh, linux-kernel, stern, michal.pecio, stable, Nikhil Solanke

Certain third-party USB game controllers exposing (or spoofing) an Xbox
360-compatible interface (VID:PID 045e:028e) fail to enumerate under Linux.
The device disconnects from the bus without responding to the initial
GET_DESCRIPTOR(CONFIGURATION) request, and the kernel logs 'unable to read
config index 0 descriptor/start: -71'.

The device then falls back to a secondary Android HID mode (with a
different VID:PID), losing XInput functionality including rumble support.
The failure reproduces across multiple machines, host controller types, and
kernel versions including current mainline and LTS. The device enumerates
correctly and remains in XInput mode under Windows. Notably, the device
enumerates correctly in Android mode when the same aklsjdasd 9-byte request
is issued for that mode's configuration descriptor, confirming the firmware
bug is specific to the XInput mode.

usbmon traces from Linux and Wireshark/USBPcap traces from Windows are
identical up to the point of failure, with no visible protocol-level
difference explaining the divergence. The root cause was identified when
Michal Pecio discovered via a QEMU bus-level capture that Windows does not
use wLength=9 for the initial config descriptor request; it uses
wLength=255. This is not visible in Windows Wireshark/USBPcap traces
because Windows routes enumeration-phase traffic to sniffers only after
initialization completes. Alan Stern subsequently confirmed this with a bus
analyzer on a different USB 2.0 device, and Michal verified the behavior
goes back to Windows 95 OSR2.1.

So, add a new quirk flag USB_QUIRK_CONFIG_SIZE which causes
usb_get_configuration() to issue a 255 byte sized configuration request
instead of USB_DT_CONFIG_SIZE (9) for the initial
GET_DESCRIPTOR(CONFIGURATION) request, mimicking long-standing Windows
behavior.

Suggested-by: Nikhil Solanke <nikhilsolanke5@gmail.com>
Suggested-by: Alan Stern <stern@rowland.harvard.edu>
Suggested-by: Michal Pecio <michal.pecio@gmail.com>
Closes: https://lore.kernel.org/linux-usb/CAFgddh+JWdT4LLwMc5qjM8q_pBu-fRo2qADR5ovAKoGHWMQrRw@mail.gmail.com/
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: <stable@vger.kernel.org>

Signed-off-by: Nikhil Solanke <nikhilsolanke5@gmail.com>
---
 drivers/usb/core/config.c  | 56 +++++++++++++++++++++++++++-----------
 drivers/usb/core/quirks.c  |  3 ++
 include/linux/usb/quirks.h |  4 +++
 3 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
index 45e20c6d76c0..623425cef085 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -912,6 +912,8 @@ int usb_get_configuration(struct usb_device *dev)
 	unsigned char *bigbuffer;
 	struct usb_config_descriptor *desc;
 	int result;
+	size_t usb_dt_config_size = (dev->quirks & USB_QUIRK_CONFIG_SIZE)
+		? USB_DT_CONFIG_SIZE_QUIRK : USB_DT_CONFIG_SIZE;
 
 	if (ncfg > USB_MAXCONFIG) {
 		dev_notice(ddev, "too many configurations: %d, "
@@ -938,7 +940,8 @@ int usb_get_configuration(struct usb_device *dev)
 	if (!dev->rawdescriptors)
 		return -ENOMEM;
 
-	desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
+	desc = kmalloc(usb_dt_config_size, GFP_KERNEL);
+
 	if (!desc)
 		return -ENOMEM;
 
@@ -946,7 +949,7 @@ int usb_get_configuration(struct usb_device *dev)
 		/* We grab just the first descriptor so we know how long
 		 * the whole configuration is */
 		result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
-		    desc, USB_DT_CONFIG_SIZE);
+		    desc, usb_dt_config_size);
 		if (result < 0) {
 			dev_err(ddev, "unable to read config index %d "
 			    "descriptor/%s: %d\n", cfgno, "start", result);
@@ -957,26 +960,39 @@ int usb_get_configuration(struct usb_device *dev)
 			break;
 		} else if (result < 4) {
 			dev_err(ddev, "config index %d descriptor too short "
-			    "(expected %i, got %i)\n", cfgno,
-			    USB_DT_CONFIG_SIZE, result);
+			    "(expected %zu, got %i)\n", cfgno,
+			    usb_dt_config_size, result);
 			result = -EINVAL;
 			goto err;
 		}
-		length = max_t(int, le16_to_cpu(desc->wTotalLength),
-		    USB_DT_CONFIG_SIZE);
+		/* If the device does returns the full length configuration
+		 * descriptor, skip the second read. Fallback to default
+		 * behavior otherwise.
+		 */
+		if (dev->quirks & USB_QUIRK_CONFIG_SIZE
+				&& result == le16_to_cpu(desc->wTotalLength)
+				&& result < USB_DT_CONFIG_SIZE_QUIRK) {
 
-		/* Now that we know the length, get the whole thing */
-		bigbuffer = kmalloc(length, GFP_KERNEL);
-		if (!bigbuffer) {
-			result = -ENOMEM;
-			goto err;
-		}
+			bigbuffer = (unsigned char *) desc;
+			desc = NULL;
+			length = result;
+		} else {
+			length = max_t(int, le16_to_cpu(desc->wTotalLength),
+			    usb_dt_config_size);
+
+			/* Now that we know the length, get the whole thing */
+			bigbuffer = kmalloc(length, GFP_KERNEL);
+			if (!bigbuffer) {
+				result = -ENOMEM;
+				goto err;
+			}
 
-		if (dev->quirks & USB_QUIRK_DELAY_INIT)
-			msleep(200);
+			if (dev->quirks & USB_QUIRK_DELAY_INIT)
+				msleep(200);
 
-		result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
-		    bigbuffer, length);
+			result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
+			    bigbuffer, length);
+		}
 		if (result < 0) {
 			dev_err(ddev, "unable to read config index %d "
 			    "descriptor/%s\n", cfgno, "all");
@@ -1000,6 +1016,14 @@ int usb_get_configuration(struct usb_device *dev)
 	}
 
 err:
+	/* Log failed device's VID:PID pair to make it easy to debug and fix
+	 * enumeration and initialization issues
+	 */
+	if (result < 0) {
+		dev_err(ddev, "Failed to initialize device %04x:%04x due to above errors.",
+		    le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct));
+	}
+
 	kfree(desc);
 	dev->descriptor.bNumConfigurations = cfgno;
 
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 87810eff974e..92219684a604 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -142,6 +142,9 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp)
 				break;
 			case 'q':
 				flags |= USB_QUIRK_FORCE_ONE_CONFIG;
+				break;
+			case 'r':
+				flags |= USB_QUIRK_CONFIG_SIZE;
 			/* Ignore unrecognized flag characters */
 			}
 		}
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index b3cc7beab4a3..f864571da870 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -81,4 +81,8 @@
 /* Device claims zero configurations, forcing to 1 */
 #define USB_QUIRK_FORCE_ONE_CONFIG		BIT(18)
 
+/* Use a 255 byte sized config descriptor request */
+#define USB_QUIRK_CONFIG_SIZE			BIT(19)
+#define USB_DT_CONFIG_SIZE_QUIRK		255
+
 #endif /* __LINUX_USB_QUIRKS_H */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-20 14:37 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-19  9:59 [PATCH] usbcore: Add quirk for 255-bytes initial config read Nikhil Solanke
2026-06-19 19:46 ` Alan Stern
2026-06-20  7:08   ` Nikhil Solanke
2026-06-20 14:37     ` Alan Stern

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox