2023-01-03 14:53:22

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 0/8] Follow-up patches for uvc v4l2-compliance

This patchset contains the fixes for the comments on "v10 of Fix
v4l2-compliance errors series". In particular to the patches

-uvcvideo: uvc_ctrl_is_accessible: check for INACTIVE
-uvcvideo: improve error handling in uvc_query_ctrl()

And the patch:
-uvcvideo: Fix handling on Bitmask controls

To: Laurent Pinchart <[email protected]>
To: Mauro Carvalho Chehab <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: Hans Verkuil <[email protected]>
Signed-off-by: Ricardo Ribalda <[email protected]>

---
Changes in v3 (Thanks Laurent):
- Add a new patch for refactoring __uvc_ctrl_add_mapping
- Use standard names for menus
- Return error on uvc_mapping_get_menu_value
- Add const
- StyLe!
- Do not return positive errors in uvc_query_ctrl()
- Improve commit message
- improve error logging in uvc_query_ctrl()
- Fix comment
- Improve doc
- Fix handling on Bitmask controls
- s/uvc/UVC
- Reflow comments to 80 chars
- Test with GET_RES first
- Remove clamp to (0,..)
- Return -EACCES for Wrong state error
- Full rewrite of commit message
- uvc_ctrl_is_accessible: check for INACTIVE
- Update commit message
- Remove try variable
- Update documentation
- Implement mask for V4L2_CTRL_TYPE_MENU
- Include linux/bits.h
- Link to v2: https://lore.kernel.org/r/[email protected]

Changes in v2:
- Include "Get menu names from framework series"
https://lore.kernel.org/r/[email protected]
- Link to v1: https://lore.kernel.org/r/[email protected]

---
Hans Verkuil (2):
media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible()
media: uvcvideo: improve error logging in uvc_query_ctrl()

Ricardo Ribalda (6):
media: uvcvideo: Return -EACCES for Wrong state error
media: uvcvideo: Do not return positive errors in uvc_query_ctrl()
media: uvcvideo: Fix handling on Bitmask controls
media: uvcvideo: Implement mask for V4L2_CTRL_TYPE_MENU
media: uvcvideo: Refactor __uvc_ctrl_add_mapping
media: uvcvideo: Use standard names for menus

drivers/media/usb/uvc/uvc_ctrl.c | 238 ++++++++++++++++++++++++++++---------
drivers/media/usb/uvc/uvc_driver.c | 10 +-
drivers/media/usb/uvc/uvc_v4l2.c | 108 ++++++++++++-----
drivers/media/usb/uvc/uvc_video.c | 15 +--
drivers/media/usb/uvc/uvcvideo.h | 8 +-
include/uapi/linux/uvcvideo.h | 4 +-
6 files changed, 281 insertions(+), 102 deletions(-)
---
base-commit: 69b41ac87e4a664de78a395ff97166f0b2943210
change-id: 20220920-resend-v4l2-compliance-4fdbe4fbd7b5

Best regards,
--
Ricardo Ribalda <[email protected]>


2023-01-03 14:53:22

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 7/8] media: uvcvideo: Refactor __uvc_ctrl_add_mapping

Simplify the exit code with a common error tag freeing all the memory.

Suggested-by: Laurent Pinchart <[email protected]>
Signed-off-by: Ricardo Ribalda <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index aa7a668f60a7..4830120e6506 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -2296,32 +2296,30 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
unsigned int i;

/*
- * Most mappings come from static kernel data and need to be duplicated.
+ * Most mappings come from static kernel data, and need to be duplicated.
* Mappings that come from userspace will be unnecessarily duplicated,
* this could be optimized.
*/
map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL);
- if (map == NULL)
+ if (!map)
return -ENOMEM;

+ map->name = NULL;
+ map->menu_info = NULL;
+
/* For UVCIOC_CTRL_MAP custom control */
if (mapping->name) {
map->name = kstrdup(mapping->name, GFP_KERNEL);
- if (!map->name) {
- kfree(map);
- return -ENOMEM;
- }
+ if (!map->name)
+ goto nomem;
}

INIT_LIST_HEAD(&map->ev_subs);

size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
- if (map->menu_info == NULL) {
- kfree(map->name);
- kfree(map);
- return -ENOMEM;
- }
+ if (!map->menu_info)
+ goto nomem;

if (map->get == NULL)
map->get = uvc_get_le_value;
@@ -2342,6 +2340,12 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
ctrl->info.selector);

return 0;
+
+nomem:
+ kfree(map->menu_info);
+ kfree(map->name);
+ kfree(map);
+ return -ENOMEM;
}

int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,

--
2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae

2023-01-03 14:56:09

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 4/8] media: uvcvideo: Do not return positive errors in uvc_query_ctrl()

If the returned size of the query does not match the expected size or it
is zero, return -EPIPE instead of 0 or a positive value.

This will avoid confusing the caller (and ultimately userspace) that
doesn't expect a positive or zero value.

Reviewed-by: Laurent Pinchart <[email protected]>
Suggested-by: Laurent Pinchart <[email protected]>
Signed-off-by: Ricardo Ribalda <[email protected]>
---
drivers/media/usb/uvc/uvc_video.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index e56ccde9bd10..feba058fcb06 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -83,7 +83,7 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
dev_err(&dev->udev->dev,
"Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n",
uvc_query_name(query), cs, unit, ret, size);
- return ret;
+ return ret < 0 ? ret : -EPIPE;
}

/* Reuse data[0] to request the error code. */

--
2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae

2023-01-03 14:56:18

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 6/8] media: uvcvideo: Implement mask for V4L2_CTRL_TYPE_MENU

Replace the count with a mask field that lets us choose not only the max
value, but also the minimum value and what values are valid in between.

Reviewed-by: Laurent Pinchart <[email protected]>
Signed-off-by: Ricardo Ribalda <[email protected]>
Suggested-by: Laurent Pinchart <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 30 ++++++++++++++++++++----------
drivers/media/usb/uvc/uvc_driver.c | 3 ++-
drivers/media/usb/uvc/uvc_v4l2.c | 3 ++-
drivers/media/usb/uvc/uvcvideo.h | 2 +-
4 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 7622c5b54b35..aa7a668f60a7 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -6,6 +6,7 @@
* Laurent Pinchart ([email protected])
*/

+#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -525,7 +526,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_BITMASK,
.menu_info = exposure_auto_controls,
- .menu_count = ARRAY_SIZE(exposure_auto_controls),
+ .menu_mask = BIT_MASK(ARRAY_SIZE(exposure_auto_controls)),
.slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, },
},
{
@@ -731,7 +732,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = {
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
.menu_info = power_line_frequency_controls,
- .menu_count = ARRAY_SIZE(power_line_frequency_controls) - 1,
+ .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls) - 1),
},
};

@@ -745,7 +746,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = {
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
.menu_info = power_line_frequency_controls,
- .menu_count = ARRAY_SIZE(power_line_frequency_controls),
+ .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls)),
},
};

@@ -975,7 +976,9 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
const struct uvc_menu_info *menu = mapping->menu_info;
unsigned int i;

