Commit 15a7cf8c authored by Daniel Scally's avatar Daniel Scally Committed by Greg Kroah-Hartman
Browse files

usb: gadget: configfs: Support arbitrary string descriptors



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: default avatarDaniel Scally <dan.scally@ideasonboard.com>
Link: https://lore.kernel.org/r/20230206161802.892954-7-dan.scally@ideasonboard.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 6e2a512d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
------------------------------

+170 −2
Original line number Diff line number Diff line
@@ -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)
+11 −0
Original line number Diff line number Diff line
@@ -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 */