Add USB functions gadget configured entirely through configfs. This is the base for adding USB functions to it. The next patch in the series demonstrates how to add functions. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- drivers/usb/gadget/Kconfig | 12 + drivers/usb/gadget/Makefile | 2 + drivers/usb/gadget/usb_functions.c | 780 ++++++++++++++++++++++++++++++++++++ 3 files changed, 794 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/usb_functions.c diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index a53be32..0ccade5 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -521,6 +521,18 @@ choice # this first set of drivers all depend on bulk-capable hardware. +config USB_FG + tristate "USB Functions Gadget (EXPERIMENTAL)" + select USB_LIBCOMPOSITE + depends on EXPERIMENTAL && 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. + config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" select USB_LIBCOMPOSITE diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 307be5f..0f8b421 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o # # USB gadget drivers # +g_usb_functions-y := usb_functions.o g_zero-y := zero.o g_audio-y := audio.o g_ether-y := ether.o @@ -57,6 +58,7 @@ g_ncm-y := ncm.o g_acm_ms-y := acm_ms.o g_tcm_usb_gadget-y := tcm_usb_gadget.o +obj-$(CONFIG_USB_FG) += g_usb_functions.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o obj-$(CONFIG_USB_ETH) += g_ether.o diff --git a/drivers/usb/gadget/usb_functions.c b/drivers/usb/gadget/usb_functions.c new file mode 100644 index 0000000..ae15719 --- /dev/null +++ b/drivers/usb/gadget/usb_functions.c @@ -0,0 +1,780 @@ +/* + * 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/configfs.h> + +/* + * Supported functions + */ + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_DESC "USB Functions Gadget" +#define DRIVER_VERSION "Pawelek" + +#define VENDOR_ID 0x1d6b /* Linux Foundation */ +#define PRODUCT_ID 0x0108 +#define BCD_DEVICE 0xffff + +USB_GADGET_COMPOSITE_OPTIONS(); + +/*----------- helper functions and macros for configfs support ----------*/ + +#define UFG_STR_LEN 256 + +#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) \ +{ \ + grp->_member = (ushort)ufg_read_twobyte(buf); \ + 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) \ +{ \ + strlcpy(grp->_member, buf, UFG_STR_LEN); \ + grp->_member##_set = true; \ + \ + 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) + +typedef struct config_group *(*make_group_fn)(struct config_group *group, + const char *name); +typedef int (*bind_function_fn)(struct usb_configuration *c, + struct config_item *item, void *data); + +static ssize_t ufg_read_twobyte(const char *buf) +{ + unsigned long tmp; + int res; + char *p = (char *)buf; + + res = kstrtoul(p, 10, &tmp); + if (res) + return -EINVAL; + + if (tmp > 16383) + return -ERANGE; + + return tmp; +} + +/*-------------------- handling of dynamic make_group --------------------*/ + +struct ufg_fn { + const char *name; + struct config_group *group; + make_group_fn make_group; + bind_function_fn bind; +}; + +static struct ufg_fn *available_functions[] = { + NULL, +}; + +struct ufg_fn *ufg_fn_by_name(const char *name) +{ + struct ufg_fn **f; + + f = available_functions; + + while (*f) { + if (sysfs_streq((*f)->name, name)) + return *f; + f++; + } + + return NULL; +} + +struct ufg_fn *ufg_fn_by_group(struct config_group *group) +{ + struct ufg_fn **f; + + f = available_functions; + + while (*f) { + if ((*f)->group == group) + return *f; + f++; + } + + return NULL; +} + +static struct config_group *ufg_make_function_subgroup( + struct config_group *group, const char *name) +{ + struct ufg_fn *fn; + + fn = ufg_fn_by_name(name); + if (!fn || !fn->make_group) + return ERR_PTR(-EINVAL); + + if (fn->group) + return ERR_PTR(-EBUSY); + + fn->group = group; + + return fn->make_group(group, name); +} + +/*------------------ USB function-level configfs group ------------------*/ + +#define UFG_FUNC_NAME_LEN UFG_STR_LEN + +struct ufg_function_grp { + /* This group needs children */ + struct config_group group; + + /* attributes' values */ + char name[UFG_FUNC_NAME_LEN]; +}; + +UFG_SHOW_STR_ATTR(name, ufg_function_grp, name); + +static ssize_t ufg_function_grp_store_name(struct ufg_function_grp *grp, + const char *buf, size_t count) +{ + struct ufg_fn *fn; + + fn = ufg_fn_by_name(buf); + if (!fn) + return -EINVAL; + + strlcpy(grp->name, buf, UFG_FUNC_NAME_LEN); + + return count; +} + +CONFIGFS_ATTR_STRUCT(ufg_function_grp); + +UFG_ATTR_RW(name, name, ufg_function_grp); + +static struct configfs_attribute *ufg_function_grp_attrs[] = { + &ufg_function_grp_name.attr, + NULL, +}; + +static 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; +} + +CONFIGFS_ATTR_OPS(ufg_function_grp); + +static void ufg_function_grp_release(struct config_item *item) +{ + kfree(to_ufg_function_grp(item)); +} + + +static struct configfs_item_operations ufg_function_grp_item_ops = { + .show_attribute = ufg_function_grp_attr_show, + .store_attribute = ufg_function_grp_attr_store, + .release = ufg_function_grp_release, +}; + +static struct configfs_group_operations ufg_function_grp_group_ops = { + .make_group = ufg_make_function_subgroup, +}; + +static struct config_item_type ufg_function_grp_type = { + .ct_attrs = ufg_function_grp_attrs, + .ct_item_ops = &ufg_function_grp_item_ops, + .ct_group_ops = &ufg_function_grp_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_ufg_function(struct config_group *group, + const char *name) +{ + struct ufg_function_grp *function; + + function = kzalloc(sizeof(*function), GFP_KERNEL); + if (!function) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&function->group, name, + &ufg_function_grp_type); + + return &function->group; +} + +/*--------------- USB configuration-level configfs group ----------------*/ + +struct ufg_config_grp { + /* This group needs children */ + struct config_group group; + + bool added; + + /* attributes' values */ + ushort max_power; + bool max_power_set; + ushort num_interfaces; + ushort conf_number; +}; + +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, +}; + +static 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; +} + +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 configfs_group_operations ufg_config_grp_group_ops = { + .make_group = make_ufg_function, +}; + +static struct config_item_type ufg_config_grp_type = { + .ct_attrs = ufg_config_grp_attrs, + .ct_item_ops = &ufg_config_grp_item_ops, + .ct_group_ops = &ufg_config_grp_group_ops, + .ct_owner = THIS_MODULE, +}; + +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); + + return &config->group; +} + +/*------------------ USB gadget-level configfs group --------------------*/ + +#define UFG_NAME_LEN UFG_STR_LEN + +struct ufg_gadget_grp { + /* This group needs children */ + struct config_group group; + + /* attributes' values */ + ushort idVendor; + bool idVendor_set; + ushort idProduct; + bool idProduct_set; + ushort bcdDevice; + bool bcdDevice_set; + char iManufacturer[UFG_NAME_LEN]; + bool iManufacturer_set; + char iProduct[UFG_NAME_LEN]; + bool iProduct_set; + char iSerialNumber[UFG_NAME_LEN]; + bool iSerialNumber_set; + bool connect; + + void *gadget_grp_data; +}; + +static int ufg_gadget_bind(struct ufg_gadget_grp *group); + +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); + +UFG_SHOW_USHORT_ATTR(connect, ufg_gadget_grp, connect); +static ssize_t ufg_gadget_grp_store_connect(struct ufg_gadget_grp *grp, + const char *buf, size_t count) +{ + bool connect; + int ret; + + if (buf[0] != '0' && buf[0] != '1') + return -EINVAL; + + connect = grp->connect; + grp->connect = buf[0] == '1'; + + if (connect && grp->connect) + return -EBUSY; + + ret = ufg_gadget_bind(grp); + if (ret) { + grp->connect = connect; + return ret; + } + + return count; +} + +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); +UFG_ATTR_RW(connect, connect, 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, + &ufg_gadget_grp_connect.attr, + NULL, +}; + +static 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; +} + +CONFIGFS_ATTR_OPS(ufg_gadget_grp); + +static void ufg_gadget_grp_release(struct config_item *item) +{ + kfree(to_ufg_gadget_grp(item)); +} + +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, +}; + +static struct configfs_group_operations ufg_gadget_grp_group_ops = { + .make_group = make_ufg_config, +}; + +static struct config_item_type ufg_gadget_grp_type = { + .ct_attrs = ufg_gadget_grp_attrs, + .ct_item_ops = &ufg_gadget_grp_item_ops, + .ct_group_ops = &ufg_gadget_grp_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *make_ufg_gadget(struct config_group *group, + const char *name) +{ + struct ufg_gadget_grp *gadget; + + gadget = kzalloc(sizeof(*gadget), GFP_KERNEL); + if (!gadget) + return ERR_PTR(-ENOMEM); + + config_group_init_type_name(&gadget->group, name, &ufg_gadget_grp_type); + + return &gadget->group; +} + +/*------------------- configfs subsystem for ufg ------------------------*/ + +#define UFG_FUNC_NAMES_BUF_LEN UFG_STR_LEN + +struct ufg_subsys { + /* This is the root of the subsystem */ + struct configfs_subsystem subsys; + + /* attributes' values */ + char avail_func[UFG_FUNC_NAMES_BUF_LEN]; +}; + +static ssize_t ufg_subsys_show_avail_func(struct ufg_subsys *grp, char *buf) +{ + return sprintf(buf, "%s\n", grp->avail_func); +} + +CONFIGFS_ATTR_STRUCT(ufg_subsys); + +UFG_ATTR_RO(avail_func, available_functions, ufg_subsys); + +static struct configfs_attribute *ufg_subsys_attrs[] = { + &ufg_subsys_avail_func.attr, + NULL, +}; + +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; +} + +CONFIGFS_ATTR_OPS(ufg_subsys); + +static struct configfs_item_operations ufg_subsys_item_ops = { + .show_attribute = ufg_subsys_attr_show, +}; + +static struct configfs_group_operations ufg_subsys_group_ops = { + .make_group = make_ufg_gadget, +}; + +static struct config_item_type ufg_subsys_type = { + .ct_attrs = ufg_subsys_attrs, + .ct_item_ops = &ufg_subsys_item_ops, + .ct_group_ops = &ufg_subsys_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct ufg_subsys ufg_subsystem = { + .subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "usb-function-gadget", + .ci_type = &ufg_subsys_type, + }, + }, + }, +}; + +/*------------------- USB composite handling code -----------------------*/ + +static const char longname[] = "USB Functions Gadget"; +static const char serial[] = "0123456789.0123456789.0123456789"; +static struct usb_string strings_dev[] = { + [USB_GADGET_MANUFACTURER_IDX].s = "", + [USB_GADGET_PRODUCT_IDX].s = longname, + [USB_GADGET_SERIAL_IDX].s = serial, + { } /* 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 int ufg_bind(struct usb_composite_dev *cdev); + + +struct ufg_dev { + struct usb_device_descriptor ufg_device_desc; + struct usb_composite_driver ufg_driver; + struct usb_composite_dev *cdev; /* rename to tmp_... */ + struct usb_configuration *config; /* rename to tmp_... */ +}; + +static void fill_device_desc(struct usb_device_descriptor *udd) +{ + udd->bLength = sizeof(udd); + udd->bDescriptorType = USB_DT_DEVICE; + udd->bcdUSB = __constant_cpu_to_le16(0x0200); + udd->bDeviceClass = USB_CLASS_PER_INTERFACE; + udd->idVendor = __constant_cpu_to_le16(VENDOR_ID); + udd->idProduct = __constant_cpu_to_le16(PRODUCT_ID); + udd->bcdDevice = __constant_cpu_to_le16(BCD_DEVICE); + udd->bNumConfigurations = 1; +} + +static void fill_ufg_driver(struct usb_composite_driver *ufgd, + struct ufg_gadget_grp *grp) +{ + struct usb_device_descriptor *desc; + + desc = &container_of(ufgd, struct ufg_dev, ufg_driver)->ufg_device_desc; + fill_device_desc(desc); + /* TODO: copy something from grp */ + + ufgd->bind = ufg_bind; + ufgd->strings = dev_strings; + ufgd->name = "usb_function_gadget"; + ufgd->dev = desc; + ufgd->needs_serial = 1; +} + +static void ufg_unbind_config(struct usb_configuration *c); + +static void fill_config_driver(struct usb_configuration *ucd) +{ + ucd->label = "ufg"; + ucd->unbind = ufg_unbind_config; + ucd->bConfigurationValue = 1; + ucd->bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER; + ucd->bMaxPower = 0xFA; /* 500ma */ +} + +static int ufg_bind(struct usb_composite_dev *cdev) +{ + struct ufg_dev *ufg_dev; + int gcnum; + struct usb_gadget *gadget = cdev->gadget; + int status; + + 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); + + gcnum = get_default_bcdDevice(); + if (gcnum >= 0) + ufg_dev->ufg_device_desc.bcdDevice = + cpu_to_le16(0x0200 + gcnum); + else { + pr_warn("%s: controller '%s' not recognized\n", + DRIVER_DESC, gadget->name); + ufg_dev->ufg_device_desc.bcdDevice = + __constant_cpu_to_le16(0x9999); + } + + usb_gadget_set_selfpowered(gadget); + + usb_composite_overwrite_options(cdev, &coverwrite); + pr_info("%s: version: %s\n", DRIVER_DESC, DRIVER_VERSION); + + return 0; +} + +static int ufg_bind_config(struct usb_configuration *c) +{ + struct ufg_dev *ufg_dev; + + ufg_dev = container_of(c->cdev->driver, struct ufg_dev, ufg_driver); + ufg_dev->config = c; + + return 0; +} + +static void ufg_unbind_config(struct usb_configuration *c) +{ + kfree(c); +} + +static void update_ufg_driver(struct ufg_gadget_grp *ufg_gadget) +{ + if (ufg_gadget->idVendor_set) + coverwrite.idVendor = ufg_gadget->idVendor; + if (ufg_gadget->idProduct_set) + coverwrite.idProduct = ufg_gadget->idProduct; + if (ufg_gadget->bcdDevice_set) + coverwrite.bcdDevice = ufg_gadget->bcdDevice; + if (ufg_gadget->iManufacturer_set) + coverwrite.manufacturer = ufg_gadget->iManufacturer; + if (ufg_gadget->iProduct_set) + coverwrite.product = ufg_gadget->iProduct; + if (ufg_gadget->iSerialNumber_set) + coverwrite.serial_number = ufg_gadget->iSerialNumber; +} + +static int ufg_gadget_bind(struct ufg_gadget_grp *ufg_gadget) +{ + struct ufg_dev *ufg_dev; + struct config_item *ci; + struct usb_configuration *ufg_config; + int r; + + if (!ufg_gadget->connect) { + ufg_dev = ufg_gadget->gadget_grp_data; + 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; + + fill_ufg_driver(&ufg_dev->ufg_driver, ufg_gadget); + update_ufg_driver(ufg_gadget); + ufg_gadget->gadget_grp_data = ufg_dev; + r = usb_composite_probe(&ufg_dev->ufg_driver); + if (r) + goto release; + + list_for_each_entry(ci, &ufg_gadget->group.cg_children, ci_entry) { + struct ufg_config_grp *config; + struct config_item *f; + + config = to_ufg_config_grp(ci); + + ufg_config = kzalloc(sizeof(*ufg_config), GFP_KERNEL); + if (!ufg_config) + goto unbind; + fill_config_driver(ufg_config); + + ufg_config->bMaxPower = config->max_power; + r = usb_add_config(ufg_dev->cdev, ufg_config, ufg_bind_config); + if (r) + goto unbind; + config->added = true; + + list_for_each_entry(f, &config->group.cg_children, ci_entry) { + struct ufg_fn *ufg_fn; + + ufg_fn = ufg_fn_by_group(to_config_group(f)); + + if (ufg_fn && ufg_fn->bind) { + struct config_item *subgroup; + struct config_group *group; + + group = to_config_group(f); + subgroup = container_of(group->cg_children.next, + struct config_item, ci_entry); + r = ufg_fn->bind(ufg_dev->config, subgroup, + ufg_dev->cdev); + if (r) + goto unbind; + } + } + + } + + return 0; + +unbind: + list_for_each_entry(ci, &ufg_gadget->group.cg_children, ci_entry) { + struct ufg_config_grp *config; + + config = to_ufg_config_grp(ci); + if (!config->added) + continue; + usb_remove_config(ufg_dev->cdev, ufg_config); + } + usb_composite_unregister(&ufg_dev->ufg_driver); +release: + kfree(ufg_dev); + return r; +} + + +/*---------------------- general module stuff ---------------------------*/ + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Andrzej Pietrasiewicz"); +MODULE_LICENSE("GPL"); + +static int __init ufg_init(void) +{ + int ret; + + config_group_init(&ufg_subsystem.subsys.su_group); + mutex_init(&ufg_subsystem.subsys.su_mutex); + ret = configfs_register_subsystem(&ufg_subsystem.subsys); + if (ret) + goto unregister; + + return 0; + +unregister: + configfs_unregister_subsystem(&ufg_subsystem.subsys); + + return ret; +} +module_init(ufg_init); + +static void ufg_cleanup(void) +{ + configfs_unregister_subsystem(&ufg_subsystem.subsys); +} +module_exit(ufg_cleanup); -- 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