#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"
{
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)
return -EINVAL;
}
-static struct fsg_common *fsg_common_setup(struct fsg_common *common)
+static struct fsg_common *fsg_common_setup(struct fsg_common *common, bool zero)
{
if (!common) {
common = kzalloc(sizeof(*common), GFP_KERNEL);
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);
{
common->sysfs = sysfs;
}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_sysfs);
static void _fsg_common_free_buffers(struct fsg_buffhd *buffhds, unsigned n)
{
return -ENOMEM;
}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_num_buffers);
static inline void fsg_common_remove_sysfs(struct fsg_lun *lun)
{
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)
{
common->luns[i] = NULL;
}
}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_remove_luns);
void fsg_common_remove_luns(struct fsg_common *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)
{
return 0;
}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_nluns);
-struct fsg_common *fsg_common_init(struct fsg_common *common,
- struct usb_composite_dev *cdev,
- struct fsg_config *cfg)
+void fsg_common_free_buffers(struct fsg_common *common)
{
- struct usb_gadget *gadget = cdev->gadget;
- struct fsg_lun **curlun_it;
- struct fsg_lun_config *lcfg;
- struct usb_string *us;
- int nluns, i, rc;
- char *pathbuf;
-
-
- common = fsg_common_setup(common);
- if (IS_ERR(common))
- return common;
- fsg_common_set_sysfs(common, true);
- common->state = FSG_STATE_IDLE;
+ _fsg_common_free_buffers(common->buffhds, common->fsg_num_buffers);
+ common->buffhds = NULL;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_free_buffers);
- 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);
- }
- common->ops = cfg->ops;
- common->private_data = cfg->private_data;
+int fsg_common_set_cdev(struct fsg_common *common,
+ struct usb_composite_dev *cdev, bool can_stall)
+{
+ struct usb_string *us;
- common->gadget = gadget;
- common->ep0 = gadget->ep0;
+ 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;
+ /*
+ * 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 = can_stall && !(gadget_is_at91(common->gadget));
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_common_set_cdev);
- rc = fsg_common_set_nluns(common, cfg->nluns);
+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;
+ }
+
+ rc = device_create_file(&lun->dev,
+ lun->cdrom
+ ? &dev_attr_ro_cdrom
+ : &dev_attr_ro);
if (rc)
- goto error_release;
- curlun_it = common->luns;
- nluns = cfg->nluns;
- for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun_it, ++lcfg) {
- struct fsg_lun *curlun;
-
- 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);
- curlun->name = dev_name(&curlun->dev);
-
- rc = device_register(&curlun->dev);
+ 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;
+
+ return 0;
+
+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)";
}
}
+ pr_info("LUN: %s%s%sfile: %s\n",
+ lun->removable ? "removable " : "",
+ lun->ro ? "read only " : "",
+ lun->cdrom ? "CD-ROM " : "",
+ p);
+ kfree(pathbuf);
+
+ 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));
-
-
+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);
}
- /* 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)
{
/*-------------------------------------------------------------------------*/
-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);
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 */
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,
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 *************************/
cfg->can_stall = params->stall;
cfg->fsg_num_buffers = fsg_num_buffers;
}
+EXPORT_SYMBOL_GPL_IF_MODULE(fsg_config_from_params);