usb: gadget: uvc: Allow definition of XUs in configfs

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".

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Link: https://lore.kernel.org/r/20230206161802.892954-4-dan.scally@ideasonboard.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Daniel Scally 2023-02-06 16:17:54 +00:00 committed by Greg Kroah-Hartman
parent 0df28607c5
commit 0525210c98
5 changed files with 553 additions and 0 deletions

View File

@ -113,6 +113,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

View File

@ -865,6 +865,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;
/* Prepare fs control class descriptors for configfs-based gadgets */
ctl_cls = opts->uvc_fs_control_cls;
ctl_cls[0] = NULL; /* assigned elsewhere by configfs */
@ -885,6 +892,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");

View File

@ -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;
bool enable_interrupt_ep;
@ -65,6 +66,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

View File

@ -662,6 +662,485 @@ static const struct uvcg_config_group_type uvcg_terminal_grp_type = {
},
};
/* -----------------------------------------------------------------------------
* 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 | __GFP_ZERO);
if (!tmp_buf) {
ret = -ENOMEM;
goto unlock;
}
xu->desc.baSourceID = tmp_buf;
xu->desc.bNrInPins = num;
xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(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 | __GFP_ZERO);
if (!tmp_buf) {
ret = -ENOMEM;
goto unlock;
}
xu->desc.bmControls = tmp_buf;
xu->desc.bControlSize = num;
xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(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, *iter;
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(page, len, __uvcg_count_item_entries, &n,
sizeof(u8));
if (ret)
goto unlock;
iter = source_ids = kcalloc(n, sizeof(u8), GFP_KERNEL);
if (!source_ids) {
ret = -ENOMEM;
goto unlock;
}
ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter,
sizeof(u8));
if (ret) {
kfree(source_ids);
goto unlock;
}
kfree(xu->desc.baSourceID);
xu->desc.baSourceID = source_ids;
xu->desc.bNrInPins = n;
xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(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, *iter;
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(page, len, __uvcg_count_item_entries, &n,
sizeof(u8));
if (ret)
goto unlock;
iter = bm_controls = kcalloc(n, sizeof(u8), GFP_KERNEL);
if (!bm_controls) {
ret = -ENOMEM;
goto unlock;
}
ret = __uvcg_iter_item_entries(page, len, __uvcg_fill_item_entries, &iter,
sizeof(u8));
if (ret) {
kfree(bm_controls);
goto unlock;
}
kfree(xu->desc.bmControls);
xu->desc.bmControls = bm_controls;
xu->desc.bControlSize = n;
xu->desc.bLength = UVC_DT_EXTENSION_UNIT_SIZE(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);
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.bNumControls = 0;
xu->desc.bNrInPins = 0;
xu->desc.baSourceID = NULL;
xu->desc.bControlSize = 0;
xu->desc.bmControls = NULL;
mutex_lock(&opts->lock);
xu->desc.bUnitID = ++opts->last_unit_id;
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}
*/
@ -909,6 +1388,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,
},
};

View File

@ -142,6 +142,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 */