- for (i = 0; i < mapping->menu_count; ++i, ++menu) {
+ for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
+ if (!test_bit(i, &mapping->menu_mask))
+ continue;
if (menu->value == value) {
value = i;
break;
@@ -1228,12 +1231,14 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,

switch (mapping->v4l2_type) {
case V4L2_CTRL_TYPE_MENU:
- v4l2_ctrl->minimum = 0;
- v4l2_ctrl->maximum = mapping->menu_count - 1;
+ v4l2_ctrl->minimum = ffs(mapping->menu_mask) - 1;
+ v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1;
v4l2_ctrl->step = 1;

menu = mapping->menu_info;
- for (i = 0; i < mapping->menu_count; ++i, ++menu) {
+ for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
+ if (!test_bit(i, &mapping->menu_mask))
+ continue;
if (menu->value == v4l2_ctrl->default_value) {
v4l2_ctrl->default_value = i;
break;
@@ -1354,7 +1359,7 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
goto done;
}

- if (query_menu->index >= mapping->menu_count) {
+ if (!test_bit(query_menu->index, &mapping->menu_mask)) {
ret = -EINVAL;
goto done;
}
@@ -1868,8 +1873,13 @@ int uvc_ctrl_set(struct uvc_fh *handle,
break;

case V4L2_CTRL_TYPE_MENU:
- if (xctrl->value < 0 || xctrl->value >= mapping->menu_count)
+ if (xctrl->value < (ffs(mapping->menu_mask) - 1) ||
+ xctrl->value > (fls(mapping->menu_mask) - 1))
return -ERANGE;
+
+ if (!test_bit(xctrl->value, &mapping->menu_mask))
+ return -EINVAL;
+
value = mapping->menu_info[xctrl->value].value;

/*
@@ -2305,7 +2315,7 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,

INIT_LIST_HEAD(&map->ev_subs);

- size = sizeof(*mapping->menu_info) * mapping->menu_count;
+ size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
if (map->menu_info == NULL) {
kfree(map->name);
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index e4bcb5011360..f9e6208c4636 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -7,6 +7,7 @@
*/

#include <linux/atomic.h>
+#include <linux/bits.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/list.h>
@@ -2384,7 +2385,7 @@ static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
.menu_info = power_line_frequency_controls_limited,
- .menu_count = ARRAY_SIZE(power_line_frequency_controls_limited),
+ .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls_limited)),
};

static const struct uvc_device_info uvc_ctrl_power_line_limited = {
diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
index 3edb54c086b2..ed2525e7e2a5 100644
--- a/drivers/media/usb/uvc/uvc_v4l2.c
+++ b/drivers/media/usb/uvc/uvc_v4l2.c
@@ -6,6 +6,7 @@
* Laurent Pinchart ([email protected])
*/

+#include <linux/bits.h>
#include <linux/compat.h>
#include <linux/kernel.h>
#include <linux/list.h>
@@ -80,7 +81,7 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
goto free_map;
}

- map->menu_count = xmap->menu_count;
+ map->menu_mask = BIT_MASK(xmap->menu_count);
break;

default:
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index a151f583cd15..f75e5864bbf7 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -117,7 +117,7 @@ struct uvc_control_mapping {
u32 data_type;

const struct uvc_menu_info *menu_info;
- u32 menu_count;
+ unsigned long menu_mask;

u32 master_id;
s32 master_manual;

--
2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae

2023-01-03 15:20:22

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 1/8] media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible()

From: Hans Verkuil <[email protected]>

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 <[email protected]>
Reviewed-by: Ricardo Ribalda <[email protected]>
Signed-off-by: Hans Verkuil <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 42 +++++++++++++++++++++++++++++++++++++++-
drivers/media/usb/uvc/uvc_v4l2.c | 3 +--
drivers/media/usb/uvc/uvcvideo.h | 3 ++-
3 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index c95a2229f4fa..6165d6b8e855 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -1085,11 +1085,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;
@@ -1104,6 +1121,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;
}

diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
index f4d4c33b6dfb..3edb54c086b2 100644
--- a/drivers/media/usb/uvc/uvc_v4l2.c
+++ b/drivers/media/usb/uvc/uvc_v4l2.c
@@ -1020,8 +1020,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;
}
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index df93db259312..a151f583cd15 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -761,7 +761,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);

--
2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae

2023-01-03 15:22:50

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 5/8] media: uvcvideo: Fix handling on Bitmask controls

Minimum and step values for V4L2_CTRL_TYPE_BITMASK controls should be 0.
There is no need to query the camera firmware about this and maybe get
invalid results.

Also value should be masked to the max value advertised by the
hardware.

Finally, handle UVC 1.5 mask controls that use MAX instead of RES to
describe the valid bits.

Fixes v4l2-compliane:
Control ioctls (Input 0):
fail: v4l2-test-controls.cpp(97): minimum must be 0 for a bitmask control
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: FAIL

Signed-off-by: Ricardo Ribalda <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 52 ++++++++++++++++++++++++++++++----------
1 file changed, 40 insertions(+), 12 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 6165d6b8e855..7622c5b54b35 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -1161,6 +1161,25 @@ static const char *uvc_map_get_name(const struct uvc_control_mapping *map)
return "Unknown Control";
}

+static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl,
+ struct uvc_control_mapping *mapping)
+{
+ /*
+ * Some controls, like CT_AE_MODE_CONTROL, use GET_RES to represent
+ * the number of bits supported. Those controls do not list GET_MAX
+ * as supported.
+ */
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)
+ return mapping->get(mapping, UVC_GET_RES,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX)
+ return mapping->get(mapping, UVC_GET_MAX,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
+
+ return ~0;
+}
+
static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
struct uvc_control *ctrl,
struct uvc_control_mapping *mapping,
@@ -1235,6 +1254,12 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
v4l2_ctrl->step = 0;
return 0;

+ case V4L2_CTRL_TYPE_BITMASK:
+ v4l2_ctrl->minimum = 0;
+ v4l2_ctrl->maximum = uvc_get_ctrl_bitmap(ctrl, mapping);
+ v4l2_ctrl->step = 0;
+ return 0;
+
default:
break;
}
@@ -1336,19 +1361,14 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,

menu_info = &mapping->menu_info[query_menu->index];

- if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
- (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
- s32 bitmap;
-
+ if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
if (!ctrl->cached) {
ret = uvc_ctrl_populate_cache(chain, ctrl);
if (ret < 0)
goto done;
}

- bitmap = mapping->get(mapping, UVC_GET_RES,
- uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
- if (!(bitmap & menu_info->value)) {
+ if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_info->value)) {
ret = -EINVAL;
goto done;
}
@@ -1831,6 +1851,17 @@ int uvc_ctrl_set(struct uvc_fh *handle,
value = xctrl->value;
break;

+ case V4L2_CTRL_TYPE_BITMASK:
+ if (!ctrl->cached) {
+ ret = uvc_ctrl_populate_cache(chain, ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ xctrl->value &= uvc_get_ctrl_bitmap(ctrl, mapping);
+ value = xctrl->value;
+ break;
+
case V4L2_CTRL_TYPE_BOOLEAN:
xctrl->value = clamp(xctrl->value, 0, 1);
value = xctrl->value;
@@ -1845,17 +1876,14 @@ int uvc_ctrl_set(struct uvc_fh *handle,
* Valid menu indices are reported by the GET_RES request for
* UVC controls that support it.
*/
- if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
- (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
+ if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
if (!ctrl->cached) {
ret = uvc_ctrl_populate_cache(chain, ctrl);
if (ret < 0)
return ret;
}

- step = mapping->get(mapping, UVC_GET_RES,
- uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
- if (!(step & value))
+ if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & value))
return -EINVAL;
}


--
2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae

2023-01-03 15:24:30

by Ricardo Ribalda

[permalink] [raw]
Subject: [PATCH v3 8/8] media: uvcvideo: Use standard names for menus

Instead of duplicating the menu info, use the one from the core.
Also, do not use extra memory for 1:1 mappings.

Signed-off-by: Ricardo Ribalda <[email protected]>
Suggested-by: Laurent Pinchart <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 126 +++++++++++++++++++++++++------------
drivers/media/usb/uvc/uvc_driver.c | 9 +--
drivers/media/usb/uvc/uvc_v4l2.c | 104 ++++++++++++++++++++++--------
drivers/media/usb/uvc/uvcvideo.h | 3 +-
include/uapi/linux/uvcvideo.h | 4 +-
5 files changed, 170 insertions(+), 76 deletions(-)

diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 4830120e6506..86d74c9740b0 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -364,19 +364,31 @@ static const u32 uvc_control_classes[] = {
V4L2_CID_USER_CLASS,
};

-static const struct uvc_menu_info power_line_frequency_controls[] = {
- { 0, "Disabled" },
- { 1, "50 Hz" },
- { 2, "60 Hz" },
- { 3, "Auto" },
-};
+static const int exposure_auto_mapping[] = { 2, 1, 4, 8 };

-static const struct uvc_menu_info exposure_auto_controls[] = {
- { 2, "Auto Mode" },
- { 1, "Manual Mode" },
- { 4, "Shutter Priority Mode" },
- { 8, "Aperture Priority Mode" },
-};
+static int uvc_mapping_get_menu_value(const struct uvc_control_mapping *mapping,
+ u32 idx)
+{
+ if (!test_bit(idx, &mapping->menu_mask))
+ return -EINVAL;
+
+ if (mapping->menu_mapping)
+ return mapping->menu_mapping[idx];
+
+ return idx;
+}
+
+static const char *
+uvc_mapping_get_menu_name(const struct uvc_control_mapping *mapping, u32 idx)
+{
+ if (!test_bit(idx, &mapping->menu_mask))
+ return NULL;
+
+ if (mapping->menu_names)
+ return mapping->menu_names[idx];
+
+ return v4l2_ctrl_get_menu(mapping->id)[idx];
+}

static s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping,
u8 query, const u8 *data)
@@ -525,8 +537,8 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
.offset = 0,
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_BITMASK,
- .menu_info = exposure_auto_controls,
- .menu_mask = BIT_MASK(ARRAY_SIZE(exposure_auto_controls)),
+ .menu_mapping = exposure_auto_mapping,
+ .menu_mask = GENMASK(ARRAY_SIZE(exposure_auto_mapping) - 1, 0),
.slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, },
},
{
@@ -731,8 +743,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = {
.offset = 0,
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
- .menu_info = power_line_frequency_controls,
- .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls) - 1),
+ .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0),
},
};

@@ -745,8 +756,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = {
.offset = 0,
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
- .menu_info = power_line_frequency_controls,
- .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls)),
+ .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0),
},
};

@@ -973,13 +983,17 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
s32 value = mapping->get(mapping, UVC_GET_CUR, data);

if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
- const struct uvc_menu_info *menu = mapping->menu_info;
unsigned int i;

