diff mbox series

[v2,3/9] usb: gadget: uvc: Allow definition of XUs in configfs

Message ID 20221121092517.225242-4-dan.scally@ideasonboard.com (mailing list archive)
State New, archived
Headers show
Series Add XU support to UVC Gadget | expand

Commit Message

Dan Scally Nov. 21, 2022, 9:25 a.m. UTC
The UVC gadget at present has no support for extension units. Add the
infrastructure to uvc_configfs.c that allows users to create XUs via
configfs. These will be stored in a new child of uvcg_control_grp_type
with the name "extensions".

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
Changes in v2:

	- Updated the ABI documentation with the new elements.
	- Locked the su_mutex when appropriate.

 .../ABI/testing/configfs-usb-gadget-uvc       |  28 +
 drivers/usb/gadget/function/f_uvc.c           |   9 +
 drivers/usb/gadget/function/u_uvc.h           |   7 +
 drivers/usb/gadget/function/uvc_configfs.c    | 480 ++++++++++++++++++
 drivers/usb/gadget/function/uvc_configfs.h    |  29 ++
 5 files changed, 553 insertions(+)

Comments

kernel test robot Nov. 22, 2022, 1:32 a.m. UTC | #1
Hi Daniel,

I love your patch! Perhaps something to improve:

[auto build test WARNING on usb/usb-testing]
[also build test WARNING on usb/usb-next usb/usb-linus westeri-thunderbolt/next linus/master v6.1-rc6 next-20221121]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Daniel-Scally/Add-XU-support-to-UVC-Gadget/20221121-172732
base:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
patch link:    https://lore.kernel.org/r/20221121092517.225242-4-dan.scally%40ideasonboard.com
patch subject: [PATCH v2 3/9] usb: gadget: uvc: Allow definition of XUs in configfs
reproduce:
        # https://github.com/intel-lab-lkp/linux/commit/385ec4d8ecbce4fe144f27da81f01d9c901b1b79
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Daniel-Scally/Add-XU-support-to-UVC-Gadget/20221121-172732
        git checkout 385ec4d8ecbce4fe144f27da81f01d9c901b1b79
        make menuconfig
        # enable CONFIG_COMPILE_TEST, CONFIG_WARN_MISSING_DOCUMENTS, CONFIG_WARN_ABI_ERRORS
        make htmldocs

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> Documentation/ABI/testing/configfs-usb-gadget-uvc:120: WARNING: Malformed table.

vim +120 Documentation/ABI/testing/configfs-usb-gadget-uvc

   118	
   119	What:		/config/usb-gadget/gadget/functions/uvc.name/control/extensions/name
 > 120	Date:		Nov 2022
   121	KernelVersion:	6.1
   122	Description:	Extension Unit (XU) Descriptor
   123	
   124			bLength, bUnitID and iExtension are read-only. All others are
   125			read-write.
   126	
   127			===============		========================================
   128			bLength			size of the descriptor in bytes
   129			bUnitID			non-zero ID of this unit
   130			guidExtensionCode	vendor specific code identifying the XU
   131			bNumControls		number of controls in this XU
   132			bNrInPins		number of input pins for this unit
   133			baSourceID		list of the IDs of the units or terminals
   134						to which this XU is connected
   135			bControlSize		size of the bmControls field in bytes
   136			bmControls		list of bitmaps detailing which vendor
   137						specific controls are supported
   138			iExtension		index of a string descriptor that describes
   139						this extension unit
   140			===============		========================================
   141
Laurent Pinchart Dec. 29, 2022, 1:35 a.m. UTC | #2
Hi Dan,

Thank you for the patch.

On Mon, Nov 21, 2022 at 09:25:11AM +0000, Daniel Scally wrote:
> The UVC gadget at present has no support for extension units. Add the
> infrastructure to uvc_configfs.c that allows users to create XUs via
> configfs. These will be stored in a new child of uvcg_control_grp_type
> with the name "extensions".
> 
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
> Changes in v2:
> 
> 	- Updated the ABI documentation with the new elements.
> 	- Locked the su_mutex when appropriate.
> 
>  .../ABI/testing/configfs-usb-gadget-uvc       |  28 +
>  drivers/usb/gadget/function/f_uvc.c           |   9 +
>  drivers/usb/gadget/function/u_uvc.h           |   7 +
>  drivers/usb/gadget/function/uvc_configfs.c    | 480 ++++++++++++++++++
>  drivers/usb/gadget/function/uvc_configfs.h    |  29 ++
>  5 files changed, 553 insertions(+)
> 
> diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc
> index feb3f2cc0c16..045c57e7e245 100644
> --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc
> +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc
> @@ -111,6 +111,34 @@ Description:	Default processing unit descriptors
>  		bUnitID		a non-zero id of this unit
>  		===============	========================================
>  
> +What:		/config/usb-gadget/gadget/functions/uvc.name/control/extensions
> +Date:		Nov 2022
> +KernelVersion:	6.1
> +Description:	Extension unit descriptors
> +
> +What:		/config/usb-gadget/gadget/functions/uvc.name/control/extensions/name
> +Date:		Nov 2022
> +KernelVersion:	6.1
> +Description:	Extension Unit (XU) Descriptor
> +
> +		bLength, bUnitID and iExtension are read-only. All others are
> +		read-write.
> +
> +		===============		========================================
> +		bLength			size of the descriptor in bytes
> +		bUnitID			non-zero ID of this unit
> +		guidExtensionCode	vendor specific code identifying the XU

