From a84391b77351bbca92ea9db2be1f44e17f2eda6d Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Mar 2026 13:34:44 +0000 Subject: media: uvcvideo: Import standard controls from uvcdynctrl The uvcdynctrl tool from libwebcam: https://sourceforge.net/projects/libwebcam/ maps proprietary controls into v4l2 controls using the UVCIOC_CTRL_MAP ioctl. The tool has not been updated for 10+ years now, and there is no reason for the UVC driver to not do the mapping by itself. This patch adds the mappings from the uvcdynctrl into the driver. Hopefully this effort can help in deprecating the UVCIOC_CTRL_MAP ioctl. Some background about UVCIOC_CTRL_MAP (thanks Laurent for the context): ``` this was envisioned as the base of a vibrant ecosystem where a large number of vendors would submit XML files that describe their XU control mappings, at a pace faster than could be supported by adding XU mappings to the driver. This vision failed to materialize and the tool has not been updated for 10+ years now. There is no reason to believe the situation will change. ``` During the porting, the following mappings where NOT imported because they were not using standard v4l2 IDs. It is recommended that userspace moves to UVCIOC_CTRL_QUERY for non standard controls. { .id = V4L2_CID_FLASH_MODE, .entity = UVC_GUID_SIS_LED_HW_CONTROL, .selector = 4, .size = 4, .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, .menu_mask = 0x3, .menu_mapping = { 0x20, 0x22 }, .menu_names = { "Off", "On" }, }, { .id = V4L2_CID_FLASH_FREQUENCY, .entity = UVC_GUID_SIS_LED_HW_CONTROL, .selector = 4, .size = 8, .offset = 16, .v4l2_type = V4L2_CTRL_TYPE_INTEGER, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, }, { .id = V4L2_CID_LED1_MODE, .entity = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1, .selector = 1, .size = 8, .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, .menu_mask = 0xF, .menu_mapping = { 0, 1, 2, 3 }, .menu_names = { "Off", "On", "Blinking", "Auto" }, }, { .id = V4L2_CID_LED1_FREQUENCY, .entity = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1, .selector = 1, .size = 8, .offset = 16, .v4l2_type = V4L2_CTRL_TYPE_INTEGER, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, }, { .id = V4L2_CID_DISABLE_PROCESSING, .entity = UVC_GUID_LOGITECH_VIDEO_PIPE_V1, .selector = 5, .size = 8, .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN, .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN, }, { .id = V4L2_CID_RAW_BITS_PER_PIXEL, .entity = UVC_GUID_LOGITECH_VIDEO_PIPE_V1, .selector = 8, .size = 8, .offset = 0, .v4l2_type = V4L2_CTRL_TYPE_INTEGER, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, }, { .id = V4L2_CID_LED1_MODE, .entity = UVC_GUID_LOGITECH_PERIPHERAL, .selector = 0x09, .size = 2, .offset = 8, .v4l2_type = V4L2_CTRL_TYPE_MENU, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, .menu_mask = 0xF, .menu_mapping = { 0, 1, 2, 3 }, .menu_names = { "Off", "On", "Blink", "Auto" }, }, { .id = V4L2_CID_LED1_FREQUENCY, .entity = UVC_GUID_LOGITECH_PERIPHERAL, .selector = 0x09, .size = 8, .offset = 24, .v4l2_type = V4L2_CTRL_TYPE_INTEGER, .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED, }, This script has been used to generate the mappings. They were then reformatted manually to follow the driver style. import sys import uuid import re import xml.etree.ElementTree as ET def get_namespace(root): return re.match(r"\{.*\}", root.tag).group(0) def get_single_guid(ns, constant): id = constant.find(ns + "id").text value = constant.find(ns + "value").text return (id, value) def get_constants(ns, root): out = dict() for constant in root.iter(ns + "constant"): attr = constant.attrib if attr["type"] == "integer": id, value = get_single_guid(ns, constant) if id in out: print(f"dupe constant {id}") out[id] = value return out def get_guids(ns, root): out = dict() for constant in root.iter(ns + "constant"): attr = constant.attrib if attr["type"] == "guid": id, value = get_single_guid(ns, constant) if id in out: print(f"dupe guid {id}") out[id] = value return out def get_single_control(ns, control): out = {} for id in "entity", "selector", "index", "size", "description": v = control.find(ns + id) if v is None and id == "description": continue out[id] = v.text reqs = set() for r in control.find(ns + "requests"): reqs.add(r.text) out["requests"] = reqs return (control.attrib["id"], out) def get_controls(ns, root): out = dict() for control in root.iter(ns + "control"): id, value = get_single_control(ns, control) if id in out: print(f"Dupe control id {id}") out[id] = value return out def get_single_mapping(ns, mapping): out = {} out["name"] = mapping.find(ns + "name").text uvc = mapping.find(ns + "uvc") for id in "size", "offset", "uvc_type": out[id] = uvc.find(ns + id).text out["control_ref"] = uvc.find(ns + "control_ref").attrib["idref"] v4l2 = mapping.find(ns + "v4l2") for id in "id", "v4l2_type": out[id] = v4l2.find(ns + id).text menu = {} for entry in v4l2.iter(ns + "menu_entry"): menu[entry.attrib["name"]] = entry.attrib["value"] if menu: out["menu"] = menu return out def get_mapping(ns, root): out = [] for control in root.iter(ns + "mapping"): mapping = get_single_mapping(ns, control) out += [mapping] return out def print_guids(guids): for g in guids: print(f"#define {g} \\") u_bytes = uuid.UUID(guids[g]).bytes_le u_bytes = [f"0x{b:02x}" for b in u_bytes] print("\t{ " + ", ".join(u_bytes) + " }") def print_flags(flags): get_range = {"GET_MIN", "GET_DEF", "GET_MAX", "GET_CUR", "GET_RES"} if get_range.issubset(flags): flags -= get_range flags.add("GET_RANGE") flags = list(flags) flags.sort() out = "" for f in flags[:-1]: out += f"UVC_CTRL_FLAG_{f}\n\t\t\t\t| " out += f"UVC_CTRL_FLAG_{flags[-1]}" return out def print_description(desc): print("/*") for line in desc.strip().splitlines(): print(f" * {line.strip()}") print("*/") def print_controls(controls, cons): for id in controls: c = controls[id] if "description" in c: print_description(c["description"]) print( f"""\t{{ \t\t.entity\t\t= {c["entity"]}, \t\t.selector\t= {cons[c["selector"]]}, \t\t.index\t\t= {c["index"]}, \t\t.size\t\t= {c["size"]}, \t\t.flags\t\t= {print_flags(c["requests"])}, \t}},""" ) def menu_mapping_txt(menu): out = f"\n\t\t.menu_mask\t= 0x{((1< Cc: Martin Rubli Reviewed-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- include/linux/usb/uvc.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/linux') diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index 05bfebab42b6..84f35c6f4d6e 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -43,6 +43,12 @@ #define UVC_GUID_MSXU_1_5 \ {0xdc, 0x95, 0x3f, 0x0f, 0x32, 0x26, 0x4e, 0x4c, \ 0x92, 0xc9, 0xa0, 0x47, 0x82, 0xf4, 0x3b, 0xc8} +#define UVC_GUID_LOGITECH_MOTOR_CONTROL_V1 \ + {0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \ + 0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x56 } +#define UVC_GUID_LOGITECH_PERIPHERAL \ + {0x21, 0x2d, 0xe5, 0xff, 0x30, 0x80, 0x2c, 0x4e, \ + 0x82, 0xd9, 0xf5, 0x87, 0xd0, 0x05, 0x40, 0xbd } /* https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/uvc-extensions-1-5#222-extension-unit-controls */ #define UVC_MSXU_CONTROL_FOCUS 0x01 -- cgit v1.2.3 From 02f93b4f940c66b99258cce954dcaeda7b3295a8 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Mar 2026 13:34:46 +0000 Subject: media: uvcvideo: Introduce allow_privacy_override module parameter Some camera modules have XU controls that can configure the behaviour of the privacy LED. Block mapping of those controls, unless the module is configured with a new parameter: allow_privacy_override. Signed-off-by: Ricardo Ribalda [johannes.goede@oss.qualcomm.com: Remove deprecation warning from param] Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 38 ++++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/uvc_driver.c | 4 ++++ drivers/media/usb/uvc/uvc_v4l2.c | 7 +++++++ drivers/media/usb/uvc/uvcvideo.h | 2 ++ include/linux/usb/uvc.h | 4 ++++ 5 files changed, 55 insertions(+) (limited to 'include/linux') diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index b6e020b41671..3ca108b83f1d 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -3001,6 +3001,35 @@ static int uvc_ctrl_init_xu_ctrl(struct uvc_device *dev, return ret; } +bool uvc_ctrl_is_privacy_control(u8 entity[16], u8 selector) +{ + /* + * This list is not exhaustive, it is a best effort to block access to + * non documented controls that can affect user's privacy. + */ + struct privacy_control { + u8 entity[16]; + u8 selector; + } privacy_control[] = { + { + .entity = UVC_GUID_LOGITECH_USER_HW_CONTROL_V1, + .selector = 1, + }, + { + .entity = UVC_GUID_LOGITECH_PERIPHERAL, + .selector = 9, + }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(privacy_control); i++) + if (!memcmp(entity, privacy_control[i].entity, 16) && + selector == privacy_control[i].selector) + return true; + + return false; +} + int uvc_xu_ctrl_query(struct uvc_video_chain *chain, struct uvc_xu_control_query *xqry) { @@ -3045,6 +3074,15 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, return -ENOENT; } + if (uvc_ctrl_is_privacy_control(entity->guid, xqry->selector) && + !uvc_allow_privacy_override_param) { + dev_warn_once(&chain->dev->intf->dev, + "Privacy related controls can only be accessed if module parameter allow_privacy_override is true\n"); + uvc_dbg(chain->dev, CONTROL, "Blocking access to privacy related Control %pUl/%u\n", + entity->guid, xqry->selector); + return -EACCES; + } + if (mutex_lock_interruptible(&chain->ctrl_mutex)) return -ERESTARTSYS; diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 31b4ac3b48c1..e289cc71ba98 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -36,6 +36,7 @@ unsigned int uvc_no_drop_param = 1; static unsigned int uvc_quirks_param = -1; unsigned int uvc_dbg_param; unsigned int uvc_timeout_param = UVC_CTRL_STREAMING_TIMEOUT; +bool uvc_allow_privacy_override_param; static struct usb_driver uvc_driver; @@ -2504,6 +2505,9 @@ module_param_named(trace, uvc_dbg_param, uint, 0644); MODULE_PARM_DESC(trace, "Trace level bitmask"); module_param_named(timeout, uvc_timeout_param, uint, 0644); MODULE_PARM_DESC(timeout, "Streaming control requests timeout"); +module_param_named(allow_privacy_override, uvc_allow_privacy_override_param, bool, 0644); +MODULE_PARM_DESC(allow_privacy_override, + "Allow access to privacy related controls"); /* ------------------------------------------------------------------------ * Driver initialization and cleanup diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index fa852b789193..644c2f1cd8e6 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -133,6 +133,13 @@ static int uvc_ioctl_xu_ctrl_map(struct uvc_video_chain *chain, return -EINVAL; } + if (uvc_ctrl_is_privacy_control(xmap->entity, xmap->selector) && + !uvc_allow_privacy_override_param) { + dev_warn_once(&chain->dev->intf->dev, + "Privacy related controls can only be mapped if module parameter allow_privacy_override is true\n"); + return -EACCES; + } + map = kzalloc_obj(*map); if (map == NULL) return -ENOMEM; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 0a0c01b2420f..4ba35727e954 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -666,6 +666,7 @@ extern unsigned int uvc_no_drop_param; extern unsigned int uvc_dbg_param; extern unsigned int uvc_timeout_param; extern unsigned int uvc_hw_timestamps_param; +extern bool uvc_allow_privacy_override_param; #define uvc_dbg(_dev, flag, fmt, ...) \ do { \ @@ -791,6 +792,7 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, struct uvc_xu_control_query *xqry); void uvc_ctrl_cleanup_fh(struct uvc_fh *handle); +bool uvc_ctrl_is_privacy_control(u8 entity[16], u8 selector); /* Utility functions */ struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts, diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index 84f35c6f4d6e..99b070ab860f 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -49,6 +49,10 @@ #define UVC_GUID_LOGITECH_PERIPHERAL \ {0x21, 0x2d, 0xe5, 0xff, 0x30, 0x80, 0x2c, 0x4e, \ 0x82, 0xd9, 0xf5, 0x87, 0xd0, 0x05, 0x40, 0xbd } +#define UVC_GUID_LOGITECH_USER_HW_CONTROL_V1 \ + {0x82, 0x06, 0x61, 0x63, 0x70, 0x50, 0xab, 0x49, \ + 0xb8, 0xcc, 0xb3, 0x85, 0x5e, 0x8d, 0x22, 0x1f } + /* https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/uvc-extensions-1-5#222-extension-unit-controls */ #define UVC_MSXU_CONTROL_FOCUS 0x01 -- cgit v1.2.3