- for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
+ for (i = 0; BIT(i) <= mapping->menu_mask; ++i) {
+ u32 menu_value;
+
if (!test_bit(i, &mapping->menu_mask))
continue;
- if (menu->value == value) {
+
+ menu_value = uvc_mapping_get_menu_value(mapping, i);
+
+ if (menu_value == value) {
value = i;
break;
}
@@ -1190,7 +1204,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
{
struct uvc_control_mapping *master_map = NULL;
struct uvc_control *master_ctrl = NULL;
- const struct uvc_menu_info *menu;
unsigned int i;

memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
@@ -1235,11 +1248,15 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1;
v4l2_ctrl->step = 1;

- menu = mapping->menu_info;
- for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
+ for (i = 0; BIT(i) <= mapping->menu_mask; ++i) {
+ u32 menu_value;
+
if (!test_bit(i, &mapping->menu_mask))
continue;
- if (menu->value == v4l2_ctrl->default_value) {
+
+ menu_value = uvc_mapping_get_menu_value(mapping, i);
+
+ if (menu_value == v4l2_ctrl->default_value) {
v4l2_ctrl->default_value = i;
break;
}
@@ -1338,11 +1355,11 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
struct v4l2_querymenu *query_menu)
{
- const struct uvc_menu_info *menu_info;
struct uvc_control_mapping *mapping;
struct uvc_control *ctrl;
u32 index = query_menu->index;
u32 id = query_menu->id;
+ const char *name;
int ret;

memset(query_menu, 0, sizeof(*query_menu));
@@ -1364,22 +1381,34 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
goto done;
}

- menu_info = &mapping->menu_info[query_menu->index];
-
if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
+ u32 menu_value;
+
if (!ctrl->cached) {
ret = uvc_ctrl_populate_cache(chain, ctrl);
if (ret < 0)
goto done;
}

- if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_info->value)) {
+ menu_value = uvc_mapping_get_menu_value(mapping,
+ query_menu->index);
+ if (menu_value < 0) {
+ ret = menu_value;
+ goto done;
+ }
+ if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_value)) {
ret = -EINVAL;
goto done;
}
}

- strscpy(query_menu->name, menu_info->name, sizeof(query_menu->name));
+ name = uvc_mapping_get_menu_name(mapping, query_menu->index);
+ if (!name) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ strscpy(query_menu->name, name, sizeof(query_menu->name));

done:
mutex_unlock(&chain->ctrl_mutex);
@@ -1880,7 +1909,7 @@ int uvc_ctrl_set(struct uvc_fh *handle,
if (!test_bit(xctrl->value, &mapping->menu_mask))
return -EINVAL;

- value = mapping->menu_info[xctrl->value].value;
+ value = uvc_mapping_get_menu_value(mapping, xctrl->value);

/*
* Valid menu indices are reported by the GET_RES request for
@@ -2305,21 +2334,36 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
return -ENOMEM;

map->name = NULL;
- map->menu_info = NULL;
+ map->menu_names = NULL;
+ map->menu_mapping = NULL;

/* For UVCIOC_CTRL_MAP custom control */
if (mapping->name) {
map->name = kstrdup(mapping->name, GFP_KERNEL);
- if (!map->name)
- goto nomem;
+ if (!map->name) {
+ kfree(map);
+ return -ENOMEM;
+ }
}

INIT_LIST_HEAD(&map->ev_subs);

- size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
- map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
- if (!map->menu_info)
- goto nomem;
+ if (mapping->menu_mapping && mapping->menu_mask) {
+ size = sizeof(mapping->menu_mapping[0])
+ * fls(mapping->menu_mask);
+ map->menu_mapping = kmemdup(mapping->menu_mapping, size,
+ GFP_KERNEL);
+ if (!map->menu_mapping)
+ goto nomem;
+ }
+ if (mapping->menu_names && mapping->menu_mask) {
+ size = sizeof(mapping->menu_names[0])
+ * fls(mapping->menu_mask);
+ map->menu_names = kmemdup(mapping->menu_names, size,
+ GFP_KERNEL);
+ if (!map->menu_names)
+ goto nomem;
+ }

if (map->get == NULL)
map->get = uvc_get_le_value;
@@ -2342,7 +2386,8 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
return 0;

nomem:
- kfree(map->menu_info);
+ kfree(map->menu_names);
+ kfree(map->menu_mapping);
kfree(map->name);
kfree(map);
return -ENOMEM;
@@ -2673,7 +2718,8 @@ static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev,

list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) {
list_del(&mapping->list);
- kfree(mapping->menu_info);
+ kfree(mapping->menu_names);
+ kfree(mapping->menu_mapping);
kfree(mapping->name);
kfree(mapping);
}
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index f9e6208c4636..ee016b370058 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -2371,11 +2371,6 @@ MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
* Driver initialization and cleanup
*/

-static const struct uvc_menu_info power_line_frequency_controls_limited[] = {
- { 1, "50 Hz" },
- { 2, "60 Hz" },
-};
-
static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
.id = V4L2_CID_POWER_LINE_FREQUENCY,
.entity = UVC_GUID_UVC_PROCESSING,
@@ -2384,8 +2379,8 @@ static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
.offset = 0,
.v4l2_type = V4L2_CTRL_TYPE_MENU,
.data_type = UVC_CTRL_DATA_TYPE_ENUM,
- .menu_info = power_line_frequency_controls_limited,
- .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls_limited)),
+ .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ V4L2_CID_POWER_LINE_FREQUENCY_50HZ),
};

static const struct uvc_device_info uvc_ctrl_power_line_limited = {
diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
index ed2525e7e2a5..dfa26ed0c7f1 100644
--- a/drivers/media/usb/uvc/uvc_v4l2.c
+++ b/drivers/media/usb/uvc/uvc_v4l2.c
@@ -26,14 +26,83 @@

#include "uvcvideo.h"

+static int uvc_control_add_xu_mapping(struct uvc_video_chain *chain,
+ struct uvc_control_mapping *map,
+ const struct uvc_xu_control_mapping *xmap)
+{
+ unsigned int i;
+ size_t size;
+ int ret;
+
+ /*
+ * Prevent excessive memory consumption, as well as integer
+ * overflows.
+ */
+ if (xmap->menu_count == 0 ||
+ xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES)
+ return -EINVAL;
+
+ map->menu_names = NULL;
+ map->menu_mapping = NULL;
+
+ map->menu_mask = BIT_MASK(xmap->menu_count);
+
+ size = xmap->menu_count * sizeof(*map->menu_mapping);
+ map->menu_mapping = kzalloc(size, GFP_KERNEL);
+ if (!map->menu_mapping) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ for (i = 0; i < xmap->menu_count ; i++) {
+ if (copy_from_user((u32 *)&map->menu_mapping[i],
+ &xmap->menu_info[i].value,
+ sizeof(map->menu_mapping[i]))) {
+ ret = -EACCES;
+ goto exit;
+ }
+ }
+
+ /* Always use the standard naming if available. */
+ if (v4l2_ctrl_get_menu(map->id))
+ goto done_mapping;
+
+ size = xmap->menu_count * sizeof(map->menu_names[0]);
+ map->menu_names = kzalloc(size, GFP_KERNEL);
+ if (!map->menu_names) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ for (i = 0; i < xmap->menu_count ; i++) {
+ /* sizeof(names[i]) - 1: to take care of \0 */
+ if (copy_from_user((char *)map->menu_names[i],
+ xmap->menu_info[i].name,
+ sizeof(map->menu_names[i]) - 1)) {
+ ret = -EACCES;
+ goto exit;
+ }
+ }
+
+done_mapping:
+ ret = uvc_ctrl_add_mapping(chain, map);
+
+exit:
+ kfree(map->menu_names);
+ map->menu_names = NULL;
+ kfree(map->menu_mapping);
+ map->menu_mapping = NULL;
+
+ return ret;
+}
+
/* ------------------------------------------------------------------------
* UVC ioctls
*/
-static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
- struct uvc_xu_control_mapping *xmap)
+static int uvc_ioctl_xu_ctrl_map(struct uvc_video_chain *chain,
+ struct uvc_xu_control_mapping *xmap)
{
struct uvc_control_mapping *map;
- unsigned int size;
int ret;

map = kzalloc(sizeof(*map), GFP_KERNEL);
@@ -61,39 +130,20 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_BUTTON:
+ ret = uvc_ctrl_add_mapping(chain, map);
break;

case V4L2_CTRL_TYPE_MENU:
- /*
- * Prevent excessive memory consumption, as well as integer
- * overflows.
- */
- if (xmap->menu_count == 0 ||
- xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) {
- ret = -EINVAL;
- goto free_map;
- }
-
- size = xmap->menu_count * sizeof(*map->menu_info);
- map->menu_info = memdup_user(xmap->menu_info, size);
- if (IS_ERR(map->menu_info)) {
- ret = PTR_ERR(map->menu_info);
- goto free_map;
- }
-
- map->menu_mask = BIT_MASK(xmap->menu_count);
+ ret = uvc_control_add_xu_mapping(chain, map, xmap);
break;

default:
uvc_dbg(chain->dev, CONTROL,
"Unsupported V4L2 control type %u\n", xmap->v4l2_type);
ret = -ENOTTY;
- goto free_map;
+ break;
}

