Add support for using uvc as a component of a composite gadget set up with configfs. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 11 + drivers/usb/gadget/Kconfig | 12 + drivers/usb/gadget/f_uvc.c | 229 +++++++++++++++++++++ drivers/usb/gadget/u_uvc.h | 9 + 4 files changed, 261 insertions(+), 0 deletions(-) create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-uvc diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc new file mode 100644 index 0000000..e9f9663 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -0,0 +1,11 @@ +What: /config/usb-gadget/gadget/functions/uvc.name +Date: Nov 2013 +KenelVersion: 3.13 +Description: + The attributes: + + streaming_interval - 1..16 + streaming_maxpacket - 1..1023 (fs), 1..3072 (hs/ss) + streaming_maxburst - 0..15 (ss only) + trace - trace level bitmask, + common for all uvc instances diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index c8a72e0..1e863ad 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -692,6 +692,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_UVC + boolean "USB Webcam function" + depends on USB_CONFIGFS + depends on VIDEO_DEV + select USB_LIBCOMPOSITE + select VIDEOBUF2_VMALLOC + select USB_F_UVC + help + The Webcam function acts as a composite USB Audio and Video Class + device. It provides a userspace API to process UVC control requests + and stream video data to the host. + config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" select USB_LIBCOMPOSITE diff --git a/drivers/usb/gadget/f_uvc.c b/drivers/usb/gadget/f_uvc.c index 9e1deff..4df877b 100644 --- a/drivers/usb/gadget/f_uvc.c +++ b/drivers/usb/gadget/f_uvc.c @@ -961,6 +961,220 @@ error: return ret; } +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uvc_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_uvc_opts); +CONFIGFS_ATTR_OPS(f_uvc_opts); + +static void uvc_attr_release(struct config_item *item) +{ + struct f_uvc_opts *opts = to_f_uvc_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations uvc_item_ops = { + .release = uvc_attr_release, + .show_attribute = f_uvc_opts_attr_show, + .store_attribute = f_uvc_opts_attr_store, +}; + +static ssize_t uvc_opts_trace_show(struct f_uvc_opts *opts, char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", uvc_gadget_trace_param); + mutex_unlock(&opts->lock); + + return result; +} + +static ssize_t uvc_opts_trace_store(struct f_uvc_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; + + uvc_gadget_trace_param = num; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_uvc_opts_attribute f_uvc_opts_trace = + __CONFIGFS_ATTR(trace, S_IRUGO | S_IWUSR, uvc_opts_trace_show, + uvc_opts_trace_store); + +static ssize_t uvc_opts_streaming_interval_show(struct f_uvc_opts *opts, + char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->streaming_interval); + mutex_unlock(&opts->lock); + result = 0; + + return result; +} + +static ssize_t uvc_opts_streaming_interval_store(struct f_uvc_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->streaming_interval = num; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_uvc_opts_attribute f_uvc_opts_streaming_interval = + __CONFIGFS_ATTR(streaming_interval, S_IRUGO | S_IWUSR, + uvc_opts_streaming_interval_show, + uvc_opts_streaming_interval_store); + +static ssize_t uvc_opts_streaming_maxpacket_show(struct f_uvc_opts *opts, + char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->streaming_maxpacket); + mutex_unlock(&opts->lock); + result = 0; + + return result; +} + +static ssize_t uvc_opts_streaming_maxpacket_store(struct f_uvc_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 > 3072) { + ret = -EINVAL; + goto end; + } + opts->streaming_maxpacket = num; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_uvc_opts_attribute f_uvc_opts_streaming_maxpacket = + __CONFIGFS_ATTR(streaming_maxpacket, S_IRUGO | S_IWUSR, + uvc_opts_streaming_maxpacket_show, + uvc_opts_streaming_maxpacket_store); + +static ssize_t uvc_opts_streaming_maxburst_show(struct f_uvc_opts *opts, + char *page) +{ + int result; + + mutex_lock(&opts->lock); + result = sprintf(page, "%d", opts->streaming_maxburst); + mutex_unlock(&opts->lock); + result = 0; + + return result; +} + +static ssize_t uvc_opts_streaming_maxburst_store(struct f_uvc_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->streaming_maxburst = num; + ret = len; + +end: + mutex_unlock(&opts->lock); + return ret; +} + +static struct f_uvc_opts_attribute f_uvc_opts_streaming_maxburst = + __CONFIGFS_ATTR(streaming_maxburst, S_IRUGO | S_IWUSR, + uvc_opts_streaming_maxburst_show, + uvc_opts_streaming_maxburst_store); + + +static struct configfs_attribute *uvc_attrs[] = { + &f_uvc_opts_trace.attr, + &f_uvc_opts_streaming_interval.attr, + &f_uvc_opts_streaming_maxpacket.attr, + &f_uvc_opts_streaming_maxburst.attr, + NULL, +}; + +static struct config_item_type uvc_func_type = { + .ct_item_ops = &uvc_item_ops, + .ct_attrs = uvc_attrs, + .ct_owner = THIS_MODULE, +}; + /* -------------------------------------------------------------------------- * USB gadget function */ @@ -981,17 +1195,26 @@ static struct usb_function_instance *uvc_alloc_inst(void) opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); opts->func_inst.free_func_inst = uvc_free_inst; + config_group_init_type_name(&opts->func_inst.group, "", + &uvc_func_type); + return &opts->func_inst; } static void uvc_free(struct usb_function *f) { struct uvc_device *uvc; + struct f_uvc_opts *opts; uvc = to_uvc(f); + opts = container_of(f->fi, struct f_uvc_opts, func_inst); kfree(uvc); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); } static void uvc_unbind(struct usb_configuration *c, struct usb_function *f) @@ -1015,11 +1238,17 @@ static void uvc_unbind(struct usb_configuration *c, struct usb_function *f) struct usb_function *uvc_alloc(struct usb_function_instance *fi) { struct uvc_device *uvc; + struct f_uvc_opts *opts; uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); if (uvc == NULL) return ERR_PTR(-ENOMEM); + opts = container_of(fi, struct f_uvc_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + uvc->state = UVC_STATE_DISCONNECTED; uvc->desc.fs_control = uvc_fs_control_cls; diff --git a/drivers/usb/gadget/u_uvc.h b/drivers/usb/gadget/u_uvc.h index 6758ba4..85b782e 100644 --- a/drivers/usb/gadget/u_uvc.h +++ b/drivers/usb/gadget/u_uvc.h @@ -24,6 +24,15 @@ struct f_uvc_opts { unsigned int streaming_interval; unsigned int streaming_maxpacket; unsigned int streaming_maxburst; + + /* + * 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 uvc_set_trace_param(unsigned int uvc_gadget_trace_param_webcam); -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html