s/vendor specific/vendor-specific/

> +		bNumControls		number of controls in this XU
> +		bNrInPins		number of input pins for this unit
> +		baSourceID		list of the IDs of the units or terminals
> +					to which this XU is connected
> +		bControlSize		size of the bmControls field in bytes
> +		bmControls		list of bitmaps detailing which vendor
> +					specific controls are supported
> +		iExtension		index of a string descriptor that describes
> +					this extension unit
> +		===============		========================================
> +
>  What:		/config/usb-gadget/gadget/functions/uvc.name/control/header
>  Date:		Dec 2014
>  KernelVersion:	4.0
> diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
> index 6e196e06181e..eca5f36dfa74 100644
> --- a/drivers/usb/gadget/function/f_uvc.c
> +++ b/drivers/usb/gadget/function/f_uvc.c
> @@ -842,6 +842,13 @@ static struct usb_function_instance *uvc_alloc_inst(void)
>  	od->bSourceID			= 2;
>  	od->iTerminal			= 0;
>  
> +	/*
> +	 * With the ability to add XUs to the UVC function graph, we need to be
> +	 * able to allocate unique unit IDs to them. The IDs are 1-based, with
> +	 * the CT, PU and OT above consuming the first 3.
> +	 */

Maybe we could use the same dynamic allocation mechanism for all units,
but this is fine for now.

> +	opts->last_unit_id		= 3;
> +
>  	md = &opts->uvc_color_matching;
>  	md->bLength			= UVC_DT_COLOR_MATCHING_SIZE;
>  	md->bDescriptorType		= USB_DT_CS_INTERFACE;
> @@ -870,6 +877,8 @@ static struct usb_function_instance *uvc_alloc_inst(void)
>  	opts->ss_control =
>  		(const struct uvc_descriptor_header * const *)ctl_cls;
>  
> +	INIT_LIST_HEAD(&opts->extension_units);
> +
>  	opts->streaming_interval = 1;
>  	opts->streaming_maxpacket = 1024;
>  	snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera");
> diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h
> index 24b8681b0d6f..5119cfe5ee4e 100644
> --- a/drivers/usb/gadget/function/u_uvc.h
> +++ b/drivers/usb/gadget/function/u_uvc.h
> @@ -28,6 +28,7 @@ struct f_uvc_opts {
>  	unsigned int					control_interface;
>  	unsigned int					streaming_interface;
>  	char						function_name[32];
> +	unsigned int					last_unit_id;
>  
>  	/*
>  	 * Control descriptors array pointers for full-/high-speed and
> @@ -64,6 +65,12 @@ struct f_uvc_opts {
>  	struct uvc_descriptor_header			*uvc_fs_control_cls[5];
>  	struct uvc_descriptor_header			*uvc_ss_control_cls[5];
>  
> +	/*
> +	 * Control descriptors for extension units. There could be any number
> +	 * of these, including none at all.
> +	 */
> +	struct list_head				extension_units;
> +
>  	/*
>  	 * Streaming descriptors for full-speed, high-speed and super-speed.
>  	 * Used by configfs only, must not be touched by legacy gadgets. The
> diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c
> index d542dd251633..0a69eb6cf221 100644
> --- a/drivers/usb/gadget/function/uvc_configfs.c
> +++ b/drivers/usb/gadget/function/uvc_configfs.c
> @@ -646,8 +646,487 @@ static int __uvcg_iter_item_entries_u##bits(const char *page, size_t len,\
>  	return 0;							\
>  }
>  
> +UVCG_ITEM_ENTRY_FUNCS(8)
>  UVCG_ITEM_ENTRY_FUNCS(32)
>  
> +/* -----------------------------------------------------------------------------
> + * control/extensions
> + */
> +
> +#define UVCG_EXTENSION_ATTR(cname, aname, ro...)			\
> +static ssize_t uvcg_extension_##cname##_show(struct config_item *item,	\
> +					     char *page)		\
> +{									\
> +	struct config_group *group = to_config_group(item->ci_parent);	\
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;		\
> +	struct uvcg_extension *xu = to_uvcg_extension(item);		\
> +	struct config_item *opts_item;					\
> +	struct f_uvc_opts *opts;					\
> +	int ret;							\
> +									\
> +	mutex_lock(su_mutex);						\
> +									\
> +	opts_item = item->ci_parent->ci_parent->ci_parent;		\
> +	opts = to_f_uvc_opts(opts_item);				\
> +									\
> +	mutex_lock(&opts->lock);					\
> +	ret = sprintf(page, "%u\n", xu->desc.aname);			\
> +	mutex_unlock(&opts->lock);					\
> +									\
> +	mutex_unlock(su_mutex);						\
> +									\
> +	return ret;							\
> +}									\