- ret = uvc_ctrl_add_mapping(chain, map);
-
- kfree(map->menu_info);
free_map:
kfree(map);

@@ -1316,7 +1366,7 @@ static long uvc_ioctl_default(struct file *file, void *fh, bool valid_prio,
switch (cmd) {
/* Dynamic controls. */
case UVCIOC_CTRL_MAP:
- return uvc_ioctl_ctrl_map(chain, arg);
+ return uvc_ioctl_xu_ctrl_map(chain, arg);

case UVCIOC_CTRL_QUERY:
return uvc_xu_ctrl_query(chain, arg);
@@ -1429,7 +1479,7 @@ static long uvc_v4l2_compat_ioctl32(struct file *file,
ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up);
if (ret)
return ret;
- ret = uvc_ioctl_ctrl_map(handle->chain, &karg.xmap);
+ ret = uvc_ioctl_xu_ctrl_map(handle->chain, &karg.xmap);
if (ret)
return ret;
ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up);
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index f75e5864bbf7..0e816be556f2 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -116,7 +116,8 @@ struct uvc_control_mapping {
enum v4l2_ctrl_type v4l2_type;
u32 data_type;

- const struct uvc_menu_info *menu_info;
+ const u32 *menu_mapping;
+ const char (*menu_names)[UVC_MENU_NAME_LEN];
unsigned long menu_mask;

u32 master_id;
diff --git a/include/uapi/linux/uvcvideo.h b/include/uapi/linux/uvcvideo.h
index 8288137387c0..d45d0c2ea252 100644
--- a/include/uapi/linux/uvcvideo.h
+++ b/include/uapi/linux/uvcvideo.h
@@ -36,9 +36,11 @@
UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES | \
UVC_CTRL_FLAG_GET_DEF)

+#define UVC_MENU_NAME_LEN 32
+
struct uvc_menu_info {
__u32 value;
- __u8 name[32];
+ __u8 name[UVC_MENU_NAME_LEN];
};

struct uvc_xu_control_mapping {

--
2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae

2023-01-03 20:40:12

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v3 5/8] media: uvcvideo: Fix handling on Bitmask controls

Hi Ricardo,

Thank you for the patch.

On Tue, Jan 03, 2023 at 03:36:23PM +0100, Ricardo Ribalda wrote:
> Minimum and step values for V4L2_CTRL_TYPE_BITMASK controls should be 0.
> There is no need to query the camera firmware about this and maybe get
> invalid results.
>
> Also value should be masked to the max value advertised by the
> hardware.
>
> Finally, handle UVC 1.5 mask controls that use MAX instead of RES to
> describe the valid bits.
>
> Fixes v4l2-compliane:
> Control ioctls (Input 0):
> fail: v4l2-test-controls.cpp(97): minimum must be 0 for a bitmask control
> test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: FAIL
>
> Signed-off-by: Ricardo Ribalda <[email protected]>

Reviewed-by: Laurent Pinchart <[email protected]>

> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 52 ++++++++++++++++++++++++++++++----------
> 1 file changed, 40 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index 6165d6b8e855..7622c5b54b35 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -1161,6 +1161,25 @@ static const char *uvc_map_get_name(const struct uvc_control_mapping *map)
> return "Unknown Control";
> }
>
> +static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl,
> + struct uvc_control_mapping *mapping)
> +{
> + /*
> + * Some controls, like CT_AE_MODE_CONTROL, use GET_RES to represent
> + * the number of bits supported. Those controls do not list GET_MAX
> + * as supported.
> + */
> + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)
> + return mapping->get(mapping, UVC_GET_RES,
> + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
> +
> + if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX)
> + return mapping->get(mapping, UVC_GET_MAX,
> + uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
> +
> + return ~0;
> +}
> +
> static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
> struct uvc_control *ctrl,
> struct uvc_control_mapping *mapping,
> @@ -1235,6 +1254,12 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
> v4l2_ctrl->step = 0;
> return 0;
>
> + case V4L2_CTRL_TYPE_BITMASK:
> + v4l2_ctrl->minimum = 0;
> + v4l2_ctrl->maximum = uvc_get_ctrl_bitmap(ctrl, mapping);
> + v4l2_ctrl->step = 0;
> + return 0;
> +
> default:
> break;
> }
> @@ -1336,19 +1361,14 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
>
> menu_info = &mapping->menu_info[query_menu->index];
>
> - if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
> - (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
> - s32 bitmap;
> -
> + if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
> if (!ctrl->cached) {
> ret = uvc_ctrl_populate_cache(chain, ctrl);
> if (ret < 0)
> goto done;
> }
>
> - bitmap = mapping->get(mapping, UVC_GET_RES,
> - uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
> - if (!(bitmap & menu_info->value)) {
> + if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_info->value)) {
> ret = -EINVAL;
> goto done;
> }
> @@ -1831,6 +1851,17 @@ int uvc_ctrl_set(struct uvc_fh *handle,
> value = xctrl->value;
> break;
>
> + case V4L2_CTRL_TYPE_BITMASK:
> + if (!ctrl->cached) {
> + ret = uvc_ctrl_populate_cache(chain, ctrl);
> + if (ret < 0)
> + return ret;
> + }
> +
> + xctrl->value &= uvc_get_ctrl_bitmap(ctrl, mapping);
> + value = xctrl->value;
> + break;
> +
> case V4L2_CTRL_TYPE_BOOLEAN:
> xctrl->value = clamp(xctrl->value, 0, 1);
> value = xctrl->value;
> @@ -1845,17 +1876,14 @@ int uvc_ctrl_set(struct uvc_fh *handle,
> * Valid menu indices are reported by the GET_RES request for
> * UVC controls that support it.
> */
> - if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
> - (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
> + if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
> if (!ctrl->cached) {
> ret = uvc_ctrl_populate_cache(chain, ctrl);
> if (ret < 0)
> return ret;
> }
>
> - step = mapping->get(mapping, UVC_GET_RES,
> - uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
> - if (!(step & value))
> + if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & value))
> return -EINVAL;
> }
>
>

--
Regards,

Laurent Pinchart

2023-01-03 20:40:29

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v3 7/8] media: uvcvideo: Refactor __uvc_ctrl_add_mapping

Hi Ricardo,

Thank you for the patch.

On Tue, Jan 03, 2023 at 03:36:25PM +0100, Ricardo Ribalda wrote:
> Simplify the exit code with a common error tag freeing all the memory.
>
> Suggested-by: Laurent Pinchart <[email protected]>
> Signed-off-by: Ricardo Ribalda <[email protected]>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 26 +++++++++++++++-----------
> 1 file changed, 15 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index aa7a668f60a7..4830120e6506 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -2296,32 +2296,30 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
> unsigned int i;
>
> /*
> - * Most mappings come from static kernel data and need to be duplicated.
> + * Most mappings come from static kernel data, and need to be duplicated.
> * Mappings that come from userspace will be unnecessarily duplicated,
> * this could be optimized.
> */
> map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL);
> - if (map == NULL)
> + if (!map)
> return -ENOMEM;
>
> + map->name = NULL;
> + map->menu_info = NULL;
> +
> /* For UVCIOC_CTRL_MAP custom control */
> if (mapping->name) {
> map->name = kstrdup(mapping->name, GFP_KERNEL);
> - if (!map->name) {
> - kfree(map);
> - return -ENOMEM;
> - }
> + if (!map->name)
> + goto nomem;
> }
>
> INIT_LIST_HEAD(&map->ev_subs);
>
> size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
> map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
> - if (map->menu_info == NULL) {
> - kfree(map->name);
> - kfree(map);
> - return -ENOMEM;
> - }
> + if (!map->menu_info)
> + goto nomem;
>
> if (map->get == NULL)
> map->get = uvc_get_le_value;
> @@ -2342,6 +2340,12 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
> ctrl->info.selector);
>
> return 0;
> +
> +nomem:

As it's an error label, I'd name it err_nomem. I'll fix that when
applying.

Reviewed-by: Laurent Pinchart <[email protected]>

> + kfree(map->menu_info);
> + kfree(map->name);
> + kfree(map);
> + return -ENOMEM;
> }
>
> int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
>

--
Regards,

Laurent Pinchart

2023-01-03 21:14:00

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v3 8/8] media: uvcvideo: Use standard names for menus

Hi Ricardo,

Thank you for the patch.

