]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
usb: gadget: configfs: OS Extended Compatibility descriptors support
authorAndrzej Pietrasiewicz <andrzej.p@samsung.com>
Thu, 8 May 2014 12:06:26 +0000 (14:06 +0200)
committerFelipe Balbi <balbi@ti.com>
Wed, 14 May 2014 14:39:25 +0000 (09:39 -0500)
Add handling of OS Extended Compatibility descriptors from configfs interface.
Hosts which expect the "OS Descriptors" ask only for configurations @ index 0,
but linux-based USB devices can provide more than one configuration.
This patch adds marking one of gadget's configurations the configuration
to be reported at index 0, regardless of the actual sequence of usb_add_config
invocations used for adding the configurations. The configuration is selected
by creating a symbolic link pointing to it from the "os_desc" directory
located at the top of a gadget's directory hierarchy.

One kind of "OS Descriptors" are "Extended Compatibility Descriptors",
which need to be specified per interface. This patch adds interface.<n>
directory in function's configfs directory to represent each interface
defined by the function. Each interface's directory contains two attributes:
"compatible_id" and "sub_compatible_id", which represent 8-byte
strings to be reported to the host as the "Compatible ID" and "Sub Compatible
ID".

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
Documentation/ABI/testing/configfs-usb-gadget
drivers/usb/gadget/configfs.c
drivers/usb/gadget/configfs.h
include/linux/usb/composite.h

index 0e7b786f24acbf802663810e3722e263bbd34e53..5c0b3e6eb98106c96486bb54dff6b281d1f8fb78 100644 (file)
@@ -62,6 +62,19 @@ KernelVersion:       3.11
 Description:
                This group contains functions available to this USB gadget.
 
+What:          /config/usb-gadget/gadget/functions/<func>.<inst>/interface.<n>
+Date:          May 2014
+KernelVersion: 3.16
+Description:
+               This group contains "Feature Descriptors" specific for one
+               gadget's USB interface or one interface group described
+               by an IAD.
+
+               The attributes:
+
+               compatible_id           - 8-byte string for "Compatible ID"
+               sub_compatible_id       - 8-byte string for "Sub Compatible ID"
+
 What:          /config/usb-gadget/gadget/strings
 Date:          Jun 2013
 KernelVersion: 3.11
index 8b9e038ac22b8c2bb8a37fe2739f22cccd957e98..fa6cb06cca09b8d9fbb62c6fed744199902d4254 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/usb/composite.h>
 #include <linux/usb/gadget_configfs.h>
 #include "configfs.h"
+#include "u_f.h"
 
 int check_user_usb_string(const char *name,
                struct usb_gadget_strings *stringtab_dev)
@@ -872,10 +873,63 @@ static void os_desc_attr_release(struct config_item *item)
        kfree(os_desc);
 }
 
