The branch main has been updated by bapt:

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

commit d0450cbec7e66ee56fd48c6482fd3bd9fb6549dc
Author:     Baptiste Daroussin <[email protected]>
AuthorDate: 2026-05-06 16:28:27 +0000
Commit:     Baptiste Daroussin <[email protected]>
CommitDate: 2026-06-16 11:21:01 +0000

    uvideo: add Camera Terminal controls
    
    Implement UVC Camera Terminal (CT) controls per UVC 1.5 specification
    Table A-12. This adds support for camera-specific controls that are
    separate from the Processing Unit controls already supported.
    
    Reviewed by:    manu
    Differential Revision:  https://reviews.freebsd.org/D56962
---
 sys/dev/usb/video/uvideo.c      | 210 +++++++++++++++++++++++++++++++++++++---
 sys/dev/usb/video/uvideo.h      |   2 +-
 sys/dev/usb/video/uvideo_v4l2.h |  13 +++
 3 files changed, 211 insertions(+), 14 deletions(-)

diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c
index a33b3ac31ea6..52998fbc9b40 100644
--- a/sys/dev/usb/video/uvideo.c
+++ b/sys/dev/usb/video/uvideo.c
@@ -95,6 +95,10 @@ static usb_error_t   uvideo_vc_parse_desc_header(struct 
uvideo_softc *,
                            const struct usb_descriptor *);
 static usb_error_t     uvideo_vc_parse_desc_pu(struct uvideo_softc *,
                            const struct usb_descriptor *);
+static usb_error_t     uvideo_vc_parse_desc_ct(struct uvideo_softc *,
+                           const struct usb_descriptor *);
+static int             uvideo_has_ct_ctrl(
+                           struct usb_video_camera_terminal_desc *, int);
 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 *,
@@ -245,6 +249,11 @@ struct uvideo_softc {
        struct usb_video_vc_processing_desc *sc_desc_vc_pu_cur;
        struct usb_video_vc_processing_desc *sc_desc_vc_pu[UVIDEO_MAX_PU];
 
+#define        UVIDEO_MAX_CT           8
+       int                     sc_desc_vc_ct_num;
+       struct usb_video_camera_terminal_desc *sc_desc_vc_ct_cur;
+       struct usb_video_camera_terminal_desc *sc_desc_vc_ct[UVIDEO_MAX_CT];
+
 #define        UVIDEO_MAX_FORMAT       8
        int                     sc_fmtgrp_idx;
        int                     sc_fmtgrp_num;
@@ -391,6 +400,88 @@ static struct uvideo_controls uvideo_ctrls[] = {
            1,
            0
        },
+       /* Camera Terminal Controls (UVC 1.5 spec Table A-12) */
+       {
+           V4L2_CID_EXPOSURE_AUTO,
+           V4L2_CTRL_TYPE_MENU,
+           "Exposure, Auto",
+           1,
+           CT_AE_MODE_CONTROL,
+           1,
+           0
+       },
+       {
+           V4L2_CID_EXPOSURE_AUTO_PRIORITY,
+           V4L2_CTRL_TYPE_BOOLEAN,
+           "Exposure, Auto Priority",
+           2,
+           CT_AE_PRIORITY_CONTROL,
+           1,
+           0
+       },
+       {
+           V4L2_CID_EXPOSURE_ABSOLUTE,
+           V4L2_CTRL_TYPE_INTEGER,
+           "Exposure (Absolute)",
+           3,
+           CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,
+           4,
+           0
+       },
+       {
+           V4L2_CID_FOCUS_ABSOLUTE,
+           V4L2_CTRL_TYPE_INTEGER,
+           "Focus (Absolute)",
+           5,
+           CT_FOCUS_ABSOLUTE_CONTROL,
+           2,
+           0
+       },
+       {
+           V4L2_CID_FOCUS_AUTO,
+           V4L2_CTRL_TYPE_BOOLEAN,
+           "Focus, Auto",
+           17,
+           CT_FOCUS_AUTO_CONTROL,
+           1,
+           0
+       },
+       {
+           V4L2_CID_ZOOM_ABSOLUTE,
+           V4L2_CTRL_TYPE_INTEGER,
+           "Zoom (Absolute)",
+           9,
+           CT_ZOOM_ABSOLUTE_CONTROL,
+           2,
+           0
+       },
+       {
+           V4L2_CID_PAN_ABSOLUTE,
+           V4L2_CTRL_TYPE_INTEGER,
+           "Pan (Absolute)",
+           11,
+           CT_PANTILT_ABSOLUTE_CONTROL,
+           4,
+           1
+       },
+       {
+           V4L2_CID_TILT_ABSOLUTE,
+           V4L2_CTRL_TYPE_INTEGER,
+           "Tilt (Absolute)",
+           11,
+           CT_PANTILT_ABSOLUTE_CONTROL,
+           4,
+           1
+       },
+       {
+           V4L2_CID_PRIVACY,
+           V4L2_CTRL_TYPE_BOOLEAN,
+           "Privacy",
+           18,
+           CT_PRIVACY_CONTROL,
+           1,
+           0
+       },
        { 0, 0, "", 0, 0, 0, 0 }
 };
 