We gace soooooo much boilerplate code related to attributes. One of
these days it would be nice to try and simplify all that.

> +UVC_ATTR##ro(uvcg_extension_, cname, aname)
> +
> +UVCG_EXTENSION_ATTR(b_length, bLength, _RO);
> +UVCG_EXTENSION_ATTR(b_unit_id, bUnitID, _RO);
> +UVCG_EXTENSION_ATTR(i_extension, iExtension, _RO);
> +
> +static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item,
> +						   const char *page, size_t len)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	int ret;
> +	u8 num;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	ret = kstrtou8(page, 0, &num);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&opts->lock);
> +	xu->desc.bNumControls = num;
> +	mutex_unlock(&opts->lock);
> +
> +	mutex_unlock(su_mutex);
> +
> +	return len;
> +}
> +UVCG_EXTENSION_ATTR(b_num_controls, bNumControls);
> +
> +/*
> + * In addition to storing bNrInPins, this function needs to realloc the
> + * memory for the baSourceID array and additionally expand bLength.
> + */
> +static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item,
> +						 const char *page, size_t len)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	void *tmp_buf;
> +	int ret;
> +	u8 num;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	ret = kstrtou8(page, 0, &num);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&opts->lock);
> +
> +	if (num == xu->desc.bNrInPins) {
> +		ret = len;
> +		goto unlock;
> +	}
> +
> +	tmp_buf = krealloc_array(xu->desc.baSourceID, num, sizeof(u8),
> +				 GFP_KERNEL);
> +	if (!tmp_buf) {
> +		ret = -ENOMEM;
> +		goto unlock;
> +	}
> +
> +	if (num >= xu->desc.bNrInPins)
> +		memset(tmp_buf + xu->desc.bNrInPins, 0,
> +		       (num - xu->desc.bNrInPins) * sizeof(u8));

You could also pass GFP_KERNEL | __GFP_ZERO to krealloc_array() and drop
this. This applies to the next function too.

> +
> +	xu->desc.baSourceID = tmp_buf;
> +	xu->desc.bNrInPins = num;
> +	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;

A static inline helper to recompute bLength would be nice.

> +
> +	ret = len;
> +
> +unlock:
> +	mutex_unlock(&opts->lock);
> +	mutex_unlock(su_mutex);
> +	return ret;
> +}
> +UVCG_EXTENSION_ATTR(b_nr_in_pins, bNrInPins);
> +
> +/*
> + * In addition to storing bControlSize, this function needs to realloc the
> + * memory for the bmControls array and additionally expand bLength.
> + */
> +static ssize_t uvcg_extension_b_control_size_store(struct config_item *item,
> +						   const char *page, size_t len)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	void *tmp_buf;
> +	int ret;
> +	u8 num;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	ret = kstrtou8(page, 0, &num);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&opts->lock);
> +
> +	if (num == xu->desc.bControlSize) {
> +		ret = len;
> +		goto unlock;
> +	}
> +
> +	tmp_buf = krealloc_array(xu->desc.bmControls, num, sizeof(u8),
> +				 GFP_KERNEL);
> +	if (!tmp_buf) {
> +		ret = -ENOMEM;
> +		goto unlock;
> +	}
> +
> +	if (num >= xu->desc.bControlSize)
> +		memset(tmp_buf + xu->desc.bControlSize, 0,
> +		       (num - xu->desc.bControlSize) * sizeof(u8));
> +
> +	xu->desc.bmControls = tmp_buf;
> +	xu->desc.bControlSize = num;
> +	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
> +
> +	ret = len;
> +
> +unlock:
> +	mutex_unlock(&opts->lock);
> +	mutex_unlock(su_mutex);
> +	return ret;
> +}
> +
> +UVCG_EXTENSION_ATTR(b_control_size, bControlSize);
> +
> +static ssize_t uvcg_extension_guid_extension_code_show(struct config_item *item,
> +						       char *page)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +	memcpy(page, xu->desc.guidExtensionCode, sizeof(xu->desc.guidExtensionCode));
> +	mutex_unlock(&opts->lock);
> +
> +	mutex_unlock(su_mutex);
> +
> +	return sizeof(xu->desc.guidExtensionCode);
> +}
> +
> +static ssize_t uvcg_extension_guid_extension_code_store(struct config_item *item,
> +							const char *page, size_t len)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	int ret;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +	memcpy(xu->desc.guidExtensionCode, page,
> +	       min(sizeof(xu->desc.guidExtensionCode), len));
> +	mutex_unlock(&opts->lock);
> +
> +	mutex_unlock(su_mutex);
> +
> +	ret = sizeof(xu->desc.guidExtensionCode);
> +
> +	return ret;
> +}
> +
> +UVC_ATTR(uvcg_extension_, guid_extension_code, guidExtensionCode);
> +
> +static ssize_t uvcg_extension_ba_source_id_show(struct config_item *item,
> +						char *page)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	char *pg = page;
> +	int ret, i;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +	for (ret = 0, i = 0; i < xu->desc.bNrInPins; ++i) {
> +		ret += sprintf(pg, "%u\n", xu->desc.baSourceID[i]);
> +		pg = page + ret;
> +	}
> +	mutex_unlock(&opts->lock);
> +
> +	mutex_unlock(su_mutex);
> +
> +	return ret;
> +}
> +
> +static ssize_t uvcg_extension_ba_source_id_store(struct config_item *item,
> +						 const char *page, size_t len)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	u8 *source_ids, *tmp;
> +	int ret, n = 0;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +
> +	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_count_item_entries, &n);
> +	if (ret)
> +		goto unlock;
> +
> +	tmp = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL);

