Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 20 Jul 2026 12:08:57 +0000
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 6e845b1333fd - main - uvideo: import quirks infrastructure from OpenBSD
Message-ID:  <6a5e0fd9.3c043.79dfe4be@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=6e845b1333fdf215216dc463ebd9ed34b045c477

commit 6e845b1333fdf215216dc463ebd9ed34b045c477
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-07-19 17:18:50 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-07-20 12:07:21 +0000

    uvideo: import quirks infrastructure from OpenBSD
    
    Import the device quirk system from OpenBSD to handle UVC devices
    that need special handling. This includes:
    
    - UVIDEO_FLAG_ISIGHT_STREAM_HEADER: non-standard streaming header
    - UVIDEO_FLAG_REATTACH: needs reattach after firmware upload
    - UVIDEO_FLAG_VENDOR_CLASS: incorrectly reports as vendor class
    - UVIDEO_FLAG_NOATTACH: device not supported
    - UVIDEO_FLAG_FORMAT_INDEX_IN_BMHINT: format index in bmHint
    
    Add quirks table with known devices and lookup function.
    Add iSight stream header decoder for Apple iSight cameras.
    
    Obtained from:  OpenBSD
---
 sys/dev/usb/video/uvideo.c | 179 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 177 insertions(+), 2 deletions(-)

diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c
index 2019931ab6cf..910915f9f745 100644
--- a/sys/dev/usb/video/uvideo.c
+++ b/sys/dev/usb/video/uvideo.c
@@ -149,6 +149,8 @@ static void		uvideo_vs_close(struct uvideo_softc *);
 static usb_error_t	uvideo_vs_init(struct uvideo_softc *);
 static void		uvideo_vs_decode_stream_header(struct uvideo_softc *,
 			    uint8_t *, int);
+static void		uvideo_vs_decode_stream_header_isight(
+			    struct uvideo_softc *, uint8_t *, int);
 static void		uvideo_isoc_decode(struct uvideo_softc *,
 			    struct usb_page_cache *, int, int);
 static uint8_t		*uvideo_mmap_getbuf(struct uvideo_softc *);
@@ -277,6 +279,8 @@ struct uvideo_softc {
 
 	struct selinfo		sc_selinfo;
 
+	const struct uvideo_quirk *sc_quirk;
+
 	void			(*sc_decode_stream_header)(
 				    struct uvideo_softc *, uint8_t *, int);
 };
@@ -558,6 +562,74 @@ static const enum v4l2_ycbcr_encoding uvideo_matrix_coefficients[] = {
 	V4L2_YCBCR_ENC_SMPTE240M,
 };
 