On Tue, Jan 03, 2023 at 03:36:26PM +0100, Ricardo Ribalda wrote:
> Instead of duplicating the menu info, use the one from the core.
> Also, do not use extra memory for 1:1 mappings.
>
> Signed-off-by: Ricardo Ribalda <[email protected]>
> Suggested-by: Laurent Pinchart <[email protected]>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 126 +++++++++++++++++++++++++------------
> drivers/media/usb/uvc/uvc_driver.c | 9 +--
> drivers/media/usb/uvc/uvc_v4l2.c | 104 ++++++++++++++++++++++--------
> drivers/media/usb/uvc/uvcvideo.h | 3 +-
> include/uapi/linux/uvcvideo.h | 4 +-
> 5 files changed, 170 insertions(+), 76 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index 4830120e6506..86d74c9740b0 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -364,19 +364,31 @@ static const u32 uvc_control_classes[] = {
> V4L2_CID_USER_CLASS,
> };
>
> -static const struct uvc_menu_info power_line_frequency_controls[] = {
> - { 0, "Disabled" },
> - { 1, "50 Hz" },
> - { 2, "60 Hz" },
> - { 3, "Auto" },
> -};
> +static const int exposure_auto_mapping[] = { 2, 1, 4, 8 };
>
> -static const struct uvc_menu_info exposure_auto_controls[] = {
> - { 2, "Auto Mode" },
> - { 1, "Manual Mode" },
> - { 4, "Shutter Priority Mode" },
> - { 8, "Aperture Priority Mode" },
> -};
> +static int uvc_mapping_get_menu_value(const struct uvc_control_mapping *mapping,
> + u32 idx)
> +{
> + if (!test_bit(idx, &mapping->menu_mask))
> + return -EINVAL;
> +
> + if (mapping->menu_mapping)
> + return mapping->menu_mapping[idx];
> +
> + return idx;
> +}
> +
> +static const char *
> +uvc_mapping_get_menu_name(const struct uvc_control_mapping *mapping, u32 idx)
> +{
> + if (!test_bit(idx, &mapping->menu_mask))
> + return NULL;
> +
> + if (mapping->menu_names)
> + return mapping->menu_names[idx];
> +
> + return v4l2_ctrl_get_menu(mapping->id)[idx];
> +}
>
> static s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping,
> u8 query, const u8 *data)
> @@ -525,8 +537,8 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
> .offset = 0,
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_BITMASK,
> - .menu_info = exposure_auto_controls,
> - .menu_mask = BIT_MASK(ARRAY_SIZE(exposure_auto_controls)),
> + .menu_mapping = exposure_auto_mapping,
> + .menu_mask = GENMASK(ARRAY_SIZE(exposure_auto_mapping) - 1, 0),
> .slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, },
> },
> {
> @@ -731,8 +743,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = {
> .offset = 0,
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_ENUM,
> - .menu_info = power_line_frequency_controls,
> - .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls) - 1),
> + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0),
> },
> };
>
> @@ -745,8 +756,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = {
> .offset = 0,
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_ENUM,
> - .menu_info = power_line_frequency_controls,
> - .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls)),
> + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0),
> },
> };
>
> @@ -973,13 +983,17 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
> s32 value = mapping->get(mapping, UVC_GET_CUR, data);
>
> if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
> - const struct uvc_menu_info *menu = mapping->menu_info;
> unsigned int i;
>
> - for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
> + for (i = 0; BIT(i) <= mapping->menu_mask; ++i) {
> + u32 menu_value;
> +
> if (!test_bit(i, &mapping->menu_mask))
> continue;
> - if (menu->value == value) {
> +
> + menu_value = uvc_mapping_get_menu_value(mapping, i);
> +
> + if (menu_value == value) {
> value = i;
> break;
> }
> @@ -1190,7 +1204,6 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
> {
> struct uvc_control_mapping *master_map = NULL;
> struct uvc_control *master_ctrl = NULL;
> - const struct uvc_menu_info *menu;
> unsigned int i;
>
> memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
> @@ -1235,11 +1248,15 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
> v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1;
> v4l2_ctrl->step = 1;
>
> - menu = mapping->menu_info;
> - for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
> + for (i = 0; BIT(i) <= mapping->menu_mask; ++i) {
> + u32 menu_value;
> +
> if (!test_bit(i, &mapping->menu_mask))
> continue;
> - if (menu->value == v4l2_ctrl->default_value) {
> +
> + menu_value = uvc_mapping_get_menu_value(mapping, i);
> +
> + if (menu_value == v4l2_ctrl->default_value) {
> v4l2_ctrl->default_value = i;
> break;
> }
> @@ -1338,11 +1355,11 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
> int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
> struct v4l2_querymenu *query_menu)
> {
> - const struct uvc_menu_info *menu_info;
> struct uvc_control_mapping *mapping;
> struct uvc_control *ctrl;
> u32 index = query_menu->index;
> u32 id = query_menu->id;
> + const char *name;
> int ret;
>
> memset(query_menu, 0, sizeof(*query_menu));
> @@ -1364,22 +1381,34 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
> goto done;
> }
>
> - menu_info = &mapping->menu_info[query_menu->index];
> -
> if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
> + u32 menu_value;
> +
> if (!ctrl->cached) {
> ret = uvc_ctrl_populate_cache(chain, ctrl);
> if (ret < 0)
> goto done;
> }
>
> - if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_info->value)) {
> + menu_value = uvc_mapping_get_menu_value(mapping,
> + query_menu->index);
> + if (menu_value < 0) {
> + ret = menu_value;
> + goto done;
> + }

Please add a blank line.

> + if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_value)) {

If mapping->menu_mapping is not specified, uvc_mapping_get_menu_value()
return an index, and here you interpret it as a bitmask. I assume this
works because the UVC_CTRL_DATA_TYPE_BITMASK control supported by the
driver (there's only one) has a menu_mapping that translates to a
bitmask.

> ret = -EINVAL;
> goto done;
> }
> }
>
> - strscpy(query_menu->name, menu_info->name, sizeof(query_menu->name));
> + name = uvc_mapping_get_menu_name(mapping, query_menu->index);
> + if (!name) {
> + ret = -EINVAL;
> + goto done;
> + }
> +
> + strscpy(query_menu->name, name, sizeof(query_menu->name));
>
> done:
> mutex_unlock(&chain->ctrl_mutex);
> @@ -1880,7 +1909,7 @@ int uvc_ctrl_set(struct uvc_fh *handle,
> if (!test_bit(xctrl->value, &mapping->menu_mask))
> return -EINVAL;
>
> - value = mapping->menu_info[xctrl->value].value;
> + value = uvc_mapping_get_menu_value(mapping, xctrl->value);
>
> /*
> * Valid menu indices are reported by the GET_RES request for
> @@ -2305,21 +2334,36 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
> return -ENOMEM;
>
> map->name = NULL;
> - map->menu_info = NULL;
> + map->menu_names = NULL;
> + map->menu_mapping = NULL;
>
> /* For UVCIOC_CTRL_MAP custom control */
> if (mapping->name) {
> map->name = kstrdup(mapping->name, GFP_KERNEL);
> - if (!map->name)
> - goto nomem;
> + if (!map->name) {
> + kfree(map);
> + return -ENOMEM;

Anything wrong with the goto nomem here ?

> + }
> }
>
> INIT_LIST_HEAD(&map->ev_subs);
>
> - size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
> - map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
> - if (!map->menu_info)
> - goto nomem;
> + if (mapping->menu_mapping && mapping->menu_mask) {
> + size = sizeof(mapping->menu_mapping[0])
> + * fls(mapping->menu_mask);
> + map->menu_mapping = kmemdup(mapping->menu_mapping, size,
> + GFP_KERNEL);
> + if (!map->menu_mapping)
> + goto nomem;
> + }
> + if (mapping->menu_names && mapping->menu_mask) {
> + size = sizeof(mapping->menu_names[0])
> + * fls(mapping->menu_mask);
> + map->menu_names = kmemdup(mapping->menu_names, size,
> + GFP_KERNEL);
> + if (!map->menu_names)
> + goto nomem;
> + }
>
> if (map->get == NULL)
> map->get = uvc_get_le_value;
> @@ -2342,7 +2386,8 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
> return 0;
>
> nomem:
> - kfree(map->menu_info);
> + kfree(map->menu_names);
> + kfree(map->menu_mapping);
> kfree(map->name);
> kfree(map);
> return -ENOMEM;
> @@ -2673,7 +2718,8 @@ static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev,
>
> list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) {
> list_del(&mapping->list);
> - kfree(mapping->menu_info);
> + kfree(mapping->menu_names);
> + kfree(mapping->menu_mapping);
> kfree(mapping->name);
> kfree(mapping);
> }
> diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
> index f9e6208c4636..ee016b370058 100644
> --- a/drivers/media/usb/uvc/uvc_driver.c
> +++ b/drivers/media/usb/uvc/uvc_driver.c
> @@ -2371,11 +2371,6 @@ MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
> * Driver initialization and cleanup
> */
>
> -static const struct uvc_menu_info power_line_frequency_controls_limited[] = {
> - { 1, "50 Hz" },
> - { 2, "60 Hz" },
> -};
> -
> static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
> .id = V4L2_CID_POWER_LINE_FREQUENCY,
> .entity = UVC_GUID_UVC_PROCESSING,
> @@ -2384,8 +2379,8 @@ static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
> .offset = 0,
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_ENUM,
> - .menu_info = power_line_frequency_controls_limited,
> - .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls_limited)),
> + .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
> + V4L2_CID_POWER_LINE_FREQUENCY_50HZ),
> };
>
> static const struct uvc_device_info uvc_ctrl_power_line_limited = {
> diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
> index ed2525e7e2a5..dfa26ed0c7f1 100644
> --- a/drivers/media/usb/uvc/uvc_v4l2.c
> +++ b/drivers/media/usb/uvc/uvc_v4l2.c
> @@ -26,14 +26,83 @@
>
> #include "uvcvideo.h"
>
> +static int uvc_control_add_xu_mapping(struct uvc_video_chain *chain,
> + struct uvc_control_mapping *map,
> + const struct uvc_xu_control_mapping *xmap)
> +{
> + unsigned int i;
> + size_t size;
> + int ret;
> +
> + /*
> + * Prevent excessive memory consumption, as well as integer
> + * overflows.
> + */
> + if (xmap->menu_count == 0 ||
> + xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES)
> + return -EINVAL;
> +
> + map->menu_names = NULL;
> + map->menu_mapping = NULL;
> +
> + map->menu_mask = BIT_MASK(xmap->menu_count);
> +
> + size = xmap->menu_count * sizeof(*map->menu_mapping);
> + map->menu_mapping = kzalloc(size, GFP_KERNEL);
> + if (!map->menu_mapping) {
> + ret = -ENOMEM;
> + goto exit;
> + }
> +
> + for (i = 0; i < xmap->menu_count ; i++) {
> + if (copy_from_user((u32 *)&map->menu_mapping[i],
> + &xmap->menu_info[i].value,
> + sizeof(map->menu_mapping[i]))) {
> + ret = -EACCES;
> + goto exit;
> + }
> + }
> +
> + /* Always use the standard naming if available. */
> + if (v4l2_ctrl_get_menu(map->id))
> + goto done_mapping;
> +
> + size = xmap->menu_count * sizeof(map->menu_names[0]);
> + map->menu_names = kzalloc(size, GFP_KERNEL);
> + if (!map->menu_names) {
> + ret = -ENOMEM;
> + goto exit;
> + }
> +
> + for (i = 0; i < xmap->menu_count ; i++) {
> + /* sizeof(names[i]) - 1: to take care of \0 */
> + if (copy_from_user((char *)map->menu_names[i],
> + xmap->menu_info[i].name,
> + sizeof(map->menu_names[i]) - 1)) {
> + ret = -EACCES;
> + goto exit;
> + }
> + }

While I'm a big proponent of goto's for error handling, I tend to frown
upon them in most other case as they hinder readability and can hide
design problems. Here you can simply write

/*
* Always use the standard naming if available, otherwise copy the names
* supplied by userspace.
*/
if (!v4l2_ctrl_get_menu(map->id)) {
size = xmap->menu_count * sizeof(map->menu_names[0]);
map->menu_names = kzalloc(size, GFP_KERNEL);
if (!map->menu_names) {
ret = -ENOMEM;
goto exit;
}

for (i = 0; i < xmap->menu_count ; i++) {
/* sizeof(names[i]) - 1: to take care of \0 */
if (copy_from_user((char *)map->menu_names[i],
xmap->menu_info[i].name,
sizeof(map->menu_names[i]) - 1)) {
ret = -EACCES;
goto exit;
}
}
}

> +
> +done_mapping:
> + ret = uvc_ctrl_add_mapping(chain, map);
> +
> +exit:

The driver uses several done: labels but no exit:. Let's stick with
'done' for consistency.

> + kfree(map->menu_names);
> + map->menu_names = NULL;
> + kfree(map->menu_mapping);
> + map->menu_mapping = NULL;
> +
> + return ret;
> +}
> +
> /* ------------------------------------------------------------------------
> * UVC ioctls
> */
> -static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
> - struct uvc_xu_control_mapping *xmap)
> +static int uvc_ioctl_xu_ctrl_map(struct uvc_video_chain *chain,
> + struct uvc_xu_control_mapping *xmap)
> {
> struct uvc_control_mapping *map;
> - unsigned int size;
> int ret;
>
> map = kzalloc(sizeof(*map), GFP_KERNEL);
> @@ -61,39 +130,20 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
> case V4L2_CTRL_TYPE_INTEGER:
> case V4L2_CTRL_TYPE_BOOLEAN:
> case V4L2_CTRL_TYPE_BUTTON:
> + ret = uvc_ctrl_add_mapping(chain, map);
> break;
>
> case V4L2_CTRL_TYPE_MENU:
> - /*
> - * Prevent excessive memory consumption, as well as integer
> - * overflows.
> - */
> - if (xmap->menu_count == 0 ||
> - xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) {
> - ret = -EINVAL;
> - goto free_map;
> - }
> -
> - size = xmap->menu_count * sizeof(*map->menu_info);
> - map->menu_info = memdup_user(xmap->menu_info, size);
> - if (IS_ERR(map->menu_info)) {
> - ret = PTR_ERR(map->menu_info);
> - goto free_map;
> - }
> -
> - map->menu_mask = BIT_MASK(xmap->menu_count);
> + ret = uvc_control_add_xu_mapping(chain, map, xmap);
> break;
>
> default:
> uvc_dbg(chain->dev, CONTROL,
> "Unsupported V4L2 control type %u\n", xmap->v4l2_type);
> ret = -ENOTTY;
> - goto free_map;
> + break;
> }
>
> - ret = uvc_ctrl_add_mapping(chain, map);
> -
> - kfree(map->menu_info);
> free_map:
> kfree(map);
>
> @@ -1316,7 +1366,7 @@ static long uvc_ioctl_default(struct file *file, void *fh, bool valid_prio,
> switch (cmd) {
> /* Dynamic controls. */
> case UVCIOC_CTRL_MAP:
> - return uvc_ioctl_ctrl_map(chain, arg);
> + return uvc_ioctl_xu_ctrl_map(chain, arg);
>
> case UVCIOC_CTRL_QUERY:
> return uvc_xu_ctrl_query(chain, arg);
> @@ -1429,7 +1479,7 @@ static long uvc_v4l2_compat_ioctl32(struct file *file,
> ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up);
> if (ret)
> return ret;
> - ret = uvc_ioctl_ctrl_map(handle->chain, &karg.xmap);
> + ret = uvc_ioctl_xu_ctrl_map(handle->chain, &karg.xmap);
> if (ret)
> return ret;
> ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up);
> diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
> index f75e5864bbf7..0e816be556f2 100644
> --- a/drivers/media/usb/uvc/uvcvideo.h
> +++ b/drivers/media/usb/uvc/uvcvideo.h
> @@ -116,7 +116,8 @@ struct uvc_control_mapping {
> enum v4l2_ctrl_type v4l2_type;
> u32 data_type;
>
> - const struct uvc_menu_info *menu_info;
> + const u32 *menu_mapping;
> + const char (*menu_names)[UVC_MENU_NAME_LEN];
> unsigned long menu_mask;
>
> u32 master_id;
> diff --git a/include/uapi/linux/uvcvideo.h b/include/uapi/linux/uvcvideo.h
> index 8288137387c0..d45d0c2ea252 100644
> --- a/include/uapi/linux/uvcvideo.h
> +++ b/include/uapi/linux/uvcvideo.h
> @@ -36,9 +36,11 @@
> UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES | \
> UVC_CTRL_FLAG_GET_DEF)
>
> +#define UVC_MENU_NAME_LEN 32
> +
> struct uvc_menu_info {
> __u32 value;
> - __u8 name[32];
> + __u8 name[UVC_MENU_NAME_LEN];
> };
>
> struct uvc_xu_control_mapping {
>

--
Regards,

Laurent Pinchart

2023-01-03 21:18:49

by Ricardo Ribalda

[permalink] [raw]
Subject: Re: [PATCH v3 0/8] Follow-up patches for uvc v4l2-compliance

Hi Laurent

On Tue, 3 Jan 2023 at 21:53, Laurent Pinchart
<[email protected]> wrote:
>
> Hi Ricardo,
>
> On Tue, Jan 03, 2023 at 03:36:18PM +0100, Ricardo Ribalda wrote:
> > This patchset contains the fixes for the comments on "v10 of Fix
> > v4l2-compliance errors series". In particular to the patches
> >
> > -uvcvideo: uvc_ctrl_is_accessible: check for INACTIVE
> > -uvcvideo: improve error handling in uvc_query_ctrl()
> >
> > And the patch:
> > -uvcvideo: Fix handling on Bitmask controls
>
> I've applied 1/8 to 7/8 to my tree and pushed the result to
> https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=next/uvc.
> You can submit a new version of 8/8 only based on that branch.

Thanks :)

>
> > To: Laurent Pinchart <[email protected]>
> > To: Mauro Carvalho Chehab <[email protected]>
> > Cc: [email protected]
> > Cc: [email protected]
> > Cc: Hans Verkuil <[email protected]>
> > Signed-off-by: Ricardo Ribalda <[email protected]>
> >
> > ---
> > Changes in v3 (Thanks Laurent):
> > - Add a new patch for refactoring __uvc_ctrl_add_mapping
> > - Use standard names for menus
> > - Return error on uvc_mapping_get_menu_value
> > - Add const
> > - StyLe!
> > - Do not return positive errors in uvc_query_ctrl()
> > - Improve commit message
> > - improve error logging in uvc_query_ctrl()
> > - Fix comment
> > - Improve doc
> > - Fix handling on Bitmask controls
> > - s/uvc/UVC
> > - Reflow comments to 80 chars
> > - Test with GET_RES first
> > - Remove clamp to (0,..)
> > - Return -EACCES for Wrong state error
> > - Full rewrite of commit message
> > - uvc_ctrl_is_accessible: check for INACTIVE
> > - Update commit message
> > - Remove try variable
> > - Update documentation
> > - Implement mask for V4L2_CTRL_TYPE_MENU
> > - Include linux/bits.h
> > - Link to v2: https://lore.kernel.org/r/[email protected]
> >
> > Changes in v2:
> > - Include "Get menu names from framework series"
> > https://lore.kernel.org/r/[email protected]
> > - Link to v1: https://lore.kernel.org/r/[email protected]
> >
> > ---
> > Hans Verkuil (2):
> > media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible()
> > media: uvcvideo: improve error logging in uvc_query_ctrl()
> >
> > Ricardo Ribalda (6):
> > media: uvcvideo: Return -EACCES for Wrong state error
> > media: uvcvideo: Do not return positive errors in uvc_query_ctrl()
> > media: uvcvideo: Fix handling on Bitmask controls
> > media: uvcvideo: Implement mask for V4L2_CTRL_TYPE_MENU
> > media: uvcvideo: Refactor __uvc_ctrl_add_mapping
> > media: uvcvideo: Use standard names for menus
> >
> > drivers/media/usb/uvc/uvc_ctrl.c | 238 ++++++++++++++++++++++++++++---------
> > drivers/media/usb/uvc/uvc_driver.c | 10 +-
> > drivers/media/usb/uvc/uvc_v4l2.c | 108 ++++++++++++-----
> > drivers/media/usb/uvc/uvc_video.c | 15 +--
> > drivers/media/usb/uvc/uvcvideo.h | 8 +-
> > include/uapi/linux/uvcvideo.h | 4 +-
> > 6 files changed, 281 insertions(+), 102 deletions(-)
> > ---
> > base-commit: 69b41ac87e4a664de78a395ff97166f0b2943210
> > change-id: 20220920-resend-v4l2-compliance-4fdbe4fbd7b5
>
> --
> Regards,
>
> Laurent Pinchart



--
Ricardo Ribalda

2023-01-03 21:19:28

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v3 0/8] Follow-up patches for uvc v4l2-compliance

