From 25d8015177ae7baedb8bdc76dffc0884b0d785a3 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Thu, 7 Nov 2013 08:41:28 +0100 Subject: [PATCH] usb: gadget: f_sourcesink: add configfs support Add support for using the sourcesink function in gadgets composed with configfs. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Kyungmin Park Signed-off-by: Felipe Balbi --- .../testing/configfs-usb-gadget-sourcesink | 12 + drivers/usb/gadget/Kconfig | 7 +- drivers/usb/gadget/f_sourcesink.c | 318 ++++++++++++++++++ drivers/usb/gadget/g_zero.h | 11 + drivers/usb/gadget/zero.c | 4 +- 5 files changed, 347 insertions(+), 5 deletions(-) create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-sourcesink diff --git a/Documentation/ABI/testing/configfs-usb-gadget-sourcesink b/Documentation/ABI/testing/configfs-usb-gadget-sourcesink new file mode 100644 index 000000000000..a30f3093ef6c --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-sourcesink @@ -0,0 +1,12 @@ +What: /config/usb-gadget/gadget/functions/SourceSink.name +Date: Nov 2013 +KenelVersion: 3.13 +Description: + The attributes: + + pattern - 0 (all zeros), 1 (mod63), 2 (none) + isoc_interval - 1..16 + isoc_maxpacket - 0 - 1023 (fs), 0 - 1024 (hs/ss) + isoc_mult - 0..2 (hs/ss only) + isoc_maxburst - 0..15 (ss only) + qlen - buffer length diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 26fe8769be45..5f1d4443aa0c 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -689,12 +689,13 @@ 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)" +config USB_CONFIGFS_F_LB_SS + boolean "Loopback and sourcesink function (for testing)" depends on USB_CONFIGFS select USB_F_SS_LB help - It loops back a configurable number of transfers. + Loopback function loops back a configurable number of transfers. + Sourcesink function either sinks and sources bulk data. 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 diff --git a/drivers/usb/gadget/f_sourcesink.c b/drivers/usb/gadget/f_sourcesink.c index c5ad4a1fa3c7..5d4251e7a377 100644 --- a/drivers/usb/gadget/f_sourcesink.c +++ b/drivers/usb/gadget/f_sourcesink.c @@ -477,6 +477,14 @@ no_iso: static void sourcesink_free_func(struct usb_function *f) { + struct f_ss_opts *opts; + + opts = container_of(f->fi, struct f_ss_opts, func_inst); + + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); + usb_free_all_descriptors(f); kfree(func_to_ss(f)); } @@ -865,6 +873,11 @@ static struct usb_function *source_sink_alloc_func( return NULL; ss_opts = container_of(fi, struct f_ss_opts, func_inst); + + mutex_lock(&ss_opts->lock); + ss_opts->refcnt++; + mutex_unlock(&ss_opts->lock); + pattern = ss_opts->pattern; isoc_interval = ss_opts->isoc_interval; isoc_maxpacket = ss_opts->isoc_maxpacket; @@ -885,6 +898,303 @@ static struct usb_function *source_sink_alloc_func( return &ss->function; } +static inline struct f_ss_opts *to_f_ss_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_ss_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_ss_opts); +CONFIGFS_ATTR_OPS(f_ss_opts); + +static void ss_attr_release(struct config_item *item) +{ + struct f_ss_opts *ss_opts = to_f_ss_opts(item); + + usb_put_function_instance(&ss_opts->func_inst); +} + +static struct configfs_item_operations ss_item_ops = { + .release = ss_attr_release, + .show_attribute = f_ss_opts_attr_show, + .store_attribute = f_ss_opts_attr_store, +}; + +static ssize_t f_ss_opts_pattern_show(struct f_ss_opts *opts, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->pattern); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_pattern_store(struct f_ss_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; + + if (num != 0 && num != 1 && num != 2) { + ret = -EINVAL; + goto end; + } + + opts->pattern = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_ss_opts_attribute f_ss_opts_pattern = + __CONFIGFS_ATTR(pattern, S_IRUGO | S_IWUSR, + f_ss_opts_pattern_show, + f_ss_opts_pattern_store); + +static ssize_t f_ss_opts_isoc_interval_show(struct f_ss_opts *opts, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->isoc_interval); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_interval_store(struct f_ss_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; + + if (num > 16) { + ret = -EINVAL; + goto end; + } + + opts->isoc_interval = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_ss_opts_attribute f_ss_opts_isoc_interval = + __CONFIGFS_ATTR(isoc_interval, S_IRUGO | S_IWUSR, + f_ss_opts_isoc_interval_show, + f_ss_opts_isoc_interval_store); + +static ssize_t f_ss_opts_isoc_maxpacket_show(struct f_ss_opts *opts, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->isoc_maxpacket); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_maxpacket_store(struct f_ss_opts *opts, + const char *page, size_t len) +{ + int ret; + u16 num; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = kstrtou16(page, 0, &num); + if (ret) + goto end; + + if (num > 1024) { + ret = -EINVAL; + goto end; + } + + opts->isoc_maxpacket = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_ss_opts_attribute f_ss_opts_isoc_maxpacket = + __CONFIGFS_ATTR(isoc_maxpacket, S_IRUGO | S_IWUSR, + f_ss_opts_isoc_maxpacket_show, + f_ss_opts_isoc_maxpacket_store); + +static ssize_t f_ss_opts_isoc_mult_show(struct f_ss_opts *opts, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->isoc_mult); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_mult_store(struct f_ss_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; + + if (num > 2) { + ret = -EINVAL; + goto end; + } + + opts->isoc_mult = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_ss_opts_attribute f_ss_opts_isoc_mult = + __CONFIGFS_ATTR(isoc_mult, S_IRUGO | S_IWUSR, + f_ss_opts_isoc_mult_show, + f_ss_opts_isoc_mult_store); + +static ssize_t f_ss_opts_isoc_maxburst_show(struct f_ss_opts *opts, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->isoc_maxburst); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t f_ss_opts_isoc_maxburst_store(struct f_ss_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; + + if (num > 15) { + ret = -EINVAL; + goto end; + } + + opts->isoc_maxburst = num; + ret = len; +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_ss_opts_attribute f_ss_opts_isoc_maxburst = + __CONFIGFS_ATTR(isoc_maxburst, S_IRUGO | S_IWUSR, + f_ss_opts_isoc_maxburst_show, + f_ss_opts_isoc_maxburst_store); + +static ssize_t f_ss_opts_bulk_buflen_show(struct f_ss_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_ss_opts_bulk_buflen_store(struct f_ss_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_ss_opts_attribute f_ss_opts_bulk_buflen = + __CONFIGFS_ATTR(buflen, S_IRUGO | S_IWUSR, + f_ss_opts_bulk_buflen_show, + f_ss_opts_bulk_buflen_store); + +static struct configfs_attribute *ss_attrs[] = { + &f_ss_opts_pattern.attr, + &f_ss_opts_isoc_interval.attr, + &f_ss_opts_isoc_maxpacket.attr, + &f_ss_opts_isoc_mult.attr, + &f_ss_opts_isoc_maxburst.attr, + &f_ss_opts_bulk_buflen.attr, + NULL, +}; + +static struct config_item_type ss_func_type = { + .ct_item_ops = &ss_item_ops, + .ct_attrs = ss_attrs, + .ct_owner = THIS_MODULE, +}; + static void source_sink_free_instance(struct usb_function_instance *fi) { struct f_ss_opts *ss_opts; @@ -900,7 +1210,15 @@ static struct usb_function_instance *source_sink_alloc_inst(void) ss_opts = kzalloc(sizeof(*ss_opts), GFP_KERNEL); if (!ss_opts) return ERR_PTR(-ENOMEM); + mutex_init(&ss_opts->lock); ss_opts->func_inst.free_func_inst = source_sink_free_instance; + ss_opts->isoc_interval = GZERO_ISOC_INTERVAL; + ss_opts->isoc_maxpacket = GZERO_ISOC_MAXPACKET; + ss_opts->bulk_buflen = GZERO_BULK_BUFLEN; + + config_group_init_type_name(&ss_opts->func_inst.group, "", + &ss_func_type); + return &ss_opts->func_inst; } DECLARE_USB_FUNCTION(SourceSink, source_sink_alloc_inst, diff --git a/drivers/usb/gadget/g_zero.h b/drivers/usb/gadget/g_zero.h index 19ec50a42a74..15f180904f8a 100644 --- a/drivers/usb/gadget/g_zero.h +++ b/drivers/usb/gadget/g_zero.h @@ -8,6 +8,8 @@ #define GZERO_BULK_BUFLEN 4096 #define GZERO_QLEN 32 +#define GZERO_ISOC_INTERVAL 4 +#define GZERO_ISOC_MAXPACKET 1024 struct usb_zero_options { unsigned pattern; @@ -27,6 +29,15 @@ struct f_ss_opts { unsigned isoc_mult; unsigned isoc_maxburst; unsigned bulk_buflen; + + /* + * 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; }; struct f_lb_opts { diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c index d954bba7b405..00b401906a32 100644 --- a/drivers/usb/gadget/zero.c +++ b/drivers/usb/gadget/zero.c @@ -64,8 +64,8 @@ static bool loopdefault = 0; module_param(loopdefault, bool, S_IRUGO|S_IWUSR); static struct usb_zero_options gzero_options = { - .isoc_interval = 4, - .isoc_maxpacket = 1024, + .isoc_interval = GZERO_ISOC_INTERVAL, + .isoc_maxpacket = GZERO_ISOC_MAXPACKET, .bulk_buflen = GZERO_BULK_BUFLEN, .qlen = GZERO_QLEN, }; -- 2.39.5