]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
usb: gadget: uvc: Allow definition of XUs in configfs
authorDaniel Scally <dan.scally@ideasonboard.com>
Mon, 6 Feb 2023 16:17:54 +0000 (16:17 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 7 Feb 2023 07:46:36 +0000 (08:46 +0100)
The UVC gadget at present has no support for extension units. Add the
infrastructure to uvc_configfs.c that allows users to create XUs via
configfs. These will be stored in a new child of uvcg_control_grp_type
with the name "extensions".

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Link: https://lore.kernel.org/r/20230206161802.892954-4-dan.scally@ideasonboard.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/configfs-usb-gadget-uvc
drivers/usb/gadget/function/f_uvc.c
drivers/usb/gadget/function/u_uvc.h
drivers/usb/gadget/function/uvc_configfs.c
drivers/usb/gadget/function/uvc_configfs.h

index c2323f2b069b8ed79e84d39ac8657cd314e9586d..80b98a4a4d0fc8f87bebdd8f7e2d9e1d690622f3 100644 (file)
@@ -113,6 +113,34 @@ Description:       Default processing unit descriptors
                bUnitID         a non-zero id of this unit
                =============== ========================================
 
+What:          /config/usb-gadget/gadget/functions/uvc.name/control/extensions
+Date:          Nov 2022
+KernelVersion: 6.1
+Description:   Extension unit descriptors
+
+What:          /config/usb-gadget/gadget/functions/uvc.name/control/extensions/name
+Date:          Nov 2022
+KernelVersion: 6.1
+Description:   Extension Unit (XU) Descriptor
+
+               bLength, bUnitID and iExtension are read-only. All others are
+               read-write.
+
+               =================       ========================================
+               bLength                 size of the descriptor in bytes
+               bUnitID                 non-zero ID of this unit
+               guidExtensionCode       Vendor-specific code identifying the XU
+               bNumControls            number of controls in this XU
+               bNrInPins               number of input pins for this unit
+               baSourceID              list of the IDs of the units or terminals
+                                       to which this XU is connected
+               bControlSize            size of the bmControls field in bytes
+               bmControls              list of bitmaps detailing which vendor
+                                       specific controls are supported
+               iExtension              index of a string descriptor that describes
+                                       this extension unit
+               =================       ========================================
+
 What:          /config/usb-gadget/gadget/functions/uvc.name/control/header
 Date:          Dec 2014
 KernelVersion: 4.0
index 835e121a806f415b71441496bcc4661d57158c53..443333471b4d6dabc2992ff3fddd7a9e7bc5ce4b 100644 (file)
@@ -865,6 +865,13 @@ static struct usb_function_instance *uvc_alloc_inst(void)
        od->bSourceID                   = 2;
        od->iTerminal                   = 0;
 
+       /*
+        * With the ability to add XUs to the UVC function graph, we need to be
+        * able to allocate unique unit IDs to them. The IDs are 1-based, with
+        * the CT, PU and OT above consuming the first 3.
+        */
+       opts->last_unit_id              = 3;
+
        /* Prepare fs control class descriptors for configfs-based gadgets */
        ctl_cls = opts->uvc_fs_control_cls;
        ctl_cls[0] = NULL;      /* assigned elsewhere by configfs */
@@ -885,6 +892,8 @@ static struct usb_function_instance *uvc_alloc_inst(void)
        opts->ss_control =
                (const struct uvc_descriptor_header * const *)ctl_cls;
 
+       INIT_LIST_HEAD(&opts->extension_units);
+
        opts->streaming_interval = 1;
        opts->streaming_maxpacket = 1024;
        snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera");
index 67cf319e9c2de0ebec87a5eb9d768196e2ea5779..0345b8fc36ff52d9c8b78b1c12f27186835a6e6a 100644 (file)
@@ -28,6 +28,7 @@ struct f_uvc_opts {
        unsigned int                                    control_interface;
        unsigned int                                    streaming_interface;
        char                                            function_name[32];
+       unsigned int                                    last_unit_id;
 
        bool                                            enable_interrupt_ep;
 
@@ -65,6 +66,12 @@ struct f_uvc_opts {
        struct uvc_descriptor_header                    *uvc_fs_control_cls[5];
        struct uvc_descriptor_header                    *uvc_ss_control_cls[5];
 
+       /*
+        * Control descriptors for extension units. There could be any number
+        * of these, including none at all.
+        */
+       struct list_head                                extension_units;
+
        /*
         * Streaming descriptors for full-speed, high-speed and super-speed.
         * Used by configfs only, must not be touched by legacy gadgets. The
index b32ecbdfd88d2a9f331d5e6ff49fd1a4a9fce4fd..c365f323af45ac810d6a3a838c7bcd6cfd703b29 100644 (file)
@@ -662,6 +662,485 @@ static const struct uvcg_config_group_type uvcg_terminal_grp_type = {
        },
 };
 
+/* -----------------------------------------------------------------------------
+ * control/extensions
+ */
+
+#define UVCG_EXTENSION_ATTR(cname, aname, ro...)                       \
+static ssize_t uvcg_extension_##cname##_show(struct config_item *item, \
+                                            char *page)                \
+{                                                                      \
+       struct config_group *group = to_config_group(item->ci_parent);  \
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;           \
+       struct uvcg_extension *xu = to_uvcg_extension(item);            \
+       struct config_item *opts_item;                                  \
+       struct f_uvc_opts *opts;                                        \
+       int ret;                                                        \
+                                                                       \
+       mutex_lock(su_mutex);                                           \
+                                                                       \
+       opts_item = item->ci_parent->ci_parent->ci_parent;              \
+       opts = to_f_uvc_opts(opts_item);                                \
+                                                                       \
+       mutex_lock(&opts->lock);                                        \
+       ret = sprintf(page, "%u\n", xu->desc.aname);                    \
+       mutex_unlock(&opts->lock);                                      \
+                                                                       \
+       mutex_unlock(su_mutex);                                         \
+                                                                       \
+       return ret;                                                     \
+}                                                                      \
+UVC_ATTR##ro(uvcg_extension_, cname, aname)
+
+UVCG_EXTENSION_ATTR(b_length, bLength, _RO);
+UVCG_EXTENSION_ATTR(b_unit_id, bUnitID, _RO);
+UVCG_EXTENSION_ATTR(i_extension, iExtension, _RO);
+
+static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item,
+                                                  const char *page, size_t len)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       int ret;
+       u8 num;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       ret = kstrtou8(page, 0, &num);
+       if (ret)
+               return ret;
+
+       mutex_lock(&opts->lock);
+       xu->desc.bNumControls = num;
+       mutex_unlock(&opts->lock);
+
+       mutex_unlock(su_mutex);
+
+       return len;
+}
+UVCG_EXTENSION_ATTR(b_num_controls, bNumControls);
+
+/*
+ * In addition to storing bNrInPins, this function needs to realloc the
+ * memory for the baSourceID array and additionally expand bLength.
+ */
+static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item,
+                                                const char *page, size_t len)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       void *tmp_buf;
+       int ret;
+       u8 num;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       ret = kstrtou8(page, 0, &num);
+       if (ret)
+               return ret;
+
+       mutex_lock(&opts->lock);
+
+       if (num == xu->desc.bNrInPins) {
+               ret = len;
+               goto unlock;
+       }
+
+       tmp_buf = krealloc_array(xu->desc.baSourceID, num, sizeof(u8),
+                                GFP_KERNEL | __GFP_ZERO);
+       if (!tmp_buf) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       xu->desc.baSourceID = tmp_buf;
+       xu->desc.bNrInPins = num;
+       xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins,
+                                                     xu->desc.bControlSize);
+
+       ret = len;
+
+unlock:
+       mutex_unlock(&opts->lock);
+       mutex_unlock(su_mutex);
+       return ret;
+}
+UVCG_EXTENSION_ATTR(b_nr_in_pins, bNrInPins);
+
+/*
+ * In addition to storing bControlSize, this function needs to realloc the
+ * memory for the bmControls array and additionally expand bLength.
+ */
+static ssize_t uvcg_extension_b_control_size_store(struct config_item *item,
+                                                  const char *page, size_t len)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       void *tmp_buf;
+       int ret;
+       u8 num;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       ret = kstrtou8(page, 0, &num);
+       if (ret)
+               return ret;
+
+       mutex_lock(&opts->lock);
+
+       if (num == xu->desc.bControlSize) {
+               ret = len;
+               goto unlock;
+       }
+
+       tmp_buf = krealloc_array(xu->desc.bmControls, num, sizeof(u8),
+                                GFP_KERNEL | __GFP_ZERO);
+       if (!tmp_buf) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       xu->desc.bmControls = tmp_buf;
+       xu->desc.bControlSize = num;
+       xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins,
+                                                     xu->desc.bControlSize);
+
+       ret = len;
+
+unlock:
+       mutex_unlock(&opts->lock);
+       mutex_unlock(su_mutex);
+       return ret;
+}
+
+UVCG_EXTENSION_ATTR(b_control_size, bControlSize);
+
+static ssize_t uvcg_extension_guid_extension_code_show(struct config_item *item,
+                                                      char *page)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+       memcpy(page, xu->desc.guidExtensionCode, sizeof(xu->desc.guidExtensionCode));
+       mutex_unlock(&opts->lock);
+
+       mutex_unlock(su_mutex);
+
+       return sizeof(xu->desc.guidExtensionCode);
+}
+
+static ssize_t uvcg_extension_guid_extension_code_store(struct config_item *item,
+                                                       const char *page, size_t len)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       int ret;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+       memcpy(xu->desc.guidExtensionCode, page,
+              min(sizeof(xu->desc.guidExtensionCode), len));
+       mutex_unlock(&opts->lock);
+
+       mutex_unlock(su_mutex);
+
+       ret = sizeof(xu->desc.guidExtensionCode);
+
+       return ret;
+}
+
+UVC_ATTR(uvcg_extension_, guid_extension_code, guidExtensionCode);
+
+static ssize_t uvcg_extension_ba_source_id_show(struct config_item *item,
+                                               char *page)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       char *pg = page;
+       int ret, i;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+       for (ret = 0, i = 0; i < xu->desc.bNrInPins; ++i) {
+               ret += sprintf(pg, "%u\n", xu->desc.baSourceID[i]);
+               pg = page + ret;
+       }
+       mutex_unlock(&opts->lock);
+
+       mutex_unlock(su_mutex);
+
+       return ret;
+}
+
+static ssize_t uvcg_extension_ba_source_id_store(struct config_item *item,
+                                                const char *page, size_t len)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       u8 *source_ids, *iter;
+       int ret, n = 0;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+
+       ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n,
+                                      sizeof(u8));
+       if (ret)
+               goto unlock;
+
+       iter = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL);
+       if (!source_ids) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter,
+                                      sizeof(u8));
+       if (ret) {
+               kfree(source_ids);
+               goto unlock;
+       }
+
+       kfree(xu->desc.baSourceID);
+       xu->desc.baSourceID = source_ids;
+       xu->desc.bNrInPins = n;
+       xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins,
+                                                     xu->desc.bControlSize);
+
+       ret = len;
+
+unlock:
+       mutex_unlock(&opts->lock);
+       mutex_unlock(su_mutex);
+       return ret;
+}
+UVC_ATTR(uvcg_extension_, ba_source_id, baSourceID);
+
+static ssize_t uvcg_extension_bm_controls_show(struct config_item *item,
+                                              char *page)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       char *pg = page;
+       int ret, i;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+       for (ret = 0, i = 0; i < xu->desc.bControlSize; ++i) {
+               ret += sprintf(pg, "0x%02x\n", xu->desc.bmControls[i]);
+               pg = page + ret;
+       }
+       mutex_unlock(&opts->lock);
+
+       mutex_unlock(su_mutex);
+
+       return ret;
+}
+
+static ssize_t uvcg_extension_bm_controls_store(struct config_item *item,
+                                               const char *page, size_t len)
+{
+       struct config_group *group = to_config_group(item->ci_parent);
+       struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+       struct uvcg_extension *xu = to_uvcg_extension(item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+       u8 *bm_controls, *iter;
+       int ret, n = 0;
+
+       mutex_lock(su_mutex);
+
+       opts_item = item->ci_parent->ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+
+       ret = __uvcg_iter_item_entries(page, len, __uvcg_count_item_entries, &n,
+                                      sizeof(u8));
+       if (ret)
+               goto unlock;
+
+       iter = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL);
+       if (!bm_controls) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter,
+                                      sizeof(u8));
+       if (ret) {
+               kfree(bm_controls);
+               goto unlock;
+       }
+
+       kfree(xu->desc.bmControls);
+       xu->desc.bmControls = bm_controls;
+       xu->desc.bControlSize = n;
+       xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(xu->desc.bNrInPins,
+                                                     xu->desc.bControlSize);
+
+       ret = len;
+
+unlock:
+       mutex_unlock(&opts->lock);
+       mutex_unlock(su_mutex);
+       return ret;
+}
+
+UVC_ATTR(uvcg_extension_, bm_controls, bmControls);
+
+static struct configfs_attribute *uvcg_extension_attrs[] = {
+       &uvcg_extension_attr_b_length,
+       &uvcg_extension_attr_b_unit_id,
+       &uvcg_extension_attr_b_num_controls,
+       &uvcg_extension_attr_b_nr_in_pins,
+       &uvcg_extension_attr_b_control_size,
+       &uvcg_extension_attr_guid_extension_code,
+       &uvcg_extension_attr_ba_source_id,
+       &uvcg_extension_attr_bm_controls,
+       &uvcg_extension_attr_i_extension,
+       NULL,
+};
+
+static void uvcg_extension_release(struct config_item *item)
+{
+       struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item);
+
+       kfree(xu);
+}
+
+static struct configfs_item_operations uvcg_extension_item_ops = {
+       .release        = uvcg_extension_release,
+};
+
+static const struct config_item_type uvcg_extension_type = {
+       .ct_item_ops    = &uvcg_extension_item_ops,
+       .ct_attrs       = uvcg_extension_attrs,
+       .ct_owner       = THIS_MODULE,
+};
+
+static void uvcg_extension_drop(struct config_group *group, struct config_item *item)
+{
+       struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item);
+       struct config_item *opts_item;
+       struct f_uvc_opts *opts;
+
+       opts_item = group->cg_item.ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       mutex_lock(&opts->lock);
+
+       config_item_put(item);
+       list_del(&xu->list);
+       kfree(xu->desc.baSourceID);
+       kfree(xu->desc.bmControls);
+
+       mutex_unlock(&opts->lock);
+}
+
+static struct config_item *uvcg_extension_make(struct config_group *group, const char *name)
+{
+       struct config_item *opts_item;
+       struct uvcg_extension *xu;
+       struct f_uvc_opts *opts;
+
+       opts_item = group->cg_item.ci_parent->ci_parent;
+       opts = to_f_uvc_opts(opts_item);
+
+       xu = kzalloc(sizeof(*xu), GFP_KERNEL);
+       if (!xu)
+               return ERR_PTR(-ENOMEM);
+
+       xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(0, 0);
+       xu->desc.bDescriptorType = USB_DT_CS_INTERFACE;
+       xu->desc.bDescriptorSubType = UVC_VC_EXTENSION_UNIT;
+       xu->desc.bNumControls = 0;
+       xu->desc.bNrInPins = 0;
+       xu->desc.baSourceID = NULL;
+       xu->desc.bControlSize = 0;
+       xu->desc.bmControls = NULL;
+
+       mutex_lock(&opts->lock);
+
+       xu->desc.bUnitID = ++opts->last_unit_id;
+
+       config_item_init_type_name(&xu->item, name, &uvcg_extension_type);
+       list_add_tail(&xu->list, &opts->extension_units);
+
+       mutex_unlock(&opts->lock);
+
+       return &xu->item;
+}
+
+static struct configfs_group_operations uvcg_extensions_grp_ops = {
+       .make_item      = uvcg_extension_make,
+       .drop_item      = uvcg_extension_drop,
+};
+
+static const struct uvcg_config_group_type uvcg_extensions_grp_type = {
+       .type = {
+               .ct_item_ops    = &uvcg_config_item_ops,
+               .ct_group_ops   = &uvcg_extensions_grp_ops,
+               .ct_owner       = THIS_MODULE,
+       },
+       .name = "extensions",
+};
+
 /* -----------------------------------------------------------------------------
  * control/class/{fs|ss}
  */
