|
@@ -213,12 +213,14 @@
|
|
|
#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>
|
|
|
#include <linux/usb/composite.h>
|
|
|
|
|
|
#include "gadget_chips.h"
|
|
|
+#include "configfs.h"
|
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
@@ -228,26 +230,30 @@
|
|
|
|
|
|
static const char fsg_string_interface[] = "Mass Storage";
|
|
|
|
|
|
-#include "storage_common.c"
|
|
|
+#include "storage_common.h"
|
|
|
+#include "f_mass_storage.h"
|
|
|
|
|
|
+/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */
|
|
|
+static struct usb_string fsg_strings[] = {
|
|
|
+ {FSG_STRING_INTERFACE, fsg_string_interface},
|
|
|
+ {}
|
|
|
+};
|
|
|
+
|
|
|
+static struct usb_gadget_strings fsg_stringtab = {
|
|
|
+ .language = 0x0409, /* en-us */
|
|
|
+ .strings = fsg_strings,
|
|
|
+};
|
|
|
+
|
|
|
+static struct usb_gadget_strings *fsg_strings_array[] = {
|
|
|
+ &fsg_stringtab,
|
|
|
+ NULL,
|
|
|
+};
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
struct fsg_dev;
|
|
|
struct fsg_common;
|
|
|
|
|
|
-/* FSF callback functions */
|
|
|
-struct fsg_operations {
|
|
|
- /*
|
|
|
- * Callback function to call when thread exits. If no
|
|
|
- * callback is set or it returns value lower then zero MSF
|
|
|
- * will force eject all LUNs it operates on (including those
|
|
|
- * marked as non-removable or with prevent_medium_removal flag
|
|
|
- * set).
|
|
|
- */
|
|
|
- int (*thread_exits)(struct fsg_common *common);
|
|
|
-};
|
|
|
-
|
|
|
/* Data shared by all the FSG instances. */
|
|
|
struct fsg_common {
|
|
|
struct usb_gadget *gadget;
|
|
@@ -268,13 +274,14 @@ struct fsg_common {
|
|
|
struct fsg_buffhd *next_buffhd_to_fill;
|
|
|
struct fsg_buffhd *next_buffhd_to_drain;
|
|
|
struct fsg_buffhd *buffhds;
|
|
|
+ unsigned int fsg_num_buffers;
|
|
|
|
|
|
int cmnd_size;
|
|
|
u8 cmnd[MAX_COMMAND_SIZE];
|
|
|
|
|
|
unsigned int nluns;
|
|
|
unsigned int lun;
|
|
|
- struct fsg_lun *luns;
|
|
|
+ struct fsg_lun **luns;
|
|
|
struct fsg_lun *curlun;
|
|
|
|
|
|
unsigned int bulk_out_maxpacket;
|
|
@@ -294,6 +301,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;
|
|
@@ -313,27 +321,6 @@ struct fsg_common {
|
|
|
struct kref ref;
|
|
|
};
|
|
|
|
|
|
-struct fsg_config {
|
|
|
- unsigned nluns;
|
|
|
- struct fsg_lun_config {
|
|
|
- const char *filename;
|
|
|
- char ro;
|
|
|
- char removable;
|
|
|
- char cdrom;
|
|
|
- char nofua;
|
|
|
- } luns[FSG_MAX_LUNS];
|
|
|
-
|
|
|
- /* Callback functions. */
|
|
|
- const struct fsg_operations *ops;
|
|
|
- /* Gadget's private data. */
|
|
|
- void *private_data;
|
|
|
-
|
|
|
- const char *vendor_name; /* 8 characters or less */
|
|
|
- const char *product_name; /* 16 characters or less */
|
|
|
-
|
|
|
- char can_stall;
|
|
|
-};
|
|
|
-
|
|
|
struct fsg_dev {
|
|
|
struct usb_function function;
|
|
|
struct usb_gadget *gadget; /* Copy of cdev->gadget */
|
|
@@ -2172,7 +2159,7 @@ static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh)
|
|
|
common->data_dir = DATA_DIR_NONE;
|
|
|
common->lun = cbw->Lun;
|
|
|
if (common->lun < common->nluns)
|
|
|
- common->curlun = &common->luns[common->lun];
|
|
|
+ common->curlun = common->luns[common->lun];
|
|
|
else
|
|
|
common->curlun = NULL;
|
|
|
common->tag = cbw->Tag;
|
|
@@ -2244,7 +2231,7 @@ reset:
|
|
|
if (common->fsg) {
|
|
|
fsg = common->fsg;
|
|
|
|
|
|
- for (i = 0; i < fsg_num_buffers; ++i) {
|
|
|
+ for (i = 0; i < common->fsg_num_buffers; ++i) {
|
|
|
struct fsg_buffhd *bh = &common->buffhds[i];
|
|
|
|
|
|
if (bh->inreq) {
|
|
@@ -2303,7 +2290,7 @@ reset:
|
|
|
clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
|
|
|
|
|
|
/* Allocate the requests */
|
|
|
- for (i = 0; i < fsg_num_buffers; ++i) {
|
|
|
+ for (i = 0; i < common->fsg_num_buffers; ++i) {
|
|
|
struct fsg_buffhd *bh = &common->buffhds[i];
|
|
|
|
|
|
rc = alloc_request(common, fsg->bulk_in, &bh->inreq);
|
|
@@ -2320,7 +2307,9 @@ reset:
|
|
|
|
|
|
common->running = 1;
|
|
|
for (i = 0; i < common->nluns; ++i)
|
|
|
- common->luns[i].unit_attention_data = SS_RESET_OCCURRED;
|
|
|
+ if (common->luns[i])
|
|
|
+ common->luns[i]->unit_attention_data =
|
|
|
+ SS_RESET_OCCURRED;
|
|
|
return rc;
|
|
|
}
|
|
|
|
|
@@ -2372,7 +2361,7 @@ static void handle_exception(struct fsg_common *common)
|
|
|
|
|
|
/* Cancel all the pending transfers */
|
|
|
if (likely(common->fsg)) {
|
|
|
- for (i = 0; i < fsg_num_buffers; ++i) {
|
|
|
+ for (i = 0; i < common->fsg_num_buffers; ++i) {
|
|
|
bh = &common->buffhds[i];
|
|
|
if (bh->inreq_busy)
|
|
|
usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
|
|
@@ -2384,7 +2373,7 @@ static void handle_exception(struct fsg_common *common)
|
|
|
/* Wait until everything is idle */
|
|
|
for (;;) {
|
|
|
int num_active = 0;
|
|
|
- for (i = 0; i < fsg_num_buffers; ++i) {
|
|
|
+ for (i = 0; i < common->fsg_num_buffers; ++i) {
|
|
|
bh = &common->buffhds[i];
|
|
|
num_active += bh->inreq_busy + bh->outreq_busy;
|
|
|
}
|
|
@@ -2407,7 +2396,7 @@ static void handle_exception(struct fsg_common *common)
|
|
|
*/
|
|
|
spin_lock_irq(&common->lock);
|
|
|
|
|
|
- for (i = 0; i < fsg_num_buffers; ++i) {
|
|
|
+ for (i = 0; i < common->fsg_num_buffers; ++i) {
|
|
|
bh = &common->buffhds[i];
|
|
|
bh->state = BUF_STATE_EMPTY;
|
|
|
}
|
|
@@ -2420,7 +2409,9 @@ static void handle_exception(struct fsg_common *common)
|
|
|
common->state = FSG_STATE_STATUS_PHASE;
|
|
|
else {
|
|
|
for (i = 0; i < common->nluns; ++i) {
|
|
|
- curlun = &common->luns[i];
|
|
|
+ curlun = common->luns[i];
|
|
|
+ if (!curlun)
|
|
|
+ continue;
|
|
|
curlun->prevent_medium_removal = 0;
|
|
|
curlun->sense_data = SS_NO_SENSE;
|
|
|
curlun->unit_attention_data = SS_NO_SENSE;
|
|
@@ -2462,8 +2453,9 @@ static void handle_exception(struct fsg_common *common)
|
|
|
* CONFIG_CHANGE cases.
|
|
|
*/
|
|
|
/* for (i = 0; i < common->nluns; ++i) */
|
|
|
- /* common->luns[i].unit_attention_data = */
|
|
|
- /* SS_RESET_OCCURRED; */
|
|
|
+ /* if (common->luns[i]) */
|
|
|
+ /* common->luns[i]->unit_attention_data = */
|
|
|
+ /* SS_RESET_OCCURRED; */
|
|
|
break;
|
|
|
|
|
|
case FSG_STATE_CONFIG_CHANGE:
|
|
@@ -2559,12 +2551,13 @@ static int fsg_main_thread(void *common_)
|
|
|
|
|
|
if (!common->ops || !common->ops->thread_exits
|
|
|
|| common->ops->thread_exits(common) < 0) {
|
|
|
- struct fsg_lun *curlun = common->luns;
|
|
|
+ struct fsg_lun **curlun_it = common->luns;
|
|
|
unsigned i = common->nluns;
|
|
|
|
|
|
down_write(&common->filesem);
|
|
|
- for (; i--; ++curlun) {
|
|
|
- if (!fsg_lun_is_open(curlun))
|
|
|
+ for (; i--; ++curlun_it) {
|
|
|
+ struct fsg_lun *curlun = *curlun_it;
|
|
|
+ if (!curlun || !fsg_lun_is_open(curlun))
|
|
|
continue;
|
|
|
|
|
|
fsg_lun_close(curlun);
|
|
@@ -2580,6 +2573,56 @@ static int fsg_main_thread(void *common_)
|
|
|
|
|
|
/*************************** DEVICE ATTRIBUTES ***************************/
|
|
|
|
|
|
+static ssize_t ro_show(struct device *dev, struct device_attribute *attr, char *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)
|
|
|
+{
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ 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);
|
|
|
static DEVICE_ATTR_RW(nofua);
|
|
|
static DEVICE_ATTR_RW(file);
|
|
@@ -2597,221 +2640,422 @@ static void fsg_lun_release(struct device *dev)
|
|
|
/* Nothing needs to be done */
|
|
|
}
|
|
|
|
|
|
-static inline void fsg_common_get(struct fsg_common *common)
|
|
|
+void fsg_common_get(struct fsg_common *common)
|
|
|
{
|
|
|
kref_get(&common->ref);
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_get);
|
|
|
|
|
|
-static inline void fsg_common_put(struct fsg_common *common)
|
|
|
+void fsg_common_put(struct fsg_common *common)
|
|
|
{
|
|
|
kref_put(&common->ref, fsg_common_release);
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_put);
|
|
|
|
|
|
-static struct fsg_common *fsg_common_init(struct fsg_common *common,
|
|
|
- struct usb_composite_dev *cdev,
|
|
|
- struct fsg_config *cfg)
|
|
|
+/* check if fsg_num_buffers is within a valid range */
|
|
|
+static inline int fsg_num_buffers_validate(unsigned int fsg_num_buffers)
|
|
|
{
|
|
|
- struct usb_gadget *gadget = cdev->gadget;
|
|
|
- struct fsg_buffhd *bh;
|
|
|
- struct fsg_lun *curlun;
|
|
|
- struct fsg_lun_config *lcfg;
|
|
|
- int nluns, i, rc;
|
|
|
- char *pathbuf;
|
|
|
-
|
|
|
- rc = fsg_num_buffers_validate();
|
|
|
- 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);
|
|
|
- }
|
|
|
+ if (fsg_num_buffers >= 2 && fsg_num_buffers <= 4)
|
|
|
+ return 0;
|
|
|
+ pr_err("fsg_num_buffers %u is out of range (%d to %d)\n",
|
|
|
+ fsg_num_buffers, 2, 4);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
|
|
|
- /* Allocate? */
|
|
|
+static struct fsg_common *fsg_common_setup(struct fsg_common *common)
|
|
|
+{
|
|
|
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);
|
|
|
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->buffhds = kcalloc(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(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;
|
|
|
|
|
|
- common->gadget = gadget;
|
|
|
- common->ep0 = gadget->ep0;
|
|
|
- common->ep0req = cdev->req;
|
|
|
- common->cdev = cdev;
|
|
|
+ rc = fsg_num_buffers_validate(n);
|
|
|
+ if (rc != 0)
|
|
|
+ return rc;
|
|
|
+
|
|
|
+ buffhds = kcalloc(n, sizeof(*buffhds), GFP_KERNEL);
|
|
|
+ if (!buffhds)
|
|
|
+ return -ENOMEM;
|
|
|
|
|
|
- /* Maybe allocate device-global string IDs, and patch descriptors */
|
|
|
- if (fsg_strings[FSG_STRING_INTERFACE].id == 0) {
|
|
|
- rc = usb_string_id(cdev);
|
|
|
- if (unlikely(rc < 0))
|
|
|
+ /* 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;
|
|
|
- fsg_strings[FSG_STRING_INTERFACE].id = rc;
|
|
|
- fsg_intf_desc.iInterface = rc;
|
|
|
- }
|
|
|
+ } 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:
|
|
|
/*
|
|
|
- * Create the LUNs, open their backing files, and register the
|
|
|
- * LUN devices in sysfs.
|
|
|
+ * "buf"s pointed to by heads after n - i are NULL
|
|
|
+ * so releasing them won't hurt
|
|
|
*/
|
|
|
- curlun = kcalloc(nluns, sizeof(*curlun), GFP_KERNEL);
|
|
|
- if (unlikely(!curlun)) {
|
|
|
- rc = -ENOMEM;
|
|
|
- goto error_release;
|
|
|
+ _fsg_common_free_buffers(buffhds, n);
|
|
|
+
|
|
|
+ return -ENOMEM;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(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);
|
|
|
}
|
|
|
- common->luns = curlun;
|
|
|
+ fsg_lun_close(lun);
|
|
|
+ kfree(lun);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_remove_lun);
|
|
|
|
|
|
- init_rwsem(&common->filesem);
|
|
|
+static void _fsg_common_remove_luns(struct fsg_common *common, int n)
|
|
|
+{
|
|
|
+ int i;
|
|
|
|
|
|
- for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun, ++lcfg) {
|
|
|
- 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);
|
|
|
- if (rc) {
|
|
|
- INFO(common, "failed to register LUN%d: %d\n", i, rc);
|
|
|
- common->nluns = i;
|
|
|
- put_device(&curlun->dev);
|
|
|
- goto error_release;
|
|
|
+ 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(fsg_common_remove_luns);
|
|
|
|
|
|
- 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);
|
|
|
- if (rc)
|
|
|
- goto error_luns;
|
|
|
+void fsg_common_remove_luns(struct fsg_common *common)
|
|
|
+{
|
|
|
+ _fsg_common_remove_luns(common, common->nluns);
|
|
|
+}
|
|
|
|
|
|
- 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;
|
|
|
- }
|
|
|
+void fsg_common_free_luns(struct fsg_common *common)
|
|
|
+{
|
|
|
+ fsg_common_remove_luns(common);
|
|
|
+ kfree(common->luns);
|
|
|
+ common->luns = NULL;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(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;
|
|
|
|
|
|
- /* Data buffers cyclic list */
|
|
|
- bh = common->buffhds;
|
|
|
- i = 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;
|
|
|
+ pr_info("Number of LUNs=%d\n", common->nluns);
|
|
|
|
|
|
- /* Prepare inquiryString */
|
|
|
- i = get_default_bcdDevice();
|
|
|
- snprintf(common->inquiry_string, sizeof common->inquiry_string,
|
|
|
- "%-8s%-16s%04x", cfg->vendor_name ?: "Linux",
|
|
|
- /* Assume product name dependent on the first LUN */
|
|
|
- cfg->product_name ?: (common->luns->cdrom
|
|
|
- ? "File-CD Gadget"
|
|
|
- : "File-Stor Gadget"),
|
|
|
- i);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_set_nluns);
|
|
|
+
|
|
|
+void fsg_common_set_ops(struct fsg_common *common,
|
|
|
+ const struct fsg_operations *ops)
|
|
|
+{
|
|
|
+ common->ops = ops;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(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(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))
|
|
|
+ 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 = cfg->can_stall &&
|
|
|
- !(gadget_is_at91(common->gadget));
|
|
|
+ common->can_stall = can_stall && !(gadget_is_at91(common->gadget));
|
|
|
|
|
|
- spin_lock_init(&common->lock);
|
|
|
- kref_init(&common->ref);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_set_cdev);
|
|
|
|
|
|
- /* 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;
|
|
|
+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;
|
|
|
}
|
|
|
- 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);
|
|
|
+ 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;
|
|
|
+
|
|
|
+ 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, "%s", name);
|
|
|
+ lun->name = dev_name(&lun->dev);
|
|
|
+
|
|
|
+ rc = fsg_common_add_sysfs(common, lun);
|
|
|
+ if (rc) {
|
|
|
+ pr_info("failed to register LUN%d: %d\n", id, rc);
|
|
|
+ goto error_sysfs;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ common->luns[id] = lun;
|
|
|
+
|
|
|
+ if (cfg->filename) {
|
|
|
+ rc = fsg_lun_open(lun, cfg->filename);
|
|
|
+ if (rc)
|
|
|
+ goto error_lun;
|
|
|
+ }
|
|
|
|
|
|
pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
|
|
|
- for (i = 0, nluns = common->nluns, curlun = common->luns;
|
|
|
- i < nluns;
|
|
|
- ++curlun, ++i) {
|
|
|
- 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)";
|
|
|
- }
|
|
|
+ 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)";
|
|
|
}
|
|
|
- LINFO(curlun, "LUN: %s%s%sfile: %s\n",
|
|
|
- curlun->removable ? "removable " : "",
|
|
|
- curlun->ro ? "read only " : "",
|
|
|
- curlun->cdrom ? "CD-ROM " : "",
|
|
|
- p);
|
|
|
}
|
|
|
+ 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(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(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", vn ?: "Linux",
|
|
|
+ /* Assume product name dependent on the first LUN */
|
|
|
+ pn ?: ((*common->luns)->cdrom
|
|
|
+ ? "File-CD Gadget"
|
|
|
+ : "File-Stor Gadget"),
|
|
|
+ i);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_set_inquiry_string);
|
|
|
+
|
|
|
+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)) {
|
|
|
+ common->state = FSG_STATE_TERMINATED;
|
|
|
+ return PTR_ERR(common->thread_task);
|
|
|
+ }
|
|
|
+
|
|
|
DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task));
|
|
|
|
|
|
wake_up_process(common->thread_task);
|
|
|
|
|
|
- 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);
|
|
|
+ return 0;
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_common_run_thread);
|
|
|
|
|
|
static void fsg_common_release(struct kref *ref)
|
|
|
{
|
|
@@ -2824,36 +3068,26 @@ static void fsg_common_release(struct kref *ref)
|
|
|
}
|
|
|
|
|
|
if (likely(common->luns)) {
|
|
|
- struct fsg_lun *lun = common->luns;
|
|
|
+ struct fsg_lun **lun_it = common->luns;
|
|
|
unsigned i = common->nluns;
|
|
|
|
|
|
/* In error recovery common->nluns may be zero. */
|
|
|
- for (; i; --i, ++lun) {
|
|
|
- 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);
|
|
|
+ for (; i; --i, ++lun_it) {
|
|
|
+ struct fsg_lun *lun = *lun_it;
|
|
|
+ if (!lun)
|
|
|
+ continue;
|
|
|
+ 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 = 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);
|
|
|
}
|
|
@@ -2861,24 +3095,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);
|
|
@@ -2887,6 +3103,19 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
|
|
|
struct usb_ep *ep;
|
|
|
unsigned max_burst;
|
|
|
int ret;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
|
|
|
fsg->gadget = gadget;
|
|
|
|
|
@@ -2939,95 +3168,472 @@ autoconf_fail:
|
|
|
return -ENOTSUPP;
|
|
|
}
|
|
|
|
|
|
-/****************************** ADD FUNCTION ******************************/
|
|
|
+/****************************** ALLOCATE FUNCTION *************************/
|
|
|
|
|
|
-static struct usb_gadget_strings *fsg_strings_array[] = {
|
|
|
- &fsg_stringtab,
|
|
|
+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);
|
|
|
+ }
|
|
|
+
|
|
|
+ usb_free_all_descriptors(&fsg->function);
|
|
|
+}
|
|
|
+
|
|
|
+static inline struct fsg_lun_opts *to_fsg_lun_opts(struct config_item *item)
|
|
|
+{
|
|
|
+ return container_of(to_config_group(item), struct fsg_lun_opts, group);
|
|
|
+}
|
|
|
+
|
|
|
+static inline struct fsg_opts *to_fsg_opts(struct config_item *item)
|
|
|
+{
|
|
|
+ return container_of(to_config_group(item), struct fsg_opts,
|
|
|
+ func_inst.group);
|
|
|
+}
|
|
|
+
|
|
|
+CONFIGFS_ATTR_STRUCT(fsg_lun_opts);
|
|
|
+CONFIGFS_ATTR_OPS(fsg_lun_opts);
|
|
|
+
|
|
|
+static void fsg_lun_attr_release(struct config_item *item)
|
|
|
+{
|
|
|
+ struct fsg_lun_opts *lun_opts;
|
|
|
+
|
|
|
+ lun_opts = to_fsg_lun_opts(item);
|
|
|
+ kfree(lun_opts);
|
|
|
+}
|
|
|
+
|
|
|
+static struct configfs_item_operations fsg_lun_item_ops = {
|
|
|
+ .release = fsg_lun_attr_release,
|
|
|
+ .show_attribute = fsg_lun_opts_attr_show,
|
|
|
+ .store_attribute = fsg_lun_opts_attr_store,
|
|
|
+};
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_file_show(struct fsg_lun_opts *opts, char *page)
|
|
|
+{
|
|
|
+ struct fsg_opts *fsg_opts;
|
|
|
+
|
|
|
+ fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
|
|
|
+
|
|
|
+ return fsg_show_file(opts->lun, &fsg_opts->common->filesem, page);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_file_store(struct fsg_lun_opts *opts,
|
|
|
+ const char *page, size_t len)
|
|
|
+{
|
|
|
+ struct fsg_opts *fsg_opts;
|
|
|
+
|
|
|
+ fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
|
|
|
+
|
|
|
+ return fsg_store_file(opts->lun, &fsg_opts->common->filesem, page, len);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_lun_opts_attribute fsg_lun_opts_file =
|
|
|
+ __CONFIGFS_ATTR(file, S_IRUGO | S_IWUSR, fsg_lun_opts_file_show,
|
|
|
+ fsg_lun_opts_file_store);
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_ro_show(struct fsg_lun_opts *opts, char *page)
|
|
|
+{
|
|
|
+ return fsg_show_ro(opts->lun, page);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_ro_store(struct fsg_lun_opts *opts,
|
|
|
+ const char *page, size_t len)
|
|
|
+{
|
|
|
+ struct fsg_opts *fsg_opts;
|
|
|
+
|
|
|
+ fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
|
|
|
+
|
|
|
+ return fsg_store_ro(opts->lun, &fsg_opts->common->filesem, page, len);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_lun_opts_attribute fsg_lun_opts_ro =
|
|
|
+ __CONFIGFS_ATTR(ro, S_IRUGO | S_IWUSR, fsg_lun_opts_ro_show,
|
|
|
+ fsg_lun_opts_ro_store);
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_removable_show(struct fsg_lun_opts *opts,
|
|
|
+ char *page)
|
|
|
+{
|
|
|
+ return fsg_show_removable(opts->lun, page);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_removable_store(struct fsg_lun_opts *opts,
|
|
|
+ const char *page, size_t len)
|
|
|
+{
|
|
|
+ return fsg_store_removable(opts->lun, page, len);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_lun_opts_attribute fsg_lun_opts_removable =
|
|
|
+ __CONFIGFS_ATTR(removable, S_IRUGO | S_IWUSR,
|
|
|
+ fsg_lun_opts_removable_show,
|
|
|
+ fsg_lun_opts_removable_store);
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_cdrom_show(struct fsg_lun_opts *opts, char *page)
|
|
|
+{
|
|
|
+ return fsg_show_cdrom(opts->lun, page);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_cdrom_store(struct fsg_lun_opts *opts,
|
|
|
+ const char *page, size_t len)
|
|
|
+{
|
|
|
+ struct fsg_opts *fsg_opts;
|
|
|
+
|
|
|
+ fsg_opts = to_fsg_opts(opts->group.cg_item.ci_parent);
|
|
|
+
|
|
|
+ return fsg_store_cdrom(opts->lun, &fsg_opts->common->filesem, page,
|
|
|
+ len);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_lun_opts_attribute fsg_lun_opts_cdrom =
|
|
|
+ __CONFIGFS_ATTR(cdrom, S_IRUGO | S_IWUSR, fsg_lun_opts_cdrom_show,
|
|
|
+ fsg_lun_opts_cdrom_store);
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_nofua_show(struct fsg_lun_opts *opts, char *page)
|
|
|
+{
|
|
|
+ return fsg_show_nofua(opts->lun, page);
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_lun_opts_nofua_store(struct fsg_lun_opts *opts,
|
|
|
+ const char *page, size_t len)
|
|
|
+{
|
|
|
+ return fsg_store_nofua(opts->lun, page, len);
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_lun_opts_attribute fsg_lun_opts_nofua =
|
|
|
+ __CONFIGFS_ATTR(nofua, S_IRUGO | S_IWUSR, fsg_lun_opts_nofua_show,
|
|
|
+ fsg_lun_opts_nofua_store);
|
|
|
+
|
|
|
+static struct configfs_attribute *fsg_lun_attrs[] = {
|
|
|
+ &fsg_lun_opts_file.attr,
|
|
|
+ &fsg_lun_opts_ro.attr,
|
|
|
+ &fsg_lun_opts_removable.attr,
|
|
|
+ &fsg_lun_opts_cdrom.attr,
|
|
|
+ &fsg_lun_opts_nofua.attr,
|
|
|
NULL,
|
|
|
};
|
|
|
|
|
|
-static int fsg_bind_config(struct usb_composite_dev *cdev,
|
|
|
- struct usb_configuration *c,
|
|
|
- struct fsg_common *common)
|
|
|
+static struct config_item_type fsg_lun_type = {
|
|
|
+ .ct_item_ops = &fsg_lun_item_ops,
|
|
|
+ .ct_attrs = fsg_lun_attrs,
|
|
|
+ .ct_owner = THIS_MODULE,
|
|
|
+};
|
|
|
+
|
|
|
+static struct config_group *fsg_lun_make(struct config_group *group,
|
|
|
+ const char *name)
|
|
|
{
|
|
|
- struct fsg_dev *fsg;
|
|
|
+ struct fsg_lun_opts *opts;
|
|
|
+ struct fsg_opts *fsg_opts;
|
|
|
+ struct fsg_lun_config config;
|
|
|
+ char *num_str;
|
|
|
+ u8 num;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ num_str = strchr(name, '.');
|
|
|
+ if (!num_str) {
|
|
|
+ pr_err("Unable to locate . in LUN.NUMBER\n");
|
|
|
+ return ERR_PTR(-EINVAL);
|
|
|
+ }
|
|
|
+ num_str++;
|
|
|
+
|
|
|
+ ret = kstrtou8(num_str, 0, &num);
|
|
|
+ if (ret)
|
|
|
+ return ERR_PTR(ret);
|
|
|
+
|
|
|
+ fsg_opts = to_fsg_opts(&group->cg_item);
|
|
|
+ if (num >= FSG_MAX_LUNS)
|
|
|
+ return ERR_PTR(-ERANGE);
|
|
|
+
|
|
|
+ mutex_lock(&fsg_opts->lock);
|
|
|
+ if (fsg_opts->refcnt || fsg_opts->common->luns[num]) {
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
|
|
+ if (!opts) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ memset(&config, 0, sizeof(config));
|
|
|
+ config.removable = true;
|
|
|
+
|
|
|
+ ret = fsg_common_create_lun(fsg_opts->common, &config, num, name,
|
|
|
+ (const char **)&group->cg_item.ci_name);
|
|
|
+ if (ret) {
|
|
|
+ kfree(opts);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ opts->lun = fsg_opts->common->luns[num];
|
|
|
+ opts->lun_id = num;
|
|
|
+ mutex_unlock(&fsg_opts->lock);
|
|
|
+
|
|
|
+ config_group_init_type_name(&opts->group, name, &fsg_lun_type);
|
|
|
+
|
|
|
+ return &opts->group;
|
|
|
+out:
|
|
|
+ mutex_unlock(&fsg_opts->lock);
|
|
|
+ return ERR_PTR(ret);
|
|
|
+}
|
|
|
+
|
|
|
+static void fsg_lun_drop(struct config_group *group, struct config_item *item)
|
|
|
+{
|
|
|
+ struct fsg_lun_opts *lun_opts;
|
|
|
+ struct fsg_opts *fsg_opts;
|
|
|
+
|
|
|
+ lun_opts = to_fsg_lun_opts(item);
|
|
|
+ fsg_opts = to_fsg_opts(&group->cg_item);
|
|
|
+
|
|
|
+ mutex_lock(&fsg_opts->lock);
|
|
|
+ if (fsg_opts->refcnt) {
|
|
|
+ struct config_item *gadget;
|
|
|
+
|
|
|
+ gadget = group->cg_item.ci_parent->ci_parent;
|
|
|
+ unregister_gadget_item(gadget);
|
|
|
+ }
|
|
|
+
|
|
|
+ fsg_common_remove_lun(lun_opts->lun, fsg_opts->common->sysfs);
|
|
|
+ fsg_opts->common->luns[lun_opts->lun_id] = NULL;
|
|
|
+ lun_opts->lun_id = 0;
|
|
|
+ mutex_unlock(&fsg_opts->lock);
|
|
|
+
|
|
|
+ config_item_put(item);
|
|
|
+}
|
|
|
+
|
|
|
+CONFIGFS_ATTR_STRUCT(fsg_opts);
|
|
|
+CONFIGFS_ATTR_OPS(fsg_opts);
|
|
|
+
|
|
|
+static void fsg_attr_release(struct config_item *item)
|
|
|
+{
|
|
|
+ struct fsg_opts *opts = to_fsg_opts(item);
|
|
|
+
|
|
|
+ usb_put_function_instance(&opts->func_inst);
|
|
|
+}
|
|
|
+
|
|
|
+static struct configfs_item_operations fsg_item_ops = {
|
|
|
+ .release = fsg_attr_release,
|
|
|
+ .show_attribute = fsg_opts_attr_show,
|
|
|
+ .store_attribute = fsg_opts_attr_store,
|
|
|
+};
|
|
|
+
|
|
|
+static ssize_t fsg_opts_stall_show(struct fsg_opts *opts, char *page)
|
|
|
+{
|
|
|
+ int result;
|
|
|
+
|
|
|
+ mutex_lock(&opts->lock);
|
|
|
+ result = sprintf(page, "%d", opts->common->can_stall);
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_opts_stall_store(struct fsg_opts *opts, const char *page,
|
|
|
+ size_t len)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ bool stall;
|
|
|
+
|
|
|
+ mutex_lock(&opts->lock);
|
|
|
+
|
|
|
+ if (opts->refcnt) {
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = strtobool(page, &stall);
|
|
|
+ if (!ret) {
|
|
|
+ opts->common->can_stall = stall;
|
|
|
+ ret = len;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_opts_attribute fsg_opts_stall =
|
|
|
+ __CONFIGFS_ATTR(stall, S_IRUGO | S_IWUSR, fsg_opts_stall_show,
|
|
|
+ fsg_opts_stall_store);
|
|
|
+
|
|
|
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
|
|
+static ssize_t fsg_opts_num_buffers_show(struct fsg_opts *opts, char *page)
|
|
|
+{
|
|
|
+ int result;
|
|
|
+
|
|
|
+ mutex_lock(&opts->lock);
|
|
|
+ result = sprintf(page, "%d", opts->common->fsg_num_buffers);
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static ssize_t fsg_opts_num_buffers_store(struct fsg_opts *opts,
|
|
|
+ const char *page, size_t len)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ u8 num;
|
|
|
+
|
|
|
+ mutex_lock(&opts->lock);
|
|
|
+ if (opts->refcnt) {
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+ ret = kstrtou8(page, 0, &num);
|
|
|
+ if (ret)
|
|
|
+ goto end;
|
|
|
+
|
|
|
+ ret = fsg_num_buffers_validate(num);
|
|
|
+ if (ret)
|
|
|
+ goto end;
|
|
|
+
|
|
|
+ fsg_common_set_num_buffers(opts->common, num);
|
|
|
+ ret = len;
|
|
|
+
|
|
|
+end:
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static struct fsg_opts_attribute fsg_opts_num_buffers =
|
|
|
+ __CONFIGFS_ATTR(num_buffers, S_IRUGO | S_IWUSR,
|
|
|
+ fsg_opts_num_buffers_show,
|
|
|
+ fsg_opts_num_buffers_store);
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+static struct configfs_attribute *fsg_attrs[] = {
|
|
|
+ &fsg_opts_stall.attr,
|
|
|
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
|
|
|
+ &fsg_opts_num_buffers.attr,
|
|
|
+#endif
|
|
|
+ NULL,
|
|
|
+};
|
|
|
+
|
|
|
+static struct configfs_group_operations fsg_group_ops = {
|
|
|
+ .make_group = fsg_lun_make,
|
|
|
+ .drop_item = fsg_lun_drop,
|
|
|
+};
|
|
|
+
|
|
|
+static struct config_item_type fsg_func_type = {
|
|
|
+ .ct_item_ops = &fsg_item_ops,
|
|
|
+ .ct_group_ops = &fsg_group_ops,
|
|
|
+ .ct_attrs = fsg_attrs,
|
|
|
+ .ct_owner = THIS_MODULE,
|
|
|
+};
|
|
|
+
|
|
|
+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;
|
|
|
+ struct fsg_lun_config config;
|
|
|
int rc;
|
|
|
|
|
|
- fsg = kzalloc(sizeof *fsg, GFP_KERNEL);
|
|
|
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
|
|
|
+ if (!opts)
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
+ mutex_init(&opts->lock);
|
|
|
+ opts->func_inst.free_func_inst = fsg_free_inst;
|
|
|
+ opts->common = fsg_common_setup(opts->common);
|
|
|
+ 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");
|
|
|
+
|
|
|
+ memset(&config, 0, sizeof(config));
|
|
|
+ config.removable = true;
|
|
|
+ rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0",
|
|
|
+ (const char **)&opts->func_inst.group.cg_item.ci_name);
|
|
|
+ opts->lun0.lun = opts->common->luns[0];
|
|
|
+ opts->lun0.lun_id = 0;
|
|
|
+ config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type);
|
|
|
+ opts->default_groups[0] = &opts->lun0.group;
|
|
|
+ opts->func_inst.group.default_groups = opts->default_groups;
|
|
|
+
|
|
|
+ config_group_init_type_name(&opts->func_inst.group, "", &fsg_func_type);
|
|
|
+
|
|
|
+ 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;
|
|
|
+ struct fsg_opts *opts;
|
|
|
+
|
|
|
+ fsg = container_of(f, struct fsg_dev, function);
|
|
|
+ opts = container_of(f->fi, struct fsg_opts, func_inst);
|
|
|
+
|
|
|
+ mutex_lock(&opts->lock);
|
|
|
+ opts->refcnt--;
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+
|
|
|
+ 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 -ENOMEM;
|
|
|
+ return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
- fsg->function.name = FSG_DRIVER_DESC;
|
|
|
- fsg->function.strings = fsg_strings_array;
|
|
|
- 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;
|
|
|
+ mutex_lock(&opts->lock);
|
|
|
+ opts->refcnt++;
|
|
|
+ mutex_unlock(&opts->lock);
|
|
|
+ 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;
|
|
|
- /*
|
|
|
- * Our caller holds a reference to common structure so we
|
|
|
- * don't have to be worry about it being freed until we return
|
|
|
- * from this function. So instead of incrementing counter now
|
|
|
- * and decrement in error recovery we increment it only when
|
|
|
- * call to usb_add_function() was successful.
|
|
|
- */
|
|
|
|
|
|
- rc = usb_add_function(c, &fsg->function);
|
|
|
- if (unlikely(rc))
|
|
|
- kfree(fsg);
|
|
|
- else
|
|
|
- fsg_common_get(fsg->common);
|
|
|
- return rc;
|
|
|
+ return &fsg->function;
|
|
|
}
|
|
|
|
|
|
+DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc);
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
+MODULE_AUTHOR("Michal Nazarewicz");
|
|
|
|
|
|
/************************* Module parameters *************************/
|
|
|
|
|
|
-struct fsg_module_parameters {
|
|
|
- char *file[FSG_MAX_LUNS];
|
|
|
- bool ro[FSG_MAX_LUNS];
|
|
|
- bool removable[FSG_MAX_LUNS];
|
|
|
- bool cdrom[FSG_MAX_LUNS];
|
|
|
- bool nofua[FSG_MAX_LUNS];
|
|
|
-
|
|
|
- unsigned int file_count, ro_count, removable_count, cdrom_count;
|
|
|
- unsigned int nofua_count;
|
|
|
- unsigned int luns; /* nluns */
|
|
|
- bool stall; /* can_stall */
|
|
|
-};
|
|
|
|
|
|
-#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc) \
|
|
|
- module_param_array_named(prefix ## name, params.name, type, \
|
|
|
- &prefix ## params.name ## _count, \
|
|
|
- S_IRUGO); \
|
|
|
- MODULE_PARM_DESC(prefix ## name, desc)
|
|
|
-
|
|
|
-#define _FSG_MODULE_PARAM(prefix, params, name, type, desc) \
|
|
|
- module_param_named(prefix ## name, params.name, type, \
|
|
|
- S_IRUGO); \
|
|
|
- MODULE_PARM_DESC(prefix ## name, desc)
|
|
|
-
|
|
|
-#define FSG_MODULE_PARAMETERS(prefix, params) \
|
|
|
- _FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp, \
|
|
|
- "names of backing files or devices"); \
|
|
|
- _FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool, \
|
|
|
- "true to force read-only"); \
|
|
|
- _FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool, \
|
|
|
- "true to simulate removable media"); \
|
|
|
- _FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool, \
|
|
|
- "true to simulate CD-ROM instead of disk"); \
|
|
|
- _FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool, \
|
|
|
- "true to ignore SCSI WRITE(10,12) FUA bit"); \
|
|
|
- _FSG_MODULE_PARAM(prefix, params, luns, uint, \
|
|
|
- "number of LUNs"); \
|
|
|
- _FSG_MODULE_PARAM(prefix, params, stall, bool, \
|
|
|
- "false to prevent bulk stalls")
|
|
|
-
|
|
|
-static void
|
|
|
-fsg_config_from_params(struct fsg_config *cfg,
|
|
|
- const struct fsg_module_parameters *params)
|
|
|
+void fsg_config_from_params(struct fsg_config *cfg,
|
|
|
+ const struct fsg_module_parameters *params,
|
|
|
+ unsigned int fsg_num_buffers)
|
|
|
{
|
|
|
struct fsg_lun_config *lun;
|
|
|
unsigned i;
|
|
@@ -3055,19 +3661,7 @@ fsg_config_from_params(struct fsg_config *cfg,
|
|
|
|
|
|
/* Finalise */
|
|
|
cfg->can_stall = params->stall;
|
|
|
+ cfg->fsg_num_buffers = fsg_num_buffers;
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(fsg_config_from_params);
|
|
|
|
|
|
-static inline struct fsg_common *
|
|
|
-fsg_common_from_params(struct fsg_common *common,
|
|
|
- struct usb_composite_dev *cdev,
|
|
|
- const struct fsg_module_parameters *params)
|
|
|
- __attribute__((unused));
|
|
|
-static inline struct fsg_common *
|
|
|
-fsg_common_from_params(struct fsg_common *common,
|
|
|
- struct usb_composite_dev *cdev,
|
|
|
- const struct fsg_module_parameters *params)
|
|
|
-{
|
|
|
- struct fsg_config cfg;
|
|
|
- fsg_config_from_params(&cfg, params);
|
|
|
- return fsg_common_init(common, cdev, &cfg);
|
|
|
-}
|