]> git.ipfire.org Git - thirdparty/kernel/linux.git/commit
media: uvcvideo: Import standard controls from uvcdynctrl
authorRicardo Ribalda <ribalda@chromium.org>
Mon, 16 Mar 2026 13:34:44 +0000 (13:34 +0000)
committerHans Verkuil <hverkuil+cisco@kernel.org>
Tue, 12 May 2026 05:30:53 +0000 (07:30 +0200)
commita84391b77351bbca92ea9db2be1f44e17f2eda6d
treeedcfcd5acc1fa9ab3dede66e2785fc9cf8182384
parent2f24ac8dd87983a55f0498898f34a5f2b735b802
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<<len(menu))-1):X},\n"
    out += f"\t\t.menu_mapping\t= {{ {", ".join(menu.values())} }},\n"
    out += f"\t\t.menu_names\t= {{ \"{"\", \"".join(menu.keys())}\" }},\n"
    return out

def print_mappings(mappings, controls, cons):
    for m in mappings:
        c = controls[m["control_ref"]]

        if "menu" in m:
            menu_mapping = menu_mapping_txt(m["menu"])
        else:
            menu_mapping = ""
        print(
            f"""\t{{
\t\t.id\t\t= {m["id"]},
\t\t.entity\t\t= {c["entity"]},
\t\t.selector\t= {cons[c["selector"]]},
\t\t.size\t\t= {m["size"]},
\t\t.offset\t\t= {m["offset"]},
\t\t.v4l2_type\t= {m["v4l2_type"]},
\t\t.data_type\t= {m["uvc_type"]},{menu_mapping}
\t}},"""
        )

def print_code(guids, cons, controls, mappings):
    used_controls = set()
    for m in mappings:
        used_controls.add(m["control_ref"])

    used_guids = set()
    for c in used_controls:
        used_guids.add(controls[c]["entity"])

    print("\n######GUIDs#######\n")
    print_guids({id: guids[id] for id in guids if id in used_guids})
    print("\n######CONTROLS#######\n")
    print_controls({id: controls[id] for id in controls if id in used_controls}, cons)
    print("\n######MAPPINGS#######\n")
    print_mappings(mappings, controls, cons)
    # print(guids)
    # print(used_controls)

root = ET.fromstring(sys.stdin.read())
ns = get_namespace(root)
cons = get_constants(ns, root)
guids = get_guids(ns, root)
controls = get_controls(ns, root)
mappings = get_mapping(ns, root)
print_code(guids, cons, controls, mappings)

Cc: Manav Gautama <bandwidthcrunch@gmail.com>
Cc: Martin Rubli <martin_rubli@logitech.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Ricardo Ribalda <ribalda@chromium.org>
Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
Signed-off-by: Hans Verkuil <hverkuil+cisco@kernel.org>
drivers/media/usb/uvc/uvc_ctrl.c
include/linux/usb/uvc.h