Demonstrate a USB gadget configured entirely through configfs. This is a work in progress. In order to integrate configfs into gadget's code there must be some place to store the code which does configfs-based binding/unbinding. It is not intended to create yet another gadget. But there must be some place for this kind of code. Probably composite.c would be just as good, but I don't want to mess with it yet. The gadget takes advantage of Sebastian's function registration framework with regard to registering and getting the usb functions by their name. In order to set up a composite gadget the user needs to: insmod libcomposite.ko insmod g_usb_functions.ko The required functions modules (e.g. f_mass_storage.ko) will be loaded automatically with request_module if request_module works on the system. Then configuration follows: mount -t configfs none /cfg mkdir -p /cfg/usb-function-gadget/G1/C1/F1 echo 0x<some id> > /cfg/usb-function-gadget/G1/idVendor echo 0x<some id> > /cfg/usb-function-gadget/G1/idProduct echo 0x<some id> > /cfg/usb-function-gadget/G1/bcdDevice echo <name> > /cfg/usb-function-gadget/G1/iManufacturer echo <something> > /cfg/usb-function-gadget/G1/iSerialNumber echo <something> > /cfg/usb-function-gadget/G1/iProduct Please note that directories names are only example and can be discussed. G1 corresponds to the whole gadget. C1 corresponds to the usb configuration it will use (just one in this example). F1 corresponds to the usb function which will be used. Whether there should be a generic name at this level, and a "name" attribute inside it, or just a function's name at this level can be discussed. The former was chosen in order to enable instantiating a function multiple times in one configuration. Then specific functions to be run follow, e.g.: echo MassStorage > /cfg/usb-function-gadget/G1/C1/F1/name After the last command the MassStorage function module (f_mass_storage.ko) is requested and function's configfs directory hierarchy is created: /cfg/usb-function-gadget/G1/C1/F1/MassStorage /cfg/usb-function-gadget/G1/C1/F1/MassStorage/luns /cfg/usb-function-gadget/G1/C1/F1/MassStorage/stall Then the function needs to be configured with echo 1 > /cfg/usb-function-gadget/G1/C1/F1/MassStorage/luns After the last command the lunX directories are created: /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0 /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0/file /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0/nofua /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0/removable /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0/ro Now we configure the single lun we have: echo <some file>.img > /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0/file Do the similar thing to other functions, then echo 1 > /cfg/usb-function-gadget/G1/ready to actually probe and bind the gadget. In this case, under F1 there will be automatically created interface00 directory with the contents similar to this: /cfg/usb-function-gadget/G1/C1/F1/interface00 /cfg/usb-function-gadget/G1/C1/F1/interface00/altsetting /cfg/usb-function-gadget/G1/C1/F1/interface00/interface_class /cfg/usb-function-gadget/G1/C1/F1/interface00/interface_number /cfg/usb-function-gadget/G1/C1/F1/interface00/interface_protocol /cfg/usb-function-gadget/G1/C1/F1/interface00/interface_subclass /cfg/usb-function-gadget/G1/C1/F1/interface00/n_endpoints /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint02 /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint02/attributes /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint02/endpoint_address /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint02/interval /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint02/max_packet_size /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint81 /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint81/attributes /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint81/endpoint_address /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint81/interval /cfg/usb-function-gadget/G1/C1/F1/interface00/endpoint81/max_packet_size In order to unload the gadget: echo 0 > /cfg/usb-function-gadget/G1/ready echo > /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0/file rmdir /cfg/usb-function-gadget/G1/C1/F1/MassStorage/lun0 rmdir /cfg/usb-function-gadget/G1/C1/F1/MassStorage rmdir /cfg/usb-function-gadget/G1/C1/F1 rmdir /cfg/usb-function-gadget/G1/C1 rmdir /cfg/usb-function-gadget/G1 umount cfg rmmod g_usb_functions.ko rmmod f_mass_storage.ko rmmod libcomposite.ko 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 | 1067 ++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/usb_functions.h | 100 ++++ 4 files changed, 1181 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/usb_functions.c create mode 100644 drivers/usb/gadget/usb_functions.h diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 14625fd..8530279 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 fef41f5..09faa34 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 @@ -56,6 +57,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..93d8345 --- /dev/null +++ b/drivers/usb/gadget/usb_functions.c @@ -0,0 +1,1067 @@ +/* + * 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 DRIVER_DESC "USB Functions Gadget" + +USB_GADGET_COMPOSITE_OPTIONS(); + +/*----------- helper functions and macros for configfs support ----------*/ + +#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'); \ + \ + if (!end) \ + end = buf + count; \ + if (end - buf >= sizeof(grp->_member)) \ + return -EINVAL; \ + memcpy(grp->_member, buf, end - buf); \ + grp->_member[end - buf] = 0; \ + 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) + +#define ufg_hdr(ptr) container_of((ptr), struct ufg_grp_hdr, group) + +/*-------------------- handling of dynamic make_group --------------------*/ + +static struct config_group *make_ufg_interface(struct config_group *group, + const char *name); + +static struct config_group *ufg_make_function_subgroup( + struct config_group *group, const char *name) +{ + struct ufg_function_grp *f_grp; + struct config_group *result; + + /* + * TODO: + * + * This makes "interface" an illegal name for a function. + */ + if (!strncmp(name, "interface", 9)) { + + result = make_ufg_interface(group, name); + + if (result) { + struct ufg_grp_hdr *h; + + h = container_of(result, struct ufg_grp_hdr, group); + h->type = UFG_INTERFACE; + } + + return result; + } + + f_grp = container_of(group, struct ufg_function_grp, group); + if (IS_ERR_OR_NULL(f_grp->f)) + return ERR_PTR(-ENODEV); + result = f_grp->f->make_group(group, name); + + if (!IS_ERR_OR_NULL(result)) { + struct ufg_grp_hdr *h; + + h = ufg_hdr(result); + h->type = UFG_FUNCTION; + } + + return result; +} + +/*------------------ USB function-level configfs group ------------------*/ + +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) +{ + int rc; + struct dentry *parent, *new; + struct qstr name; + char name_buf[UFG_STR_LEN]; + + if (count >= sizeof(name_buf)) + return -ENODEV; + + sscanf(buf, "%s", name_buf); + grp->f = usb_get_function(name_buf); + if (IS_ERR_OR_NULL(grp->f)) + return -ENODEV; + + name.name = name_buf; + name.len = strlen(name_buf); + name.hash = full_name_hash(name.name, name.len); + + parent = grp->group.cg_item.ci_dentry; + new = d_alloc(parent, &name); + if (IS_ERR_OR_NULL(new)) + return -ENOMEM; + d_add(new, NULL); + rc = ufg_mkdir(parent, new); + if (rc) { + d_drop(new); + dput(new); + + return rc; + } + dput(new); /* make the refcount 1 */ + + 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 endpoint-level configfs group ------------------*/ + +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; +}; + +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, +}; + +static 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; +} + +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, + //.store_attribute = ufg_endpoint_grp_attr_store, + .release = ufg_endpoint_grp_release, +}; + +static struct configfs_group_operations ufg_endpoint_grp_group_ops = { + .make_group = NULL, /* TODO: implement a make endpoint group */ +}; + +static struct config_item_type ufg_endpoint_grp_type = { + .ct_attrs = ufg_endpoint_grp_attrs, + .ct_item_ops = &ufg_endpoint_grp_item_ops, + .ct_group_ops = &ufg_endpoint_grp_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *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->group; +} + +/*------------------ USB interface-level configfs group ------------------*/ + +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; +}; + +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, +}; + +static 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; +} + +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, + //.store_attribute = ufg_interface_grp_attr_store, + .release = ufg_interface_grp_release, +}; + +static struct configfs_group_operations ufg_interface_grp_group_ops = { + .make_group = make_ufg_endpoint, +}; + +static struct config_item_type ufg_interface_grp_type = { + .ct_attrs = ufg_interface_grp_attrs, + .ct_item_ops = &ufg_interface_grp_item_ops, + .ct_group_ops = &ufg_interface_grp_group_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *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->group; +} + +/*--------------- USB configuration-level configfs group ----------------*/ + +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; +}; + +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 --------------------*/ + +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(ready, ufg_gadget_grp, ready); + +static int ufg_gadget_ready(struct ufg_gadget_grp *group); + +static ssize_t ufg_gadget_grp_store_ready(struct ufg_gadget_grp *ufg_gadget_grp, + const char *buf, size_t count) +{ + bool ready; + int ret; + + if (buf[0] != '0' && buf[0] != '1') + return -EINVAL; + + ready = ufg_gadget_grp->ready; + ufg_gadget_grp->ready = buf[0] == '1'; + + if (ready && ufg_gadget_grp->ready) + return -EBUSY; + + ret = ufg_gadget_ready(ufg_gadget_grp); + if (ret) { + ufg_gadget_grp->ready = ready; + 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(ready, ready, 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_ready.attr, + 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 *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); + + return &ufg_gadget_grp->group; +} + +/*------------------- configfs subsystem for ufg ------------------------*/ + +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, +}; + +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 void ufg_unbind_config(struct usb_configuration *c) +{ + kfree(c); +} + +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; +} + +#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_rmdir_item(i, d) ufg_rmdir((i)->ci_dentry, (d)->ci_dentry) + +static void ufg_rmdirs(struct ufg_gadget_grp *ufg_gadget_grp) +{ + struct config_item *ci; + + /* configurations */ + ufg_for_each_child(ci, &ufg_gadget_grp->group) { + struct ufg_config_grp *ufg_cgrp = to_ufg_config_grp(ci); + struct config_item *f; + + /* functions' main groups */ + ufg_for_each_child(f, &ufg_cgrp->group) { + struct config_group *f_grp = to_config_group(f); + 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) + ufg_rmdir_item(i, e); + + ufg_rmdir_item(f, 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; +} + +#define INTF_NAME_TEMPL "interface00" +#define INTF_NR 9 + +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 qstr name; + struct dentry *parent, *new; + struct config_item *i; + struct ufg_interface_grp *igrp; + int r; + + parent = f_grp->cg_item.ci_dentry; + name.name = INTF_NAME_TEMPL; + sprintf((char *)(name.name + INTF_NR), "%02x", id->bInterfaceNumber); + name.len = strlen(name.name); + name.hash = full_name_hash(name.name, name.len); + new = d_alloc(parent, &name); + if (IS_ERR_OR_NULL(new)) + return NULL; + + d_add(new, NULL); + r = ufg_mkdir(parent, new); + if (r) { + d_drop(new); + dput(new); + + return ERR_PTR(r); + } + dput(new); /* make the refcount 1 */ + + i = config_group_find_item(f_grp, name.name); + igrp = to_ufg_interface_grp(i); + if (!igrp) { + d_drop(new); + dput(new); + + return NULL; + } + + igrp->interface_nr = id->bInterfaceNumber; + igrp->alt_setting = id->bAlternateSetting; + igrp->num_endpoints = id->bNumEndpoints; + igrp->intf_class = id->bInterfaceClass; + igrp->intf_subclass = id->bInterfaceSubClass; + igrp->intf_protocol = id->bInterfaceProtocol; + + return to_config_group(i); +} + +#define ENDP_NAME_TEMPL "endpoint00" +#define ENDP_NR 8 + +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 qstr name; + struct dentry *parent, *new; + struct config_item *i; + struct ufg_endpoint_grp *egrp; + int r; + + parent = interface_grp->cg_item.ci_dentry; + name.name = ENDP_NAME_TEMPL; + sprintf((char *)(name.name + ENDP_NR), "%02x", ed->bEndpointAddress); + name.len = strlen(name.name); + name.hash = full_name_hash(name.name, name.len); + new = d_alloc(parent, &name); + if (IS_ERR_OR_NULL(new)) + return NULL; + + d_add(new, NULL); + r = ufg_mkdir(parent, new); + if (r) { + d_drop(new); + dput(new); + + return ERR_PTR(r); + } + dput(new); /* make the refcount 1 */ + + i = config_group_find_item(interface_grp, name.name); + egrp = to_ufg_endpoint_grp(i); + if (!egrp) { + d_drop(new); + dput(new); + + return NULL; + } + + egrp->endpoint_address = ed->bEndpointAddress; + egrp->attributes = ed->bmAttributes; + egrp->max_packet_sz = ed->wMaxPacketSize; + egrp->interval = ed->bInterval; + + return to_config_group(i); +} + +static int ufg_add_f(struct ufg_dev *ufg_dev, struct usb_configuration *u_cfg, + struct config_item *f, ushort /*out*/ *num_interfaces) +{ + struct config_group *f_grp = to_config_group(f); + struct ufg_function_grp *function_grp = + container_of(f_grp, struct ufg_function_grp, group); + struct usb_function *fn = function_grp->f; + + if (IS_ERR_OR_NULL(fn)) + return PTR_ERR(fn); + + if (fn && fn->add_function) { + struct config_item *fn_grp; + struct usb_descriptor_header **descriptors; + struct usb_descriptor_header **d; + struct config_group *i_grp = NULL; + struct config_group *e_grp = NULL; + int r; + + fn_grp = list_entry(f_grp->cg_children.next, + struct config_item, ci_entry); + r = fn->add_function(u_cfg, fn, fn_grp, ufg_dev->cdev); + if (r) + return -1; + + 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; + /* keep gcc quiet about a value not being used*/ + *num_interfaces = *num_interfaces + 1; + 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 int ufg_bind(struct usb_composite_dev *cdev) +{ + struct ufg_dev *ufg_dev; + struct usb_gadget *gadget = cdev->gadget; + struct config_item *ci; + int gcnum; + 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); + + 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 = cpu_to_le16(0x9999); + } + + usb_gadget_set_selfpowered(gadget); + + usb_composite_overwrite_options(cdev, &coverwrite); + pr_info("%s\n", DRIVER_DESC); + + /* configurations */ + ufg_for_each_child(ci, &ufg_dev->g_grp->group) { + struct ufg_config_grp *c_grp = to_ufg_config_grp(ci); + struct usb_configuration *u_cfg; + struct config_item *f; + + 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; + + /* functions' main groups */ + ufg_for_each_child(f, &c_grp->group) { + status = ufg_add_f(ufg_dev, u_cfg, f, &c_grp->num_interfaces); + if (status) + goto unbind; + } + + } + + return 0; + +unbind: + ufg_rmdirs(ufg_dev->g_grp); + return status; +} + +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; + desc->bNumConfigurations = 1; + + ufgd->bind = ufg_bind; + 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_set) + coverwrite.manufacturer = g_grp->iManufacturer; + if (g_grp->iProduct_set) + coverwrite.product = g_grp->iProduct; + if (g_grp->iSerialNumber_set) + 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 module stuff ---------------------------*/ + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Andrzej Pietrasiewicz"); +MODULE_ALIAS("usb_functions"); +MODULE_LICENSE("GPL"); + +struct ufg_subsys *UFG_SUBSYSTEM; +EXPORT_SYMBOL(UFG_SUBSYSTEM); + +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; + + UFG_SUBSYSTEM = &ufg_subsystem; + 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); diff --git a/drivers/usb/gadget/usb_functions.h b/drivers/usb/gadget/usb_functions.h new file mode 100644 index 0000000..678f4b8 --- /dev/null +++ b/drivers/usb/gadget/usb_functions.h @@ -0,0 +1,100 @@ +/* + * 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 + + +#define ufg_mkdir(parent, new) (parent)->d_inode->i_op->mkdir(NULL, (new), 0) +#define ufg_rmdir(p, d) (p)->d_inode->i_op->rmdir((p)->d_inode, (d)) + +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; + + /* attributes' values */ + char name[UFG_FUNC_NAME_LEN]; + + struct usb_function *f; +}; + +struct ufg_gadget_grp { + /* This group needs children */ + struct config_group group; + + /* attributes' values */ + ushort idVendor; + ushort idVendor_set:1; + ushort idProduct; + ushort idProduct_set:1; + ushort bcdDevice; + ushort bcdDevice_set:1; + char iManufacturer[UFG_NAME_LEN]; + ushort iManufacturer_set:1; + char iProduct[UFG_NAME_LEN]; + ushort iProduct_set:1; + char iSerialNumber[UFG_NAME_LEN]; + ushort iSerialNumber_set:1; + bool ready; + + void *gadget_grp_data; +}; + +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; +} + +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; + +#endif /* __LINUX_USB_USB_FUNCTIONS__ */ -- 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