UPSTREAM: usb: gadget: configfs: Add frame-based frame format support

Add support for frame-based frame format, which can be used to support
multiple formats like H264 or H265, in addition to MJPEG and YUV frames.

The frame-based format is set to H264 by default, but it can be updated
to other formats by modifying the GUID through the guid configfs
attribute. Different structures are used for all three formats, as
H264 has a different structure compared to MJPEG and uncompressed
formats. These structures will be passed to the frame make function
based on the active format, using a common frame structure with
additional parameters needed only for frame-based formats. These
parameters are handled at runtime in the UVC driver.

Signed-off-by: Akash Kumar <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Greg Kroah-Hartman <[email protected]>

Git-commit: 7b5a58952fc3b51905c2963647485565df1e5e26
Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git.

Change-Id: Iffef14deba1f7e22c74b89b72a252f23802549d4
Signed-off-by: Akash Kumar <[email protected]>
This commit is contained in:
Akash Kumar 2025-10-30 16:07:18 +05:30
commit 5cef717354
5 changed files with 469 additions and 14 deletions

View file

@ -808,9 +808,9 @@ static struct usb_function_instance *uvc_alloc_inst(void)
cd->wObjectiveFocalLengthMax = cpu_to_le16(0);
cd->wOcularFocalLength = cpu_to_le16(0);
cd->bControlSize = 3;
cd->bmControls[0] = 2;
cd->bmControls[1] = 0;
cd->bmControls[2] = 0;
cd->bmControls[0] = 62;
cd->bmControls[1] = 126;
cd->bmControls[2] = 10;
pd = &opts->uvc_processing;
pd->bLength = UVC_DT_PROCESSING_UNIT_SIZE(2);
@ -820,8 +820,8 @@ static struct usb_function_instance *uvc_alloc_inst(void)
pd->bSourceID = 1;
pd->wMaxMultiplier = cpu_to_le16(16*1024);
pd->bControlSize = 2;
pd->bmControls[0] = 1;
pd->bmControls[1] = 0;
pd->bmControls[0] = 91;
pd->bmControls[1] = 23;
pd->iProcessing = 0;
pd->bmVideoStandards = 0;

View file

