git: d0450cbec7e6 - main - uvideo: add Camera Terminal controls
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 16 Jun 2026 11:23:04 UTC
The branch main has been updated by bapt:
URL: https://cgit.FreeBSD.org/src/commit/?id=d0450cbec7e66ee56fd48c6482fd3bd9fb6549dc
commit d0450cbec7e66ee56fd48c6482fd3bd9fb6549dc
Author: Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-05-06 16:28:27 +0000
Commit: Baptiste Daroussin <bapt@FreeBSD.org>
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
*/