+static int os_desc_link(struct config_item *os_desc_ci,
+                       struct config_item *usb_cfg_ci)
+{
+       struct gadget_info *gi = container_of(to_config_group(os_desc_ci),
+                                       struct gadget_info, os_desc_group);
+       struct usb_composite_dev *cdev = &gi->cdev;
+       struct config_usb_cfg *c_target =
+               container_of(to_config_group(usb_cfg_ci),
+                            struct config_usb_cfg, group);
+       struct usb_configuration *c;
+       int ret;
+
+       mutex_lock(&gi->lock);
+       list_for_each_entry(c, &cdev->configs, list) {
+               if (c == &c_target->c)
+                       break;
+       }
+       if (c != &c_target->c) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (cdev->os_desc_config) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       cdev->os_desc_config = &c_target->c;
+       ret = 0;
+
+out:
+       mutex_unlock(&gi->lock);
+       return ret;
+}
+
+static int os_desc_unlink(struct config_item *os_desc_ci,
+                         struct config_item *usb_cfg_ci)
+{
+       struct gadget_info *gi = container_of(to_config_group(os_desc_ci),
+                                       struct gadget_info, os_desc_group);
+       struct usb_composite_dev *cdev = &gi->cdev;
+
+       mutex_lock(&gi->lock);
+       if (gi->udc_name)
+               unregister_gadget(gi);
+       cdev->os_desc_config = NULL;
+       WARN_ON(gi->udc_name);
+       mutex_unlock(&gi->lock);
+       return 0;
+}
+
 static struct configfs_item_operations os_desc_ops = {
        .release                = os_desc_attr_release,
        .show_attribute         = os_desc_attr_show,
        .store_attribute        = os_desc_attr_store,
+       .allow_link             = os_desc_link,
+       .drop_link              = os_desc_unlink,
 };
 
 static struct config_item_type os_desc_type = {
@@ -884,6 +938,133 @@ static struct config_item_type os_desc_type = {
        .ct_owner       = THIS_MODULE,
 };
 
+CONFIGFS_ATTR_STRUCT(usb_os_desc);
+CONFIGFS_ATTR_OPS(usb_os_desc);
+
+static struct configfs_item_operations interf_item_ops = {
+       .show_attribute         = usb_os_desc_attr_show,
+       .store_attribute        = usb_os_desc_attr_store,
+};
+
+static ssize_t rndis_grp_compatible_id_show(struct usb_os_desc *desc,
+                                           char *page)
+{
+       memcpy(page, desc->ext_compat_id, 8);
+       return 8;
+}
+
+static ssize_t rndis_grp_compatible_id_store(struct usb_os_desc *desc,
+                                            const char *page, size_t len)
+{
+       int l;
+
+       l = min_t(int, 8, len);
+       if (page[l - 1] == '\n')
+               --l;
+       if (desc->opts_mutex)
+               mutex_lock(desc->opts_mutex);
+       memcpy(desc->ext_compat_id, page, l);
+       desc->ext_compat_id[l] = '\0';
+
+       if (desc->opts_mutex)
+               mutex_unlock(desc->opts_mutex);
+
+       return len;
+}
+
+static struct usb_os_desc_attribute rndis_grp_attr_compatible_id =
+       __CONFIGFS_ATTR(compatible_id, S_IRUGO | S_IWUSR,
+                       rndis_grp_compatible_id_show,
+                       rndis_grp_compatible_id_store);
+
+static ssize_t rndis_grp_sub_compatible_id_show(struct usb_os_desc *desc,
+                                               char *page)
+{
+       memcpy(page, desc->ext_compat_id + 8, 8);
+       return 8;
+}
+
+static ssize_t rndis_grp_sub_compatible_id_store(struct usb_os_desc *desc,
+                                                const char *page, size_t len)
+{
+       int l;
+
+       l = min_t(int, 8, len);
+       if (page[l - 1] == '\n')
+               --l;
+       if (desc->opts_mutex)
+               mutex_lock(desc->opts_mutex);
+       memcpy(desc->ext_compat_id + 8, page, l);
+       desc->ext_compat_id[l + 8] = '\0';
+
+       if (desc->opts_mutex)
+               mutex_unlock(desc->opts_mutex);
+
+       return len;
+}
+
+static struct usb_os_desc_attribute rndis_grp_attr_sub_compatible_id =
+       __CONFIGFS_ATTR(sub_compatible_id, S_IRUGO | S_IWUSR,
+                       rndis_grp_sub_compatible_id_show,
+                       rndis_grp_sub_compatible_id_store);
+
+static struct configfs_attribute *interf_grp_attrs[] = {
+       &rndis_grp_attr_compatible_id.attr,
+       &rndis_grp_attr_sub_compatible_id.attr,
+       NULL
+};
+
+int usb_os_desc_prepare_interf_dir(struct config_group *parent,
+                                  int n_interf,
+                                  struct usb_os_desc **desc,
+                                  struct module *owner)
+{
+       struct config_group **f_default_groups, *os_desc_group,
+                               **interface_groups;
+       struct config_item_type *os_desc_type, *interface_type;
+
+       vla_group(data_chunk);
+       vla_item(data_chunk, struct config_group *, f_default_groups, 2);
+       vla_item(data_chunk, struct config_group, os_desc_group, 1);
+       vla_item(data_chunk, struct config_group *, interface_groups,
+                n_interf + 1);
+       vla_item(data_chunk, struct config_item_type, os_desc_type, 1);
+       vla_item(data_chunk, struct config_item_type, interface_type, 1);
+
+       char *vlabuf = kzalloc(vla_group_size(data_chunk), GFP_KERNEL);
+       if (!vlabuf)
+               return -ENOMEM;
+
+       f_default_groups = vla_ptr(vlabuf, data_chunk, f_default_groups);
+       os_desc_group = vla_ptr(vlabuf, data_chunk, os_desc_group);
+       os_desc_type = vla_ptr(vlabuf, data_chunk, os_desc_type);
+       interface_groups = vla_ptr(vlabuf, data_chunk, interface_groups);
+       interface_type = vla_ptr(vlabuf, data_chunk, interface_type);
+
+       parent->default_groups = f_default_groups;
+       os_desc_type->ct_owner = owner;
+       config_group_init_type_name(os_desc_group, "os_desc", os_desc_type);
+       f_default_groups[0] = os_desc_group;
+
+       os_desc_group->default_groups = interface_groups;
+       interface_type->ct_item_ops = &interf_item_ops;
+       interface_type->ct_attrs = interf_grp_attrs;
+       interface_type->ct_owner = owner;
+
+       while (n_interf--) {
+               struct usb_os_desc *d;
+
+               d = desc[n_interf];
+               config_group_init_type_name(&d->group, "", interface_type);
+               config_item_set_name(&d->group.cg_item, "interface.%d",
+                                    n_interf);
+               interface_groups[n_interf] = &d->group;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(usb_os_desc_prepare_interf_dir);
+
 static int configfs_do_nothing(struct usb_composite_dev *cdev)
 {
        WARN_ON(1);
@@ -893,6 +1074,9 @@ static int configfs_do_nothing(struct usb_composite_dev *cdev)
 int composite_dev_prepare(struct usb_composite_driver *composite,
                struct usb_composite_dev *dev);
 
+int composite_os_desc_req_prepare(struct usb_composite_dev *cdev,
+                                 struct usb_ep *ep0);
+
 static void purge_configs_funcs(struct gadget_info *gi)
 {
        struct usb_configuration        *c;
@@ -1028,6 +1212,12 @@ static int configfs_composite_bind(struct usb_gadget *gadget,
                }
                usb_ep_autoconfig_reset(cdev->gadget);
        }
+       if (cdev->use_os_string) {
+               ret = composite_os_desc_req_prepare(cdev, gadget->ep0);
+               if (ret)
+                       goto err_purge_funcs;
+       }
+
        usb_ep_autoconfig_reset(cdev->gadget);
        return 0;
 
index a7b564a913d1e30bfc1633408f0bff3c2b159ec4..a14ac792c69865a616279daa20343c2f5a01cc30 100644 (file)
@@ -1,6 +1,18 @@
 #ifndef USB__GADGET__CONFIGFS__H
 #define USB__GADGET__CONFIGFS__H
 
+#include <linux/configfs.h>
+
 void unregister_gadget_item(struct config_item *item);
 
+int usb_os_desc_prepare_interf_dir(struct config_group *parent,
+                                  int n_interf,
+                                  struct usb_os_desc **desc,
+                                  struct module *owner);
+
+static inline struct usb_os_desc *to_usb_os_desc(struct config_item *item)
+{
+       return container_of(to_config_group(item), struct usb_os_desc, group);
+}
+
 #endif /*  USB__GADGET__CONFIGFS__H */
index 549f5382b01ae49472c97c8fc1e5a5957a8fec84..9c3903d767812274a4e3479bfaad9d182e5b42b2 100644 (file)
@@ -80,12 +80,16 @@ struct usb_os_desc_ext_prop {
  * @ext_prop: Extended Properties list
  * @ext_prop_len: Total length of Extended Properties blobs
  * @ext_prop_count: Number of Extended Properties
+ * @opts_mutex: Optional mutex protecting config data of a usb_function_instance
+ * @group: Represents OS descriptors associated with an interface in configfs
  */
 struct usb_os_desc {
        char                    *ext_compat_id;
        struct list_head        ext_prop;
        int                     ext_prop_len;
        int                     ext_prop_count;
+       struct mutex            *opts_mutex;
+       struct config_group     group;
 };
 
 /**
@@ -381,6 +385,8 @@ extern void usb_composite_unregister(struct usb_composite_driver *driver);
 extern void usb_composite_setup_continue(struct usb_composite_dev *cdev);
 extern int composite_dev_prepare(struct usb_composite_driver *composite,
                struct usb_composite_dev *cdev);
+extern int composite_os_desc_req_prepare(struct usb_composite_dev *cdev,
+                                        struct usb_ep *ep0);
 void composite_dev_cleanup(struct usb_composite_dev *cdev);
 
 static inline struct usb_composite_driver *to_cdriver(