@ -762,16 +762,19 @@ static const struct uvcg_config_group_type uvcg_control_grp_type = {
/* -----------------------------------------------------------------------------
* streaming/uncompressed
* streaming/mjpeg
* streaming/framebased
*/
static const char * const uvcg_format_names[] = {
"uncompressed",
"mjpeg",
"framebased"
};
enum uvcg_format_type {
UVCG_UNCOMPRESSED = 0,
UVCG_MJPEG,
UVCG_FRAMEBASED,
};
struct uvcg_format {
@ -1080,6 +1083,7 @@ struct uvcg_frame {
u32 dw_max_video_frame_buffer_size;
u32 dw_default_frame_interval;
u8 b_frame_interval_type;
u32 dw_bytes_perline;
} __attribute__((packed)) frame;
u32 *dw_frame_interval;
};
@ -1190,6 +1194,7 @@ UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32);
UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32);
UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32);
UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32);
UVCG_FRAME_ATTR(dw_bytes_perline, dwBytesPerLine, 32);
#undef UVCG_FRAME_ATTR
@ -1324,7 +1329,7 @@ end:
UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval);
static struct configfs_attribute *uvcg_frame_attrs[] = {
static struct configfs_attribute *uvcg_frame_attrs1[] = {
&uvcg_frame_attr_b_frame_index,
&uvcg_frame_attr_bm_capabilities,
&uvcg_frame_attr_w_width,
@ -1337,12 +1342,32 @@ static struct configfs_attribute *uvcg_frame_attrs[] = {
NULL,
};
static const struct config_item_type uvcg_frame_type = {
static struct configfs_attribute *uvcg_frame_attrs2[] = {
&uvcg_frame_attr_b_frame_index,
&uvcg_frame_attr_bm_capabilities,
&uvcg_frame_attr_w_width,
&uvcg_frame_attr_w_height,
&uvcg_frame_attr_dw_min_bit_rate,
&uvcg_frame_attr_dw_max_bit_rate,
&uvcg_frame_attr_dw_max_video_frame_buffer_size,
&uvcg_frame_attr_dw_default_frame_interval,
&uvcg_frame_attr_dw_frame_interval,
&uvcg_frame_attr_dw_bytes_perline,
NULL,
};
static const struct config_item_type uvcg_frame_type1 = {
.ct_item_ops = &uvcg_config_item_ops,
.ct_attrs = uvcg_frame_attrs,
.ct_attrs = uvcg_frame_attrs1,
.ct_owner = THIS_MODULE,
};
static const struct config_item_type uvcg_frame_type2 = {
.ct_item_ops = &uvcg_config_item_ops,
.ct_attrs = uvcg_frame_attrs2,
.ct_owner = THIS_MODULE,
};
static struct config_item *uvcg_frame_make(struct config_group *group,
const char *name)
{
@ -1363,6 +1388,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
h->frame.dw_max_bit_rate = 55296000;
h->frame.dw_max_video_frame_buffer_size = 460800;
h->frame.dw_default_frame_interval = 666666;
h->frame.dw_bytes_perline = 0;
opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
opts = to_f_uvc_opts(opts_item);
@ -1375,6 +1401,9 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
} else if (fmt->type == UVCG_MJPEG) {
h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG;
h->fmt_type = UVCG_MJPEG;
} else if (fmt->type == UVCG_FRAMEBASED) {
h->frame.b_descriptor_subtype = UVC_VS_FRAME_FRAME_BASED;
h->fmt_type = UVCG_FRAMEBASED;
} else {
mutex_unlock(&opts->lock);
kfree(h);
@ -1383,7 +1412,10 @@ static struct config_item *uvcg_frame_make(struct config_group *group,
++fmt->num_frames;
mutex_unlock(&opts->lock);
config_item_init_type_name(&h->item, name, &uvcg_frame_type);
if (fmt->type == UVCG_FRAMEBASED)
config_item_init_type_name(&h->item, name, &uvcg_frame_type2);
else
config_item_init_type_name(&h->item, name, &uvcg_frame_type1);
return &h->item;
}
@ -1413,9 +1445,6 @@ static void uvcg_format_set_indices(struct config_group *fmt)
list_for_each_entry(ci, &fmt->cg_children, ci_entry) {
struct uvcg_frame *frm;
if (ci->ci_type != &uvcg_frame_type)
continue;
frm = to_uvcg_frame(ci);
frm->frame.b_frame_index = i++;
}
@ -1856,6 +1885,260 @@ static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = {
.name = "mjpeg",
};
/* -----------------------------------------------------------------------------
* streaming/framebased/<NAME>
*/
struct uvcg_framebased {
struct uvcg_format fmt;
struct uvc_format_framebased desc;
};
static inline struct uvcg_framebased *to_uvcg_framebased(struct config_item *item)
{
return container_of(to_uvcg_format(item), struct uvcg_framebased, fmt);
}
static struct configfs_group_operations uvcg_framebased_group_ops = {
.make_item = uvcg_frame_make,
.drop_item = uvcg_frame_drop,
};
#define UVCG_FRAMEBASED_ATTR_RO(cname, aname, bits) \
static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \
char *page) \
{ \
struct uvcg_framebased *u = to_uvcg_framebased(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
int result; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; \
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
result = scnprintf(page, PAGE_SIZE, "%u\n", le##bits##_to_cpu(u->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
return result; \
} \
\
UVC_ATTR_RO(uvcg_framebased_, cname, aname)
#define UVCG_FRAMEBASED_ATTR(cname, aname, bits) \
static ssize_t uvcg_framebased_##cname##_show(struct config_item *item, \
char *page) \
{ \
struct uvcg_framebased *u = to_uvcg_framebased(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
int result; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
result = scnprintf(page, PAGE_SIZE, "%u\n", le##bits##_to_cpu(u->desc.aname));\
mutex_unlock(&opts->lock); \
\
mutex_unlock(su_mutex); \
return result; \
} \
\
static ssize_t \
uvcg_framebased_##cname##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct uvcg_framebased *u = to_uvcg_framebased(item); \
struct f_uvc_opts *opts; \
struct config_item *opts_item; \
struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \
int ret; \
u8 num; \
\
mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
\
opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
opts = to_f_uvc_opts(opts_item); \
\
mutex_lock(&opts->lock); \
if (u->fmt.linked || opts->refcnt) { \
ret = -EBUSY; \
goto end; \
} \
\
ret = kstrtou8(page, 0, &num); \
if (ret) \
goto end; \
\
if (num > 255) { \
ret = -EINVAL; \
goto end; \
} \
u->desc.aname = num; \
ret = len; \
end: \
mutex_unlock(&opts->lock); \
mutex_unlock(su_mutex); \
return ret; \
} \
\
UVC_ATTR(uvcg_framebased_, cname, aname)
UVCG_FRAMEBASED_ATTR_RO(b_format_index, bFormatIndex, 8);
UVCG_FRAMEBASED_ATTR_RO(b_bits_per_pixel, bBitsPerPixel, 8);
UVCG_FRAMEBASED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8);
UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8);
UVCG_FRAMEBASED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8);
UVCG_FRAMEBASED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8);
#undef UVCG_FRAMEBASED_ATTR
#undef UVCG_FRAMEBASED_ATTR_RO
static ssize_t uvcg_framebased_guid_format_show(struct config_item *item,
char *page)
{
struct uvcg_framebased *ch = to_uvcg_framebased(item);
struct f_uvc_opts *opts;
struct config_item *opts_item;
struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
opts = to_f_uvc_opts(opts_item);
mutex_lock(&opts->lock);
memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat));
mutex_unlock(&opts->lock);
mutex_unlock(su_mutex);
return sizeof(ch->desc.guidFormat);
}
static ssize_t uvcg_framebased_guid_format_store(struct config_item *item,
const char *page, size_t len)
{
struct uvcg_framebased *ch = to_uvcg_framebased(item);
struct f_uvc_opts *opts;
struct config_item *opts_item;
struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
int ret;
mutex_lock(su_mutex); /* for navigating configfs hierarchy */
opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
opts = to_f_uvc_opts(opts_item);
mutex_lock(&opts->lock);
if (ch->fmt.linked || opts->refcnt) {
ret = -EBUSY;
goto end;
}
memcpy(ch->desc.guidFormat, page,
min(sizeof(ch->desc.guidFormat), len));
ret = sizeof(ch->desc.guidFormat);
end:
mutex_unlock(&opts->lock);
mutex_unlock(su_mutex);
return ret;
}
UVC_ATTR(uvcg_framebased_, guid_format, guidFormat);
static inline ssize_t
uvcg_framebased_bma_controls_show(struct config_item *item, char *page)
{
struct uvcg_framebased *u = to_uvcg_framebased(item);
return uvcg_format_bma_controls_show(&u->fmt, page);
}
static inline ssize_t
uvcg_framebased_bma_controls_store(struct config_item *item,
const char *page, size_t len)
{
struct uvcg_framebased *u = to_uvcg_framebased(item);
return uvcg_format_bma_controls_store(&u->fmt, page, len);
}
UVC_ATTR(uvcg_framebased_, bma_controls, bmaControls);
static struct configfs_attribute *uvcg_framebased_attrs[] = {
&uvcg_framebased_attr_b_format_index,
&uvcg_framebased_attr_b_default_frame_index,
&uvcg_framebased_attr_b_bits_per_pixel,
&uvcg_framebased_attr_b_aspect_ratio_x,
&uvcg_framebased_attr_b_aspect_ratio_y,
&uvcg_framebased_attr_bm_interface_flags,
&uvcg_framebased_attr_bma_controls,
&uvcg_framebased_attr_guid_format,
NULL,
};
static const struct config_item_type uvcg_framebased_type = {
.ct_item_ops = &uvcg_config_item_ops,
.ct_group_ops = &uvcg_framebased_group_ops,
.ct_attrs = uvcg_framebased_attrs,
.ct_owner = THIS_MODULE,
};
static struct config_group *uvcg_framebased_make(struct config_group *group,
const char *name)
{
static char guid[] = { /*Declear frame based as H264 format*/
'H', '2', '6', '4', 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
};
struct uvcg_framebased *h;
h = kzalloc(sizeof(*h), GFP_KERNEL);
if (!h)
return ERR_PTR(-ENOMEM);
h->desc.bLength = UVC_DT_FORMAT_FRAMEBASED_SIZE;
h->desc.bDescriptorType = USB_DT_CS_INTERFACE;
h->desc.bDescriptorSubType = UVC_VS_FORMAT_FRAME_BASED;
memcpy(h->desc.guidFormat, guid, sizeof(guid));
h->desc.bBitsPerPixel = 0;
h->desc.bDefaultFrameIndex = 1;
h->desc.bAspectRatioX = 0;
h->desc.bAspectRatioY = 0;
h->desc.bmInterfaceFlags = 0;
h->desc.bCopyProtect = 0;
h->desc.bVariableSize = 1;
h->fmt.type = UVCG_FRAMEBASED;
config_group_init_type_name(&h->fmt.group, name,
&uvcg_framebased_type);
return &h->fmt.group;
}
static struct configfs_group_operations uvcg_framebased_grp_ops = {
.make_group = uvcg_framebased_make,
};
static const struct uvcg_config_group_type uvcg_framebased_grp_type = {
.type = {
.ct_item_ops = &uvcg_config_item_ops,
.ct_group_ops = &uvcg_framebased_grp_ops,
.ct_owner = THIS_MODULE,
},
.name = "framebased",
};
/* -----------------------------------------------------------------------------
* streaming/color_matching/default
*/
@ -2001,6 +2284,7 @@ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h,
if (ret)
return ret;
grp = &f->fmt->group;
j = 0;
list_for_each_entry(item, &grp->cg_children, ci_entry) {
frm = to_uvcg_frame(item);
ret = fun(frm, priv2, priv3, j++, UVCG_FRAME);
@ -2049,6 +2333,11 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
container_of(fmt, struct uvcg_mjpeg, fmt);
*size += sizeof(m->desc);
} else if (fmt->type == UVCG_FRAMEBASED) {
struct uvcg_framebased *f =
container_of(fmt, struct uvcg_framebased, fmt);
*size += sizeof(f->desc);
} else {
return -EINVAL;
}
@ -2059,6 +2348,11 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
int sz = sizeof(frm->dw_frame_interval);
*size += sizeof(frm->frame);
/*
* framebased has duplicate member with uncompressed and
* mjpeg, so minus it
*/
*size -= sizeof(u32);
*size += frm->frame.b_frame_interval_type * sz;
}
break;
@ -2069,6 +2363,27 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
return 0;
}
static int __uvcg_copy_framebased_desc(void *dest, struct uvcg_frame *frm,
int sz)
{
struct uvc_frame_framebased *desc = dest;
desc->bLength = frm->frame.b_length;
desc->bDescriptorType = frm->frame.b_descriptor_type;
desc->bDescriptorSubType = frm->frame.b_descriptor_subtype;
desc->bFrameIndex = frm->frame.b_frame_index;
desc->bmCapabilities = frm->frame.bm_capabilities;
desc->wWidth = frm->frame.w_width;
desc->wHeight = frm->frame.w_height;
desc->dwMinBitRate = frm->frame.dw_min_bit_rate;
desc->dwMaxBitRate = frm->frame.dw_max_bit_rate;
desc->dwDefaultFrameInterval = frm->frame.dw_default_frame_interval;
desc->bFrameIntervalType = frm->frame.b_frame_interval_type;
desc->dwBytesPerLine = frm->frame.dw_bytes_perline;
return 0;
}
/*
* Fill an array of streaming descriptors.
*
@ -2123,6 +2438,15 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
m->desc.bNumFrameDescriptors = fmt->num_frames;
memcpy(*dest, &m->desc, sizeof(m->desc));
*dest += sizeof(m->desc);
} else if (fmt->type == UVCG_FRAMEBASED) {
struct uvcg_framebased *f =
container_of(fmt, struct uvcg_framebased,
fmt);
f->desc.bFormatIndex = n + 1;
f->desc.bNumFrameDescriptors = fmt->num_frames;
memcpy(*dest, &f->desc, sizeof(f->desc));
*dest += sizeof(f->desc);
} else {
return -EINVAL;
}
@ -2132,8 +2456,11 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
struct uvcg_frame *frm = priv1;
struct uvc_descriptor_header *h = *dest;
sz = sizeof(frm->frame);
memcpy(*dest, &frm->frame, sz);
sz = sizeof(frm->frame) - 4;
if (frm->fmt_type != UVCG_FRAMEBASED)
memcpy(*dest, &frm->frame, sz);
else
__uvcg_copy_framebased_desc(*dest, frm, sz);
*dest += sz;
sz = frm->frame.b_frame_interval_type *
sizeof(*frm->dw_frame_interval);
@ -2145,6 +2472,9 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
else if (frm->fmt_type == UVCG_MJPEG)
h->bLength = UVC_DT_FRAME_MJPEG_SIZE(
frm->frame.b_frame_interval_type);
else if (frm->fmt_type == UVCG_FRAMEBASED)
h->bLength = UVC_DT_FRAME_FRAMEBASED_SIZE(
frm->frame.b_frame_interval_type);
}
break;
}
@ -2357,6 +2687,7 @@ static const struct uvcg_config_group_type uvcg_streaming_grp_type = {
&uvcg_streaming_header_grp_type,
&uvcg_uncompressed_grp_type,
&uvcg_mjpeg_grp_type,
&uvcg_framebased_grp_type,
&uvcg_color_matching_grp_type,
&uvcg_streaming_class_grp_type,
NULL,

View file

@ -58,6 +58,7 @@ struct uvc_format {
static struct uvc_format uvc_formats[] = {
{ 16, V4L2_PIX_FMT_YUYV },
{ 0, V4L2_PIX_FMT_MJPEG },
{ 0, V4L2_PIX_FMT_H264 },
{ 12, V4L2_PIX_FMT_YUV420 },
{ 8, V4L2_PIX_FMT_GREY },
};