tmp is usually a bad name. iter would be better. Bonus points for
patching uvcg_frame_dw_frame_interval_store() accordingly.

I'm also thinking that kcalloc() may not be that useful when the second
argument is 1 :-) It's fine though, conceptually that's the right API.

> +	if (!source_ids) {
> +		ret = -ENOMEM;
> +		goto unlock;
> +	}
> +
> +	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_fill_item_entries_u8, &tmp);
> +	if (ret) {
> +		kfree(source_ids);
> +		goto unlock;
> +	}
> +
> +	kfree(xu->desc.baSourceID);
> +	xu->desc.baSourceID = source_ids;
> +	xu->desc.bNrInPins = n;
> +	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
> +
> +	ret = len;
> +
> +unlock:
> +	mutex_unlock(&opts->lock);
> +	mutex_unlock(su_mutex);
> +	return ret;
> +}
> +UVC_ATTR(uvcg_extension_, ba_source_id, baSourceID);
> +
> +static ssize_t uvcg_extension_bm_controls_show(struct config_item *item,
> +					       char *page)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	char *pg = page;
> +	int ret, i;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +	for (ret = 0, i = 0; i < xu->desc.bControlSize; ++i) {
> +		ret += sprintf(pg, "0x%02x\n", xu->desc.bmControls[i]);
> +		pg = page + ret;
> +	}
> +	mutex_unlock(&opts->lock);
> +
> +	mutex_unlock(su_mutex);
> +
> +	return ret;
> +}
> +
> +static ssize_t uvcg_extension_bm_controls_store(struct config_item *item,
> +						const char *page, size_t len)
> +{
> +	struct config_group *group = to_config_group(item->ci_parent);
> +	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
> +	struct uvcg_extension *xu = to_uvcg_extension(item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +	u8 *bm_controls, *tmp;
> +	int ret, n = 0;
> +
> +	mutex_lock(su_mutex);
> +
> +	opts_item = item->ci_parent->ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +
> +	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_count_item_entries, &n);
> +	if (ret)
> +		goto unlock;
> +
> +	tmp = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL);
> +	if (!bm_controls) {
> +		ret = -ENOMEM;
> +		goto unlock;
> +	}
> +
> +	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_fill_item_entries_u8, &tmp);
> +	if (ret) {
> +		kfree(bm_controls);
> +		goto unlock;
> +	}
> +
> +	kfree(xu->desc.bmControls);
> +	xu->desc.bmControls = bm_controls;
> +	xu->desc.bControlSize = n;
> +	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
> +
> +	ret = len;
> +
> +unlock:
> +	mutex_unlock(&opts->lock);
> +	mutex_unlock(su_mutex);
> +	return ret;
> +}
> +
> +UVC_ATTR(uvcg_extension_, bm_controls, bmControls);
> +
> +static struct configfs_attribute *uvcg_extension_attrs[] = {
> +	&uvcg_extension_attr_b_length,
> +	&uvcg_extension_attr_b_unit_id,
> +	&uvcg_extension_attr_b_num_controls,
> +	&uvcg_extension_attr_b_nr_in_pins,
> +	&uvcg_extension_attr_b_control_size,
> +	&uvcg_extension_attr_guid_extension_code,
> +	&uvcg_extension_attr_ba_source_id,
> +	&uvcg_extension_attr_bm_controls,
> +	&uvcg_extension_attr_i_extension,
> +	NULL,
> +};
> +
> +static void uvcg_extension_release(struct config_item *item)
> +{
> +	struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item);
> +
> +	kfree(xu);
> +}
> +
> +static struct configfs_item_operations uvcg_extension_item_ops = {
> +	.release	= uvcg_extension_release,
> +};
> +
> +static const struct config_item_type uvcg_extension_type = {
> +	.ct_item_ops	= &uvcg_extension_item_ops,
> +	.ct_attrs	= uvcg_extension_attrs,
> +	.ct_owner	= THIS_MODULE,
> +};
> +
> +static void uvcg_extension_drop(struct config_group *group, struct config_item *item)
> +{
> +	struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item);
> +	struct config_item *opts_item;
> +	struct f_uvc_opts *opts;
> +
> +	opts_item = group->cg_item.ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +
> +	config_item_put(item);
> +	list_del(&xu->list);
> +	kfree(xu->desc.baSourceID);
> +	kfree(xu->desc.bmControls);
> +
> +	mutex_unlock(&opts->lock);
> +}
> +
> +static struct config_item *uvcg_extension_make(struct config_group *group, const char *name)
> +{
> +	struct config_item *opts_item;
> +	struct uvcg_extension *xu;
> +	struct f_uvc_opts *opts;
> +
> +	opts_item = group->cg_item.ci_parent->ci_parent;
> +	opts = to_f_uvc_opts(opts_item);
> +
> +	mutex_lock(&opts->lock);
> +
> +	xu = kzalloc(sizeof(*xu), GFP_KERNEL);
> +	if (!xu)
> +		return ERR_PTR(-ENOMEM);

You're returning without unlocking. Move the allocation above the lock
to fix this.

> +
> +	xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(0, 0);
> +	xu->desc.bDescriptorType = USB_DT_CS_INTERFACE;
> +	xu->desc.bDescriptorSubType = UVC_VC_EXTENSION_UNIT;
> +	xu->desc.bUnitID = ++opts->last_unit_id;
> +	xu->desc.bNumControls = 0;
> +	xu->desc.bNrInPins = 0;
> +	xu->desc.baSourceID = NULL;
> +	xu->desc.bControlSize = 0;
> +	xu->desc.bmControls = NULL;
> +
> +	config_item_init_type_name(&xu->item, name, &uvcg_extension_type);

You could move all of this but the bUnitID initialization above the
mutex_lock() call too.

> +	list_add_tail(&xu->list, &opts->extension_units);
> +
> +	mutex_unlock(&opts->lock);
> +
> +	return &xu->item;
> +}
> +
> +static struct configfs_group_operations uvcg_extensions_grp_ops = {
> +	.make_item	= uvcg_extension_make,
> +	.drop_item	= uvcg_extension_drop,
> +};
> +
> +static const struct uvcg_config_group_type uvcg_extensions_grp_type = {
> +	.type = {
> +		.ct_item_ops	= &uvcg_config_item_ops,
> +		.ct_group_ops	= &uvcg_extensions_grp_ops,
> +		.ct_owner	= THIS_MODULE,
> +	},
> +	.name = "extensions",
> +};
> +
>  /* -----------------------------------------------------------------------------
>   * control/class/{fs|ss}
>   */
> @@ -842,6 +1321,7 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = {
>  		&uvcg_processing_grp_type,
>  		&uvcg_terminal_grp_type,
>  		&uvcg_control_class_grp_type,
> +		&uvcg_extensions_grp_type,
>  		NULL,
>  	},
>  };
> diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h
> index ad2ec8c4c78c..c9a4182fb26f 100644
> --- a/drivers/usb/gadget/function/uvc_configfs.h
> +++ b/drivers/usb/gadget/function/uvc_configfs.h
> @@ -132,6 +132,35 @@ static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
>  	return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt);
>  }
>  
> +/* -----------------------------------------------------------------------------
> + * control/extensions/<NAME>
> + */
> +
> +struct uvcg_extension_unit_descriptor {
> +	u8 bLength;
> +	u8 bDescriptorType;
> +	u8 bDescriptorSubType;
> +	u8 bUnitID;
> +	u8 guidExtensionCode[16];
> +	u8 bNumControls;
> +	u8 bNrInPins;
> +	u8 *baSourceID;
> +	u8 bControlSize;
> +	u8 *bmControls;
> +	u8 iExtension;
> +} __packed;
> +
> +struct uvcg_extension {
> +	struct config_item item;
> +	struct list_head list;
> +	struct uvcg_extension_unit_descriptor desc;
> +};
> +
> +static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item)
> +{
> +	return container_of(item, struct uvcg_extension, item);
> +}
> +
>  int uvcg_attach_configfs(struct f_uvc_opts *opts);
>  
>  #endif /* UVC_CONFIGFS_H */
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc
index feb3f2cc0c16..045c57e7e245 100644
--- a/Documentation/ABI/testing/configfs-usb-gadget-uvc
+++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc
@@ -111,6 +111,34 @@  Description:	Default processing unit descriptors
 		bUnitID		a non-zero id of this unit
 		===============	========================================
 