@@ -824,6 +915,14 @@ uvideo_vc_parse_desc(struct uvideo_softc *sc)
                                return (error);
                        vc_header_found = 1;
                        break;
+               case UDESCSUB_VC_INPUT_TERMINAL:
+                   {
+                       struct usb_video_input_terminal_desc *itd;
+                       itd = (struct usb_video_input_terminal_desc *)desc;
+                       if (UGETW(itd->wTerminalType) == ITT_CAMERA)
+                               (void)uvideo_vc_parse_desc_ct(sc, desc);
+                       break;
+                   }
                case UDESCSUB_VC_PROCESSING_UNIT:
                        (void)uvideo_vc_parse_desc_pu(sc, desc);
                        break;
@@ -883,6 +982,25 @@ uvideo_vc_parse_desc_pu(struct uvideo_softc *sc,
        return (USB_ERR_NORMAL_COMPLETION);
 }
 
+static usb_error_t
+uvideo_vc_parse_desc_ct(struct uvideo_softc *sc,
+    const struct usb_descriptor *desc)
+{
+       struct usb_video_camera_terminal_desc *d;
+
+       d = __DECONST(struct usb_video_camera_terminal_desc *, desc);
+
+       if (sc->sc_desc_vc_ct_num == UVIDEO_MAX_CT) {
+               device_printf(sc->sc_dev, "too many CT descriptors\n");
+               return (USB_ERR_INVAL);
+       }
+
+       sc->sc_desc_vc_ct[sc->sc_desc_vc_ct_num] = d;
+       sc->sc_desc_vc_ct_num++;
+
+       return (USB_ERR_NORMAL_COMPLETION);
+}
+
 static usb_error_t
 uvideo_vc_get_ctrl(struct uvideo_softc *sc, uint8_t *ctrl_data,
     uint8_t request, uint8_t unitid, uint16_t ctrl_selector, uint16_t ctrl_len)
