The branch main has been updated by bapt:

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

commit 6e845b1333fdf215216dc463ebd9ed34b045c477
Author:     Baptiste Daroussin <[email protected]>
AuthorDate: 2026-07-19 17:18:50 +0000
Commit:     Baptiste Daroussin <[email protected]>
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)
 {

Reply via email to