Hi Ricardo,

On Tue, Jan 03, 2023 at 03:36:18PM +0100, Ricardo Ribalda wrote:
> This patchset contains the fixes for the comments on "v10 of Fix
> v4l2-compliance errors series". In particular to the patches
>
> -uvcvideo: uvc_ctrl_is_accessible: check for INACTIVE
> -uvcvideo: improve error handling in uvc_query_ctrl()
>
> And the patch:
> -uvcvideo: Fix handling on Bitmask controls

I've applied 1/8 to 7/8 to my tree and pushed the result to
https://git.kernel.org/pub/scm/linux/kernel/git/pinchartl/linux.git/log/?h=next/uvc.
You can submit a new version of 8/8 only based on that branch.

> To: Laurent Pinchart <[email protected]>
> To: Mauro Carvalho Chehab <[email protected]>
> Cc: [email protected]
> Cc: [email protected]
> Cc: Hans Verkuil <[email protected]>
> Signed-off-by: Ricardo Ribalda <[email protected]>
>
> ---
> Changes in v3 (Thanks Laurent):
> - Add a new patch for refactoring __uvc_ctrl_add_mapping
> - Use standard names for menus
> - Return error on uvc_mapping_get_menu_value
> - Add const
> - StyLe!
> - Do not return positive errors in uvc_query_ctrl()
> - Improve commit message
> - improve error logging in uvc_query_ctrl()
> - Fix comment
> - Improve doc
> - Fix handling on Bitmask controls
> - s/uvc/UVC
> - Reflow comments to 80 chars
> - Test with GET_RES first
> - Remove clamp to (0,..)
> - Return -EACCES for Wrong state error
> - Full rewrite of commit message
> - uvc_ctrl_is_accessible: check for INACTIVE
> - Update commit message
> - Remove try variable
> - Update documentation
> - Implement mask for V4L2_CTRL_TYPE_MENU
> - Include linux/bits.h
> - Link to v2: https://lore.kernel.org/r/[email protected]
>
> Changes in v2:
> - Include "Get menu names from framework series"
> https://lore.kernel.org/r/[email protected]
> - Link to v1: https://lore.kernel.org/r/[email protected]
>
> ---
> Hans Verkuil (2):
> media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible()
> media: uvcvideo: improve error logging in uvc_query_ctrl()
>
> Ricardo Ribalda (6):
> media: uvcvideo: Return -EACCES for Wrong state error
> media: uvcvideo: Do not return positive errors in uvc_query_ctrl()
> media: uvcvideo: Fix handling on Bitmask controls
> media: uvcvideo: Implement mask for V4L2_CTRL_TYPE_MENU
> media: uvcvideo: Refactor __uvc_ctrl_add_mapping
> media: uvcvideo: Use standard names for menus
>
> drivers/media/usb/uvc/uvc_ctrl.c | 238 ++++++++++++++++++++++++++++---------
> drivers/media/usb/uvc/uvc_driver.c | 10 +-
> drivers/media/usb/uvc/uvc_v4l2.c | 108 ++++++++++++-----
> drivers/media/usb/uvc/uvc_video.c | 15 +--
> drivers/media/usb/uvc/uvcvideo.h | 8 +-
> include/uapi/linux/uvcvideo.h | 4 +-
> 6 files changed, 281 insertions(+), 102 deletions(-)
> ---
> base-commit: 69b41ac87e4a664de78a395ff97166f0b2943210
> change-id: 20220920-resend-v4l2-compliance-4fdbe4fbd7b5

