Add support for using FunctionFS in configfs-based USB gadgets. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- Documentation/ABI/testing/configfs-usb-gadget-ffs | 9 ++ drivers/usb/gadget/f_fs.c | 154 ++++++++++++++++++++- drivers/usb/gadget/u_fs.h | 5 + 3 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-ffs diff --git a/Documentation/ABI/testing/configfs-usb-gadget-ffs b/Documentation/ABI/testing/configfs-usb-gadget-ffs new file mode 100644 index 0000000..8803bc0 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-ffs @@ -0,0 +1,9 @@ +What: /config/usb-gadget/gadget/functions/ffs.name +Date: Oct 2013 +KenelVersion: 3.13 +Description: The purpose of this directory is to create and remove it. + + A corresponding USB function instance is created/removed. + There are no attributes here. + + All parameters are set through FunctionFS. diff --git a/drivers/usb/gadget/f_fs.c b/drivers/usb/gadget/f_fs.c index fbceb47..6a3619a 100644 --- a/drivers/usb/gadget/f_fs.c +++ b/drivers/usb/gadget/f_fs.c @@ -29,6 +29,7 @@ #include <linux/usb/functionfs.h> #include "u_fs.h" +#include "configfs.h" #define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ @@ -151,6 +152,7 @@ static struct ffs_dev *ffs_alloc_dev(void); static void ffs_free_dev(struct ffs_dev *dev); static struct mutex *ffs_dev_lock; +static DEFINE_MUTEX(_ffs_dev_lock); static void *(*ffs_acquire_dev_cb)(const char *name); static void (*ffs_release_dev_cb)(struct ffs_data *ffs_data); @@ -2361,6 +2363,117 @@ void ffs_set_closed_cb(void (*cb)(struct ffs_data *ffs_data)) EXPORT_SYMBOL(ffs_set_closed_cb); +/* Configfs support *********************************************************/ + +static int ffs_ready_callback(struct ffs_data *ffs) +{ + struct ffs_dev *ffs_dev; + int ret = 0; + + ENTER(); + mutex_lock(ffs_dev_lock); + + ffs_dev = ffs->private_data; + if (!ffs_dev) { + ret = -EINVAL; + goto done; + } + + if (WARN_ON(ffs_dev->desc_ready)) { + ret = -EBUSY; + goto done; + } + ffs_dev->desc_ready = true; + ffs_dev->ffs_data = ffs; + +done: + mutex_unlock(ffs_dev_lock); + return ret; +} + +static void ffs_closed_callback(struct ffs_data *ffs) +{ + struct ffs_dev *ffs_dev; + + ENTER(); + mutex_lock(ffs_dev_lock); + + ffs_dev = ffs->private_data; + if (!ffs_dev) + goto done; + + ffs_dev->desc_ready = false; + if (!ffs_dev->opts || !ffs_dev->opts->func_inst.group.cg_item.ci_parent) + goto done; + + unregister_gadget_item(ffs_dev->opts-> + func_inst.group.cg_item.ci_parent->ci_parent); + +done: + mutex_unlock(ffs_dev_lock); +} + +static void *ffs_acquire_dev_callback(const char *dev_name) +{ + struct ffs_dev *ffs_dev; + + ENTER(); + mutex_lock(ffs_dev_lock); + + ffs_dev = ffs_find_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(ffs_dev_lock); + return ffs_dev; +} + +static inline struct f_fs_opts *to_ffs_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_fs_opts, + func_inst.group); +} + +static void ffs_release_dev_callback(struct ffs_data *ffs_data) +{ + struct ffs_dev *ffs_dev; + + ENTER(); + mutex_lock(ffs_dev_lock); + + ffs_dev = ffs_data->private_data; + if (ffs_dev) + ffs_dev->mounted = false; + + mutex_unlock(ffs_dev_lock); +} + +static void ffs_attr_release(struct config_item *item) +{ + struct f_fs_opts *opts = to_ffs_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations ffs_item_ops = { + .release = ffs_attr_release, +}; + +static struct config_item_type ffs_func_type = { + .ct_item_ops = &ffs_item_ops, + .ct_owner = THIS_MODULE, +}; + + /* Function registration interface ******************************************/ static void do_cleanup(struct kref *ref) @@ -2383,6 +2496,31 @@ static void ffs_free_inst(struct usb_function_instance *f) mutex_unlock(&auto_init_lock); } +#define MAX_INST_NAME_LEN 40 + +static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name) +{ + struct f_fs_opts *opts; + char *ptr; + int name_len; + + name_len = strlen(name) + 1; + if (name_len > MAX_INST_NAME_LEN) + return -ENAMETOOLONG; + + ptr = kstrndup(name, name_len, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + opts = to_f_fs_opts(fi); + if (opts->dev->name_allocated) + kfree(opts->dev->name); + opts->dev->name = ptr; + opts->dev->name_allocated = true; + + return 0; +} + static struct usb_function_instance *ffs_alloc_inst(void) { struct f_fs_opts *opts; @@ -2393,6 +2531,7 @@ static struct usb_function_instance *ffs_alloc_inst(void) if (!opts) return ERR_PTR(-ENOMEM); + opts->func_inst.set_inst_name = ffs_set_inst_name; opts->func_inst.free_func_inst = ffs_free_inst; dev = ffs_alloc_dev(); if (IS_ERR(dev)) { @@ -2400,17 +2539,16 @@ static struct usb_function_instance *ffs_alloc_inst(void) goto error; } opts->dev = dev; + dev->opts = opts; mutex_lock(&auto_init_lock); if (auto_init_active && do_auto_init) { kref_init(&auto_init_ref); - /* - * ffs_set_acquire_dev_cb(); - * ffs_set_release_dev_cb(); - * ffs_set_ready_cb(); - * ffs_set_closed_cb(); - */ - /* fails if callbacks not set */ + ffs_set_acquire_dev_cb(ffs_acquire_dev_callback); + ffs_set_release_dev_cb(ffs_release_dev_callback); + ffs_set_ready_cb(ffs_ready_callback); + ffs_set_closed_cb(ffs_closed_callback); + ffs_dev_lock = &_ffs_dev_lock; ret = functionfs_init(NULL); if (ret) { mutex_unlock(&auto_init_lock); @@ -2427,6 +2565,8 @@ static struct usb_function_instance *ffs_alloc_inst(void) } mutex_unlock(&auto_init_lock); + config_group_init_type_name(&opts->func_inst.group, "", + &ffs_func_type); return &opts->func_inst; error: ffs_free_dev(opts->dev); diff --git a/drivers/usb/gadget/u_fs.h b/drivers/usb/gadget/u_fs.h index e5eca6c..8d1d4db 100644 --- a/drivers/usb/gadget/u_fs.h +++ b/drivers/usb/gadget/u_fs.h @@ -19,6 +19,8 @@ #include <linux/usb/composite.h> #include <linux/list.h> +#define VERBOSE_DEBUG + #ifdef VERBOSE_DEBUG #ifndef pr_vdebug # define pr_vdebug pr_debug @@ -34,12 +36,15 @@ #define ENTER() pr_vdebug("%s()\n", __func__) +struct f_fs_opts; struct ffs_dev { const char *name; + bool name_allocated; bool mounted; bool desc_ready; struct ffs_data *ffs_data; + struct f_fs_opts *opts; struct list_head entry; }; -- 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