diff options
-rw-r--r-- | drivers/staging/ccg/ccg.c | 326 | ||||
-rw-r--r-- | drivers/staging/ccg/sysfs-class-ccg_usb | 32 |
2 files changed, 346 insertions, 12 deletions
diff --git a/drivers/staging/ccg/ccg.c b/drivers/staging/ccg/ccg.c index a0f7f75cfc9d..a5b36a97598d 100644 --- a/drivers/staging/ccg/ccg.c +++ b/drivers/staging/ccg/ccg.c @@ -58,7 +58,7 @@ #include "../../usb/gadget/u_ether.c" #include "../../usb/gadget/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"); @@ -68,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; @@ -97,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; @@ -108,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; @@ -115,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), @@ -166,6 +180,272 @@ 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; + + 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; + + 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; + + 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; @@ -495,6 +775,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, @@ -629,11 +910,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); @@ -649,7 +934,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) @@ -663,16 +949,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("ccg_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; } } @@ -696,8 +1000,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; @@ -721,11 +1029,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, &ccg_config_driver); - dev->enabled = false; + reset_usb(dev); } else { pr_err("ccg_usb: already %s\n", dev->enabled ? "enabled" : "disabled"); diff --git a/drivers/staging/ccg/sysfs-class-ccg_usb b/drivers/staging/ccg/sysfs-class-ccg_usb index 4c8ff9a77ea0..dd12a332fb00 100644 --- a/drivers/staging/ccg/sysfs-class-ccg_usb +++ b/drivers/staging/ccg/sysfs-class-ccg_usb @@ -22,7 +22,9 @@ KernelVersion: 3.4 Contact: linux-usb@vger.kernel.org Description: A comma-separated list of USB function names to be activated - in this ccg gadget. + in this ccg gadget. It includes both the functions provided + in-kernel by the ccg gadget and the functions provided from + userspace through FunctionFS. What: /sys/class/ccg_usb/ccgX/enable Date: May 2012 @@ -58,6 +60,34 @@ Contact: linux-usb@vger.kernel.org Description: Maximum number of the /dev/ttyGS<X> interface the driver uses. +What: /sys/class/ccg_usb/ccgX/f_fs +Date: May 2012 +KernelVersion: 3.4 +Contact: linux-usb@vger.kernel.org +Description: + The /sys/class/ccg_usb/ccgX/f_fs subdirectory + corresponds to the gadget's FunctionFS driver. + +What: /sys/class/ccg_usb/ccgX/f_fs/user_functions +Date: May 2012 +KernelVersion: 3.4 +Contact: linux-usb@vger.kernel.org +Description: + A comma-separeted list of USB function names to be supported + from userspace. No other userspace FunctionFS functions can + be supported than listed here. However, the actual activation + of these functions is still done through + /sys/class/ccg_usb/ccgX/functions, where it is possible + to specify any subset (including maximum and empty) of + /sys/class/ccg_usb/ccgX/f_fs/user_functions. + +What: /sys/class/ccg_usb/ccgX/f_fs/max_user_functions +Date: May 2012 +KernelVersion: 3.4 +Contact: linux-usb@vger.kernel.org +Description: + Maximum number of USB functions to be supported from userspace. + What: /sys/class/ccg_usb/ccgX/f_rndis Date: May 2012 KernelVersion: 3.4 |