@@ -934,8 +1052,8 @@ uvideo_find_ctrl(struct uvideo_softc *sc, int id)
 {
        int i, j, found;
 
-       if (sc->sc_desc_vc_pu_num == 0) {
-               DPRINTFN(1, "no processing unit descriptors found!\n");
+       if (sc->sc_desc_vc_pu_num == 0 && sc->sc_desc_vc_ct_num == 0) {
+               DPRINTFN(1, "no PU or CT descriptors found!\n");
                return (EINVAL);
        }
 
@@ -951,19 +1069,34 @@ uvideo_find_ctrl(struct uvideo_softc *sc, int id)
                return (EINVAL);
        }
 
-       /* does the device support this control? */
+       /* does a PU support this control? */
+       sc->sc_desc_vc_pu_cur = NULL;
+       sc->sc_desc_vc_ct_cur = NULL;
        for (found = 0, j = 0; j < sc->sc_desc_vc_pu_num; j++) {
                if (uvideo_has_ctrl(sc->sc_desc_vc_pu[j],
                    uvideo_ctrls[i].ctrl_bit) != 0) {
                        found = 1;
+                       sc->sc_desc_vc_pu_cur = sc->sc_desc_vc_pu[j];
                        break;
                }
        }
+
+       /* does a CT support this control? */
+       if (found == 0) {
+               for (j = 0; j < sc->sc_desc_vc_ct_num; j++) {
+                       if (uvideo_has_ct_ctrl(sc->sc_desc_vc_ct[j],
+                           uvideo_ctrls[i].ctrl_bit) != 0) {
+                               found = 1;
+                               sc->sc_desc_vc_ct_cur = sc->sc_desc_vc_ct[j];
+                               break;
+                       }
+               }
+       }
+
        if (found == 0) {
                DPRINTFN(1, "control not supported by device!\n");
                return (EINVAL);
        }
-       sc->sc_desc_vc_pu_cur = sc->sc_desc_vc_pu[j];
 
        return (i);
 }
@@ -978,6 +1111,16 @@ uvideo_has_ctrl(struct usb_video_vc_processing_desc 
*desc, int ctrl_bit)
        return (desc->bmControls[byteof(ctrl_bit)] & bitof(ctrl_bit));
 }
 
+static int
+uvideo_has_ct_ctrl(struct usb_video_camera_terminal_desc *desc, int ctrl_bit)
+{
+
+       if (desc->bControlSize * 8 <= ctrl_bit)
+               return (0);
+
+       return (desc->bmControls[byteof(ctrl_bit)] & bitof(ctrl_bit));
+}
+
 static usb_error_t
 uvideo_vs_parse_desc(struct uvideo_softc *sc,
     struct usb_config_descriptor *cdesc)
@@ -3353,13 +3496,19 @@ uvideo_queryctrl(struct uvideo_softc *sc, struct 
v4l2_queryctrl *qctrl)
        usb_error_t error;
        uint8_t *ctrl_data;
        uint16_t ctrl_len;
+       uint8_t unit_id;
 
        i = uvideo_find_ctrl(sc, qctrl->id);
        if (i == EINVAL)
                return (i);
 
+       if (sc->sc_desc_vc_ct_cur != NULL)
+               unit_id = sc->sc_desc_vc_ct_cur->bTerminalID;
+       else
+               unit_id = sc->sc_desc_vc_pu_cur->bUnitID;
+
        ctrl_len = uvideo_ctrls[i].ctrl_len;
