mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 16:56:26 +00:00
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: 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>
This commit is contained in:
parent
6e2a512d95
commit
15a7cf8caa
@ -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
|
||||
------------------------------
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user