]> Pileus Git - ~andy/linux/blobdiff - drivers/usb/gadget/f_mass_storage.c
usb: gadget: storage_common: make attribute operations more generic
[~andy/linux] / drivers / usb / gadget / f_mass_storage.c
index 32e5ab73f74689a9059436963b37a1364b8cf577..d80be5f0e6d233d743a9e985af94aad6ae40d241 100644 (file)
 #include <linux/spinlock.h>
 #include <linux/string.h>
 #include <linux/freezer.h>
+#include <linux/module.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
 #define FSG_DRIVER_DESC                "Mass Storage Function"
 #define FSG_DRIVER_VERSION     "2009/09/11"
 
+/* to avoid a lot of #ifndef-#endif in the temporary compatibility layer */
+#ifndef USB_FMS_INCLUDED
+#define EXPORT_SYMBOL_GPL_IF_MODULE(m) EXPORT_SYMBOL_GPL(m);
+#else
+#define EXPORT_SYMBOL_GPL_IF_MODULE(m)
+#endif
+
 static const char fsg_string_interface[] = "Mass Storage";
 
 #include "storage_common.h"
@@ -299,6 +307,7 @@ struct fsg_common {
        unsigned int            short_packet_received:1;
        unsigned int            bad_lun_okay:1;
        unsigned int            running:1;
+       unsigned int            sysfs:1;
 
        int                     thread_wakeup_needed;
        struct completion       thread_notifier;
@@ -2572,37 +2581,52 @@ static int fsg_main_thread(void *common_)
 
 static ssize_t ro_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       return fsg_show_ro(dev, attr, buf);
+       struct fsg_lun          *curlun = fsg_lun_from_dev(dev);
+
+       return fsg_show_ro(curlun, buf);
 }
 
 static ssize_t nofua_show(struct device *dev, struct device_attribute *attr,
                          char *buf)
 {
-       return fsg_show_nofua(dev, attr, buf);
+       struct fsg_lun          *curlun = fsg_lun_from_dev(dev);
+
+       return fsg_show_nofua(curlun, buf);
 }
 
 static ssize_t file_show(struct device *dev, struct device_attribute *attr,
                         char *buf)
 {
-       return fsg_show_file(dev, attr, buf);
+       struct fsg_lun          *curlun = fsg_lun_from_dev(dev);
+       struct rw_semaphore     *filesem = dev_get_drvdata(dev);
+
+       return fsg_show_file(curlun, filesem, buf);
 }
 
 static ssize_t ro_store(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count)
 {
-       return fsg_store_ro(dev, attr, buf, count);
+       struct fsg_lun          *curlun = fsg_lun_from_dev(dev);
+       struct rw_semaphore     *filesem = dev_get_drvdata(dev);
+
+       return fsg_store_ro(curlun, filesem, buf, count);
 }
 
 static ssize_t nofua_store(struct device *dev, struct device_attribute *attr,
                           const char *buf, size_t count)
 {
-       return fsg_store_nofua(dev, attr, buf, count);
+       struct fsg_lun          *curlun = fsg_lun_from_dev(dev);
+
+       return fsg_store_nofua(curlun, buf, count);
 }
 
 static ssize_t file_store(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t count)
 {
-       return fsg_store_file(dev, attr, buf, count);
+       struct fsg_lun          *curlun = fsg_lun_from_dev(dev);
+       struct rw_semaphore     *filesem = dev_get_drvdata(dev);
+
+       return fsg_store_file(curlun, filesem, buf, count);
 }
 
 static DEVICE_ATTR_RW(ro);
@@ -2626,11 +2650,13 @@ void fsg_common_get(struct fsg_common *common)
 {
        kref_get(&common->ref);
 }
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_get);
 
 void fsg_common_put(struct fsg_common *common)
 {
        kref_put(&common->ref, fsg_common_release);
 }
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_put);
 
 /* check if fsg_num_buffers is within a valid range */
 static inline int fsg_num_buffers_validate(unsigned int fsg_num_buffers)
@@ -2642,224 +2668,456 @@ static inline int fsg_num_buffers_validate(unsigned int fsg_num_buffers)
        return -EINVAL;
 }
 
