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 */
