From nobody Mon Jul 20 12:08:57 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4h3fSb471lz6ls12 for ; Mon, 20 Jul 2026 12:09:03 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4h3fSb12BTz3QCk for ; Mon, 20 Jul 2026 12:09:03 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784549343; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=j7/Gn08kC5dGtIReeTAzWMFrVfTejm/mecyD11gj5zA=; b=xfTQjov97Eex7jKoaZi5P7uqTryZW25u8VHcXJfHvv2IHJHUBe4b7GitN6qjphtxi5Knjj ZcmLVr9MpLJCtj4uHTVXDi2rFxyOBS9Ea+XIyU7bZ9Id3mXzzht6NRKJhga100mJf+eIdI 9MQ4E9LQwpbq85EurIswKU1Z0BstkijZydurnBMVpRGiY2VWVDygoHV5/Ea2HjO7C/b3Ga +6QlgWl5rE/DsQDvEi3PS+00xquPPYKa0gbHU+WtDNuLcXGek377zKCAWKlqy9hHHw8cxz 4Qp7ii0oJ/UBUlefW4kT9bUu8yV6LpAxLrS31LMkx6pYyTHc8AdbSVX8OZDt9g== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1784549343; a=rsa-sha256; cv=none; b=wBInwUMm+8Fgx81Hq2FAK0YN+Tl37n584XTIfcoR8ZRwch8IVBKhK8lFB7EGVqRLz8DGgR DQ/zX/GfviRFpUMG0JDeNDrI3f1cqsFiSAiWA2q0s0ZTU0Hj4u4EmClGLeqbVGgcfmlP+u ZmUSQXw2O4dvXI0eseO4Mjz9FcBEgweTE3OlpQ268YB43VoapKPAHe0JSSbew17KzWyf8L kYw3r1+3sKAFspljX5ut9sdVkSNtvMJPbWOSTf8A+tHJM1Ev7ZPYMRkJYkweRr4py8gdwS DquUHPHEZCrGXOZjrSZqGb36cLQiZSqQYuRO3gFHiTCQL0K4m/1uMTS68hhqew== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1784549343; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=j7/Gn08kC5dGtIReeTAzWMFrVfTejm/mecyD11gj5zA=; b=r/uaf5Pf4Ciod91etarzPDhSeCJ4E8zBSqI6gpdclIqWvTTj/cDeLgWQ1m0ockLCsymB0V fV5rtV3DZEswx1bOE/t8GcniKTcUxrFBkcTE9vd75lP469hBtGmv2ywpdF/rB4dWM7urrz la5ZDGnq61bHi4Ek09qB0FbIoyRQoADOAH1F6+AlIGxsY06CjEt/DE9rSwsHPPS3sD7AgV KAzMpOMd+nB0l2O4xuXQiv6GhSH+i3IerXj3uo478SEO0T2X5KKHv3yEQ8Pawo3FkcMD93 AxNFvWSYuDN4DyFRN8znzB9k9/edFF+sNQh4nL+UiouNs9MQpJd0RilXkMTMDA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h3fSZ6jJ1z10qS for ; Mon, 20 Jul 2026 12:09:02 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3c043 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 20 Jul 2026 12:08:57 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Baptiste Daroussin Subject: git: 6e845b1333fd - main - uvideo: import quirks infrastructure from OpenBSD List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bapt X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 6e845b1333fdf215216dc463ebd9ed34b045c477 Auto-Submitted: auto-generated Date: Mon, 20 Jul 2026 12:08:57 +0000 Message-Id: <6a5e0fd9.3c043.79dfe4be@gitrepo.freebsd.org> The branch main has been updated by bapt: URL: https://cgit.FreeBSD.org/src/commit/?id=6e845b1333fdf215216dc463ebd9ed34b045c477 commit 6e845b1333fdf215216dc463ebd9ed34b045c477 Author: Baptiste Daroussin AuthorDate: 2026-07-19 17:18:50 +0000 Commit: Baptiste Daroussin 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) {