summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/configfs-usb-gadget-loopback8
-rw-r--r--drivers/usb/gadget/Kconfig12
-rw-r--r--drivers/usb/gadget/f_loopback.c132
-rw-r--r--drivers/usb/gadget/g_zero.h12
-rw-r--r--drivers/usb/gadget/zero.c4
5 files changed, 166 insertions, 2 deletions
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-loopback b/Documentation/ABI/testing/configfs-usb-gadget-loopback
new file mode 100644
index 000000000000..852b2365a5b5
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-usb-gadget-loopback
@@ -0,0 +1,8 @@
+What: /config/usb-gadget/gadget/functions/Loopback.name
+Date: Nov 2013
+KenelVersion: 3.13
+Description:
+ The attributes:
+
+ qlen - depth of loopback queue
+ bulk_buflen - buffer length
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index a91e6422f930..26fe8769be45 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -689,6 +689,18 @@ config USB_CONFIGFS_MASS_STORAGE
device (in much the same way as the "loop" device driver),
specified as a module parameter or sysfs option.
+config USB_CONFIGFS_F_LB
+ boolean "Loopback function (for testing)"
+ depends on USB_CONFIGFS
+ select USB_F_SS_LB
+ help
+ It loops back a configurable number of transfers.
+ It also implements control requests, for "chapter 9" conformance.
+ Make this be the first driver you try using on top of any new
+ USB peripheral controller driver. Then you can use host-side
+ test software, like the "usbtest" driver, to put your hardware
+ and its driver through a basic set of functional tests.
+
config USB_ZERO
tristate "Gadget Zero (DEVELOPMENT)"
select USB_LIBCOMPOSITE
diff --git a/drivers/usb/gadget/f_loopback.c b/drivers/usb/gadget/f_loopback.c
index b79065376a16..c35bb40cabf2 100644
--- a/drivers/usb/gadget/f_loopback.c
+++ b/drivers/usb/gadget/f_loopback.c
@@ -231,6 +231,14 @@ autoconf_fail:
static void lb_free_func(struct usb_function *f)
{
+ struct f_lb_opts *opts;
+
+ opts = container_of(f->fi, struct f_lb_opts, func_inst);
+
+ mutex_lock(&opts->lock);
+ opts->refcnt--;
+ mutex_unlock(&opts->lock);
+
usb_free_all_descriptors(f);
kfree(func_to_loop(f));
}
@@ -386,6 +394,11 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
return ERR_PTR(-ENOMEM);
lb_opts = container_of(fi, struct f_lb_opts, func_inst);
+
+ mutex_lock(&lb_opts->lock);
+ lb_opts->refcnt++;
+ mutex_unlock(&lb_opts->lock);
+
buflen = lb_opts->bulk_buflen;
qlen = lb_opts->qlen;
if (!qlen)
@@ -402,6 +415,118 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
return &loop->function;
}
+static inline struct f_lb_opts *to_f_lb_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_lb_opts,
+ func_inst.group);
+}
+
+CONFIGFS_ATTR_STRUCT(f_lb_opts);
+CONFIGFS_ATTR_OPS(f_lb_opts);
+
+static void lb_attr_release(struct config_item *item)
+{
+ struct f_lb_opts *lb_opts = to_f_lb_opts(item);
+
+ usb_put_function_instance(&lb_opts->func_inst);
+}
+
+static struct configfs_item_operations lb_item_ops = {
+ .release = lb_attr_release,
+ .show_attribute = f_lb_opts_attr_show,
+ .store_attribute = f_lb_opts_attr_store,
+};
+
+static ssize_t f_lb_opts_qlen_show(struct f_lb_opts *opts, char *page)
+{
+ int result;
+
+ mutex_lock(&opts->lock);
+ result = sprintf(page, "%d", opts->qlen);
+ mutex_unlock(&opts->lock);
+
+ return result;
+}
+
+static ssize_t f_lb_opts_qlen_store(struct f_lb_opts *opts,
+ const char *page, size_t len)
+{
+ int ret;
+ u32 num;
+
+ mutex_lock(&opts->lock);
+ if (opts->refcnt) {
+ ret = -EBUSY;
+ goto end;
+ }
+
+ ret = kstrtou32(page, 0, &num);
+ if (ret)
+ goto end;
+
+ opts->qlen = num;
+ ret = len;
+end:
+ mutex_unlock(&opts->lock);
+ return ret;
+}
+
+static struct f_lb_opts_attribute f_lb_opts_qlen =
+ __CONFIGFS_ATTR(qlen, S_IRUGO | S_IWUSR,
+ f_lb_opts_qlen_show,
+ f_lb_opts_qlen_store);
+
+static ssize_t f_lb_opts_bulk_buflen_show(struct f_lb_opts *opts, char *page)
+{
+ int result;
+
+ mutex_lock(&opts->lock);
+ result = sprintf(page, "%d", opts->bulk_buflen);
+ mutex_unlock(&opts->lock);
+
+ return result;
+}
+
+static ssize_t f_lb_opts_bulk_buflen_store(struct f_lb_opts *opts,
+ const char *page, size_t len)
+{
+ int ret;
+ u32 num;
+
+ mutex_lock(&opts->lock);
+ if (opts->refcnt) {
+ ret = -EBUSY;
+ goto end;
+ }
+
+ ret = kstrtou32(page, 0, &num);
+ if (ret)
+ goto end;
+
+ opts->bulk_buflen = num;
+ ret = len;
+end:
+ mutex_unlock(&opts->lock);
+ return ret;
+}
+
+static struct f_lb_opts_attribute f_lb_opts_bulk_buflen =
+ __CONFIGFS_ATTR(buflen, S_IRUGO | S_IWUSR,
+ f_lb_opts_bulk_buflen_show,
+ f_lb_opts_bulk_buflen_store);
+
+static struct configfs_attribute *lb_attrs[] = {
+ &f_lb_opts_qlen.attr,
+ &f_lb_opts_bulk_buflen.attr,
+ NULL,
+};
+
+static struct config_item_type lb_func_type = {
+ .ct_item_ops = &lb_item_ops,
+ .ct_attrs = lb_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
static void lb_free_instance(struct usb_function_instance *fi)
{
struct f_lb_opts *lb_opts;
@@ -417,7 +542,14 @@ static struct usb_function_instance *loopback_alloc_instance(void)
lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL);
if (!lb_opts)
return ERR_PTR(-ENOMEM);
+ mutex_init(&lb_opts->lock);
lb_opts->func_inst.free_func_inst = lb_free_instance;
+ lb_opts->bulk_buflen = GZERO_BULK_BUFLEN;
+ lb_opts->qlen = GZERO_QLEN;
+
+ config_group_init_type_name(&lb_opts->func_inst.group, "",
+ &lb_func_type);
+
return &lb_opts->func_inst;
}
DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc);
diff --git a/drivers/usb/gadget/g_zero.h b/drivers/usb/gadget/g_zero.h
index a1c1a978b5e9..19ec50a42a74 100644
--- a/drivers/usb/gadget/g_zero.h
+++ b/drivers/usb/gadget/g_zero.h
@@ -6,6 +6,9 @@
#ifndef __G_ZERO_H
#define __G_ZERO_H
+#define GZERO_BULK_BUFLEN 4096
+#define GZERO_QLEN 32
+
struct usb_zero_options {
unsigned pattern;
unsigned isoc_interval;
@@ -30,6 +33,15 @@ struct f_lb_opts {
struct usb_function_instance func_inst;
unsigned bulk_buflen;
unsigned qlen;
+
+ /*
+ * Read/write access to configfs attributes is handled by configfs.
+ *
+ * This is to protect the data from concurrent access by read/write
+ * and create symlink/remove symlink.
+ */
+ struct mutex lock;
+ int refcnt;
};
void lb_modexit(void);
diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c
index 0dd07ae1555d..d954bba7b405 100644
--- a/drivers/usb/gadget/zero.c
+++ b/drivers/usb/gadget/zero.c
@@ -66,8 +66,8 @@ module_param(loopdefault, bool, S_IRUGO|S_IWUSR);
static struct usb_zero_options gzero_options = {
.isoc_interval = 4,
.isoc_maxpacket = 1024,
- .bulk_buflen = 4096,
- .qlen = 32,
+ .bulk_buflen = GZERO_BULK_BUFLEN,
+ .qlen = GZERO_QLEN,
};
/*-------------------------------------------------------------------------*/