-struct fsg_common *fsg_common_init(struct fsg_common *common,
-                                  struct usb_composite_dev *cdev,
-                                  struct fsg_config *cfg)
+static struct fsg_common *fsg_common_setup(struct fsg_common *common, bool zero)
 {
-       struct usb_gadget *gadget = cdev->gadget;
-       struct fsg_buffhd *bh;
-       struct fsg_lun **curlun_it;
-       struct fsg_lun_config *lcfg;
-       struct usb_string *us;
-       int nluns, i, rc;
-       char *pathbuf;
-
-       rc = fsg_num_buffers_validate(cfg->fsg_num_buffers);
-       if (rc != 0)
-               return ERR_PTR(rc);
-
-       /* Find out how many LUNs there should be */
-       nluns = cfg->nluns;
-       if (nluns < 1 || nluns > FSG_MAX_LUNS) {
-               dev_err(&gadget->dev, "invalid number of LUNs: %u\n", nluns);
-               return ERR_PTR(-EINVAL);
-       }
-
-       /* Allocate? */
        if (!common) {
-               common = kzalloc(sizeof *common, GFP_KERNEL);
+               common = kzalloc(sizeof(*common), GFP_KERNEL);
                if (!common)
                        return ERR_PTR(-ENOMEM);
                common->free_storage_on_release = 1;
        } else {
-               memset(common, 0, sizeof *common);
+               if (zero)
+                       memset(common, 0, sizeof(*common));
                common->free_storage_on_release = 0;
        }
+       init_rwsem(&common->filesem);
+       spin_lock_init(&common->lock);
+       kref_init(&common->ref);
+       init_completion(&common->thread_notifier);
+       init_waitqueue_head(&common->fsg_wait);
+       common->state = FSG_STATE_TERMINATED;
 
-       common->fsg_num_buffers = cfg->fsg_num_buffers;
-       common->buffhds = kcalloc(common->fsg_num_buffers,
-                                 sizeof *(common->buffhds), GFP_KERNEL);
-       if (!common->buffhds) {
-               if (common->free_storage_on_release)
-                       kfree(common);
-               return ERR_PTR(-ENOMEM);
+       return common;
+}
+
+void fsg_common_set_sysfs(struct fsg_common *common, bool sysfs)
+{
+       common->sysfs = sysfs;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_sysfs);
+
+static void _fsg_common_free_buffers(struct fsg_buffhd *buffhds, unsigned n)
+{
+       if (buffhds) {
+               struct fsg_buffhd *bh = buffhds;
+               while (n--) {
+                       kfree(bh->buf);
+                       ++bh;
+               }
+               kfree(buffhds);
        }
+}
 
-       common->ops = cfg->ops;
-       common->private_data = cfg->private_data;
+int fsg_common_set_num_buffers(struct fsg_common *common, unsigned int n)
+{
+       struct fsg_buffhd *bh, *buffhds;
+       int i, rc;
+
+       rc = fsg_num_buffers_validate(n);
+       if (rc != 0)
+               return rc;
+
+       buffhds = kcalloc(n, sizeof(*buffhds), GFP_KERNEL);
+       if (!buffhds)
+               return -ENOMEM;
+
+       /* Data buffers cyclic list */
+       bh = buffhds;
+       i = n;
+       goto buffhds_first_it;
+       do {
+               bh->next = bh + 1;
+               ++bh;
+buffhds_first_it:
+               bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL);
+               if (unlikely(!bh->buf))
+                       goto error_release;
+       } while (--i);
+       bh->next = buffhds;
+
+       _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
+       common->fsg_num_buffers = n;
+       common->buffhds = buffhds;
+
+       return 0;
+
+error_release:
+       /*
+        * "buf"s pointed to by heads after n - i are NULL
+        * so releasing them won't hurt
+        */
+       _fsg_common_free_buffers(buffhds, n);
+
+       return -ENOMEM;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_num_buffers);
+
+static inline void fsg_common_remove_sysfs(struct fsg_lun *lun)
+{
+       device_remove_file(&lun->dev, &dev_attr_nofua);
+       /*
+        * device_remove_file() =>
+        *
+        * here the attr (e.g. dev_attr_ro) is only used to be passed to:
+        *
+        *      sysfs_remove_file() =>
+        *
+        *      here e.g. both dev_attr_ro_cdrom and dev_attr_ro are in
+        *      the same namespace and
+        *      from here only attr->name is passed to:
+        *
+        *              sysfs_hash_and_remove()
+        *
+        *              attr->name is the same for dev_attr_ro_cdrom and
+        *              dev_attr_ro
+        *              attr->name is the same for dev_attr_file and
+        *              dev_attr_file_nonremovable
+        *
+        * so we don't differentiate between removing e.g. dev_attr_ro_cdrom
+        * and dev_attr_ro
+        */
+       device_remove_file(&lun->dev, &dev_attr_ro);
+       device_remove_file(&lun->dev, &dev_attr_file);
+}
+
+void fsg_common_remove_lun(struct fsg_lun *lun, bool sysfs)
+{
+       if (sysfs) {
+               fsg_common_remove_sysfs(lun);
+               device_unregister(&lun->dev);
+       }
+       fsg_lun_close(lun);
+       kfree(lun);
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_remove_lun);
+
+static void _fsg_common_remove_luns(struct fsg_common *common, int n)
+{
+       int i;
 
-       common->gadget = gadget;
-       common->ep0 = gadget->ep0;
+       for (i = 0; i < n; ++i)
+               if (common->luns[i]) {
+                       fsg_common_remove_lun(common->luns[i], common->sysfs);
+                       common->luns[i] = NULL;
+               }
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_remove_luns);
+
+void fsg_common_remove_luns(struct fsg_common *common)
+{
+       _fsg_common_remove_luns(common, common->nluns);
+}
+
+void fsg_common_free_luns(struct fsg_common *common)
+{
+       fsg_common_remove_luns(common);
+       kfree(common->luns);
+       common->luns = NULL;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_free_luns);
+
+int fsg_common_set_nluns(struct fsg_common *common, int nluns)
+{
+       struct fsg_lun **curlun;
+
+       /* Find out how many LUNs there should be */
+       if (nluns < 1 || nluns > FSG_MAX_LUNS) {
+               pr_err("invalid number of LUNs: %u\n", nluns);
+               return -EINVAL;
+       }
+
+       curlun = kcalloc(nluns, sizeof(*curlun), GFP_KERNEL);
+       if (unlikely(!curlun))
+               return -ENOMEM;
+
+       if (common->luns)
+               fsg_common_free_luns(common);
+
+       common->luns = curlun;
+       common->nluns = nluns;
+
+       pr_info("Number of LUNs=%d\n", common->nluns);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_nluns);
+
+void fsg_common_set_ops(struct fsg_common *common,
+                       const struct fsg_operations *ops)
+{
+       common->ops = ops;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_ops);
+
+void fsg_common_free_buffers(struct fsg_common *common)
+{
+       _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
+       common->buffhds = NULL;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_free_buffers);
+
+int fsg_common_set_cdev(struct fsg_common *common,
+                        struct usb_composite_dev *cdev, bool can_stall)
+{
+       struct usb_string *us;
+
+       common->gadget = cdev->gadget;
+       common->ep0 = cdev->gadget->ep0;
        common->ep0req = cdev->req;
        common->cdev = cdev;
 
        us = usb_gstrings_attach(cdev, fsg_strings_array,
                                 ARRAY_SIZE(fsg_strings));
-       if (IS_ERR(us)) {
-               rc = PTR_ERR(us);
-               goto error_release;
-       }
+       if (IS_ERR(us))
+               return PTR_ERR(us);
+
        fsg_intf_desc.iInterface = us[FSG_STRING_INTERFACE].id;
 
        /*
-        * Create the LUNs, open their backing files, and register the
-        * LUN devices in sysfs.
+        * Some peripheral controllers are known not to be able to
+        * halt bulk endpoints correctly.  If one of them is present,
+        * disable stalls.
         */
-       curlun_it = kcalloc(nluns, sizeof(*curlun_it), GFP_KERNEL);
-       if (unlikely(!curlun_it)) {
-               rc = -ENOMEM;
-               goto error_release;
+       common->can_stall = can_stall && !(gadget_is_at91(common->gadget));
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_cdev);
+
+static inline int fsg_common_add_sysfs(struct fsg_common *common,
+                                      struct fsg_lun *lun)
+{
+       int rc;
+
+       rc = device_register(&lun->dev);
+       if (rc) {
+               put_device(&lun->dev);
+               return rc;
        }
-       common->luns = curlun_it;
 
-       init_rwsem(&common->filesem);
+       rc = device_create_file(&lun->dev,
+                               lun->cdrom
+                             ? &dev_attr_ro_cdrom
+                             : &dev_attr_ro);
+       if (rc)
+               goto error;
+       rc = device_create_file(&lun->dev,
+                               lun->removable
+                             ? &dev_attr_file
+                             : &dev_attr_file_nonremovable);
+       if (rc)
+               goto error;
+       rc = device_create_file(&lun->dev, &dev_attr_nofua);
+       if (rc)
+               goto error;
 
-       for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun_it, ++lcfg) {
-               struct fsg_lun *curlun;
+       return 0;
 
-               curlun = kzalloc(sizeof(*curlun), GFP_KERNEL);
-               if (!curlun) {
-                       rc = -ENOMEM;
-                       common->nluns = i;
-                       goto error_release;
-               }
-               *curlun_it = curlun;
-
-               curlun->cdrom = !!lcfg->cdrom;
-               curlun->ro = lcfg->cdrom || lcfg->ro;
-               curlun->initially_ro = curlun->ro;
-               curlun->removable = lcfg->removable;
-               curlun->dev.release = fsg_lun_release;
-               curlun->dev.parent = &gadget->dev;
-               /* curlun->dev.driver = &fsg_driver.driver; XXX */
-               dev_set_drvdata(&curlun->dev, &common->filesem);
-               dev_set_name(&curlun->dev, "lun%d", i);
-
-               rc = device_register(&curlun->dev);
+error:
+       /* removing nonexistent files is a no-op */
+       fsg_common_remove_sysfs(lun);
+       device_unregister(&lun->dev);
+       return rc;
+}
+
+int fsg_common_create_lun(struct fsg_common *common, struct fsg_lun_config *cfg,
+                         unsigned int id, const char *name,
+                         const char **name_pfx)
+{
+       struct fsg_lun *lun;
+       char *pathbuf, *p;
+       int rc = -ENOMEM;
+
+       if (!common->nluns || !common->luns)
+               return -ENODEV;
+
+       if (common->luns[id])
+               return -EBUSY;
+
+       if (!cfg->filename && !cfg->removable) {
+               pr_err("no file given for LUN%d\n", id);
+               return -EINVAL;
+       }
+
+       lun = kzalloc(sizeof(*lun), GFP_KERNEL);
+       if (!lun)
+               return -ENOMEM;
+
+       lun->name_pfx = name_pfx;
+
+       lun->cdrom = !!cfg->cdrom;
+       lun->ro = cfg->cdrom || cfg->ro;
+       lun->initially_ro = lun->ro;
+       lun->removable = !!cfg->removable;
+
+       if (!common->sysfs) {
+               /* we DON'T own the name!*/
+               lun->name = name;
+       } else {
+               lun->dev.release = fsg_lun_release;
+               lun->dev.parent = &common->gadget->dev;
+               dev_set_drvdata(&lun->dev, &common->filesem);
+               dev_set_name(&lun->dev, name);
+               lun->name = dev_name(&lun->dev);
+
+               rc = fsg_common_add_sysfs(common, lun);
                if (rc) {
-                       INFO(common, "failed to register LUN%d: %d\n", i, rc);
-                       common->nluns = i;
-                       put_device(&curlun->dev);
-                       kfree(curlun);
-                       goto error_release;
+                       pr_info("failed to register LUN%d: %d\n", id, rc);
+                       goto error_sysfs;
                }
+       }
 
-               rc = device_create_file(&curlun->dev,
-                                       curlun->cdrom
-                                     ? &dev_attr_ro_cdrom
-                                     : &dev_attr_ro);
-               if (rc)
-                       goto error_luns;
-               rc = device_create_file(&curlun->dev,
-                                       curlun->removable
-                                     ? &dev_attr_file
-                                     : &dev_attr_file_nonremovable);
-               if (rc)
-                       goto error_luns;
-               rc = device_create_file(&curlun->dev, &dev_attr_nofua);
+       common->luns[id] = lun;
+
+       if (cfg->filename) {
+               rc = fsg_lun_open(lun, cfg->filename);
                if (rc)
-                       goto error_luns;
+                       goto error_lun;
+       }
 
-               if (lcfg->filename) {
-                       rc = fsg_lun_open(curlun, lcfg->filename);
-                       if (rc)
-                               goto error_luns;
-               } else if (!curlun->removable) {
-                       ERROR(common, "no file given for LUN%d\n", i);
-                       rc = -EINVAL;
-                       goto error_luns;
+       pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+       p = "(no medium)";
+       if (fsg_lun_is_open(lun)) {
+               p = "(error)";
+               if (pathbuf) {
+                       p = d_path(&lun->filp->f_path, pathbuf, PATH_MAX);
+                       if (IS_ERR(p))
+                               p = "(error)";
                }
        }
-       common->nluns = nluns;
+       pr_info("LUN: %s%s%sfile: %s\n",
+             lun->removable ? "removable " : "",
+             lun->ro ? "read only " : "",
+             lun->cdrom ? "CD-ROM " : "",
+             p);
+       kfree(pathbuf);
 
-       /* Data buffers cyclic list */
-       bh = common->buffhds;
-       i = common->fsg_num_buffers;
-       goto buffhds_first_it;
-       do {
-               bh->next = bh + 1;
-               ++bh;
-buffhds_first_it:
-               bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL);
-               if (unlikely(!bh->buf)) {
-                       rc = -ENOMEM;
-                       goto error_release;
-               }
-       } while (--i);
-       bh->next = common->buffhds;
+       return 0;
+
+error_lun:
+       if (common->sysfs) {
+               fsg_common_remove_sysfs(lun);
+               device_unregister(&lun->dev);
+       }
+       fsg_lun_close(lun);
+       common->luns[id] = NULL;
+error_sysfs:
+       kfree(lun);
+       return rc;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_create_lun);
+
+int fsg_common_create_luns(struct fsg_common *common, struct fsg_config *cfg)
+{
+       char buf[8]; /* enough for 100000000 different numbers, decimal */
+       int i, rc;
+
+       for (i = 0; i < common->nluns; ++i) {
+               snprintf(buf, sizeof(buf), "lun%d", i);
+               rc = fsg_common_create_lun(common, &cfg->luns[i], i, buf, NULL);
+               if (rc)
+                       goto fail;
+       }
+
+       pr_info("Number of LUNs=%d\n", common->nluns);
+
+       return 0;
+
+fail:
+       _fsg_common_remove_luns(common, i);
+       return rc;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_create_luns);
+
+void fsg_common_set_inquiry_string(struct fsg_common *common, const char *vn,
+                                  const char *pn)
+{
+       int i;
 
        /* Prepare inquiryString */
        i = get_default_bcdDevice();
-       snprintf(common->inquiry_string, sizeof common->inquiry_string,
-                "%-8s%-16s%04x", cfg->vendor_name ?: "Linux",
+       snprintf(common->inquiry_string, sizeof(common->inquiry_string),
+                "%-8s%-16s%04x", vn ?: "Linux",
                 /* Assume product name dependent on the first LUN */
-                cfg->product_name ?: ((*common->luns)->cdrom
-                                    ? "File-CD Gadget"
-                                    : "File-Stor Gadget"),
+                pn ?: ((*common->luns)->cdrom
+                    ? "File-CD Gadget"
+                    : "File-Stor Gadget"),
                 i);
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_inquiry_string);
 
-       /*
-        * Some peripheral controllers are known not to be able to
-        * halt bulk endpoints correctly.  If one of them is present,
-        * disable stalls.
-        */
-       common->can_stall = cfg->can_stall &&
-               !(gadget_is_at91(common->gadget));
-
-       spin_lock_init(&common->lock);
-       kref_init(&common->ref);
-
+int fsg_common_run_thread(struct fsg_common *common)
+{
+       common->state = FSG_STATE_IDLE;
        /* Tell the thread to start working */
        common->thread_task =
                kthread_create(fsg_main_thread, common, "file-storage");
        if (IS_ERR(common->thread_task)) {
-               rc = PTR_ERR(common->thread_task);
-               goto error_release;
+               common->state = FSG_STATE_TERMINATED;
+               return PTR_ERR(common->thread_task);
        }
-       init_completion(&common->thread_notifier);
-       init_waitqueue_head(&common->fsg_wait);
 
-       /* Information */
-       INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
-       INFO(common, "Number of LUNs=%d\n", common->nluns);
+       DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task));
 
-       pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
-       for (i = 0, nluns = common->nluns, curlun_it = common->luns;
-            i < nluns;
-            ++curlun_it, ++i) {
-               struct fsg_lun *curlun = *curlun_it;
-               char *p = "(no medium)";
-               if (fsg_lun_is_open(curlun)) {
-                       p = "(error)";
-                       if (pathbuf) {
-                               p = d_path(&curlun->filp->f_path,
-                                          pathbuf, PATH_MAX);
-                               if (IS_ERR(p))
-                                       p = "(error)";
-                       }
-               }
-               LINFO(curlun, "LUN: %s%s%sfile: %s\n",
-                     curlun->removable ? "removable " : "",
-                     curlun->ro ? "read only " : "",
-                     curlun->cdrom ? "CD-ROM " : "",
-                     p);
+       wake_up_process(common->thread_task);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_run_thread);
+
+struct fsg_common *fsg_common_init(struct fsg_common *common,
+                                  struct usb_composite_dev *cdev,
+                                  struct fsg_config *cfg)
+{
+       int rc;
+
+       common = fsg_common_setup(common, !!common);
+       if (IS_ERR(common))
+               return common;
+       fsg_common_set_sysfs(common, true);
+       common->state = FSG_STATE_IDLE;
+
+       rc = fsg_common_set_num_buffers(common, cfg->fsg_num_buffers);
+       if (rc) {
+               if (common->free_storage_on_release)
+                       kfree(common);
+               return ERR_PTR(rc);
        }
-       kfree(pathbuf);
+       common->ops = cfg->ops;
+       common->private_data = cfg->private_data;
 
-       DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task));
+       rc = fsg_common_set_cdev(common, cdev, cfg->can_stall);
+       if (rc)
+               goto error_release;
 