-       if (ctrl_len < 1 || ctrl_len > 2) {
+       if (ctrl_len < 1 || ctrl_len > 4) {
                device_printf(sc->sc_dev,
                    "invalid control length: %d\n", ctrl_len);
                return (EINVAL);
@@ -3374,7 +3523,7 @@ uvideo_queryctrl(struct uvideo_softc *sc, struct 
v4l2_queryctrl *qctrl)
 
        /* get minimum */
        error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_MIN,
-           sc->sc_desc_vc_pu_cur->bUnitID,
+           unit_id,
            uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
        if (error != USB_ERR_NORMAL_COMPLETION) {
                ret = EINVAL;
@@ -3389,11 +3538,15 @@ uvideo_queryctrl(struct uvideo_softc *sc, struct 
v4l2_queryctrl *qctrl)
                qctrl->minimum = uvideo_ctrls[i].sig ?
                    (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
                break;
+       case 4:
+               qctrl->minimum = uvideo_ctrls[i].sig ?
+                   (int32_t)UGETDW(ctrl_data) : UGETDW(ctrl_data);
+               break;
        }
 
        /* get maximum */
        error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_MAX,
-           sc->sc_desc_vc_pu_cur->bUnitID,
+           unit_id,
            uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
        if (error != USB_ERR_NORMAL_COMPLETION) {
                ret = EINVAL;
@@ -3408,11 +3561,15 @@ uvideo_queryctrl(struct uvideo_softc *sc, struct 
v4l2_queryctrl *qctrl)
                qctrl->maximum = uvideo_ctrls[i].sig ?
                    (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
                break;
+       case 4:
+               qctrl->maximum = uvideo_ctrls[i].sig ?
+                   (int32_t)UGETDW(ctrl_data) : UGETDW(ctrl_data);
+               break;
        }
 
        /* get resolution/step */
        error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_RES,
-           sc->sc_desc_vc_pu_cur->bUnitID,
+           unit_id,
            uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
        if (error != USB_ERR_NORMAL_COMPLETION) {
                ret = EINVAL;
@@ -3427,11 +3584,15 @@ uvideo_queryctrl(struct uvideo_softc *sc, struct 
v4l2_queryctrl *qctrl)
                qctrl->step = uvideo_ctrls[i].sig ?
                    (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
                break;
+       case 4:
+               qctrl->step = uvideo_ctrls[i].sig ?
+                   (int32_t)UGETDW(ctrl_data) : UGETDW(ctrl_data);
+               break;
        }
 
        /* get default */
        error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_DEF,
-           sc->sc_desc_vc_pu_cur->bUnitID,
+           unit_id,
            uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
        if (error != USB_ERR_NORMAL_COMPLETION) {
                ret = EINVAL;
@@ -3446,6 +3607,10 @@ uvideo_queryctrl(struct uvideo_softc *sc, struct 
v4l2_queryctrl *qctrl)
                qctrl->default_value = uvideo_ctrls[i].sig ?
                    (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
                break;
+       case 4:
+               qctrl->default_value = uvideo_ctrls[i].sig ?
+                   (int32_t)UGETDW(ctrl_data) : UGETDW(ctrl_data);
+               break;
        }
 
        qctrl->flags = 0;
@@ -3462,13 +3627,19 @@ uvideo_g_ctrl(struct uvideo_softc *sc, struct 
v4l2_control *gctrl)
        usb_error_t error;
        uint8_t *ctrl_data;
        uint16_t ctrl_len;
+       uint8_t unit_id;
 
        i = uvideo_find_ctrl(sc, gctrl->id);
        if (i == EINVAL)
                return (i);
 
+       if (sc->sc_desc_vc_ct_cur != NULL)
+               unit_id = sc->sc_desc_vc_ct_cur->bTerminalID;
+       else
+               unit_id = sc->sc_desc_vc_pu_cur->bUnitID;
+
        ctrl_len = uvideo_ctrls[i].ctrl_len;
-       if (ctrl_len < 1 || ctrl_len > 2)
+       if (ctrl_len < 1 || ctrl_len > 4)
                return (EINVAL);
 
        ctrl_data = malloc(ctrl_len, M_USBDEV, M_WAITOK | M_ZERO);
@@ -3476,7 +3647,7 @@ uvideo_g_ctrl(struct uvideo_softc *sc, struct 
v4l2_control *gctrl)
                return (ENOMEM);
 
        error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_CUR,
-           sc->sc_desc_vc_pu_cur->bUnitID,
+           unit_id,
            uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
        if (error != USB_ERR_NORMAL_COMPLETION) {
                ret = EINVAL;
@@ -3491,6 +3662,10 @@ uvideo_g_ctrl(struct uvideo_softc *sc, struct 
v4l2_control *gctrl)
                gctrl->value = uvideo_ctrls[i].sig ?
                    (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
                break;
+       case 4:
+               gctrl->value = uvideo_ctrls[i].sig ?
+                   (int32_t)UGETDW(ctrl_data) : UGETDW(ctrl_data);
+               break;
        }
 
 out:
@@ -3505,13 +3680,19 @@ uvideo_s_ctrl(struct uvideo_softc *sc, struct 
v4l2_control *sctrl)
        usb_error_t error;
        uint8_t *ctrl_data;
        uint16_t ctrl_len;
+       uint8_t unit_id;
 
        i = uvideo_find_ctrl(sc, sctrl->id);
        if (i == EINVAL)
                return (i);
 
+       if (sc->sc_desc_vc_ct_cur != NULL)
+               unit_id = sc->sc_desc_vc_ct_cur->bTerminalID;
+       else
+               unit_id = sc->sc_desc_vc_pu_cur->bUnitID;
+
        ctrl_len = uvideo_ctrls[i].ctrl_len;
-       if (ctrl_len < 1 || ctrl_len > 2)
+       if (ctrl_len < 1 || ctrl_len > 4)
                return (EINVAL);
 
        ctrl_data = malloc(ctrl_len, M_USBDEV, M_WAITOK | M_ZERO);
@@ -3528,10 +3709,13 @@ uvideo_s_ctrl(struct uvideo_softc *sc, struct 
v4l2_control *sctrl)
        case 2:
                USETW(ctrl_data, sctrl->value);
                break;
+       case 4:
+               USETDW(ctrl_data, sctrl->value);
+               break;
        }
 
        error = uvideo_vc_set_ctrl(sc, ctrl_data, SET_CUR,
-           sc->sc_desc_vc_pu_cur->bUnitID,
+           unit_id,
            uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
        if (error != USB_ERR_NORMAL_COMPLETION)
                ret = EINVAL;
diff --git a/sys/dev/usb/video/uvideo.h b/sys/dev/usb/video/uvideo.h
index bccaf4cf0216..00e5118a65ae 100644
--- a/sys/dev/usb/video/uvideo.h
+++ b/sys/dev/usb/video/uvideo.h
@@ -211,7 +211,7 @@ struct usb_video_camera_terminal_desc {
        uWord   wObjectiveFocalLengthMax;
        uWord   wOcularFocalLength;
        uByte   bControlSize;
-       uByte   *bmControls;
+       uByte   bmControls[255]; /* [bControlSize] */
 } __packed;
 
 /* Table 3-8: VC Processing Unit Descriptor */
diff --git a/sys/dev/usb/video/uvideo_v4l2.h b/sys/dev/usb/video/uvideo_v4l2.h
index 05736bff3e9c..6192ada9fbb6 100644
--- a/sys/dev/usb/video/uvideo_v4l2.h
+++ b/sys/dev/usb/video/uvideo_v4l2.h
@@ -453,6 +453,19 @@ struct v4l2_frmivalenum {
 #define V4L2_CID_SHARPNESS             (V4L2_CID_BASE + 27)
 #define V4L2_CID_BACKLIGHT_COMPENSATION        (V4L2_CID_BASE + 28)
 
+#define V4L2_CID_CAMERA_CLASS_BASE     0x009a0000
+#define V4L2_CID_EXPOSURE_AUTO         (V4L2_CID_CAMERA_CLASS_BASE + 1)
+#define V4L2_CID_EXPOSURE_ABSOLUTE     (V4L2_CID_CAMERA_CLASS_BASE + 2)
+#define V4L2_CID_EXPOSURE_AUTO_PRIORITY        (V4L2_CID_CAMERA_CLASS_BASE + 3)
+#define V4L2_CID_FOCUS_ABSOLUTE                (V4L2_CID_CAMERA_CLASS_BASE + 4)
+#define V4L2_CID_FOCUS_RELATIVE                (V4L2_CID_CAMERA_CLASS_BASE + 5)
+#define V4L2_CID_PAN_ABSOLUTE          (V4L2_CID_CAMERA_CLASS_BASE + 8)
+#define V4L2_CID_TILT_ABSOLUTE         (V4L2_CID_CAMERA_CLASS_BASE + 9)
+#define V4L2_CID_FOCUS_AUTO            (V4L2_CID_CAMERA_CLASS_BASE + 12)
+#define V4L2_CID_ZOOM_ABSOLUTE         (V4L2_CID_CAMERA_CLASS_BASE + 13)
+#define V4L2_CID_ZOOM_CONTINUOUS       (V4L2_CID_CAMERA_CLASS_BASE + 15)
+#define V4L2_CID_PRIVACY               (V4L2_CID_CAMERA_CLASS_BASE + 16)
+
 /*
  *  V4L2 ioctl definitions
  */

Reply via email to