media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible()
authorHans Verkuil <hverkuil-cisco@xs4all.nl>
Tue, 3 Jan 2023 14:36:19 +0000 (15:36 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 10 Mar 2023 08:39:36 +0000 (09:39 +0100)
[ Upstream commit 9f582f0418ed1c18f92c9e4628075d6ec9a7d9fb ]

Check for inactive controls in uvc_ctrl_is_accessible().

Use the new value for the master_id controls if present, otherwise
use the existing value to determine if it is OK to set the control.
Doing this here avoids attempting to set an inactive control, which
will return an error from the USB device, which returns an invalid
errorcode.

This fixes:
  warn: v4l2-test-controls.cpp(483): s_ctrl returned EIO
  warn: v4l2-test-controls.cpp(483): s_ctrl returned EIO
test VIDIOC_G/S_CTRL: OK
  warn: v4l2-test-controls.cpp(739): s_ext_ctrls returned EIO
  warn: v4l2-test-controls.cpp(739): s_ext_ctrls returned EIO
  warn: v4l2-test-controls.cpp(816): s_ext_ctrls returned EIO
test VIDIOC_G/S/TRY_EXT_CTRLS: OK

Tested with:
v4l2-ctl -c auto_exposure=1
OK
v4l2-ctl -c exposure_time_absolute=251
OK
v4l2-ctl -c auto_exposure=3
OK
v4l2-ctl -c exposure_time_absolute=251
VIDIOC_S_EXT_CTRLS: failed: Input/output error
exposure_time_absolute: Input/output error
ERROR
v4l2-ctl -c auto_exposure=3,exposure_time_absolute=251,auto_exposure=1
v4l2-ctl -C auto_exposure,exposure_time_absolute  
auto_exposure: 1
exposure_time_absolute: 251

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Ricardo Ribalda <ribalda@chromium.org>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/media/usb/uvc/uvc_ctrl.c
drivers/media/usb/uvc/uvc_v4l2.c
drivers/media/usb/uvc/uvcvideo.h

index fcfbd0f7269368a92762b725093aec35a48ffae0..4b3a44264b2ced06caf1c3b708f87b383f48c237 100644 (file)
@@ -1062,11 +1062,28 @@ static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id,
        return 0;
 }
 
+/*
+ * Check if control @v4l2_id can be accessed by the given control @ioctl
+ * (VIDIOC_G_EXT_CTRLS, VIDIOC_TRY_EXT_CTRLS or VIDIOC_S_EXT_CTRLS).
+ *
+ * For set operations on slave controls, check if the master's value is set to
+ * manual, either in the others controls set in the same ioctl call, or from
+ * the master's current value. This catches VIDIOC_S_EXT_CTRLS calls that set
+ * both the master and slave control, such as for instance setting
+ * auto_exposure=1, exposure_time_absolute=251.
+ */
 int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
-                          bool read)
+                          const struct v4l2_ext_controls *ctrls,
+                          unsigned long ioctl)
 {
+       struct uvc_control_mapping *master_map = NULL;
+       struct uvc_control *master_ctrl = NULL;
        struct uvc_control_mapping *mapping;
        struct uvc_control *ctrl;
+       bool read = ioctl == VIDIOC_G_EXT_CTRLS;
+       s32 val;
+       int ret;
+       int i;
 
        if (__uvc_query_v4l2_class(chain, v4l2_id, 0) >= 0)
                return -EACCES;
@@ -1081,6 +1098,29 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
        if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read)
                return -EACCES;
 
+       if (ioctl != VIDIOC_S_EXT_CTRLS || !mapping->master_id)
+               return 0;
+
+       /*
+        * Iterate backwards in cases where the master control is accessed
+        * multiple times in the same ioctl. We want the last value.
+        */
+       for (i = ctrls->count - 1; i >= 0; i--) {
+               if (ctrls->controls[i].id == mapping->master_id)
+                       return ctrls->controls[i].value ==
+                                       mapping->master_manual ? 0 : -EACCES;
+       }
+
+       __uvc_find_control(ctrl->entity, mapping->master_id, &master_map,
+                          &master_ctrl, 0);
+
+       if (!master_ctrl || !(master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
+               return 0;
+
+       ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val);
+       if (ret >= 0 && val != mapping->master_manual)
+               return -EACCES;
+
        return 0;
 }
 
index 7dd387e96c9dee03925f045e959718df834c7a3d..077e1eb7535bef4cc179b5b1a6eecd1571e87e0e 100644 (file)
@@ -1015,8 +1015,7 @@ static int uvc_ctrl_check_access(struct uvc_video_chain *chain,
        int ret = 0;
 
        for (i = 0; i < ctrls->count; ++ctrl, ++i) {
-               ret = uvc_ctrl_is_accessible(chain, ctrl->id,
-                                           ioctl == VIDIOC_G_EXT_CTRLS);
+               ret = uvc_ctrl_is_accessible(chain, ctrl->id, ctrls, ioctl);
                if (ret)
                        break;
        }
index d414a22244057d87cb5548e495044f60c73bf281..d7c4f6f5fca9262718fd57295e59f17f54f8d9a8 100644 (file)
@@ -902,7 +902,8 @@ static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
 int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl);
 int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl);
 int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
-                          bool read);
+                          const struct v4l2_ext_controls *ctrls,
+                          unsigned long ioctl);
 
 int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
                      struct uvc_xu_control_query *xqry);