+/*
+ * Quirk flags for devices needing special handling.
+ */
+#define	UVIDEO_FLAG_ISIGHT_STREAM_HEADER	0x01
+#define	UVIDEO_FLAG_REATTACH			0x02
+#define	UVIDEO_FLAG_VENDOR_CLASS		0x04
+#define	UVIDEO_FLAG_NOATTACH			0x08
+#define	UVIDEO_FLAG_FORMAT_INDEX_IN_BMHINT	0x10
+
+/*
+ * Devices which either fail to declare themselves as UICLASS_VIDEO,
+ * or which need firmware uploads or other quirk handling later on.
+ */
+static const struct uvideo_quirk {
+	struct usb_device_id	uv_dev;
+	const char		*ucode_name;
+	usb_error_t		(*ucode_loader)(struct uvideo_softc *);
+	int			flags;
+} uvideo_quirks[] = {
+	{ { USB_VP(0x05ca, 0x1835) }, "uvideo_r5u87x_05ca-1835",
+	  NULL, 0 },	/* Ricoh VGP VCC5 */
+	{ { USB_VP(0x05ca, 0x1836) }, "uvideo_r5u87x_05ca-1836",
+	  NULL, 0 },	/* Ricoh VGP VCC4 */
+	{ { USB_VP(0x05ca, 0x1837) }, "uvideo_r5u87x_05ca-1837",
+	  NULL, 0 },	/* Ricoh VGP VCC4 (2) */
+	{ { USB_VP(0x05ca, 0x1839) }, "uvideo_r5u87x_05ca-1839",
+	  NULL, 0 },	/* Ricoh VGP VCC6 */
+	{ { USB_VP(0x05ca, 0x183a) }, "uvideo_r5u87x_05ca-183a",
+	  NULL, 0 },	/* Ricoh VGP VCC7 */
+	{ { USB_VP(0x05ca, 0x183b) }, "uvideo_r5u87x_05ca-183b",
+	  NULL, 0 },	/* Ricoh VGP VCC8 */
+	{ { USB_VP(0x05ca, 0x183e) }, "uvideo_r5u87x_05ca-183e",
+	  NULL, 0 },	/* Ricoh VGP VCC9 */
+	{ { USB_VP(0x05ac, 0x8300) }, "uvideo_isight_05ac-8300",
+	  NULL, UVIDEO_FLAG_REATTACH },	/* Apple iSight (needs firmware) */
+	{ { USB_VP(0x05ac, 0x8501) }, NULL, NULL,
+	  UVIDEO_FLAG_ISIGHT_STREAM_HEADER },	/* Apple iSight (non-standard header) */
+	{ { USB_VP(0x046d, 0x08b0) }, NULL, NULL,
+	  UVIDEO_FLAG_VENDOR_CLASS },	/* Logitech QuickCam Fusion */
+	{ { USB_VP(0x046d, 0x08bc) }, NULL, NULL,
+	  UVIDEO_FLAG_VENDOR_CLASS },	/* Logitech QuickCam Orbit MP */
+	{ { USB_VP(0x046d, 0x08c1) }, NULL, NULL,
+	  UVIDEO_FLAG_VENDOR_CLASS },	/* Logitech QuickCam NB Pro */
+	{ { USB_VP(0x046d, 0x08c6) }, NULL, NULL,
+	  UVIDEO_FLAG_VENDOR_CLASS },	/* Logitech QuickCam Pro 5000 */
+	{ { USB_VP(0x046d, 0x08c7) }, NULL, NULL,
+	  UVIDEO_FLAG_VENDOR_CLASS },	/* Logitech QuickCam OEM */
+	{ { USB_VP(0x046d, 0x08c8) }, NULL, NULL,
+	  UVIDEO_FLAG_VENDOR_CLASS },	/* Logitech QuickCam OEM */
+	{ { USB_VP(0x04f2, 0xb2ea) }, NULL, NULL,
+	  UVIDEO_FLAG_NOATTACH },	/* Chicony IR camera (unsupported) */
+	{ { USB_VP(0x0fd9, 0x0066) }, NULL, NULL,
+	  UVIDEO_FLAG_FORMAT_INDEX_IN_BMHINT },	/* Elgato Game Capture HD60 */
+};
+
+static const struct uvideo_quirk *
+uvideo_lookup_quirk(struct usb_attach_arg *uaa)
+{
+	int i;
+
+	for (i = 0; i < nitems(uvideo_quirks); i++) {
+		if (uaa->info.idVendor == uvideo_quirks[i].uv_dev.idVendor &&
+		    uaa->info.idProduct == uvideo_quirks[i].uv_dev.idProduct)
+			return (&uvideo_quirks[i]);
+	}
+	return (NULL);
+}
+
 /*
  * USB device ID table - match standard UVC devices
  */
