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:
parent
0df28607c5
commit
0525210c98
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue