Add FunctionFS support. It allows certain USB functions to be provided from userspace, e.g. MTP, PTP, adb. The functions provided by the gadget itself are enumerated in /sys/class/ccg0/functions. The functions which can be supplied from userspace must be enumerated in /sys/class/ccg0/f_fs/user_functions. No other userspace functions can be used than specified in the above mentioned file, but just specifying them there is not enough to activate them. The userspace functions in order to be activated need also be enumerated in /sys/class/ccg0/functions. An example sequence of operations can be as follows: $ echo 0 > /sys/class/ccg_usb/ccg0/enable $ echo 2d01 > /sys/class/ccg_usb/ccg0/idProduct $ echo MyDevice > /sys/class/ccg_usb/ccg0/iSerial $ echo 1d6b > /sys/class/ccg_usb/ccg0/idVendor $ echo adb,mtp,ptp > /sys/class/ccg_usb/ccg0/f_fs/user_functions $ echo mass_storage,ptp > /sys/class/ccg_usb/ccg0/functions $ echo /file.img > /sys/class/ccg_usb/ccg0/f_mass_storage/lun/file $ mkdir -p /dev/usbgadget/ptp $ mount -t functionfs ptp /dev/usbgadget/ptp $ ./ptp & $ echo 1 > /sys/class/ccg_usb/ccg0/enable The above example declares that adb, mtp and ptp functions can be supplied from userspace through FunctionFS. But of them all only ptp is actually activated, together with mass_storage, the latter being implemented in the gadget itself (in kernel, not in userspace). The list of functions can be modified at runtime while the gadget is not enabled, that is, after $ echo 0 > /sys/class/ccg_usb/ccg0/enable The FunctionFS function is implicitly handled by the gadget, that is, if a userspace function name is provided in /sys/class/ccg_usb/ccg0/functions, then the FunctionFS function is activated. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> Acked-by: Michal Nazarewicz <mina86@xxxxxxxxxx> --- drivers/usb/gadget/Kconfig | 2 +- drivers/usb/gadget/ccg.c | 331 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 321 insertions(+), 12 deletions(-) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 286b8b8..b4d1fff 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -887,7 +887,7 @@ config USB_G_CCG depends on EXPERIMENTAL help The Configurable Composite Gadget supports multiple USB - functions: acm, mass storage, and rndis. + functions: acm, mass storage, rndis and FunctionFS. Each function can be configured and enabled/disabled dynamically from userspace through a sysfs interface. diff --git a/drivers/usb/gadget/ccg.c b/drivers/usb/gadget/ccg.c index 7d71635..0405021 100644 --- a/drivers/usb/gadget/ccg.c +++ b/drivers/usb/gadget/ccg.c @@ -56,8 +56,9 @@ #include "f_rndis.c" #include "rndis.c" #include "u_ether.c" +#include "f_fs.c" -MODULE_AUTHOR("Mike Lockwood"); +MODULE_AUTHOR("Mike Lockwood, Andrzej Pietrasiewicz"); MODULE_DESCRIPTION("Configurable Composite USB Gadget"); MODULE_LICENSE("GPL"); MODULE_VERSION("1.0"); @@ -67,6 +68,7 @@ static const char longname[] = "Configurable Composite Gadget"; /* Default vendor and product IDs, overridden by userspace */ #define VENDOR_ID 0x1d6b /* Linux Foundation */ #define PRODUCT_ID 0x0107 +#define GFS_MAX_DEVS 10 struct ccg_usb_function { char *name; @@ -96,6 +98,14 @@ struct ccg_usb_function { const struct usb_ctrlrequest *); }; +struct ffs_obj { + const char *name; + bool mounted; + bool desc_ready; + bool used; + struct ffs_data *ffs_data; +}; + struct ccg_dev { struct ccg_usb_function **functions; struct list_head enabled_functions; @@ -107,6 +117,10 @@ struct ccg_dev { bool connected; bool sw_connected; struct work_struct work; + + unsigned int max_func_num; + unsigned int func_num; + struct ffs_obj ffs_tab[GFS_MAX_DEVS]; }; static struct class *ccg_class; @@ -114,6 +128,7 @@ static struct ccg_dev *_ccg_dev; static int ccg_bind_config(struct usb_configuration *c); static void ccg_unbind_config(struct usb_configuration *c); +static char func_names_buf[256]; static struct usb_device_descriptor device_desc = { .bLength = sizeof(device_desc), @@ -165,6 +180,276 @@ static void ccg_work(struct work_struct *data) /*-------------------------------------------------------------------------*/ /* Supported functions initialization */ +static struct ffs_obj *functionfs_find_dev(struct ccg_dev *dev, + const char *dev_name) +{ + int i; + + ENTER(); + + for (i = 0; i < dev->max_func_num; i++) + if (strcmp(dev->ffs_tab[i].name, dev_name) == 0) + return &dev->ffs_tab[i]; + + return NULL; +} + +static bool functionfs_all_ready(struct ccg_dev *dev) +{ + int i; + + for (i = 0; i < dev->max_func_num; i++) + if (dev->ffs_tab[i].used && !dev->ffs_tab[i].desc_ready) + return false; + + return true; +} + +static int functionfs_ready_callback(struct ffs_data *ffs) +{ + struct ffs_obj *ffs_obj; + int ret; + + ENTER(); + mutex_lock(&_ccg_dev->mutex); + + ffs_obj = ffs->private_data; + if (!ffs_obj) { + ret = -EINVAL; + goto done; + } + if (WARN_ON(ffs_obj->desc_ready)) { + ret = -EBUSY; + goto done; + } + ffs_obj->ffs_data = ffs; + + if (functionfs_all_ready(_ccg_dev)) { + ret = -EBUSY; + goto done; + } + ffs_obj->desc_ready = true; + +done: + mutex_unlock(&_ccg_dev->mutex); + return ret; +} + +static void reset_usb(struct ccg_dev *dev) +{ + /* Cancel pending control requests */ + usb_ep_dequeue(dev->cdev->gadget->ep0, dev->cdev->req); + usb_remove_config(dev->cdev, &ccg_config_driver); + dev->enabled = false; + usb_gadget_disconnect(dev->cdev->gadget); +} + +static void functionfs_closed_callback(struct ffs_data *ffs) +{ + struct ffs_obj *ffs_obj; + + ENTER(); + mutex_lock(&_ccg_dev->mutex); + + ffs_obj = ffs->private_data; + if (!ffs_obj) + goto done; + + ffs_obj->desc_ready = false; + + if (_ccg_dev->enabled) + reset_usb(_ccg_dev); + +done: + mutex_unlock(&_ccg_dev->mutex); +} + +static void *functionfs_acquire_dev_callback(const char *dev_name) +{ + struct ffs_obj *ffs_dev; + + mutex_lock(&_ccg_dev->mutex); + + ffs_dev = functionfs_find_dev(_ccg_dev, dev_name); + if (!ffs_dev) { + ffs_dev = ERR_PTR(-ENODEV); + goto done; + } + + if (ffs_dev->mounted) { + ffs_dev = ERR_PTR(-EBUSY); + goto done; + } + ffs_dev->mounted = true; + +done: + mutex_unlock(&_ccg_dev->mutex); + return ffs_dev; +} + +static void functionfs_release_dev_callback(struct ffs_data *ffs_data) +{ + struct ffs_obj *ffs_dev; + + mutex_lock(&_ccg_dev->mutex); + + ffs_dev = ffs_data->private_data; + if (ffs_dev) + ffs_dev->mounted = false; + + mutex_unlock(&_ccg_dev->mutex); +} + +static int functionfs_function_init(struct ccg_usb_function *f, + struct usb_composite_dev *cdev) +{ + return functionfs_init(); +} + +static void functionfs_function_cleanup(struct ccg_usb_function *f) +{ + functionfs_cleanup(); +} + +static int functionfs_function_bind_config(struct ccg_usb_function *f, + struct usb_configuration *c) +{ + struct ccg_dev *dev = _ccg_dev; + int i, ret; + + for (i = dev->max_func_num; i--; ) { + if (!dev->ffs_tab[i].used) + continue; + ret = functionfs_bind(dev->ffs_tab[i].ffs_data, c->cdev); + if (unlikely(ret < 0)) { + while (++i < dev->max_func_num) + functionfs_unbind(dev->ffs_tab[i].ffs_data); + return ret; + } + } + + for (i = dev->max_func_num; i--; ) { + if (!dev->ffs_tab[i].used) + continue; + ret = functionfs_bind_config(c->cdev, c, + dev->ffs_tab[i].ffs_data); + if (unlikely(ret < 0)) + return ret; + } + + return 0; +} + +static void functionfs_function_unbind_config(struct ccg_usb_function *f, + struct usb_configuration *c) +{ + struct ccg_dev *dev = _ccg_dev; + int i; + + for (i = dev->max_func_num; i--; ) + if (dev->ffs_tab[i].ffs_data) + functionfs_unbind(dev->ffs_tab[i].ffs_data); +} + +static ssize_t functionfs_user_functions_show(struct device *_dev, + struct device_attribute *attr, + char *buf) +{ + struct ccg_dev *dev = _ccg_dev; + char *buff = buf; + int i; + + mutex_lock(&dev->mutex); + + for (i = 0; i < dev->max_func_num; i++) + buff += snprintf(buff, PAGE_SIZE + buf - buff, "%s,", + dev->ffs_tab[i].name); + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff - 1) = '\n'; + return buff - buf; +} + +static ssize_t functionfs_user_functions_store(struct device *_dev, + struct device_attribute *attr, + const char *buff, size_t size) +{ + struct ccg_dev *dev = _ccg_dev; + char *name, *b; + ssize_t ret = size; + int i; + + buff = skip_spaces(buff); + if (!*buff) + return -EINVAL; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + ret = -EBUSY; + goto end; + } + + for (i = 0; i < dev->max_func_num; i++) + if (dev->ffs_tab[i].mounted) { + ret = -EBUSY; + goto end; + } + + strlcpy(func_names_buf, buff, sizeof(func_names_buf)); + b = strim(func_names_buf); + + /* replace the list of functions */ + dev->max_func_num = 0; + while (b) { + name = strsep(&b, ","); + if (dev->max_func_num == GFS_MAX_DEVS) { + ret = -ENOSPC; + goto end; + } + if (functionfs_find_dev(dev, name)) { + ret = -EEXIST; + continue; + } + dev->ffs_tab[dev->max_func_num++].name = name; + } + +end: + mutex_unlock(&dev->mutex); + return ret; +} + +static DEVICE_ATTR(user_functions, S_IRUGO | S_IWUSR, + functionfs_user_functions_show, + functionfs_user_functions_store); + +static ssize_t functionfs_max_user_functions_show(struct device *_dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d", GFS_MAX_DEVS); +} + +static DEVICE_ATTR(max_user_functions, S_IRUGO, + functionfs_max_user_functions_show, NULL); + +static struct device_attribute *functionfs_function_attributes[] = { + &dev_attr_user_functions, + &dev_attr_max_user_functions, + NULL +}; + +static struct ccg_usb_function functionfs_function = { + .name = "fs", + .init = functionfs_function_init, + .cleanup = functionfs_function_cleanup, + .bind_config = functionfs_function_bind_config, + .unbind_config = functionfs_function_unbind_config, + .attributes = functionfs_function_attributes, +}; + #define MAX_ACM_INSTANCES 4 struct acm_function_config { int instances; @@ -494,6 +779,7 @@ static struct ccg_usb_function mass_storage_function = { }; static struct ccg_usb_function *supported_functions[] = { + &functionfs_function, &acm_function, &rndis_function, &mass_storage_function, @@ -628,11 +914,15 @@ functions_show(struct device *pdev, struct device_attribute *attr, char *buf) struct ccg_dev *dev = dev_get_drvdata(pdev); struct ccg_usb_function *f; char *buff = buf; + int i; mutex_lock(&dev->mutex); list_for_each_entry(f, &dev->enabled_functions, enabled_list) buff += sprintf(buff, "%s,", f->name); + for (i = 0; i < dev->max_func_num; i++) + if (dev->ffs_tab[i].used) + buff += sprintf(buff, "%s", dev->ffs_tab[i].name); mutex_unlock(&dev->mutex); @@ -648,7 +938,8 @@ functions_store(struct device *pdev, struct device_attribute *attr, struct ccg_dev *dev = dev_get_drvdata(pdev); char *name; char buf[256], *b; - int err; + int err, i; + bool functionfs_enabled; buff = skip_spaces(buff); if (!*buff) @@ -662,16 +953,34 @@ functions_store(struct device *pdev, struct device_attribute *attr, } INIT_LIST_HEAD(&dev->enabled_functions); + functionfs_enabled = false; + for (i = 0; i < dev->max_func_num; i++) + dev->ffs_tab[i].used = false; strlcpy(buf, buff, sizeof(buf)); b = strim(buf); while (b) { + struct ffs_obj *user_func; + name = strsep(&b, ","); - if (name) { + /* handle FunctionFS implicitly */ + if (!strcmp(name, functionfs_function.name)) { + pr_err("ccg_usb: Cannot explicitly enable '%s'", name); + continue; + } + user_func = functionfs_find_dev(dev, name); + if (user_func) + name = functionfs_function.name; + err = 0; + if (!user_func || !functionfs_enabled) err = ccg_enable_function(dev, name); - if (err) - pr_err("android_usb: Cannot enable '%s'", name); + if (err) + pr_err("ccg_usb: Cannot enable '%s'", name); + else if (user_func) { + user_func->used = true; + dev->func_num++; + functionfs_enabled = true; } } @@ -695,8 +1004,12 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, int enabled = 0; mutex_lock(&dev->mutex); - sscanf(buff, "%d", &enabled); + if (enabled && dev->func_num && !functionfs_all_ready(dev)) { + mutex_unlock(&dev->mutex); + return -ENODEV; + } + if (enabled && !dev->enabled) { int ret; @@ -720,11 +1033,7 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, usb_remove_config(cdev, &ccg_config_driver); } } else if (!enabled && dev->enabled) { - usb_gadget_disconnect(cdev->gadget); - /* Cancel pending control requests */ - usb_ep_dequeue(cdev->gadget->ep0, cdev->req); - usb_remove_config(cdev, &android_config_driver); - dev->enabled = false; + reset_usb(dev); } else { pr_err("ccg_usb: already %s\n", dev->enabled ? "enabled" : "disabled"); -- 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