usb: gadget: f_loopback: add configfs support
authorAndrzej Pietrasiewicz <andrzej.p@samsung.com>
Thu, 7 Nov 2013 07:41:27 +0000 (08:41 +0100)
committerFelipe Balbi <balbi@ti.com>
Tue, 26 Nov 2013 19:46:13 +0000 (13:46 -0600)
Add support for using the loopback USB function in gadgets composed with
configfs.

Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
Documentation/ABI/testing/configfs-usb-gadget-loopback [new file with mode: 0644]
drivers/usb/gadget/Kconfig
drivers/usb/gadget/f_loopback.c
drivers/usb/gadget/g_zero.h
drivers/usb/gadget/zero.c

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-loopback b/Documentation/ABI/testing/configfs-usb-gadget-loopback
new file mode 100644 (file)
index 0000000..852b236
--- /dev/null
@@ -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
index a91e6422f93021f912042298a99cdde36362b09b..26fe8769be458fa929518d0f62aef7c38fa25cd9 100644 (file)
@@ -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
index b79065376a16db8e9f0335fda2ea7b7be4b81810..c35bb40cabf29b79102406fb0700d7da1b77ed35 100644 (file)
@@ -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);
index a1c1a978b5e916911de2dad2b9454443be6999ab..19ec50a42a74c65f247df28ef30e2a357aa2b93f 100644 (file)
@@ -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);
index 0dd07ae1555ddf066312e0ff4e8182a02f27a3d6..d954bba7b40504405fd858041b23a37275e2bf8a 100644 (file)
@@ -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,
 };
 
 /*-------------------------------------------------------------------------*/