-       wake_up_process(common->thread_task);
+       rc = fsg_common_set_nluns(common, cfg->nluns);
+       if (rc)
+               goto error_release;
+
+       rc = fsg_common_create_luns(common, cfg);
+       if (rc)
+               goto error_release;
+
+
+       fsg_common_set_inquiry_string(common, cfg->vendor_name,
+                                     cfg->product_name);
+
+       /* Information */
+       INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
+
+       rc = fsg_common_run_thread(common);
+       if (rc)
+               goto error_release;
 
        return common;
 
-error_luns:
-       common->nluns = i + 1;
 error_release:
        common->state = FSG_STATE_TERMINATED;   /* The thread is dead */
        /* Call fsg_common_release() directly, ref might be not initialised. */
        fsg_common_release(&common->ref);
        return ERR_PTR(rc);
 }
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_init);
 
 static void fsg_common_release(struct kref *ref)
 {
@@ -2880,32 +3138,18 @@ static void fsg_common_release(struct kref *ref)
                        struct fsg_lun *lun = *lun_it;
                        if (!lun)
                                continue;
-                       device_remove_file(&lun->dev, &dev_attr_nofua);
-                       device_remove_file(&lun->dev,
-                                          lun->cdrom
-                                        ? &dev_attr_ro_cdrom
-                                        : &dev_attr_ro);
-                       device_remove_file(&lun->dev,
-                                          lun->removable
-                                        ? &dev_attr_file
-                                        : &dev_attr_file_nonremovable);
+                       if (common->sysfs)
+                               fsg_common_remove_sysfs(lun);
                        fsg_lun_close(lun);
-                       device_unregister(&lun->dev);
+                       if (common->sysfs)
+                               device_unregister(&lun->dev);
                        kfree(lun);
                }
 
                kfree(common->luns);
        }
 
