|# modprobe dummy_hcd num=2 |# find /sys/kernel/config/ -ls | 557 0 drwxr-xr-x 3 root root 0 Nov 29 17:26 /sys/kernel/config/ | 558 0 drwxr-xr-x 5 root root 0 Nov 29 17:26 /sys/kernel/config/usb_gadget | 561 0 drwxr-xr-x 4 root root 0 Nov 29 17:26 /sys/kernel/config/usb_gadget/udcs | 569 0 drwxr-xr-x 2 root root 0 Nov 29 17:26 /sys/kernel/config/usb_gadget/udcs/dummy_udc.1 | 568 0 drwxr-xr-x 2 root root 0 Nov 29 17:26 /sys/kernel/config/usb_gadget/udcs/dummy_udc.0 | 560 0 drwxr-xr-x 2 root root 0 Nov 29 17:26 /sys/kernel/config/usb_gadget/gadgets | # lsmod | Module Size Used by | dummy_hcd 20287 0 | udc 10219 1 dummy_hcd |# mkdir /sys/kernel/config/usb_gadget/gadgets/oha |# cd /sys/kernel/config/usb_gadget/gadgets/oha |# mkdir configs/def.1 |# mkdir configs/def.2 |# mkdir functions/acm.ttyS1 |# mkdir strings/0x1 | mkdir: cannot create directory `strings/0x1': Invalid argument |# mkdir strings/0x409 |# mkdir strings/1033 | mkdir: cannot create directory `strings/1033': File exists |# mkdir strings/1032 |# find . -ls | 8555 0 drwxr-xr-x 5 root root 0 Dec 10 19:29 . | 8558 0 drwxr-xr-x 4 root root 0 Dec 10 19:30 ./strings | 8565 0 drwxr-xr-x 2 root root 0 Dec 10 19:30 ./strings/1032 | 1948 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./strings/1032/serialnumber | 1949 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./strings/1032/product | 1950 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./strings/1032/manufacturer | 949 0 drwxr-xr-x 2 root root 0 Dec 10 19:30 ./strings/0x409 | 1951 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./strings/0x409/serialnumber | 1952 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./strings/0x409/product | 1953 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./strings/0x409/manufacturer | 8557 0 drwxr-xr-x 4 root root 0 Dec 10 19:29 ./configs | 8559 0 drwxr-xr-x 3 root root 0 Dec 10 19:29 ./configs/def.2 | 8560 0 drwxr-xr-x 2 root root 0 Dec 10 19:29 ./configs/def.2/strings | 1954 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./configs/def.2/bmAttributes | 1955 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./configs/def.2/MaxPower | 9253 0 drwxr-xr-x 3 root root 0 Dec 10 19:29 ./configs/def.1 | 9254 0 drwxr-xr-x 2 root root 0 Dec 10 19:29 ./configs/def.1/strings | 1956 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./configs/def.1/bmAttributes | 1957 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./configs/def.1/MaxPower | 8556 0 drwxr-xr-x 3 root root 0 Dec 10 19:29 ./functions | 9256 0 drwxr-xr-x 2 root root 0 Dec 10 19:29 ./functions/acm.ttyS1 | 1958 0 -r--r--r-- 1 root root 4096 Dec 10 19:31 ./functions/acm.ttyS1/port_num | 1959 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./bcdUSB | 1960 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./idProduct | 1961 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./idVendor | 1962 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./bMaxPacketSize0 | 1963 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./bDeviceProtocol | 1964 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./bDeviceSubClass | 1965 0 -rw-r--r-- 1 root root 4096 Dec 10 19:31 ./bDeviceClass |# mkdir default.0 |mkdir: cannot create directory `default.0': Invalid argument |oha# mkdir another.2 |mkdir: cannot create directory `another.1': File exists v4…v5 - string rework. This will add a strings folder incl. language code like strings/409/manufacturer as suggested by Alan. - rebased ontop reworked functions.c which has usb_function_instance which is used prior after "mkdir acm.instance" and can be directly used for configuration via configfs. symlinking not complete but it is getting there. v3…v4 - moved functions from the root folde down to the gadget as suggested by Michał - configs have now their own configs folder as suggested by Michał. The folder is still name.bConfigurationValue where name becomes the sConfiguration. Is this usefull should we just stilc configs/bConfigurationValue/ ? - added configfs support to the ACM function. The port_num attribute is exported by f_acm. An argument has been added to the USB alloc function to distinguish between "old" (use facm_configure() to configure and configfs interface (expose a config_node). The port_num is currently a dumb counter. It will require some function re-work to make it work. v2…v3 - replaced one ifndef by ifdef as suggested by Micahał - strstr()/strchr() function_make as suggested by Micahł - replace [iSerialNumber|iProduct|iManufacturer] with [sSerialNumber|sProduct|sManufacturer] as suggested by Alan - added creation of config descriptors v1…v2 - moved gadgets from configfs' root directory into /udcs/ within our "usb_gadget" folder. Requested by Andrzej & Michał - use a dot as a delimiter between function's name and its instance's name as suggested by Michał - renamed all config_item_type, configfs_group_operations, make_group, drop_item as suggested by suggested by Andrzej to remain consisten within this file and within other configfs users - Since configfs.c and functions.c are now part of the udc-core module, the module itself is now called udc. Also added a tiny ifdef around init code becuase udc-core is subsys init and this is too early for configfs in the built-in case. In the module case, we can only have one init function. Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx> --- drivers/usb/gadget/Makefile | 5 +- drivers/usb/gadget/configfs.c | 702 +++++++++++++++++++++++++++++++++++ drivers/usb/gadget/f_acm.c | 58 +++ drivers/usb/gadget/udc-core.c | 24 ++ include/linux/usb/composite.h | 3 +- include/linux/usb/gadget.h | 5 + include/linux/usb/gadget_configfs.h | 109 ++++++ 7 files changed, 903 insertions(+), 3 deletions(-) create mode 100644 drivers/usb/gadget/configfs.c create mode 100644 include/linux/usb/gadget_configfs.h diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 97a13c3..8171093 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -3,10 +3,11 @@ # ccflags-$(CONFIG_USB_GADGET_DEBUG) := -DDEBUG -obj-$(CONFIG_USB_GADGET) += udc-core.o +obj-$(CONFIG_USB_GADGET) += udc.o +udc-y += udc-core.o configfs.o functions.o obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o libcomposite-y := usbstring.o config.o epautoconf.o -libcomposite-y += composite.o functions.o +libcomposite-y += composite.o obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o obj-$(CONFIG_USB_NET2272) += net2272.o obj-$(CONFIG_USB_NET2280) += net2280.o diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c new file mode 100644 index 0000000..8f251cb --- /dev/null +++ b/drivers/usb/gadget/configfs.c @@ -0,0 +1,702 @@ +#include <linux/configfs.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget_configfs.h> + +int check_user_usb_string(const char *name, + struct usb_gadget_strings *stringtab_dev) +{ + unsigned primary_lang; + unsigned sub_lang; + u16 num; + int ret; + + ret = kstrtou16(name, 0, &num); + if (ret) + return ret; + + primary_lang = num & 0x3ff; + sub_lang = num >> 10; + + /* simple sanity check for valid langid */ + switch (primary_lang) { + case 0: + case 0x62 ... 0xfe: + case 0x100 ... 0x3ff: + return -EINVAL; + } + if (!sub_lang) + return -EINVAL; + + stringtab_dev->language = num; + return 0; +} + +#define MAX_NAME_LEN 40 + +static struct config_item_type gadget_core_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_item_type udc_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group udc_group = { + .cg_item = { + .ci_namebuf = "udcs", + .ci_type = &udc_type, + }, +}; + +#define MAX_USB_STRING_LANGS 2 + +struct gadget_info { + struct config_group group; + struct config_group config_group; + struct config_group strings_group; + struct config_group *default_groups[4]; + + struct usb_device_descriptor device_desc; + struct usb_gadget_strings *strings[MAX_USB_STRING_LANGS]; + struct list_head string_list; + + struct usb_composite_driver driver; +#ifdef CONFIG_USB_OTG + struct usb_otg_descriptor otg; +#endif + struct usb_composite_driver composite; + struct usb_composite_dev cdev; +}; + +struct config_usb_cfg { + struct config_group group; + struct config_group strings_group; + struct config_group *default_groups[2]; + struct list_head string_list; + struct usb_configuration c; +}; + +struct gadget_strings { + struct usb_gadget_strings stringtab_dev; + struct usb_string stringdev[USB_GADGET_FIRST_AVAIL_IDX]; + char *manufacturer; + char *product; + char *serialnumber; + + struct config_group group; + struct list_head list; +}; + +struct gadget_config_name { + struct usb_gadget_strings stringtab_dev; + struct usb_string stringdev[USB_GADGET_FIRST_AVAIL_IDX]; + char *configuration; + + struct config_group group; + struct list_head list; +}; + +static int usb_string_copy(const char *s, char **s_copy) +{ + int ret; + char *str; + char *copy = *s_copy; + ret = strlen(s); + if (ret > 126) + return -EOVERFLOW; + + str = kstrdup(s, GFP_KERNEL); + if (!str) + return -ENOMEM; + if (str[ret - 1] == '\n') + str[ret - 1] = '\0'; + kfree(copy); + *s_copy = str; + return 0; +} + +CONFIGFS_ATTR_STRUCT(gadget_info); +CONFIGFS_ATTR_STRUCT(config_usb_cfg); + +#define GI_DEVICE_DESC_ITEM_ATTR(name) \ + static struct gadget_info_attribute gadget_device_desc_##name = \ + __CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \ + gadget_dev_desc_##name##_show, \ + gadget_dev_desc_##name##_store) + +#define GI_DEVICE_DESC_SIMPLE_R_u8(__name) \ + static ssize_t gadget_dev_desc_##__name##_show(struct gadget_info *gi, char *page) \ +{ \ + return sprintf(page, "0x%02x\n", gi->device_desc.__name); \ +} + +#define GI_DEVICE_DESC_SIMPLE_R_u16(__name) \ + static ssize_t gadget_dev_desc_##__name##_show(struct gadget_info *gi, char *page) \ +{ \ + return sprintf(page, "0x%04x\n", le16_to_cpup(&gi->device_desc.__name)); \ +} + + +#define GI_DEVICE_DESC_SIMPLE_W_u8(_name) \ + static ssize_t gadget_dev_desc_##_name##_store(struct gadget_info *gi, \ + const char *page, size_t len) \ +{ \ + u8 val; \ + int ret; \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + return ret; \ + gi->device_desc._name = val; \ + return len; \ +} + +#define GI_DEVICE_DESC_SIMPLE_W_u16(_name) \ + static ssize_t gadget_dev_desc_##_name##_store(struct gadget_info *gi, \ + const char *page, size_t len) \ +{ \ + u16 val; \ + int ret; \ + ret = kstrtou16(page, 0, &val); \ + if (ret) \ + return ret; \ + gi->device_desc._name = cpu_to_le16p(&val); \ + return len; \ +} + +#define GI_DEVICE_DESC_SIMPLE_RW(_name, _type) \ + GI_DEVICE_DESC_SIMPLE_R_##_type(_name) \ + GI_DEVICE_DESC_SIMPLE_W_##_type(_name) + +GI_DEVICE_DESC_SIMPLE_RW(bDeviceClass, u8); +GI_DEVICE_DESC_SIMPLE_RW(bDeviceSubClass, u8); +GI_DEVICE_DESC_SIMPLE_RW(bDeviceProtocol, u8); +GI_DEVICE_DESC_SIMPLE_RW(bMaxPacketSize0, u8); +GI_DEVICE_DESC_SIMPLE_RW(idVendor, u16); +GI_DEVICE_DESC_SIMPLE_RW(idProduct, u16); +GI_DEVICE_DESC_SIMPLE_R_u16(bcdUSB); + +static ssize_t gadget_dev_desc_bcdUSB_store(struct gadget_info *gi, + const char *page, size_t len) +{ + u16 bcdUSB; + int ret; + + ret = kstrtou16(page, 0, &bcdUSB); + if (ret) + return ret; + if ((bcdUSB & 0xf) > 9) + return -EINVAL; + if (((bcdUSB >> 4) & 0xf) > 9) + return -EINVAL; + if (((bcdUSB >> 8) & 0xf) > 9) + return -EINVAL; + if (((bcdUSB >> 12) & 0xf) > 9) + return -EINVAL; + + gi->device_desc.bcdUSB = cpu_to_le16(bcdUSB); + return len; +} + +GI_DEVICE_DESC_ITEM_ATTR(bDeviceClass); +GI_DEVICE_DESC_ITEM_ATTR(bDeviceSubClass); +GI_DEVICE_DESC_ITEM_ATTR(bDeviceProtocol); +GI_DEVICE_DESC_ITEM_ATTR(bMaxPacketSize0); +GI_DEVICE_DESC_ITEM_ATTR(idVendor); +GI_DEVICE_DESC_ITEM_ATTR(idProduct); +GI_DEVICE_DESC_ITEM_ATTR(bcdUSB); + +static struct configfs_attribute *gadget_root_attrs[] = { + &gadget_device_desc_bDeviceClass.attr, + &gadget_device_desc_bDeviceSubClass.attr, + &gadget_device_desc_bDeviceProtocol.attr, + &gadget_device_desc_bMaxPacketSize0.attr, + &gadget_device_desc_idVendor.attr, + &gadget_device_desc_idProduct.attr, + &gadget_device_desc_bcdUSB.attr, + NULL, +}; + +static inline struct gadget_info *to_gadget_info(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_info, group); +} + +static inline struct gadget_strings *to_gadget_strings(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_strings, group); +} + +static inline struct gadget_config_name *to_gadget_config_name(struct config_item *item) +{ + return container_of(to_config_group(item), struct gadget_config_name, group); +} + +static inline struct config_usb_cfg *to_config_usb_cfg(struct config_item *item) +{ + return container_of(to_config_group(item), struct config_usb_cfg, + group); +} + +static void gadget_info_attr_release(struct config_item *item) +{ + struct gadget_info *gi = to_gadget_info(item); + + //WARN_ON(!list_empty(&gi->cdev.configs)); + + kfree(gi); +} + +CONFIGFS_ATTR_OPS(gadget_info); + +static struct configfs_item_operations gadget_root_item_ops = { + .release = gadget_info_attr_release, + .show_attribute = gadget_info_attr_show, + .store_attribute = gadget_info_attr_store, +}; + +static void gadget_config_attr_release(struct config_item *item) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(item); + + list_del(&cfg->c.list); + kfree(cfg); +} + +static int config_usb_cfg_link( + struct config_item *usb_cfg_ci, + struct config_item *usb_func_ci) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci); + struct config_group *group = to_config_group(usb_func_ci); + struct usb_function_instance *fi = container_of(group, + struct usb_function_instance, group); + struct usb_function *f; + + f = usb_get_function(fi); + usb_put_function(f); + pr_err("%s() created a valid function? %d for cfg %p\n", __func__, + !IS_ERR(f), cfg); + return -EINVAL; +} + +static int config_usb_cfg_unlink( + struct config_item *usb_cfg_ci, + struct config_item *usb_func_ci) +{ + struct config_usb_cfg *cfg = to_config_usb_cfg(usb_cfg_ci); + struct config_group *group = to_config_group(usb_func_ci); + struct usb_function_instance *fi = container_of(group, + struct usb_function_instance, group); + /* usb_put_function(f) */ + return 0; +} + +CONFIGFS_ATTR_OPS(config_usb_cfg); + +static struct configfs_item_operations gadget_config_item_ops = { + .release = gadget_config_attr_release, + .show_attribute = config_usb_cfg_attr_show, + .store_attribute = config_usb_cfg_attr_store, + .allow_link = config_usb_cfg_link, + .drop_link = config_usb_cfg_unlink, +}; + + +static ssize_t gadget_config_desc_MaxPower_show(struct config_usb_cfg *cfg, char *page) +{ + return sprintf(page, "%u\n", cfg->c.MaxPower); +} + +static ssize_t gadget_config_desc_MaxPower_store(struct config_usb_cfg *cfg, + const char *page, size_t len) +{ + u16 val; + int ret; + ret = kstrtou16(page, 0, &val); + if (ret) + return ret; + if (DIV_ROUND_UP(val, 8) > 0xff) + return -ERANGE; + cfg->c.MaxPower = val; + return len; +} + +static ssize_t gadget_config_desc_bmAttributes_show(struct config_usb_cfg *cfg, char *page) +{ + return sprintf(page, "0x%02x\n", cfg->c.bmAttributes); +} + +static ssize_t gadget_config_desc_bmAttributes_store(struct config_usb_cfg *cfg, + const char *page, size_t len) +{ + u8 val; + int ret; + ret = kstrtou8(page, 0, &val); + if (ret) + return ret; + if (!(val & USB_CONFIG_ATT_ONE)) + return -EINVAL; + if (val & ~(USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER | + USB_CONFIG_ATT_WAKEUP)) + return -EINVAL; + cfg->c.bmAttributes = val; + return len; +} + +#define CFG_CONFIG_DESC_ITEM_ATTR(name) \ + static struct config_usb_cfg_attribute gadget_usb_cfg_##name = \ + __CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \ + gadget_config_desc_##name##_show, \ + gadget_config_desc_##name##_store) + +CFG_CONFIG_DESC_ITEM_ATTR(MaxPower); +CFG_CONFIG_DESC_ITEM_ATTR(bmAttributes); + +static struct configfs_attribute *gadget_config_attrs[] = { + &gadget_usb_cfg_MaxPower.attr, + &gadget_usb_cfg_bmAttributes.attr, + NULL, +}; + +static struct config_item_type gadget_config_type = { + .ct_item_ops = &gadget_config_item_ops, + .ct_attrs = gadget_config_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item_type gadget_root_type = { + .ct_item_ops = &gadget_root_item_ops, + .ct_attrs = gadget_root_attrs, + .ct_owner = THIS_MODULE, +}; + +static void composite_init_dev(struct usb_composite_dev *cdev) +{ + spin_lock_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->configs); +} + +static struct config_group *function_make( + struct config_group *group, + const char *name) +{ + struct usb_function_instance *fi; + char buf[MAX_NAME_LEN]; + char *func_name; + char *instance_name; + int ret; + + ret = snprintf(buf, MAX_NAME_LEN, "%s", name); + if (ret >= MAX_NAME_LEN) + ERR_PTR(-ENAMETOOLONG); + + func_name = buf; + instance_name = strchr(func_name, '.'); + if (!instance_name) { + pr_err("Unable to locate . in FUNC.INSTANCE\n"); + return ERR_PTR(-EINVAL); + } + *instance_name = '\0'; + instance_name++; + + fi = usb_get_function_instance(func_name); + if (IS_ERR(fi)) + return ERR_PTR(PTR_ERR(fi)); + + return &fi->group; +} + +static void function_drop( + struct config_group *group, + struct config_item *item) +{ + /* XXX release */ + config_item_put(item); +} + +static struct configfs_group_operations functions_ops = { + .make_group = &function_make, + .drop_item = &function_drop, +}; + +static struct config_item_type functions_type = { + .ct_group_ops = &functions_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group func_group = { + .cg_item = { + .ci_namebuf = "functions", + .ci_type = &functions_type, + }, +}; + +CONFIGFS_ATTR_STRUCT(gadget_config_name); +GS_STRINGS_RW(gadget_config_name, configuration); + +static struct configfs_attribute *gadget_config_name_langid_attrs[] = { + &gadget_config_name_configuration.attr, + NULL, +}; + +static void gadget_config_name_attr_release(struct config_item *item) +{ + struct gadget_config_name *cn = to_gadget_config_name(item); + + kfree(cn->configuration); + + list_del(&cn->list); + kfree(cn); +} + +USB_CONFIG_STRING_RW_OPS(gadget_config_name); +USB_CONFIG_STRINGS_LANG(gadget_config_name, config_usb_cfg); + +static struct config_group *config_desc_make( + struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + struct usb_configuration *c; + struct config_usb_cfg *cfg; + char buf[MAX_NAME_LEN]; + char *num_str; + u8 num; + int ret; + + gi = container_of(group, struct gadget_info, config_group); + ret = snprintf(buf, MAX_NAME_LEN, "%s", name); + if (ret >= MAX_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + num_str = strchr(buf, '.'); + if (!num_str) { + pr_err("Unable to locate . in name.bConfigurationValue\n"); + return ERR_PTR(-EINVAL); + } + + *num_str = '\0'; + num_str++; + + if (!strlen(buf)) + return ERR_PTR(-EINVAL); + + ret = kstrtou8(num_str, 0, &num); + if (ret) + return ERR_PTR(ret); + + if (!num) + return ERR_PTR(-EINVAL); + + list_for_each_entry(c, &gi->cdev.configs, list) { + if (c->bConfigurationValue == num) + return ERR_PTR(-EEXIST); + } + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return ERR_PTR(-ENOMEM); + cfg->c.label = "configfs-configuration"; + cfg->c.bConfigurationValue = num; + cfg->c.MaxPower = CONFIG_USB_GADGET_VBUS_DRAW; + cfg->c.bmAttributes = USB_CONFIG_ATT_ONE; + INIT_LIST_HEAD(&cfg->string_list); + + cfg->group.default_groups = cfg->default_groups; + cfg->default_groups[0] = &cfg->strings_group; + + config_group_init_type_name(&cfg->group, "", + &gadget_config_type); + config_group_init_type_name(&cfg->strings_group, "strings", + &gadget_config_name_strings_type); + + list_add_tail(&cfg->c.list, &gi->cdev.configs); + + return &cfg->group; +} + +static void config_desc_drop( + struct config_group *group, + struct config_item *item) +{ + config_item_put(item); +} + +static struct configfs_group_operations config_desc_ops = { + .make_group = &config_desc_make, + .drop_item = &config_desc_drop, +}; + +static struct config_item_type config_desc_type = { + .ct_group_ops = &config_desc_ops, + .ct_owner = THIS_MODULE, +}; + +CONFIGFS_ATTR_STRUCT(gadget_strings); +GS_STRINGS_RW(gadget_strings, manufacturer); +GS_STRINGS_RW(gadget_strings, product); +GS_STRINGS_RW(gadget_strings, serialnumber); + +static struct configfs_attribute *gadget_strings_langid_attrs[] = { + &gadget_strings_manufacturer.attr, + &gadget_strings_product.attr, + &gadget_strings_serialnumber.attr, + NULL, +}; + +static void gadget_strings_attr_release(struct config_item *item) +{ + struct gadget_strings *gs = to_gadget_strings(item); + + kfree(gs->manufacturer); + kfree(gs->product); + kfree(gs->serialnumber); + + list_del(&gs->list); + kfree(gs); +} + +USB_CONFIG_STRING_RW_OPS(gadget_strings); +USB_CONFIG_STRINGS_LANG(gadget_strings, gadget_info); + +static struct config_group *gadgets_make( + struct config_group *group, + const char *name) +{ + struct gadget_info *gi; + + gi = kzalloc(sizeof(*gi), GFP_KERNEL); + if (!gi) + return ERR_PTR(-ENOMEM); + + gi->group.default_groups = gi->default_groups; + gi->group.default_groups[0] = &func_group, + gi->group.default_groups[1] = &gi->config_group; + gi->group.default_groups[2] = &gi->strings_group; + + config_group_init_type_name(&gi->config_group, "configs", + &config_desc_type); + config_group_init_type_name(&gi->strings_group, "strings", + &gadget_strings_strings_type); + + gi->device_desc.bLength = USB_DT_DEVICE_SIZE; + gi->device_desc.bDescriptorType = USB_DT_DEVICE; + + gi->driver.dev = &gi->device_desc; + gi->driver.bind = NULL; + gi->driver.unbind = NULL; + gi->driver.suspend = NULL; + gi->driver.resume = NULL; + gi->driver.max_speed = USB_SPEED_SUPER; + + INIT_LIST_HEAD(&gi->string_list); + composite_init_dev(&gi->cdev); + /* gi->composite.gadget_driver = composite_driver_template; */ + +#ifdef CONFIG_USB_OTG + gi->otg.bLength = sizeof(struct usb_otg_descriptor); + gi->otg.bDescriptorType = USB_DT_OTG; + gi->otg.bmAttributes = USB_OTG_SRP | USB_OTG_HNP; +#endif + + config_group_init_type_name(&gi->group, "", + &gadget_root_type); + return &gi->group; +} + +static void gadgets_drop(struct config_group *group, struct config_item *item) +{ + config_item_put(item); +} + +static struct configfs_group_operations gadgets_ops = { + .make_group = &gadgets_make, + .drop_item = &gadgets_drop, +}; + +static struct config_item_type gadgets_type = { + .ct_group_ops = &gadgets_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group gadget_group = { + .cg_item = { + .ci_namebuf = "gadgets", + .ci_type = &gadgets_type, + }, +}; + +static struct config_group *root_groups[] = { + &gadget_group, + &udc_group, + NULL +}; + +static struct configfs_subsystem gadget_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "usb_gadget", + .ci_type = &gadget_core_type, + }, + .default_groups = root_groups, + }, + .su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex), +}; + +struct udc_configfs { + struct config_group group; +}; + +struct config_group *udc_add_configfs(struct device *dev) +{ + struct udc_configfs *udc_cfs; + struct config_group *group; + int ret; + + udc_cfs = kzalloc(sizeof(struct udc_configfs), GFP_KERNEL); + if (!udc_cfs) + return ERR_PTR(-ENOMEM); + + group = &udc_cfs->group; + config_group_init_type_name(group, + kobject_name(&dev->kobj), + &gadget_core_type); + ret = configfs_create_group(&udc_group, group); + if (ret) + goto err; + return group; +err: + kfree(udc_cfs); + return ERR_PTR(ret); +} + +void udc_del_configfs(struct config_group *group) +{ + struct udc_configfs *udc_cfs; + + udc_cfs = container_of(group, struct udc_configfs, group); + + configfs_remove_group(group); + kfree(udc_cfs); +} + +int __init gadget_cfs_init(void) +{ + int ret; + + config_group_init(&gadget_subsys.su_group); + config_group_init(&func_group); + config_group_init(&gadget_group); + config_group_init(&udc_group); + + ret = configfs_register_subsystem(&gadget_subsys); + return ret; +} + +void __exit gadget_cfs_exit(void) +{ + configfs_unregister_subsystem(&gadget_subsys); +} diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index 1a82ef7..6534ebe 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -760,6 +760,61 @@ static void acm_free_func(struct usb_function *f) acm_unbind(NULL, f); } +static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_serial_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_serial_opts); +static ssize_t f_acm_attr_show(struct config_item *item, + struct configfs_attribute *attr, + char *page) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + struct f_serial_opts_attribute *f_serial_opts_attr = + container_of(attr, struct f_serial_opts_attribute, attr); + ssize_t ret = 0; + + if (f_serial_opts_attr->show) + ret = f_serial_opts_attr->show(opts, page); + return ret; +} + +static void acm_attr_release(struct config_item *item) +{ + struct f_serial_opts *opts = to_f_serial_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations acm_item_ops = { + .release = acm_attr_release, + .show_attribute = f_acm_attr_show, +}; + +static ssize_t f_acm_port_num_show(struct f_serial_opts *opts, char *page) +{ + return sprintf(page, "%u\n", opts->port_num); +} + +static struct f_serial_opts_attribute f_acm_port_num = + __CONFIGFS_ATTR_RO(port_num, f_acm_port_num_show); + + +static struct configfs_attribute *acm_attrs[] = { + &f_acm_port_num.attr, + NULL, +}; + +static struct config_item_type acm_func_type = { + .ct_item_ops = &acm_item_ops, + .ct_attrs = acm_attrs, + .ct_owner = THIS_MODULE, +}; + +static int dumb_port_counter; + static struct usb_function *acm_alloc_func(struct usb_function_instance *fi) { struct f_serial_opts *opts; @@ -792,6 +847,9 @@ static struct usb_function_instance *acm_alloc_instance(void) if (!opts) return ERR_PTR(-ENOMEM); opts->func_inst.free_func_inst = acm_free_instance; + config_group_init_type_name(&opts->func_inst.group, "", + &acm_func_type); + opts->port_num = dumb_port_counter++; return &opts->func_inst; } DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func); diff --git a/drivers/usb/gadget/udc-core.c b/drivers/usb/gadget/udc-core.c index 4d90a80..48ca094 100644 --- a/drivers/usb/gadget/udc-core.c +++ b/drivers/usb/gadget/udc-core.c @@ -26,6 +26,7 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include <linux/configfs.h> /** * struct usb_udc - describes one usb device controller @@ -42,6 +43,7 @@ struct usb_udc { struct usb_gadget *gadget; struct device dev; struct list_head list; + struct config_group *group; }; static struct class *udc_class; @@ -231,6 +233,7 @@ int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget) if (ret) goto err3; + udc->group = udc_add_configfs(&udc->dev); mutex_unlock(&udc_lock); return 0; @@ -305,6 +308,7 @@ void usb_del_gadget_udc(struct usb_gadget *gadget) usb_gadget_remove_driver(udc); kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); + udc_del_configfs(udc->group); device_unregister(&udc->dev); } EXPORT_SYMBOL_GPL(usb_del_gadget_udc); @@ -504,6 +508,8 @@ static int usb_udc_uevent(struct device *dev, struct kobj_uevent_env *env) static int __init usb_udc_init(void) { + int ret __maybe_unused; + udc_class = class_create(THIS_MODULE, "udc"); if (IS_ERR(udc_class)) { pr_err("failed to create udc class --> %ld\n", @@ -512,12 +518,30 @@ static int __init usb_udc_init(void) } udc_class->dev_uevent = usb_udc_uevent; +#ifdef MODULE + ret = gadget_cfs_init(); + if (ret) + class_destroy(udc_class); + return ret; +#else return 0; +#endif } subsys_initcall(usb_udc_init); +#ifndef MODULE +static int __init usb_udc_init_mod(void) +{ + if (IS_ERR(udc_class)) + return PTR_ERR(udc_class); + return gadget_cfs_init(); +} +module_init(usb_udc_init_mod); +#endif + static void __exit usb_udc_exit(void) { + gadget_cfs_exit(); class_destroy(udc_class); } module_exit(usb_udc_exit); diff --git a/include/linux/usb/composite.h b/include/linux/usb/composite.h index dd1db1b..7a7eed5 100644 --- a/include/linux/usb/composite.h +++ b/include/linux/usb/composite.h @@ -39,6 +39,7 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/log2.h> +#include <linux/configfs.h> /* * USB function drivers should return USB_GADGET_DELAYED_STATUS if they @@ -120,7 +121,6 @@ struct usb_configuration; * two or more distinct instances within the same configuration, providing * several independent logical data links to a USB host. */ - struct usb_function { const char *name; struct usb_gadget_strings **strings; @@ -447,6 +447,7 @@ struct usb_function_driver { }; struct usb_function_instance { + struct config_group group; struct usb_function_driver *fd; void (*free_func_inst)(struct usb_function_instance *inst); }; diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 0af6569..6f5fe20 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -883,6 +883,11 @@ extern void usb_del_gadget_udc(struct usb_gadget *gadget); /*-------------------------------------------------------------------------*/ +struct config_group *udc_add_configfs(struct device *dev); +void udc_del_configfs(struct config_group *group); +int gadget_cfs_init(void); +void __exit gadget_cfs_exit(void); + /* utility to simplify dealing with string descriptors */ /** diff --git a/include/linux/usb/gadget_configfs.h b/include/linux/usb/gadget_configfs.h new file mode 100644 index 0000000..efbbadf --- /dev/null +++ b/include/linux/usb/gadget_configfs.h @@ -0,0 +1,109 @@ +#ifndef __GADGET_CONFIGFS__ +#define __GADGET_CONFIGFS__ + +#include <linux/configfs.h> + +int check_user_usb_string(const char *name, + struct usb_gadget_strings *stringtab_dev); + +#define GS_STRINGS_W(__struct, __name) \ + static ssize_t __struct##_##__name##_store(struct __struct *gs, \ + const char *page, size_t len) \ +{ \ + int ret; \ + \ + ret = usb_string_copy(page, &gs->__name); \ + if (ret) \ + return ret; \ + return len; \ +} + +#define GS_STRINGS_R(__struct, __name) \ + static ssize_t __struct##_##__name##_show(struct __struct *gs, char *page) \ +{ \ + return sprintf(page, "%s\n", gs->__name); \ +} + +#define GS_STRING_ITEM_ATTR(struct_name, name) \ + static struct struct_name##_attribute struct_name##_##name = \ + __CONFIGFS_ATTR(name, S_IRUGO | S_IWUSR, \ + struct_name##_##name##_show, \ + struct_name##_##name##_store) + +#define GS_STRINGS_RW(struct_name, _name) \ + GS_STRINGS_R(struct_name, _name) \ + GS_STRINGS_W(struct_name, _name) \ + GS_STRING_ITEM_ATTR(struct_name, _name) + +#define USB_CONFIG_STRING_RW_OPS(struct_in) \ + CONFIGFS_ATTR_OPS(struct_in); \ + \ +static struct configfs_item_operations struct_in##_langid_item_ops = { \ + .release = struct_in##_attr_release, \ + .show_attribute = struct_in##_attr_show, \ + .store_attribute = struct_in##_attr_store, \ +}; \ + \ +static struct config_item_type struct_in##_langid_type = { \ + .ct_item_ops = &struct_in##_langid_item_ops, \ + .ct_attrs = struct_in##_langid_attrs, \ + .ct_owner = THIS_MODULE, \ +} + +#define USB_CONFIG_STRINGS_LANG(struct_in, struct_member) \ + static struct config_group *struct_in##_strings_make( \ + struct config_group *group, \ + const char *name) \ + { \ + struct struct_member *gi; \ + struct struct_in *gs; \ + struct struct_in *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, "", \ + &struct_in##_langid_type); \ + \ + gi = container_of(group, struct struct_member, 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); \ + return &new->group; \ +err: \ + kfree(new); \ + return ERR_PTR(ret); \ +} \ + \ +static void struct_in##_strings_drop( \ + struct config_group *group, \ + struct config_item *item) \ +{ \ + config_item_put(item); \ +} \ + \ +static struct configfs_group_operations struct_in##_strings_ops = { \ + .make_group = &struct_in##_strings_make, \ + .drop_item = &struct_in##_strings_drop, \ +}; \ + \ +static struct config_item_type struct_in##_strings_type = { \ + .ct_group_ops = &struct_in##_strings_ops, \ + .ct_owner = THIS_MODULE, \ +} + +#endif -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html