Add support for using uvc as a component of a composite gadget set up with configfs. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 11 + drivers/usb/gadget/Kconfig | 11 + drivers/usb/gadget/function/Makefile | 2 +- drivers/usb/gadget/function/f_uvc.c | 94 + drivers/usb/gadget/function/u_uvc.h | 19 + drivers/usb/gadget/function/uvc_configfs.c | 2928 +++++++++++++++++++++ drivers/usb/gadget/function/uvc_configfs.h | 283 ++ 7 files changed, 3347 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-uvc create mode 100644 drivers/usb/gadget/function/uvc_configfs.c create mode 100644 drivers/usb/gadget/function/uvc_configfs.h diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc new file mode 100644 index 0000000..b3b4ba5 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -0,0 +1,11 @@ +What: /config/usb-gadget/gadget/functions/uvc.name +Date: Oct 2014 +KenelVersion: 3.18 +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 4b3d4e9..ce55234 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -356,6 +356,17 @@ config USB_CONFIGFS_F_FS implemented in kernel space (for instance Ethernet, serial or mass storage) and other are implemented in user space. +config USB_CONFIGFS_F_UVC + boolean "USB Webcam function" + depends on USB_CONFIGFS + depends on VIDEO_DEV + 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. + source "drivers/usb/gadget/legacy/Kconfig" endchoice diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index ad80f21..94391f3 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -32,5 +32,5 @@ usb_f_mass_storage-y := f_mass_storage.o storage_common.o obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o usb_f_fs-y := f_fs.o obj-$(CONFIG_USB_F_FS) += usb_f_fs.o -usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o +usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 9d22928..10ad916 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -28,6 +28,7 @@ #include <media/v4l2-event.h> #include "uvc.h" +#include "uvc_configfs.h" #include "uvc_v4l2.h" #include "uvc_video.h" #include "u_uvc.h" @@ -467,6 +468,9 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) break; } + if (!uvc_control_desc || !uvc_streaming_cls) + return ERR_PTR(-ENODEV); + /* Descriptors layout * * uvc_iad @@ -642,6 +646,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc_streaming_intf_alt0.iInterface = ret; uvc_streaming_intf_alt1.iInterface = ret; + /* Allocate interface IDs. */ if ((ret = usb_interface_id(c, f)) < 0) goto error; @@ -657,10 +662,25 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) /* Copy descriptors */ f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL); + if (IS_ERR(f->fs_descriptors)) { + ret = PTR_ERR(f->fs_descriptors); + f->fs_descriptors = NULL; + goto error; + } if (gadget_is_dualspeed(cdev->gadget)) f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); + if (IS_ERR(f->hs_descriptors)) { + ret = PTR_ERR(f->hs_descriptors); + f->hs_descriptors = NULL; + goto error; + } if (gadget_is_superspeed(c->cdev->gadget)) f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); + if (IS_ERR(f->ss_descriptors)) { + ret = PTR_ERR(f->ss_descriptors); + f->ss_descriptors = NULL; + goto error; + } /* Preallocate control endpoint request. */ uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); @@ -735,16 +755,87 @@ 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(&f_uvc_header_group, "header", + &f_uvc_header_type); + config_group_init_type_name(&f_uvc_processing_group, "processing", + &f_uvc_processing_type); + config_group_init_type_name(&f_uvc_class_fs_group, "fs", + &f_uvc_class_fs_type); + config_group_init_type_name(&f_uvc_class_ss_group, "ss", + &f_uvc_class_ss_type); + f_uvc_class_group.default_groups = f_uvc_class_default_groups; + config_group_init_type_name(&f_uvc_class_group, "class", + &f_uvc_class_type); + config_group_init_type_name(&f_uvc_camera_group, "camera", + &f_uvc_camera_type); + config_group_init_type_name(&f_uvc_output_group, "output", + &f_uvc_output_type); + f_uvc_terminal_group.default_groups = f_uvc_terminal_default_groups; + config_group_init_type_name(&f_uvc_terminal_group, "terminal", + &f_uvc_terminal_type); + f_uvc_control_group.group.default_groups = f_uvc_control_default_groups; + INIT_LIST_HEAD(&f_uvc_control_group.known_targets); + config_group_init_type_name(&f_uvc_control_group.group, "control", + &f_uvc_control_type); + config_group_init_type_name(&f_uvc_input_header_group, "input_header", + &f_uvc_input_header_type); + config_group_init_type_name(&f_uvc_color_matching_group, "color_matching", + &f_uvc_color_matching_type); + config_group_init_type_name(&f_uvc_streaming_fs_group, "fs", + &f_uvc_streaming_fs_type); + config_group_init_type_name(&f_uvc_streaming_hs_group, "hs", + &f_uvc_streaming_hs_type); + config_group_init_type_name(&f_uvc_streaming_ss_group, "ss", + &f_uvc_streaming_ss_type); + f_uvc_streaming_class_group.default_groups = + f_uvc_streaming_class_default_groups; + config_group_init_type_name(&f_uvc_streaming_class_group, "class", + &f_uvc_streaming_class_type); + config_group_init_type_name(&f_uvc_frame_yuv_group, "yuv", + &f_uvc_frame_yuv_type); + config_group_init_type_name(&f_uvc_frame_mjpeg_group, "mjpeg", + &f_uvc_frame_mjpeg_type); + f_uvc_frame_group.default_groups = f_uvc_frame_default_groups; + config_group_init_type_name(&f_uvc_frame_group, "frame", + &f_uvc_frame_type); + config_group_init_type_name(&f_uvc_format_yuv_group, "yuv", + &f_uvc_format_yuv_type); + config_group_init_type_name(&f_uvc_format_mjpeg_group, "mjpeg", + &f_uvc_format_mjpeg_type); + f_uvc_format_group.group.default_groups = f_uvc_format_default_groups; + INIT_LIST_HEAD(&f_uvc_format_group.known_targets); + config_group_init_type_name(&f_uvc_format_group.group, "format", + &f_uvc_format_type); + f_uvc_streaming_group.group.default_groups = f_uvc_streaming_default_groups; + INIT_LIST_HEAD(&f_uvc_streaming_group.known_targets); + config_group_init_type_name(&f_uvc_streaming_group.group, "streaming", + &f_uvc_streaming_type); + opts->func_inst.group.default_groups = f_uvc_default_groups; + opts->fs_class = &f_uvc_class_fs_group.cg_item; + opts->ss_class = &f_uvc_class_ss_group.cg_item; + opts->fs_streaming_class = &f_uvc_streaming_fs_group.cg_item; + opts->hs_streaming_class = &f_uvc_streaming_hs_group.cg_item; + opts->ss_streaming_class = &f_uvc_streaming_ss_group.cg_item; + INIT_LIST_HEAD(&opts->known_targets); + 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 = to_uvc(f); + struct f_uvc_opts *opts; + 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) @@ -777,11 +868,14 @@ struct usb_function *uvc_alloc(struct usb_function_instance *fi) uvc->state = UVC_STATE_DISCONNECTED; opts = fi_to_f_uvc_opts(fi); + mutex_lock(&opts->lock); + ++opts->refcnt; uvc->desc.fs_control = opts->fs_control; uvc->desc.ss_control = opts->ss_control; uvc->desc.fs_streaming = opts->fs_streaming; uvc->desc.hs_streaming = opts->hs_streaming; uvc->desc.ss_streaming = opts->ss_streaming; + mutex_unlock(&opts->lock); /* Register the function. */ uvc->func.name = "uvc"; diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 9307bd8..737001b 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -31,6 +31,25 @@ struct f_uvc_opts { const struct uvc_descriptor_header * const *fs_streaming; const struct uvc_descriptor_header * const *hs_streaming; const struct uvc_descriptor_header * const *ss_streaming; + + /* + * 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; + /* + * In uvc function's configfs directory there will be symbolic links. + * The allowed targets are in the "known_targets" list. + */ + struct list_head known_targets; + struct config_item *fs_class; + struct config_item *ss_class; + struct config_item *fs_streaming_class; + struct config_item *ss_streaming_class; + struct config_item *hs_streaming_class; }; void uvc_set_trace_param(unsigned int trace); diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c new file mode 100644 index 0000000..2031114 --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -0,0 +1,2928 @@ +/* + * uvc_configfs.c + * + * Configfs hierarchy definitions for the uvc function + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "uvc_configfs.h" +#include "configfs.h" +#include "u_uvc.h" + +#define __F_UVC_STRUCT_DO_SHOW(__conf_struct__, __attr__, __file__) \ +static ssize_t f_uvc_##__conf_struct__##_##__attr__##_show( \ + struct f_uvc_##__conf_struct__ *h, char *page) \ +{ \ + struct configfs_subsystem *subsys; \ + struct f_uvc_opts *opts; \ + int result; \ + \ + subsys = __conf_struct__##_to_subsys(h); \ + mutex_lock(&subsys->su_mutex); \ + opts = __conf_struct__##_to_opts(h); \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d", h->desc.__file__); \ + mutex_unlock(&opts->lock); \ + mutex_unlock(&subsys->su_mutex); \ + \ + return result; \ +} + +#define __F_UVC_STRUCT_DO_STORE( \ +__conf_struct__, __attr__, __file__, __prec__, __max__) \ +static ssize_t f_uvc_##__conf_struct__##_##__attr__##_store( \ + struct f_uvc_##__conf_struct__ *h, const char *page, size_t len)\ +{ \ + struct configfs_subsystem *subsys; \ + struct f_uvc_opts *opts; \ + int ret; \ + u##__prec__ num; \ + \ + subsys = __conf_struct__##_to_subsys(h); \ + mutex_lock(&subsys->su_mutex); \ + opts = __conf_struct__##_to_opts(h); \ + mutex_lock(&opts->lock); \ + if (opts->refcnt || h->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou##__prec__(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > (__max__)) { \ + ret = -EINVAL; \ + goto end; \ + } \ + h->desc.__file__ = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(&subsys->su_mutex); \ + return ret; \ +} + +#define __F_UVC_OPTS_DO_SHOW(__conf_struct__, __attr__, __member__) \ +static ssize_t f_uvc_##__conf_struct__##_##__attr__##_show( \ + struct f_uvc_##__conf_struct__ *h, char *page) \ +{ \ + struct configfs_subsystem *subsys; \ + struct f_uvc_opts *opts; \ + int result; \ + \ + subsys = __conf_struct__##_to_subsys(h); \ + mutex_lock(&subsys->su_mutex); \ + opts = __conf_struct__##_to_opts(h); \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d", opts->__member__); \ + mutex_unlock(&opts->lock); \ + mutex_unlock(&subsys->su_mutex); \ + \ + return result; \ +} + +#define __F_UVC_OPTS_DO_STORE( \ +__conf_struct__, __attr__, __member__, __prec__, __max__) \ +static ssize_t f_uvc_##__conf_struct__##_##__attr__##_store( \ + struct f_uvc_##__conf_struct__ *h, const char *page, size_t len)\ +{ \ + struct configfs_subsystem *subsys; \ + struct f_uvc_opts *opts; \ + int ret; \ + u##__prec__ num; \ + \ + subsys = __conf_struct__##_to_subsys(h); \ + mutex_lock(&subsys->su_mutex); \ + opts = __conf_struct__##_to_opts(h); \ + mutex_lock(&opts->lock); \ + if (opts->refcnt || h->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou##__prec__(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > (__max__)) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->__member__ = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(&subsys->su_mutex); \ + return ret; \ +} + +#define __F_UVC_DROP(__conf_struct__, __parent__) \ +static void f_uvc_##__conf_struct__##_drop(struct config_group *group, \ + struct config_item *item) \ +{ \ + struct f_uvc_##__conf_struct__##_item *cs; \ + struct f_uvc_opts *opts; \ + \ + cs = to_f_uvc_##__conf_struct__##_item(item); \ + opts = container_of( \ + to_config_group(group->cg_item.__parent__), \ + struct f_uvc_opts, func_inst.group); \ + mutex_lock(&opts->lock); \ + list_del(&cs->target_entry); \ + mutex_unlock(&opts->lock); \ + config_item_put(item); \ +} + +#define __F_UVC_OPS(__conf_struct__) \ +static struct configfs_group_operations f_uvc_##__conf_struct__##_ops = {\ + .make_item = &f_uvc_##__conf_struct__##_make, \ + .drop_item = &f_uvc_##__conf_struct__##_drop, \ +}; + +#define __F_UVC_ITEM_OPS(__conf_struct__) \ +static struct configfs_item_operations f_uvc_##__conf_struct__##_ops = {\ + .release = f_uvc_##__conf_struct__##_release, \ + .show_attribute = &f_uvc_##__conf_struct__##_attr_show, \ + .store_attribute = &f_uvc_##__conf_struct__##_attr_store,\ +}; + +#define __TO_F_UVC_STRUCT(__conf_struct__) \ +static inline struct f_uvc_##__conf_struct__ *to_f_uvc_##__conf_struct__(\ + struct config_item *item) \ +{ \ + return container_of(to_config_group(item), \ + struct f_uvc_##__conf_struct__, \ + group); \ +} + +#define __TO_F_UVC_STRUCT_ITEM(__item_struct__) \ +static inline struct f_uvc_##__item_struct__ \ +*to_f_uvc_##__item_struct__(struct config_item *item) \ +{ \ + return container_of(item, struct f_uvc_##__item_struct__, item);\ +} + +#define __TO_OPTS(__uvc_struct__, __parent__) \ +static inline struct f_uvc_opts \ +*__uvc_struct__##_to_opts(struct f_uvc_##__uvc_struct__ *c) \ +{ \ + return container_of( \ + to_config_group(c->group.cg_item.__parent__), \ + struct f_uvc_opts, \ + func_inst.group); \ +} + +#define __ITEM_TO_OPTS(__uvc_struct__, __parent__) \ +static inline struct f_uvc_opts \ +*__uvc_struct__##_to_opts(struct f_uvc_##__uvc_struct__ *s) \ +{ \ + return container_of( \ + to_config_group(s->item.__parent__), \ + struct f_uvc_opts, \ + func_inst.group); \ +} + +#define __ITEM_TO_SUBSYS(__item_struct__) \ +static inline struct configfs_subsystem \ +*__item_struct__##_to_subsys(struct f_uvc_##__item_struct__ *h) \ +{ \ + struct config_group *pg; \ + \ + pg = to_config_group(h->item.ci_parent); \ + return pg->cg_subsys; \ +} + +#define __GROUP_TYPE(__uvc_struct__) \ +struct config_item_type f_uvc_##__uvc_struct__##_type = { \ + .ct_group_ops = &f_uvc_##__uvc_struct__##_ops, \ + .ct_owner = THIS_MODULE, \ +} + +#define __ITEM_TYPE(__uvc_struct__) \ +struct config_item_type f_uvc_##__uvc_struct__##_type = { \ + .ct_item_ops = &f_uvc_##__uvc_struct__##_ops, \ + .ct_attrs = f_uvc_##__uvc_struct__##_attrs, \ + .ct_owner = THIS_MODULE, \ +} + +#define __F_UVC_ATTR(__uvc_struct__, __attr_var__, __attr_name__, \ + __prec__, __max__) \ +__F_UVC_STRUCT_DO_SHOW(__uvc_struct__, __attr_var__, __attr_name__); \ +__F_UVC_STRUCT_DO_STORE(__uvc_struct__, __attr_var__, __attr_name__, \ + __prec__, __max__); \ + \ +static struct f_uvc_##__uvc_struct__##_attribute \ + f_uvc_##__uvc_struct__##_##__attr_var__ = \ + __CONFIGFS_ATTR(__attr_name__, S_IRUGO | S_IWUSR, \ + f_uvc_##__uvc_struct__##_##__attr_var__##_show, \ + f_uvc_##__uvc_struct__##_##__attr_var__##_store) \ + +#define __F_UVC_CLASS_LINK(uvc_struct, UVC_ENUM, speed) \ +static inline int f_uvc_class_##speed##_link_##uvc_struct( \ + struct f_uvc_class_##speed##_item *fsi, \ + struct f_uvc_##uvc_struct##_item *i) \ +{ \ + if (fsi->uvc_##speed##_control_cls[F_UVC_CLASS_##UVC_ENUM]) \ + return -EBUSY; \ + \ + fsi->uvc_##speed##_control_cls[F_UVC_CLASS_##UVC_ENUM] = \ + (struct uvc_descriptor_header *)&i->desc; \ + ++i->refcnt; \ + \ + return 0; \ +} + +#define __F_UVC_STREAMING_LINK(uvc_struct, UVC_ENUM, speed) \ +static inline int f_uvc_streaming_##speed##_link_##uvc_struct( \ + struct f_uvc_streaming_##speed##_item *fsi,\ + struct f_uvc_##uvc_struct##_item *i) \ +{ \ + if (fsi->streaming[F_UVC_STREAMING_##UVC_ENUM]) \ + return -EBUSY; \ + \ + fsi->streaming[F_UVC_STREAMING_##UVC_ENUM] = \ + (struct uvc_descriptor_header *)&i->desc; \ + ++i->refcnt; \ + \ + return 0; \ +} + +struct f_uvc_link { + struct f_uvc_item_struct_header *desc; + struct list_head link; +}; + +static inline u8 __uvc_dt(struct config_item *ci) +{ + struct f_uvc_item_struct_header *h; + + /* + * by design the config_items passed to this function + * are the first members of structs compatible with + * f_uvc_item_struct_header + */ + h = (struct f_uvc_item_struct_header *)ci; + + return h->desc.bDescriptorSubType; +} + +extern unsigned int uvc_gadget_trace_param; + +/* uvc.fun/control */ +__TO_F_UVC_STRUCT(control); + +__TO_OPTS(control, ci_parent); + +struct config_item_type f_uvc_control_type = { + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/control/header/item */ +__TO_F_UVC_STRUCT_ITEM(header_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_header_item); +CONFIGFS_ATTR_OPS(f_uvc_header_item); + +static void f_uvc_header_item_release(struct config_item *item) +{ + struct f_uvc_header_item *header_item; + + header_item = to_f_uvc_header_item(item); + kfree(header_item); +} + +__F_UVC_ITEM_OPS(header_item); + +__ITEM_TO_OPTS(header_item, ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(header_item); + +__F_UVC_ATTR(header_item, bcd_uvc, bcdUVC, 16, 65535); +__F_UVC_ATTR(header_item, dw_clock_freq, dwClockFrequency, 32, 4294967295U); + +static struct configfs_attribute *f_uvc_header_item_attrs[] = { + &f_uvc_header_item_bcd_uvc.attr, + &f_uvc_header_item_dw_clock_freq.attr, + NULL, +}; + +__ITEM_TYPE(header_item); + +/* uvc.fun/control/header */ +static struct config_item *f_uvc_header_make(struct config_group *group, + const char *name) +{ + struct f_uvc_header_item *hi; + struct f_uvc_control *control; + struct f_uvc_opts *opts; + + hi = kzalloc(sizeof(*hi), GFP_KERNEL); + if (!hi) + return ERR_CAST(hi); + + hi->desc.bLength = UVC_DT_HEADER_SIZE(1); + hi->desc.bDescriptorType = USB_DT_CS_INTERFACE; + hi->desc.bDescriptorSubType = UVC_VC_HEADER; + hi->desc.bcdUVC = cpu_to_le16(0x0100); + hi->desc.wTotalLength = 0; /* dynamic */ + hi->desc.dwClockFrequency = cpu_to_le32(48000000); + hi->desc.bInCollection = 0; /* dynamic */ + hi->desc.baInterfaceNr[0] = 0; /* dynamic */ + config_item_init_type_name(&hi->item, name, &f_uvc_header_item_type); + + control = to_f_uvc_control(group->cg_item.ci_parent); + opts = control_to_opts(control); + mutex_lock(&opts->lock); + list_add_tail(&hi->target_entry, &control->known_targets); + mutex_unlock(&opts->lock); + + return &hi->item; +} + +__F_UVC_DROP(header, ci_parent->ci_parent); +__F_UVC_OPS(header); +__GROUP_TYPE(header); + +/* uvc.fun/control/processing/item */ +__TO_F_UVC_STRUCT_ITEM(processing_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_processing_item); +CONFIGFS_ATTR_OPS(f_uvc_processing_item); + +static void f_uvc_processing_item_release(struct config_item *item) +{ + struct f_uvc_processing_item *processing_item; + + processing_item = to_f_uvc_processing_item(item); + kfree(processing_item); +} + +__F_UVC_ITEM_OPS(processing_item); + +__ITEM_TO_OPTS(processing_item, ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(processing_item); + +__F_UVC_ATTR(processing_item, w_max_multiplier, wMaxMultiplier, 16, 65535); + +static struct configfs_attribute *f_uvc_processing_item_attrs[] = { + &f_uvc_processing_item_w_max_multiplier.attr, + NULL, +}; + +__ITEM_TYPE(processing_item); + +/* uvc.fun/control/processing */ +static struct config_item *f_uvc_processing_make(struct config_group *group, + const char *name) +{ + struct f_uvc_processing_item *pi; + struct f_uvc_control *control; + struct f_uvc_opts *opts; + + pi = kzalloc(sizeof(*pi), GFP_KERNEL); + if (!pi) + return ERR_CAST(pi); + + pi->desc.bLength = UVC_DT_PROCESSING_UNIT_SIZE(2), + pi->desc.bDescriptorType = USB_DT_CS_INTERFACE, + pi->desc.bDescriptorSubType = UVC_VC_PROCESSING_UNIT, + pi->desc.bUnitID = 2, + pi->desc.bSourceID = 1, + pi->desc.wMaxMultiplier = cpu_to_le16(16*1024), + pi->desc.bControlSize = 2, + pi->desc.bmControls[0] = 1, + pi->desc.bmControls[1] = 0, + pi->desc.iProcessing = 0, + config_item_init_type_name(&pi->item, name, + &f_uvc_processing_item_type); + + control = to_f_uvc_control(group->cg_item.ci_parent); + opts = control_to_opts(control); + mutex_lock(&opts->lock); + list_add_tail(&pi->target_entry, &control->known_targets); + mutex_unlock(&opts->lock); + + return &pi->item; +} + +__F_UVC_DROP(processing, ci_parent->ci_parent); +__F_UVC_OPS(processing); +__GROUP_TYPE(processing); + +/* uvc.fun/control/class */ +struct config_item_type f_uvc_class_type = { + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/control/class/fs/item */ +__TO_F_UVC_STRUCT_ITEM(class_fs_item); + +static void f_uvc_class_fs_item_release(struct config_item *item) +{ + struct f_uvc_class_fs_item *class_fs_item; + + class_fs_item = to_f_uvc_class_fs_item(item); + kfree(class_fs_item); +} + +__F_UVC_CLASS_LINK(header, HEADER, fs); +__F_UVC_CLASS_LINK(processing, PROCESSING, fs); +__F_UVC_CLASS_LINK(camera, INPUT, fs); +__F_UVC_CLASS_LINK(output, OUTPUT, fs); + +static inline struct f_uvc_camera_item +*to_f_uvc_camera_item(struct config_item *item); + +static inline struct f_uvc_output_item +*to_f_uvc_output_item(struct config_item *item); + +static int f_uvc_class_fs_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_control *control; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_class_fs_item *fsi; + int ret = 0; + + fsi = to_f_uvc_class_fs_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + control = to_f_uvc_control(src->ci_parent->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = control_to_opts(control); + mutex_lock(&opts->lock); + if (fsi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &control->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VC_HEADER: + ret = f_uvc_class_fs_link_header(fsi, + to_f_uvc_header_item(target)); + break; + case UVC_VC_PROCESSING_UNIT: + ret = f_uvc_class_fs_link_processing( + fsi, to_f_uvc_processing_item(target)); + break; + case UVC_VC_INPUT_TERMINAL: + ret = f_uvc_class_fs_link_camera(fsi, + to_f_uvc_camera_item(target)); + break; + case UVC_VC_OUTPUT_TERMINAL: + ret = f_uvc_class_fs_link_output(fsi, + to_f_uvc_output_item(target)); + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static inline struct f_uvc_opts +*class_fs_item_to_opts(struct f_uvc_class_fs_item *s); + +static int f_uvc_class_fs_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_class_fs_item *fsi; + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + + fsi = to_f_uvc_class_fs_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + opts = class_fs_item_to_opts(fsi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VC_HEADER: + fsi->uvc_fs_control_cls[F_UVC_CLASS_HEADER] = NULL; + --to_f_uvc_header_item(target)->refcnt; + break; + case UVC_VC_PROCESSING_UNIT: + fsi->uvc_fs_control_cls[F_UVC_CLASS_PROCESSING] = NULL; + --to_f_uvc_processing_item(target)->refcnt; + break; + case UVC_VC_INPUT_TERMINAL: + fsi->uvc_fs_control_cls[F_UVC_CLASS_INPUT] = NULL; + --to_f_uvc_camera_item(target)->refcnt; + break; + case UVC_VC_OUTPUT_TERMINAL: + fsi->uvc_fs_control_cls[F_UVC_CLASS_OUTPUT] = NULL; + --to_f_uvc_output_item(target)->refcnt; + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_class_fs_item_ops = { + .allow_link = f_uvc_class_fs_link, + .drop_link = f_uvc_class_fs_unlink, + .release = f_uvc_class_fs_item_release, +}; + +__ITEM_TO_OPTS(class_fs_item, ci_parent->ci_parent->ci_parent->ci_parent); + +static struct config_item_type f_uvc_class_fs_item_type = { + .ct_item_ops = &f_uvc_class_fs_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/control/class/fs */ +static struct config_item *f_uvc_class_fs_make(struct config_group *group, + const char *name) +{ + struct f_uvc_class_fs_item *fsi; + struct config_item *uvc_opts_item; + struct f_uvc_opts *opts; + + fsi = kzalloc(sizeof(*fsi), GFP_KERNEL); + if (!fsi) + return ERR_CAST(fsi); + + config_item_init_type_name(&fsi->item, name, &f_uvc_class_fs_item_type); + + uvc_opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = container_of(to_config_group(uvc_opts_item), struct f_uvc_opts, + func_inst.group); + mutex_lock(&opts->lock); + list_add_tail(&fsi->target_entry, &opts->known_targets); + mutex_unlock(&opts->lock); + + return &fsi->item; +} + +__F_UVC_DROP(class_fs, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(class_fs); +__GROUP_TYPE(class_fs); + +/* uvc.fun/control/class/ss/item */ +__TO_F_UVC_STRUCT_ITEM(class_ss_item); + +static void f_uvc_class_ss_item_release(struct config_item *item) +{ + struct f_uvc_class_ss_item *class_ss_item; + + class_ss_item = to_f_uvc_class_ss_item(item); + kfree(class_ss_item); +} + +__F_UVC_CLASS_LINK(header, HEADER, ss); +__F_UVC_CLASS_LINK(processing, PROCESSING, ss); +__F_UVC_CLASS_LINK(camera, INPUT, ss); +__F_UVC_CLASS_LINK(output, OUTPUT, ss); + +static inline struct f_uvc_camera_item +*to_f_uvc_camera_item(struct config_item *item); + +static inline struct f_uvc_output_item +*to_f_uvc_output_item(struct config_item *item); + +static int f_uvc_class_ss_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_control *control; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_class_ss_item *ssi; + int ret = 0; + + ssi = to_f_uvc_class_ss_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + control = to_f_uvc_control(src->ci_parent->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = control_to_opts(control); + mutex_lock(&opts->lock); + if (ssi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &control->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VC_HEADER: + ret = f_uvc_class_ss_link_header(ssi, + to_f_uvc_header_item(target)); + break; + case UVC_VC_PROCESSING_UNIT: + ret = f_uvc_class_ss_link_processing( + ssi, to_f_uvc_processing_item(target)); + break; + case UVC_VC_INPUT_TERMINAL: + ret = f_uvc_class_ss_link_camera(ssi, + to_f_uvc_camera_item(target)); + break; + case UVC_VC_OUTPUT_TERMINAL: + ret = f_uvc_class_ss_link_output(ssi, + to_f_uvc_output_item(target)); + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static inline struct f_uvc_opts +*class_ss_item_to_opts(struct f_uvc_class_ss_item *s); + +static int f_uvc_class_ss_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_class_ss_item *ssi; + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + + ssi = to_f_uvc_class_ss_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + opts = class_ss_item_to_opts(ssi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VC_HEADER: + ssi->uvc_ss_control_cls[F_UVC_CLASS_HEADER] = NULL; + --to_f_uvc_header_item(target)->refcnt; + break; + case UVC_VC_PROCESSING_UNIT: + ssi->uvc_ss_control_cls[F_UVC_CLASS_PROCESSING] = NULL; + --to_f_uvc_processing_item(target)->refcnt; + break; + case UVC_VC_INPUT_TERMINAL: + ssi->uvc_ss_control_cls[F_UVC_CLASS_INPUT] = NULL; + --to_f_uvc_camera_item(target)->refcnt; + break; + case UVC_VC_OUTPUT_TERMINAL: + ssi->uvc_ss_control_cls[F_UVC_CLASS_OUTPUT] = NULL; + --to_f_uvc_output_item(target)->refcnt; + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_class_ss_item_ops = { + .allow_link = f_uvc_class_ss_link, + .drop_link = f_uvc_class_ss_unlink, + .release = f_uvc_class_ss_item_release, +}; + +__ITEM_TO_OPTS(class_ss_item, ci_parent->ci_parent->ci_parent->ci_parent); + +static struct config_item_type f_uvc_class_ss_item_type = { + .ct_item_ops = &f_uvc_class_ss_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/control/class/ss */ +static struct config_item *f_uvc_class_ss_make(struct config_group *group, + const char *name) +{ + struct f_uvc_class_ss_item *ssi; + struct config_item *uvc_opts_item; + struct f_uvc_opts *opts; + + ssi = kzalloc(sizeof(*ssi), GFP_KERNEL); + if (!ssi) + return ERR_CAST(ssi); + + config_item_init_type_name(&ssi->item, name, &f_uvc_class_ss_item_type); + + uvc_opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = container_of(to_config_group(uvc_opts_item), struct f_uvc_opts, + func_inst.group); + mutex_lock(&opts->lock); + list_add_tail(&ssi->target_entry, &opts->known_targets); + mutex_unlock(&opts->lock); + + return &ssi->item; +} + +__F_UVC_DROP(class_ss, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(class_ss); +__GROUP_TYPE(class_ss); + +/* uvc.fun/control/terminal */ +struct config_item_type f_uvc_terminal_type = { + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/control/terminal/camera/item */ +__TO_F_UVC_STRUCT_ITEM(camera_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_camera_item); +CONFIGFS_ATTR_OPS(f_uvc_camera_item); + +static void f_uvc_camera_item_release(struct config_item *item) +{ + struct f_uvc_camera_item *camera_item; + + camera_item = to_f_uvc_camera_item(item); + kfree(camera_item); +} + +__F_UVC_ITEM_OPS(camera_item); + +__ITEM_TO_OPTS(camera_item, ci_parent->ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(camera_item); + +__F_UVC_ATTR(camera_item, w_terminal_type, wTerminalType, 16, 65535); +__F_UVC_ATTR(camera_item, w_objective_focal_length_min, + wObjectiveFocalLengthMin, 16, 65535); +__F_UVC_ATTR(camera_item, w_objective_focal_length_max, + wObjectiveFocalLengthMax, 16, 65535); +__F_UVC_ATTR(camera_item, w_ocular_focal_length, wOcularFocalLength, 16, 65535); + +static struct configfs_attribute *f_uvc_camera_item_attrs[] = { + &f_uvc_camera_item_w_terminal_type.attr, + &f_uvc_camera_item_w_objective_focal_length_min.attr, + &f_uvc_camera_item_w_objective_focal_length_max.attr, + &f_uvc_camera_item_w_ocular_focal_length.attr, + NULL, +}; + +__ITEM_TYPE(camera_item); + +/* uvc.fun/control/terminal/camera */ +static struct config_item *f_uvc_camera_make(struct config_group *group, + const char *name) +{ + struct f_uvc_camera_item *ci; + struct f_uvc_control *control; + struct f_uvc_opts *opts; + + ci = kzalloc(sizeof(*ci), GFP_KERNEL); + if (!ci) + return ERR_CAST(ci); + + ci->desc.bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3), + ci->desc.bDescriptorType = USB_DT_CS_INTERFACE, + ci->desc.bDescriptorSubType = UVC_VC_INPUT_TERMINAL, + ci->desc.bTerminalID = 1, + ci->desc.wTerminalType = cpu_to_le16(0x0201), + ci->desc.bAssocTerminal = 0, + ci->desc.iTerminal = 0, + ci->desc.wObjectiveFocalLengthMin = cpu_to_le16(0), + ci->desc.wObjectiveFocalLengthMax = cpu_to_le16(0), + ci->desc.wOcularFocalLength = cpu_to_le16(0), + ci->desc.bControlSize = 3, + ci->desc.bmControls[0] = 2, + ci->desc.bmControls[1] = 0, + ci->desc.bmControls[2] = 0, + config_item_init_type_name(&ci->item, name, &f_uvc_camera_item_type); + + control = to_f_uvc_control(group->cg_item.ci_parent->ci_parent); + opts = control_to_opts(control); + mutex_lock(&opts->lock); + list_add_tail(&ci->target_entry, &control->known_targets); + mutex_unlock(&opts->lock); + + return &ci->item; +} + +__F_UVC_DROP(camera, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(camera); +__GROUP_TYPE(camera); + +/* uvc.fun/control/terminal/output/item */ +__TO_F_UVC_STRUCT_ITEM(output_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_output_item); +CONFIGFS_ATTR_OPS(f_uvc_output_item); + +static void f_uvc_output_item_release(struct config_item *item) +{ + struct f_uvc_output_item *output_item; + + output_item = to_f_uvc_output_item(item); + kfree(output_item); +} + +__F_UVC_ITEM_OPS(output_item); + +__ITEM_TO_OPTS(output_item, ci_parent->ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(output_item); + +__F_UVC_ATTR(output_item, w_terminal_type, wTerminalType, 16, 65535); + +static struct configfs_attribute *f_uvc_output_item_attrs[] = { + &f_uvc_output_item_w_terminal_type.attr, + NULL, +}; + +__ITEM_TYPE(output_item); + +/* uvc.fun/control/terminal/output */ +static struct config_item *f_uvc_output_make(struct config_group *group, + const char *name) +{ + struct f_uvc_output_item *oi; + struct f_uvc_control *control; + struct f_uvc_opts *opts; + + oi = kzalloc(sizeof(*oi), GFP_KERNEL); + if (!oi) + return ERR_CAST(oi); + + oi->desc.bLength = UVC_DT_OUTPUT_TERMINAL_SIZE, + oi->desc.bDescriptorType = USB_DT_CS_INTERFACE, + oi->desc.bDescriptorSubType = UVC_VC_OUTPUT_TERMINAL, + oi->desc.bTerminalID = 3, + oi->desc.wTerminalType = cpu_to_le16(0x0101), + oi->desc.bAssocTerminal = 0, + oi->desc.bSourceID = 2, + oi->desc.iTerminal = 0, + config_item_init_type_name(&oi->item, name, &f_uvc_output_item_type); + + control = to_f_uvc_control(group->cg_item.ci_parent->ci_parent); + opts = control_to_opts(control); + mutex_lock(&opts->lock); + list_add_tail(&oi->target_entry, &control->known_targets); + mutex_unlock(&opts->lock); + + return &oi->item; +} + +__F_UVC_DROP(output, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(output); +__GROUP_TYPE(output); + +/* uvc.fun/streaming */ +__TO_F_UVC_STRUCT(streaming); + +CONFIGFS_ATTR_STRUCT(f_uvc_streaming); +CONFIGFS_ATTR_OPS(f_uvc_streaming); + +static struct configfs_item_operations f_uvc_streaming_ops = { + .show_attribute = f_uvc_streaming_attr_show, + .store_attribute = f_uvc_streaming_attr_store, +}; + +__TO_OPTS(streaming, ci_parent); + +static inline struct configfs_subsystem +*streaming_to_subsys(struct f_uvc_streaming *s) +{ + return s->group.cg_subsys; +} + +__F_UVC_OPTS_DO_SHOW(streaming, interval, streaming_interval); +__F_UVC_OPTS_DO_STORE(streaming, interval, streaming_interval, 8, 16); + +static struct f_uvc_streaming_attribute f_uvc_streaming_interval = + __CONFIGFS_ATTR(interval, S_IRUGO | S_IWUSR, + f_uvc_streaming_interval_show, + f_uvc_streaming_interval_store); + +__F_UVC_OPTS_DO_SHOW(streaming, maxpacket, streaming_maxpacket); +__F_UVC_OPTS_DO_STORE(streaming, maxpacket, streaming_maxpacket, 16, 3072); + +static struct f_uvc_streaming_attribute f_uvc_streaming_maxpacket = + __CONFIGFS_ATTR(maxpacket, S_IRUGO | S_IWUSR, + f_uvc_streaming_maxpacket_show, + f_uvc_streaming_maxpacket_store); + +__F_UVC_OPTS_DO_SHOW(streaming, maxburst, streaming_maxburst); +__F_UVC_OPTS_DO_STORE(streaming, maxburst, streaming_maxburst, 8, 15); + +static struct f_uvc_streaming_attribute f_uvc_streaming_maxburst = + __CONFIGFS_ATTR(maxburst, S_IRUGO | S_IWUSR, + f_uvc_streaming_maxburst_show, + f_uvc_streaming_maxburst_store); + +static struct configfs_attribute *f_uvc_streaming_attrs[] = { + &f_uvc_streaming_interval.attr, + &f_uvc_streaming_maxpacket.attr, + &f_uvc_streaming_maxburst.attr, + NULL, +}; + +__ITEM_TYPE(streaming); + +/* uvc.fun/streaming/input_header/item */ +__TO_F_UVC_STRUCT_ITEM(input_header_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_input_header_item); +CONFIGFS_ATTR_OPS(f_uvc_input_header_item); + +static void f_uvc_input_header_item_release(struct config_item *item) +{ + struct f_uvc_input_header_item *input_header_item; + + input_header_item = to_f_uvc_input_header_item(item); + kfree(input_header_item); +} + +__F_UVC_ITEM_OPS(input_header_item); + +__ITEM_TO_OPTS(input_header_item, ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(input_header_item); + +__F_UVC_STRUCT_DO_SHOW(input_header_item, bm_info, bmInfo); + +static struct f_uvc_input_header_item_attribute + f_uvc_input_header_item_bm_info = + __CONFIGFS_ATTR_RO(bmInfo, f_uvc_input_header_item_bm_info_show); + +static struct configfs_attribute *f_uvc_input_header_item_attrs[] = { + &f_uvc_input_header_item_bm_info.attr, + NULL, +}; + +__ITEM_TYPE(input_header_item); + +/* uvc.fun/streaming/input_header */ +static struct config_item *f_uvc_input_header_make(struct config_group *group, + const char *name) +{ + struct f_uvc_input_header_item *ihi; + struct f_uvc_streaming *streaming; + struct f_uvc_opts *opts; + + ihi = kzalloc(sizeof(*ihi), GFP_KERNEL); + if (!ihi) + return ERR_CAST(ihi); + + ihi->desc.bLength = UVC_DT_INPUT_HEADER_SIZE(1, 2); + ihi->desc.bDescriptorType = USB_DT_CS_INTERFACE; + ihi->desc.bDescriptorSubType = UVC_VS_INPUT_HEADER; + ihi->desc.bNumFormats = 2; + ihi->desc.wTotalLength = 0; /* dynamic */ + ihi->desc.bEndpointAddress = 0; /* dynamic */ + ihi->desc.bmInfo = 0; + ihi->desc.bTerminalLink = 3; + ihi->desc.bStillCaptureMethod = 0; + ihi->desc.bTriggerSupport = 0; + ihi->desc.bTriggerUsage = 0; + ihi->desc.bControlSize = 1; + ihi->desc.bmaControls[0][0] = 0; + ihi->desc.bmaControls[1][0] = 4; + config_item_init_type_name(&ihi->item, name, + &f_uvc_input_header_item_type); + + streaming = to_f_uvc_streaming(group->cg_item.ci_parent); + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + list_add_tail(&ihi->target_entry, &streaming->known_targets); + mutex_unlock(&opts->lock); + + return &ihi->item; +} + +__F_UVC_DROP(input_header, ci_parent->ci_parent); +__F_UVC_OPS(input_header); +__GROUP_TYPE(input_header); + +/* uvc.fun/streaming/color_matching/item */ +__TO_F_UVC_STRUCT_ITEM(color_matching_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_color_matching_item); +CONFIGFS_ATTR_OPS(f_uvc_color_matching_item); + +static void f_uvc_color_matching_item_release(struct config_item *item) +{ + struct f_uvc_color_matching_item *color_matching_item; + + color_matching_item = to_f_uvc_color_matching_item(item); + kfree(color_matching_item); +} + +__F_UVC_ITEM_OPS(color_matching_item); + +__ITEM_TO_OPTS(color_matching_item, ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(color_matching_item); + +__F_UVC_STRUCT_DO_SHOW(color_matching_item, b_color_primaries, bColorPrimaries); + +static struct f_uvc_color_matching_item_attribute + f_uvc_color_matching_item_b_color_primaries = + __CONFIGFS_ATTR_RO(bColorPrimaries, + f_uvc_color_matching_item_b_color_primaries_show); + +static struct configfs_attribute *f_uvc_color_matching_item_attrs[] = { + &f_uvc_color_matching_item_b_color_primaries.attr, + NULL, +}; + +__ITEM_TYPE(color_matching_item); + +/* uvc.fun/streaming/color_matching */ +static struct config_item *f_uvc_color_matching_make(struct config_group *group, + const char *name) +{ + struct f_uvc_color_matching_item *cmi; + struct f_uvc_streaming *streaming; + struct f_uvc_opts *opts; + + cmi = kzalloc(sizeof(*cmi), GFP_KERNEL); + if (!cmi) + return ERR_CAST(cmi); + + cmi->desc.bLength = UVC_DT_COLOR_MATCHING_SIZE; + cmi->desc.bDescriptorType = USB_DT_CS_INTERFACE; + cmi->desc.bDescriptorSubType = UVC_VS_COLORFORMAT; + cmi->desc.bColorPrimaries = 1; + cmi->desc.bTransferCharacteristics = 1; + cmi->desc.bMatrixCoefficients = 4; + config_item_init_type_name(&cmi->item, name, + &f_uvc_color_matching_item_type); + + streaming = to_f_uvc_streaming(group->cg_item.ci_parent); + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + list_add_tail(&cmi->target_entry, &streaming->known_targets); + mutex_unlock(&opts->lock); + + return &cmi->item; +} + +__F_UVC_DROP(color_matching, ci_parent->ci_parent); +__F_UVC_OPS(color_matching); +__GROUP_TYPE(color_matching); + +/* uvc.fun/streaming/class */ +struct config_item_type f_uvc_streaming_class_type = { + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/streaming/class/fs/item */ +__TO_F_UVC_STRUCT_ITEM(streaming_fs_item); + +static void f_uvc_streaming_fs_item_release(struct config_item *item) +{ + struct f_uvc_streaming_fs_item *streaming_fs_item; + + streaming_fs_item = to_f_uvc_streaming_fs_item(item); + kfree(streaming_fs_item); +} + +static inline struct f_uvc_format_yuv_item +*to_f_uvc_format_yuv_item(struct config_item *item); + +static inline struct f_uvc_format_mjpeg_item +*to_f_uvc_format_mjpeg_item(struct config_item *item); + +__F_UVC_STREAMING_LINK(input_header, INPUT_HEADER, fs); +__F_UVC_STREAMING_LINK(color_matching, COLOR_MATCHING, fs); + +static int f_uvc_streaming_fs_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_streaming *streaming; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_streaming_fs_item *fsi; + struct f_uvc_link *link, *l; + int ret = 0; + + fsi = to_f_uvc_streaming_fs_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + streaming = to_f_uvc_streaming(src->ci_parent->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + if (fsi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &streaming->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VS_INPUT_HEADER: + kfree(link); + ret = f_uvc_streaming_fs_link_input_header(fsi, + to_f_uvc_input_header_item(target)); + break; + case UVC_VS_COLORFORMAT: + kfree(link); + ret = f_uvc_streaming_fs_link_color_matching(fsi, + to_f_uvc_color_matching_item(target)); + break; + case UVC_VS_FORMAT_UNCOMPRESSED: + list_for_each_entry(l, &fsi->formats, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &fsi->formats); + ++to_f_uvc_format_yuv_item(target)->refcnt; + fsi->n_children += to_f_uvc_format_yuv_item(target)->n_frames; + ++fsi->n_children; + break; + case UVC_VS_FORMAT_MJPEG: + list_for_each_entry(l, &fsi->formats, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &fsi->formats); + ++to_f_uvc_format_mjpeg_item(target)->refcnt; + fsi->n_children += to_f_uvc_format_yuv_item(target)->n_frames; + ++fsi->n_children; + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static inline struct f_uvc_opts +*streaming_fs_item_to_opts(struct f_uvc_streaming_fs_item *s); + +static int f_uvc_streaming_fs_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_streaming_fs_item *fsi; + struct configfs_subsystem *subsys; + struct f_uvc_item_struct_header *h; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + struct f_uvc_link *link; + + fsi = to_f_uvc_streaming_fs_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + h = (struct f_uvc_item_struct_header *)target; + opts = streaming_fs_item_to_opts(fsi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VS_INPUT_HEADER: + fsi->streaming[F_UVC_STREAMING_INPUT_HEADER] = NULL; + --to_f_uvc_input_header_item(target)->refcnt; + break; + case UVC_VS_COLORFORMAT: + fsi->streaming[F_UVC_STREAMING_COLOR_MATCHING] = NULL; + --to_f_uvc_color_matching_item(target)->refcnt; + break; + case UVC_VS_FORMAT_UNCOMPRESSED: + list_for_each_entry(link, &fsi->formats, link) + if (link->desc == h) { + struct f_uvc_format_yuv_item *fyi; + + list_del(&link->link); + fyi = to_f_uvc_format_yuv_item(target); + --fyi->refcnt; + fsi->n_children -= fyi->n_frames; + --fsi->n_children; + kfree(link); + break; + } + break; + case UVC_VS_FORMAT_MJPEG: + list_for_each_entry(link, &fsi->formats, link) + if (link->desc == h) { + struct f_uvc_format_mjpeg_item *fmi; + + list_del(&link->link); + fmi = to_f_uvc_format_mjpeg_item(target); + --fmi->refcnt; + fsi->n_children -= fmi->n_frames; + --fsi->n_children; + kfree(link); + break; + } + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_streaming_fs_item_ops = { + .allow_link = f_uvc_streaming_fs_link, + .drop_link = f_uvc_streaming_fs_unlink, + .release = f_uvc_streaming_fs_item_release, +}; + +__ITEM_TO_OPTS(streaming_fs_item, ci_parent->ci_parent->ci_parent->ci_parent); + +static struct config_item_type f_uvc_streaming_fs_item_type = { + .ct_item_ops = &f_uvc_streaming_fs_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/streaming/class/fs */ +static struct config_item *f_uvc_streaming_fs_make(struct config_group *group, + const char *name) +{ + struct f_uvc_streaming_fs_item *fsi; + struct config_item *uvc_opts_item; + struct f_uvc_opts *opts; + + fsi = kzalloc(sizeof(*fsi), GFP_KERNEL); + if (!fsi) + return ERR_CAST(fsi); + + INIT_LIST_HEAD(&fsi->formats); + config_item_init_type_name(&fsi->item, name, + &f_uvc_streaming_fs_item_type); + + uvc_opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = container_of(to_config_group(uvc_opts_item), struct f_uvc_opts, + func_inst.group); + mutex_lock(&opts->lock); + list_add_tail(&fsi->target_entry, &opts->known_targets); + mutex_unlock(&opts->lock); + + return &fsi->item; +} + +__F_UVC_DROP(streaming_fs, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(streaming_fs); +__GROUP_TYPE(streaming_fs); + +/* uvc.fun/streaming/class/hs/item */ +__TO_F_UVC_STRUCT_ITEM(streaming_hs_item); + +static void f_uvc_streaming_hs_item_release(struct config_item *item) +{ + struct f_uvc_streaming_hs_item *streaming_hs_item; + + streaming_hs_item = to_f_uvc_streaming_hs_item(item); + kfree(streaming_hs_item); +} + +static inline struct f_uvc_format_yuv_item +*to_f_uvc_format_yuv_item(struct config_item *item); + +static inline struct f_uvc_format_mjpeg_item +*to_f_uvc_format_mjpeg_item(struct config_item *item); + +__F_UVC_STREAMING_LINK(input_header, INPUT_HEADER, hs); +__F_UVC_STREAMING_LINK(color_matching, COLOR_MATCHING, hs); + +static int f_uvc_streaming_hs_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_streaming *streaming; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_streaming_hs_item *hsi; + struct f_uvc_link *link, *l; + int ret = 0; + + hsi = to_f_uvc_streaming_hs_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + streaming = to_f_uvc_streaming(src->ci_parent->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + if (hsi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &streaming->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VS_INPUT_HEADER: + kfree(link); + ret = f_uvc_streaming_hs_link_input_header(hsi, + to_f_uvc_input_header_item(target)); + break; + case UVC_VS_COLORFORMAT: + kfree(link); + ret = f_uvc_streaming_hs_link_color_matching(hsi, + to_f_uvc_color_matching_item(target)); + break; + case UVC_VS_FORMAT_UNCOMPRESSED: + list_for_each_entry(l, &hsi->formats, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &hsi->formats); + ++to_f_uvc_format_yuv_item(target)->refcnt; + hsi->n_children += to_f_uvc_format_yuv_item(target)->n_frames; + ++hsi->n_children; + break; + case UVC_VS_FORMAT_MJPEG: + list_for_each_entry(l, &hsi->formats, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &hsi->formats); + ++to_f_uvc_format_mjpeg_item(target)->refcnt; + hsi->n_children += to_f_uvc_format_yuv_item(target)->n_frames; + ++hsi->n_children; + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static inline struct f_uvc_opts +*streaming_hs_item_to_opts(struct f_uvc_streaming_hs_item *s); + +static int f_uvc_streaming_hs_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_streaming_hs_item *hsi; + struct configfs_subsystem *subsys; + struct f_uvc_item_struct_header *h; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + struct f_uvc_link *link; + + hsi = to_f_uvc_streaming_hs_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + h = (struct f_uvc_item_struct_header *)target; + opts = streaming_hs_item_to_opts(hsi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VS_INPUT_HEADER: + hsi->streaming[F_UVC_STREAMING_INPUT_HEADER] = NULL; + --to_f_uvc_input_header_item(target)->refcnt; + break; + case UVC_VS_COLORFORMAT: + hsi->streaming[F_UVC_STREAMING_COLOR_MATCHING] = NULL; + --to_f_uvc_color_matching_item(target)->refcnt; + break; + case UVC_VS_FORMAT_UNCOMPRESSED: + list_for_each_entry(link, &hsi->formats, link) + if (link->desc == h) { + struct f_uvc_format_yuv_item *fyi; + + list_del(&link->link); + fyi = to_f_uvc_format_yuv_item(target); + --fyi->refcnt; + hsi->n_children -= fyi->n_frames; + --hsi->n_children; + kfree(link); + break; + } + break; + case UVC_VS_FORMAT_MJPEG: + list_for_each_entry(link, &hsi->formats, link) + if (link->desc == h) { + struct f_uvc_format_mjpeg_item *fmi; + + list_del(&link->link); + fmi = to_f_uvc_format_mjpeg_item(target); + --fmi->refcnt; + hsi->n_children -= fmi->n_frames; + --hsi->n_children; + kfree(link); + break; + } + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_streaming_hs_item_ops = { + .allow_link = f_uvc_streaming_hs_link, + .drop_link = f_uvc_streaming_hs_unlink, + .release = f_uvc_streaming_hs_item_release, +}; + +__ITEM_TO_OPTS(streaming_hs_item, ci_parent->ci_parent->ci_parent->ci_parent); + +static struct config_item_type f_uvc_streaming_hs_item_type = { + .ct_item_ops = &f_uvc_streaming_hs_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/streaming/class/hs */ +static struct config_item *f_uvc_streaming_hs_make(struct config_group *group, + const char *name) +{ + struct f_uvc_streaming_hs_item *hsi; + struct config_item *uvc_opts_item; + struct f_uvc_opts *opts; + + hsi = kzalloc(sizeof(*hsi), GFP_KERNEL); + if (!hsi) + return ERR_CAST(hsi); + + INIT_LIST_HEAD(&hsi->formats); + config_item_init_type_name(&hsi->item, name, + &f_uvc_streaming_hs_item_type); + + uvc_opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = container_of(to_config_group(uvc_opts_item), struct f_uvc_opts, + func_inst.group); + mutex_lock(&opts->lock); + list_add_tail(&hsi->target_entry, &opts->known_targets); + mutex_unlock(&opts->lock); + + return &hsi->item; +} + +__F_UVC_DROP(streaming_hs, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(streaming_hs); +__GROUP_TYPE(streaming_hs); + +/* uvc.fun/streaming/class/ss/item */ +__TO_F_UVC_STRUCT_ITEM(streaming_ss_item); + +static void f_uvc_streaming_ss_item_release(struct config_item *item) +{ + struct f_uvc_streaming_ss_item *streaming_ss_item; + + streaming_ss_item = to_f_uvc_streaming_ss_item(item); + kfree(streaming_ss_item); +} + +static inline struct f_uvc_format_yuv_item +*to_f_uvc_format_yuv_item(struct config_item *item); + +static inline struct f_uvc_format_mjpeg_item +*to_f_uvc_format_mjpeg_item(struct config_item *item); + +__F_UVC_STREAMING_LINK(input_header, INPUT_HEADER, ss); +__F_UVC_STREAMING_LINK(color_matching, COLOR_MATCHING, ss); + +static int f_uvc_streaming_ss_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_streaming *streaming; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_streaming_ss_item *ssi; + struct f_uvc_link *link, *l; + int ret = 0; + + ssi = to_f_uvc_streaming_ss_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + streaming = to_f_uvc_streaming(src->ci_parent->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + if (ssi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &streaming->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VS_INPUT_HEADER: + kfree(link); + ret = f_uvc_streaming_ss_link_input_header(ssi, + to_f_uvc_input_header_item(target)); + break; + case UVC_VS_COLORFORMAT: + kfree(link); + ret = f_uvc_streaming_ss_link_color_matching(ssi, + to_f_uvc_color_matching_item(target)); + break; + case UVC_VS_FORMAT_UNCOMPRESSED: + list_for_each_entry(l, &ssi->formats, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &ssi->formats); + ++to_f_uvc_format_yuv_item(target)->refcnt; + ssi->n_children += to_f_uvc_format_yuv_item(target)->n_frames; + ++ssi->n_children; + break; + case UVC_VS_FORMAT_MJPEG: + list_for_each_entry(l, &ssi->formats, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &ssi->formats); + ++to_f_uvc_format_mjpeg_item(target)->refcnt; + ssi->n_children += to_f_uvc_format_yuv_item(target)->n_frames; + ++ssi->n_children; + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static inline struct f_uvc_opts +*streaming_ss_item_to_opts(struct f_uvc_streaming_ss_item *s); + +static int f_uvc_streaming_ss_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_streaming_ss_item *ssi; + struct configfs_subsystem *subsys; + struct f_uvc_item_struct_header *h; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + struct f_uvc_link *link; + + ssi = to_f_uvc_streaming_ss_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + h = (struct f_uvc_item_struct_header *)target; + opts = streaming_ss_item_to_opts(ssi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VS_INPUT_HEADER: + ssi->streaming[F_UVC_STREAMING_INPUT_HEADER] = NULL; + --to_f_uvc_input_header_item(target)->refcnt; + break; + case UVC_VS_COLORFORMAT: + ssi->streaming[F_UVC_STREAMING_COLOR_MATCHING] = NULL; + --to_f_uvc_color_matching_item(target)->refcnt; + break; + case UVC_VS_FORMAT_UNCOMPRESSED: + list_for_each_entry(link, &ssi->formats, link) + if (link->desc == h) { + struct f_uvc_format_yuv_item *fyi; + + list_del(&link->link); + --to_f_uvc_format_yuv_item(target)->refcnt; + fyi = to_f_uvc_format_yuv_item(target); + ssi->n_children -= fyi->n_frames; + --ssi->n_children; + kfree(link); + break; + } + break; + case UVC_VS_FORMAT_MJPEG: + list_for_each_entry(link, &ssi->formats, link) + if (link->desc == h) { + struct f_uvc_format_mjpeg_item *fmi; + + list_del(&link->link); + --to_f_uvc_format_mjpeg_item(target)->refcnt; + fmi = to_f_uvc_format_mjpeg_item(target); + ssi->n_children -= fmi->n_frames; + --ssi->n_children; + kfree(link); + break; + } + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_streaming_ss_item_ops = { + .allow_link = f_uvc_streaming_ss_link, + .drop_link = f_uvc_streaming_ss_unlink, + .release = f_uvc_streaming_ss_item_release, +}; + +__ITEM_TO_OPTS(streaming_ss_item, ci_parent->ci_parent->ci_parent->ci_parent); + +static struct config_item_type f_uvc_streaming_ss_item_type = { + .ct_item_ops = &f_uvc_streaming_ss_item_ops, + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/streaming/class/ss */ +static struct config_item *f_uvc_streaming_ss_make(struct config_group *group, + const char *name) +{ + struct f_uvc_streaming_ss_item *ssi; + struct config_item *uvc_opts_item; + struct f_uvc_opts *opts; + + ssi = kzalloc(sizeof(*ssi), GFP_KERNEL); + if (!ssi) + return ERR_CAST(ssi); + + INIT_LIST_HEAD(&ssi->formats); + config_item_init_type_name(&ssi->item, name, + &f_uvc_streaming_ss_item_type); + + uvc_opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = container_of(to_config_group(uvc_opts_item), struct f_uvc_opts, + func_inst.group); + mutex_lock(&opts->lock); + list_add_tail(&ssi->target_entry, &opts->known_targets); + mutex_unlock(&opts->lock); + + return &ssi->item; +} + +__F_UVC_DROP(streaming_ss, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(streaming_ss); +__GROUP_TYPE(streaming_ss); + +/* uvc.fun/streaming/format */ +__TO_F_UVC_STRUCT(format); + +__TO_OPTS(format, ci_parent->ci_parent); + +struct config_item_type f_uvc_format_type = { + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/streaming/format/frame */ +struct config_item_type f_uvc_frame_type = { + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/streaming/format/frame/yuv/item */ +__TO_F_UVC_STRUCT_ITEM(frame_yuv_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_frame_yuv_item); +CONFIGFS_ATTR_OPS(f_uvc_frame_yuv_item); + +static void f_uvc_frame_yuv_item_release(struct config_item *item) +{ + struct f_uvc_frame_yuv_item *frame_yuv_item; + + frame_yuv_item = to_f_uvc_frame_yuv_item(item); + kfree(frame_yuv_item->dwFrameInterval); + kfree(frame_yuv_item); +} + +__F_UVC_ITEM_OPS(frame_yuv_item); + +__ITEM_TO_OPTS(frame_yuv_item, + ci_parent->ci_parent->ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(frame_yuv_item); + +__F_UVC_ATTR(frame_yuv_item, w_width, wWidth, 16, 65535); +__F_UVC_ATTR(frame_yuv_item, w_height, wHeight, 16, 65535); +__F_UVC_ATTR(frame_yuv_item, dw_min_bit_rate, dwMinBitRate, 32, 2147483648U); +__F_UVC_ATTR(frame_yuv_item, dw_max_bit_rate, dwMaxBitRate, 32, 2147483648U); +__F_UVC_ATTR(frame_yuv_item, dw_max_video_frame_buffer_size, + dwMaxVideoFrameBufferSize, 32, 2147483648U); +__F_UVC_ATTR(frame_yuv_item, dw_default_frame_interval, + dwDefaultFrameInterval, 16, 65535); + +static ssize_t f_uvc_frame_yuv_item_b_frame_interval_show( + struct f_uvc_frame_yuv_item *h, char *page) +{ + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + int result, i, tmp; + + subsys = frame_yuv_item_to_subsys(h); + mutex_lock(&subsys->su_mutex); + opts = frame_yuv_item_to_opts(h); + mutex_lock(&opts->lock); + + result = 0; + for (i = 0; i < h->n_interval; ++i) { + if (i) { + tmp = sprintf(page + result, ","); + if (tmp < 0) { + result = tmp; + goto end; + } + result += tmp; + } + tmp = sprintf(page + result, "%d", h->dwFrameInterval[i]); + if (tmp < 0) { + result = tmp; + goto end; + } + result += tmp; + + } + ++result; +end: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + + return result; +} + +#define F_UVC_FRAME_INTERVAL_MAX 5 +#define F_UVC_FRAME_INTERVAL_LEN 256 + +static ssize_t f_uvc_frame_yuv_item_b_frame_interval_store( + struct f_uvc_frame_yuv_item *h, const char *page, size_t len) +{ + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + char buf[F_UVC_FRAME_INTERVAL_LEN]; + char *buf_ptr, *interval; + int ret, n; + u32 num; + + subsys = frame_yuv_item_to_subsys(h); + mutex_lock(&subsys->su_mutex); + opts = frame_yuv_item_to_opts(h); + mutex_lock(&opts->lock); + if (opts->refcnt || h->refcnt) { + ret = -EBUSY; + goto end; + } + + n = 0; + strlcpy(buf, page, sizeof(buf)); + buf_ptr = strim(buf); + while (buf_ptr) { + if (n >= F_UVC_FRAME_INTERVAL_MAX) { + ret = -ENOSPC; + goto end; + } + interval = strsep(&buf_ptr, ","); + + ret = kstrtou32(interval, 0, &num); + if (ret) { + ret = -EINVAL; + goto end; + } + + if (num > 65536) { + ret = -EINVAL; + goto end; + } + + h->dwFrameInterval[n++] = num; + } + h->n_interval = n; + + ret = len; + +end: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static struct f_uvc_frame_yuv_item_attribute + f_uvc_frame_yuv_item_b_frame_interval = + __CONFIGFS_ATTR(bFrameInterval, S_IRUGO | S_IWUSR, + f_uvc_frame_yuv_item_b_frame_interval_show, + f_uvc_frame_yuv_item_b_frame_interval_store); + +static struct configfs_attribute *f_uvc_frame_yuv_item_attrs[] = { + &f_uvc_frame_yuv_item_w_width.attr, + &f_uvc_frame_yuv_item_w_height.attr, + &f_uvc_frame_yuv_item_dw_min_bit_rate.attr, + &f_uvc_frame_yuv_item_dw_max_bit_rate.attr, + &f_uvc_frame_yuv_item_dw_max_video_frame_buffer_size.attr, + &f_uvc_frame_yuv_item_dw_default_frame_interval.attr, + &f_uvc_frame_yuv_item_b_frame_interval.attr, + NULL, +}; + +__ITEM_TYPE(frame_yuv_item); + +/* uvc.fun/streaming/format/frame/yuv */ +static struct config_item *f_uvc_frame_yuv_make(struct config_group *group, + const char *name) +{ + struct f_uvc_frame_yuv_item *fyi; + struct f_uvc_format *format; + struct f_uvc_opts *opts; + int ret; + + fyi = kzalloc(sizeof(*fyi), GFP_KERNEL); + if (!fyi) + return ERR_CAST(fyi); + fyi->dwFrameInterval = kcalloc(F_UVC_FRAME_INTERVAL_MAX, + sizeof(*fyi->dwFrameInterval), GFP_KERNEL); + if (!fyi->dwFrameInterval) { + ret = PTR_ERR(fyi->dwFrameInterval); + kfree(fyi); + return ERR_PTR(ret); + } + + fyi->desc.bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3); + fyi->desc.bDescriptorType = USB_DT_CS_INTERFACE; + fyi->desc.bDescriptorSubType = UVC_VS_FRAME_UNCOMPRESSED; + fyi->desc.bFrameIndex = 1; + fyi->desc.bmCapabilities = 0; + fyi->desc.wWidth = cpu_to_le16(640); + fyi->desc.wHeight = cpu_to_le16(360); + fyi->desc.dwMinBitRate = cpu_to_le32(18432000); + fyi->desc.dwMaxBitRate = cpu_to_le32(55296000); + fyi->desc.dwMaxVideoFrameBufferSize = cpu_to_le32(460800); + fyi->desc.dwDefaultFrameInterval = cpu_to_le32(666666); + fyi->desc.bFrameIntervalType = 3; + fyi->n_interval = 3; + fyi->dwFrameInterval[0] = cpu_to_le32(666666); + fyi->dwFrameInterval[1] = cpu_to_le32(1000000); + fyi->dwFrameInterval[2] = cpu_to_le32(5000000); + config_item_init_type_name(&fyi->item, name, + &f_uvc_frame_yuv_item_type); + + format = to_f_uvc_format(group->cg_item.ci_parent->ci_parent); + opts = format_to_opts(format); + mutex_lock(&opts->lock); + list_add_tail(&fyi->target_entry, &format->known_targets); + mutex_unlock(&opts->lock); + + return &fyi->item; +} + +__F_UVC_DROP(frame_yuv, ci_parent->ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(frame_yuv); +__GROUP_TYPE(frame_yuv); + +/* uvc.fun/streaming/format/frame/mjpeg/item */ +__TO_F_UVC_STRUCT_ITEM(frame_mjpeg_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_frame_mjpeg_item); +CONFIGFS_ATTR_OPS(f_uvc_frame_mjpeg_item); + +static void f_uvc_frame_mjpeg_item_release(struct config_item *item) +{ + struct f_uvc_frame_mjpeg_item *frame_mjpeg_item; + + frame_mjpeg_item = to_f_uvc_frame_mjpeg_item(item); + kfree(frame_mjpeg_item->dwFrameInterval); + kfree(frame_mjpeg_item); +} + +__F_UVC_ITEM_OPS(frame_mjpeg_item); + +__ITEM_TO_OPTS(frame_mjpeg_item, + ci_parent->ci_parent->ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(frame_mjpeg_item); + +__F_UVC_ATTR(frame_mjpeg_item, w_width, wWidth, 16, 65535); +__F_UVC_ATTR(frame_mjpeg_item, w_height, wHeight, 16, 65535); +__F_UVC_ATTR(frame_mjpeg_item, dw_min_bit_rate, dwMinBitRate, 32, 2147483648U); +__F_UVC_ATTR(frame_mjpeg_item, dw_max_bit_rate, dwMaxBitRate, 32, 2147483648U); +__F_UVC_ATTR(frame_mjpeg_item, dw_max_video_frame_buffer_size, + dwMaxVideoFrameBufferSize, 32, 2147483648U); +__F_UVC_ATTR(frame_mjpeg_item, dw_default_frame_interval, + dwDefaultFrameInterval, 16, 65535); + +static ssize_t f_uvc_frame_mjpeg_item_b_frame_interval_show( + struct f_uvc_frame_mjpeg_item *h, char *page) +{ + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + int result, i, tmp; + + subsys = frame_mjpeg_item_to_subsys(h); + mutex_lock(&subsys->su_mutex); + opts = frame_mjpeg_item_to_opts(h); + mutex_lock(&opts->lock); + + result = 0; + for (i = 0; i < h->n_interval; ++i) { + if (i) { + tmp = sprintf(page + result, ","); + if (tmp < 0) { + result = tmp; + goto end; + } + result += tmp; + } + tmp = sprintf(page + result, "%d", h->dwFrameInterval[i]); + if (tmp < 0) { + result = tmp; + goto end; + } + result += tmp; + + } + ++result; +end: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + + return result; +} + +static ssize_t f_uvc_frame_mjpeg_item_b_frame_interval_store( + struct f_uvc_frame_mjpeg_item *h, const char *page, size_t len) +{ + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + char buf[F_UVC_FRAME_INTERVAL_LEN]; + char *buf_ptr, *interval; + int ret, n; + u32 num; + + subsys = frame_mjpeg_item_to_subsys(h); + mutex_lock(&subsys->su_mutex); + opts = frame_mjpeg_item_to_opts(h); + mutex_lock(&opts->lock); + if (opts->refcnt || h->refcnt) { + ret = -EBUSY; + goto end; + } + + n = 0; + strlcpy(buf, page, sizeof(buf)); + buf_ptr = strim(buf); + while (buf_ptr) { + if (n >= F_UVC_FRAME_INTERVAL_MAX) { + ret = -ENOSPC; + goto end; + } + interval = strsep(&buf_ptr, ","); + + ret = kstrtou32(interval, 0, &num); + if (ret) { + ret = -EINVAL; + goto end; + } + + if (num > 65536) { + ret = -EINVAL; + goto end; + } + + h->dwFrameInterval[n++] = num; + } + h->n_interval = n; + + ret = len; + +end: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static struct f_uvc_frame_mjpeg_item_attribute + f_uvc_frame_mjpeg_item_b_frame_interval = + __CONFIGFS_ATTR(bFrameInterval, S_IRUGO | S_IWUSR, + f_uvc_frame_mjpeg_item_b_frame_interval_show, + f_uvc_frame_mjpeg_item_b_frame_interval_store); + +static struct configfs_attribute *f_uvc_frame_mjpeg_item_attrs[] = { + &f_uvc_frame_mjpeg_item_w_width.attr, + &f_uvc_frame_mjpeg_item_w_height.attr, + &f_uvc_frame_mjpeg_item_dw_min_bit_rate.attr, + &f_uvc_frame_mjpeg_item_dw_max_bit_rate.attr, + &f_uvc_frame_mjpeg_item_dw_max_video_frame_buffer_size.attr, + &f_uvc_frame_mjpeg_item_dw_default_frame_interval.attr, + &f_uvc_frame_mjpeg_item_b_frame_interval.attr, + NULL, +}; + +__ITEM_TYPE(frame_mjpeg_item); + +/* uvc.fun/streaming/format/frame/mjpeg */ +static struct config_item *f_uvc_frame_mjpeg_make(struct config_group *group, + const char *name) +{ + struct f_uvc_frame_mjpeg_item *fyi; + struct f_uvc_format *format; + struct f_uvc_opts *opts; + int ret; + + fyi = kzalloc(sizeof(*fyi), GFP_KERNEL); + if (!fyi) + return ERR_CAST(fyi); + fyi->dwFrameInterval = kcalloc(F_UVC_FRAME_INTERVAL_MAX, + sizeof(*fyi->dwFrameInterval), GFP_KERNEL); + if (!fyi->dwFrameInterval) { + ret = PTR_ERR(fyi->dwFrameInterval); + kfree(fyi); + return ERR_PTR(ret); + } + + fyi->desc.bLength = UVC_DT_FRAME_MJPEG_SIZE(3); + fyi->desc.bDescriptorType = USB_DT_CS_INTERFACE; + fyi->desc.bDescriptorSubType = UVC_VS_FRAME_MJPEG; + fyi->desc.bFrameIndex = 1; + fyi->desc.bmCapabilities = 0; + fyi->desc.wWidth = cpu_to_le16(640); + fyi->desc.wHeight = cpu_to_le16(360); + fyi->desc.dwMinBitRate = cpu_to_le32(18432000); + fyi->desc.dwMaxBitRate = cpu_to_le32(55296000); + fyi->desc.dwMaxVideoFrameBufferSize = cpu_to_le32(460800); + fyi->desc.dwDefaultFrameInterval = cpu_to_le32(666666); + fyi->desc.bFrameIntervalType = 3; + fyi->n_interval = 3; + fyi->dwFrameInterval[0] = cpu_to_le32(666666); + fyi->dwFrameInterval[1] = cpu_to_le32(1000000); + fyi->dwFrameInterval[2] = cpu_to_le32(5000000); + config_item_init_type_name(&fyi->item, name, + &f_uvc_frame_mjpeg_item_type); + + format = to_f_uvc_format(group->cg_item.ci_parent->ci_parent); + opts = format_to_opts(format); + mutex_lock(&opts->lock); + list_add_tail(&fyi->target_entry, &format->known_targets); + mutex_unlock(&opts->lock); + + return &fyi->item; +} + +__F_UVC_DROP(frame_mjpeg, ci_parent->ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(frame_mjpeg); +__GROUP_TYPE(frame_mjpeg); + +/* uvc.fun/streaming/format/yuv/item */ +__TO_F_UVC_STRUCT_ITEM(format_yuv_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_format_yuv_item); +CONFIGFS_ATTR_OPS(f_uvc_format_yuv_item); + +static void f_uvc_format_yuv_item_release(struct config_item *item) +{ + struct f_uvc_format_yuv_item *format_yuv_item; + + format_yuv_item = to_f_uvc_format_yuv_item(item); + kfree(format_yuv_item); +} + +static int f_uvc_format_yuv_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_format *format; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_format_yuv_item *fsi; + struct f_uvc_link *link, *l; + int ret = 0; + + fsi = to_f_uvc_format_yuv_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + format = to_f_uvc_format(src->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = format_to_opts(format); + mutex_lock(&opts->lock); + if (fsi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &format->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VS_FRAME_UNCOMPRESSED: + list_for_each_entry(l, &fsi->frames, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &fsi->frames); + ++to_f_uvc_frame_yuv_item(target)->refcnt; + ++fsi->n_frames; + break; + case UVC_VS_FRAME_MJPEG: + ret = -ENODEV; + kfree(link); + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +__ITEM_TO_OPTS(format_yuv_item, ci_parent->ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(format_yuv_item); + +static int f_uvc_format_yuv_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_format_yuv_item *fsi; + struct configfs_subsystem *subsys; + struct f_uvc_item_struct_header *h; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + struct f_uvc_link *link; + + fsi = to_f_uvc_format_yuv_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + h = (struct f_uvc_item_struct_header *)target; + opts = format_yuv_item_to_opts(fsi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VS_FRAME_UNCOMPRESSED: + list_for_each_entry(link, &fsi->frames, link) + if (link->desc == h) { + list_del(&link->link); + --to_f_uvc_frame_yuv_item(target)->refcnt; + --fsi->n_frames; + kfree(link); + break; + } + break; + case UVC_VS_FRAME_MJPEG: + goto end; + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); +end: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_format_yuv_item_ops = { + .release = f_uvc_format_yuv_item_release, + .allow_link = f_uvc_format_yuv_link, + .drop_link = f_uvc_format_yuv_unlink, + .show_attribute = &f_uvc_format_yuv_item_attr_show, + .store_attribute = &f_uvc_format_yuv_item_attr_store, +}; + +__F_UVC_ATTR(format_yuv_item, b_bits_per_pixel, bBitsPerPixel, 8, 255); + +static struct configfs_attribute *f_uvc_format_yuv_item_attrs[] = { + &f_uvc_format_yuv_item_b_bits_per_pixel.attr, + NULL, +}; + +__ITEM_TYPE(format_yuv_item); + +/* uvc.fun/streaming/format/yuv */ +static const __u8 yuv_guid_format[] = { + 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 +}; + +static struct config_item *f_uvc_format_yuv_make(struct config_group *group, + const char *name) +{ + struct f_uvc_format_yuv_item *fyi; + struct f_uvc_streaming *streaming; + struct f_uvc_opts *opts; + + fyi = kzalloc(sizeof(*fyi), GFP_KERNEL); + if (!fyi) + return ERR_CAST(fyi); + + fyi->desc.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE; + fyi->desc.bDescriptorType = USB_DT_CS_INTERFACE; + fyi->desc.bDescriptorSubType = UVC_VS_FORMAT_UNCOMPRESSED; + fyi->desc.bFormatIndex = 1; + fyi->desc.bNumFrameDescriptors = 2; + memcpy(fyi->desc.guidFormat, yuv_guid_format, sizeof(yuv_guid_format)); + fyi->desc.bBitsPerPixel = 16; + fyi->desc.bDefaultFrameIndex = 1; + fyi->desc.bAspectRatioX = 0; + fyi->desc.bAspectRatioY = 0; + fyi->desc.bmInterfaceFlags = 0; + fyi->desc.bCopyProtect = 0; + INIT_LIST_HEAD(&fyi->frames); + config_item_init_type_name(&fyi->item, name, + &f_uvc_format_yuv_item_type); + + streaming = to_f_uvc_streaming(group->cg_item.ci_parent->ci_parent); + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + list_add_tail(&fyi->target_entry, &streaming->known_targets); + mutex_unlock(&opts->lock); + + return &fyi->item; +} + +__F_UVC_DROP(format_yuv, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(format_yuv); +__GROUP_TYPE(format_yuv); + +/* uvc.fun/streaming/format/mjpeg/item */ +__TO_F_UVC_STRUCT_ITEM(format_mjpeg_item); + +CONFIGFS_ATTR_STRUCT(f_uvc_format_mjpeg_item); +CONFIGFS_ATTR_OPS(f_uvc_format_mjpeg_item); + +static void f_uvc_format_mjpeg_item_release(struct config_item *item) +{ + struct f_uvc_format_mjpeg_item *format_mjpeg_item; + + format_mjpeg_item = to_f_uvc_format_mjpeg_item(item); + kfree(format_mjpeg_item); +} + +static int f_uvc_format_mjpeg_link(struct config_item *src, + struct config_item *target) +{ + struct configfs_subsystem *subsys; + struct f_uvc_format *format; + struct f_uvc_item_struct_header *h, *e; + struct f_uvc_opts *opts; + struct f_uvc_format_mjpeg_item *fsi; + struct f_uvc_link *link, *l; + int ret = 0; + + fsi = to_f_uvc_format_mjpeg_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + format = to_f_uvc_format(src->ci_parent->ci_parent); + h = (struct f_uvc_item_struct_header *)target; + opts = format_to_opts(format); + mutex_lock(&opts->lock); + if (fsi->refcnt) { + ret = -EBUSY; + goto error; + } + + list_for_each_entry(e, &format->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto error; + } + + switch (__uvc_dt(target)) { + case UVC_VS_FRAME_MJPEG: + list_for_each_entry(l, &fsi->frames, link) + if (l->desc == h) { + kfree(link); + ret = -EBUSY; + goto error; + } + link->desc = h; + list_add_tail(&link->link, &fsi->frames); + ++to_f_uvc_frame_mjpeg_item(target)->refcnt; + ++fsi->n_frames; + break; + case UVC_VS_FRAME_UNCOMPRESSED: + ret = -ENODEV; + kfree(link); + break; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +__ITEM_TO_OPTS(format_mjpeg_item, ci_parent->ci_parent->ci_parent->ci_parent); +__ITEM_TO_SUBSYS(format_mjpeg_item); + +static int f_uvc_format_mjpeg_unlink(struct config_item *src, + struct config_item *target) +{ + struct f_uvc_format_mjpeg_item *fsi; + struct configfs_subsystem *subsys; + struct f_uvc_item_struct_header *h; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + struct f_uvc_link *link; + + fsi = to_f_uvc_format_mjpeg_item(src); + subsys = to_config_group(src->ci_parent)->cg_subsys; + mutex_lock(&subsys->su_mutex); + h = (struct f_uvc_item_struct_header *)target; + opts = format_mjpeg_item_to_opts(fsi); + mutex_lock(&opts->lock); + switch (__uvc_dt(target)) { + case UVC_VS_FRAME_MJPEG: + list_for_each_entry(link, &fsi->frames, link) + if (link->desc == h) { + list_del(&link->link); + --to_f_uvc_frame_mjpeg_item(target)->refcnt; + --fsi->n_frames; + kfree(link); + break; + } + break; + case UVC_VS_FRAME_UNCOMPRESSED: + goto end; + break; + } + gadget_item = src->ci_parent->ci_parent->ci_parent->ci_parent; + unregister_gadget_item(gadget_item); +end: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations f_uvc_format_mjpeg_item_ops = { + .release = f_uvc_format_mjpeg_item_release, + .allow_link = f_uvc_format_mjpeg_link, + .drop_link = f_uvc_format_mjpeg_unlink, + .show_attribute = &f_uvc_format_mjpeg_item_attr_show, + .store_attribute = &f_uvc_format_mjpeg_item_attr_store, +}; + +__F_UVC_ATTR(format_mjpeg_item, b_copy_protect, bCopyProtect, 8, 255); + +static struct configfs_attribute *f_uvc_format_mjpeg_item_attrs[] = { + &f_uvc_format_mjpeg_item_b_copy_protect.attr, + NULL, +}; + +__ITEM_TYPE(format_mjpeg_item); + +/* uvc.fun/streaming/format/mjpeg */ +static struct config_item *f_uvc_format_mjpeg_make(struct config_group *group, + const char *name) +{ + struct f_uvc_format_mjpeg_item *fmi; + struct f_uvc_streaming *streaming; + struct f_uvc_opts *opts; + + fmi = kzalloc(sizeof(*fmi), GFP_KERNEL); + if (!fmi) + return ERR_CAST(fmi); + + fmi->desc.bLength = UVC_DT_FORMAT_MJPEG_SIZE, + fmi->desc.bDescriptorType = USB_DT_CS_INTERFACE, + fmi->desc.bDescriptorSubType = UVC_VS_FORMAT_MJPEG, + fmi->desc.bFormatIndex = 2, + fmi->desc.bNumFrameDescriptors = 2, + fmi->desc.bmFlags = 0, + fmi->desc.bDefaultFrameIndex = 1, + fmi->desc.bAspectRatioX = 0, + fmi->desc.bAspectRatioY = 0, + fmi->desc.bmInterfaceFlags = 0, + fmi->desc.bCopyProtect = 0, + INIT_LIST_HEAD(&fmi->frames); + config_item_init_type_name(&fmi->item, name, + &f_uvc_format_mjpeg_item_type); + + streaming = to_f_uvc_streaming(group->cg_item.ci_parent->ci_parent); + opts = streaming_to_opts(streaming); + mutex_lock(&opts->lock); + list_add_tail(&fmi->target_entry, &streaming->known_targets); + mutex_unlock(&opts->lock); + + return &fmi->item; +} + +__F_UVC_DROP(format_mjpeg, ci_parent->ci_parent->ci_parent); +__F_UVC_OPS(format_mjpeg); +__GROUP_TYPE(format_mjpeg); + +/* uvc.fun */ +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 inline int f_uvc_opts_link_fs_class(struct f_uvc_opts *opts, + struct f_uvc_class_fs_item *i) +{ + if (opts->fs_control) + return -EBUSY; + + opts->fs_control = (const struct uvc_descriptor_header * const *) + i->uvc_fs_control_cls; + ++i->refcnt; + + return 0; +} + +static inline int f_uvc_opts_link_ss_class(struct f_uvc_opts *opts, + struct f_uvc_class_ss_item *i) +{ + if (opts->ss_control) + return -EBUSY; + + opts->ss_control = (const struct uvc_descriptor_header * const *) + i->uvc_ss_control_cls; + ++i->refcnt; + + return 0; +} + +static inline int f_uvc_opts_link_fs_streaming(struct f_uvc_opts *opts, + struct f_uvc_streaming_fs_item *i) +{ + struct f_uvc_link *l, *m; + struct uvc_descriptor_header **fs_streaming; + int j; + + if (opts->fs_streaming) + return -EBUSY; + + fs_streaming = kcalloc(i->n_children + 1 + 2, sizeof(*fs_streaming), + GFP_KERNEL); + if (!fs_streaming) + return -ENOMEM; + + fs_streaming[0] = i->streaming[0]; + fs_streaming[1] = i->streaming[1]; + j = 2; + list_for_each_entry(l, &i->formats, link) { + struct f_uvc_format_yuv_item *fyi; + struct f_uvc_format_mjpeg_item *fmi; + switch (l->desc->desc.bDescriptorSubType) { + case UVC_VS_FORMAT_UNCOMPRESSED: + fyi = (struct f_uvc_format_yuv_item *)l->desc; + fs_streaming[j++] = &l->desc->desc; + list_for_each_entry(m, &fyi->frames, link) + fs_streaming[j++] = &m->desc->desc; + break; + case UVC_VS_FORMAT_MJPEG: + fmi = (struct f_uvc_format_mjpeg_item *)l->desc; + fs_streaming[j++] = &l->desc->desc; + list_for_each_entry(m, &fmi->frames, link) + fs_streaming[j++] = &m->desc->desc; + break; + } + } + + opts->fs_streaming = + (const struct uvc_descriptor_header * const *)fs_streaming; + ++i->refcnt; + + return 0; +} + +static inline int f_uvc_opts_link_hs_streaming(struct f_uvc_opts *opts, + struct f_uvc_streaming_hs_item *i) +{ + struct f_uvc_link *l, *m; + struct uvc_descriptor_header **hs_streaming; + int j; + + if (opts->hs_streaming) + return -EBUSY; + + hs_streaming = kcalloc(i->n_children + 1 + 2, sizeof(*hs_streaming), + GFP_KERNEL); + if (!hs_streaming) + return -ENOMEM; + + hs_streaming[0] = i->streaming[0]; + hs_streaming[1] = i->streaming[1]; + j = 2; + list_for_each_entry(l, &i->formats, link) { + struct f_uvc_format_yuv_item *fyi; + struct f_uvc_format_mjpeg_item *fmi; + switch (l->desc->desc.bDescriptorSubType) { + case UVC_VS_FORMAT_UNCOMPRESSED: + fyi = (struct f_uvc_format_yuv_item *)l->desc; + hs_streaming[j++] = &l->desc->desc; + list_for_each_entry(m, &fyi->frames, link) + hs_streaming[j++] = &m->desc->desc; + break; + case UVC_VS_FORMAT_MJPEG: + fmi = (struct f_uvc_format_mjpeg_item *)l->desc; + hs_streaming[j++] = &l->desc->desc; + list_for_each_entry(m, &fmi->frames, link) + hs_streaming[j++] = &m->desc->desc; + break; + } + } + + opts->hs_streaming = + (const struct uvc_descriptor_header * const *)hs_streaming; + ++i->refcnt; + + return 0; +} + +static inline int f_uvc_opts_link_ss_streaming(struct f_uvc_opts *opts, + struct f_uvc_streaming_ss_item *i) +{ + struct f_uvc_link *l, *m; + struct uvc_descriptor_header **ss_streaming; + int j; + + if (opts->ss_streaming) + return -EBUSY; + + ss_streaming = kcalloc(i->n_children + 1 + 2, sizeof(*ss_streaming), + GFP_KERNEL); + if (!ss_streaming) + return -ENOMEM; + + ss_streaming[0] = i->streaming[0]; + ss_streaming[1] = i->streaming[1]; + j = 2; + list_for_each_entry(l, &i->formats, link) { + struct f_uvc_format_yuv_item *fyi; + struct f_uvc_format_mjpeg_item *fmi; + switch (l->desc->desc.bDescriptorSubType) { + case UVC_VS_FORMAT_UNCOMPRESSED: + fyi = (struct f_uvc_format_yuv_item *)l->desc; + ss_streaming[j++] = &l->desc->desc; + list_for_each_entry(m, &fyi->frames, link) + ss_streaming[j++] = &m->desc->desc; + break; + case UVC_VS_FORMAT_MJPEG: + fmi = (struct f_uvc_format_mjpeg_item *)l->desc; + ss_streaming[j++] = &l->desc->desc; + list_for_each_entry(m, &fmi->frames, link) + ss_streaming[j++] = &m->desc->desc; + break; + } + } + + opts->ss_streaming = + (const struct uvc_descriptor_header * const *)ss_streaming; + ++i->refcnt; + + return 0; +} + +static int f_uvc_opts_link(struct config_item *src, struct config_item *trg) +{ + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + struct f_uvc_class_struct_header *h, *e; + int ret = 0; + + opts = container_of(to_config_group(src), struct f_uvc_opts, + func_inst.group); + subsys = to_config_group(src)->cg_subsys; + mutex_lock(&subsys->su_mutex); + h = (struct f_uvc_class_struct_header *)trg; + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = -EBUSY; + goto error; + } + list_for_each_entry(e, &opts->known_targets, target_entry) + if (e == h) + break; + + if (e != h) { + ret = -ENODEV; + goto error; + } + + if (trg->ci_parent == opts->fs_class) { + ret = f_uvc_opts_link_fs_class(opts, + to_f_uvc_class_fs_item(trg)); + } else if (trg->ci_parent == opts->ss_class) { + ret = f_uvc_opts_link_ss_class(opts, + to_f_uvc_class_ss_item(trg)); + } else if (trg->ci_parent == opts->fs_streaming_class) { + ret = f_uvc_opts_link_fs_streaming(opts, + to_f_uvc_streaming_fs_item(trg)); + } else if (trg->ci_parent == opts->hs_streaming_class) { + ret = f_uvc_opts_link_hs_streaming(opts, + to_f_uvc_streaming_hs_item(trg)); + } else if (trg->ci_parent == opts->ss_streaming_class) { + ret = f_uvc_opts_link_ss_streaming(opts, + to_f_uvc_streaming_ss_item(trg)); + } else { + ret = -ENODEV; + goto error; + } + +error: + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return ret; +} + +static int f_uvc_opts_unlink(struct config_item *src, struct config_item *trg) +{ + struct configfs_subsystem *subsys; + struct f_uvc_opts *opts; + struct config_item *gadget_item; + + subsys = to_config_group(src)->cg_subsys; + mutex_lock(&subsys->su_mutex); + opts = container_of(to_config_group(src), struct f_uvc_opts, + func_inst.group); + mutex_lock(&opts->lock); + + if (trg->ci_parent == opts->fs_class) { + struct f_uvc_class_fs_item *fsi; + + fsi = to_f_uvc_class_fs_item(trg); + --fsi->refcnt; + opts->fs_control = NULL; + } else if (trg->ci_parent == opts->ss_class) { + struct f_uvc_class_ss_item *ssi; + + ssi = to_f_uvc_class_ss_item(trg); + --ssi->refcnt; + opts->ss_control = NULL; + } else if (trg->ci_parent == opts->fs_streaming_class) { + struct f_uvc_streaming_fs_item *fsi; + + fsi = to_f_uvc_streaming_fs_item(trg); + --fsi->refcnt; + kfree(opts->fs_streaming); + opts->fs_streaming = NULL; + } else if (trg->ci_parent == opts->hs_streaming_class) { + struct f_uvc_streaming_hs_item *hsi; + + hsi = to_f_uvc_streaming_hs_item(trg); + --hsi->refcnt; + kfree(opts->hs_streaming); + opts->hs_streaming = NULL; + } else if (trg->ci_parent == opts->ss_streaming_class) { + struct f_uvc_streaming_ss_item *ssi; + + ssi = to_f_uvc_streaming_ss_item(trg); + --ssi->refcnt; + kfree(opts->ss_streaming); + opts->ss_streaming = NULL; + } + gadget_item = src->ci_parent; + unregister_gadget_item(gadget_item); + mutex_unlock(&opts->lock); + mutex_unlock(&subsys->su_mutex); + return 0; +} + +static struct configfs_item_operations uvc_item_ops = { + .allow_link = f_uvc_opts_link, + .drop_link = f_uvc_opts_unlink, + .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 struct configfs_attribute *uvc_attrs[] = { + &f_uvc_opts_trace.attr, + NULL, +}; + +struct config_item_type uvc_func_type = { + .ct_item_ops = &uvc_item_ops, + .ct_attrs = uvc_attrs, + .ct_owner = THIS_MODULE, +}; + +/* uvc.fun/control */ +struct f_uvc_control f_uvc_control_group; + +/* uvc.fun/control/header*/ +struct config_group f_uvc_header_group; + +/* uvc.fun/control/processing*/ +struct config_group f_uvc_processing_group; + +/* uvc.fun/control/class */ +struct config_group f_uvc_class_group; + +/* uvc.fun/control/class/fs */ +struct config_group f_uvc_class_fs_group; + +/* uvc.fun/control/class/ss */ +struct config_group f_uvc_class_ss_group; + +struct config_group *f_uvc_class_default_groups[] = { + &f_uvc_class_fs_group, + &f_uvc_class_ss_group, + NULL, +}; + +/* uvc.fun/control/terminal */ +struct config_group f_uvc_terminal_group; + +/* uvc.fun/control/terminal/camera */ +struct config_group f_uvc_camera_group; + +/* uvc.fun/control/terminal/output */ +struct config_group f_uvc_output_group; + +struct config_group *f_uvc_terminal_default_groups[] = { + &f_uvc_camera_group, + &f_uvc_output_group, + NULL, +}; + +struct config_group *f_uvc_control_default_groups[] = { + &f_uvc_header_group, + &f_uvc_processing_group, + &f_uvc_class_group, + &f_uvc_terminal_group, + NULL, +}; + +/* uvc.fun/streaming */ +struct f_uvc_streaming f_uvc_streaming_group; + +/* uvc.fun/streaming/input_header */ +struct config_group f_uvc_input_header_group; + +/* uvc.fun/streaming/color_matching */ +struct config_group f_uvc_color_matching_group; + +/* uvc.fun/streaming/class */ +struct config_group f_uvc_streaming_class_group; + +/* uvc.fun/streaming/class/fs */ +struct config_group f_uvc_streaming_fs_group; + +/* uvc.fun/streaming/class/hs */ +struct config_group f_uvc_streaming_hs_group; + +/* uvc.fun/streaming/class/ss */ +struct config_group f_uvc_streaming_ss_group; + +struct config_group *f_uvc_streaming_class_default_groups[] = { + &f_uvc_streaming_fs_group, + &f_uvc_streaming_hs_group, + &f_uvc_streaming_ss_group, + NULL, +}; + +/* uvc.fun/streaming/format */ +struct f_uvc_format f_uvc_format_group; + +/* uvc.fun/streaming/format/frame */ +struct config_group f_uvc_frame_group; + +/* uvc.fun/streaming/format/frame/yuv */ +struct config_group f_uvc_frame_yuv_group; + +/* uvc.fun/streaming/format/frame/mjpeg */ +struct config_group f_uvc_frame_mjpeg_group; + +struct config_group *f_uvc_frame_default_groups[] = { + &f_uvc_frame_yuv_group, + &f_uvc_frame_mjpeg_group, + NULL, +}; + +/* uvc.fun/streaming/format/yuv */ +struct config_group f_uvc_format_yuv_group; + +/* uvc.fun/streaming/format/mjpeg */ +struct config_group f_uvc_format_mjpeg_group; + +struct config_group *f_uvc_format_default_groups[] = { + &f_uvc_frame_group, + &f_uvc_format_yuv_group, + &f_uvc_format_mjpeg_group, + NULL, +}; + +struct config_group *f_uvc_streaming_default_groups[] = { + &f_uvc_input_header_group, + &f_uvc_color_matching_group, + &f_uvc_streaming_class_group, + &f_uvc_format_group.group, + NULL, +}; + +struct config_group *f_uvc_default_groups[] = { + &f_uvc_control_group.group, + &f_uvc_streaming_group.group, + NULL, +}; diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h new file mode 100644 index 0000000..13f554f --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -0,0 +1,283 @@ +/* + * uvc_configfs.h + * + * Configfs hierarchy definitions for the uvc function + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef UVC_CONFIGFS_H +#define UVC_CONFIGFS_H + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/configfs.h> +#include <linux/usb/video.h> + +struct f_uvc_item_struct_header { + struct config_item item; + struct list_head target_entry; + struct uvc_descriptor_header desc; +}; + +struct f_uvc_class_struct_header { + struct config_item item; + struct list_head target_entry; +}; + +struct f_uvc_control { + struct config_group group; + /* + * In control/class/<fs|hs>/<*> there will be symbolic links. + * The allowed targets are in the "known_targets" list. + */ + struct list_head known_targets; +}; + +DECLARE_UVC_HEADER_DESCRIPTOR(1); +struct f_uvc_header_item { + struct config_item item; + struct list_head target_entry; + struct UVC_HEADER_DESCRIPTOR(1) desc; + int refcnt; +}; + +struct f_uvc_processing_item { + struct config_item item; + struct list_head target_entry; + struct uvc_processing_unit_descriptor desc; + int refcnt; +}; + +enum f_uvc_class_component { + F_UVC_CLASS_HEADER = 0, + F_UVC_CLASS_INPUT, + F_UVC_CLASS_PROCESSING, + F_UVC_CLASS_OUTPUT, +}; + +struct f_uvc_class_fs_item { + struct config_item item; + struct list_head target_entry; + struct uvc_descriptor_header *uvc_fs_control_cls[5]; + int refcnt; +}; + +struct f_uvc_class_ss_item { + struct config_item item; + struct list_head target_entry; + struct uvc_descriptor_header *uvc_ss_control_cls[5]; + int refcnt; +}; + +struct f_uvc_camera_item { + struct config_item item; + struct list_head target_entry; + struct uvc_camera_terminal_descriptor desc; + int refcnt; +}; + +struct f_uvc_output_item { + struct config_item item; + struct list_head target_entry; + struct uvc_output_terminal_descriptor desc; + int refcnt; +}; + +struct f_uvc_streaming { + int refcnt; + struct config_group group; + /* + * In streaming/class/<fs|hs|ss>/<*> there will be symbolic links. + * The allowed targets are in the "known_targets" list. + */ + struct list_head known_targets; +}; + +DECLARE_UVC_INPUT_HEADER_DESCRIPTOR(1, 2); +struct f_uvc_input_header_item { + struct config_item item; + struct list_head target_entry; + struct UVC_INPUT_HEADER_DESCRIPTOR(1, 2) desc; + int refcnt; +}; + +struct f_uvc_color_matching_item { + struct config_item item; + struct list_head target_entry; + struct uvc_color_matching_descriptor desc; + int refcnt; +}; + +enum f_uvc_streaming_component { + F_UVC_STREAMING_INPUT_HEADER = 0, + F_UVC_STREAMING_COLOR_MATCHING, +}; + +struct f_uvc_streaming_fs_item { + struct config_item item; + struct list_head target_entry; + struct uvc_descriptor_header *streaming[2]; + int refcnt; + struct list_head formats; + int n_children; +}; + +struct f_uvc_streaming_hs_item { + struct config_item item; + struct list_head target_entry; + struct uvc_descriptor_header *streaming[2]; + int refcnt; + struct list_head formats; + int n_children; +}; + +struct f_uvc_streaming_ss_item { + struct config_item item; + struct list_head target_entry; + struct uvc_descriptor_header *streaming[2]; + int refcnt; + struct list_head formats; + int n_children; +}; + +struct f_uvc_format { + int refcnt; + struct config_group group; + /* + * In streaming/format/<yuv|mjpeg>/<*> there will be symbolic links. + * The allowed targets are in the "known_targets" list. + */ + struct list_head known_targets; +}; + +struct f_uvc_frame_yuv_item { + struct config_item item; + struct list_head target_entry; + struct uvc_frame_uncompressed desc; + __u32 *dwFrameInterval; + int n_interval; + struct uvc_frame_uncompressed *normalized_desc; + int refcnt; +}; + +struct f_uvc_frame_mjpeg_item { + struct config_item item; + struct list_head target_entry; + struct uvc_frame_mjpeg desc; + __u32 *dwFrameInterval; + int n_interval; + struct uvc_frame_mjpeg *normalized_desc; + int refcnt; +}; + +struct f_uvc_format_yuv_item { + struct config_item item; + struct list_head target_entry; + struct uvc_format_uncompressed desc; + struct list_head frames; + int n_frames; + int refcnt; +}; + +struct f_uvc_format_mjpeg_item { + struct config_item item; + struct list_head target_entry; + struct uvc_format_mjpeg desc; + struct list_head frames; + int n_frames; + int refcnt; +}; + +extern struct config_item_type f_uvc_control_type; +extern struct f_uvc_control f_uvc_control_group; + +extern struct config_item_type f_uvc_header_type; +extern struct config_group f_uvc_header_group; + +extern struct config_item_type f_uvc_processing_type; +extern struct config_group f_uvc_processing_group; + +extern struct config_item_type f_uvc_class_type; +extern struct config_group f_uvc_class_group; + +extern struct config_item_type f_uvc_class_fs_type; +extern struct config_group f_uvc_class_fs_group; + +extern struct config_item_type f_uvc_class_ss_type; +extern struct config_group f_uvc_class_ss_group; + +extern struct config_group *f_uvc_class_default_groups[]; + +extern struct config_item_type f_uvc_terminal_type; +extern struct config_group f_uvc_terminal_group; + +extern struct config_item_type f_uvc_camera_type; +extern struct config_group f_uvc_camera_group; + +extern struct config_item_type f_uvc_output_type; +extern struct config_group f_uvc_output_group; + +extern struct config_group *f_uvc_terminal_default_groups[]; + +extern struct config_group *f_uvc_control_default_groups[]; + +extern struct config_item_type f_uvc_streaming_type; +extern struct f_uvc_streaming f_uvc_streaming_group; + +extern struct config_item_type f_uvc_input_header_type; +extern struct config_group f_uvc_input_header_group; + +extern struct config_item_type f_uvc_color_matching_type; +extern struct config_group f_uvc_color_matching_group; + +extern struct config_item_type f_uvc_streaming_class_type; +extern struct config_group f_uvc_streaming_class_group; + +extern struct config_item_type f_uvc_streaming_fs_type; +extern struct config_group f_uvc_streaming_fs_group; + +extern struct config_item_type f_uvc_streaming_hs_type; +extern struct config_group f_uvc_streaming_hs_group; + +extern struct config_item_type f_uvc_streaming_ss_type; +extern struct config_group f_uvc_streaming_ss_group; + +extern struct config_group *f_uvc_streaming_class_default_groups[]; + +extern struct config_item_type f_uvc_format_type; +extern struct f_uvc_format f_uvc_format_group; + +extern struct config_item_type f_uvc_frame_type; +extern struct config_group f_uvc_frame_group; + +extern struct config_item_type f_uvc_frame_yuv_type; +extern struct config_group f_uvc_frame_yuv_group; + +extern struct config_item_type f_uvc_frame_mjpeg_type; +extern struct config_group f_uvc_frame_mjpeg_group; + +extern struct config_group *f_uvc_frame_default_groups[]; + +extern struct config_item_type f_uvc_format_yuv_type; +extern struct config_group f_uvc_format_yuv_group; + +extern struct config_item_type f_uvc_format_mjpeg_type; +extern struct config_group f_uvc_format_mjpeg_group; + +extern struct config_group *f_uvc_format_default_groups[]; + +extern struct config_group *f_uvc_streaming_default_groups[]; + +extern struct config_group *f_uvc_default_groups[]; + +extern struct config_item_type uvc_func_type; + +#endif /* UVC_CONFIGFS_H */ -- 1.9.1 -- 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