[RFC 3/3] usb/gadget: f_uvc: add configfs support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux