diff mbox series

[v2,4/8] usb: gadget: uvc: configfs: Allocate groups dynamically

Message ID 20180801215505.7658-5-laurent.pinchart@ideasonboard.com (mailing list archive)
State New, archived
Headers show
Series usb: gadget: uvc: Improve configfs support | expand

Commit Message

Laurent Pinchart Aug. 1, 2018, 9:55 p.m. UTC
The UVC configfs implementation creates all groups as global static
variables. This prevents creation of multiple UVC function instances,
as they would all require their own configfs group instances.

Fix this by allocating all groups dynamically. To avoid duplicating code
around, extend the config_item_type structure with group name and
children, and implement helper functions to create children
automatically for most groups.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since v1:

- Free groups by implementing .release() handler and removing children
  explicitly.
---
 drivers/usb/gadget/function/f_uvc.c        |   8 +-
 drivers/usb/gadget/function/uvc_configfs.c | 581 ++++++++++++++++-------------
 2 files changed, 338 insertions(+), 251 deletions(-)

Comments

Kieran Bingham Sept. 24, 2018, 11:50 a.m. UTC | #1
Hi Laurent,

On 01/08/18 22:55, Laurent Pinchart wrote:
> The UVC configfs implementation creates all groups as global static
> variables. This prevents creation of multiple UVC function instances,
> as they would all require their own configfs group instances.
> 
> Fix this by allocating all groups dynamically. To avoid duplicating code
> around, extend the config_item_type structure with group name and
> children, and implement helper functions to create children
> automatically for most groups.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
> Changes since v1:
> 
> - Free groups by implementing .release() handler and removing children
>   explicitly.

Great - that resolves my concerns from the previous iteration.

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>