-       {
-               struct fsg_buffhd *bh = common->buffhds;
-               unsigned i = common->fsg_num_buffers;
-               do {
-                       kfree(bh->buf);
-               } while (++bh, --i);
-       }
-
-       kfree(common->buffhds);
+       _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
        if (common->free_storage_on_release)
                kfree(common);
 }
@@ -2913,24 +3157,6 @@ static void fsg_common_release(struct kref *ref)
 
 /*-------------------------------------------------------------------------*/
 
-static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
-{
-       struct fsg_dev          *fsg = fsg_from_func(f);
-       struct fsg_common       *common = fsg->common;
-
-       DBG(fsg, "unbind\n");
-       if (fsg->common->fsg == fsg) {
-               fsg->common->new_fsg = NULL;
-               raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE);
-               /* FIXME: make interruptible or killable somehow? */
-               wait_event(common->fsg_wait, common->fsg != fsg);
-       }
-
-       fsg_common_put(common);
-       usb_free_all_descriptors(&fsg->function);
-       kfree(fsg);
-}
-
 static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
 {
        struct fsg_dev          *fsg = fsg_from_func(f);
@@ -2940,6 +3166,21 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
        unsigned                max_burst;
        int                     ret;
 
+#ifndef USB_FMS_INCLUDED
+       struct fsg_opts         *opts;
+       opts = fsg_opts_from_func_inst(f->fi);
+       if (!opts->no_configfs) {
+               ret = fsg_common_set_cdev(fsg->common, c->cdev,
+                                         fsg->common->can_stall);
+               if (ret)
+                       return ret;
+               fsg_common_set_inquiry_string(fsg->common, 0, 0);
+               ret = fsg_common_run_thread(fsg->common);
+               if (ret)
+                       return ret;
+       }
+#endif
+
        fsg->gadget = gadget;
 
        /* New interface */
@@ -2991,7 +3232,31 @@ autoconf_fail:
        return -ENOTSUPP;
 }
 
