Demonstrate a USB gadget configured entirely through configfs. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- Documentation/usb/ufg.txt | 2 + drivers/usb/gadget/Kconfig | 13 + drivers/usb/gadget/Makefile | 5 +- drivers/usb/gadget/udc-core.c | 27 +- drivers/usb/gadget/usb_functions.c | 1142 ++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/usb_functions.h | 188 ++++++ include/linux/usb/gadget.h | 5 + 7 files changed, 1379 insertions(+), 3 deletions(-) create mode 100644 Documentation/usb/ufg.txt create mode 100644 drivers/usb/gadget/usb_functions.c create mode 100644 drivers/usb/gadget/usb_functions.h diff --git a/Documentation/usb/ufg.txt b/Documentation/usb/ufg.txt new file mode 100644 index 0000000..c4691e0 --- /dev/null +++ b/Documentation/usb/ufg.txt @@ -0,0 +1,2 @@ +USB Functions Gadget + diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 14625fd..80ab9a5 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -15,6 +15,7 @@ menuconfig USB_GADGET tristate "USB Gadget Support" + select USB_LIBCOMPOSITE select NLS help USB is a master/slave protocol, organized with one master @@ -521,6 +522,18 @@ choice # this first set of drivers all depend on bulk-capable hardware. +config USB_FG + tristate "USB Functions Gadget" + depends on CONFIGFS_FS + help + USB Functions Gadget is a device which aggregates a number of + USB functions. The gadget is composed by userspace through a + configfs interface, which enables specifying what USB + configurations the gadget is composed of, what USB functions + a USB configuration is composed of and enabling/disabling + the gadget. + For more information, see Documentation/usb/ufg.txt + config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" select USB_LIBCOMPOSITE diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index fef41f5..378296b 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 obj-$(CONFIG_USB_LIBCOMPOSITE) += libcomposite.o libcomposite-y := usbstring.o config.o epautoconf.o -libcomposite-y += composite.o functions.o +libcomposite-y += composite.o +udc-y += udc-core.o functions.o usb_functions.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/udc-core.c b/drivers/usb/gadget/udc-core.c index 4d90a80..5d5bf37 100644 --- a/drivers/usb/gadget/udc-core.c +++ b/drivers/usb/gadget/udc-core.c @@ -40,6 +40,7 @@ struct usb_udc { struct usb_gadget_driver *driver; struct usb_gadget *gadget; + struct config_group *group; struct device dev; struct list_head list; }; @@ -196,6 +197,7 @@ static void usb_udc_release(struct device *dev) } static const struct attribute_group *usb_udc_attr_groups[]; + /** * usb_add_gadget_udc - adds a new gadget to the udc class driver list * @parent: the parent device to this udc. Usually the controller @@ -231,6 +233,7 @@ int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget) if (ret) goto err3; + udc->group = udc_configfs_register(&udc->dev); mutex_unlock(&udc_lock); return 0; @@ -305,6 +308,7 @@ found: usb_gadget_remove_driver(udc); kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); + udc_configfs_unregister(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 rc = 0; + udc_class = class_create(THIS_MODULE, "udc"); if (IS_ERR(udc_class)) { pr_err("failed to create udc class --> %ld\n", @@ -512,12 +518,31 @@ static int __init usb_udc_init(void) } udc_class->dev_uevent = usb_udc_uevent; - return 0; + +#ifdef MODULE + rc = ufg_init(); + if (rc) + class_destroy(udc_class); +#endif + + return rc; } subsys_initcall(usb_udc_init); +#ifndef MODULE +static int __init usb_udc_init_module(void) +{ + if (IS_ERR(udc_class)) + return PTR_ERR(udc_class); + + return ufg_init(); +} +module_init(usb_udc_init_module); +#endif + static void __exit usb_udc_exit(void) { + ufg_cleanup(); class_destroy(udc_class); } module_exit(usb_udc_exit); diff --git a/drivers/usb/gadget/usb_functions.c b/drivers/usb/gadget/usb_functions.c new file mode 100644 index 0000000..7f52eda --- /dev/null +++ b/drivers/usb/gadget/usb_functions.c @@ -0,0 +1,1142 @@ +/* + * USB Functions Gadget + * + * Copyright (C) 2012 Samsung Electronics + * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/configfs.h> +#include <linux/dcache.h> +#include <linux/fs.h> + +#include "usb_functions.h" + +#define ufg_for_each_child(cursor, grp) \ + list_for_each_entry((cursor), &(grp)->cg_children, ci_entry) + +#define ufg_for_each_child_safe(cursor, tmp, grp) \ + list_for_each_entry_safe((cursor), (tmp), &(grp)->cg_children, ci_entry) + +#define UFG_SHOW_USHORT_ATTR(_name, _type, _member) \ +static ssize_t _type##_show_##_name(struct _type *grp, char *buf) \ +{ \ + return sprintf(buf, "%d\n", grp->_member); \ +} + +#define UFG_STORE_USHORT_ATTR(_name, _type, _member) \ +static ssize_t _type##_store_##_name(struct _type *grp, \ + const char *buf, size_t count) \ +{ \ + u16 tmp; \ + int res; \ + char *p = (char *)buf; \ + \ + res = kstrtou16(p, 16, &tmp); \ + if (res < 0) \ + return res; \ + grp->_member = (ushort)tmp; \ + grp->_member##_set = true; \ + \ + return count; \ +} + +#define UFG_SHOW_STR_ATTR(_name, _type, _member) \ +static ssize_t _type##_show_##_name(struct _type *grp, char *buf) \ +{ \ + return strlcpy(buf, grp->_member, UFG_STR_LEN); \ +} + +#define UFG_STORE_STR_ATTR(_name, _type, _member) \ +static ssize_t _type##_store_##_name(struct _type *grp, \ + const char *buf, size_t count) \ +{ \ + const char *end = strchr(buf, '\n'); \ + char *tmp; \ + \ + if (!end) \ + end = buf + count; \ + tmp = kzalloc(end - buf + 1, GFP_KERNEL); \ + if (!tmp) \ + return -ENOMEM; \ + kfree(grp->_member); \ + grp->_member = tmp; \ + memcpy(grp->_member, buf, end - buf); \ + grp->_member[end - buf] = 0; \ + \ + return count; \ +} + +#define UFG_ATTR_RW(_name, _attr, _type) \ +static struct _type##_attribute _type##_##_name = \ + __CONFIGFS_ATTR(_attr, S_IRUGO | S_IWUSR, _type##_show_##_name, \ + _type##_store_##_name) + +#define UFG_ATTR_RO(_name, _attr, _type) \ +static struct _type##_attribute _type##_##_name = \ + __CONFIGFS_ATTR(_attr, S_IRUGO | S_IWUSR, _type##_show_##_name, NULL) + +/* + * USB endpoint-level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/functions/<name>.#/interface#/endpoint# + * + */ + +UFG_SHOW_USHORT_ATTR(endpoint_address, ufg_endpoint_grp, endpoint_address); +UFG_SHOW_USHORT_ATTR(attributes, ufg_endpoint_grp, attributes); +UFG_SHOW_USHORT_ATTR(max_packet_sz, ufg_endpoint_grp, max_packet_sz); +UFG_SHOW_USHORT_ATTR(interval, ufg_endpoint_grp, interval); + +CONFIGFS_ATTR_STRUCT(ufg_endpoint_grp); + +UFG_ATTR_RO(endpoint_address, endpoint_address, ufg_endpoint_grp); +UFG_ATTR_RO(attributes, attributes, ufg_endpoint_grp); +UFG_ATTR_RO(max_packet_sz, max_packet_size, ufg_endpoint_grp); +UFG_ATTR_RO(interval, interval, ufg_endpoint_grp); + +static struct configfs_attribute *ufg_endpoint_grp_attrs[] = { + &ufg_endpoint_grp_endpoint_address.attr, + &ufg_endpoint_grp_attributes.attr, + &ufg_endpoint_grp_max_packet_sz.attr, + &ufg_endpoint_grp_interval.attr, + NULL, +}; + +CONFIGFS_ATTR_OPS(ufg_endpoint_grp); + +static void ufg_endpoint_grp_release(struct config_item *item) +{ + kfree(to_ufg_endpoint_grp(item)); +} + +static struct configfs_item_operations ufg_endpoint_grp_item_ops = { + .show_attribute = ufg_endpoint_grp_attr_show, + .release = ufg_endpoint_grp_release, +}; + +static struct config_item_type ufg_endpoint_grp_type = { + .ct_attrs = ufg_endpoint_grp_attrs, + .ct_item_ops = &ufg_endpoint_grp_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct ufg_endpoint_grp *make_ufg_endpoint(struct config_group *group, + const char *name) +{ + struct ufg_endpoint_grp *endpoint; + + endpoint = kzalloc(sizeof(*endpoint), GFP_KERNEL); + if (!endpoint) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&endpoint->group, name, + &ufg_endpoint_grp_type); + + return endpoint; +} + +/* + * USB interface-level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/functions/<name>.#/interface# + */ + +UFG_SHOW_USHORT_ATTR(interface_nr, ufg_interface_grp, interface_nr); +UFG_SHOW_USHORT_ATTR(alt_setting, ufg_interface_grp, alt_setting); +UFG_SHOW_USHORT_ATTR(num_endpoints, ufg_interface_grp, num_endpoints); +UFG_SHOW_USHORT_ATTR(intf_class, ufg_interface_grp, intf_class); +UFG_SHOW_USHORT_ATTR(intf_subclass, ufg_interface_grp, intf_subclass); +UFG_SHOW_USHORT_ATTR(intf_protocol, ufg_interface_grp, intf_protocol); + +CONFIGFS_ATTR_STRUCT(ufg_interface_grp); + +UFG_ATTR_RO(interface_nr, interface_number, ufg_interface_grp); +UFG_ATTR_RO(alt_setting, altsetting, ufg_interface_grp); +UFG_ATTR_RO(num_endpoints, n_endpoints, ufg_interface_grp); +UFG_ATTR_RO(intf_class, interface_class, ufg_interface_grp); +UFG_ATTR_RO(intf_subclass, interface_subclass, ufg_interface_grp); +UFG_ATTR_RO(intf_protocol, interface_protocol, ufg_interface_grp); + +static struct configfs_attribute *ufg_interface_grp_attrs[] = { + &ufg_interface_grp_interface_nr.attr, + &ufg_interface_grp_alt_setting.attr, + &ufg_interface_grp_num_endpoints.attr, + &ufg_interface_grp_intf_class.attr, + &ufg_interface_grp_intf_subclass.attr, + &ufg_interface_grp_intf_protocol.attr, + NULL, +}; + +CONFIGFS_ATTR_OPS(ufg_interface_grp); + +static void ufg_interface_grp_release(struct config_item *item) +{ + kfree(to_ufg_interface_grp(item)); +} + +static struct configfs_item_operations ufg_interface_grp_item_ops = { + .show_attribute = ufg_interface_grp_attr_show, + .release = ufg_interface_grp_release, +}; + +static struct config_item_type ufg_interface_grp_type = { + .ct_attrs = ufg_interface_grp_attrs, + .ct_item_ops = &ufg_interface_grp_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct ufg_interface_grp *make_ufg_interface(struct config_group *group, + const char *name) +{ + struct ufg_interface_grp *interface; + + interface = kzalloc(sizeof(*interface), GFP_KERNEL); + if (!interface) + return ERR_PTR(-ENOMEM); + + interface->type = UFG_INTERFACE; + config_group_init_type_name(&interface->group, name, + &ufg_interface_grp_type); + + return interface; +} + +/* + * USB function-level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/functions/<name>.# + */ + +static struct config_group *make_ufg_function(struct config_group *group, + const char *name) +{ + struct usb_function *f; + struct config_group *new; + struct ufg_function_grp *ufg_function_grp; + char name_buf[UFG_STR_LEN]; + char *func_name; + char *instance_name; + int rc; + + rc = snprintf(name_buf, UFG_STR_LEN, "%s", name); + if (rc >= UFG_STR_LEN) + return ERR_PTR(-ENAMETOOLONG); + + func_name = name_buf; + instance_name = strchr(func_name, '.'); + if (!instance_name) { + pr_err("name must be of <function>.<instance> form\n"); + return ERR_PTR(-EINVAL); + } + *instance_name = '\0'; + + f = usb_get_function(func_name); + + if (IS_ERR_OR_NULL(f)) + return ERR_PTR(-ENODEV); + + *instance_name = '.'; + new = f->make_group(group, func_name); + if (IS_ERR_OR_NULL(new)) { + usb_put_function(f); + + return new; + } + + ufg_function_grp = container_of(new, struct ufg_function_grp, group); + ufg_function_grp->f = f; + + return new; +} + +/* + * USB gadget's functions level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/functions + */ + +static struct configfs_group_operations ufg_functions_group_ops = { + .make_group = make_ufg_function, +}; + +static struct config_item_type ufg_functions_type = { + .ct_group_ops = &ufg_functions_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group ufg_functions_group = { + .cg_item = { + .ci_namebuf = "functions", + .ci_type = &ufg_functions_type, + }, +}; + +/* + * USB configuration's functions level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/configs/#/functions + */ + +static struct ufg_function_grp *ufg_check_link(struct config_item *src, + struct config_item *target) +{ + struct config_group *src_grp; + struct ufg_config_functions *src_fun_grp; + struct config_group *target_grp; + struct ufg_function_grp *target_fun_grp; + + src_grp = to_config_group(src); + if (!src_grp) + return ERR_PTR(-EPERM); + + src_fun_grp = container_of(src_grp, struct ufg_config_functions, group); + if (!src_fun_grp) + return ERR_PTR(-EPERM); + + target_grp = to_config_group(target); + if (!target_grp) + return ERR_PTR(-EPERM); + + if (!src->ci_parent || !src->ci_parent->ci_parent || + !src->ci_parent->ci_parent->ci_parent || + !target->ci_parent || !target->ci_parent->ci_parent) + return ERR_PTR(-EPERM); + + if (src->ci_parent->ci_parent->ci_parent != + target->ci_parent->ci_parent) + return ERR_PTR(-EPERM); + + target_fun_grp = to_ufg_function_grp(&target_grp->cg_item); + if (!target_fun_grp) + return ERR_PTR(-EPERM); + + if (target_fun_grp->used) + return ERR_PTR(-EBUSY); + + return target_fun_grp; +} + +static int ufg_config_functions_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_group *src_grp; + struct ufg_config_functions *src_fun_grp; + struct ufg_function_grp *target_fun_grp; + + target_fun_grp = ufg_check_link(src, target); + if (IS_ERR(target_fun_grp)) + return PTR_ERR(target_fun_grp); + + src_grp = to_config_group(src); + src_fun_grp = container_of(src_grp, struct ufg_config_functions, group); + + target_fun_grp->used = true; + list_add_tail(&target_fun_grp->entry, &src_fun_grp->list); + + return 0; +} + +static int ufg_config_functions_drop_link(struct config_item *src, + struct config_item *target) +{ + struct ufg_function_grp *target_fun_grp; + + target_fun_grp = ufg_check_link(src, target); + if (IS_ERR(target_fun_grp)) + return PTR_ERR(target_fun_grp); + + target_fun_grp->used = false; + list_del(&target_fun_grp->entry); + + return 0; +} + +static struct configfs_item_operations ufg_config_functions_item_ops = { + .allow_link = ufg_config_functions_allow_link, + .drop_link = ufg_config_functions_drop_link, +}; + +static struct config_item_type ufg_config_functions_type = { + .ct_item_ops = &ufg_config_functions_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct ufg_config_functions ufg_config_functions_group = { + .group = { + .cg_item = { + .ci_namebuf = "functions", + .ci_type = &ufg_config_functions_type, + }, + }, +}; + +/* + * USB configuration-level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/configs/# + */ + +UFG_SHOW_USHORT_ATTR(max_power, ufg_config_grp, max_power); +UFG_STORE_USHORT_ATTR(max_power, ufg_config_grp, max_power); + +UFG_SHOW_USHORT_ATTR(num_interfaces, ufg_config_grp, num_interfaces); + +UFG_SHOW_USHORT_ATTR(conf_number, ufg_config_grp, conf_number); + +CONFIGFS_ATTR_STRUCT(ufg_config_grp); + +UFG_ATTR_RW(max_power, maximum_power, ufg_config_grp); + +UFG_ATTR_RO(num_interfaces, number_of_interfaces, ufg_config_grp); + +UFG_ATTR_RO(conf_number, configuration_number, ufg_config_grp); + +static struct configfs_attribute *ufg_config_grp_attrs[] = { + &ufg_config_grp_max_power.attr, + &ufg_config_grp_num_interfaces.attr, + &ufg_config_grp_conf_number.attr, + NULL, +}; + +CONFIGFS_ATTR_OPS(ufg_config_grp); + +static void ufg_config_grp_release(struct config_item *item) +{ + kfree(to_ufg_config_grp(item)); +} + +static struct configfs_item_operations ufg_config_grp_item_ops = { + .show_attribute = ufg_config_grp_attr_show, + .store_attribute = ufg_config_grp_attr_store, + .release = ufg_config_grp_release, +}; + +static struct config_item_type ufg_config_grp_type = { + .ct_attrs = ufg_config_grp_attrs, + .ct_item_ops = &ufg_config_grp_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *ufg_config_default_groups[] = { + &ufg_config_functions_group.group, + NULL +}; + +static struct config_group *make_ufg_config(struct config_group *group, + const char *name) +{ + struct ufg_config_grp *config; + + config = kzalloc(sizeof(*config), GFP_KERNEL); + if (!config) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&config->group, name, &ufg_config_grp_type); + + INIT_LIST_HEAD(&ufg_config_functions_group.list); + config->group.default_groups = ufg_config_default_groups; + + return &config->group; +} + +/* + * USB gadget's configurations level configfs group + * + * /usb-function-gadget/gadgets/<gadget>/configs + */ + +static struct configfs_group_operations ufg_configs_group_ops = { + .make_group = make_ufg_config, +}; + +static struct config_item_type ufg_configs_type = { + .ct_group_ops = &ufg_configs_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group ufg_configs_group = { + .cg_item = { + .ci_namebuf = "configs", + .ci_type = &ufg_configs_type, + }, +}; + +/* + * USB gadget-level configfs group + * + * /usb-function-gadget/gadgets/<gadget> + */ + +UFG_SHOW_USHORT_ATTR(id_vendor, ufg_gadget_grp, idVendor); +UFG_STORE_USHORT_ATTR(id_vendor, ufg_gadget_grp, idVendor); + +UFG_SHOW_USHORT_ATTR(id_product, ufg_gadget_grp, idProduct); +UFG_STORE_USHORT_ATTR(id_product, ufg_gadget_grp, idProduct); + +UFG_SHOW_USHORT_ATTR(bcd_device, ufg_gadget_grp, bcdDevice); +UFG_STORE_USHORT_ATTR(bcd_device, ufg_gadget_grp, bcdDevice); + +UFG_SHOW_STR_ATTR(i_manufacturer, ufg_gadget_grp, iManufacturer); +UFG_STORE_STR_ATTR(i_manufacturer, ufg_gadget_grp, iManufacturer); + +UFG_SHOW_STR_ATTR(i_product, ufg_gadget_grp, iProduct); +UFG_STORE_STR_ATTR(i_product, ufg_gadget_grp, iProduct); + +UFG_SHOW_STR_ATTR(i_serial_number, ufg_gadget_grp, iSerialNumber); +UFG_STORE_STR_ATTR(i_serial_number, ufg_gadget_grp, iSerialNumber); + +CONFIGFS_ATTR_STRUCT(ufg_gadget_grp); + +UFG_ATTR_RW(id_vendor, idVendor, ufg_gadget_grp); +UFG_ATTR_RW(id_product, idProduct, ufg_gadget_grp); +UFG_ATTR_RW(bcd_device, bcdDevice, ufg_gadget_grp); +UFG_ATTR_RW(i_manufacturer, iManufacturer, ufg_gadget_grp); +UFG_ATTR_RW(i_product, iProduct, ufg_gadget_grp); +UFG_ATTR_RW(i_serial_number, iSerialNumber, ufg_gadget_grp); + +static struct configfs_attribute *ufg_gadget_grp_attrs[] = { + &ufg_gadget_grp_id_vendor.attr, + &ufg_gadget_grp_id_product.attr, + &ufg_gadget_grp_bcd_device.attr, + &ufg_gadget_grp_i_manufacturer.attr, + &ufg_gadget_grp_i_product.attr, + &ufg_gadget_grp_i_serial_number.attr, + NULL, +}; + +CONFIGFS_ATTR_OPS(ufg_gadget_grp); + +static void ufg_gadget_grp_release(struct config_item *item) +{ + struct ufg_gadget_grp *ufg_gadget_grp; + + ufg_gadget_grp = to_ufg_gadget_grp(item); + kfree(ufg_gadget_grp->iManufacturer); + kfree(ufg_gadget_grp->iProduct); + kfree(ufg_gadget_grp->iSerialNumber); + kfree(ufg_gadget_grp); +} + +static int ufg_gadget_ready(struct ufg_gadget_grp *g_grp); + +static int ufg_gadget_grp_allow_link(struct config_item *src, + struct config_item *target) +{ + struct ufg_gadget_grp *gadget_grp; + struct config_group *udc; + struct ufg_udc *udc_grp; + int ret; + + gadget_grp = to_ufg_gadget_grp(src); + if (!gadget_grp) + return -EPERM; + + if (gadget_grp->ready) + return -EBUSY; + + udc = to_config_group(target); + udc_grp = container_of(udc, struct ufg_udc, group); + if (!udc_grp) + return -EPERM; + + if (udc_grp->used) + return -EBUSY; + + mutex_lock(&gadget_grp->lock); + + gadget_grp->ready = 1; + udc_grp->used = 1; + + ret = ufg_gadget_ready(gadget_grp); + if (ret) { + gadget_grp->ready = 0; + udc_grp->used = 0; + + ret = -EBUSY; + goto end; + } + +end: + mutex_unlock(&gadget_grp->lock); + + return ret; +} + +static int ufg_gadget_grp_drop_link(struct config_item *src, + struct config_item *target) +{ + struct ufg_gadget_grp *gadget_grp; + struct config_group *udc; + struct ufg_udc *udc_grp; + int ret; + + gadget_grp = to_ufg_gadget_grp(src); + if (!gadget_grp) + return -EPERM; + + udc = to_config_group(target); + udc_grp = container_of(udc, struct ufg_udc, group); + if (!udc_grp) + return -EPERM; + + mutex_lock(&gadget_grp->lock); + + gadget_grp->ready = 0; + udc_grp->used = 0; + + ret = ufg_gadget_ready(gadget_grp); + + mutex_unlock(&gadget_grp->lock); + + return ret; +} + +static struct configfs_item_operations ufg_gadget_grp_item_ops = { + .show_attribute = ufg_gadget_grp_attr_show, + .store_attribute = ufg_gadget_grp_attr_store, + .release = ufg_gadget_grp_release, + .allow_link = ufg_gadget_grp_allow_link, + .drop_link = ufg_gadget_grp_drop_link, +}; + +static struct config_item_type ufg_gadget_grp_type = { + .ct_attrs = ufg_gadget_grp_attrs, + .ct_item_ops = &ufg_gadget_grp_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *ufg_gadget_default_groups[] = { + &ufg_configs_group, + &ufg_functions_group, + NULL +}; + +static struct config_group *make_ufg_gadget(struct config_group *group, + const char *name) +{ + struct ufg_gadget_grp *ufg_gadget_grp; + + ufg_gadget_grp = kzalloc(sizeof(*ufg_gadget_grp), GFP_KERNEL); + if (!ufg_gadget_grp) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&ufg_gadget_grp->group, name, + &ufg_gadget_grp_type); + + ufg_gadget_grp->group.default_groups = ufg_gadget_default_groups; + + mutex_init(&ufg_gadget_grp->lock); + + return &ufg_gadget_grp->group; +} + +/* + * USB gadgets level configfs group + * + * /usb-function-gadget/gadgets + */ + +static struct configfs_group_operations ufg_gadgets_group_ops = { + .make_group = make_ufg_gadget, +}; + +static struct config_item_type ufg_gadgets_type = { + .ct_group_ops = &ufg_gadgets_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group ufg_gadgets_group = { + .cg_item = { + .ci_namebuf = "gadgets", + .ci_type = &ufg_gadgets_type, + }, +}; + +/* + * USB udcs level configfs group + * + * /usb-function-gadget/udcs + */ + +static struct config_item_type ufg_udcs_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group ufg_udcs_group = { + .cg_item = { + .ci_namebuf = "udcs", + .ci_type = &ufg_udcs_type, + }, +}; + +static struct config_item_type ufg_udc_type = { + .ct_owner = THIS_MODULE, +}; + +struct config_group *udc_configfs_register(struct device *dev) +{ + struct ufg_udc *ufg_udc; + int ret; + + ufg_udc = kzalloc(sizeof(*ufg_udc), GFP_KERNEL); + if (!ufg_udc) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&ufg_udc->group, kobject_name(&dev->kobj), + &ufg_udc_type); + ret = configfs_create_group(&ufg_udcs_group, &ufg_udc->group); + if (ret) { + kfree(ufg_udc); + return ERR_PTR(ret); + } + return &ufg_udc->group; +} + +void udc_configfs_unregister(struct config_group *group) +{ + struct ufg_udc *ufg_udc; + + ufg_udc = container_of(group, struct ufg_udc, group); + + configfs_remove_group(group); + kfree(ufg_udc); +} + +/* + * configfs subsystem for ufg + * + * /usb-function-gadget + */ + +static struct ufg_subsys *to_ufg_subsys(struct config_item *item) +{ + return item ? container_of(to_configfs_subsystem(to_config_group(item)), + struct ufg_subsys, subsys) : NULL; +} + +static struct config_item_type ufg_subsys_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *ufg_root_groups[] = { + &ufg_gadgets_group, + &ufg_udcs_group, + NULL, +}; + +struct ufg_subsys ufg_subsystem = { + .subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "usb-function-gadget", + .ci_type = &ufg_subsys_type, + }, + .default_groups = ufg_root_groups, + }, + }, +}; + +/*------------------- USB composite handling code -----------------------*/ + +static struct usb_composite_overwrite coverwrite; + +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = "", + [USB_GADGET_SERIAL_IDX].s = "", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static void ufg_unbind_config(struct usb_configuration *c) +{ + kfree(c); +} + +static void ufg_rmdirs(struct ufg_gadget_grp *ufg_gadget_grp) +{ + struct config_item *ci; + struct config_group *grp; + + ci = config_group_find_item(&ufg_gadget_grp->group, "functions"); + if (!ci) + return; + grp = to_config_group(ci); + if (!grp) + return; + + /* functions' main groups */ + ufg_for_each_child(ci, grp) { + struct config_group *f_grp = to_config_group(ci); + struct config_item *i, *i_tmp; + + /* interfaces */ + ufg_for_each_child_safe(i, i_tmp, f_grp) { + struct config_group *i_grp = to_config_group(i); + struct config_item *e, *e_tmp; + struct ufg_grp_hdr *h = ufg_hdr(i_grp); + if (h->type != UFG_INTERFACE) + continue; + + /* endpoints */ + ufg_for_each_child_safe(e, e_tmp, i_grp) + configfs_remove_group(to_config_group(e)); + + configfs_remove_group(to_config_group(i)); + } + } +} + +static struct usb_descriptor_header **ufg_get_function_descriptors( + struct usb_configuration *u_cfg, struct ufg_dev *ufg_dev) +{ + struct usb_function *u_fn; + + /* last added function */ + u_fn = list_entry(u_cfg->functions.prev, struct usb_function, list); + if (!u_fn) + return NULL; + + switch (ufg_dev->cdev->gadget->speed) { + case USB_SPEED_SUPER: + if (gadget_is_superspeed(ufg_dev->cdev->gadget)) + return u_fn->ss_descriptors; + /* else: fall trough */ + case USB_SPEED_HIGH: + if (gadget_is_dualspeed(ufg_dev->cdev->gadget)) + return u_fn->hs_descriptors; + /* else: fall through */ + default: + return u_fn->fs_descriptors; + } + + return NULL; +} + +static struct config_group *ufg_process_interface_desc( + struct usb_descriptor_header *d, struct config_group *f_grp) +{ + struct usb_interface_descriptor *id = + (struct usb_interface_descriptor *)d; + struct ufg_interface_grp *new; + char *name_templ = "interface00"; + int rc; + + sprintf(name_templ, "interface%02x", id->bInterfaceNumber); + new = make_ufg_interface(f_grp, name_templ); + if (IS_ERR(new)) + return ERR_PTR(PTR_ERR(new)); + + rc = configfs_create_group(f_grp, &new->group); + if (rc) { + ufg_interface_grp_release(&new->group.cg_item); + + return ERR_PTR(rc); + } + + new->interface_nr = id->bInterfaceNumber; + new->alt_setting = id->bAlternateSetting; + new->num_endpoints = id->bNumEndpoints; + new->intf_class = id->bInterfaceClass; + new->intf_subclass = id->bInterfaceSubClass; + new->intf_protocol = id->bInterfaceProtocol; + + return &new->group; +} + +static struct config_group *ufg_process_endpoint_desc( + struct usb_descriptor_header *d, struct config_group *interface_grp) +{ + struct usb_endpoint_descriptor *ed = + (struct usb_endpoint_descriptor *)d; + struct ufg_endpoint_grp *new; + char *name_templ = "endpoint00"; + int rc; + + sprintf(name_templ, "endpoint%02x", ed->bEndpointAddress); + new = make_ufg_endpoint(interface_grp, name_templ); + if (IS_ERR(new)) + return ERR_PTR(PTR_ERR(new)); + + rc = configfs_create_group(interface_grp, &new->group); + if (rc) { + ufg_endpoint_grp_release(&new->group.cg_item); + + return ERR_PTR(rc); + } + + new->endpoint_address = ed->bEndpointAddress; + new->attributes = ed->bmAttributes; + new->max_packet_sz = ed->wMaxPacketSize; + new->interval = ed->bInterval; + + return &new->group; + +} + +static int ufg_add_f(struct ufg_dev *ufg_dev, struct usb_configuration *u_cfg, + struct config_item *f, ushort *num_interfaces) +{ + struct config_group *f_grp = to_config_group(f); + struct ufg_function_grp *function_grp; + struct usb_function *fn; + + function_grp = container_of(f_grp, struct ufg_function_grp, group); + fn = function_grp->f; + if (!fn) + return -ENODEV; + + if (fn->add_function) { + struct usb_descriptor_header **descriptors; + struct usb_descriptor_header **d; + struct config_group *i_grp = NULL; + struct config_group *e_grp = NULL; + int r; + + r = fn->add_function(u_cfg, fn, f, ufg_dev->cdev); + if (r) + return -EPERM; + + descriptors = ufg_get_function_descriptors(u_cfg, ufg_dev); + if (!descriptors) + return -1; + + for (d = descriptors; *d; d++) + switch ((*d)->bDescriptorType) { + case USB_DT_INTERFACE: + i_grp = ufg_process_interface_desc(*d, f_grp); + if (IS_ERR_OR_NULL(i_grp)) + return -1; + (*num_interfaces)++; + break; + case USB_DT_ENDPOINT: + e_grp = ufg_process_endpoint_desc(*d, i_grp); + if (IS_ERR_OR_NULL(e_grp)) + return -1; + break; + } + } + + return 0; +} + +static void ufg_fill_config_driver(struct usb_configuration *u_cfg, + struct ufg_config_grp *c_grp, int n) +{ + u_cfg->label = "ufg"; + u_cfg->unbind = ufg_unbind_config; + u_cfg->bConfigurationValue = c_grp->conf_number = n; + u_cfg->bmAttributes = USB_CONFIG_ATT_ONE | + USB_CONFIG_ATT_SELFPOWER; + u_cfg->bMaxPower = c_grp->max_power; +} + +static int ufg_bind(struct usb_composite_dev *cdev) +{ + struct ufg_dev *ufg_dev; + struct usb_gadget *gadget = cdev->gadget; + struct config_item *ci; + struct config_group *configs; + int status; + int n = 1; + + ufg_dev = container_of(cdev->driver, struct ufg_dev, ufg_driver); + ufg_dev->cdev = cdev; + status = usb_string_ids_tab(cdev, strings_dev); + if (status < 0) + return status; + + ufg_dev->ufg_device_desc.iManufacturer = + strings_dev[USB_GADGET_MANUFACTURER_IDX].id; + ufg_dev->ufg_device_desc.iProduct = + strings_dev[USB_GADGET_PRODUCT_IDX].id; + ufg_dev->ufg_device_desc.iSerialNumber = + strings_dev[USB_GADGET_SERIAL_IDX].id; + + /* + * Start disconnected. Userspace will connect the gadget once + * it is done configuring the functions. + */ + usb_gadget_disconnect(gadget); + + usb_gadget_set_selfpowered(gadget); + + usb_composite_overwrite_options(cdev, &coverwrite); + + ci = config_group_find_item(&ufg_dev->g_grp->group, "configs"); + if (!ci) + return -ENODEV; + configs = to_config_group(ci); + if (!configs) + return -ENODEV; + + /* configurations */ + ufg_for_each_child(ci, configs) { + struct ufg_config_grp *c_grp = to_ufg_config_grp(ci); + struct ufg_function_grp *function; + struct usb_configuration *u_cfg; + struct config_item *f; + struct config_group *f_grp; + struct ufg_config_functions *cfg_functions; + + u_cfg = kzalloc(sizeof(*u_cfg), GFP_KERNEL); + if (!u_cfg) + goto unbind; + + ufg_fill_config_driver(u_cfg, c_grp, n++); + status = usb_add_config_only(ufg_dev->cdev, u_cfg); + if (status) + goto unbind; + + f = config_group_find_item(&c_grp->group, "functions"); + if (!f) + goto unbind; + f_grp = to_config_group(f); + if (!f_grp) + goto unbind; + cfg_functions = container_of(f_grp, struct ufg_config_functions, + group); + if (!cfg_functions) + goto unbind; + + list_for_each_entry(function, &cfg_functions->list, entry) { + status = ufg_add_f(ufg_dev, u_cfg, + &function->group.cg_item, + &c_grp->num_interfaces); + if (status) + goto unbind; + } + } + + ufg_dev->ufg_device_desc.bNumConfigurations = n - 1; + return 0; + +unbind: + ufg_rmdirs(ufg_dev->g_grp); + return status; +} + +static int ufg_unbind(struct usb_composite_dev *cdev) +{ + struct ufg_dev *ufg_dev; + + ufg_dev = container_of(cdev->driver, struct ufg_dev, ufg_driver); + + if (!mutex_trylock(&ufg_dev->g_grp->lock)) + return 0; + + ufg_dev->g_grp->ready = false; + ufg_gadget_ready(ufg_dev->g_grp); + mutex_unlock(&ufg_dev->g_grp->lock); + + return 0; +} + +static void ufg_fill_ufg_driver(struct usb_composite_driver *ufgd, + struct ufg_gadget_grp *g_grp) +{ + struct usb_device_descriptor *desc; + + desc = &container_of(ufgd, struct ufg_dev, ufg_driver)->ufg_device_desc; + desc->bLength = sizeof(desc); /* TODO: *desc ? */ + desc->bDescriptorType = USB_DT_DEVICE; + desc->bcdUSB = cpu_to_le16(0x0200); + desc->bDeviceClass = USB_CLASS_PER_INTERFACE; + + ufgd->bind = ufg_bind; + ufgd->unbind = ufg_unbind; + ufgd->strings = dev_strings; + ufgd->name = "usb_function_gadget"; + ufgd->dev = desc; + ufgd->needs_serial = 1; + + if (g_grp->idVendor_set) + coverwrite.idVendor = g_grp->idVendor; + if (g_grp->idProduct_set) + coverwrite.idProduct = g_grp->idProduct; + if (g_grp->bcdDevice_set) + coverwrite.bcdDevice = g_grp->bcdDevice; + if (g_grp->iManufacturer) + coverwrite.manufacturer = g_grp->iManufacturer; + if (g_grp->iProduct) + coverwrite.product = g_grp->iProduct; + if (g_grp->iSerialNumber) + coverwrite.serial_number = g_grp->iSerialNumber; +} + +static int ufg_gadget_ready(struct ufg_gadget_grp *g_grp) +{ + struct ufg_dev *ufg_dev; + int r; + + if (!g_grp->ready) { + ufg_dev = g_grp->gadget_grp_data; + + ufg_rmdirs(g_grp); + usb_composite_unregister(&ufg_dev->ufg_driver); + kfree(ufg_dev); + return 0; + } + + ufg_dev = kzalloc(sizeof(*ufg_dev), GFP_KERNEL); + if (!ufg_dev) + return -ENOMEM; + + ufg_fill_ufg_driver(&ufg_dev->ufg_driver, g_grp); + g_grp->gadget_grp_data = ufg_dev; + ufg_dev->g_grp = g_grp; + r = usb_composite_probe(&ufg_dev->ufg_driver); + if (r) { + usb_composite_unregister(&ufg_dev->ufg_driver); + kfree(ufg_dev); + } + + return r; +} + +/*---------------------- general stuff ---------------------------*/ + +struct ufg_subsys *UFG_SUBSYSTEM; +EXPORT_SYMBOL(UFG_SUBSYSTEM); + +int __init ufg_init(void) +{ + config_group_init(&ufg_subsystem.subsys.su_group); + config_group_init(&ufg_config_functions_group.group); + config_group_init(&ufg_functions_group); + config_group_init(&ufg_configs_group); + config_group_init(&ufg_gadgets_group); + config_group_init(&ufg_udcs_group); + mutex_init(&ufg_subsystem.subsys.su_mutex); + UFG_SUBSYSTEM = &ufg_subsystem; + + return configfs_register_subsystem(&ufg_subsystem.subsys); +} + +void ufg_cleanup(void) +{ + configfs_unregister_subsystem(&ufg_subsystem.subsys); +} diff --git a/drivers/usb/gadget/usb_functions.h b/drivers/usb/gadget/usb_functions.h new file mode 100644 index 0000000..1548ede --- /dev/null +++ b/drivers/usb/gadget/usb_functions.h @@ -0,0 +1,188 @@ +/* + * USB Functions Gadget + * + * Copyright (C) 2012 Samsung Electronics + * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_USB_USB_FUNCTIONS__ +#define __LINUX_USB_USB_FUNCTIONS__ + +#include <linux/configfs.h> +#include <linux/usb/composite.h> + +#define UFG_STR_LEN 256 +#define UFG_FUNC_NAMES_BUF_LEN 256 +#define UFG_NAME_LEN 256 +#define UFG_FUNC_NAME_LEN 256 + +enum ufg_hdr_type { + UFG_FUNCTION, + UFG_INTERFACE, +}; + +struct ufg_grp_hdr { + struct config_group group; + + enum ufg_hdr_type type; +}; + +struct ufg_function_grp { + /* This group needs children */ + struct config_group group; + + enum ufg_hdr_type type; + + struct usb_function *f; + + struct list_head entry; + bool used; +}; + +struct ufg_endpoint_grp { + /* This group needs children */ + struct config_group group; + + /* attributes' values */ + int endpoint_address; + int attributes; + int max_packet_sz; + int interval; +}; + +struct ufg_interface_grp { + /* This group needs children */ + struct config_group group; + + enum ufg_hdr_type type; + + /* attributes' values */ + int interface_nr; + int alt_setting; + int num_endpoints; + int intf_class; + int intf_subclass; + int intf_protocol; +}; + +struct ufg_config_functions { + struct config_group group; + + struct list_head list; +}; + +struct ufg_config_grp { + /* This group needs children */ + struct config_group group; + + struct usb_configuration *usb_configuration; + + /* attributes' values */ + ushort max_power; + bool max_power_set; + ushort num_interfaces; + ushort conf_number; +}; + +struct ufg_gadget_grp { + /* This group needs children */ + struct config_group group; + + /* attributes' values */ + ushort idVendor; + ushort idProduct; + ushort bcdDevice; + char *iManufacturer; + char *iProduct; + char *iSerialNumber; + + ushort idVendor_set:1; + ushort idProduct_set:1; + ushort bcdDevice_set:1; + ushort ready:1; + + struct mutex lock; + + void *gadget_grp_data; +}; + +struct ufg_udc { + struct config_group group; + + bool used; +}; + +struct ufg_subsys { + /* This is the root of the subsystem */ + struct configfs_subsystem subsys; + + /* attributes' values */ + char avail_func[UFG_FUNC_NAMES_BUF_LEN]; +}; + +struct ufg_dev { + struct usb_device_descriptor ufg_device_desc; + struct usb_composite_driver ufg_driver; + struct usb_composite_dev *cdev; + struct ufg_gadget_grp *g_grp; +}; + +extern struct usb_composite_driver *UFG_COMPOSITE; +extern struct ufg_subsys *UFG_SUBSYSTEM; + +static inline struct ufg_grp_hdr *ufg_hdr(struct config_group *config_group) +{ + return container_of(config_group, struct ufg_grp_hdr, group); +} + +static inline struct ufg_function_grp +*to_ufg_function_grp(struct config_item *item) +{ + return item ? container_of(to_config_group(item), + struct ufg_function_grp, group) : NULL; +} + +static inline struct ufg_endpoint_grp +*to_ufg_endpoint_grp(struct config_item *item) +{ + return item ? container_of(to_config_group(item), + struct ufg_endpoint_grp, group) : NULL; +} + +static inline struct ufg_interface_grp +*to_ufg_interface_grp(struct config_item *item) +{ + return item ? container_of(to_config_group(item), + struct ufg_interface_grp, group) : NULL; +} + +static inline struct ufg_config_grp *to_ufg_config_grp(struct config_item *item) +{ + return item ? container_of(to_config_group(item), struct ufg_config_grp, + group) : NULL; +} + +static inline struct ufg_gadget_grp *to_ufg_gadget_grp(struct config_item *item) +{ + return item ? container_of(to_config_group(item), struct ufg_gadget_grp, + group) : NULL; +} + +static inline void init_name(struct qstr *n, const char *s) +{ + n->name = s; + n->len = strlen(n->name); + n->hash = full_name_hash(n->name, n->len); +} + +#endif /* __LINUX_USB_USB_FUNCTIONS__ */ diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 0af6569..4a5d57a 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -881,6 +881,11 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver); extern int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget); extern void usb_del_gadget_udc(struct usb_gadget *gadget); +struct config_group *udc_configfs_register(struct device *dev); +void udc_configfs_unregister(struct config_group *group); +int __init ufg_init(void); +void ufg_cleanup(void); + /*-------------------------------------------------------------------------*/ /* utility to simplify dealing with string descriptors */ -- 1.7.0.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