Message ID | 20210318202928.166955-1-ribalda@chromium.org |
---|---|
Headers | show |
Series | uvcvideo: Fix v4l2-compliance errors | expand |
On 18/03/2021 21:29, Ricardo Ribalda wrote: > Drivers that do not use the ctrl-framework use this function instead. > > Fix the following issues: > > - Do not check for multiple classes when getting the DEF_VAL. > - Return -EINVAL for request_api calls > - Default value cannot be changed, return EINVAL as soon as possible. > - Return the right error_idx > [If an error is found when validating the list of controls passed with > VIDIOC_G_EXT_CTRLS, then error_idx shall be set to ctrls->count to > indicate to userspace that no actual hardware was touched. > It would have been much nicer of course if error_idx could point to the > control index that failed the validation, but sadly that's not how the > API was designed.] > > Fixes v4l2-compliance: > Control ioctls (Input 0): > warn: v4l2-test-controls.cpp(834): error_idx should be equal to count > warn: v4l2-test-controls.cpp(855): error_idx should be equal to count > fail: v4l2-test-controls.cpp(813): doioctl(node, VIDIOC_G_EXT_CTRLS, &ctrls) > test VIDIOC_G/S/TRY_EXT_CTRLS: FAIL > Buffer ioctls (Input 0): > fail: v4l2-test-buffers.cpp(1994): ret != EINVAL && ret != EBADR && ret != ENOTTY > test Requests: FAIL > > Cc: stable@vger.kernel.org > Fixes: 6fa6f831f095 ("media: v4l2-ctrls: add core request support") > Suggested-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> > Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> > Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> > --- > drivers/media/v4l2-core/v4l2-ioctl.c | 59 ++++++++++++++++++---------- > 1 file changed, 38 insertions(+), 21 deletions(-) > > diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c > index 31d1342e61e8..ccd21b4d9c72 100644 > --- a/drivers/media/v4l2-core/v4l2-ioctl.c > +++ b/drivers/media/v4l2-core/v4l2-ioctl.c > @@ -908,7 +908,7 @@ static void v4l_print_default(const void *arg, bool write_only) > pr_cont("driver-specific ioctl\n"); > } > > -static int check_ext_ctrls(struct v4l2_ext_controls *c, int allow_priv) > +static bool check_ext_ctrls(struct v4l2_ext_controls *c, unsigned long ioctl) > { > __u32 i; > > @@ -917,23 +917,40 @@ static int check_ext_ctrls(struct v4l2_ext_controls *c, int allow_priv) > for (i = 0; i < c->count; i++) > c->controls[i].reserved2[0] = 0; > > - /* V4L2_CID_PRIVATE_BASE cannot be used as control class > - when using extended controls. > - Only when passed in through VIDIOC_G_CTRL and VIDIOC_S_CTRL > - is it allowed for backwards compatibility. > - */ > - if (!allow_priv && c->which == V4L2_CID_PRIVATE_BASE) > - return 0; > - if (!c->which) > - return 1; > + switch (c->which) { > + case V4L2_CID_PRIVATE_BASE: > + /* > + * V4L2_CID_PRIVATE_BASE cannot be used as control class > + * when using extended controls. > + * Only when passed in through VIDIOC_G_CTRL and VIDIOC_S_CTRL > + * is it allowed for backwards compatibility. > + */ > + if (ioctl == VIDIOC_G_CTRL || ioctl == VIDIOC_S_CROP) S_CROP -> S_CTRL :-) Regards, Hans > + return false; > + break; > + case V4L2_CTRL_WHICH_DEF_VAL: > + /* Default value cannot be changed */ > + if (ioctl == VIDIOC_S_EXT_CTRLS || > + ioctl == VIDIOC_TRY_EXT_CTRLS) { > + c->error_idx = c->count; > + return false; > + } > + return true; > + case V4L2_CTRL_WHICH_CUR_VAL: > + return true; > + case V4L2_CTRL_WHICH_REQUEST_VAL: > + c->error_idx = c->count; > + return false; > + } > + > /* Check that all controls are from the same control class. */ > for (i = 0; i < c->count; i++) { > if (V4L2_CTRL_ID2WHICH(c->controls[i].id) != c->which) { > - c->error_idx = i; > - return 0; > + c->error_idx = ioctl == VIDIOC_TRY_EXT_CTRLS ? i : c->count; > + return false; > } > } > - return 1; > + return true; > } > > static int check_fmt(struct file *file, enum v4l2_buf_type type) > @@ -2229,7 +2246,7 @@ static int v4l_g_ctrl(const struct v4l2_ioctl_ops *ops, > ctrls.controls = &ctrl; > ctrl.id = p->id; > ctrl.value = p->value; > - if (check_ext_ctrls(&ctrls, 1)) { > + if (check_ext_ctrls(&ctrls, VIDIOC_G_CTRL)) { > int ret = ops->vidioc_g_ext_ctrls(file, fh, &ctrls); > > if (ret == 0) > @@ -2263,7 +2280,7 @@ static int v4l_s_ctrl(const struct v4l2_ioctl_ops *ops, > ctrls.controls = &ctrl; > ctrl.id = p->id; > ctrl.value = p->value; > - if (check_ext_ctrls(&ctrls, 1)) > + if (check_ext_ctrls(&ctrls, VIDIOC_S_CTRL)) > return ops->vidioc_s_ext_ctrls(file, fh, &ctrls); > return -EINVAL; > } > @@ -2285,8 +2302,8 @@ static int v4l_g_ext_ctrls(const struct v4l2_ioctl_ops *ops, > vfd, vfd->v4l2_dev->mdev, p); > if (ops->vidioc_g_ext_ctrls == NULL) > return -ENOTTY; > - return check_ext_ctrls(p, 0) ? ops->vidioc_g_ext_ctrls(file, fh, p) : > - -EINVAL; > + return check_ext_ctrls(p, VIDIOC_G_EXT_CTRLS) ? > + ops->vidioc_g_ext_ctrls(file, fh, p) : -EINVAL; > } > > static int v4l_s_ext_ctrls(const struct v4l2_ioctl_ops *ops, > @@ -2306,8 +2323,8 @@ static int v4l_s_ext_ctrls(const struct v4l2_ioctl_ops *ops, > vfd, vfd->v4l2_dev->mdev, p); > if (ops->vidioc_s_ext_ctrls == NULL) > return -ENOTTY; > - return check_ext_ctrls(p, 0) ? ops->vidioc_s_ext_ctrls(file, fh, p) : > - -EINVAL; > + return check_ext_ctrls(p, VIDIOC_S_EXT_CTRLS) ? > + ops->vidioc_s_ext_ctrls(file, fh, p) : -EINVAL; > } > > static int v4l_try_ext_ctrls(const struct v4l2_ioctl_ops *ops, > @@ -2327,8 +2344,8 @@ static int v4l_try_ext_ctrls(const struct v4l2_ioctl_ops *ops, > vfd, vfd->v4l2_dev->mdev, p); > if (ops->vidioc_try_ext_ctrls == NULL) > return -ENOTTY; > - return check_ext_ctrls(p, 0) ? ops->vidioc_try_ext_ctrls(file, fh, p) : > - -EINVAL; > + return check_ext_ctrls(p, VIDIOC_TRY_EXT_CTRLS) ? > + ops->vidioc_try_ext_ctrls(file, fh, p) : -EINVAL; > } > > /* >
On 18/03/2021 21:29, Ricardo Ribalda wrote: > If a control is inactive return -EACCES to let the userspace know that > the value will not be applied automatically when the control is active > again. > > Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> > Suggested-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> > Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> > --- > drivers/media/usb/uvc/uvc_ctrl.c | 68 ++++++++++++++++++++++---------- > drivers/media/usb/uvc/uvc_v4l2.c | 11 +++++- > drivers/media/usb/uvc/uvcvideo.h | 2 +- > 3 files changed, 58 insertions(+), 23 deletions(-) > > diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c > index 24fd5afc4e4f..1ec8333811bc 100644 > --- a/drivers/media/usb/uvc/uvc_ctrl.c > +++ b/drivers/media/usb/uvc/uvc_ctrl.c > @@ -1046,8 +1046,33 @@ static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id, > return 0; > } > > +static bool uvc_ctrl_is_inactive(struct uvc_video_chain *chain, > + struct uvc_control *ctrl, > + struct uvc_control_mapping *mapping) > +{ > + struct uvc_control_mapping *master_map = NULL; > + struct uvc_control *master_ctrl = NULL; > + s32 val; > + int ret; > + > + if (!mapping->master_id) > + return false; > + > + __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 false; > + > + ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); > + if (ret < 0 || val == mapping->master_manual) > + return false; > + > + return true; > +} This doesn't work. The problem is that you need to test the new value for the master control against master_manual, and you are testing against the old value. So for my uvc camera I have this situation after boot: auto_exposure 0x009a0901 (menu) : min=0 max=3 default=3 value=3 (Aperture Priority Mode) 1: Manual Mode 3: Aperture Priority Mode exposure_time_absolute 0x009a0902 (int) : min=3 max=2047 step=1 default=250 value=250 flags=inactive But trying to change both auto_exposure to manual AND set the new exposure_time_absolute will fail: $ v4l2-ctl -c auto_exposure=1,exposure_time_absolute=251 VIDIOC_S_EXT_CTRLS: failed: Permission denied Error setting controls: Permission denied This works though with the uvc driver as is currently in the kernel. Unfortunately, this is not something that is explicitly tested in v4l2-compliance. Regards, Hans > + > int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, > - bool read) > + unsigned long ioctl) > { > struct uvc_control_mapping *mapping; > struct uvc_control *ctrl; > @@ -1059,11 +1084,26 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, > if (!ctrl) > return -EINVAL; > > - if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) && read) > - return -EACCES; > - > - if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read) > - return -EACCES; > + switch (ioctl) { > + case VIDIOC_G_CTRL: > + case VIDIOC_G_EXT_CTRLS: > + if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) > + return -EACCES; > + break; > + case VIDIOC_S_EXT_CTRLS: > + case VIDIOC_S_CTRL: > + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) > + return -EACCES; > + if (uvc_ctrl_is_inactive(chain, ctrl, mapping)) > + return -EACCES; > + break; > + case VIDIOC_TRY_EXT_CTRLS: > + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) > + return -EACCES; > + break; > + default: > + return -EINVAL; > + } > > return 0; > } > @@ -1087,8 +1127,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, > struct uvc_control_mapping *mapping, > struct v4l2_queryctrl *v4l2_ctrl) > { > - struct uvc_control_mapping *master_map = NULL; > - struct uvc_control *master_ctrl = NULL; > const struct uvc_menu_info *menu; > unsigned int i; > > @@ -1104,18 +1142,8 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, > if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) > v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > - if (mapping->master_id) > - __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)) { > - s32 val; > - int ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); > - if (ret < 0) > - return ret; > - > - if (val != mapping->master_manual) > - v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; > - } > + if (uvc_ctrl_is_inactive(chain, ctrl, mapping)) > + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; > > if (!ctrl->cached) { > int ret = uvc_ctrl_populate_cache(chain, ctrl); > diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c > index fbb99f3c2fb4..ddebdeb5a81b 100644 > --- a/drivers/media/usb/uvc/uvc_v4l2.c > +++ b/drivers/media/usb/uvc/uvc_v4l2.c > @@ -999,6 +999,10 @@ static int uvc_ioctl_g_ctrl(struct file *file, void *fh, > struct v4l2_ext_control xctrl; > int ret; > > + ret = uvc_ctrl_is_accessible(chain, ctrl->id, VIDIOC_G_CTRL); > + if (ret) > + return ret; > + > memset(&xctrl, 0, sizeof(xctrl)); > xctrl.id = ctrl->id; > > @@ -1023,6 +1027,10 @@ static int uvc_ioctl_s_ctrl(struct file *file, void *fh, > struct v4l2_ext_control xctrl; > int ret; > > + ret = uvc_ctrl_is_accessible(chain, ctrl->id, VIDIOC_S_CTRL); > + if (ret) > + return ret; > + > memset(&xctrl, 0, sizeof(xctrl)); > xctrl.id = ctrl->id; > xctrl.value = ctrl->value; > @@ -1054,8 +1062,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, ioctl); > if (ret) > break; > } > diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h > index 9471c342a310..a93aeedb5499 100644 > --- a/drivers/media/usb/uvc/uvcvideo.h > +++ b/drivers/media/usb/uvc/uvcvideo.h > @@ -903,7 +903,7 @@ 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); > + unsigned long ioctl); > > int uvc_xu_ctrl_query(struct uvc_video_chain *chain, > struct uvc_xu_control_query *xqry); >
Hi Hans Thanks for testing this. On Fri, Mar 19, 2021 at 10:10 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote: > > On 18/03/2021 21:29, Ricardo Ribalda wrote: > > If a control is inactive return -EACCES to let the userspace know that > > the value will not be applied automatically when the control is active > > again. > > > > Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> > > Suggested-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> > > Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> > > --- > > drivers/media/usb/uvc/uvc_ctrl.c | 68 ++++++++++++++++++++++---------- > > drivers/media/usb/uvc/uvc_v4l2.c | 11 +++++- > > drivers/media/usb/uvc/uvcvideo.h | 2 +- > > 3 files changed, 58 insertions(+), 23 deletions(-) > > > > diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c > > index 24fd5afc4e4f..1ec8333811bc 100644 > > --- a/drivers/media/usb/uvc/uvc_ctrl.c > > +++ b/drivers/media/usb/uvc/uvc_ctrl.c > > @@ -1046,8 +1046,33 @@ static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id, > > return 0; > > } > > > > +static bool uvc_ctrl_is_inactive(struct uvc_video_chain *chain, > > + struct uvc_control *ctrl, > > + struct uvc_control_mapping *mapping) > > +{ > > + struct uvc_control_mapping *master_map = NULL; > > + struct uvc_control *master_ctrl = NULL; > > + s32 val; > > + int ret; > > + > > + if (!mapping->master_id) > > + return false; > > + > > + __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 false; > > + > > + ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); > > + if (ret < 0 || val == mapping->master_manual) > > + return false; > > + > > + return true; > > +} > > This doesn't work. The problem is that you need to test the new value for the > master control against master_manual, and you are testing against the old value. > > So for my uvc camera I have this situation after boot: > > auto_exposure 0x009a0901 (menu) : min=0 max=3 default=3 value=3 (Aperture Priority Mode) > 1: Manual Mode > 3: Aperture Priority Mode > exposure_time_absolute 0x009a0902 (int) : min=3 max=2047 step=1 default=250 value=250 flags=inactive > > But trying to change both auto_exposure to manual AND set the new exposure_time_absolute > will fail: > > $ v4l2-ctl -c auto_exposure=1,exposure_time_absolute=251 > VIDIOC_S_EXT_CTRLS: failed: Permission denied > Error setting controls: Permission denied > > This works though with the uvc driver as is currently in the kernel. > > Unfortunately, this is not something that is explicitly tested in v4l2-compliance. > Can you try dropping this patch and relaying on media: uvcvideo: Set error_idx during ctrl_commit errors ? thanks! > Regards, > > Hans > > > + > > int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, > > - bool read) > > + unsigned long ioctl) > > { > > struct uvc_control_mapping *mapping; > > struct uvc_control *ctrl; > > @@ -1059,11 +1084,26 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, > > if (!ctrl) > > return -EINVAL; > > > > - if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) && read) > > - return -EACCES; > > - > > - if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read) > > - return -EACCES; > > + switch (ioctl) { > > + case VIDIOC_G_CTRL: > > + case VIDIOC_G_EXT_CTRLS: > > + if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) > > + return -EACCES; > > + break; > > + case VIDIOC_S_EXT_CTRLS: > > + case VIDIOC_S_CTRL: > > + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) > > + return -EACCES; > > + if (uvc_ctrl_is_inactive(chain, ctrl, mapping)) > > + return -EACCES; > > + break; > > + case VIDIOC_TRY_EXT_CTRLS: > > + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) > > + return -EACCES; > > + break; > > + default: > > + return -EINVAL; > > + } > > > > return 0; > > } > > @@ -1087,8 +1127,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, > > struct uvc_control_mapping *mapping, > > struct v4l2_queryctrl *v4l2_ctrl) > > { > > - struct uvc_control_mapping *master_map = NULL; > > - struct uvc_control *master_ctrl = NULL; > > const struct uvc_menu_info *menu; > > unsigned int i; > > > > @@ -1104,18 +1142,8 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, > > if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) > > v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > > > - if (mapping->master_id) > > - __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)) { > > - s32 val; > > - int ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); > > - if (ret < 0) > > - return ret; > > - > > - if (val != mapping->master_manual) > > - v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; > > - } > > + if (uvc_ctrl_is_inactive(chain, ctrl, mapping)) > > + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; > > > > if (!ctrl->cached) { > > int ret = uvc_ctrl_populate_cache(chain, ctrl); > > diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c > > index fbb99f3c2fb4..ddebdeb5a81b 100644 > > --- a/drivers/media/usb/uvc/uvc_v4l2.c > > +++ b/drivers/media/usb/uvc/uvc_v4l2.c > > @@ -999,6 +999,10 @@ static int uvc_ioctl_g_ctrl(struct file *file, void *fh, > > struct v4l2_ext_control xctrl; > > int ret; > > > > + ret = uvc_ctrl_is_accessible(chain, ctrl->id, VIDIOC_G_CTRL); > > + if (ret) > > + return ret; > > + > > memset(&xctrl, 0, sizeof(xctrl)); > > xctrl.id = ctrl->id; > > > > @@ -1023,6 +1027,10 @@ static int uvc_ioctl_s_ctrl(struct file *file, void *fh, > > struct v4l2_ext_control xctrl; > > int ret; > > > > + ret = uvc_ctrl_is_accessible(chain, ctrl->id, VIDIOC_S_CTRL); > > + if (ret) > > + return ret; > > + > > memset(&xctrl, 0, sizeof(xctrl)); > > xctrl.id = ctrl->id; > > xctrl.value = ctrl->value; > > @@ -1054,8 +1062,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, ioctl); > > if (ret) > > break; > > } > > diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h > > index 9471c342a310..a93aeedb5499 100644 > > --- a/drivers/media/usb/uvc/uvcvideo.h > > +++ b/drivers/media/usb/uvc/uvcvideo.h > > @@ -903,7 +903,7 @@ 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); > > + unsigned long ioctl); > > > > int uvc_xu_ctrl_query(struct uvc_video_chain *chain, > > struct uvc_xu_control_query *xqry); > > > -- Ricardo Ribalda
On 19/03/2021 10:49, Ricardo Ribalda wrote: > Hi Hans > > Thanks for testing this. > > > > > On Fri, Mar 19, 2021 at 10:10 AM Hans Verkuil <hverkuil-cisco@xs4all.nl> wrote: >> >> On 18/03/2021 21:29, Ricardo Ribalda wrote: >>> If a control is inactive return -EACCES to let the userspace know that >>> the value will not be applied automatically when the control is active >>> again. >>> >>> Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> >>> Suggested-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> >>> Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> >>> --- >>> drivers/media/usb/uvc/uvc_ctrl.c | 68 ++++++++++++++++++++++---------- >>> drivers/media/usb/uvc/uvc_v4l2.c | 11 +++++- >>> drivers/media/usb/uvc/uvcvideo.h | 2 +- >>> 3 files changed, 58 insertions(+), 23 deletions(-) >>> >>> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c >>> index 24fd5afc4e4f..1ec8333811bc 100644 >>> --- a/drivers/media/usb/uvc/uvc_ctrl.c >>> +++ b/drivers/media/usb/uvc/uvc_ctrl.c >>> @@ -1046,8 +1046,33 @@ static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id, >>> return 0; >>> } >>> >>> +static bool uvc_ctrl_is_inactive(struct uvc_video_chain *chain, >>> + struct uvc_control *ctrl, >>> + struct uvc_control_mapping *mapping) >>> +{ >>> + struct uvc_control_mapping *master_map = NULL; >>> + struct uvc_control *master_ctrl = NULL; >>> + s32 val; >>> + int ret; >>> + >>> + if (!mapping->master_id) >>> + return false; >>> + >>> + __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 false; >>> + >>> + ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); >>> + if (ret < 0 || val == mapping->master_manual) >>> + return false; >>> + >>> + return true; >>> +} >> >> This doesn't work. The problem is that you need to test the new value for the >> master control against master_manual, and you are testing against the old value. >> >> So for my uvc camera I have this situation after boot: >> >> auto_exposure 0x009a0901 (menu) : min=0 max=3 default=3 value=3 (Aperture Priority Mode) >> 1: Manual Mode >> 3: Aperture Priority Mode >> exposure_time_absolute 0x009a0902 (int) : min=3 max=2047 step=1 default=250 value=250 flags=inactive >> >> But trying to change both auto_exposure to manual AND set the new exposure_time_absolute >> will fail: >> >> $ v4l2-ctl -c auto_exposure=1,exposure_time_absolute=251 >> VIDIOC_S_EXT_CTRLS: failed: Permission denied >> Error setting controls: Permission denied >> >> This works though with the uvc driver as is currently in the kernel. >> >> Unfortunately, this is not something that is explicitly tested in v4l2-compliance. >> > > Can you try dropping this patch and relaying on media: uvcvideo: Set > error_idx during ctrl_commit errors ? That doesn't work all that well. The uvc_query_ctrl() function is problematic: 1) It can return -EILSEQ where -EACCES is expected. Not a big deal, EACCES makes more sense here anyway. 2) It can return error code 6 (Invalid control), and then it returns -EIO. I'm not entirely clear why I get code 6, I haven't dug deep enough for that. If I change that to EACCES, then v4l2-compliance passes, but... 3) This function keeps spamming the "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n" error in the kernel log. In all fairness, the current kernel driver does the same, but with this patch it no longer does. I think the uvc driver really has to check this. It doesn't have to be during the validation step in uvc_ctrl_check_access(), it is fine if this check happens during the commit phase. I checked the UVC 1.5 spec and in 4.2.2.1.4 (Exposure Time (Absolute) Control) it says: When the Auto-Exposure Mode control is in Auto mode or Aperture Priority mode attempts to programmatically set this control shall result in a protocol STALL and an error code of bRequestErrorCode = “Wrong state”. So the uvc driver should just detect this and avoid writing this control in such a case. Regards, Hans > > thanks! > >> Regards, >> >> Hans >> >>> + >>> int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, >>> - bool read) >>> + unsigned long ioctl) >>> { >>> struct uvc_control_mapping *mapping; >>> struct uvc_control *ctrl; >>> @@ -1059,11 +1084,26 @@ int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id, >>> if (!ctrl) >>> return -EINVAL; >>> >>> - if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) && read) >>> - return -EACCES; >>> - >>> - if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read) >>> - return -EACCES; >>> + switch (ioctl) { >>> + case VIDIOC_G_CTRL: >>> + case VIDIOC_G_EXT_CTRLS: >>> + if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) >>> + return -EACCES; >>> + break; >>> + case VIDIOC_S_EXT_CTRLS: >>> + case VIDIOC_S_CTRL: >>> + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) >>> + return -EACCES; >>> + if (uvc_ctrl_is_inactive(chain, ctrl, mapping)) >>> + return -EACCES; >>> + break; >>> + case VIDIOC_TRY_EXT_CTRLS: >>> + if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) >>> + return -EACCES; >>> + break; >>> + default: >>> + return -EINVAL; >>> + } >>> >>> return 0; >>> } >>> @@ -1087,8 +1127,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, >>> struct uvc_control_mapping *mapping, >>> struct v4l2_queryctrl *v4l2_ctrl) >>> { >>> - struct uvc_control_mapping *master_map = NULL; >>> - struct uvc_control *master_ctrl = NULL; >>> const struct uvc_menu_info *menu; >>> unsigned int i; >>> >>> @@ -1104,18 +1142,8 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, >>> if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR)) >>> v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; >>> >>> - if (mapping->master_id) >>> - __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)) { >>> - s32 val; >>> - int ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); >>> - if (ret < 0) >>> - return ret; >>> - >>> - if (val != mapping->master_manual) >>> - v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; >>> - } >>> + if (uvc_ctrl_is_inactive(chain, ctrl, mapping)) >>> + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; >>> >>> if (!ctrl->cached) { >>> int ret = uvc_ctrl_populate_cache(chain, ctrl); >>> diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c >>> index fbb99f3c2fb4..ddebdeb5a81b 100644 >>> --- a/drivers/media/usb/uvc/uvc_v4l2.c >>> +++ b/drivers/media/usb/uvc/uvc_v4l2.c >>> @@ -999,6 +999,10 @@ static int uvc_ioctl_g_ctrl(struct file *file, void *fh, >>> struct v4l2_ext_control xctrl; >>> int ret; >>> >>> + ret = uvc_ctrl_is_accessible(chain, ctrl->id, VIDIOC_G_CTRL); >>> + if (ret) >>> + return ret; >>> + >>> memset(&xctrl, 0, sizeof(xctrl)); >>> xctrl.id = ctrl->id; >>> >>> @@ -1023,6 +1027,10 @@ static int uvc_ioctl_s_ctrl(struct file *file, void *fh, >>> struct v4l2_ext_control xctrl; >>> int ret; >>> >>> + ret = uvc_ctrl_is_accessible(chain, ctrl->id, VIDIOC_S_CTRL); >>> + if (ret) >>> + return ret; >>> + >>> memset(&xctrl, 0, sizeof(xctrl)); >>> xctrl.id = ctrl->id; >>> xctrl.value = ctrl->value; >>> @@ -1054,8 +1062,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, ioctl); >>> if (ret) >>> break; >>> } >>> diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h >>> index 9471c342a310..a93aeedb5499 100644 >>> --- a/drivers/media/usb/uvc/uvcvideo.h >>> +++ b/drivers/media/usb/uvc/uvcvideo.h >>> @@ -903,7 +903,7 @@ 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); >>> + unsigned long ioctl); >>> >>> int uvc_xu_ctrl_query(struct uvc_video_chain *chain, >>> struct uvc_xu_control_query *xqry); >>> >> > >