@@ -909,6 +1388,7 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = {
                &uvcg_processing_grp_type,
                &uvcg_terminal_grp_type,
                &uvcg_control_class_grp_type,
+               &uvcg_extensions_grp_type,
                NULL,
        },
 };
index 174ee691302b799c7d86061d0f51134a60dc9f35..5557813bcca9edf0fb2a2fa3e851ef08e478d3b0 100644 (file)
@@ -142,6 +142,35 @@ static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
        return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt);
 }
 
+/* -----------------------------------------------------------------------------
+ * control/extensions/<NAME>
+ */
+
+struct uvcg_extension_unit_descriptor {
+       u8 bLength;
+       u8 bDescriptorType;
+       u8 bDescriptorSubType;
+       u8 bUnitID;
+       u8 guidExtensionCode[16];
+       u8 bNumControls;
+       u8 bNrInPins;
+       u8 *baSourceID;
+       u8 bControlSize;
+       u8 *bmControls;
+       u8 iExtension;
+} __packed;
+
+struct uvcg_extension {
+       struct config_item item;
+       struct list_head list;
+       struct uvcg_extension_unit_descriptor desc;
+};
+
+static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item)
+{
+       return container_of(item, struct uvcg_extension, item);
+}
+
 int uvcg_attach_configfs(struct f_uvc_opts *opts);
 
 #endif /* UVC_CONFIGFS_H */