> ---
>  drivers/usb/gadget/function/f_uvc.c        |   8 +-
>  drivers/usb/gadget/function/uvc_configfs.c | 581 ++++++++++++++++-------------
>  2 files changed, 338 insertions(+), 251 deletions(-)
> 
> diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
> index d8ce7868fe22..95cb1b5f5ffe 100644
> --- a/drivers/usb/gadget/function/f_uvc.c
> +++ b/drivers/usb/gadget/function/f_uvc.c
> @@ -792,6 +792,7 @@ static struct usb_function_instance *uvc_alloc_inst(void)
>  	struct uvc_output_terminal_descriptor *od;
>  	struct uvc_color_matching_descriptor *md;
>  	struct uvc_descriptor_header **ctl_cls;
> +	int ret;
>  
>  	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
>  	if (!opts)
> @@ -868,7 +869,12 @@ static struct usb_function_instance *uvc_alloc_inst(void)
>  	opts->streaming_interval = 1;
>  	opts->streaming_maxpacket = 1024;
>  
> -	uvcg_attach_configfs(opts);
> +	ret = uvcg_attach_configfs(opts);
> +	if (ret < 0) {
> +		kfree(opts);
> +		return ERR_PTR(ret);
> +	}
> +
>  	return &opts->func_inst;
>  }
>  
> diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c
> index 8d513cc6fb8c..ae722549eabc 100644
> --- a/drivers/usb/gadget/function/uvc_configfs.c
> +++ b/drivers/usb/gadget/function/uvc_configfs.c
> @@ -41,6 +41,71 @@ static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item)
>  			    func_inst.group);
>  }
>  
> +struct uvcg_config_group_type {
> +	struct config_item_type type;
> +	const char *name;
> +	const struct uvcg_config_group_type **children;
> +	int (*create_children)(struct config_group *group);
> +};
> +
> +static void uvcg_config_item_release(struct config_item *item)
> +{
> +	struct config_group *group = to_config_group(item);
> +
> +	kfree(group);
> +}
> +
> +static struct configfs_item_operations uvcg_config_item_ops = {
> +	.release	= uvcg_config_item_release,
> +};
> +
> +static int uvcg_config_create_group(struct config_group *parent,
> +				    const struct uvcg_config_group_type *type);
> +
> +static int uvcg_config_create_children(struct config_group *group,
> +				const struct uvcg_config_group_type *type)
> +{
> +	const struct uvcg_config_group_type **child;
> +	int ret;
> +
> +	if (type->create_children)
> +		return type->create_children(group);
> +
> +	for (child = type->children; child && *child; ++child) {
> +		ret = uvcg_config_create_group(group, *child);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int uvcg_config_create_group(struct config_group *parent,
> +				    const struct uvcg_config_group_type *type)
> +{
> +	struct config_group *group;
> +
> +	group = kzalloc(sizeof(*group), GFP_KERNEL);
> +	if (!group)
> +		return -ENOMEM;
> +
> +	config_group_init_type_name(group, type->name, &type->type);
> +	configfs_add_default_group(group, parent);
> +
> +	return uvcg_config_create_children(group, type);
> +}
> +
> +static void uvcg_config_remove_children(struct config_group *group)
> +{
> +	struct config_group *child, *n;
> +
> +	list_for_each_entry_safe(child, n, &group->default_groups, group_entry) {
> +		list_del(&child->group_entry);
> +		uvcg_config_remove_children(child);
> +		config_item_put(&child->cg_item);
> +	}
> +}
> +
>  /* -----------------------------------------------------------------------------
>   * control/header/<NAME>
>   * control/header
> @@ -137,6 +202,7 @@ static struct configfs_attribute *uvcg_control_header_attrs[] = {
>  };
>  
>  static const struct config_item_type uvcg_control_header_type = {
> +	.ct_item_ops	= &uvcg_config_item_ops,
>  	.ct_attrs	= uvcg_control_header_attrs,
>  	.ct_owner	= THIS_MODULE,
>  };
> @@ -161,32 +227,23 @@ static struct config_item *uvcg_control_header_make(struct config_group *group,
>  	return &h->item;
>  }
>  
> -static void uvcg_control_header_drop(struct config_group *group,
> -			      struct config_item *item)
> -{
> -	struct uvcg_control_header *h = to_uvcg_control_header(item);
> -
> -	kfree(h);
> -}
> -
> -static struct config_group uvcg_control_header_grp;
> -
>  static struct configfs_group_operations uvcg_control_header_grp_ops = {
>  	.make_item		= uvcg_control_header_make,
> -	.drop_item		= uvcg_control_header_drop,
>  };
>  
> -static const struct config_item_type uvcg_control_header_grp_type = {
> -	.ct_group_ops	= &uvcg_control_header_grp_ops,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_control_header_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_group_ops	= &uvcg_control_header_grp_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "header",
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/processing/default
>   */
>  
> -static struct config_group uvcg_default_processing_grp;
> -
>  #define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv)		\
>  static ssize_t uvcg_default_processing_##cname##_show(			\
>  	struct config_item *item, char *page)				\
> @@ -265,27 +322,35 @@ static struct configfs_attribute *uvcg_default_processing_attrs[] = {
>  	NULL,
>  };
>  
> -static const struct config_item_type uvcg_default_processing_type = {
> -	.ct_attrs	= uvcg_default_processing_attrs,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_default_processing_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_attrs	= uvcg_default_processing_attrs,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "default",
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/processing
>   */
>  
> -static struct config_group uvcg_processing_grp;
> -
> -static const struct config_item_type uvcg_processing_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_processing_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "processing",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_default_processing_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/terminal/camera/default
>   */
>  
> -static struct config_group uvcg_default_camera_grp;
> -
>  #define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv)			\
>  static ssize_t uvcg_default_camera_##cname##_show(			\
>  	struct config_item *item, char *page)				\
> @@ -375,27 +440,35 @@ static struct configfs_attribute *uvcg_default_camera_attrs[] = {
>  	NULL,
>  };
>  
> -static const struct config_item_type uvcg_default_camera_type = {
> -	.ct_attrs	= uvcg_default_camera_attrs,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_default_camera_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_attrs	= uvcg_default_camera_attrs,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "default",
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/terminal/camera
>   */
>  
> -static struct config_group uvcg_camera_grp;
> -
> -static const struct config_item_type uvcg_camera_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_camera_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "camera",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_default_camera_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/terminal/output/default
>   */
>  
> -static struct config_group uvcg_default_output_grp;
> -
>  #define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv)			\
>  static ssize_t uvcg_default_output_##cname##_show(			\
>  	struct config_item *item, char *page)				\
> @@ -446,47 +519,68 @@ static struct configfs_attribute *uvcg_default_output_attrs[] = {
>  	NULL,
>  };
>  
> -static const struct config_item_type uvcg_default_output_type = {
> -	.ct_attrs	= uvcg_default_output_attrs,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_default_output_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_attrs	= uvcg_default_output_attrs,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "default",
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/terminal/output
>   */
>  
> -static struct config_group uvcg_output_grp;
> -
> -static const struct config_item_type uvcg_output_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_output_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "output",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_default_output_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/terminal
>   */
>  
> -static struct config_group uvcg_terminal_grp;
> -
> -static const struct config_item_type uvcg_terminal_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_terminal_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "terminal",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_camera_grp_type,
> +		&uvcg_output_grp_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control/class/{fs|ss}
>   */
>  
> -static struct config_group uvcg_control_class_fs_grp;
> -static struct config_group uvcg_control_class_ss_grp;
> +struct uvcg_control_class_group {
> +	struct config_group group;
> +	const char *name;
> +};
>  
>  static inline struct uvc_descriptor_header
>  **uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o)
>  {
> -	struct config_group *group = to_config_group(i);
> +	struct uvcg_control_class_group *group =
> +		container_of(i, struct uvcg_control_class_group,
> +			     group.cg_item);
>  
> -	if (group == &uvcg_control_class_fs_grp)
> +	if (!strcmp(group->name, "fs"))
>  		return o->uvc_fs_control_cls;
>  
> -	if (group == &uvcg_control_class_ss_grp)
> +	if (!strcmp(group->name, "ss"))
>  		return o->uvc_ss_control_cls;
>  
>  	return NULL;
> @@ -570,6 +664,7 @@ static void uvcg_control_class_drop_link(struct config_item *src,
>  }
>  
>  static struct configfs_item_operations uvcg_control_class_item_ops = {
> +	.release	= uvcg_config_item_release,
>  	.allow_link	= uvcg_control_class_allow_link,
>  	.drop_link	= uvcg_control_class_drop_link,
>  };
> @@ -583,20 +678,54 @@ static const struct config_item_type uvcg_control_class_type = {
>   * control/class
>   */
>  
> -static struct config_group uvcg_control_class_grp;
> +static int uvcg_control_class_create_children(struct config_group *parent)
> +{
> +	static const char * const names[] = { "fs", "ss" };
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(names); ++i) {
> +		struct uvcg_control_class_group *group;
>  
> -static const struct config_item_type uvcg_control_class_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +		group = kzalloc(sizeof(*group), GFP_KERNEL);
> +		if (!group)
> +			return -ENOMEM;
> +
> +		group->name = names[i];
> +
> +		config_group_init_type_name(&group->group, group->name,
> +					    &uvcg_control_class_type);
> +		configfs_add_default_group(&group->group, parent);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct uvcg_config_group_type uvcg_control_class_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "class",
> +	.create_children = uvcg_control_class_create_children,
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * control
>   */
>  
> -static struct config_group uvcg_control_grp;
> -
> -static const struct config_item_type uvcg_control_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_control_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "control",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_control_header_grp_type,
> +		&uvcg_processing_grp_type,
> +		&uvcg_terminal_grp_type,
> +		&uvcg_control_class_grp_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
> @@ -604,12 +733,9 @@ static const struct config_item_type uvcg_control_grp_type = {
>   * streaming/mjpeg
>   */
>  
> -static struct config_group uvcg_uncompressed_grp;
> -static struct config_group uvcg_mjpeg_grp;
> -
> -static struct config_item *fmt_parent[] = {
> -	&uvcg_uncompressed_grp.cg_item,
> -	&uvcg_mjpeg_grp.cg_item,
> +static const char * const uvcg_format_names[] = {
> +	"uncompressed",
> +	"mjpeg",
>  };
>  
>  enum uvcg_format_type {
> @@ -735,10 +861,22 @@ static int uvcg_streaming_header_allow_link(struct config_item *src,
>  		goto out;
>  	}
>  
> -	for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i)
> -		if (target->ci_parent == fmt_parent[i])
> +	/*
> +	 * Linking is only allowed to direct children of the format nodes
> +	 * (streaming/uncompressed or streaming/mjpeg nodes). First check that
> +	 * the grand-parent of the target matches the grand-parent of the source
> +	 * (the streaming node), and then verify that the target parent is a
> +	 * format node.
> +	 */
> +	if (src->ci_parent->ci_parent != target->ci_parent->ci_parent)
> +		goto out;
> +
> +	for (i = 0; i < ARRAY_SIZE(uvcg_format_names); ++i) {
> +		if (!strcmp(target->ci_parent->ci_name, uvcg_format_names[i]))
>  			break;
> -	if (i == ARRAY_SIZE(fmt_parent))
> +	}
> +
> +	if (i == ARRAY_SIZE(uvcg_format_names))
>  		goto out;
>  
>  	target_fmt = container_of(to_config_group(target), struct uvcg_format,
> @@ -798,8 +936,9 @@ static void uvcg_streaming_header_drop_link(struct config_item *src,
>  }
>  
>  static struct configfs_item_operations uvcg_streaming_header_item_ops = {
> -	.allow_link		= uvcg_streaming_header_allow_link,
> -	.drop_link		= uvcg_streaming_header_drop_link,
> +	.release	= uvcg_config_item_release,
> +	.allow_link	= uvcg_streaming_header_allow_link,
> +	.drop_link	= uvcg_streaming_header_drop_link,
>  };
>  
>  #define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv)			\
> @@ -875,24 +1014,17 @@ static struct config_item
>  	return &h->item;
>  }
>  
> -static void uvcg_streaming_header_drop(struct config_group *group,
> -			      struct config_item *item)
> -{
> -	struct uvcg_streaming_header *h = to_uvcg_streaming_header(item);
> -
> -	kfree(h);
> -}
> -
> -static struct config_group uvcg_streaming_header_grp;
> -
>  static struct configfs_group_operations uvcg_streaming_header_grp_ops = {
>  	.make_item		= uvcg_streaming_header_make,
> -	.drop_item		= uvcg_streaming_header_drop,
>  };
>  
> -static const struct config_item_type uvcg_streaming_header_grp_type = {
> -	.ct_group_ops	= &uvcg_streaming_header_grp_ops,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_group_ops	= &uvcg_streaming_header_grp_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "header",
>  };
>  
>  /* -----------------------------------------------------------------------------
> @@ -900,6 +1032,8 @@ static const struct config_item_type uvcg_streaming_header_grp_type = {
>   */
>  
>  struct uvcg_frame {
> +	struct config_item	item;
> +	enum uvcg_format_type	fmt_type;
>  	struct {
>  		u8	b_length;
>  		u8	b_descriptor_type;
> @@ -915,8 +1049,6 @@ struct uvcg_frame {
>  		u8	b_frame_interval_type;
>  	} __attribute__((packed)) frame;
>  	u32 *dw_frame_interval;
> -	enum uvcg_format_type	fmt_type;
> -	struct config_item	item;
>  };
>  
>  static struct uvcg_frame *to_uvcg_frame(struct config_item *item)
> @@ -1143,6 +1275,7 @@ static struct configfs_attribute *uvcg_frame_attrs[] = {
>  };
>  
>  static const struct config_item_type uvcg_frame_type = {
> +	.ct_item_ops	= &uvcg_config_item_ops,
>  	.ct_attrs	= uvcg_frame_attrs,
>  	.ct_owner	= THIS_MODULE,
>  };
> @@ -1194,7 +1327,6 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
>  
>  static void uvcg_frame_drop(struct config_group *group, struct config_item *item)
>  {
> -	struct uvcg_frame *h = to_uvcg_frame(item);
>  	struct uvcg_format *fmt;
>  	struct f_uvc_opts *opts;
>  	struct config_item *opts_item;
> @@ -1205,8 +1337,9 @@ static void uvcg_frame_drop(struct config_group *group, struct config_item *item
>  	mutex_lock(&opts->lock);
>  	fmt = to_uvcg_format(&group->cg_item);
>  	--fmt->num_frames;
> -	kfree(h);
>  	mutex_unlock(&opts->lock);
> +
> +	config_item_put(item);
>  }
>  
>  /* -----------------------------------------------------------------------------
> @@ -1415,6 +1548,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = {
>  };
>  
>  static const struct config_item_type uvcg_uncompressed_type = {
> +	.ct_item_ops	= &uvcg_config_item_ops,
>  	.ct_group_ops	= &uvcg_uncompressed_group_ops,
>  	.ct_attrs	= uvcg_uncompressed_attrs,
>  	.ct_owner	= THIS_MODULE,
> @@ -1451,22 +1585,17 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group,
>  	return &h->fmt.group;
>  }
>  
> -static void uvcg_uncompressed_drop(struct config_group *group,
> -			    struct config_item *item)
> -{
> -	struct uvcg_uncompressed *h = to_uvcg_uncompressed(item);
> -
> -	kfree(h);
> -}
> -
>  static struct configfs_group_operations uvcg_uncompressed_grp_ops = {
>  	.make_group		= uvcg_uncompressed_make,
> -	.drop_item		= uvcg_uncompressed_drop,
>  };
>  
> -static const struct config_item_type uvcg_uncompressed_grp_type = {
> -	.ct_group_ops	= &uvcg_uncompressed_grp_ops,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_group_ops	= &uvcg_uncompressed_grp_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "uncompressed",
>  };
>  
>  /* -----------------------------------------------------------------------------
> @@ -1618,6 +1747,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = {
>  };
>  
>  static const struct config_item_type uvcg_mjpeg_type = {
> +	.ct_item_ops	= &uvcg_config_item_ops,
>  	.ct_group_ops	= &uvcg_mjpeg_group_ops,
>  	.ct_attrs	= uvcg_mjpeg_attrs,
>  	.ct_owner	= THIS_MODULE,
> @@ -1648,30 +1778,23 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group,
>  	return &h->fmt.group;
>  }
>  
> -static void uvcg_mjpeg_drop(struct config_group *group,
> -			    struct config_item *item)
> -{
> -	struct uvcg_mjpeg *h = to_uvcg_mjpeg(item);
> -
> -	kfree(h);
> -}
> -
>  static struct configfs_group_operations uvcg_mjpeg_grp_ops = {
>  	.make_group		= uvcg_mjpeg_make,
> -	.drop_item		= uvcg_mjpeg_drop,
>  };
>  
> -static const struct config_item_type uvcg_mjpeg_grp_type = {
> -	.ct_group_ops	= &uvcg_mjpeg_grp_ops,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_group_ops	= &uvcg_mjpeg_grp_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "mjpeg",
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * streaming/color_matching/default
>   */
>  
> -static struct config_group uvcg_default_color_matching_grp;
> -
>  #define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv)		\
>  static ssize_t uvcg_default_color_matching_##cname##_show(		\
>  	struct config_item *item, char *page)			\
> @@ -1719,41 +1842,54 @@ static struct configfs_attribute *uvcg_default_color_matching_attrs[] = {
>  	NULL,
>  };
>  
> -static const struct config_item_type uvcg_default_color_matching_type = {
> -	.ct_attrs	= uvcg_default_color_matching_attrs,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_default_color_matching_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_attrs	= uvcg_default_color_matching_attrs,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "default",
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * streaming/color_matching
>   */
>  
> -static struct config_group uvcg_color_matching_grp;
> -
> -static const struct config_item_type uvcg_color_matching_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_color_matching_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "color_matching",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_default_color_matching_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * streaming/class/{fs|hs|ss}
>   */
>  
> -static struct config_group uvcg_streaming_class_fs_grp;
> -static struct config_group uvcg_streaming_class_hs_grp;
> -static struct config_group uvcg_streaming_class_ss_grp;
> +struct uvcg_streaming_class_group {
> +	struct config_group group;
> +	const char *name;
> +};
>  
>  static inline struct uvc_descriptor_header
>  ***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o)
>  {
> -	struct config_group *group = to_config_group(i);
> +	struct uvcg_streaming_class_group *group =
> +		container_of(i, struct uvcg_streaming_class_group,
> +			     group.cg_item);
>  
> -	if (group == &uvcg_streaming_class_fs_grp)
> +	if (!strcmp(group->name, "fs"))
>  		return &o->uvc_fs_streaming_cls;
>  
> -	if (group == &uvcg_streaming_class_hs_grp)
> +	if (!strcmp(group->name, "hs"))
>  		return &o->uvc_hs_streaming_cls;
>  
> -	if (group == &uvcg_streaming_class_ss_grp)
> +	if (!strcmp(group->name, "ss"))
>  		return &o->uvc_ss_streaming_cls;
>  
>  	return NULL;
> @@ -2074,6 +2210,7 @@ static void uvcg_streaming_class_drop_link(struct config_item *src,
>  }
>  
>  static struct configfs_item_operations uvcg_streaming_class_item_ops = {
> +	.release	= uvcg_config_item_release,
>  	.allow_link	= uvcg_streaming_class_allow_link,
>  	.drop_link	= uvcg_streaming_class_drop_link,
>  };
> @@ -2087,35 +2224,71 @@ static const struct config_item_type uvcg_streaming_class_type = {
>   * streaming/class
>   */
>  
> -static struct config_group uvcg_streaming_class_grp;
> +static int uvcg_streaming_class_create_children(struct config_group *parent)
> +{
> +	static const char * const names[] = { "fs", "hs", "ss" };
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(names); ++i) {
> +		struct uvcg_streaming_class_group *group;
> +
> +		group = kzalloc(sizeof(*group), GFP_KERNEL);
> +		if (!group)
> +			return -ENOMEM;
> +
> +		group->name = names[i];
>  
> -static const struct config_item_type uvcg_streaming_class_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +		config_group_init_type_name(&group->group, group->name,
> +					    &uvcg_streaming_class_type);
> +		configfs_add_default_group(&group->group, parent);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "class",
> +	.create_children = uvcg_streaming_class_create_children,
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * streaming
>   */
>  
> -static struct config_group uvcg_streaming_grp;
> -
> -static const struct config_item_type uvcg_streaming_grp_type = {
> -	.ct_owner = THIS_MODULE,
> +static const struct uvcg_config_group_type uvcg_streaming_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "streaming",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_streaming_header_grp_type,
> +		&uvcg_uncompressed_grp_type,
> +		&uvcg_mjpeg_grp_type,
> +		&uvcg_color_matching_grp_type,
> +		&uvcg_streaming_class_grp_type,
> +		NULL,
> +	},
>  };
>  
>  /* -----------------------------------------------------------------------------
>   * UVC function
>   */
>  
> -static void uvc_attr_release(struct config_item *item)
> +static void uvc_func_item_release(struct config_item *item)
>  {
>  	struct f_uvc_opts *opts = to_f_uvc_opts(item);
>  
> +	uvcg_config_remove_children(to_config_group(item));
>  	usb_put_function_instance(&opts->func_inst);
>  }
>  
> -static struct configfs_item_operations uvc_item_ops = {
> -	.release		= uvc_attr_release,
> +static struct configfs_item_operations uvc_func_item_ops = {
> +	.release	= uvc_func_item_release,
>  };
>  
>  #define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit)	\
> @@ -2183,123 +2356,31 @@ static struct configfs_attribute *uvc_attrs[] = {
>  	NULL,
>  };
>  
> -static const struct config_item_type uvc_func_type = {
> -	.ct_item_ops	= &uvc_item_ops,
> -	.ct_attrs	= uvc_attrs,
> -	.ct_owner	= THIS_MODULE,
> +static const struct uvcg_config_group_type uvc_func_type = {
> +	.type = {
> +		.ct_item_ops	= &uvc_func_item_ops,
> +		.ct_attrs	= uvc_attrs,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "",
> +	.children = (const struct uvcg_config_group_type*[]) {
> +		&uvcg_control_grp_type,
> +		&uvcg_streaming_grp_type,
> +		NULL,
> +	},
>  };
>  
>  int uvcg_attach_configfs(struct f_uvc_opts *opts)
>  {
> -	config_group_init_type_name(&uvcg_control_header_grp,
> -				    "header",
> -				    &uvcg_control_header_grp_type);
> -
> -	config_group_init_type_name(&uvcg_default_processing_grp,
> -			"default", &uvcg_default_processing_type);
> -	config_group_init_type_name(&uvcg_processing_grp,
> -			"processing", &uvcg_processing_grp_type);
> -	configfs_add_default_group(&uvcg_default_processing_grp,
> -			&uvcg_processing_grp);
> -
> -	config_group_init_type_name(&uvcg_default_camera_grp,
> -			"default", &uvcg_default_camera_type);
> -	config_group_init_type_name(&uvcg_camera_grp,
> -			"camera", &uvcg_camera_grp_type);
> -	configfs_add_default_group(&uvcg_default_camera_grp,
> -			&uvcg_camera_grp);
> -
> -	config_group_init_type_name(&uvcg_default_output_grp,
> -			"default", &uvcg_default_output_type);
> -	config_group_init_type_name(&uvcg_output_grp,
> -			"output", &uvcg_output_grp_type);
> -	configfs_add_default_group(&uvcg_default_output_grp,
> -			&uvcg_output_grp);
> -
> -	config_group_init_type_name(&uvcg_terminal_grp,
> -			"terminal", &uvcg_terminal_grp_type);
> -	configfs_add_default_group(&uvcg_camera_grp,
> -			&uvcg_terminal_grp);
> -	configfs_add_default_group(&uvcg_output_grp,
> -			&uvcg_terminal_grp);
> -
> -	config_group_init_type_name(&uvcg_control_class_fs_grp,
> -			"fs", &uvcg_control_class_type);
> -	config_group_init_type_name(&uvcg_control_class_ss_grp,
> -			"ss", &uvcg_control_class_type);
> -	config_group_init_type_name(&uvcg_control_class_grp,
> -			"class",
> -			&uvcg_control_class_grp_type);
> -	configfs_add_default_group(&uvcg_control_class_fs_grp,
> -			&uvcg_control_class_grp);
> -	configfs_add_default_group(&uvcg_control_class_ss_grp,
> -			&uvcg_control_class_grp);
> -
> -	config_group_init_type_name(&uvcg_control_grp,
> -			"control",
> -			&uvcg_control_grp_type);
> -	configfs_add_default_group(&uvcg_control_header_grp,
> -			&uvcg_control_grp);
> -	configfs_add_default_group(&uvcg_processing_grp,
> -			&uvcg_control_grp);
> -	configfs_add_default_group(&uvcg_terminal_grp,
> -			&uvcg_control_grp);
> -	configfs_add_default_group(&uvcg_control_class_grp,
> -			&uvcg_control_grp);
> -
> -	config_group_init_type_name(&uvcg_streaming_header_grp,
> -				    "header",
> -				    &uvcg_streaming_header_grp_type);
> -	config_group_init_type_name(&uvcg_uncompressed_grp,
> -				    "uncompressed",
> -				    &uvcg_uncompressed_grp_type);
> -	config_group_init_type_name(&uvcg_mjpeg_grp,
> -				    "mjpeg",
> -				    &uvcg_mjpeg_grp_type);
> -	config_group_init_type_name(&uvcg_default_color_matching_grp,
> -				    "default",
> -				    &uvcg_default_color_matching_type);
> -	config_group_init_type_name(&uvcg_color_matching_grp,
> -			"color_matching",
> -			&uvcg_color_matching_grp_type);
> -	configfs_add_default_group(&uvcg_default_color_matching_grp,
> -			&uvcg_color_matching_grp);
> -
> -	config_group_init_type_name(&uvcg_streaming_class_fs_grp,
> -			"fs", &uvcg_streaming_class_type);
> -	config_group_init_type_name(&uvcg_streaming_class_hs_grp,
> -			"hs", &uvcg_streaming_class_type);
> -	config_group_init_type_name(&uvcg_streaming_class_ss_grp,
> -			"ss", &uvcg_streaming_class_type);
> -	config_group_init_type_name(&uvcg_streaming_class_grp,
> -			"class", &uvcg_streaming_class_grp_type);
> -	configfs_add_default_group(&uvcg_streaming_class_fs_grp,
> -			&uvcg_streaming_class_grp);
> -	configfs_add_default_group(&uvcg_streaming_class_hs_grp,
> -			&uvcg_streaming_class_grp);
> -	configfs_add_default_group(&uvcg_streaming_class_ss_grp,
> -			&uvcg_streaming_class_grp);
> -
> -	config_group_init_type_name(&uvcg_streaming_grp,
> -			"streaming", &uvcg_streaming_grp_type);
> -	configfs_add_default_group(&uvcg_streaming_header_grp,
> -			&uvcg_streaming_grp);
> -	configfs_add_default_group(&uvcg_uncompressed_grp,
> -			&uvcg_streaming_grp);
> -	configfs_add_default_group(&uvcg_mjpeg_grp,
> -			&uvcg_streaming_grp);
> -	configfs_add_default_group(&uvcg_color_matching_grp,
> -			&uvcg_streaming_grp);
> -	configfs_add_default_group(&uvcg_streaming_class_grp,
> -			&uvcg_streaming_grp);
> -
> -	config_group_init_type_name(&opts->func_inst.group,
> -			"",
> -			&uvc_func_type);
> -	configfs_add_default_group(&uvcg_control_grp,
> -			&opts->func_inst.group);
> -	configfs_add_default_group(&uvcg_streaming_grp,
> -			&opts->func_inst.group);
> +	int ret;
>  
> -	return 0;
> +	config_group_init_type_name(&opts->func_inst.group, uvc_func_type.name,
> +				    &uvc_func_type.type);
> +
> +	ret = uvcg_config_create_children(&opts->func_inst.group,
> +					  &uvc_func_type);
> +	if (ret < 0)
> +		config_group_put(&opts->func_inst.group);
> +
> +	return ret;
>  }
>
diff mbox series

Patch

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index d8ce7868fe22..95cb1b5f5ffe 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -792,6 +792,7 @@  static struct usb_function_instance *uvc_alloc_inst(void)
 	struct uvc_output_terminal_descriptor *od;
 	struct uvc_color_matching_descriptor *md;
 	struct uvc_descriptor_header **ctl_cls;
+	int ret;
 
 	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
 	if (!opts)
@@ -868,7 +869,12 @@  static struct usb_function_instance *uvc_alloc_inst(void)
 	opts->streaming_interval = 1;
 	opts->streaming_maxpacket = 1024;
 
-	uvcg_attach_configfs(opts);
+	ret = uvcg_attach_configfs(opts);
+	if (ret < 0) {
+		kfree(opts);
+		return ERR_PTR(ret);
+	}
+
 	return &opts->func_inst;
 }
 
diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c
index 8d513cc6fb8c..ae722549eabc 100644
--- a/drivers/usb/gadget/function/uvc_configfs.c
+++ b/drivers/usb/gadget/function/uvc_configfs.c
@@ -41,6 +41,71 @@  static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item)
 			    func_inst.group);
 }
 
+struct uvcg_config_group_type {
+	struct config_item_type type;
+	const char *name;
+	const struct uvcg_config_group_type **children;
+	int (*create_children)(struct config_group *group);
+};
+
+static void uvcg_config_item_release(struct config_item *item)
+{
+	struct config_group *group = to_config_group(item);
+
+	kfree(group);
+}
+
+static struct configfs_item_operations uvcg_config_item_ops = {
+	.release	= uvcg_config_item_release,
+};
+
+static int uvcg_config_create_group(struct config_group *parent,
+				    const struct uvcg_config_group_type *type);
+
+static int uvcg_config_create_children(struct config_group *group,
+				const struct uvcg_config_group_type *type)
+{
+	const struct uvcg_config_group_type **child;
+	int ret;
+
+	if (type->create_children)
+		return type->create_children(group);
+
+	for (child = type->children; child && *child; ++child) {
+		ret = uvcg_config_create_group(group, *child);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int uvcg_config_create_group(struct config_group *parent,
+				    const struct uvcg_config_group_type *type)
+{
+	struct config_group *group;
+
+	group = kzalloc(sizeof(*group), GFP_KERNEL);
+	if (!group)
+		return -ENOMEM;
+
+	config_group_init_type_name(group, type->name, &type->type);
+	configfs_add_default_group(group, parent);
+
+	return uvcg_config_create_children(group, type);
+}
+
+static void uvcg_config_remove_children(struct config_group *group)
+{
+	struct config_group *child, *n;
+
+	list_for_each_entry_safe(child, n, &group->default_groups, group_entry) {
+		list_del(&child->group_entry);
+		uvcg_config_remove_children(child);
+		config_item_put(&child->cg_item);
+	}
+}
+
 /* -----------------------------------------------------------------------------
  * control/header/<NAME>
  * control/header
@@ -137,6 +202,7 @@  static struct configfs_attribute *uvcg_control_header_attrs[] = {
 };
 
 static const struct config_item_type uvcg_control_header_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
 	.ct_attrs	= uvcg_control_header_attrs,
 	.ct_owner	= THIS_MODULE,
 };
@@ -161,32 +227,23 @@  static struct config_item *uvcg_control_header_make(struct config_group *group,
 	return &h->item;
 }
 
-static void uvcg_control_header_drop(struct config_group *group,
-			      struct config_item *item)
-{
-	struct uvcg_control_header *h = to_uvcg_control_header(item);
-
-	kfree(h);
-}
-
-static struct config_group uvcg_control_header_grp;
-
 static struct configfs_group_operations uvcg_control_header_grp_ops = {
 	.make_item		= uvcg_control_header_make,
-	.drop_item		= uvcg_control_header_drop,
 };
 
-static const struct config_item_type uvcg_control_header_grp_type = {
-	.ct_group_ops	= &uvcg_control_header_grp_ops,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_control_header_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_control_header_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "header",
 };
 
 /* -----------------------------------------------------------------------------
  * control/processing/default
  */
 
-static struct config_group uvcg_default_processing_grp;
-
 #define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv)		\
 static ssize_t uvcg_default_processing_##cname##_show(			\
 	struct config_item *item, char *page)				\
@@ -265,27 +322,35 @@  static struct configfs_attribute *uvcg_default_processing_attrs[] = {
 	NULL,
 };
 
-static const struct config_item_type uvcg_default_processing_type = {
-	.ct_attrs	= uvcg_default_processing_attrs,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_processing_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_processing_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
 };
 
 /* -----------------------------------------------------------------------------
  * control/processing
  */
 
-static struct config_group uvcg_processing_grp;
-
-static const struct config_item_type uvcg_processing_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_processing_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "processing",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_processing_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
  * control/terminal/camera/default
  */
 
-static struct config_group uvcg_default_camera_grp;
-
 #define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv)			\
 static ssize_t uvcg_default_camera_##cname##_show(			\
 	struct config_item *item, char *page)				\
@@ -375,27 +440,35 @@  static struct configfs_attribute *uvcg_default_camera_attrs[] = {
 	NULL,
 };
 
-static const struct config_item_type uvcg_default_camera_type = {
-	.ct_attrs	= uvcg_default_camera_attrs,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_camera_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_camera_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
 };
 
 /* -----------------------------------------------------------------------------
  * control/terminal/camera
  */
 
-static struct config_group uvcg_camera_grp;
-
-static const struct config_item_type uvcg_camera_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_camera_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "camera",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_camera_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
  * control/terminal/output/default
  */
 
-static struct config_group uvcg_default_output_grp;
-
 #define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv)			\
 static ssize_t uvcg_default_output_##cname##_show(			\
 	struct config_item *item, char *page)				\
@@ -446,47 +519,68 @@  static struct configfs_attribute *uvcg_default_output_attrs[] = {
 	NULL,
 };
 
-static const struct config_item_type uvcg_default_output_type = {
-	.ct_attrs	= uvcg_default_output_attrs,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_output_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_output_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
 };
 
 /* -----------------------------------------------------------------------------
  * control/terminal/output
  */
 
-static struct config_group uvcg_output_grp;
-
-static const struct config_item_type uvcg_output_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_output_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "output",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_output_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
  * control/terminal
  */
 
-static struct config_group uvcg_terminal_grp;
-
-static const struct config_item_type uvcg_terminal_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_terminal_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "terminal",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_camera_grp_type,
+		&uvcg_output_grp_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
  * control/class/{fs|ss}
  */
 
-static struct config_group uvcg_control_class_fs_grp;
-static struct config_group uvcg_control_class_ss_grp;
+struct uvcg_control_class_group {
+	struct config_group group;
+	const char *name;
+};
 
 static inline struct uvc_descriptor_header
 **uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o)
 {
-	struct config_group *group = to_config_group(i);
+	struct uvcg_control_class_group *group =
+		container_of(i, struct uvcg_control_class_group,
+			     group.cg_item);
 
-	if (group == &uvcg_control_class_fs_grp)
+	if (!strcmp(group->name, "fs"))
 		return o->uvc_fs_control_cls;
 
-	if (group == &uvcg_control_class_ss_grp)
+	if (!strcmp(group->name, "ss"))
 		return o->uvc_ss_control_cls;
 
 	return NULL;
@@ -570,6 +664,7 @@  static void uvcg_control_class_drop_link(struct config_item *src,
 }
 
 static struct configfs_item_operations uvcg_control_class_item_ops = {
+	.release	= uvcg_config_item_release,
 	.allow_link	= uvcg_control_class_allow_link,
 	.drop_link	= uvcg_control_class_drop_link,
 };
@@ -583,20 +678,54 @@  static const struct config_item_type uvcg_control_class_type = {
  * control/class
  */
 
-static struct config_group uvcg_control_class_grp;
+static int uvcg_control_class_create_children(struct config_group *parent)
+{
+	static const char * const names[] = { "fs", "ss" };
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(names); ++i) {
+		struct uvcg_control_class_group *group;
 
-static const struct config_item_type uvcg_control_class_grp_type = {
-	.ct_owner = THIS_MODULE,
+		group = kzalloc(sizeof(*group), GFP_KERNEL);
+		if (!group)
+			return -ENOMEM;
+
+		group->name = names[i];
+
+		config_group_init_type_name(&group->group, group->name,
+					    &uvcg_control_class_type);
+		configfs_add_default_group(&group->group, parent);
+	}
+
+	return 0;
+}
+
+static const struct uvcg_config_group_type uvcg_control_class_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "class",
+	.create_children = uvcg_control_class_create_children,
 };
 
 /* -----------------------------------------------------------------------------
  * control
  */
 
-static struct config_group uvcg_control_grp;
-
-static const struct config_item_type uvcg_control_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_control_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "control",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_control_header_grp_type,
+		&uvcg_processing_grp_type,
+		&uvcg_terminal_grp_type,
+		&uvcg_control_class_grp_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
@@ -604,12 +733,9 @@  static const struct config_item_type uvcg_control_grp_type = {
  * streaming/mjpeg
  */
 
-static struct config_group uvcg_uncompressed_grp;
-static struct config_group uvcg_mjpeg_grp;
-
-static struct config_item *fmt_parent[] = {
-	&uvcg_uncompressed_grp.cg_item,
-	&uvcg_mjpeg_grp.cg_item,
+static const char * const uvcg_format_names[] = {
+	"uncompressed",
+	"mjpeg",
 };
 
 enum uvcg_format_type {
@@ -735,10 +861,22 @@  static int uvcg_streaming_header_allow_link(struct config_item *src,
 		goto out;
 	}
 
-	for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i)
-		if (target->ci_parent == fmt_parent[i])
+	/*
+	 * Linking is only allowed to direct children of the format nodes
+	 * (streaming/uncompressed or streaming/mjpeg nodes). First check that
+	 * the grand-parent of the target matches the grand-parent of the source
+	 * (the streaming node), and then verify that the target parent is a
+	 * format node.
+	 */
+	if (src->ci_parent->ci_parent != target->ci_parent->ci_parent)
+		goto out;
+
+	for (i = 0; i < ARRAY_SIZE(uvcg_format_names); ++i) {
+		if (!strcmp(target->ci_parent->ci_name, uvcg_format_names[i]))
 			break;
-	if (i == ARRAY_SIZE(fmt_parent))
+	}
+
+	if (i == ARRAY_SIZE(uvcg_format_names))
 		goto out;
 
 	target_fmt = container_of(to_config_group(target), struct uvcg_format,
@@ -798,8 +936,9 @@  static void uvcg_streaming_header_drop_link(struct config_item *src,
 }
 
 static struct configfs_item_operations uvcg_streaming_header_item_ops = {
-	.allow_link		= uvcg_streaming_header_allow_link,
-	.drop_link		= uvcg_streaming_header_drop_link,
+	.release	= uvcg_config_item_release,
+	.allow_link	= uvcg_streaming_header_allow_link,
+	.drop_link	= uvcg_streaming_header_drop_link,
 };
 
 #define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv)			\
@@ -875,24 +1014,17 @@  static struct config_item
 	return &h->item;
 }
 
-static void uvcg_streaming_header_drop(struct config_group *group,
-			      struct config_item *item)
-{
-	struct uvcg_streaming_header *h = to_uvcg_streaming_header(item);
-
-	kfree(h);
-}
-
-static struct config_group uvcg_streaming_header_grp;
-
 static struct configfs_group_operations uvcg_streaming_header_grp_ops = {
 	.make_item		= uvcg_streaming_header_make,
-	.drop_item		= uvcg_streaming_header_drop,
 };
 
-static const struct config_item_type uvcg_streaming_header_grp_type = {
-	.ct_group_ops	= &uvcg_streaming_header_grp_ops,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_streaming_header_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "header",
 };
 
 /* -----------------------------------------------------------------------------
@@ -900,6 +1032,8 @@  static const struct config_item_type uvcg_streaming_header_grp_type = {
  */
 
 struct uvcg_frame {
+	struct config_item	item;
+	enum uvcg_format_type	fmt_type;
 	struct {
 		u8	b_length;
 		u8	b_descriptor_type;
@@ -915,8 +1049,6 @@  struct uvcg_frame {
 		u8	b_frame_interval_type;
 	} __attribute__((packed)) frame;
 	u32 *dw_frame_interval;
-	enum uvcg_format_type	fmt_type;
-	struct config_item	item;
 };
 
 static struct uvcg_frame *to_uvcg_frame(struct config_item *item)
@@ -1143,6 +1275,7 @@  static struct configfs_attribute *uvcg_frame_attrs[] = {
 };
 
 static const struct config_item_type uvcg_frame_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
 	.ct_attrs	= uvcg_frame_attrs,
 	.ct_owner	= THIS_MODULE,
 };
@@ -1194,7 +1327,6 @@  static struct config_item *uvcg_frame_make(struct config_group *group,
 
 static void uvcg_frame_drop(struct config_group *group, struct config_item *item)
 {
-	struct uvcg_frame *h = to_uvcg_frame(item);
 	struct uvcg_format *fmt;
 	struct f_uvc_opts *opts;
 	struct config_item *opts_item;
@@ -1205,8 +1337,9 @@  static void uvcg_frame_drop(struct config_group *group, struct config_item *item
 	mutex_lock(&opts->lock);
 	fmt = to_uvcg_format(&group->cg_item);
 	--fmt->num_frames;
-	kfree(h);
 	mutex_unlock(&opts->lock);
+
+	config_item_put(item);
 }
 
 /* -----------------------------------------------------------------------------
@@ -1415,6 +1548,7 @@  static struct configfs_attribute *uvcg_uncompressed_attrs[] = {
 };
 
 static const struct config_item_type uvcg_uncompressed_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
 	.ct_group_ops	= &uvcg_uncompressed_group_ops,
 	.ct_attrs	= uvcg_uncompressed_attrs,
 	.ct_owner	= THIS_MODULE,
@@ -1451,22 +1585,17 @@  static struct config_group *uvcg_uncompressed_make(struct config_group *group,
 	return &h->fmt.group;
 }
 
-static void uvcg_uncompressed_drop(struct config_group *group,
-			    struct config_item *item)
-{
-	struct uvcg_uncompressed *h = to_uvcg_uncompressed(item);
-
-	kfree(h);
-}
-
 static struct configfs_group_operations uvcg_uncompressed_grp_ops = {
 	.make_group		= uvcg_uncompressed_make,
-	.drop_item		= uvcg_uncompressed_drop,
 };
 
-static const struct config_item_type uvcg_uncompressed_grp_type = {
-	.ct_group_ops	= &uvcg_uncompressed_grp_ops,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_uncompressed_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "uncompressed",
 };
 
 /* -----------------------------------------------------------------------------
@@ -1618,6 +1747,7 @@  static struct configfs_attribute *uvcg_mjpeg_attrs[] = {
 };
 
 static const struct config_item_type uvcg_mjpeg_type = {
+	.ct_item_ops	= &uvcg_config_item_ops,
 	.ct_group_ops	= &uvcg_mjpeg_group_ops,
 	.ct_attrs	= uvcg_mjpeg_attrs,
 	.ct_owner	= THIS_MODULE,
@@ -1648,30 +1778,23 @@  static struct config_group *uvcg_mjpeg_make(struct config_group *group,
 	return &h->fmt.group;
 }
 
-static void uvcg_mjpeg_drop(struct config_group *group,
-			    struct config_item *item)
-{
-	struct uvcg_mjpeg *h = to_uvcg_mjpeg(item);
-
-	kfree(h);
-}
-
 static struct configfs_group_operations uvcg_mjpeg_grp_ops = {
 	.make_group		= uvcg_mjpeg_make,
-	.drop_item		= uvcg_mjpeg_drop,
 };
 
-static const struct config_item_type uvcg_mjpeg_grp_type = {
-	.ct_group_ops	= &uvcg_mjpeg_grp_ops,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_mjpeg_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "mjpeg",
 };
 
 /* -----------------------------------------------------------------------------
  * streaming/color_matching/default
  */
 
-static struct config_group uvcg_default_color_matching_grp;
-
 #define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv)		\
 static ssize_t uvcg_default_color_matching_##cname##_show(		\
 	struct config_item *item, char *page)			\
@@ -1719,41 +1842,54 @@  static struct configfs_attribute *uvcg_default_color_matching_attrs[] = {
 	NULL,
 };
 
-static const struct config_item_type uvcg_default_color_matching_type = {
-	.ct_attrs	= uvcg_default_color_matching_attrs,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_default_color_matching_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_attrs	= uvcg_default_color_matching_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "default",
 };
 
 /* -----------------------------------------------------------------------------
  * streaming/color_matching
  */
 
-static struct config_group uvcg_color_matching_grp;
-
-static const struct config_item_type uvcg_color_matching_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_color_matching_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "color_matching",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_default_color_matching_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
  * streaming/class/{fs|hs|ss}
  */
 
-static struct config_group uvcg_streaming_class_fs_grp;
-static struct config_group uvcg_streaming_class_hs_grp;
-static struct config_group uvcg_streaming_class_ss_grp;
+struct uvcg_streaming_class_group {
+	struct config_group group;
+	const char *name;
+};
 
 static inline struct uvc_descriptor_header
 ***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o)
 {
-	struct config_group *group = to_config_group(i);
+	struct uvcg_streaming_class_group *group =
+		container_of(i, struct uvcg_streaming_class_group,
+			     group.cg_item);
 
-	if (group == &uvcg_streaming_class_fs_grp)
+	if (!strcmp(group->name, "fs"))
 		return &o->uvc_fs_streaming_cls;
 
-	if (group == &uvcg_streaming_class_hs_grp)
+	if (!strcmp(group->name, "hs"))
 		return &o->uvc_hs_streaming_cls;
 
-	if (group == &uvcg_streaming_class_ss_grp)
+	if (!strcmp(group->name, "ss"))
 		return &o->uvc_ss_streaming_cls;
 
 	return NULL;
@@ -2074,6 +2210,7 @@  static void uvcg_streaming_class_drop_link(struct config_item *src,
 }
 
 static struct configfs_item_operations uvcg_streaming_class_item_ops = {
+	.release	= uvcg_config_item_release,
 	.allow_link	= uvcg_streaming_class_allow_link,
 	.drop_link	= uvcg_streaming_class_drop_link,
 };
@@ -2087,35 +2224,71 @@  static const struct config_item_type uvcg_streaming_class_type = {
  * streaming/class
  */
 
-static struct config_group uvcg_streaming_class_grp;
+static int uvcg_streaming_class_create_children(struct config_group *parent)
+{
+	static const char * const names[] = { "fs", "hs", "ss" };
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(names); ++i) {
+		struct uvcg_streaming_class_group *group;
+
+		group = kzalloc(sizeof(*group), GFP_KERNEL);
+		if (!group)
+			return -ENOMEM;
+
+		group->name = names[i];
 
-static const struct config_item_type uvcg_streaming_class_grp_type = {
-	.ct_owner = THIS_MODULE,
+		config_group_init_type_name(&group->group, group->name,
+					    &uvcg_streaming_class_type);
+		configfs_add_default_group(&group->group, parent);
+	}
+
+	return 0;
+}
+
+static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "class",
+	.create_children = uvcg_streaming_class_create_children,
 };
 
 /* -----------------------------------------------------------------------------
  * streaming
  */
 
-static struct config_group uvcg_streaming_grp;
-
-static const struct config_item_type uvcg_streaming_grp_type = {
-	.ct_owner = THIS_MODULE,
+static const struct uvcg_config_group_type uvcg_streaming_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "streaming",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_streaming_header_grp_type,
+		&uvcg_uncompressed_grp_type,
+		&uvcg_mjpeg_grp_type,
+		&uvcg_color_matching_grp_type,
+		&uvcg_streaming_class_grp_type,
+		NULL,
+	},
 };
 
 /* -----------------------------------------------------------------------------
  * UVC function
  */
 
-static void uvc_attr_release(struct config_item *item)
+static void uvc_func_item_release(struct config_item *item)
 {
 	struct f_uvc_opts *opts = to_f_uvc_opts(item);
 
+	uvcg_config_remove_children(to_config_group(item));
 	usb_put_function_instance(&opts->func_inst);
 }
 
-static struct configfs_item_operations uvc_item_ops = {
-	.release		= uvc_attr_release,
+static struct configfs_item_operations uvc_func_item_ops = {
+	.release	= uvc_func_item_release,
 };
 
 #define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit)	\
@@ -2183,123 +2356,31 @@  static struct configfs_attribute *uvc_attrs[] = {
 	NULL,
 };
 
-static const struct config_item_type uvc_func_type = {
-	.ct_item_ops	= &uvc_item_ops,
-	.ct_attrs	= uvc_attrs,
-	.ct_owner	= THIS_MODULE,
+static const struct uvcg_config_group_type uvc_func_type = {
+	.type = {
+		.ct_item_ops	= &uvc_func_item_ops,
+		.ct_attrs	= uvc_attrs,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "",
+	.children = (const struct uvcg_config_group_type*[]) {
+		&uvcg_control_grp_type,
+		&uvcg_streaming_grp_type,
+		NULL,
+	},
 };
 
 int uvcg_attach_configfs(struct f_uvc_opts *opts)
 {
-	config_group_init_type_name(&uvcg_control_header_grp,
-				    "header",
-				    &uvcg_control_header_grp_type);
-
-	config_group_init_type_name(&uvcg_default_processing_grp,
-			"default", &uvcg_default_processing_type);
-	config_group_init_type_name(&uvcg_processing_grp,
-			"processing", &uvcg_processing_grp_type);
-	configfs_add_default_group(&uvcg_default_processing_grp,
-			&uvcg_processing_grp);
-
-	config_group_init_type_name(&uvcg_default_camera_grp,
-			"default", &uvcg_default_camera_type);
-	config_group_init_type_name(&uvcg_camera_grp,
-			"camera", &uvcg_camera_grp_type);
-	configfs_add_default_group(&uvcg_default_camera_grp,
-			&uvcg_camera_grp);
-
-	config_group_init_type_name(&uvcg_default_output_grp,
-			"default", &uvcg_default_output_type);
-	config_group_init_type_name(&uvcg_output_grp,
-			"output", &uvcg_output_grp_type);
-	configfs_add_default_group(&uvcg_default_output_grp,
-			&uvcg_output_grp);
-
-	config_group_init_type_name(&uvcg_terminal_grp,
-			"terminal", &uvcg_terminal_grp_type);
-	configfs_add_default_group(&uvcg_camera_grp,
-			&uvcg_terminal_grp);
-	configfs_add_default_group(&uvcg_output_grp,
-			&uvcg_terminal_grp);
-
-	config_group_init_type_name(&uvcg_control_class_fs_grp,
-			"fs", &uvcg_control_class_type);
-	config_group_init_type_name(&uvcg_control_class_ss_grp,
-			"ss", &uvcg_control_class_type);
-	config_group_init_type_name(&uvcg_control_class_grp,
-			"class",
-			&uvcg_control_class_grp_type);
-	configfs_add_default_group(&uvcg_control_class_fs_grp,
-			&uvcg_control_class_grp);
-	configfs_add_default_group(&uvcg_control_class_ss_grp,
-			&uvcg_control_class_grp);
-
-	config_group_init_type_name(&uvcg_control_grp,
-			"control",
-			&uvcg_control_grp_type);
-	configfs_add_default_group(&uvcg_control_header_grp,
-			&uvcg_control_grp);
-	configfs_add_default_group(&uvcg_processing_grp,
-			&uvcg_control_grp);
-	configfs_add_default_group(&uvcg_terminal_grp,
-			&uvcg_control_grp);
-	configfs_add_default_group(&uvcg_control_class_grp,
-			&uvcg_control_grp);
-
-	config_group_init_type_name(&uvcg_streaming_header_grp,
-				    "header",
-				    &uvcg_streaming_header_grp_type);
-	config_group_init_type_name(&uvcg_uncompressed_grp,
-				    "uncompressed",
-				    &uvcg_uncompressed_grp_type);
-	config_group_init_type_name(&uvcg_mjpeg_grp,
-				    "mjpeg",
-				    &uvcg_mjpeg_grp_type);
-	config_group_init_type_name(&uvcg_default_color_matching_grp,
-				    "default",
-				    &uvcg_default_color_matching_type);
-	config_group_init_type_name(&uvcg_color_matching_grp,
-			"color_matching",
-			&uvcg_color_matching_grp_type);
-	configfs_add_default_group(&uvcg_default_color_matching_grp,
-			&uvcg_color_matching_grp);
-
-	config_group_init_type_name(&uvcg_streaming_class_fs_grp,
-			"fs", &uvcg_streaming_class_type);
-	config_group_init_type_name(&uvcg_streaming_class_hs_grp,
-			"hs", &uvcg_streaming_class_type);
-	config_group_init_type_name(&uvcg_streaming_class_ss_grp,
-			"ss", &uvcg_streaming_class_type);
-	config_group_init_type_name(&uvcg_streaming_class_grp,
-			"class", &uvcg_streaming_class_grp_type);
-	configfs_add_default_group(&uvcg_streaming_class_fs_grp,
-			&uvcg_streaming_class_grp);
-	configfs_add_default_group(&uvcg_streaming_class_hs_grp,
-			&uvcg_streaming_class_grp);
-	configfs_add_default_group(&uvcg_streaming_class_ss_grp,
-			&uvcg_streaming_class_grp);
-
-	config_group_init_type_name(&uvcg_streaming_grp,
-			"streaming", &uvcg_streaming_grp_type);
-	configfs_add_default_group(&uvcg_streaming_header_grp,
-			&uvcg_streaming_grp);
-	configfs_add_default_group(&uvcg_uncompressed_grp,
-			&uvcg_streaming_grp);
-	configfs_add_default_group(&uvcg_mjpeg_grp,
-			&uvcg_streaming_grp);
-	configfs_add_default_group(&uvcg_color_matching_grp,
-			&uvcg_streaming_grp);
-	configfs_add_default_group(&uvcg_streaming_class_grp,
-			&uvcg_streaming_grp);
-
-	config_group_init_type_name(&opts->func_inst.group,
-			"",
-			&uvc_func_type);
-	configfs_add_default_group(&uvcg_control_grp,
-			&opts->func_inst.group);
-	configfs_add_default_group(&uvcg_streaming_grp,
-			&opts->func_inst.group);
+	int ret;
 
-	return 0;
+	config_group_init_type_name(&opts->func_inst.group, uvc_func_type.name,
+				    &uvc_func_type.type);
+
+	ret = uvcg_config_create_children(&opts->func_inst.group,
+					  &uvc_func_type);
+	if (ret < 0)
+		config_group_put(&opts->func_inst.group);
+
+	return ret;
 }