Re: [PATCH v6 1/2] media: uvcvideo: add UVC_QUIRK_CTRL_THROTTLE for fragile USB firmware

Ricardo Ribalda
2026-04-09T07:57:19Z

On Thu, 9 Apr 2026 at 09:44, JP Hein <jp@jphein.com> wrote:
>
> Some UVC devices have firmware that locks up under sustained rapid
> USB control transfers, crashing the xHCI host controller and taking
> all USB devices on the bus with it.
>
> The Razer Kiyo Pro (1532:0e05) is the first known example: approximately
> 25 rapid consecutive control transfers cause the firmware to stall an
> endpoint. The kernel's standard UVC error recovery (GET_CUR on
> UVC_VC_REQUEST_ERROR_CODE_CONTROL) then sends a second transfer to the
> already-stalling device, amplifying the failure into complete firmware
> lockup and xHCI controller death.
>
> Add UVC_QUIRK_CTRL_THROTTLE which:
> - Rate-limits all USB control transfers to 50ms intervals in
> __uvc_query_ctrl(), the lowest-level UVC control transfer function,
> ensuring all callers are throttled including uvc_set_video_ctrl()
> which bypasses uvc_query_ctrl()
> - Skips the error-code query after EPIPE to avoid amplifying stalls
>
> The 50ms interval was determined experimentally: the device is stable
> at this rate under sustained operation, while shorter intervals
> eventually trigger the firmware bug.
>
> Signed-off-by: JP Hein <jp@jphein.com>
> ---
> drivers/media/usb/uvc/uvc_video.c | 30 ++++++++++++++++++++++++++++++
> drivers/media/usb/uvc/uvcvideo.h | 3 +++
> 2 files changed, 33 insertions(+)
>
> diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
> index a5013a7..cee93ac 100644
> --- a/drivers/media/usb/uvc/uvc_video.c
> +++ b/drivers/media/usb/uvc/uvc_video.c
> @@ -36,6 +36,26 @@ static int __uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
> u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
> unsigned int pipe;
>
> + /*
> + * Rate-limit control transfers for devices with fragile firmware.
> + * The Razer Kiyo Pro locks up under sustained rapid control
> + * transfers (hundreds without delay), crashing the xHCI controller.
> + * Throttle in this low-level function to cover all callers,
> + * including uvc_set_video_ctrl() which bypasses uvc_query_ctrl().
> + */
> + if (dev->quirks & UVC_QUIRK_CTRL_THROTTLE) {
> + unsigned long min_interval = msecs_to_jiffies(50);
> +
> + if (dev->last_ctrl_jiffies &&
> + time_before(jiffies,
> + dev->last_ctrl_jiffies + min_interval)) {
> + unsigned long wait = dev->last_ctrl_jiffies +
> + min_interval - jiffies;
> + msleep(jiffies_to_msecs(wait));
> + }
> + dev->last_ctrl_jiffies = jiffies;
> + }
> +
> pipe = (query & 0x80) ? usb_rcvctrlpipe(dev->udev, 0)
> : usb_sndctrlpipe(dev->udev, 0);
> type |= (query & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
> @@ -108,6 +128,16 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
> return ret < 0 ? ret : -EPIPE;
> }
>
> + /*
> + * Skip the error code query for devices that crash under load.
> + * The standard error-code query (GET_CUR on
> + * UVC_VC_REQUEST_ERROR_CODE_CONTROL) sends a second USB transfer to
> + * a device that is already stalling, which can amplify the failure
> + * into a full firmware lockup and xHCI controller death.
> + */
> + if (dev->quirks & UVC_QUIRK_CTRL_THROTTLE)
> + return -EPIPE;

I do not believe this is needed anymore now that the check is in the
inner funcion

> +
> /* Reuse data[0] to request the error code. */
> tmp = *(u8 *)data;
>
> diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
> index 757254f..31f2af5 100644
> --- a/drivers/media/usb/uvc/uvcvideo.h
> +++ b/drivers/media/usb/uvc/uvcvideo.h
> @@ -78,6 +78,7 @@
> #define UVC_QUIRK_INVALID_DEVICE_SOF 0x00010000
> #define UVC_QUIRK_MJPEG_NO_EOF 0x00020000
> #define UVC_QUIRK_MSXU_META 0x00040000
> +#define UVC_QUIRK_CTRL_THROTTLE 0x00080000
>
> /* Format flags */
> #define UVC_FMT_FLAG_COMPRESSED 0x00000001
> @@ -583,6 +584,8 @@ struct uvc_device {
> struct usb_interface *intf;
> unsigned long warnings;
> u32 quirks;
> + /* UVC control transfer throttling (UVC_QUIRK_CTRL_THROTTLE) */
> + unsigned long last_ctrl_jiffies;
> int intfnum;
> char name[32];
>
> --
> 2.43.0
>

--
Ricardo Ribalda