Demonstrate a USB gadget configured entirely through configfs. This is a work in progress. 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 | 1204 ++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/usb_functions.h | 32 + 4 files changed, 1250 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 dfb51a4..2229acd 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..bcbebbf --- /dev/null +++ b/drivers/usb/gadget/usb_functions.c @@ -0,0 +1,1204 @@ +/* + * 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 + */ +#include "f_mass_storage.c" + +/*-------------------------------------------------------------------------*/ + +#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 f_mass_storage = { + .name = "f_mass_storage", + .make_group = alloc_fsg_common, + .bind = fsg_bind_function, +}; + +static struct ufg_fn *available_functions[] = { + &f_mass_storage, + 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 *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 config_group *result; + struct ufg_fn *fn; + + /* + * 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; + } + + 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; + + result = fn->make_group(group, name); + + if (result) { + struct ufg_grp_hdr *h; + + h = container_of(result, struct ufg_grp_hdr, group); + h->type = UFG_FUNCTION; + } + + return result; +} + +/*------------------ 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 endpoint-level configfs group ------------------*/ + +#define UFG_FUNC_NAME_LEN UFG_STR_LEN + +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 ------------------*/ + +#define UFG_FUNC_NAME_LEN UFG_STR_LEN + +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 --------------------*/ + +#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 *ufg_gadget_grp, + const char *buf, size_t count) +{ + bool connect; + int ret; + + if (buf[0] != '0' && buf[0] != '1') + return -EINVAL; + + connect = ufg_gadget_grp->connect; + ufg_gadget_grp->connect = buf[0] == '1'; + + if (connect && ufg_gadget_grp->connect) + return -EBUSY; + + ret = ufg_gadget_bind(ufg_gadget_grp); + if (ret) { + ufg_gadget_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 *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 ------------------------*/ + +#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 void ufg_list_available_functions(char *buffer) +{ + struct ufg_fn **f; + + f = available_functions; + while (*f) { + strcpy(buffer + strlen(buffer), (*f)->name); + f++; + } +} + +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 ufg_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 update_ufg_driver(struct ufg_gadget_grp *g_grp) +{ + 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 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; + ufg_fill_device_desc(desc); + + ufgd->bind = ufg_bind; + ufgd->strings = dev_strings; + ufgd->name = "usb_function_gadget"; + ufgd->dev = desc; + ufgd->needs_serial = 1; + + update_ufg_driver(g_grp); +} + +static void ufg_unbind_config(struct usb_configuration *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; +} + +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); +} + +#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_hdr(ptr) container_of((ptr), struct ufg_grp_hdr, group) + +#define ufg_rmdir(i, d) \ + (i)->ci_dentry->d_inode->i_op->rmdir((i)->ci_dentry->d_inode, \ + (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(i, e); + + ufg_rmdir(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->descriptors; + } + + return NULL; +} + +#define INTF_NAME_TEMPL "interface00" +#define INTF_NR 9 + +#define ufg_mkdir(parent, new) (parent)->d_inode->i_op->mkdir(NULL, (new), 0) + +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_bind_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_fn *fn = ufg_fn_by_group(f_grp); + + if (fn && fn->bind) { + 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->bind(ufg_dev->config, 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_gadget_bind(struct ufg_gadget_grp *g_grp) +{ + struct ufg_dev *ufg_dev; + struct config_item *ci; + int r, n = 1; + + if (!g_grp->connect) { + 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; + r = usb_composite_probe(&ufg_dev->ufg_driver); + if (r) + goto release; + + /* configurations */ + ufg_for_each_child(ci, &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++); + r = usb_add_config(ufg_dev->cdev, u_cfg, ufg_bind_config); + if (r) + goto unbind; + c_grp->usb_configuration = u_cfg; + + /* functions' main groups */ + ufg_for_each_child(f, &c_grp->group) + if (ufg_bind_f(ufg_dev, u_cfg, f, &c_grp->num_interfaces)) + goto unbind; + + } + + return 0; + +unbind: + ufg_rmdirs(g_grp); + 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); + ufg_list_available_functions(ufg_subsystem.avail_func); + 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); diff --git a/drivers/usb/gadget/usb_functions.h b/drivers/usb/gadget/usb_functions.h new file mode 100644 index 0000000..878e721 --- /dev/null +++ b/drivers/usb/gadget/usb_functions.h @@ -0,0 +1,32 @@ +/* + * 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__ + +enum ufg_hdr_type { + UFG_FUNCTION, + UFG_INTERFACE, +}; + +struct ufg_grp_hdr { + struct config_group group; + + enum ufg_hdr_type type; +}; + +#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