usb: gadget: configfs: Support arbitrary string descriptors
authorDaniel Scally <dan.scally@ideasonboard.com>
Mon, 6 Feb 2023 16:17:57 +0000 (16:17 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 7 Feb 2023 07:46:36 +0000 (08:46 +0100)
Add a framework to allow users to define arbitrary string descriptors
for a USB Gadget. This is modelled as a new type of config item rather
than as hardcoded attributes so as to be as flexible as possible.

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Link: https://lore.kernel.org/r/20230206161802.892954-7-dan.scally@ideasonboard.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/usb/gadget_configfs.rst
drivers/usb/gadget/configfs.c
include/linux/usb/gadget.h

index e4566ffb223f29a8124f84e94feaf1a7cfc865b9..868e118a2644f75080537a5936e3d4e1f45822c5 100644 (file)
@@ -90,6 +90,16 @@ Then the strings can be specified::
        $ echo <manufacturer> > strings/0x409/manufacturer
        $ echo <product> > strings/0x409/product
 
+Further custom string descriptors can be created as directories within the
+language's directory, with the string text being written to the "s" attribute
+within the string's directory:
+
+       $ mkdir strings/0x409/xu.0
+       $ echo <string text> > strings/0x409/xu.0/s
+
+Where function drivers support it, functions may allow symlinks to these custom
+string descriptors to associate those strings with class descriptors.
+
 2. Creating the configurations
 ------------------------------
 
index 3beeafcf2e3bdad51fef860fab337ef9202da3df..ac275855eeb68a53f580f5dd0693ad8d2e3c8ccf 100644 (file)
@@ -95,6 +95,8 @@ struct gadget_language {
 
        struct config_group group;
        struct list_head list;
+       struct list_head gadget_strings;
+       unsigned int nstrings;
 };
 
 struct gadget_config_name {
@@ -791,8 +793,174 @@ static void gadget_language_attr_release(struct config_item *item)
        kfree(gs);
 }
 
-USB_CONFIG_STRING_RW_OPS(gadget_language);
-USB_CONFIG_STRINGS_LANG(gadget_language, gadget_info);
+static struct configfs_item_operations gadget_language_langid_item_ops = {
+       .release                = gadget_language_attr_release,
+};
+
+static ssize_t gadget_string_id_show(struct config_item *item, char *page)
+{
+       struct gadget_string *string = to_gadget_string(item);
+       int ret;
+
+       ret = sprintf(page, "%u\n", string->usb_string.id);
+       return ret;
+}
+CONFIGFS_ATTR_RO(gadget_string_, id);
+
+static ssize_t gadget_string_s_show(struct config_item *item, char *page)
+{
+       struct gadget_string *string = to_gadget_string(item);
+       int ret;
+
+       ret = snprintf(page, sizeof(string->string), "%s\n", string->string);
+       return ret;
+}
+
+static ssize_t gadget_string_s_store(struct config_item *item, const char *page,
+                                    size_t len)
+{
+       struct gadget_string *string = to_gadget_string(item);
+       int size = min(sizeof(string->string), len + 1);
+       int ret;
+
+       if (len > USB_MAX_STRING_LEN)
+               return -EINVAL;
+
+       ret = strscpy(string->string, page, size);
+       return len;
+}
+CONFIGFS_ATTR(gadget_string_, s);
+
+static struct configfs_attribute *gadget_string_attrs[] = {
+       &gadget_string_attr_id,
+       &gadget_string_attr_s,
+       NULL,
+};
+
+static void gadget_string_release(struct config_item *item)
+{
+       struct gadget_string *string = to_gadget_string(item);
+
+       kfree(string);
+}
+
+static struct configfs_item_operations gadget_string_item_ops = {
+       .release        = gadget_string_release,
+};
+
+static const struct config_item_type gadget_string_type = {
+       .ct_item_ops    = &gadget_string_item_ops,
+       .ct_attrs       = gadget_string_attrs,
+       .ct_owner       = THIS_MODULE,
+};
+
+static struct config_item *gadget_language_string_make(struct config_group *group,
+                                                      const char *name)
+{
+       struct gadget_language *language;
+       struct gadget_string *string;
+
+       language = to_gadget_language(&group->cg_item);
+
+       string = kzalloc(sizeof(*string), GFP_KERNEL);
+       if (!string)
+               return ERR_PTR(-ENOMEM);
+
+       string->usb_string.id = language->nstrings++;
+       string->usb_string.s = string->string;
+       list_add_tail(&string->list, &language->gadget_strings);
+
+       config_item_init_type_name(&string->item, name, &gadget_string_type);
+
+       return &string->item;
+}
+
+static void gadget_language_string_drop(struct config_group *group,
+                                       struct config_item *item)
+{
+       struct gadget_language *language;
+       struct gadget_string *string;
+       unsigned int i = USB_GADGET_FIRST_AVAIL_IDX;
+
+       language = to_gadget_language(&group->cg_item);
+       string = to_gadget_string(item);
+
+       list_del(&string->list);
+       language->nstrings--;
+
+       /* Reset the ids for the language's strings to guarantee a continuous set */
+       list_for_each_entry(string, &language->gadget_strings, list)
+               string->usb_string.id = i++;
+}
+
+static struct configfs_group_operations gadget_language_langid_group_ops = {
+       .make_item              = gadget_language_string_make,
+       .drop_item              = gadget_language_string_drop,
+};
+
+static struct config_item_type gadget_language_type = {
+       .ct_item_ops    = &gadget_language_langid_item_ops,
+       .ct_group_ops   = &gadget_language_langid_group_ops,
+       .ct_attrs       = gadget_language_langid_attrs,
+       .ct_owner       = THIS_MODULE,
+};
+
+static struct config_group *gadget_language_make(struct config_group *group,
+                                                const char *name)
+{
+       struct gadget_info *gi;
+       struct gadget_language *gs;
+       struct gadget_language *new;
+       int langs = 0;
+       int ret;
+
+       new = kzalloc(sizeof(*new), GFP_KERNEL);
+       if (!new)
+               return ERR_PTR(-ENOMEM);
+
+       ret = check_user_usb_string(name, &new->stringtab_dev);
+       if (ret)
+               goto err;
+       config_group_init_type_name(&new->group, name,
+                                   &gadget_language_type);
+
+       gi = container_of(group, struct gadget_info, strings_group);
+       ret = -EEXIST;
+       list_for_each_entry(gs, &gi->string_list, list) {
+               if (gs->stringtab_dev.language == new->stringtab_dev.language)
+                       goto err;
+               langs++;
+       }
+       ret = -EOVERFLOW;
+       if (langs >= MAX_USB_STRING_LANGS)
+               goto err;
+
+       list_add_tail(&new->list, &gi->string_list);
+       INIT_LIST_HEAD(&new->gadget_strings);
+
+       /* We have the default manufacturer, product and serialnumber strings */
+       new->nstrings = 3;
+       return &new->group;
+err:
+       kfree(new);
+       return ERR_PTR(ret);
+}
+
+static void gadget_language_drop(struct config_group *group,
+                                struct config_item *item)
+{
+       config_item_put(item);
+}
+
+static struct configfs_group_operations gadget_language_group_ops = {
+       .make_group     = &gadget_language_make,
+       .drop_item      = &gadget_language_drop,
+};
+
+static struct config_item_type gadget_language_strings_type = {
+       .ct_group_ops   = &gadget_language_group_ops,
+       .ct_owner       = THIS_MODULE,
+};
 
 static inline struct gadget_info *webusb_item_to_gadget_info(
                struct config_item *item)
index dc3092cea99e9b99a6eb8d6aba6102629f6f9344..00750f7020f3bfe13373fe455bac4c1859643681 100644 (file)
@@ -15,6 +15,7 @@
 #ifndef __LINUX_USB_GADGET_H
 #define __LINUX_USB_GADGET_H
 
+#include <linux/configfs.h>
 #include <linux/device.h>
 #include <linux/errno.h>
 #include <linux/init.h>
@@ -821,6 +822,16 @@ int usb_gadget_get_string(const struct usb_gadget_strings *table, int id, u8 *bu
 /* check if the given language identifier is valid */
 bool usb_validate_langid(u16 langid);
 
+struct gadget_string {
+       struct config_item item;
+       struct list_head list;
+       char string[USB_MAX_STRING_LEN];
+       struct usb_string usb_string;
+};
+
+#define to_gadget_string(str_item)\
+container_of(str_item, struct gadget_string, item)
+
 /*-------------------------------------------------------------------------*/
 
 /* utility to simplify managing config descriptors */