[PATCH] media: uvcvideo: Handle payloads starting in the middle of a bulk URB
From: Muhammad Waleed Badar
Date: Fri Oct 09 2026 - 13:01:51 EST
The bulk decoder assumes that every payload starts at the beginning of
a URB. It detects the end of a payload either by a short URB or by the
accumulated payload size reaching dwMaxPayloadTransferSize, and then
decodes the start of the next URB as a new payload header.
Some devices, such as the Realtek USB Camera (0bda:579f) integrated in
Razer Blade laptops, send payloads of dwMaxPayloadTransferSize bytes
back to back without aligning them to URB boundaries. When a frame
spans more than one payload, the next payload header lands in the
middle of a URB. The driver then copies that header and the data that
follows into the current frame, and parses frame data at the start of
the next URB as a header. This shows up as "Marking buffer as bad
(error bit set)" and "Dropping payload (out of sync)" trace messages,
and most frames larger than one payload are returned empty or
truncated. On this camera every MJPEG mode above 1280x720 is affected.
Fix it by splitting URBs at payload boundaries and decoding each part
separately, so that a payload ending in the middle of a URB is
completed and the remainder of the URB is decoded as a new payload.
Devices whose payloads end on URB boundaries are unaffected, as the
whole URB is then processed in a single iteration.
Fixes: c0efd232929c ("V4L/DVB (8145a): USB Video Class driver")
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=207045
Assisted-by: LLM
Signed-off-by: Muhammad Waleed Badar <walid.badar@xxxxxxxxx>
---
Notes:
Tested on a Razer Blade with the integrated Realtek 0bda:579f camera
(bulk endpoint, wMaxPacketSize 512), on Fedora kernel 7.1.10 with the
patch applied to the matching uvcvideo sources. 150 MJPEG frames were
captured per mode with ffmpeg and checked for a valid JPEG EOI marker:
mode before after
1920x1080 25-36 valid 150 valid
1280x1024 37 valid 150 valid
1280x800 38 valid 148 valid
1280x720 150 valid 150 valid
640x480 150 valid 150 valid
GNOME Snapshot (PipeWire) no longer reports JPEG decode errors at
1920x1080. The patch also builds against 7.2.8. It has not been tested
on other bulk-mode UVC cameras.
The root cause analysis and the patch were produced with an AI coding
assistant (Claude Code) and reviewed by me.
drivers/media/usb/uvc/uvc_video.c | 70 ++++++++++++++++++++++++-------
1 file changed, 54 insertions(+), 16 deletions(-)
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index fc3536a43..fe1714ccc 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -1612,27 +1612,20 @@ static void uvc_video_decode_isoc(struct uvc_urb *uvc_urb,
}
}
-static void uvc_video_decode_bulk(struct uvc_urb *uvc_urb,
- struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+static void uvc_video_decode_bulk_chunk(struct uvc_urb *uvc_urb,
+ struct uvc_buffer **bufp,
+ struct uvc_buffer **meta_bufp,
+ u8 *mem, int len, bool short_urb)
{
- struct urb *urb = uvc_urb->urb;
struct uvc_streaming *stream = uvc_urb->stream;
- u8 *mem;
- int len, ret;
-
- /*
- * Ignore ZLPs if they're not part of a frame, otherwise process them
- * to trigger the end of payload detection.
- */
- if (urb->actual_length == 0 && stream->bulk.header_size == 0)
- return;
+ struct uvc_buffer *buf = *bufp;
+ struct uvc_buffer *meta_buf = *meta_bufp;
+ int ret;
- mem = urb->transfer_buffer;
- len = urb->actual_length;
stream->bulk.payload_size += len;
/*
- * If the URB is the first of its payload, decode and save the
+ * If the chunk is the first of its payload, decode and save the
* header.
*/
if (stream->bulk.header_size == 0 && !stream->bulk.skip_payload) {
@@ -1671,7 +1664,7 @@ static void uvc_video_decode_bulk(struct uvc_urb *uvc_urb,
* Detect the payload end by a URB smaller than the maximum size (or
* a payload size equal to the maximum) and process the header again.
*/
- if (urb->actual_length < urb->transfer_buffer_length ||
+ if (short_urb ||
stream->bulk.payload_size >= stream->bulk.max_payload_size) {
if (!stream->bulk.skip_payload && buf != NULL) {
uvc_video_decode_end(stream, buf, stream->bulk.header,
@@ -1684,6 +1677,51 @@ static void uvc_video_decode_bulk(struct uvc_urb *uvc_urb,
stream->bulk.skip_payload = 0;
stream->bulk.payload_size = 0;
}
+
+ *bufp = buf;
+ *meta_bufp = meta_buf;
+}
+
+static void uvc_video_decode_bulk(struct uvc_urb *uvc_urb,
+ struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+ struct urb *urb = uvc_urb->urb;
+ struct uvc_streaming *stream = uvc_urb->stream;
+ bool short_urb = urb->actual_length < urb->transfer_buffer_length;
+ u8 *mem;
+ int len;
+
+ /*
+ * Ignore ZLPs if they're not part of a frame, otherwise process them
+ * to trigger the end of payload detection.
+ */
+ if (urb->actual_length == 0 && stream->bulk.header_size == 0)
+ return;
+
+ mem = urb->transfer_buffer;
+ len = urb->actual_length;
+
+ /*
+ * Some devices (such as the Realtek 0bda:579f) start the next payload
+ * immediately after the previous one instead of in a new URB, so a
+ * payload boundary can fall in the middle of a URB. Split the URB at
+ * payload boundaries and decode each piece separately.
+ */
+ do {
+ int chunk = len;
+
+ if (stream->bulk.payload_size + chunk >
+ stream->bulk.max_payload_size)
+ chunk = stream->bulk.max_payload_size -
+ stream->bulk.payload_size;
+ if (chunk <= 0)
+ chunk = len;
+
+ uvc_video_decode_bulk_chunk(uvc_urb, &buf, &meta_buf, mem,
+ chunk, short_urb && chunk == len);
+ mem += chunk;
+ len -= chunk;
+ } while (len > 0);
}
static void uvc_video_encode_bulk(struct uvc_urb *uvc_urb,
--
2.55.0