@@ -677,10 +749,22 @@ static int
 uvideo_probe(device_t dev)
 {
 	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	const struct uvideo_quirk *quirk;
 
 	if (uaa->usb_mode != USB_MODE_HOST)
 		return (ENXIO);
 
+	/* Check quirks table first */
+	quirk = uvideo_lookup_quirk(uaa);
+	if (quirk != NULL) {
+		if (quirk->flags & UVIDEO_FLAG_REATTACH)
+			return (BUS_PROBE_DEFAULT);
+		if (quirk->flags & UVIDEO_FLAG_VENDOR_CLASS &&
+		    uaa->info.bInterfaceClass == UICLASS_VENDOR &&
+		    uaa->info.bInterfaceSubClass == UISUBCLASS_VIDEOCONTROL)
+			return (BUS_PROBE_DEFAULT);
+	}
+
 	if (uaa->info.bInterfaceClass != UICLASS_VIDEO)
 		return (ENXIO);
 
@@ -711,6 +795,15 @@ uvideo_attach(device_t dev)
 	mtx_init(&sc->sc_mtx, "uvideo", NULL, MTX_DEF);
 	knlist_init_mtx(&sc->sc_selinfo.si_note, &sc->sc_mtx);
 
+	/* Look up quirks for this device */
+	sc->sc_quirk = uvideo_lookup_quirk(uaa);
+
+	if (sc->sc_quirk != NULL &&
+	    sc->sc_quirk->flags & UVIDEO_FLAG_NOATTACH) {
+		device_printf(dev, "device not supported\n");
+		goto detach;
+	}
+
 	/* Get the config descriptor to iterate */
 	cdesc = usbd_get_config_descriptor(sc->sc_udev);
 	if (cdesc == NULL) {
@@ -752,8 +845,14 @@ uvideo_attach(device_t dev)
 	sc->sc_iface_index = first_iface;
 	sc->sc_nifaces = nifaces;
 
-	/* Standard UVC stream header decode */
-	sc->sc_decode_stream_header = uvideo_vs_decode_stream_header;
+	/* Map stream header decode function based on quirks */
+	if (sc->sc_quirk != NULL &&
+	    sc->sc_quirk->flags & UVIDEO_FLAG_ISIGHT_STREAM_HEADER) {
+		sc->sc_decode_stream_header =
+		    uvideo_vs_decode_stream_header_isight;
+	} else {
+		sc->sc_decode_stream_header = uvideo_vs_decode_stream_header;
+	}
 
 	/* Parse video control descriptors */
 	error = uvideo_vc_parse_desc(sc);
@@ -2074,6 +2173,21 @@ uvideo_vs_get_probe(struct uvideo_softc *sc, uint8_t *probe_data,
 		bzero(probe_data + actlen,
 		    sizeof(struct usb_video_probe_commit) - actlen);
 
+	/*
+	 * Some devices (e.g. Elgato Cam Link 4K, Elgato Game Capture HD60)
+	 * return an invalid bmHint response which contains the bFormatIndex
+	 * in the second byte. Fix it up.
+	 */
+	if (sc->sc_quirk != NULL &&
+	    sc->sc_quirk->flags & UVIDEO_FLAG_FORMAT_INDEX_IN_BMHINT) {
+		struct usb_video_probe_commit *pc =
+		    (struct usb_video_probe_commit *)probe_data;
+		if (UGETW(pc->bmHint) > 255) {
+			pc->bFormatIndex = UGETW(pc->bmHint) >> 8;
+			USETW(pc->bmHint, 1);
+		}
+	}
+
 	DPRINTFN(1, "GET probe OK, length=%d\n", actlen);
 	return (USB_ERR_NORMAL_COMPLETION);
 }
@@ -2538,6 +2652,67 @@ uvideo_vs_decode_stream_header(struct uvideo_softc *sc, uint8_t *frame,
 	}
 }
 
+/*
+ * The iSight first generation device uses a non-standard streaming
+ * protocol. The stream header is sent once per image and looks like:
+ *
+ * uByte header length
+ * uByte flags
+ * uByte magic1[4] always "11223344"
+ * uByte magic2[8] always "deadbeefdeadface"
+ * uByte unknown[16]
+ *
+ * Sometimes the stream header is prefixed by an unknown byte.
+ */
+static void
+uvideo_vs_decode_stream_header_isight(struct uvideo_softc *sc,
+    uint8_t *frame, int frame_size)
+{
+	struct uvideo_frame_buffer *fb = &sc->sc_frame_buffer;
+	int sample_len, header = 0;
+	uint8_t *buf;
+	uint8_t magic[] = { 0x11, 0x22, 0x33, 0x44, 0xde, 0xad, 0xbe,
+	    0xef, 0xde, 0xad, 0xfa, 0xce };
+
+	if (frame_size > 13 && !memcmp(&frame[2], magic, 12))
+		header = 1;
+	if (frame_size > 14 && !memcmp(&frame[3], magic, 12))
+		header = 1;
+
+	if (header && fb->fid == 0) {
+		fb->fid = 1;
+		return;
+	}
+
+	if (header) {
+		if (sc->sc_mmap_flag) {
+			if (!fb->mmap_q_full)
+				uvideo_mmap_queue(sc, fb->offset, 0);
+		} else {
+			uvideo_read_frame(sc, fb->buf, fb->offset);
+		}
+		fb->offset = 0;
+		fb->mmap_q_full = 0;
+	} else {
+		if (sc->sc_mmap_flag) {
+			if (!fb->mmap_q_full) {
+				buf = uvideo_mmap_getbuf(sc);
+				if (buf == NULL)
+					fb->mmap_q_full = 1;
+			}
+		} else
+			buf = sc->sc_frame_buffer.buf;
+
+		/* Save sample */
+		sample_len = frame_size;
+		if (!fb->mmap_q_full &&
+		    (fb->offset + sample_len) < fb->buf_size) {
+			bcopy(frame, buf + fb->offset, sample_len);
+			fb->offset += sample_len;
+		}
+	}
+}
+
 static uint8_t *
 uvideo_mmap_getbuf(struct uvideo_softc *sc)
 {


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a5e0fd9.3c043.79dfe4be>