Commit c0501f47 authored by Andrzej Pietrasiewicz's avatar Andrzej Pietrasiewicz Committed by Felipe Balbi

usb: gadget: f_loopback: add configfs support

Add support for using the loopback USB function in gadgets composed with
configfs.
Signed-off-by: default avatarAndrzej Pietrasiewicz <andrzej.p@samsung.com>
Signed-off-by: default avatarKyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 1efd54ea
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
...@@ -689,6 +689,18 @@ config USB_CONFIGFS_MASS_STORAGE ...@@ -689,6 +689,18 @@ config USB_CONFIGFS_MASS_STORAGE
device (in much the same way as the "loop" device driver), device (in much the same way as the "loop" device driver),
specified as a module parameter or sysfs option. 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 config USB_ZERO
tristate "Gadget Zero (DEVELOPMENT)" tristate "Gadget Zero (DEVELOPMENT)"
select USB_LIBCOMPOSITE select USB_LIBCOMPOSITE
......
...@@ -231,6 +231,14 @@ static int loopback_bind(struct usb_configuration *c, struct usb_function *f) ...@@ -231,6 +231,14 @@ static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
static void lb_free_func(struct usb_function *f) 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); usb_free_all_descriptors(f);
kfree(func_to_loop(f)); kfree(func_to_loop(f));
} }
...@@ -386,6 +394,11 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi) ...@@ -386,6 +394,11 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
lb_opts = container_of(fi, struct f_lb_opts, func_inst); 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; buflen = lb_opts->bulk_buflen;
qlen = lb_opts->qlen; qlen = lb_opts->qlen;
if (!qlen) if (!qlen)
...@@ -402,6 +415,118 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi) ...@@ -402,6 +415,118 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
return &loop->function; 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) static void lb_free_instance(struct usb_function_instance *fi)
{ {
struct f_lb_opts *lb_opts; struct f_lb_opts *lb_opts;
...@@ -417,7 +542,14 @@ static struct usb_function_instance *loopback_alloc_instance(void) ...@@ -417,7 +542,14 @@ static struct usb_function_instance *loopback_alloc_instance(void)
lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL); lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL);
if (!lb_opts) if (!lb_opts)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
mutex_init(&lb_opts->lock);
lb_opts->func_inst.free_func_inst = lb_free_instance; 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; return &lb_opts->func_inst;
} }
DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc); DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc);
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
#ifndef __G_ZERO_H #ifndef __G_ZERO_H
#define __G_ZERO_H #define __G_ZERO_H
#define GZERO_BULK_BUFLEN 4096
#define GZERO_QLEN 32
struct usb_zero_options { struct usb_zero_options {
unsigned pattern; unsigned pattern;
unsigned isoc_interval; unsigned isoc_interval;
...@@ -30,6 +33,15 @@ struct f_lb_opts { ...@@ -30,6 +33,15 @@ struct f_lb_opts {
struct usb_function_instance func_inst; struct usb_function_instance func_inst;
unsigned bulk_buflen; unsigned bulk_buflen;
unsigned qlen; 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); void lb_modexit(void);
......
...@@ -66,8 +66,8 @@ module_param(loopdefault, bool, S_IRUGO|S_IWUSR); ...@@ -66,8 +66,8 @@ module_param(loopdefault, bool, S_IRUGO|S_IWUSR);
static struct usb_zero_options gzero_options = { static struct usb_zero_options gzero_options = {
.isoc_interval = 4, .isoc_interval = 4,
.isoc_maxpacket = 1024, .isoc_maxpacket = 1024,
.bulk_buflen = 4096, .bulk_buflen = GZERO_BULK_BUFLEN,
.qlen = 32, .qlen = GZERO_QLEN,
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment