summaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/function/uvc_configfs.c
diff options
context:
space:
mode:
authorAndrzej Pietrasiewicz <andrzej.p@samsung.com>2014-12-10 12:34:02 +0100
committerFelipe Balbi <balbi@ti.com>2015-01-12 12:13:26 -0600
commit46919a23ee87bbc4eeb6d958471174e26836f0e1 (patch)
treef6630c766ed3ebd0efcaf92cb04ceb105760140c /drivers/usb/gadget/function/uvc_configfs.c
parent6c25955ed632227d28b85db274e519b766e26ddd (diff)
usb: gadget: uvc: configfs support in uvc function
Add support for using the uvc function as a component of USB gadgets composed with configfs. Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
Diffstat (limited to 'drivers/usb/gadget/function/uvc_configfs.c')
-rw-r--r--drivers/usb/gadget/function/uvc_configfs.c2439
1 files changed, 2439 insertions, 0 deletions
diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c
new file mode 100644
index 000000000000..33d92ab3c031
--- /dev/null
+++ b/drivers/usb/gadget/function/uvc_configfs.c
@@ -0,0 +1,2439 @@
+/*
+ * uvc_configfs.c
+ *
+ * Configfs support for the uvc function.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include "u_uvc.h"
+#include "uvc_configfs.h"
+
+#define UVCG_STREAMING_CONTROL_SIZE 1
+
+#define CONFIGFS_ATTR_OPS_RO(_item) \
+static ssize_t _item##_attr_show(struct config_item *item, \
+ struct configfs_attribute *attr, \
+ char *page) \
+{ \
+ struct _item *_item = to_##_item(item); \
+ struct _item##_attribute *_item##_attr = \
+ container_of(attr, struct _item##_attribute, attr); \
+ ssize_t ret = 0; \
+ \
+ if (_item##_attr->show) \
+ ret = _item##_attr->show(_item, page); \
+ return ret; \
+}
+
+static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item);
+
+/* control/header/<NAME> */
+DECLARE_UVC_HEADER_DESCRIPTOR(1);
+
+struct uvcg_control_header {
+ struct config_item item;
+ struct UVC_HEADER_DESCRIPTOR(1) desc;
+ unsigned linked;
+};
+
+struct uvcg_control_header *to_uvcg_control_header(struct config_item *item)
+{
+ return container_of(item, struct uvcg_control_header, item);
+}
+
+CONFIGFS_ATTR_STRUCT(uvcg_control_header);
+CONFIGFS_ATTR_OPS(uvcg_control_header);
+
+static struct configfs_item_operations uvcg_control_header_item_ops = {
+ .show_attribute = uvcg_control_header_attr_show,
+ .store_attribute = uvcg_control_header_attr_store,
+};
+
+#define UVCG_CTRL_HDR_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \
+static ssize_t uvcg_control_header_##cname##_show( \
+ struct uvcg_control_header *ch, char *page) \
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = ch->item.ci_parent->ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%d\n", conv(ch->desc.aname)); \
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ return result; \
+} \
+ \
+static ssize_t \
+uvcg_control_header_##cname##_store(struct uvcg_control_header *ch, \
+ const char *page, size_t len) \
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
+ int ret; \
+ uxx num; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = ch->item.ci_parent->ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ if (ch->linked || opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = str2u(page, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ if (num > limit) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ ch->desc.aname = vnoc(num); \
+ ret = len; \
+end: \
+ mutex_unlock(&opts->lock); \
+ mutex_unlock(su_mutex); \
+ return ret; \
+} \
+ \
+static struct uvcg_control_header_attribute \
+ uvcg_control_header_##cname = \
+ __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \
+ uvcg_control_header_##cname##_show, \
+ uvcg_control_header_##cname##_store)
+
+UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, le16_to_cpu, kstrtou16, u16, cpu_to_le16,
+ 0xffff);
+
+UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, le32_to_cpu, kstrtou32,
+ u32, cpu_to_le32, 0x7fffffff);
+
+#undef UVCG_CTRL_HDR_ATTR
+
+static struct configfs_attribute *uvcg_control_header_attrs[] = {
+ &uvcg_control_header_bcd_uvc.attr,
+ &uvcg_control_header_dw_clock_frequency.attr,
+ NULL,
+};
+
+struct config_item_type uvcg_control_header_type = {
+ .ct_item_ops = &uvcg_control_header_item_ops,
+ .ct_attrs = uvcg_control_header_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_item *uvcg_control_header_make(struct config_group *group,
+ const char *name)
+{
+ struct uvcg_control_header *h;
+
+ h = kzalloc(sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return ERR_CAST(h);
+
+ h->desc.bLength = UVC_DT_HEADER_SIZE(1);
+ h->desc.bDescriptorType = USB_DT_CS_INTERFACE;
+ h->desc.bDescriptorSubType = UVC_VC_HEADER;
+ h->desc.bcdUVC = cpu_to_le16(0x0100);
+ h->desc.dwClockFrequency = cpu_to_le32(48000000);
+
+ config_item_init_type_name(&h->item, name, &uvcg_control_header_type);
+
+ return &h->item;
+}
+
+void uvcg_control_header_drop(struct config_group *group,
+ struct config_item *item)
+{
+ struct uvcg_control_header *h = to_uvcg_control_header(item);
+
+ kfree(h);
+}
+
+/* control/header */
+static struct uvcg_control_header_grp {
+ struct config_group group;
+} uvcg_control_header_grp;
+
+static struct configfs_group_operations uvcg_control_header_grp_ops = {
+ .make_item = uvcg_control_header_make,
+ .drop_item = uvcg_control_header_drop,
+};
+
+static struct config_item_type uvcg_control_header_grp_type = {
+ .ct_group_ops = &uvcg_control_header_grp_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* control/processing/default */
+static struct uvcg_default_processing {
+ struct config_group group;
+} uvcg_default_processing;
+
+static inline struct uvcg_default_processing
+*to_uvcg_default_processing(struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct uvcg_default_processing, group);
+}
+
+CONFIGFS_ATTR_STRUCT(uvcg_default_processing);
+CONFIGFS_ATTR_OPS_RO(uvcg_default_processing);
+
+static struct configfs_item_operations uvcg_default_processing_item_ops = {
+ .show_attribute = uvcg_default_processing_attr_show,
+};
+
+#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \
+static ssize_t uvcg_default_processing_##cname##_show( \
+ struct uvcg_default_processing *dp, char *page) \
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; \
+ struct uvc_processing_unit_descriptor *pd; \
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ pd = &opts->uvc_processing; \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%d\n", conv(pd->aname)); \
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ return result; \
+} \
+ \
+static struct uvcg_default_processing_attribute \
+ uvcg_default_processing_##cname = \
+ __CONFIGFS_ATTR_RO(aname, uvcg_default_processing_##cname##_show)
+
+#define identity_conv(x) (x)
+
+UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, identity_conv);
+UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, identity_conv);
+UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, le16_to_cpu);
+UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv);
+
+#undef identity_conv
+
+#undef UVCG_DEFAULT_PROCESSING_ATTR
+
+static ssize_t uvcg_default_processing_bm_controls_show(
+ struct uvcg_default_processing *dp, char *page)
+{
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex;
+ struct uvc_processing_unit_descriptor *pd;
+ int result, i;
+ char *pg = page;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+ pd = &opts->uvc_processing;
+
+ mutex_lock(&opts->lock);
+ for (result = 0, i = 0; i < pd->bControlSize; ++i) {
+ result += sprintf(pg, "%d\n", pd->bmControls[i]);
+ pg = page + result;
+ }
+ mutex_unlock(&opts->lock);
+
+ mutex_unlock(su_mutex);
+
+ return result;
+}
+
+static struct uvcg_default_processing_attribute
+ uvcg_default_processing_bm_controls =
+ __CONFIGFS_ATTR_RO(bmControls,
+ uvcg_default_processing_bm_controls_show);
+
+static struct configfs_attribute *uvcg_default_processing_attrs[] = {
+ &uvcg_default_processing_b_unit_id.attr,
+ &uvcg_default_processing_b_source_id.attr,
+ &uvcg_default_processing_w_max_multiplier.attr,
+ &uvcg_default_processing_bm_controls.attr,
+ &uvcg_default_processing_i_processing.attr,
+ NULL,
+};
+
+static struct config_item_type uvcg_default_processing_type = {
+ .ct_item_ops = &uvcg_default_processing_item_ops,
+ .ct_attrs = uvcg_default_processing_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/* struct uvcg_processing {}; */
+
+static struct config_group *uvcg_processing_default_groups[] = {
+ &uvcg_default_processing.group,
+ NULL,
+};
+
+/* control/processing */
+static struct uvcg_processing_grp {
+ struct config_group group;
+} uvcg_processing_grp;
+
+static struct config_item_type uvcg_processing_grp_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/* control/terminal/camera/default */
+static struct uvcg_default_camera {
+ struct config_group group;
+} uvcg_default_camera;
+
+static inline struct uvcg_default_camera
+*to_uvcg_default_camera(struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct uvcg_default_camera, group);
+}
+
+CONFIGFS_ATTR_STRUCT(uvcg_default_camera);
+CONFIGFS_ATTR_OPS_RO(uvcg_default_camera);
+
+static struct configfs_item_operations uvcg_default_camera_item_ops = {
+ .show_attribute = uvcg_default_camera_attr_show,
+};
+
+#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \
+static ssize_t uvcg_default_camera_##cname##_show( \
+ struct uvcg_default_camera *dc, char *page) \
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \
+ struct uvc_camera_terminal_descriptor *cd; \
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> \
+ ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ cd = &opts->uvc_camera_terminal; \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%d\n", conv(cd->aname)); \
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ \
+ return result; \
+} \
+ \
+static struct uvcg_default_camera_attribute \
+ uvcg_default_camera_##cname = \
+ __CONFIGFS_ATTR_RO(aname, uvcg_default_camera_##cname##_show)
+
+#define identity_conv(x) (x)
+
+UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, identity_conv);
+UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, le16_to_cpu);
+UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv);
+UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, identity_conv);
+UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin,
+ le16_to_cpu);
+UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax,
+ le16_to_cpu);
+UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength,
+ le16_to_cpu);
+
+#undef identity_conv
+
+#undef UVCG_DEFAULT_CAMERA_ATTR
+
+static ssize_t uvcg_default_camera_bm_controls_show(
+ struct uvcg_default_camera *dc, char *page)
+{
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex;
+ struct uvc_camera_terminal_descriptor *cd;
+ int result, i;
+ char *pg = page;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent->
+ ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+ cd = &opts->uvc_camera_terminal;
+
+ mutex_lock(&opts->lock);
+ for (result = 0, i = 0; i < cd->bControlSize; ++i) {
+ result += sprintf(pg, "%d\n", cd->bmControls[i]);
+ pg = page + result;
+ }
+ mutex_unlock(&opts->lock);
+
+ mutex_unlock(su_mutex);
+ return result;
+}
+
+static struct uvcg_default_camera_attribute
+ uvcg_default_camera_bm_controls =
+ __CONFIGFS_ATTR_RO(bmControls, uvcg_default_camera_bm_controls_show);
+
+static struct configfs_attribute *uvcg_default_camera_attrs[] = {
+ &uvcg_default_camera_b_terminal_id.attr,
+ &uvcg_default_camera_w_terminal_type.attr,
+ &uvcg_default_camera_b_assoc_terminal.attr,
+ &uvcg_default_camera_i_terminal.attr,
+ &uvcg_default_camera_w_objective_focal_length_min.attr,
+ &uvcg_default_camera_w_objective_focal_length_max.attr,
+ &uvcg_default_camera_w_ocular_focal_length.attr,
+ &uvcg_default_camera_bm_controls.attr,
+ NULL,
+};
+
+static struct config_item_type uvcg_default_camera_type = {
+ .ct_item_ops = &uvcg_default_camera_item_ops,
+ .ct_attrs = uvcg_default_camera_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/* struct uvcg_camera {}; */
+
+static struct config_group *uvcg_camera_default_groups[] = {
+ &uvcg_default_camera.group,
+ NULL,
+};
+
+/* control/terminal/camera */
+static struct uvcg_camera_grp {
+ struct config_group group;
+} uvcg_camera_grp;
+
+static struct config_item_type uvcg_camera_grp_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/* control/terminal/output/default */
+static struct uvcg_default_output {
+ struct config_group group;
+} uvcg_default_output;
+
+static inline struct uvcg_default_output
+*to_uvcg_default_output(struct config_item *item)
+{
+ return container_of(to_config_group(item),
+ struct uvcg_default_output, group);
+}
+
+CONFIGFS_ATTR_STRUCT(uvcg_default_output);
+CONFIGFS_ATTR_OPS_RO(uvcg_default_output);
+
+static struct configfs_item_operations uvcg_default_output_item_ops = {
+ .show_attribute = uvcg_default_output_attr_show,
+};
+
+#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \
+static ssize_t uvcg_default_output_##cname##_show( \
+ struct uvcg_default_output *dout, char *page) \
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &dout->group.cg_subsys->su_mutex; \
+ struct uvc_output_terminal_descriptor *cd; \
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = dout->group.cg_item.ci_parent->ci_parent-> \
+ ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ cd = &opts->uvc_output_terminal; \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%d\n", conv(cd->aname)); \
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ \
+ return result; \
+} \
+ \
+static struct uvcg_default_output_attribute \
+ uvcg_default_output_##cname = \
+ __CONFIGFS_ATTR_RO(aname, uvcg_default_output_##cname##_show)
+
+#define identity_conv(x) (x)
+
+UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, identity_conv);
+UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, le16_to_cpu);
+UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv);
+UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, identity_conv);
+UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, identity_conv);
+
+#undef identity_conv
+
+#undef UVCG_DEFAULT_OUTPUT_ATTR
+
+static struct configfs_attribute *uvcg_default_output_attrs[] = {
+ &uvcg_default_output_b_terminal_id.attr,
+ &uvcg_default_output_w_terminal_type.attr,
+ &uvcg_default_output_b_assoc_terminal.attr,
+ &uvcg_default_output_b_source_id.attr,
+ &uvcg_default_output_i_terminal.attr,
+ NULL,
+};
+
+static struct config_item_type uvcg_default_output_type = {
+ .ct_item_ops = &uvcg_default_output_item_ops,
+ .ct_attrs = uvcg_default_output_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+/* struct uvcg_output {}; */
+
+static struct config_group *uvcg_output_default_groups[] = {
+ &uvcg_default_output.group,
+ NULL,
+};
+
+/* control/terminal/output */
+static struct uvcg_output_grp {
+ struct config_group group;
+} uvcg_output_grp;
+
+static struct config_item_type uvcg_output_grp_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *uvcg_terminal_default_groups[] = {
+ &uvcg_camera_grp.group,
+ &uvcg_output_grp.group,
+ NULL,
+};
+
+/* control/terminal */
+static struct uvcg_terminal_grp {
+ struct config_group group;
+} uvcg_terminal_grp;
+
+static struct config_item_type uvcg_terminal_grp_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/* control/class/{fs} */
+static struct uvcg_control_class {
+ struct config_group group;
+} uvcg_control_class_fs, uvcg_control_class_ss;
+
+
+static inline struct uvc_descriptor_header
+**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o)
+{
+ struct uvcg_control_class *cl = container_of(to_config_group(i),
+ struct uvcg_control_class, group);
+
+ if (cl == &uvcg_control_class_fs)
+ return o->uvc_fs_control_cls;
+
+ if (cl == &uvcg_control_class_ss)
+ return o->uvc_ss_control_cls;
+
+ return NULL;
+}
+
+static int uvcg_control_class_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct config_item *control, *header;
+ struct f_uvc_opts *opts;
+ struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+ struct uvc_descriptor_header **class_array;
+ struct uvcg_control_header *target_hdr;
+ int ret = -EINVAL;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ control = src->ci_parent->ci_parent;
+ header = config_group_find_item(to_config_group(control), "header");
+ if (!header || target->ci_parent != header)
+ goto out;
+
+ opts = to_f_uvc_opts(control->ci_parent);
+
+ mutex_lock(&opts->lock);
+
+ class_array = uvcg_get_ctl_class_arr(src, opts);
+ if (!class_array)
+ goto unlock;
+ if (opts->refcnt || class_array[0]) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ target_hdr = to_uvcg_control_header(target);
+ ++target_hdr->linked;
+ class_array[0] = (struct uvc_descriptor_header *)&target_hdr->desc;
+ ret = 0;
+
+unlock:
+ mutex_unlock(&opts->lock);
+out:
+ mutex_unlock(su_mutex);
+ return ret;
+}
+
+static int uvcg_control_class_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct config_item *control, *header;
+ struct f_uvc_opts *opts;
+ struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+ struct uvc_descriptor_header **class_array;
+ struct uvcg_control_header *target_hdr;
+ int ret = -EINVAL;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ control = src->ci_parent->ci_parent;
+ header = config_group_find_item(to_config_group(control), "header");
+ if (!header || target->ci_parent != header)
+ goto out;
+
+ opts = to_f_uvc_opts(control->ci_parent);
+
+ mutex_lock(&opts->lock);
+
+ class_array = uvcg_get_ctl_class_arr(src, opts);
+ if (!class_array)
+ goto unlock;
+ if (opts->refcnt) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ target_hdr = to_uvcg_control_header(target);
+ --target_hdr->linked;
+ class_array[0] = NULL;
+ ret = 0;
+
+unlock:
+ mutex_unlock(&opts->lock);
+out:
+ mutex_unlock(su_mutex);
+ return ret;
+}
+
+static struct configfs_item_operations uvcg_control_class_item_ops = {
+ .allow_link = uvcg_control_class_allow_link,
+ .drop_link = uvcg_control_class_drop_link,
+};
+
+static struct config_item_type uvcg_control_class_type = {
+ .ct_item_ops = &uvcg_control_class_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *uvcg_control_class_default_groups[] = {
+ &uvcg_control_class_fs.group,
+ &uvcg_control_class_ss.group,
+ NULL,
+};
+
+/* control/class */
+static struct uvcg_control_class_grp {
+ struct config_group group;
+} uvcg_control_class_grp;
+
+static struct config_item_type uvcg_control_class_grp_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *uvcg_control_default_groups[] = {
+ &uvcg_control_header_grp.group,
+ &uvcg_processing_grp.group,
+ &uvcg_terminal_grp.group,
+ &uvcg_control_class_grp.group,
+ NULL,
+};
+
+/* control */
+static struct uvcg_control_grp {
+ struct config_group group;
+} uvcg_control_grp;
+
+static struct config_item_type uvcg_control_grp_type = {
+ .ct_owner = THIS_MODULE,
+};
+
+/* streaming/uncompressed */
+static struct uvcg_uncompressed_grp {
+ struct config_group group;
+} uvcg_uncompressed_grp;
+
+/* streaming/mjpeg */
+static struct uvcg_mjpeg_grp {
+ struct config_group group;
+} uvcg_mjpeg_grp;
+
+static struct config_item *fmt_parent[] = {
+ &uvcg_uncompressed_grp.group.cg_item,
+ &uvcg_mjpeg_grp.group.cg_item,
+};
+
+enum uvcg_format_type {
+ UVCG_UNCOMPRESSED = 0,
+ UVCG_MJPEG,
+};
+
+struct uvcg_format {
+ struct config_group group;
+ enum uvcg_format_type type;
+ unsigned linked;
+ unsigned num_frames;
+ __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE];
+};
+
+struct uvcg_format *to_uvcg_format(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct uvcg_format, group);
+}
+
+static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page)
+{
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct mutex *su_mutex = &f->group.cg_subsys->su_mutex;
+ int result, i;
+ char *pg = page;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = f->group.cg_item.ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ result = sprintf(pg, "0x");
+ pg += result;
+ for (i = 0; i < UVCG_STREAMING_CONTROL_SIZE; ++i) {
+ result += sprintf(pg, "%x\n", f->bmaControls[i]);
+ pg = page + result;
+ }
+ mutex_unlock(&opts->lock);
+
+ mutex_unlock(su_mutex);
+ return result;
+}
+
+static ssize_t uvcg_format_bma_controls_store(struct uvcg_format *ch,
+ const char *page, size_t len)
+{
+ struct f_uvc_opts *opts;
+ struct config_item *opts_item;
+ struct mutex *su_mutex = &ch->group.cg_subsys->su_mutex;
+ int ret = -EINVAL;
+
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = ch->group.cg_item.ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ if (ch->linked || opts->refcnt) {
+ ret = -EBUSY;
+ goto end;
+ }
+
+ if (len < 4 || *page != '0' ||
+ (*(page + 1) != 'x' && *(page + 1) != 'X'))
+ goto end;
+ ret = hex2bin(ch->bmaControls, page + 2, 1);
+ if (ret < 0)
+ goto end;
+ ret = len;
+end:
+ mutex_unlock(&opts->lock);
+ mutex_unlock(su_mutex);
+ return ret;
+}
+
+struct uvcg_format_ptr {
+ struct uvcg_format *fmt;
+ struct list_head entry;
+};
+
+/* streaming/header/<NAME> */
+struct uvcg_streaming_header {
+ struct config_item item;
+ struct uvc_input_header_descriptor desc;
+ unsigned linked;
+ struct list_head formats;
+ unsigned num_fmt;
+};
+
+struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item)
+{
+ return container_of(item, struct uvcg_streaming_header, item);
+}
+
+CONFIGFS_ATTR_STRUCT(uvcg_streaming_header);
+CONFIGFS_ATTR_OPS(uvcg_streaming_header);
+
+static int uvcg_streaming_header_allow_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+ struct config_item *opts_item;
+ struct f_uvc_opts *opts;
+ struct uvcg_streaming_header *src_hdr;
+ struct uvcg_format *target_fmt = NULL;
+ struct uvcg_format_ptr *format_ptr;
+ int i, ret = -EINVAL;
+
+ src_hdr = to_uvcg_streaming_header(src);
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = src->ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+
+ if (src_hdr->linked) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i)
+ if (target->ci_parent == fmt_parent[i])
+ break;
+ if (i == ARRAY_SIZE(fmt_parent))
+ goto out;
+
+ target_fmt = container_of(to_config_group(target), struct uvcg_format,
+ group);
+ if (!target_fmt)
+ goto out;
+
+ format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL);
+ if (!format_ptr) {
+ ret = PTR_ERR(format_ptr);
+ goto out;
+ }
+ ret = 0;
+ format_ptr->fmt = target_fmt;
+ list_add_tail(&format_ptr->entry, &src_hdr->formats);
+ ++src_hdr->num_fmt;
+
+out:
+ mutex_unlock(&opts->lock);
+ mutex_unlock(su_mutex);
+ return ret;
+}
+
+static int uvcg_streaming_header_drop_link(struct config_item *src,
+ struct config_item *target)
+{
+ struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
+ struct config_item *opts_item;
+ struct f_uvc_opts *opts;
+ struct uvcg_streaming_header *src_hdr;
+ struct uvcg_format *target_fmt = NULL;
+ struct uvcg_format_ptr *format_ptr, *tmp;
+ int ret = -EINVAL;
+
+ src_hdr = to_uvcg_streaming_header(src);
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */
+
+ opts_item = src->ci_parent->ci_parent->ci_parent;
+ opts = to_f_uvc_opts(opts_item);
+
+ mutex_lock(&opts->lock);
+ target_fmt = container_of(to_config_group(target), struct uvcg_format,
+ group);
+ if (!target_fmt)
+ goto out;
+
+ list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry)
+ if (format_ptr->fmt == target_fmt) {
+ list_del(&format_ptr->entry);
+ kfree(format_ptr);
+ --src_hdr->num_fmt;
+ break;
+ }
+
+out:
+ mutex_unlock(&opts->lock);
+ mutex_unlock(su_mutex);
+ return ret;
+
+}
+
+static struct configfs_item_operations uvcg_streaming_header_item_ops = {
+ .show_attribute = uvcg_streaming_header_attr_show,
+ .store_attribute = uvcg_streaming_header_attr_store,
+ .allow_link = uvcg_streaming_header_allow_link,
+ .drop_link = uvcg_streaming_header_drop_link,
+};
+
+#define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv) \
+static ssize_t uvcg_streaming_header_##cname##_show( \
+ struct uvcg_streaming_header *sh, char *page) \
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &sh->item.ci_group->cg_subsys->su_mutex;\
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = sh->item.ci_parent->ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%d\n", conv(sh->desc.aname)); \
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ return result; \
+} \
+ \
+static struct uvcg_streaming_header_attribute \
+ uvcg_streaming_header_##cname = \
+ __CONFIGFS_ATTR_RO(aname, uvcg_streaming_header_##cname##_show)
+
+#define identity_conv(x) (x)
+
+UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, identity_conv);
+UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, identity_conv);
+UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod,
+ identity_conv);
+UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, identity_conv);
+UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, identity_conv);
+
+#undef identity_conv
+
+#undef UVCG_STREAMING_HEADER_ATTR
+
+static struct configfs_attribute *uvcg_streaming_header_attrs[] = {
+ &uvcg_streaming_header_bm_info.attr,
+ &uvcg_streaming_header_b_terminal_link.attr,
+ &uvcg_streaming_header_b_still_capture_method.attr,
+ &uvcg_streaming_header_b_trigger_support.attr,
+ &uvcg_streaming_header_b_trigger_usage.attr,
+ NULL,
+};
+
+struct config_item_type uvcg_streaming_header_type = {
+ .ct_item_ops = &uvcg_streaming_header_item_ops,
+ .ct_attrs = uvcg_streaming_header_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_item
+*uvcg_streaming_header_make(struct config_group *group, const char *name)
+{
+ struct uvcg_streaming_header *h;
+
+ h = kzalloc(sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return ERR_CAST(h);
+
+ INIT_LIST_HEAD(&h->formats);
+ h->desc.bDescriptorType = USB_DT_CS_INTERFACE;
+ h->desc.bDescriptorSubType = UVC_VS_INPUT_HEADER;
+ h->desc.bTerminalLink = 3;
+ h->desc.bControlSize = UVCG_STREAMING_CONTROL_SIZE;
+
+ config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type);
+
+ return &h->item;
+}
+
+void uvcg_streaming_header_drop(struct config_group *group,
+ struct config_item *item)
+{
+ struct uvcg_streaming_header *h = to_uvcg_streaming_header(item);
+
+ kfree(h);
+}
+
+/* streaming/header */
+static struct uvcg_streaming_header_grp {
+ struct config_group group;
+} uvcg_streaming_header_grp;
+
+static struct configfs_group_operations uvcg_streaming_header_grp_ops = {
+ .make_item = uvcg_streaming_header_make,
+ .drop_item = uvcg_streaming_header_drop,
+};
+
+static struct config_item_type uvcg_streaming_header_grp_type = {
+ .ct_group_ops = &uvcg_streaming_header_grp_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* streaming/<mode>/<format>/<NAME> */
+struct uvcg_frame {
+ struct {
+ u8 b_length;
+ u8 b_descriptor_type;
+ u8 b_descriptor_subtype;
+ u8 b_frame_index;
+ u8 bm_capabilities;
+ u16 w_width;
+ u16 w_height;
+ u32 dw_min_bit_rate;
+ u32 dw_max_bit_rate;
+ u32 dw_max_video_frame_buffer_size;
+ u32 dw_default_frame_interval;
+ u8 b_frame_interval_type;
+ } __attribute__((packed)) frame;
+ u32 *dw_frame_interval;
+ enum uvcg_format_type fmt_type;
+ struct config_item item;
+};
+
+struct uvcg_frame *to_uvcg_frame(struct config_item *item)
+{
+ return container_of(item, struct uvcg_frame, item);
+}
+
+CONFIGFS_ATTR_STRUCT(uvcg_frame);
+CONFIGFS_ATTR_OPS(uvcg_frame);
+
+static struct configfs_item_operations uvcg_frame_item_ops = {
+ .show_attribute = uvcg_frame_attr_show,
+ .store_attribute = uvcg_frame_attr_store,
+};
+
+#define UVCG_FRAME_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \
+static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
+ int result; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%d\n", conv(f->frame.cname)); \
+ mutex_unlock(&opts->lock); \
+ \
+ mutex_unlock(su_mutex); \
+ return result; \
+} \
+ \
+static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \
+ const char *page, size_t len)\
+{ \
+ struct f_uvc_opts *opts; \
+ struct config_item *opts_item; \
+ struct uvcg_format *fmt; \
+ struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
+ int ret; \
+ uxx num; \
+ \
+ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \
+ \
+ opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \
+ opts = to_f_uvc_opts(opts_item); \
+ fmt = to_uvcg_format(f->item.ci_parent); \
+ \
+ mutex_lock(&opts->lock); \
+ if (fmt->linked || opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = str2u(page, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ if (num > limit) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ f->frame.cname = vnoc(num); \
+ ret = len; \
+end: \
+ mutex_unlock(&opts->lock); \
+ mutex_unlock(su_mutex); \
+ return ret; \
+} \
+ \
+static struct uvcg_frame_attribute \
+ uvcg_frame_##cname = \
+ __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \
+ uvcg_frame_##cname##_show, \
+ uvcg_frame_##cname##_store)
+
+#define identity_conv(x) (x)
+
+UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, identity_conv, kstrtou8, u8,
+ identity_conv, 0xFF);
+UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, kstrtou16, u16, cpu_to_le16,
+ 0xFFFF);
+UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, kstrtou16, u16, cpu_to_le16,
+ 0xFFFF);
+UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, kstrtou32, u32,
+ cpu_to_le32, 0xFFFFFFFF);
+UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, kstrtou32, u32,
+ cpu_to_le32, 0xFFFFFFFF);
+UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize,
+ le32_to_cpu, kstrtou32