From 38798e8a456563709f3e5c190f08ec1fef089a1b Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin Date: Sun, 19 Jul 2026 19:19:12 +0200 Subject: [PATCH] uvideo: add quirks for Logitech C920 webcams Add Logitech C920 (0x046d:0x082d, 0x046d:0x08e5) to the quirks table with UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT to work around firmware bugs where controls are lost after initialization. Implement the save/restore mechanism for UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT to work around devices that reset their control values when streaming starts. The Logitech C920 is known to reset Exposure Absolute and other controls after stream initialization. - Add uvideo_vc_save_ctrls() to save all supported control values during device attach - Add uvideo_vc_restore_ctrls() to restore saved values after stream initialization in uvideo_vs_open() - Add uvideo_vc_free_saved_ctrls() to free saved control data during detach - Add Logitech HD Pro Webcam C922 (0x046d:0x085c) to the quirks table with UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT --- sys/dev/usb/video/uvideo.c | 144 +++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c index 910915f9f745..24d4fbe82907 100644 --- a/sys/dev/usb/video/uvideo.c +++ b/sys/dev/usb/video/uvideo.c @@ -103,6 +103,9 @@ static usb_error_t uvideo_vc_get_ctrl(struct uvideo_softc *, uint8_t *, uint8_t, uint8_t, uint16_t, uint16_t); static usb_error_t uvideo_vc_set_ctrl(struct uvideo_softc *, uint8_t *, uint8_t, uint8_t, uint16_t, uint16_t); +static void uvideo_vc_save_ctrls(struct uvideo_softc *); +static void uvideo_vc_restore_ctrls(struct uvideo_softc *); +static void uvideo_vc_free_saved_ctrls(struct uvideo_softc *); static int uvideo_find_ctrl(struct uvideo_softc *, int); static int uvideo_has_ctrl(struct usb_video_vc_processing_desc *, int); @@ -283,6 +286,16 @@ struct uvideo_softc { void (*sc_decode_stream_header)( struct uvideo_softc *, uint8_t *, int); + + /* Saved control values for UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT */ +#define UVIDEO_MAX_SAVED_CTRLS 32 + int sc_saved_ctrl_count; + struct uvideo_saved_ctrl { + uint8_t unit_id; + uint16_t ctrl_selector; + uint16_t ctrl_len; + uint8_t *ctrl_data; + } sc_saved_ctrls[UVIDEO_MAX_SAVED_CTRLS]; }; /* @@ -570,6 +583,7 @@ static const enum v4l2_ycbcr_encoding uvideo_matrix_coefficients[] = { #define UVIDEO_FLAG_VENDOR_CLASS 0x04 #define UVIDEO_FLAG_NOATTACH 0x08 #define UVIDEO_FLAG_FORMAT_INDEX_IN_BMHINT 0x10 +#define UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT 0x20 /* * Devices which either fail to declare themselves as UICLASS_VIDEO, @@ -615,6 +629,12 @@ static const struct uvideo_quirk { UVIDEO_FLAG_NOATTACH }, /* Chicony IR camera (unsupported) */ { { USB_VP(0x0fd9, 0x0066) }, NULL, NULL, UVIDEO_FLAG_FORMAT_INDEX_IN_BMHINT }, /* Elgato Game Capture HD60 */ + { { USB_VP(0x046d, 0x082d) }, NULL, NULL, + UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT }, /* Logitech HD Pro Webcam C920 */ + { { USB_VP(0x046d, 0x08e5) }, NULL, NULL, + UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT }, /* Logitech C920 PRO HD Webcam */ + { { USB_DEVICE(0x046d, 0x085c) }, NULL, NULL, + UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT }, /* Logitech HD Pro Webcam C922 */ }; static const struct uvideo_quirk * @@ -886,6 +906,12 @@ uvideo_attach(device_t dev) goto detach; } + /* Save current control values for devices that need restoration */ + if (sc->sc_quirk != NULL && + sc->sc_quirk->flags & UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT) { + uvideo_vc_save_ctrls(sc); + } + /* Report what we found */ if (sc->sc_vs_cur != NULL) { device_printf(dev, "%d format(s), iface_index=%d, " @@ -970,6 +996,9 @@ uvideo_detach(device_t dev) /* Unsetup USB transfers */ usbd_transfer_unsetup(sc->sc_xfer, UVIDEO_N_XFER); + /* Free saved control data */ + uvideo_vc_free_saved_ctrls(sc); + seldrain(&sc->sc_selinfo); knlist_destroy(&sc->sc_selinfo.si_note); mtx_destroy(&sc->sc_mtx); @@ -1172,6 +1201,111 @@ uvideo_vc_set_ctrl(struct uvideo_softc *sc, uint8_t *ctrl_data, return (USB_ERR_NORMAL_COMPLETION); } +/* + * Save current values of all supported controls for later restoration. + * Used by UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT to work around devices + * that reset controls when streaming starts (e.g. Logitech C920). + */ +static void +uvideo_vc_save_ctrls(struct uvideo_softc *sc) +{ + int i, idx; + uint8_t *data; + usb_error_t error; + + sc->sc_saved_ctrl_count = 0; + + for (i = 0; uvideo_ctrls[i].cid != 0; i++) { + if (sc->sc_saved_ctrl_count >= UVIDEO_MAX_SAVED_CTRLS) + break; + + idx = uvideo_find_ctrl(sc, uvideo_ctrls[i].cid); + if (idx == EINVAL) + continue; + + data = malloc(uvideo_ctrls[idx].ctrl_len, M_USBDEV, + M_WAITOK | M_CANFAIL); + if (data == NULL) + continue; + + if (sc->sc_desc_vc_pu_cur != NULL) { + error = uvideo_vc_get_ctrl(sc, data, GET_CUR, + sc->sc_desc_vc_pu_cur->bUnitID, + uvideo_ctrls[idx].ctrl_selector, + uvideo_ctrls[idx].ctrl_len); + } else if (sc->sc_desc_vc_ct_cur != NULL) { + error = uvideo_vc_get_ctrl(sc, data, GET_CUR, + sc->sc_desc_vc_ct_cur->bTerminalID, + uvideo_ctrls[idx].ctrl_selector, + uvideo_ctrls[idx].ctrl_len); + } else { + free(data, M_USBDEV, uvideo_ctrls[idx].ctrl_len); + continue; + } + + if (error != USB_ERR_NORMAL_COMPLETION) { + free(data, M_USBDEV, uvideo_ctrls[idx].ctrl_len); + continue; + } + + if (sc->sc_desc_vc_pu_cur != NULL) + sc->sc_saved_ctrls[sc->sc_saved_ctrl_count].unit_id = + sc->sc_desc_vc_pu_cur->bUnitID; + else + sc->sc_saved_ctrls[sc->sc_saved_ctrl_count].unit_id = + sc->sc_desc_vc_ct_cur->bTerminalID; + + sc->sc_saved_ctrls[sc->sc_saved_ctrl_count].ctrl_selector = + uvideo_ctrls[idx].ctrl_selector; + sc->sc_saved_ctrls[sc->sc_saved_ctrl_count].ctrl_len = + uvideo_ctrls[idx].ctrl_len; + sc->sc_saved_ctrls[sc->sc_saved_ctrl_count].ctrl_data = data; + sc->sc_saved_ctrl_count++; + } + + DPRINTFN(1, "saved %d control values\n", sc->sc_saved_ctrl_count); +} + +/* + * Restore control values that were saved earlier. + * Called after stream initialization for devices that need it. + */ +static void +uvideo_vc_restore_ctrls(struct uvideo_softc *sc) +{ + int i; + + for (i = 0; i < sc->sc_saved_ctrl_count; i++) { + uvideo_vc_set_ctrl(sc, + sc->sc_saved_ctrls[i].ctrl_data, + SET_CUR, + sc->sc_saved_ctrls[i].unit_id, + sc->sc_saved_ctrls[i].ctrl_selector, + sc->sc_saved_ctrls[i].ctrl_len); + } + + DPRINTFN(1, "restored %d control values\n", + sc->sc_saved_ctrl_count); +} + +/* + * Free saved control data. + */ +static void +uvideo_vc_free_saved_ctrls(struct uvideo_softc *sc) +{ + int i; + + for (i = 0; i < sc->sc_saved_ctrl_count; i++) { + if (sc->sc_saved_ctrls[i].ctrl_data != NULL) { + free(sc->sc_saved_ctrls[i].ctrl_data, M_USBDEV, + sc->sc_saved_ctrls[i].ctrl_len); + sc->sc_saved_ctrls[i].ctrl_data = NULL; + } + } + sc->sc_saved_ctrl_count = 0; +} + static int uvideo_find_ctrl(struct uvideo_softc *sc, int id) { @@ -2348,6 +2482,16 @@ uvideo_vs_open(struct uvideo_softc *sc) sc->sc_vs_cur->curalt, sc->sc_vs_cur->bulk_endpoint ? "bulk" : "isoc"); + /* + * Restore control values for devices that reset them on stream init. + * The Logitech C920 is known to reset Exposure Absolute and other + * controls when streaming starts. + */ + if (sc->sc_quirk != NULL && + sc->sc_quirk->flags & UVIDEO_FLAG_RESTORE_CTRLS_ON_INIT) { + uvideo_vc_restore_ctrls(sc); + } + return (USB_ERR_NORMAL_COMPLETION); } -- 2.55.0