+What:		/config/usb-gadget/gadget/functions/uvc.name/control/extensions
+Date:		Nov 2022
+KernelVersion:	6.1
+Description:	Extension unit descriptors
+
+What:		/config/usb-gadget/gadget/functions/uvc.name/control/extensions/name
+Date:		Nov 2022
+KernelVersion:	6.1
+Description:	Extension Unit (XU) Descriptor
+
+		bLength, bUnitID and iExtension are read-only. All others are
+		read-write.
+
+		===============		========================================
+		bLength			size of the descriptor in bytes
+		bUnitID			non-zero ID of this unit
+		guidExtensionCode	vendor specific code identifying the XU
+		bNumControls		number of controls in this XU
+		bNrInPins		number of input pins for this unit
+		baSourceID		list of the IDs of the units or terminals
+					to which this XU is connected
+		bControlSize		size of the bmControls field in bytes
+		bmControls		list of bitmaps detailing which vendor
+					specific controls are supported
+		iExtension		index of a string descriptor that describes
+					this extension unit
+		===============		========================================
+
 What:		/config/usb-gadget/gadget/functions/uvc.name/control/header
 Date:		Dec 2014
 KernelVersion:	4.0
diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 6e196e06181e..eca5f36dfa74 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -842,6 +842,13 @@  static struct usb_function_instance *uvc_alloc_inst(void)
 	od->bSourceID			= 2;
 	od->iTerminal			= 0;
 