--
Regards,

Laurent Pinchart

2023-01-04 18:36:09

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v3 8/8] media: uvcvideo: Use standard names for menus

Hi Ricardo,

I love your patch! Perhaps something to improve:

[auto build test WARNING on 69b41ac87e4a664de78a395ff97166f0b2943210]

url: https://github.com/intel-lab-lkp/linux/commits/Ricardo-Ribalda/media-uvcvideo-Check-for-INACTIVE-in-uvc_ctrl_is_accessible/20230103-223901
base: 69b41ac87e4a664de78a395ff97166f0b2943210
patch link: https://lore.kernel.org/r/20220920-resend-v4l2-compliance-v3-8-598d33a15815%40chromium.org
patch subject: [PATCH v3 8/8] media: uvcvideo: Use standard names for menus
config: arm-randconfig-m041-20230101
compiler: arm-linux-gnueabi-gcc (GCC) 12.1.0

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>

smatch warnings:
drivers/media/usb/uvc/uvc_ctrl.c:1395 uvc_query_v4l2_menu() warn: unsigned 'menu_value' is never less than zero.

vim +/menu_value +1395 drivers/media/usb/uvc/uvc_ctrl.c

1345
1346 /*
1347 * Mapping V4L2 controls to UVC controls can be straightforward if done well.
1348 * Most of the UVC controls exist in V4L2, and can be mapped directly. Some
1349 * must be grouped (for instance the Red Balance, Blue Balance and Do White
1350 * Balance V4L2 controls use the White Balance Component UVC control) or
1351 * otherwise translated. The approach we take here is to use a translation
1352 * table for the controls that can be mapped directly, and handle the others
1353 * manually.
1354 */
1355 int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
1356 struct v4l2_querymenu *query_menu)
1357 {
1358 struct uvc_control_mapping *mapping;
1359 struct uvc_control *ctrl;
1360 u32 index = query_menu->index;
1361 u32 id = query_menu->id;
1362 const char *name;
1363 int ret;
1364
1365 memset(query_menu, 0, sizeof(*query_menu));
1366 query_menu->id = id;
1367 query_menu->index = index;
1368
1369 ret = mutex_lock_interruptible(&chain->ctrl_mutex);
1370 if (ret < 0)
1371 return -ERESTARTSYS;
1372
1373 ctrl = uvc_find_control(chain, query_menu->id, &mapping);
1374 if (ctrl == NULL || mapping->v4l2_type != V4L2_CTRL_TYPE_MENU) {
1375 ret = -EINVAL;
1376 goto done;
1377 }
1378
1379 if (!test_bit(query_menu->index, &mapping->menu_mask)) {
1380 ret = -EINVAL;
1381 goto done;
1382 }
1383
1384 if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK) {
1385 u32 menu_value;
1386
1387 if (!ctrl->cached) {
1388 ret = uvc_ctrl_populate_cache(chain, ctrl);
1389 if (ret < 0)
1390 goto done;
1391 }
1392
1393 menu_value = uvc_mapping_get_menu_value(mapping,
1394 query_menu->index);
> 1395 if (menu_value < 0) {
1396 ret = menu_value;
1397 goto done;
1398 }
1399 if (!(uvc_get_ctrl_bitmap(ctrl, mapping) & menu_value)) {
1400 ret = -EINVAL;
1401 goto done;
1402 }
1403 }
1404
1405 name = uvc_mapping_get_menu_name(mapping, query_menu->index);
1406 if (!name) {
1407 ret = -EINVAL;
1408 goto done;
1409 }
1410
1411 strscpy(query_menu->name, name, sizeof(query_menu->name));
1412
1413 done:
1414 mutex_unlock(&chain->ctrl_mutex);
1415 return ret;
1416 }
1417