-/****************************** ADD FUNCTION ******************************/
+/****************************** ALLOCATE FUNCTION *************************/
+
+static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+       struct fsg_dev          *fsg = fsg_from_func(f);
+       struct fsg_common       *common = fsg->common;
+
+       DBG(fsg, "unbind\n");
+       if (fsg->common->fsg == fsg) {
+               fsg->common->new_fsg = NULL;
+               raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE);
+               /* FIXME: make interruptible or killable somehow? */
+               wait_event(common->fsg_wait, common->fsg != fsg);
+       }
+
+#ifdef USB_FMS_INCLUDED
+       fsg_common_put(common);
+#endif
+       usb_free_all_descriptors(&fsg->function);
+#ifdef USB_FMS_INCLUDED
+       kfree(fsg);
+#endif
+}
+
+#ifdef USB_FMS_INCLUDED
 
 static int fsg_bind_config(struct usb_composite_dev *cdev,
                           struct usb_configuration *c,
@@ -3028,6 +3293,88 @@ static int fsg_bind_config(struct usb_composite_dev *cdev,
        return rc;
 }
 
+#else
+
+static void fsg_free_inst(struct usb_function_instance *fi)
+{
+       struct fsg_opts *opts;
+
+       opts = fsg_opts_from_func_inst(fi);
+       fsg_common_put(opts->common);
+       kfree(opts);
+}
+
+static struct usb_function_instance *fsg_alloc_inst(void)
+{
+       struct fsg_opts *opts;
+       int rc;
+
+       opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+       if (!opts)
+               return ERR_PTR(-ENOMEM);
+       opts->func_inst.free_func_inst = fsg_free_inst;
+       opts->common = fsg_common_setup(opts->common, false);
+       if (IS_ERR(opts->common)) {
+               rc = PTR_ERR(opts->common);
+               goto release_opts;
+       }
+       rc = fsg_common_set_nluns(opts->common, FSG_MAX_LUNS);
+       if (rc)
+               goto release_opts;
+
+       rc = fsg_common_set_num_buffers(opts->common,
+                                       CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS);
+       if (rc)
+               goto release_luns;
+
+       pr_info(FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
+
+       return &opts->func_inst;
+
+release_luns:
+       kfree(opts->common->luns);
+release_opts:
+       kfree(opts);
+       return ERR_PTR(rc);
+}
+
+static void fsg_free(struct usb_function *f)
+{
+       struct fsg_dev *fsg;
+
+       fsg = container_of(f, struct fsg_dev, function);
+
+       kfree(fsg);
+}
+
+static struct usb_function *fsg_alloc(struct usb_function_instance *fi)
+{
+       struct fsg_opts *opts = fsg_opts_from_func_inst(fi);
+       struct fsg_common *common = opts->common;
+       struct fsg_dev *fsg;
+
+       fsg = kzalloc(sizeof(*fsg), GFP_KERNEL);
+       if (unlikely(!fsg))
+               return ERR_PTR(-ENOMEM);
+
+       fsg->function.name      = FSG_DRIVER_DESC;
+       fsg->function.bind      = fsg_bind;
+       fsg->function.unbind    = fsg_unbind;
+       fsg->function.setup     = fsg_setup;
+       fsg->function.set_alt   = fsg_set_alt;
+       fsg->function.disable   = fsg_disable;
+       fsg->function.free_func = fsg_free;
+
+       fsg->common               = common;
+
+       return &fsg->function;
+}
+
+DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Michal Nazarewicz");
+
+#endif
 
 /************************* Module parameters *************************/
 
@@ -3064,4 +3411,5 @@ void fsg_config_from_params(struct fsg_config *cfg,
        cfg->can_stall = params->stall;
        cfg->fsg_num_buffers = fsg_num_buffers;
 }
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_config_from_params);