+	/*
+	 * With the ability to add XUs to the UVC function graph, we need to be
+	 * able to allocate unique unit IDs to them. The IDs are 1-based, with
+	 * the CT, PU and OT above consuming the first 3.
+	 */
+	opts->last_unit_id		= 3;
+
 	md = &opts->uvc_color_matching;
 	md->bLength			= UVC_DT_COLOR_MATCHING_SIZE;
 	md->bDescriptorType		= USB_DT_CS_INTERFACE;
@@ -870,6 +877,8 @@  static struct usb_function_instance *uvc_alloc_inst(void)
 	opts->ss_control =
 		(const struct uvc_descriptor_header * const *)ctl_cls;
 
+	INIT_LIST_HEAD(&opts->extension_units);
+
 	opts->streaming_interval = 1;
 	opts->streaming_maxpacket = 1024;
 	snprintf(opts->function_name, sizeof(opts->function_name), "UVC Camera");
diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h
index 24b8681b0d6f..5119cfe5ee4e 100644
--- a/drivers/usb/gadget/function/u_uvc.h
+++ b/drivers/usb/gadget/function/u_uvc.h
@@ -28,6 +28,7 @@  struct f_uvc_opts {
 	unsigned int					control_interface;
 	unsigned int					streaming_interface;
 	char						function_name[32];
+	unsigned int					last_unit_id;
 
 	/*
 	 * Control descriptors array pointers for full-/high-speed and
@@ -64,6 +65,12 @@  struct f_uvc_opts {
 	struct uvc_descriptor_header			*uvc_fs_control_cls[5];
 	struct uvc_descriptor_header			*uvc_ss_control_cls[5];
 
+	/*
+	 * Control descriptors for extension units. There could be any number
+	 * of these, including none at all.
+	 */
+	struct list_head				extension_units;
+
 	/*
 	 * Streaming descriptors for full-speed, high-speed and super-speed.
 	 * Used by configfs only, must not be touched by legacy gadgets. The
diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c
index d542dd251633..0a69eb6cf221 100644
--- a/drivers/usb/gadget/function/uvc_configfs.c
+++ b/drivers/usb/gadget/function/uvc_configfs.c
@@ -646,8 +646,487 @@  static int __uvcg_iter_item_entries_u##bits(const char *page, size_t len,\
 	return 0;							\
 }
 
+UVCG_ITEM_ENTRY_FUNCS(8)
 UVCG_ITEM_ENTRY_FUNCS(32)
 
+/* -----------------------------------------------------------------------------
+ * control/extensions
+ */
+
+#define UVCG_EXTENSION_ATTR(cname, aname, ro...)			\
+static ssize_t uvcg_extension_##cname##_show(struct config_item *item,	\
+					     char *page)		\
+{									\
+	struct config_group *group = to_config_group(item->ci_parent);	\
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;		\
+	struct uvcg_extension *xu = to_uvcg_extension(item);		\
+	struct config_item *opts_item;					\
+	struct f_uvc_opts *opts;					\
+	int ret;							\
+									\
+	mutex_lock(su_mutex);						\
+									\
+	opts_item = item->ci_parent->ci_parent->ci_parent;		\
+	opts = to_f_uvc_opts(opts_item);				\
+									\
+	mutex_lock(&opts->lock);					\
+	ret = sprintf(page, "%u\n", xu->desc.aname);			\
+	mutex_unlock(&opts->lock);					\
+									\
+	mutex_unlock(su_mutex);						\
+									\
+	return ret;							\
+}									\
+UVC_ATTR##ro(uvcg_extension_, cname, aname)
+
+UVCG_EXTENSION_ATTR(b_length, bLength, _RO);
+UVCG_EXTENSION_ATTR(b_unit_id, bUnitID, _RO);
+UVCG_EXTENSION_ATTR(i_extension, iExtension, _RO);
+
+static ssize_t uvcg_extension_b_num_controls_store(struct config_item *item,
+						   const char *page, size_t len)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	int ret;
+	u8 num;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		return ret;
+
+	mutex_lock(&opts->lock);
+	xu->desc.bNumControls = num;
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return len;
+}
+UVCG_EXTENSION_ATTR(b_num_controls, bNumControls);
+
+/*
+ * In addition to storing bNrInPins, this function needs to realloc the
+ * memory for the baSourceID array and additionally expand bLength.
+ */
+static ssize_t uvcg_extension_b_nr_in_pins_store(struct config_item *item,
+						 const char *page, size_t len)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	void *tmp_buf;
+	int ret;
+	u8 num;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		return ret;
+
+	mutex_lock(&opts->lock);
+
+	if (num == xu->desc.bNrInPins) {
+		ret = len;
+		goto unlock;
+	}
+
+	tmp_buf = krealloc_array(xu->desc.baSourceID, num, sizeof(u8),
+				 GFP_KERNEL);
+	if (!tmp_buf) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	if (num >= xu->desc.bNrInPins)
+		memset(tmp_buf + xu->desc.bNrInPins, 0,
+		       (num - xu->desc.bNrInPins) * sizeof(u8));
+
+	xu->desc.baSourceID = tmp_buf;
+	xu->desc.bNrInPins = num;
+	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
+
+	ret = len;
+
+unlock:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+UVCG_EXTENSION_ATTR(b_nr_in_pins, bNrInPins);
+
+/*
+ * In addition to storing bControlSize, this function needs to realloc the
+ * memory for the bmControls array and additionally expand bLength.
+ */
+static ssize_t uvcg_extension_b_control_size_store(struct config_item *item,
+						   const char *page, size_t len)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	void *tmp_buf;
+	int ret;
+	u8 num;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	ret = kstrtou8(page, 0, &num);
+	if (ret)
+		return ret;
+
+	mutex_lock(&opts->lock);
+
+	if (num == xu->desc.bControlSize) {
+		ret = len;
+		goto unlock;
+	}
+
+	tmp_buf = krealloc_array(xu->desc.bmControls, num, sizeof(u8),
+				 GFP_KERNEL);
+	if (!tmp_buf) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	if (num >= xu->desc.bControlSize)
+		memset(tmp_buf + xu->desc.bControlSize, 0,
+		       (num - xu->desc.bControlSize) * sizeof(u8));
+
+	xu->desc.bmControls = tmp_buf;
+	xu->desc.bControlSize = num;
+	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
+
+	ret = len;
+
+unlock:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+UVCG_EXTENSION_ATTR(b_control_size, bControlSize);
+
+static ssize_t uvcg_extension_guid_extension_code_show(struct config_item *item,
+						       char *page)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	memcpy(page, xu->desc.guidExtensionCode, sizeof(xu->desc.guidExtensionCode));
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return sizeof(xu->desc.guidExtensionCode);
+}
+
+static ssize_t uvcg_extension_guid_extension_code_store(struct config_item *item,
+							const char *page, size_t len)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	int ret;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	memcpy(xu->desc.guidExtensionCode, page,
+	       min(sizeof(xu->desc.guidExtensionCode), len));
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	ret = sizeof(xu->desc.guidExtensionCode);
+
+	return ret;
+}
+
+UVC_ATTR(uvcg_extension_, guid_extension_code, guidExtensionCode);
+
+static ssize_t uvcg_extension_ba_source_id_show(struct config_item *item,
+						char *page)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	char *pg = page;
+	int ret, i;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	for (ret = 0, i = 0; i < xu->desc.bNrInPins; ++i) {
+		ret += sprintf(pg, "%u\n", xu->desc.baSourceID[i]);
+		pg = page + ret;
+	}
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return ret;
+}
+
+static ssize_t uvcg_extension_ba_source_id_store(struct config_item *item,
+						 const char *page, size_t len)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	u8 *source_ids, *tmp;
+	int ret, n = 0;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+
+	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_count_item_entries, &n);
+	if (ret)
+		goto unlock;
+
+	tmp = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL);
+	if (!source_ids) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_fill_item_entries_u8, &tmp);
+	if (ret) {
+		kfree(source_ids);
+		goto unlock;
+	}
+
+	kfree(xu->desc.baSourceID);
+	xu->desc.baSourceID = source_ids;
+	xu->desc.bNrInPins = n;
+	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
+
+	ret = len;
+
+unlock:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+UVC_ATTR(uvcg_extension_, ba_source_id, baSourceID);
+
+static ssize_t uvcg_extension_bm_controls_show(struct config_item *item,
+					       char *page)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	char *pg = page;
+	int ret, i;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+	for (ret = 0, i = 0; i < xu->desc.bControlSize; ++i) {
+		ret += sprintf(pg, "0x%02x\n", xu->desc.bmControls[i]);
+		pg = page + ret;
+	}
+	mutex_unlock(&opts->lock);
+
+	mutex_unlock(su_mutex);
+
+	return ret;
+}
+
+static ssize_t uvcg_extension_bm_controls_store(struct config_item *item,
+						const char *page, size_t len)
+{
+	struct config_group *group = to_config_group(item->ci_parent);
+	struct mutex *su_mutex = &group->cg_subsys->su_mutex;
+	struct uvcg_extension *xu = to_uvcg_extension(item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+	u8 *bm_controls, *tmp;
+	int ret, n = 0;
+
+	mutex_lock(su_mutex);
+
+	opts_item = item->ci_parent->ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+
+	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_count_item_entries, &n);
+	if (ret)
+		goto unlock;
+
+	tmp = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL);
+	if (!bm_controls) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	ret = __uvcg_iter_item_entries_u8(page, len, __uvcg_fill_item_entries_u8, &tmp);
+	if (ret) {
+		kfree(bm_controls);
+		goto unlock;
+	}
+
+	kfree(xu->desc.bmControls);
+	xu->desc.bmControls = bm_controls;
+	xu->desc.bControlSize = n;
+	xu->desc.bLength = 24 + xu->desc.bNrInPins + xu->desc.bControlSize;
+
+	ret = len;
+
+unlock:
+	mutex_unlock(&opts->lock);
+	mutex_unlock(su_mutex);
+	return ret;
+}
+
+UVC_ATTR(uvcg_extension_, bm_controls, bmControls);
+
+static struct configfs_attribute *uvcg_extension_attrs[] = {
+	&uvcg_extension_attr_b_length,
+	&uvcg_extension_attr_b_unit_id,
+	&uvcg_extension_attr_b_num_controls,
+	&uvcg_extension_attr_b_nr_in_pins,
+	&uvcg_extension_attr_b_control_size,
+	&uvcg_extension_attr_guid_extension_code,
+	&uvcg_extension_attr_ba_source_id,
+	&uvcg_extension_attr_bm_controls,
+	&uvcg_extension_attr_i_extension,
+	NULL,
+};
+
+static void uvcg_extension_release(struct config_item *item)
+{
+	struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item);
+
+	kfree(xu);
+}
+
+static struct configfs_item_operations uvcg_extension_item_ops = {
+	.release	= uvcg_extension_release,
+};
+
+static const struct config_item_type uvcg_extension_type = {
+	.ct_item_ops	= &uvcg_extension_item_ops,
+	.ct_attrs	= uvcg_extension_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void uvcg_extension_drop(struct config_group *group, struct config_item *item)
+{
+	struct uvcg_extension *xu = container_of(item, struct uvcg_extension, item);
+	struct config_item *opts_item;
+	struct f_uvc_opts *opts;
+
+	opts_item = group->cg_item.ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+
+	config_item_put(item);
+	list_del(&xu->list);
+	kfree(xu->desc.baSourceID);
+	kfree(xu->desc.bmControls);
+
+	mutex_unlock(&opts->lock);
+}
+
+static struct config_item *uvcg_extension_make(struct config_group *group, const char *name)
+{
+	struct config_item *opts_item;
+	struct uvcg_extension *xu;
+	struct f_uvc_opts *opts;
+
+	opts_item = group->cg_item.ci_parent->ci_parent;
+	opts = to_f_uvc_opts(opts_item);
+
+	mutex_lock(&opts->lock);
+
+	xu = kzalloc(sizeof(*xu), GFP_KERNEL);
+	if (!xu)
+		return ERR_PTR(-ENOMEM);
+
+	xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(0, 0);
+	xu->desc.bDescriptorType = USB_DT_CS_INTERFACE;
+	xu->desc.bDescriptorSubType = UVC_VC_EXTENSION_UNIT;
+	xu->desc.bUnitID = ++opts->last_unit_id;
+	xu->desc.bNumControls = 0;
+	xu->desc.bNrInPins = 0;
+	xu->desc.baSourceID = NULL;
+	xu->desc.bControlSize = 0;
+	xu->desc.bmControls = NULL;
+
+	config_item_init_type_name(&xu->item, name, &uvcg_extension_type);
+	list_add_tail(&xu->list, &opts->extension_units);
+
+	mutex_unlock(&opts->lock);
+
+	return &xu->item;
+}
+
+static struct configfs_group_operations uvcg_extensions_grp_ops = {
+	.make_item	= uvcg_extension_make,
+	.drop_item	= uvcg_extension_drop,
+};
+
+static const struct uvcg_config_group_type uvcg_extensions_grp_type = {
+	.type = {
+		.ct_item_ops	= &uvcg_config_item_ops,
+		.ct_group_ops	= &uvcg_extensions_grp_ops,
+		.ct_owner	= THIS_MODULE,
+	},
+	.name = "extensions",
+};
+
 /* -----------------------------------------------------------------------------
  * control/class/{fs|ss}
  */
@@ -842,6 +1321,7 @@  static const struct uvcg_config_group_type uvcg_control_grp_type = {
 		&uvcg_processing_grp_type,
 		&uvcg_terminal_grp_type,
 		&uvcg_control_class_grp_type,
+		&uvcg_extensions_grp_type,
 		NULL,
 	},
 };
diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h
index ad2ec8c4c78c..c9a4182fb26f 100644
--- a/drivers/usb/gadget/function/uvc_configfs.h
+++ b/drivers/usb/gadget/function/uvc_configfs.h
@@ -132,6 +132,35 @@  static inline struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
 	return container_of(to_uvcg_format(item), struct uvcg_mjpeg, fmt);
 }
 
+/* -----------------------------------------------------------------------------
+ * control/extensions/<NAME>
+ */
+
+struct uvcg_extension_unit_descriptor {
+	u8 bLength;
+	u8 bDescriptorType;
+	u8 bDescriptorSubType;
+	u8 bUnitID;
+	u8 guidExtensionCode[16];
+	u8 bNumControls;
+	u8 bNrInPins;
+	u8 *baSourceID;
+	u8 bControlSize;
+	u8 *bmControls;
+	u8 iExtension;
+} __packed;
+
+struct uvcg_extension {
+	struct config_item item;
+	struct list_head list;
+	struct uvcg_extension_unit_descriptor desc;
+};
+
+static inline struct uvcg_extension *to_uvcg_extension(struct config_item *item)
+{
+	return container_of(item, struct uvcg_extension, item);
+}
+
 int uvcg_attach_configfs(struct f_uvc_opts *opts);
 
 #endif /* UVC_CONFIGFS_H */