--
0-DAY CI Kernel Test Service
https://01.org/lkp


Attachments:
(No filename) (3.29 kB)
config (173.60 kB)
Download all attachments

2023-01-05 11:55:07

by Ricardo Ribalda

[permalink] [raw]
Subject: Re: [PATCH v3 6/8] media: uvcvideo: Implement mask for V4L2_CTRL_TYPE_MENU

HI Laurent

There is a bug here as well.

When you drop "Fix power line control for Lenovo Integrated Camera"
Can you drop this as well?


Thanks!


On Tue, 3 Jan 2023 at 15:36, Ricardo Ribalda <[email protected]> wrote:
>
> Replace the count with a mask field that lets us choose not only the max
> value, but also the minimum value and what values are valid in between.
>
> Reviewed-by: Laurent Pinchart <[email protected]>
> Signed-off-by: Ricardo Ribalda <[email protected]>
> Suggested-by: Laurent Pinchart <[email protected]>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 30 ++++++++++++++++++++----------
> drivers/media/usb/uvc/uvc_driver.c | 3 ++-
> drivers/media/usb/uvc/uvc_v4l2.c | 3 ++-
> drivers/media/usb/uvc/uvcvideo.h | 2 +-
> 4 files changed, 25 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index 7622c5b54b35..aa7a668f60a7 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -6,6 +6,7 @@
> * Laurent Pinchart ([email protected])
> */
>
> +#include <linux/bitops.h>
> #include <linux/kernel.h>
> #include <linux/list.h>
> #include <linux/module.h>
> @@ -525,7 +526,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_BITMASK,
> .menu_info = exposure_auto_controls,
> - .menu_count = ARRAY_SIZE(exposure_auto_controls),
> + .menu_mask = BIT_MASK(ARRAY_SIZE(exposure_auto_controls)),
This should say
.menu_mask =
GENMASK(ARRAY_SIZE(exposure_auto_controls) - 1, 0

> .slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, },
> },
> {
> @@ -731,7 +732,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc11[] = {
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_ENUM,
> .menu_info = power_line_frequency_controls,
> - .menu_count = ARRAY_SIZE(power_line_frequency_controls) - 1,
> + .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls) - 1),

.menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
V4L2_CID_POWER_LINE_FREQUENCY_50HZ),



> },
> };
>
> @@ -745,7 +746,7 @@ static const struct uvc_control_mapping uvc_ctrl_mappings_uvc15[] = {
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_ENUM,
> .menu_info = power_line_frequency_controls,
> - .menu_count = ARRAY_SIZE(power_line_frequency_controls),
> + .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls)),
> },
> };
>
> @@ -975,7 +976,9 @@ static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
> const struct uvc_menu_info *menu = mapping->menu_info;
> unsigned int i;
>
> - for (i = 0; i < mapping->menu_count; ++i, ++menu) {
> + for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
> + if (!test_bit(i, &mapping->menu_mask))
> + continue;
> if (menu->value == value) {
> value = i;
> break;
> @@ -1228,12 +1231,14 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
>
> switch (mapping->v4l2_type) {
> case V4L2_CTRL_TYPE_MENU:
> - v4l2_ctrl->minimum = 0;
> - v4l2_ctrl->maximum = mapping->menu_count - 1;
> + v4l2_ctrl->minimum = ffs(mapping->menu_mask) - 1;
> + v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1;
> v4l2_ctrl->step = 1;
>
> menu = mapping->menu_info;
> - for (i = 0; i < mapping->menu_count; ++i, ++menu) {
> + for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
> + if (!test_bit(i, &mapping->menu_mask))
> + continue;
> if (menu->value == v4l2_ctrl->default_value) {
> v4l2_ctrl->default_value = i;
> break;
> @@ -1354,7 +1359,7 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
> goto done;
> }
>
> - if (query_menu->index >= mapping->menu_count) {
> + if (!test_bit(query_menu->index, &mapping->menu_mask)) {
> ret = -EINVAL;
> goto done;
> }
> @@ -1868,8 +1873,13 @@ int uvc_ctrl_set(struct uvc_fh *handle,
> break;
>
> case V4L2_CTRL_TYPE_MENU:
> - if (xctrl->value < 0 || xctrl->value >= mapping->menu_count)
> + if (xctrl->value < (ffs(mapping->menu_mask) - 1) ||
> + xctrl->value > (fls(mapping->menu_mask) - 1))
> return -ERANGE;
> +
> + if (!test_bit(xctrl->value, &mapping->menu_mask))
> + return -EINVAL;
> +
> value = mapping->menu_info[xctrl->value].value;
>
> /*
> @@ -2305,7 +2315,7 @@ static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
>
> INIT_LIST_HEAD(&map->ev_subs);
>
> - size = sizeof(*mapping->menu_info) * mapping->menu_count;
> + size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
> map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
> if (map->menu_info == NULL) {
> kfree(map->name);
> diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
> index e4bcb5011360..f9e6208c4636 100644
> --- a/drivers/media/usb/uvc/uvc_driver.c
> +++ b/drivers/media/usb/uvc/uvc_driver.c
> @@ -7,6 +7,7 @@
> */
>
> #include <linux/atomic.h>
> +#include <linux/bits.h>
> #include <linux/gpio/consumer.h>
> #include <linux/kernel.h>
> #include <linux/list.h>
> @@ -2384,7 +2385,7 @@ static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
> .v4l2_type = V4L2_CTRL_TYPE_MENU,
> .data_type = UVC_CTRL_DATA_TYPE_ENUM,
> .menu_info = power_line_frequency_controls_limited,
> - .menu_count = ARRAY_SIZE(power_line_frequency_controls_limited),
> + .menu_mask = BIT_MASK(ARRAY_SIZE(power_line_frequency_controls_limited)),
> };
>
> static const struct uvc_device_info uvc_ctrl_power_line_limited = {
> diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
> index 3edb54c086b2..ed2525e7e2a5 100644
> --- a/drivers/media/usb/uvc/uvc_v4l2.c
> +++ b/drivers/media/usb/uvc/uvc_v4l2.c
> @@ -6,6 +6,7 @@
> * Laurent Pinchart ([email protected])
> */
>
> +#include <linux/bits.h>
> #include <linux/compat.h>
> #include <linux/kernel.h>
> #include <linux/list.h>
> @@ -80,7 +81,7 @@ static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
> goto free_map;
> }
>
> - map->menu_count = xmap->menu_count;
> + map->menu_mask = BIT_MASK(xmap->menu_count);
> break;
>
> default:
> diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
> index a151f583cd15..f75e5864bbf7 100644
> --- a/drivers/media/usb/uvc/uvcvideo.h
> +++ b/drivers/media/usb/uvc/uvcvideo.h
> @@ -117,7 +117,7 @@ struct uvc_control_mapping {
> u32 data_type;
>
> const struct uvc_menu_info *menu_info;
> - u32 menu_count;
> + unsigned long menu_mask;
>
> u32 master_id;
> s32 master_manual;
>
> --
> 2.39.0.314.g84b9a713c41-goog-b4-0.11.0-dev-696ae



--
Ricardo Ribalda

2023-01-12 22:26:30

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v3 1/8] media: uvcvideo: Check for INACTIVE in uvc_ctrl_is_accessible()

Hi Hans,

On Tue, Jan 03, 2023 at 03:36:19PM +0100, Ricardo Ribalda wrote:
> From: Hans Verkuil <[email protected]>

There's a mismatch between the From and Signed-off-by tag. Which one
should I modify to match the other ?

> 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 <[email protected]>
> Reviewed-by: Ricardo Ribalda <[email protected]>
> Signed-off-by: Hans Verkuil <[email protected]>
> ---
> drivers/media/usb/uvc/uvc_ctrl.c | 42 +++++++++++++++++++++++++++++++++++++++-
> drivers/media/usb/uvc/uvc_v4l2.c | 3 +--
> drivers/media/usb/uvc/uvcvideo.h | 3 ++-
> 3 files changed, 44 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
> index c95a2229f4fa..6165d6b8e855 100644
> --- a/drivers/media/usb/uvc/uvc_ctrl.c
> +++ b/drivers/media/usb/uvc/uvc_ctrl.c
> @@ -1085,11 +1085,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;
> @@ -1104,6 +1121,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;
> }
>
> diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
> index f4d4c33b6dfb..3edb54c086b2 100644
> --- a/drivers/media/usb/uvc/uvc_v4l2.c
> +++ b/drivers/media/usb/uvc/uvc_v4l2.c
> @@ -1020,8 +1020,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;
> }
> diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
> index df93db259312..a151f583cd15 100644
> --- a/drivers/media/usb/uvc/uvcvideo.h
> +++ b/drivers/media/usb/uvc/uvcvideo.h
> @@ -761,7 +761,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);
>

--
Regards,

Laurent Pinchart