[PATCH] usb: core: add USB_QUIRK_CONFIG_DESC_READ_255 for Razer BlackShark V3 Pro

From: Charles Daoust

Date: Mon Jul 20 2026 - 11:26:40 EST


From: Charles D'Aoust <charles.daoust@xxxxxxxxx>

The Razer BlackShark V3 Pro wireless headset dongle (1532:0577)
fingerprints its host during the first enumeration after power-on: if
the first GET_DESCRIPTOR(CONFIGURATION) request asks for 255 bytes, as
Windows does, the dongle enables its vendor HID command channel; if it
sees the 9-byte header-only read that Linux issues, it disables that
channel for the rest of the power session. Audio still works in that
state, but battery reporting and all vendor commands are silently
ignored, and nothing short of removing power recovers the device:
resets, re-enumerations and byte-exact replays of complete Windows
control sessions were all verified not to help.

Both read lengths are spec-compliant (the device truncates the reply
to wLength); the firmware was evidently only validated against the
larger request. The kernel already accommodates this class of
firmware assumption during enumeration: hub_port_init() reads the
device descriptor with a 64-byte request because that is what Windows
does and what many devices expect.

Add USB_QUIRK_CONFIG_DESC_READ_255, which makes usb_get_configuration()
request 255 bytes for the initial configuration descriptor read rather
than USB_DT_CONFIG_SIZE, apply it to 1532:0577, and expose it as
runtime quirk letter 'r'. Devices without the quirk are unaffected.

The trigger was isolated by single-variable bisection on otherwise
unmodified kernels: with only the widened initial read, the dongle's
vendor channel comes up enabled on a cold plug with no interface
drivers bound (bare enumeration only); without it, it never does.

Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Charles D'Aoust <charles.daoust@xxxxxxxxx>
---
Documentation/admin-guide/kernel-parameters.txt | 3 +++
drivers/usb/core/config.c | 13 ++++++++++---
drivers/usb/core/quirks.c | 6 ++++++
include/linux/usb/quirks.h | 3 +++
4 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5493a7f8..1bd138ba6 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -8169,6 +8169,9 @@ Kernel parameters
q = USB_QUIRK_FORCE_ONE_CONFIG (Device
claims zero configurations,
forcing to 1);
+ r = USB_QUIRK_CONFIG_DESC_READ_255 (initial
+ configuration descriptor read must
+ request 255 bytes, as Windows does);
Example: quirks=0781:5580:bk,0a5c:5834:gij

usbhid.mousepoll=
diff --git a/drivers/usb/core/config.c b/drivers/usb/core/config.c
index cd3231d21..c7e21cc76 100644
--- a/drivers/usb/core/config.c
+++ b/drivers/usb/core/config.c
@@ -908,7 +908,7 @@ int usb_get_configuration(struct usb_device *dev)
{
struct device *ddev = &dev->dev;
int ncfg = dev->descriptor.bNumConfigurations;
- unsigned int cfgno, length;
+ unsigned int cfgno, length, first_read_len;
unsigned char *bigbuffer;
struct usb_config_descriptor *desc;
int result;
@@ -938,7 +938,14 @@ int usb_get_configuration(struct usb_device *dev)
if (!dev->rawdescriptors)
return -ENOMEM;

- desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
+ /*
+ * Some devices (see USB_QUIRK_CONFIG_DESC_READ_255) malfunction unless
+ * the initial configuration descriptor read requests 255 bytes, as
+ * Windows does. Only the header is consumed here either way.
+ */
+ first_read_len = (dev->quirks & USB_QUIRK_CONFIG_DESC_READ_255) ?
+ 255 : USB_DT_CONFIG_SIZE;
+ desc = kmalloc(first_read_len, GFP_KERNEL);
if (!desc)
return -ENOMEM;

@@ -946,7 +953,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, first_read_len);
if (result < 0) {
dev_err(ddev, "unable to read config index %d "
"descriptor/%s: %d\n", cfgno, "start", result);
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 87ee2d938..c8f71b825 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_DESC_READ_255;
/* Ignore unrecognized flag characters */
}
}
@@ -496,6 +499,9 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Razer - Razer Blade Keyboard */
{ USB_DEVICE(0x1532, 0x0116), .driver_info =
USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL },
+ /* Razer - Razer BlackShark V3 Pro wireless headset dongle */
+ { USB_DEVICE(0x1532, 0x0577), .driver_info =
+ USB_QUIRK_CONFIG_DESC_READ_255 },
/* Razer - Razer Kiyo Pro Webcam */
{ USB_DEVICE(0x1532, 0x0e05), .driver_info = USB_QUIRK_NO_LPM },

diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index b3cc7beab..02de5ed48 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -81,4 +81,7 @@
/* Device claims zero configurations, forcing to 1 */
#define USB_QUIRK_FORCE_ONE_CONFIG BIT(18)

+/* initial configuration descriptor read must request 255 bytes (Windows) */
+#define USB_QUIRK_CONFIG_DESC_READ_255 BIT(19)
+
#endif /* __LINUX_USB_QUIRKS_H */
--
2.55.0