summaryrefslogtreecommitdiffstats
path: root/drivers/media/usb/uvc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/usb/uvc
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--drivers/media/usb/uvc/Kconfig21
-rw-r--r--drivers/media/usb/uvc/Makefile7
-rw-r--r--drivers/media/usb/uvc/uvc_ctrl.c2697
-rw-r--r--drivers/media/usb/uvc/uvc_debugfs.c105
-rw-r--r--drivers/media/usb/uvc/uvc_driver.c3033
-rw-r--r--drivers/media/usb/uvc/uvc_entity.c161
-rw-r--r--drivers/media/usb/uvc/uvc_isight.c140
-rw-r--r--drivers/media/usb/uvc/uvc_metadata.c176
-rw-r--r--drivers/media/usb/uvc/uvc_queue.c517
-rw-r--r--drivers/media/usb/uvc/uvc_status.c350
-rw-r--r--drivers/media/usb/uvc/uvc_v4l2.c1555
-rw-r--r--drivers/media/usb/uvc/uvc_video.c2251
-rw-r--r--drivers/media/usb/uvc/uvcvideo.h790
13 files changed, 11803 insertions, 0 deletions
diff --git a/drivers/media/usb/uvc/Kconfig b/drivers/media/usb/uvc/Kconfig
new file mode 100644
index 000000000..ca51ee8e4
--- /dev/null
+++ b/drivers/media/usb/uvc/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config USB_VIDEO_CLASS
+ tristate "USB Video Class (UVC)"
+ depends on VIDEO_DEV
+ select VIDEOBUF2_VMALLOC
+ help
+ Support for the USB Video Class (UVC). Currently only video
+ input devices, such as webcams, are supported.
+
+ For more information see: <http://linux-uvc.berlios.de/>
+
+config USB_VIDEO_CLASS_INPUT_EVDEV
+ bool "UVC input events device support"
+ default y
+ depends on USB_VIDEO_CLASS
+ depends on USB_VIDEO_CLASS=INPUT || INPUT=y
+ help
+ This option makes USB Video Class devices register an input device
+ to report button events.
+
+ If you are in doubt, say Y.
diff --git a/drivers/media/usb/uvc/Makefile b/drivers/media/usb/uvc/Makefile
new file mode 100644
index 000000000..4f9eee4f8
--- /dev/null
+++ b/drivers/media/usb/uvc/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+uvcvideo-objs := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
+ uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o
+ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
+uvcvideo-objs += uvc_entity.o
+endif
+obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
new file mode 100644
index 000000000..6d7535efc
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -0,0 +1,2697 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_ctrl.c -- USB Video Class driver - Controls
+ *
+ * Copyright (C) 2005-2010
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <asm/barrier.h>
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-uvc.h>
+
+#include "uvcvideo.h"
+
+#define UVC_CTRL_DATA_CURRENT 0
+#define UVC_CTRL_DATA_BACKUP 1
+#define UVC_CTRL_DATA_MIN 2
+#define UVC_CTRL_DATA_MAX 3
+#define UVC_CTRL_DATA_RES 4
+#define UVC_CTRL_DATA_DEF 5
+#define UVC_CTRL_DATA_LAST 6
+
+/* ------------------------------------------------------------------------
+ * Controls
+ */
+
+static const struct uvc_control_info uvc_ctrls[] = {
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_BRIGHTNESS_CONTROL,
+ .index = 0,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_CONTRAST_CONTROL,
+ .index = 1,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_HUE_CONTROL,
+ .index = 2,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_SATURATION_CONTROL,
+ .index = 3,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_SHARPNESS_CONTROL,
+ .index = 4,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_GAMMA_CONTROL,
+ .index = 5,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL,
+ .index = 6,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL,
+ .index = 7,
+ .size = 4,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL,
+ .index = 8,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_GAIN_CONTROL,
+ .index = 9,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+ .index = 10,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_HUE_AUTO_CONTROL,
+ .index = 11,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL,
+ .index = 12,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL,
+ .index = 13,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_DIGITAL_MULTIPLIER_CONTROL,
+ .index = 14,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL,
+ .index = 15,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL,
+ .index = 16,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_GET_CUR,
+ },
+ {
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_ANALOG_LOCK_STATUS_CONTROL,
+ .index = 17,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_GET_CUR,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_SCANNING_MODE_CONTROL,
+ .index = 0,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_AE_MODE_CONTROL,
+ .index = 1,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_GET_RES
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_AE_PRIORITY_CONTROL,
+ .index = 2,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,
+ .index = 3,
+ .size = 4,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL,
+ .index = 4,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_FOCUS_ABSOLUTE_CONTROL,
+ .index = 5,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_FOCUS_RELATIVE_CONTROL,
+ .index = 6,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN
+ | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES
+ | UVC_CTRL_FLAG_GET_DEF
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_IRIS_ABSOLUTE_CONTROL,
+ .index = 7,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_IRIS_RELATIVE_CONTROL,
+ .index = 8,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_ZOOM_ABSOLUTE_CONTROL,
+ .index = 9,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_ZOOM_RELATIVE_CONTROL,
+ .index = 10,
+ .size = 3,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN
+ | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES
+ | UVC_CTRL_FLAG_GET_DEF
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+ .index = 11,
+ .size = 8,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PANTILT_RELATIVE_CONTROL,
+ .index = 12,
+ .size = 4,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_ROLL_ABSOLUTE_CONTROL,
+ .index = 13,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR
+ | UVC_CTRL_FLAG_GET_RANGE
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_ROLL_RELATIVE_CONTROL,
+ .index = 14,
+ .size = 2,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN
+ | UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES
+ | UVC_CTRL_FLAG_GET_DEF
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_FOCUS_AUTO_CONTROL,
+ .index = 17,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+ },
+ {
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PRIVACY_CONTROL,
+ .index = 18,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_RESTORE
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+ {
+ .entity = UVC_GUID_EXT_GPIO_CONTROLLER,
+ .selector = UVC_CT_PRIVACY_CONTROL,
+ .index = 0,
+ .size = 1,
+ .flags = UVC_CTRL_FLAG_GET_CUR
+ | UVC_CTRL_FLAG_AUTO_UPDATE,
+ },
+};
+
+static const u32 uvc_control_classes[] = {
+ V4L2_CID_CAMERA_CLASS,
+ V4L2_CID_USER_CLASS,
+};
+
+static const struct uvc_menu_info power_line_frequency_controls[] = {
+ { 0, "Disabled" },
+ { 1, "50 Hz" },
+ { 2, "60 Hz" },
+ { 3, "Auto" },
+};
+
+static const struct uvc_menu_info exposure_auto_controls[] = {
+ { 2, "Auto Mode" },
+ { 1, "Manual Mode" },
+ { 4, "Shutter Priority Mode" },
+ { 8, "Aperture Priority Mode" },
+};
+
+static s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping,
+ u8 query, const u8 *data)
+{
+ s8 zoom = (s8)data[0];
+
+ switch (query) {
+ case UVC_GET_CUR:
+ return (zoom == 0) ? 0 : (zoom > 0 ? data[2] : -data[2]);
+
+ case UVC_GET_MIN:
+ case UVC_GET_MAX:
+ case UVC_GET_RES:
+ case UVC_GET_DEF:
+ default:
+ return data[2];
+ }
+}
+
+static void uvc_ctrl_set_zoom(struct uvc_control_mapping *mapping,
+ s32 value, u8 *data)
+{
+ data[0] = value == 0 ? 0 : (value > 0) ? 1 : 0xff;
+ data[2] = min((int)abs(value), 0xff);
+}
+
+static s32 uvc_ctrl_get_rel_speed(struct uvc_control_mapping *mapping,
+ u8 query, const u8 *data)
+{
+ unsigned int first = mapping->offset / 8;
+ s8 rel = (s8)data[first];
+
+ switch (query) {
+ case UVC_GET_CUR:
+ return (rel == 0) ? 0 : (rel > 0 ? data[first+1]
+ : -data[first+1]);
+ case UVC_GET_MIN:
+ return -data[first+1];
+ case UVC_GET_MAX:
+ case UVC_GET_RES:
+ case UVC_GET_DEF:
+ default:
+ return data[first+1];
+ }
+}
+
+static void uvc_ctrl_set_rel_speed(struct uvc_control_mapping *mapping,
+ s32 value, u8 *data)
+{
+ unsigned int first = mapping->offset / 8;
+
+ data[first] = value == 0 ? 0 : (value > 0) ? 1 : 0xff;
+ data[first+1] = min_t(int, abs(value), 0xff);
+}
+
+static const struct uvc_control_mapping uvc_ctrl_mappings[] = {
+ {
+ .id = V4L2_CID_BRIGHTNESS,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_BRIGHTNESS_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ },
+ {
+ .id = V4L2_CID_CONTRAST,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_CONTRAST_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_HUE,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_HUE_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ .master_id = V4L2_CID_HUE_AUTO,
+ .master_manual = 0,
+ },
+ {
+ .id = V4L2_CID_SATURATION,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_SATURATION_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_SHARPNESS,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_SHARPNESS_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_GAMMA,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_GAMMA_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_BACKLIGHT_COMPENSATION,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_BACKLIGHT_COMPENSATION_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_GAIN,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_GAIN_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_HUE_AUTO,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_HUE_AUTO_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ .slave_ids = { V4L2_CID_HUE, },
+ },
+ {
+ .id = V4L2_CID_EXPOSURE_AUTO,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_AE_MODE_CONTROL,
+ .size = 4,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_MENU,
+ .data_type = UVC_CTRL_DATA_TYPE_BITMASK,
+ .menu_info = exposure_auto_controls,
+ .menu_mask = GENMASK(V4L2_EXPOSURE_APERTURE_PRIORITY,
+ V4L2_EXPOSURE_AUTO),
+ .slave_ids = { V4L2_CID_EXPOSURE_ABSOLUTE, },
+ },
+ {
+ .id = V4L2_CID_EXPOSURE_AUTO_PRIORITY,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_AE_PRIORITY_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ },
+ {
+ .id = V4L2_CID_EXPOSURE_ABSOLUTE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,
+ .size = 32,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ .master_id = V4L2_CID_EXPOSURE_AUTO,
+ .master_manual = V4L2_EXPOSURE_MANUAL,
+ },
+ {
+ .id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ .slave_ids = { V4L2_CID_WHITE_BALANCE_TEMPERATURE, },
+ },
+ {
+ .id = V4L2_CID_WHITE_BALANCE_TEMPERATURE,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ .master_id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .master_manual = 0,
+ },
+ {
+ .id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ .slave_ids = { V4L2_CID_BLUE_BALANCE,
+ V4L2_CID_RED_BALANCE },
+ },
+ {
+ .id = V4L2_CID_BLUE_BALANCE,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ .master_id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .master_manual = 0,
+ },
+ {
+ .id = V4L2_CID_RED_BALANCE,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL,
+ .size = 16,
+ .offset = 16,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ .master_id = V4L2_CID_AUTO_WHITE_BALANCE,
+ .master_manual = 0,
+ },
+ {
+ .id = V4L2_CID_FOCUS_ABSOLUTE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_FOCUS_ABSOLUTE_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ .master_id = V4L2_CID_FOCUS_AUTO,
+ .master_manual = 0,
+ },
+ {
+ .id = V4L2_CID_FOCUS_AUTO,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_FOCUS_AUTO_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ .slave_ids = { V4L2_CID_FOCUS_ABSOLUTE, },
+ },
+ {
+ .id = V4L2_CID_IRIS_ABSOLUTE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_IRIS_ABSOLUTE_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_IRIS_RELATIVE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_IRIS_RELATIVE_CONTROL,
+ .size = 8,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ },
+ {
+ .id = V4L2_CID_ZOOM_ABSOLUTE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_ZOOM_ABSOLUTE_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_UNSIGNED,
+ },
+ {
+ .id = V4L2_CID_ZOOM_CONTINUOUS,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_ZOOM_RELATIVE_CONTROL,
+ .size = 0,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ .get = uvc_ctrl_get_zoom,
+ .set = uvc_ctrl_set_zoom,
+ },
+ {
+ .id = V4L2_CID_PAN_ABSOLUTE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+ .size = 32,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ },
+ {
+ .id = V4L2_CID_TILT_ABSOLUTE,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+ .size = 32,
+ .offset = 32,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ },
+ {
+ .id = V4L2_CID_PAN_SPEED,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PANTILT_RELATIVE_CONTROL,
+ .size = 16,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ .get = uvc_ctrl_get_rel_speed,
+ .set = uvc_ctrl_set_rel_speed,
+ },
+ {
+ .id = V4L2_CID_TILT_SPEED,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PANTILT_RELATIVE_CONTROL,
+ .size = 16,
+ .offset = 16,
+ .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
+ .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
+ .get = uvc_ctrl_get_rel_speed,
+ .set = uvc_ctrl_set_rel_speed,
+ },
+ {
+ .id = V4L2_CID_PRIVACY,
+ .entity = UVC_GUID_UVC_CAMERA,
+ .selector = UVC_CT_PRIVACY_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ },
+ {
+ .id = V4L2_CID_PRIVACY,
+ .entity = UVC_GUID_EXT_GPIO_CONTROLLER,
+ .selector = UVC_CT_PRIVACY_CONTROL,
+ .size = 1,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_BOOLEAN,
+ .data_type = UVC_CTRL_DATA_TYPE_BOOLEAN,
+ },
+};
+
+const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited = {
+ .id = V4L2_CID_POWER_LINE_FREQUENCY,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+ .size = 2,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_MENU,
+ .data_type = UVC_CTRL_DATA_TYPE_ENUM,
+ .menu_info = power_line_frequency_controls,
+ .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ V4L2_CID_POWER_LINE_FREQUENCY_50HZ),
+};
+
+static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc11 = {
+ .id = V4L2_CID_POWER_LINE_FREQUENCY,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+ .size = 2,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_MENU,
+ .data_type = UVC_CTRL_DATA_TYPE_ENUM,
+ .menu_info = power_line_frequency_controls,
+ .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
+};
+
+static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc11[] = {
+ &uvc_ctrl_power_line_mapping_uvc11,
+ NULL, /* Sentinel */
+};
+
+static const struct uvc_control_mapping uvc_ctrl_power_line_mapping_uvc15 = {
+ .id = V4L2_CID_POWER_LINE_FREQUENCY,
+ .entity = UVC_GUID_UVC_PROCESSING,
+ .selector = UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+ .size = 2,
+ .offset = 0,
+ .v4l2_type = V4L2_CTRL_TYPE_MENU,
+ .data_type = UVC_CTRL_DATA_TYPE_ENUM,
+ .menu_info = power_line_frequency_controls,
+ .menu_mask = GENMASK(V4L2_CID_POWER_LINE_FREQUENCY_AUTO,
+ V4L2_CID_POWER_LINE_FREQUENCY_DISABLED),
+};
+
+static const struct uvc_control_mapping *uvc_ctrl_mappings_uvc15[] = {
+ &uvc_ctrl_power_line_mapping_uvc15,
+ NULL, /* Sentinel */
+};
+
+/* ------------------------------------------------------------------------
+ * Utility functions
+ */
+
+static inline u8 *uvc_ctrl_data(struct uvc_control *ctrl, int id)
+{
+ return ctrl->uvc_data + id * ctrl->info.size;
+}
+
+static inline int uvc_test_bit(const u8 *data, int bit)
+{
+ return (data[bit >> 3] >> (bit & 7)) & 1;
+}
+
+static inline void uvc_clear_bit(u8 *data, int bit)
+{
+ data[bit >> 3] &= ~(1 << (bit & 7));
+}
+
+/*
+ * Extract the bit string specified by mapping->offset and mapping->size
+ * from the little-endian data stored at 'data' and return the result as
+ * a signed 32bit integer. Sign extension will be performed if the mapping
+ * references a signed data type.
+ */
+static s32 uvc_get_le_value(struct uvc_control_mapping *mapping,
+ u8 query, const u8 *data)
+{
+ int bits = mapping->size;
+ int offset = mapping->offset;
+ s32 value = 0;
+ u8 mask;
+
+ data += offset / 8;
+ offset &= 7;
+ mask = ((1LL << bits) - 1) << offset;
+
+ while (1) {
+ u8 byte = *data & mask;
+ value |= offset > 0 ? (byte >> offset) : (byte << (-offset));
+ bits -= 8 - (offset > 0 ? offset : 0);
+ if (bits <= 0)
+ break;
+
+ offset -= 8;
+ mask = (1 << bits) - 1;
+ data++;
+ }
+
+ /* Sign-extend the value if needed. */
+ if (mapping->data_type == UVC_CTRL_DATA_TYPE_SIGNED)
+ value |= -(value & (1 << (mapping->size - 1)));
+
+ return value;
+}
+
+/*
+ * Set the bit string specified by mapping->offset and mapping->size
+ * in the little-endian data stored at 'data' to the value 'value'.
+ */
+static void uvc_set_le_value(struct uvc_control_mapping *mapping,
+ s32 value, u8 *data)
+{
+ int bits = mapping->size;
+ int offset = mapping->offset;
+ u8 mask;
+
+ /*
+ * According to the v4l2 spec, writing any value to a button control
+ * should result in the action belonging to the button control being
+ * triggered. UVC devices however want to see a 1 written -> override
+ * value.
+ */
+ if (mapping->v4l2_type == V4L2_CTRL_TYPE_BUTTON)
+ value = -1;
+
+ data += offset / 8;
+ offset &= 7;
+
+ for (; bits > 0; data++) {
+ mask = ((1LL << bits) - 1) << offset;
+ *data = (*data & ~mask) | ((value << offset) & mask);
+ value >>= offset ? offset : 8;
+ bits -= 8 - offset;
+ offset = 0;
+ }
+}
+
+/* ------------------------------------------------------------------------
+ * Terminal and unit management
+ */
+
+static int uvc_entity_match_guid(const struct uvc_entity *entity,
+ const u8 guid[16])
+{
+ return memcmp(entity->guid, guid, sizeof(entity->guid)) == 0;
+}
+
+/* ------------------------------------------------------------------------
+ * UVC Controls
+ */
+
+static void __uvc_find_control(struct uvc_entity *entity, u32 v4l2_id,
+ struct uvc_control_mapping **mapping, struct uvc_control **control,
+ int next)
+{
+ struct uvc_control *ctrl;
+ struct uvc_control_mapping *map;
+ unsigned int i;
+
+ if (entity == NULL)
+ return;
+
+ for (i = 0; i < entity->ncontrols; ++i) {
+ ctrl = &entity->controls[i];
+ if (!ctrl->initialized)
+ continue;
+
+ list_for_each_entry(map, &ctrl->info.mappings, list) {
+ if ((map->id == v4l2_id) && !next) {
+ *control = ctrl;
+ *mapping = map;
+ return;
+ }
+
+ if ((*mapping == NULL || (*mapping)->id > map->id) &&
+ (map->id > v4l2_id) && next) {
+ *control = ctrl;
+ *mapping = map;
+ }
+ }
+ }
+}
+
+static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain,
+ u32 v4l2_id, struct uvc_control_mapping **mapping)
+{
+ struct uvc_control *ctrl = NULL;
+ struct uvc_entity *entity;
+ int next = v4l2_id & V4L2_CTRL_FLAG_NEXT_CTRL;
+
+ *mapping = NULL;
+
+ /* Mask the query flags. */
+ v4l2_id &= V4L2_CTRL_ID_MASK;
+
+ /* Find the control. */
+ list_for_each_entry(entity, &chain->entities, chain) {
+ __uvc_find_control(entity, v4l2_id, mapping, &ctrl, next);
+ if (ctrl && !next)
+ return ctrl;
+ }
+
+ if (ctrl == NULL && !next)
+ uvc_dbg(chain->dev, CONTROL, "Control 0x%08x not found\n",
+ v4l2_id);
+
+ return ctrl;
+}
+
+static int uvc_ctrl_populate_cache(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl)
+{
+ int ret;
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_DEF) {
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_DEF, ctrl->entity->id,
+ chain->dev->intfnum, ctrl->info.selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF),
+ ctrl->info.size);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN) {
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_MIN, ctrl->entity->id,
+ chain->dev->intfnum, ctrl->info.selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN),
+ ctrl->info.size);
+ if (ret < 0)
+ return ret;
+ }
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX) {
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_MAX, ctrl->entity->id,
+ chain->dev->intfnum, ctrl->info.selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX),
+ ctrl->info.size);
+ if (ret < 0)
+ return ret;
+ }
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES) {
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_RES, ctrl->entity->id,
+ chain->dev->intfnum, ctrl->info.selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES),
+ ctrl->info.size);
+ if (ret < 0) {
+ if (UVC_ENTITY_TYPE(ctrl->entity) !=
+ UVC_VC_EXTENSION_UNIT)
+ return ret;
+
+ /*
+ * GET_RES is mandatory for XU controls, but some
+ * cameras still choke on it. Ignore errors and set the
+ * resolution value to zero.
+ */
+ uvc_warn_once(chain->dev, UVC_WARN_XU_GET_RES,
+ "UVC non compliance - GET_RES failed on "
+ "an XU control. Enabling workaround.\n");
+ memset(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES), 0,
+ ctrl->info.size);
+ }
+ }
+
+ ctrl->cached = 1;
+ return 0;
+}
+
+static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
+ const u8 *data)
+{
+ s32 value = mapping->get(mapping, UVC_GET_CUR, data);
+
+ if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
+ const struct uvc_menu_info *menu = mapping->menu_info;
+ unsigned int i;
+
+ for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
+ if (!test_bit(i, &mapping->menu_mask))
+ continue;
+ if (menu->value == value) {
+ value = i;
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
+static int __uvc_ctrl_load_cur(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl)
+{
+ u8 *data;
+ int ret;
+
+ if (ctrl->loaded)
+ return 0;
+
+ data = uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT);
+
+ if ((ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) == 0) {
+ memset(data, 0, ctrl->info.size);
+ ctrl->loaded = 1;
+
+ return 0;
+ }
+
+ if (ctrl->entity->get_cur)
+ ret = ctrl->entity->get_cur(chain->dev, ctrl->entity,
+ ctrl->info.selector, data,
+ ctrl->info.size);
+ else
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR,
+ ctrl->entity->id, chain->dev->intfnum,
+ ctrl->info.selector, data,
+ ctrl->info.size);
+
+ if (ret < 0)
+ return ret;
+
+ ctrl->loaded = 1;
+
+ return ret;
+}
+
+static int __uvc_ctrl_get(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl,
+ struct uvc_control_mapping *mapping,
+ s32 *value)
+{
+ int ret;
+
+ if ((ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) == 0)
+ return -EACCES;
+
+ ret = __uvc_ctrl_load_cur(chain, ctrl);
+ if (ret < 0)
+ return ret;
+
+ *value = __uvc_ctrl_get_value(mapping,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
+
+ return 0;
+}
+
+static int __uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id,
+ u32 found_id)
+{
+ bool find_next = req_id & V4L2_CTRL_FLAG_NEXT_CTRL;
+ unsigned int i;
+
+ req_id &= V4L2_CTRL_ID_MASK;
+
+ for (i = 0; i < ARRAY_SIZE(uvc_control_classes); i++) {
+ if (!(chain->ctrl_class_bitmap & BIT(i)))
+ continue;
+ if (!find_next) {
+ if (uvc_control_classes[i] == req_id)
+ return i;
+ continue;
+ }
+ if (uvc_control_classes[i] > req_id &&
+ uvc_control_classes[i] < found_id)
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+static int uvc_query_v4l2_class(struct uvc_video_chain *chain, u32 req_id,
+ u32 found_id, struct v4l2_queryctrl *v4l2_ctrl)
+{
+ int idx;
+
+ idx = __uvc_query_v4l2_class(chain, req_id, found_id);
+ if (idx < 0)
+ return -ENODEV;
+
+ memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
+ v4l2_ctrl->id = uvc_control_classes[idx];
+ strscpy(v4l2_ctrl->name, v4l2_ctrl_get_name(v4l2_ctrl->id),
+ sizeof(v4l2_ctrl->name));
+ v4l2_ctrl->type = V4L2_CTRL_TYPE_CTRL_CLASS;
+ v4l2_ctrl->flags = V4L2_CTRL_FLAG_WRITE_ONLY
+ | V4L2_CTRL_FLAG_READ_ONLY;
+ return 0;
+}
+
+/*
+ * Check if control @v4l2_id can be accessed by the given control @ioctl
+ * (VIDIOC_G_EXT_CTRLS, VIDIOC_TRY_EXT_CTRLS or VIDIOC_S_EXT_CTRLS).
+ *
+ * For set operations on slave controls, check if the master's value is set to
+ * manual, either in the others controls set in the same ioctl call, or from
+ * the master's current value. This catches VIDIOC_S_EXT_CTRLS calls that set
+ * both the master and slave control, such as for instance setting
+ * auto_exposure=1, exposure_time_absolute=251.
+ */
+int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
+ const struct v4l2_ext_controls *ctrls,
+ unsigned long ioctl)
+{
+ struct uvc_control_mapping *master_map = NULL;
+ struct uvc_control *master_ctrl = NULL;
+ struct uvc_control_mapping *mapping;
+ struct uvc_control *ctrl;
+ bool read = ioctl == VIDIOC_G_EXT_CTRLS;
+ s32 val;
+ int ret;
+ int i;
+
+ if (__uvc_query_v4l2_class(chain, v4l2_id, 0) >= 0)
+ return -EACCES;
+
+ ctrl = uvc_find_control(chain, v4l2_id, &mapping);
+ if (!ctrl)
+ return -EINVAL;
+
+ if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) && read)
+ return -EACCES;
+
+ if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR) && !read)
+ return -EACCES;
+
+ if (ioctl != VIDIOC_S_EXT_CTRLS || !mapping->master_id)
+ return 0;
+
+ /*
+ * Iterate backwards in cases where the master control is accessed
+ * multiple times in the same ioctl. We want the last value.
+ */
+ for (i = ctrls->count - 1; i >= 0; i--) {
+ if (ctrls->controls[i].id == mapping->master_id)
+ return ctrls->controls[i].value ==
+ mapping->master_manual ? 0 : -EACCES;
+ }
+
+ __uvc_find_control(ctrl->entity, mapping->master_id, &master_map,
+ &master_ctrl, 0);
+
+ if (!master_ctrl || !(master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
+ return 0;
+
+ ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val);
+ if (ret >= 0 && val != mapping->master_manual)
+ return -EACCES;
+
+ return 0;
+}
+
+static const char *uvc_map_get_name(const struct uvc_control_mapping *map)
+{
+ const char *name;
+
+ if (map->name)
+ return map->name;
+
+ name = v4l2_ctrl_get_name(map->id);
+ if (name)
+ return name;
+
+ return "Unknown Control";
+}
+
+static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl,
+ struct uvc_control_mapping *mapping,
+ struct v4l2_queryctrl *v4l2_ctrl)
+{
+ struct uvc_control_mapping *master_map = NULL;
+ struct uvc_control *master_ctrl = NULL;
+ const struct uvc_menu_info *menu;
+ unsigned int i;
+
+ memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
+ v4l2_ctrl->id = mapping->id;
+ v4l2_ctrl->type = mapping->v4l2_type;
+ strscpy(v4l2_ctrl->name, uvc_map_get_name(mapping),
+ sizeof(v4l2_ctrl->name));
+ v4l2_ctrl->flags = 0;
+
+ if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY;
+ if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR))
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ if (mapping->master_id)
+ __uvc_find_control(ctrl->entity, mapping->master_id,
+ &master_map, &master_ctrl, 0);
+ if (master_ctrl && (master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) {
+ s32 val;
+ int ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val != mapping->master_manual)
+ v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE;
+ }
+
+ if (!ctrl->cached) {
+ int ret = uvc_ctrl_populate_cache(chain, ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_DEF) {
+ v4l2_ctrl->default_value = mapping->get(mapping, UVC_GET_DEF,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF));
+ }
+
+ switch (mapping->v4l2_type) {
+ case V4L2_CTRL_TYPE_MENU:
+ v4l2_ctrl->minimum = ffs(mapping->menu_mask) - 1;
+ v4l2_ctrl->maximum = fls(mapping->menu_mask) - 1;
+ v4l2_ctrl->step = 1;
+
+ menu = mapping->menu_info;
+ for (i = 0; BIT(i) <= mapping->menu_mask; ++i, ++menu) {
+ if (!test_bit(i, &mapping->menu_mask))
+ continue;
+ if (menu->value == v4l2_ctrl->default_value) {
+ v4l2_ctrl->default_value = i;
+ break;
+ }
+ }
+
+ return 0;
+
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ v4l2_ctrl->minimum = 0;
+ v4l2_ctrl->maximum = 1;
+ v4l2_ctrl->step = 1;
+ return 0;
+
+ case V4L2_CTRL_TYPE_BUTTON:
+ v4l2_ctrl->minimum = 0;
+ v4l2_ctrl->maximum = 0;
+ v4l2_ctrl->step = 0;
+ return 0;
+
+ default:
+ break;
+ }
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN)
+ v4l2_ctrl->minimum = mapping->get(mapping, UVC_GET_MIN,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN));
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX)
+ v4l2_ctrl->maximum = mapping->get(mapping, UVC_GET_MAX,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)
+ v4l2_ctrl->step = mapping->get(mapping, UVC_GET_RES,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+
+ return 0;
+}
+
+int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
+ struct v4l2_queryctrl *v4l2_ctrl)
+{
+ struct uvc_control *ctrl;
+ struct uvc_control_mapping *mapping;
+ int ret;
+
+ ret = mutex_lock_interruptible(&chain->ctrl_mutex);
+ if (ret < 0)
+ return -ERESTARTSYS;
+
+ /* Check if the ctrl is a know class */
+ if (!(v4l2_ctrl->id & V4L2_CTRL_FLAG_NEXT_CTRL)) {
+ ret = uvc_query_v4l2_class(chain, v4l2_ctrl->id, 0, v4l2_ctrl);
+ if (!ret)
+ goto done;
+ }
+
+ ctrl = uvc_find_control(chain, v4l2_ctrl->id, &mapping);
+ if (ctrl == NULL) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * If we're enumerating control with V4L2_CTRL_FLAG_NEXT_CTRL, check if
+ * a class should be inserted between the previous control and the one
+ * we have just found.
+ */
+ if (v4l2_ctrl->id & V4L2_CTRL_FLAG_NEXT_CTRL) {
+ ret = uvc_query_v4l2_class(chain, v4l2_ctrl->id, mapping->id,
+ v4l2_ctrl);
+ if (!ret)
+ goto done;
+ }
+
+ ret = __uvc_query_v4l2_ctrl(chain, ctrl, mapping, v4l2_ctrl);
+done:
+ mutex_unlock(&chain->ctrl_mutex);
+ return ret;
+}
+
+/*
+ * Mapping V4L2 controls to UVC controls can be straightforward if done well.
+ * Most of the UVC controls exist in V4L2, and can be mapped directly. Some
+ * must be grouped (for instance the Red Balance, Blue Balance and Do White
+ * Balance V4L2 controls use the White Balance Component UVC control) or
+ * otherwise translated. The approach we take here is to use a translation
+ * table for the controls that can be mapped directly, and handle the others
+ * manually.
+ */
+int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
+ struct v4l2_querymenu *query_menu)
+{
+ const struct uvc_menu_info *menu_info;
+ struct uvc_control_mapping *mapping;
+ struct uvc_control *ctrl;
+ u32 index = query_menu->index;
+ u32 id = query_menu->id;
+ int ret;
+
+ memset(query_menu, 0, sizeof(*query_menu));
+ query_menu->id = id;
+ query_menu->index = index;
+
+ if (index >= BITS_PER_TYPE(mapping->menu_mask))
+ return -EINVAL;
+
+ ret = mutex_lock_interruptible(&chain->ctrl_mutex);
+ if (ret < 0)
+ return -ERESTARTSYS;
+
+ ctrl = uvc_find_control(chain, query_menu->id, &mapping);
+ if (ctrl == NULL || mapping->v4l2_type != V4L2_CTRL_TYPE_MENU) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (!test_bit(query_menu->index, &mapping->menu_mask)) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ menu_info = &mapping->menu_info[query_menu->index];
+
+ if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
+ (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
+ s32 bitmap;
+
+ if (!ctrl->cached) {
+ ret = uvc_ctrl_populate_cache(chain, ctrl);
+ if (ret < 0)
+ goto done;
+ }
+
+ bitmap = mapping->get(mapping, UVC_GET_RES,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+ if (!(bitmap & menu_info->value)) {
+ ret = -EINVAL;
+ goto done;
+ }
+ }
+
+ strscpy(query_menu->name, menu_info->name, sizeof(query_menu->name));
+
+done:
+ mutex_unlock(&chain->ctrl_mutex);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Ctrl event handling
+ */
+
+static void uvc_ctrl_fill_event(struct uvc_video_chain *chain,
+ struct v4l2_event *ev,
+ struct uvc_control *ctrl,
+ struct uvc_control_mapping *mapping,
+ s32 value, u32 changes)
+{
+ struct v4l2_queryctrl v4l2_ctrl;
+
+ __uvc_query_v4l2_ctrl(chain, ctrl, mapping, &v4l2_ctrl);
+
+ memset(ev, 0, sizeof(*ev));
+ ev->type = V4L2_EVENT_CTRL;
+ ev->id = v4l2_ctrl.id;
+ ev->u.ctrl.value = value;
+ ev->u.ctrl.changes = changes;
+ ev->u.ctrl.type = v4l2_ctrl.type;
+ ev->u.ctrl.flags = v4l2_ctrl.flags;
+ ev->u.ctrl.minimum = v4l2_ctrl.minimum;
+ ev->u.ctrl.maximum = v4l2_ctrl.maximum;
+ ev->u.ctrl.step = v4l2_ctrl.step;
+ ev->u.ctrl.default_value = v4l2_ctrl.default_value;
+}
+
+/*
+ * Send control change events to all subscribers for the @ctrl control. By
+ * default the subscriber that generated the event, as identified by @handle,
+ * is not notified unless it has set the V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK flag.
+ * @handle can be NULL for asynchronous events related to auto-update controls,
+ * in which case all subscribers are notified.
+ */
+static void uvc_ctrl_send_event(struct uvc_video_chain *chain,
+ struct uvc_fh *handle, struct uvc_control *ctrl,
+ struct uvc_control_mapping *mapping, s32 value, u32 changes)
+{
+ struct v4l2_fh *originator = handle ? &handle->vfh : NULL;
+ struct v4l2_subscribed_event *sev;
+ struct v4l2_event ev;
+
+ if (list_empty(&mapping->ev_subs))
+ return;
+
+ uvc_ctrl_fill_event(chain, &ev, ctrl, mapping, value, changes);
+
+ list_for_each_entry(sev, &mapping->ev_subs, node) {
+ if (sev->fh != originator ||
+ (sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK) ||
+ (changes & V4L2_EVENT_CTRL_CH_FLAGS))
+ v4l2_event_queue_fh(sev->fh, &ev);
+ }
+}
+
+/*
+ * Send control change events for the slave of the @master control identified
+ * by the V4L2 ID @slave_id. The @handle identifies the event subscriber that
+ * generated the event and may be NULL for auto-update events.
+ */
+static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain,
+ struct uvc_fh *handle, struct uvc_control *master, u32 slave_id)
+{
+ struct uvc_control_mapping *mapping = NULL;
+ struct uvc_control *ctrl = NULL;
+ u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
+ s32 val = 0;
+
+ __uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0);
+ if (ctrl == NULL)
+ return;
+
+ if (__uvc_ctrl_get(chain, ctrl, mapping, &val) == 0)
+ changes |= V4L2_EVENT_CTRL_CH_VALUE;
+
+ uvc_ctrl_send_event(chain, handle, ctrl, mapping, val, changes);
+}
+
+void uvc_ctrl_status_event(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, const u8 *data)
+{
+ struct uvc_control_mapping *mapping;
+ struct uvc_fh *handle;
+ unsigned int i;
+
+ mutex_lock(&chain->ctrl_mutex);
+
+ handle = ctrl->handle;
+ ctrl->handle = NULL;
+
+ list_for_each_entry(mapping, &ctrl->info.mappings, list) {
+ s32 value = __uvc_ctrl_get_value(mapping, data);
+
+ /*
+ * handle may be NULL here if the device sends auto-update
+ * events without a prior related control set from userspace.
+ */
+ for (i = 0; i < ARRAY_SIZE(mapping->slave_ids); ++i) {
+ if (!mapping->slave_ids[i])
+ break;
+
+ uvc_ctrl_send_slave_event(chain, handle, ctrl,
+ mapping->slave_ids[i]);
+ }
+
+ uvc_ctrl_send_event(chain, handle, ctrl, mapping, value,
+ V4L2_EVENT_CTRL_CH_VALUE);
+ }
+
+ mutex_unlock(&chain->ctrl_mutex);
+}
+
+static void uvc_ctrl_status_event_work(struct work_struct *work)
+{
+ struct uvc_device *dev = container_of(work, struct uvc_device,
+ async_ctrl.work);
+ struct uvc_ctrl_work *w = &dev->async_ctrl;
+ int ret;
+
+ uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
+
+ /* The barrier is needed to synchronize with uvc_status_stop(). */
+ if (smp_load_acquire(&dev->flush_status))
+ return;
+
+ /* Resubmit the URB. */
+ w->urb->interval = dev->int_ep->desc.bInterval;
+ ret = usb_submit_urb(w->urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&dev->udev->dev,
+ "Failed to resubmit status URB (%d).\n", ret);
+}
+
+bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, const u8 *data)
+{
+ struct uvc_device *dev = chain->dev;
+ struct uvc_ctrl_work *w = &dev->async_ctrl;
+
+ if (list_empty(&ctrl->info.mappings)) {
+ ctrl->handle = NULL;
+ return false;
+ }
+
+ w->data = data;
+ w->urb = urb;
+ w->chain = chain;
+ w->ctrl = ctrl;
+
+ schedule_work(&w->work);
+
+ return true;
+}
+
+static bool uvc_ctrl_xctrls_has_control(const struct v4l2_ext_control *xctrls,
+ unsigned int xctrls_count, u32 id)
+{
+ unsigned int i;
+
+ for (i = 0; i < xctrls_count; ++i) {
+ if (xctrls[i].id == id)
+ return true;
+ }
+
+ return false;
+}
+
+static void uvc_ctrl_send_events(struct uvc_fh *handle,
+ const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
+{
+ struct uvc_control_mapping *mapping;
+ struct uvc_control *ctrl;
+ u32 changes = V4L2_EVENT_CTRL_CH_VALUE;
+ unsigned int i;
+ unsigned int j;
+
+ for (i = 0; i < xctrls_count; ++i) {
+ ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping);
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+ /* Notification will be sent from an Interrupt event. */
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(mapping->slave_ids); ++j) {
+ u32 slave_id = mapping->slave_ids[j];
+
+ if (!slave_id)
+ break;
+
+ /*
+ * We can skip sending an event for the slave if the
+ * slave is being modified in the same transaction.
+ */
+ if (uvc_ctrl_xctrls_has_control(xctrls, xctrls_count,
+ slave_id))
+ continue;
+
+ uvc_ctrl_send_slave_event(handle->chain, handle, ctrl,
+ slave_id);
+ }
+
+ /*
+ * If the master is being modified in the same transaction
+ * flags may change too.
+ */
+ if (mapping->master_id &&
+ uvc_ctrl_xctrls_has_control(xctrls, xctrls_count,
+ mapping->master_id))
+ changes |= V4L2_EVENT_CTRL_CH_FLAGS;
+
+ uvc_ctrl_send_event(handle->chain, handle, ctrl, mapping,
+ xctrls[i].value, changes);
+ }
+}
+
+static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems)
+{
+ struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh);
+ struct uvc_control_mapping *mapping;
+ struct uvc_control *ctrl;
+ int ret;
+
+ ret = mutex_lock_interruptible(&handle->chain->ctrl_mutex);
+ if (ret < 0)
+ return -ERESTARTSYS;
+
+ if (__uvc_query_v4l2_class(handle->chain, sev->id, 0) >= 0) {
+ ret = 0;
+ goto done;
+ }
+
+ ctrl = uvc_find_control(handle->chain, sev->id, &mapping);
+ if (ctrl == NULL) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ list_add_tail(&sev->node, &mapping->ev_subs);
+ if (sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL) {
+ struct v4l2_event ev;
+ u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
+ s32 val = 0;
+
+ if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
+ changes |= V4L2_EVENT_CTRL_CH_VALUE;
+
+ uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, val,
+ changes);
+ /*
+ * Mark the queue as active, allowing this initial event to be
+ * accepted.
+ */
+ sev->elems = elems;
+ v4l2_event_queue_fh(sev->fh, &ev);
+ }
+
+done:
+ mutex_unlock(&handle->chain->ctrl_mutex);
+ return ret;
+}
+
+static void uvc_ctrl_del_event(struct v4l2_subscribed_event *sev)
+{
+ struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh);
+
+ mutex_lock(&handle->chain->ctrl_mutex);
+ if (__uvc_query_v4l2_class(handle->chain, sev->id, 0) >= 0)
+ goto done;
+ list_del(&sev->node);
+done:
+ mutex_unlock(&handle->chain->ctrl_mutex);
+}
+
+const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops = {
+ .add = uvc_ctrl_add_event,
+ .del = uvc_ctrl_del_event,
+ .replace = v4l2_ctrl_replace,
+ .merge = v4l2_ctrl_merge,
+};
+
+/* --------------------------------------------------------------------------
+ * Control transactions
+ *
+ * To make extended set operations as atomic as the hardware allows, controls
+ * are handled using begin/commit/rollback operations.
+ *
+ * At the beginning of a set request, uvc_ctrl_begin should be called to
+ * initialize the request. This function acquires the control lock.
+ *
+ * When setting a control, the new value is stored in the control data field
+ * at position UVC_CTRL_DATA_CURRENT. The control is then marked as dirty for
+ * later processing. If the UVC and V4L2 control sizes differ, the current
+ * value is loaded from the hardware before storing the new value in the data
+ * field.
+ *
+ * After processing all controls in the transaction, uvc_ctrl_commit or
+ * uvc_ctrl_rollback must be called to apply the pending changes to the
+ * hardware or revert them. When applying changes, all controls marked as
+ * dirty will be modified in the UVC device, and the dirty flag will be
+ * cleared. When reverting controls, the control data field
+ * UVC_CTRL_DATA_CURRENT is reverted to its previous value
+ * (UVC_CTRL_DATA_BACKUP) for all dirty controls. Both functions release the
+ * control lock.
+ */
+int uvc_ctrl_begin(struct uvc_video_chain *chain)
+{
+ return mutex_lock_interruptible(&chain->ctrl_mutex) ? -ERESTARTSYS : 0;
+}
+
+static int uvc_ctrl_commit_entity(struct uvc_device *dev,
+ struct uvc_entity *entity, int rollback, struct uvc_control **err_ctrl)
+{
+ struct uvc_control *ctrl;
+ unsigned int i;
+ int ret;
+
+ if (entity == NULL)
+ return 0;
+
+ for (i = 0; i < entity->ncontrols; ++i) {
+ ctrl = &entity->controls[i];
+ if (!ctrl->initialized)
+ continue;
+
+ /*
+ * Reset the loaded flag for auto-update controls that were
+ * marked as loaded in uvc_ctrl_get/uvc_ctrl_set to prevent
+ * uvc_ctrl_get from using the cached value, and for write-only
+ * controls to prevent uvc_ctrl_set from setting bits not
+ * explicitly set by the user.
+ */
+ if (ctrl->info.flags & UVC_CTRL_FLAG_AUTO_UPDATE ||
+ !(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
+ ctrl->loaded = 0;
+
+ if (!ctrl->dirty)
+ continue;
+
+ if (!rollback)
+ ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
+ dev->intfnum, ctrl->info.selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+ ctrl->info.size);
+ else
+ ret = 0;
+
+ if (rollback || ret < 0)
+ memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
+ ctrl->info.size);
+
+ ctrl->dirty = 0;
+
+ if (ret < 0) {
+ if (err_ctrl)
+ *err_ctrl = ctrl;
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int uvc_ctrl_find_ctrl_idx(struct uvc_entity *entity,
+ struct v4l2_ext_controls *ctrls,
+ struct uvc_control *uvc_control)
+{
+ struct uvc_control_mapping *mapping = NULL;
+ struct uvc_control *ctrl_found = NULL;
+ unsigned int i;
+
+ if (!entity)
+ return ctrls->count;
+
+ for (i = 0; i < ctrls->count; i++) {
+ __uvc_find_control(entity, ctrls->controls[i].id, &mapping,
+ &ctrl_found, 0);
+ if (uvc_control == ctrl_found)
+ return i;
+ }
+
+ return ctrls->count;
+}
+
+int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
+ struct v4l2_ext_controls *ctrls)
+{
+ struct uvc_video_chain *chain = handle->chain;
+ struct uvc_control *err_ctrl;
+ struct uvc_entity *entity;
+ int ret = 0;
+
+ /* Find the control. */
+ list_for_each_entry(entity, &chain->entities, chain) {
+ ret = uvc_ctrl_commit_entity(chain->dev, entity, rollback,
+ &err_ctrl);
+ if (ret < 0)
+ goto done;
+ }
+
+ if (!rollback)
+ uvc_ctrl_send_events(handle, ctrls->controls, ctrls->count);
+done:
+ if (ret < 0 && ctrls)
+ ctrls->error_idx = uvc_ctrl_find_ctrl_idx(entity, ctrls,
+ err_ctrl);
+ mutex_unlock(&chain->ctrl_mutex);
+ return ret;
+}
+
+int uvc_ctrl_get(struct uvc_video_chain *chain,
+ struct v4l2_ext_control *xctrl)
+{
+ struct uvc_control *ctrl;
+ struct uvc_control_mapping *mapping;
+
+ if (__uvc_query_v4l2_class(chain, xctrl->id, 0) >= 0)
+ return -EACCES;
+
+ ctrl = uvc_find_control(chain, xctrl->id, &mapping);
+ if (ctrl == NULL)
+ return -EINVAL;
+
+ return __uvc_ctrl_get(chain, ctrl, mapping, &xctrl->value);
+}
+
+int uvc_ctrl_set(struct uvc_fh *handle,
+ struct v4l2_ext_control *xctrl)
+{
+ struct uvc_video_chain *chain = handle->chain;
+ struct uvc_control *ctrl;
+ struct uvc_control_mapping *mapping;
+ s32 value;
+ u32 step;
+ s32 min;
+ s32 max;
+ int ret;
+
+ if (__uvc_query_v4l2_class(chain, xctrl->id, 0) >= 0)
+ return -EACCES;
+
+ ctrl = uvc_find_control(chain, xctrl->id, &mapping);
+ if (ctrl == NULL)
+ return -EINVAL;
+ if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR))
+ return -EACCES;
+
+ /* Clamp out of range values. */
+ switch (mapping->v4l2_type) {
+ case V4L2_CTRL_TYPE_INTEGER:
+ if (!ctrl->cached) {
+ ret = uvc_ctrl_populate_cache(chain, ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ min = mapping->get(mapping, UVC_GET_MIN,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN));
+ max = mapping->get(mapping, UVC_GET_MAX,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
+ step = mapping->get(mapping, UVC_GET_RES,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+ if (step == 0)
+ step = 1;
+
+ xctrl->value = min + DIV_ROUND_CLOSEST((u32)(xctrl->value - min),
+ step) * step;
+ if (mapping->data_type == UVC_CTRL_DATA_TYPE_SIGNED)
+ xctrl->value = clamp(xctrl->value, min, max);
+ else
+ xctrl->value = clamp_t(u32, xctrl->value, min, max);
+ value = xctrl->value;
+ break;
+
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ xctrl->value = clamp(xctrl->value, 0, 1);
+ value = xctrl->value;
+ break;
+
+ case V4L2_CTRL_TYPE_MENU:
+ if (xctrl->value < (ffs(mapping->menu_mask) - 1) ||
+ xctrl->value > (fls(mapping->menu_mask) - 1))
+ return -ERANGE;
+
+ if (!test_bit(xctrl->value, &mapping->menu_mask))
+ return -EINVAL;
+
+ value = mapping->menu_info[xctrl->value].value;
+
+ /*
+ * Valid menu indices are reported by the GET_RES request for
+ * UVC controls that support it.
+ */
+ if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
+ (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
+ if (!ctrl->cached) {
+ ret = uvc_ctrl_populate_cache(chain, ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ step = mapping->get(mapping, UVC_GET_RES,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+ if (!(step & value))
+ return -EINVAL;
+ }
+
+ break;
+
+ default:
+ value = xctrl->value;
+ break;
+ }
+
+ /*
+ * If the mapping doesn't span the whole UVC control, the current value
+ * needs to be loaded from the device to perform the read-modify-write
+ * operation.
+ */
+ if ((ctrl->info.size * 8) != mapping->size) {
+ ret = __uvc_ctrl_load_cur(chain, ctrl);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Backup the current value in case we need to rollback later. */
+ if (!ctrl->dirty) {
+ memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+ ctrl->info.size);
+ }
+
+ mapping->set(mapping, value,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
+
+ if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+ ctrl->handle = handle;
+
+ ctrl->dirty = 1;
+ ctrl->modified = 1;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Dynamic controls
+ */
+
+/*
+ * Retrieve flags for a given control
+ */
+static int uvc_ctrl_get_flags(struct uvc_device *dev,
+ const struct uvc_control *ctrl,
+ struct uvc_control_info *info)
+{
+ u8 *data;
+ int ret;
+
+ data = kmalloc(1, GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ if (ctrl->entity->get_info)
+ ret = ctrl->entity->get_info(dev, ctrl->entity,
+ ctrl->info.selector, data);
+ else
+ ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id,
+ dev->intfnum, info->selector, data, 1);
+ if (!ret)
+ info->flags |= (data[0] & UVC_CONTROL_CAP_GET ?
+ UVC_CTRL_FLAG_GET_CUR : 0)
+ | (data[0] & UVC_CONTROL_CAP_SET ?
+ UVC_CTRL_FLAG_SET_CUR : 0)
+ | (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ?
+ UVC_CTRL_FLAG_AUTO_UPDATE : 0)
+ | (data[0] & UVC_CONTROL_CAP_ASYNCHRONOUS ?
+ UVC_CTRL_FLAG_ASYNCHRONOUS : 0);
+
+ kfree(data);
+ return ret;
+}
+
+static void uvc_ctrl_fixup_xu_info(struct uvc_device *dev,
+ const struct uvc_control *ctrl, struct uvc_control_info *info)
+{
+ struct uvc_ctrl_fixup {
+ struct usb_device_id id;
+ u8 entity;
+ u8 selector;
+ u8 flags;
+ };
+
+ static const struct uvc_ctrl_fixup fixups[] = {
+ { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
+ UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+ UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
+ UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+ UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ { { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
+ UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+ UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+ UVC_CTRL_FLAG_AUTO_UPDATE },
+ };
+
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
+ if (!usb_match_one_id(dev->intf, &fixups[i].id))
+ continue;
+
+ if (fixups[i].entity == ctrl->entity->id &&
+ fixups[i].selector == info->selector) {
+ info->flags = fixups[i].flags;
+ return;
+ }
+ }
+}
+
+/*
+ * Query control information (size and flags) for XU controls.
+ */
+static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
+ const struct uvc_control *ctrl, struct uvc_control_info *info)
+{
+ u8 *data;
+ int ret;
+
+ data = kmalloc(2, GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ memcpy(info->entity, ctrl->entity->guid, sizeof(info->entity));
+ info->index = ctrl->index;
+ info->selector = ctrl->index + 1;
+
+ /* Query and verify the control length (GET_LEN) */
+ ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id, dev->intfnum,
+ info->selector, data, 2);
+ if (ret < 0) {
+ uvc_dbg(dev, CONTROL,
+ "GET_LEN failed on control %pUl/%u (%d)\n",
+ info->entity, info->selector, ret);
+ goto done;
+ }
+
+ info->size = le16_to_cpup((__le16 *)data);
+
+ info->flags = UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX
+ | UVC_CTRL_FLAG_GET_RES | UVC_CTRL_FLAG_GET_DEF;
+
+ ret = uvc_ctrl_get_flags(dev, ctrl, info);
+ if (ret < 0) {
+ uvc_dbg(dev, CONTROL,
+ "Failed to get flags for control %pUl/%u (%d)\n",
+ info->entity, info->selector, ret);
+ goto done;
+ }
+
+ uvc_ctrl_fixup_xu_info(dev, ctrl, info);
+
+ uvc_dbg(dev, CONTROL,
+ "XU control %pUl/%u queried: len %u, flags { get %u set %u auto %u }\n",
+ info->entity, info->selector, info->size,
+ (info->flags & UVC_CTRL_FLAG_GET_CUR) ? 1 : 0,
+ (info->flags & UVC_CTRL_FLAG_SET_CUR) ? 1 : 0,
+ (info->flags & UVC_CTRL_FLAG_AUTO_UPDATE) ? 1 : 0);
+
+done:
+ kfree(data);
+ return ret;
+}
+
+static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl,
+ const struct uvc_control_info *info);
+
+static int uvc_ctrl_init_xu_ctrl(struct uvc_device *dev,
+ struct uvc_control *ctrl)
+{
+ struct uvc_control_info info;
+ int ret;
+
+ if (ctrl->initialized)
+ return 0;
+
+ ret = uvc_ctrl_fill_xu_info(dev, ctrl, &info);
+ if (ret < 0)
+ return ret;
+
+ ret = uvc_ctrl_add_info(dev, ctrl, &info);
+ if (ret < 0)
+ uvc_dbg(dev, CONTROL,
+ "Failed to initialize control %pUl/%u on device %s entity %u\n",
+ info.entity, info.selector, dev->udev->devpath,
+ ctrl->entity->id);
+
+ return ret;
+}
+
+int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
+ struct uvc_xu_control_query *xqry)
+{
+ struct uvc_entity *entity;
+ struct uvc_control *ctrl;
+ unsigned int i;
+ bool found;
+ u32 reqflags;
+ u16 size;
+ u8 *data = NULL;
+ int ret;
+
+ /* Find the extension unit. */
+ found = false;
+ list_for_each_entry(entity, &chain->entities, chain) {
+ if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT &&
+ entity->id == xqry->unit) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ uvc_dbg(chain->dev, CONTROL, "Extension unit %u not found\n",
+ xqry->unit);
+ return -ENOENT;
+ }
+
+ /* Find the control and perform delayed initialization if needed. */
+ found = false;
+ for (i = 0; i < entity->ncontrols; ++i) {
+ ctrl = &entity->controls[i];
+ if (ctrl->index == xqry->selector - 1) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ uvc_dbg(chain->dev, CONTROL, "Control %pUl/%u not found\n",
+ entity->guid, xqry->selector);
+ return -ENOENT;
+ }
+
+ if (mutex_lock_interruptible(&chain->ctrl_mutex))
+ return -ERESTARTSYS;
+
+ ret = uvc_ctrl_init_xu_ctrl(chain->dev, ctrl);
+ if (ret < 0) {
+ ret = -ENOENT;
+ goto done;
+ }
+
+ /* Validate the required buffer size and flags for the request */
+ reqflags = 0;
+ size = ctrl->info.size;
+
+ switch (xqry->query) {
+ case UVC_GET_CUR:
+ reqflags = UVC_CTRL_FLAG_GET_CUR;
+ break;
+ case UVC_GET_MIN:
+ reqflags = UVC_CTRL_FLAG_GET_MIN;
+ break;
+ case UVC_GET_MAX:
+ reqflags = UVC_CTRL_FLAG_GET_MAX;
+ break;
+ case UVC_GET_DEF:
+ reqflags = UVC_CTRL_FLAG_GET_DEF;
+ break;
+ case UVC_GET_RES:
+ reqflags = UVC_CTRL_FLAG_GET_RES;
+ break;
+ case UVC_SET_CUR:
+ reqflags = UVC_CTRL_FLAG_SET_CUR;
+ break;
+ case UVC_GET_LEN:
+ size = 2;
+ break;
+ case UVC_GET_INFO:
+ size = 1;
+ break;
+ default:
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (size != xqry->size) {
+ ret = -ENOBUFS;
+ goto done;
+ }
+
+ if (reqflags && !(ctrl->info.flags & reqflags)) {
+ ret = -EBADRQC;
+ goto done;
+ }
+
+ data = kmalloc(size, GFP_KERNEL);
+ if (data == NULL) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ if (xqry->query == UVC_SET_CUR &&
+ copy_from_user(data, xqry->data, size)) {
+ ret = -EFAULT;
+ goto done;
+ }
+
+ ret = uvc_query_ctrl(chain->dev, xqry->query, xqry->unit,
+ chain->dev->intfnum, xqry->selector, data, size);
+ if (ret < 0)
+ goto done;
+
+ if (xqry->query != UVC_SET_CUR &&
+ copy_to_user(xqry->data, data, size))
+ ret = -EFAULT;
+done:
+ kfree(data);
+ mutex_unlock(&chain->ctrl_mutex);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+/*
+ * Restore control values after resume, skipping controls that haven't been
+ * changed.
+ *
+ * TODO
+ * - Don't restore modified controls that are back to their default value.
+ * - Handle restore order (Auto-Exposure Mode should be restored before
+ * Exposure Time).
+ */
+int uvc_ctrl_restore_values(struct uvc_device *dev)
+{
+ struct uvc_control *ctrl;
+ struct uvc_entity *entity;
+ unsigned int i;
+ int ret;
+
+ /* Walk the entities list and restore controls when possible. */
+ list_for_each_entry(entity, &dev->entities, list) {
+
+ for (i = 0; i < entity->ncontrols; ++i) {
+ ctrl = &entity->controls[i];
+
+ if (!ctrl->initialized || !ctrl->modified ||
+ (ctrl->info.flags & UVC_CTRL_FLAG_RESTORE) == 0)
+ continue;
+ dev_dbg(&dev->udev->dev,
+ "restoring control %pUl/%u/%u\n",
+ ctrl->info.entity, ctrl->info.index,
+ ctrl->info.selector);
+ ctrl->dirty = 1;
+ }
+
+ ret = uvc_ctrl_commit_entity(dev, entity, 0, NULL);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Control and mapping handling
+ */
+
+/*
+ * Add control information to a given control.
+ */
+static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl,
+ const struct uvc_control_info *info)
+{
+ ctrl->info = *info;
+ INIT_LIST_HEAD(&ctrl->info.mappings);
+
+ /* Allocate an array to save control values (cur, def, max, etc.) */
+ ctrl->uvc_data = kzalloc(ctrl->info.size * UVC_CTRL_DATA_LAST + 1,
+ GFP_KERNEL);
+ if (!ctrl->uvc_data)
+ return -ENOMEM;
+
+ ctrl->initialized = 1;
+
+ uvc_dbg(dev, CONTROL, "Added control %pUl/%u to device %s entity %u\n",
+ ctrl->info.entity, ctrl->info.selector, dev->udev->devpath,
+ ctrl->entity->id);
+
+ return 0;
+}
+
+/*
+ * Add a control mapping to a given control.
+ */
+static int __uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, const struct uvc_control_mapping *mapping)
+{
+ struct uvc_control_mapping *map;
+ unsigned int size;
+ unsigned int i;
+
+ /*
+ * Most mappings come from static kernel data and need to be duplicated.
+ * Mappings that come from userspace will be unnecessarily duplicated,
+ * this could be optimized.
+ */
+ map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL);
+ if (map == NULL)
+ return -ENOMEM;
+
+ /* For UVCIOC_CTRL_MAP custom control */
+ if (mapping->name) {
+ map->name = kstrdup(mapping->name, GFP_KERNEL);
+ if (!map->name) {
+ kfree(map);
+ return -ENOMEM;
+ }
+ }
+
+ INIT_LIST_HEAD(&map->ev_subs);
+
+ size = sizeof(*mapping->menu_info) * fls(mapping->menu_mask);
+ map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
+ if (map->menu_info == NULL) {
+ kfree(map->name);
+ kfree(map);
+ return -ENOMEM;
+ }
+
+ if (map->get == NULL)
+ map->get = uvc_get_le_value;
+ if (map->set == NULL)
+ map->set = uvc_set_le_value;
+
+ for (i = 0; i < ARRAY_SIZE(uvc_control_classes); i++) {
+ if (V4L2_CTRL_ID2WHICH(uvc_control_classes[i]) ==
+ V4L2_CTRL_ID2WHICH(map->id)) {
+ chain->ctrl_class_bitmap |= BIT(i);
+ break;
+ }
+ }
+
+ list_add_tail(&map->list, &ctrl->info.mappings);
+ uvc_dbg(chain->dev, CONTROL, "Adding mapping '%s' to control %pUl/%u\n",
+ uvc_map_get_name(map), ctrl->info.entity,
+ ctrl->info.selector);
+
+ return 0;
+}
+
+int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
+ const struct uvc_control_mapping *mapping)
+{
+ struct uvc_device *dev = chain->dev;
+ struct uvc_control_mapping *map;
+ struct uvc_entity *entity;
+ struct uvc_control *ctrl;
+ int found = 0;
+ int ret;
+
+ if (mapping->id & ~V4L2_CTRL_ID_MASK) {
+ uvc_dbg(dev, CONTROL,
+ "Can't add mapping '%s', control id 0x%08x is invalid\n",
+ uvc_map_get_name(mapping), mapping->id);
+ return -EINVAL;
+ }
+
+ /* Search for the matching (GUID/CS) control on the current chain */
+ list_for_each_entry(entity, &chain->entities, chain) {
+ unsigned int i;
+
+ if (UVC_ENTITY_TYPE(entity) != UVC_VC_EXTENSION_UNIT ||
+ !uvc_entity_match_guid(entity, mapping->entity))
+ continue;
+
+ for (i = 0; i < entity->ncontrols; ++i) {
+ ctrl = &entity->controls[i];
+ if (ctrl->index == mapping->selector - 1) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found)
+ break;
+ }
+ if (!found)
+ return -ENOENT;
+
+ if (mutex_lock_interruptible(&chain->ctrl_mutex))
+ return -ERESTARTSYS;
+
+ /* Perform delayed initialization of XU controls */
+ ret = uvc_ctrl_init_xu_ctrl(dev, ctrl);
+ if (ret < 0) {
+ ret = -ENOENT;
+ goto done;
+ }
+
+ /* Validate the user-provided bit-size and offset */
+ if (mapping->size > 32 ||
+ mapping->offset + mapping->size > ctrl->info.size * 8) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ list_for_each_entry(map, &ctrl->info.mappings, list) {
+ if (mapping->id == map->id) {
+ uvc_dbg(dev, CONTROL,
+ "Can't add mapping '%s', control id 0x%08x already exists\n",
+ uvc_map_get_name(mapping), mapping->id);
+ ret = -EEXIST;
+ goto done;
+ }
+ }
+
+ /* Prevent excess memory consumption */
+ if (atomic_inc_return(&dev->nmappings) > UVC_MAX_CONTROL_MAPPINGS) {
+ atomic_dec(&dev->nmappings);
+ uvc_dbg(dev, CONTROL,
+ "Can't add mapping '%s', maximum mappings count (%u) exceeded\n",
+ uvc_map_get_name(mapping), UVC_MAX_CONTROL_MAPPINGS);
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ ret = __uvc_ctrl_add_mapping(chain, ctrl, mapping);
+ if (ret < 0)
+ atomic_dec(&dev->nmappings);
+
+done:
+ mutex_unlock(&chain->ctrl_mutex);
+ return ret;
+}
+
+/*
+ * Prune an entity of its bogus controls using a blacklist. Bogus controls
+ * are currently the ones that crash the camera or unconditionally return an
+ * error when queried.
+ */
+static void uvc_ctrl_prune_entity(struct uvc_device *dev,
+ struct uvc_entity *entity)
+{
+ struct uvc_ctrl_blacklist {
+ struct usb_device_id id;
+ u8 index;
+ };
+
+ static const struct uvc_ctrl_blacklist processing_blacklist[] = {
+ { { USB_DEVICE(0x13d3, 0x509b) }, 9 }, /* Gain */
+ { { USB_DEVICE(0x1c4f, 0x3000) }, 6 }, /* WB Temperature */
+ { { USB_DEVICE(0x5986, 0x0241) }, 2 }, /* Hue */
+ };
+ static const struct uvc_ctrl_blacklist camera_blacklist[] = {
+ { { USB_DEVICE(0x06f8, 0x3005) }, 9 }, /* Zoom, Absolute */
+ };
+
+ const struct uvc_ctrl_blacklist *blacklist;
+ unsigned int size;
+ unsigned int count;
+ unsigned int i;
+ u8 *controls;
+
+ switch (UVC_ENTITY_TYPE(entity)) {
+ case UVC_VC_PROCESSING_UNIT:
+ blacklist = processing_blacklist;
+ count = ARRAY_SIZE(processing_blacklist);
+ controls = entity->processing.bmControls;
+ size = entity->processing.bControlSize;
+ break;
+
+ case UVC_ITT_CAMERA:
+ blacklist = camera_blacklist;
+ count = ARRAY_SIZE(camera_blacklist);
+ controls = entity->camera.bmControls;
+ size = entity->camera.bControlSize;
+ break;
+
+ default:
+ return;
+ }
+
+ for (i = 0; i < count; ++i) {
+ if (!usb_match_one_id(dev->intf, &blacklist[i].id))
+ continue;
+
+ if (blacklist[i].index >= 8 * size ||
+ !uvc_test_bit(controls, blacklist[i].index))
+ continue;
+
+ uvc_dbg(dev, CONTROL,
+ "%u/%u control is black listed, removing it\n",
+ entity->id, blacklist[i].index);
+
+ uvc_clear_bit(controls, blacklist[i].index);
+ }
+}
+
+/*
+ * Add control information and hardcoded stock control mappings to the given
+ * device.
+ */
+static void uvc_ctrl_init_ctrl(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl)
+{
+ const struct uvc_control_mapping **mappings;
+ unsigned int i;
+
+ /*
+ * XU controls initialization requires querying the device for control
+ * information. As some buggy UVC devices will crash when queried
+ * repeatedly in a tight loop, delay XU controls initialization until
+ * first use.
+ */
+ if (UVC_ENTITY_TYPE(ctrl->entity) == UVC_VC_EXTENSION_UNIT)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(uvc_ctrls); ++i) {
+ const struct uvc_control_info *info = &uvc_ctrls[i];
+
+ if (uvc_entity_match_guid(ctrl->entity, info->entity) &&
+ ctrl->index == info->index) {
+ uvc_ctrl_add_info(chain->dev, ctrl, info);
+ /*
+ * Retrieve control flags from the device. Ignore errors
+ * and work with default flag values from the uvc_ctrl
+ * array when the device doesn't properly implement
+ * GET_INFO on standard controls.
+ */
+ uvc_ctrl_get_flags(chain->dev, ctrl, &ctrl->info);
+ break;
+ }
+ }
+
+ if (!ctrl->initialized)
+ return;
+
+ /*
+ * First check if the device provides a custom mapping for this control,
+ * used to override standard mappings for non-conformant devices. Don't
+ * process standard mappings if a custom mapping is found. This
+ * mechanism doesn't support combining standard and custom mappings for
+ * a single control.
+ */
+ if (chain->dev->info->mappings) {
+ bool custom = false;
+
+ for (i = 0; chain->dev->info->mappings[i]; ++i) {
+ const struct uvc_control_mapping *mapping =
+ chain->dev->info->mappings[i];
+
+ if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
+ ctrl->info.selector == mapping->selector) {
+ __uvc_ctrl_add_mapping(chain, ctrl, mapping);
+ custom = true;
+ }
+ }
+
+ if (custom)
+ return;
+ }
+
+ /* Process common mappings next. */
+ for (i = 0; i < ARRAY_SIZE(uvc_ctrl_mappings); ++i) {
+ const struct uvc_control_mapping *mapping = &uvc_ctrl_mappings[i];
+
+ if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
+ ctrl->info.selector == mapping->selector)
+ __uvc_ctrl_add_mapping(chain, ctrl, mapping);
+ }
+
+ /* Finally process version-specific mappings. */
+ mappings = chain->dev->uvc_version < 0x0150
+ ? uvc_ctrl_mappings_uvc11 : uvc_ctrl_mappings_uvc15;
+
+ for (i = 0; mappings[i]; ++i) {
+ const struct uvc_control_mapping *mapping = mappings[i];
+
+ if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
+ ctrl->info.selector == mapping->selector)
+ __uvc_ctrl_add_mapping(chain, ctrl, mapping);
+ }
+}
+
+/*
+ * Initialize device controls.
+ */
+static int uvc_ctrl_init_chain(struct uvc_video_chain *chain)
+{
+ struct uvc_entity *entity;
+ unsigned int i;
+
+ /* Walk the entities list and instantiate controls */
+ list_for_each_entry(entity, &chain->entities, chain) {
+ struct uvc_control *ctrl;
+ unsigned int bControlSize = 0, ncontrols;
+ u8 *bmControls = NULL;
+
+ if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT) {
+ bmControls = entity->extension.bmControls;
+ bControlSize = entity->extension.bControlSize;
+ } else if (UVC_ENTITY_TYPE(entity) == UVC_VC_PROCESSING_UNIT) {
+ bmControls = entity->processing.bmControls;
+ bControlSize = entity->processing.bControlSize;
+ } else if (UVC_ENTITY_TYPE(entity) == UVC_ITT_CAMERA) {
+ bmControls = entity->camera.bmControls;
+ bControlSize = entity->camera.bControlSize;
+ } else if (UVC_ENTITY_TYPE(entity) == UVC_EXT_GPIO_UNIT) {
+ bmControls = entity->gpio.bmControls;
+ bControlSize = entity->gpio.bControlSize;
+ }
+
+ /* Remove bogus/blacklisted controls */
+ uvc_ctrl_prune_entity(chain->dev, entity);
+
+ /* Count supported controls and allocate the controls array */
+ ncontrols = memweight(bmControls, bControlSize);
+ if (ncontrols == 0)
+ continue;
+
+ entity->controls = kcalloc(ncontrols, sizeof(*ctrl),
+ GFP_KERNEL);
+ if (entity->controls == NULL)
+ return -ENOMEM;
+ entity->ncontrols = ncontrols;
+
+ /* Initialize all supported controls */
+ ctrl = entity->controls;
+ for (i = 0; i < bControlSize * 8; ++i) {
+ if (uvc_test_bit(bmControls, i) == 0)
+ continue;
+
+ ctrl->entity = entity;
+ ctrl->index = i;
+
+ uvc_ctrl_init_ctrl(chain, ctrl);
+ ctrl++;
+ }
+ }
+
+ return 0;
+}
+
+int uvc_ctrl_init_device(struct uvc_device *dev)
+{
+ struct uvc_video_chain *chain;
+ int ret;
+
+ INIT_WORK(&dev->async_ctrl.work, uvc_ctrl_status_event_work);
+
+ list_for_each_entry(chain, &dev->chains, list) {
+ ret = uvc_ctrl_init_chain(chain);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Cleanup device controls.
+ */
+static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev,
+ struct uvc_control *ctrl)
+{
+ struct uvc_control_mapping *mapping, *nm;
+
+ list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) {
+ list_del(&mapping->list);
+ kfree(mapping->menu_info);
+ kfree(mapping->name);
+ kfree(mapping);
+ }
+}
+
+void uvc_ctrl_cleanup_device(struct uvc_device *dev)
+{
+ struct uvc_entity *entity;
+ unsigned int i;
+
+ /* Can be uninitialized if we are aborting on probe error. */
+ if (dev->async_ctrl.work.func)
+ cancel_work_sync(&dev->async_ctrl.work);
+
+ /* Free controls and control mappings for all entities. */
+ list_for_each_entry(entity, &dev->entities, list) {
+ for (i = 0; i < entity->ncontrols; ++i) {
+ struct uvc_control *ctrl = &entity->controls[i];
+
+ if (!ctrl->initialized)
+ continue;
+
+ uvc_ctrl_cleanup_mappings(dev, ctrl);
+ kfree(ctrl->uvc_data);
+ }
+
+ kfree(entity->controls);
+ }
+}
diff --git a/drivers/media/usb/uvc/uvc_debugfs.c b/drivers/media/usb/uvc/uvc_debugfs.c
new file mode 100644
index 000000000..1a1258d4f
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_debugfs.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_debugfs.c -- USB Video Class driver - Debugging support
+ *
+ * Copyright (C) 2011
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "uvcvideo.h"
+
+/* -----------------------------------------------------------------------------
+ * Statistics
+ */
+
+#define UVC_DEBUGFS_BUF_SIZE 1024
+
+struct uvc_debugfs_buffer {
+ size_t count;
+ char data[UVC_DEBUGFS_BUF_SIZE];
+};
+
+static int uvc_debugfs_stats_open(struct inode *inode, struct file *file)
+{
+ struct uvc_streaming *stream = inode->i_private;
+ struct uvc_debugfs_buffer *buf;
+
+ buf = kmalloc(sizeof(*buf), GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf->count = uvc_video_stats_dump(stream, buf->data, sizeof(buf->data));
+
+ file->private_data = buf;
+ return 0;
+}
+
+static ssize_t uvc_debugfs_stats_read(struct file *file, char __user *user_buf,
+ size_t nbytes, loff_t *ppos)
+{
+ struct uvc_debugfs_buffer *buf = file->private_data;
+
+ return simple_read_from_buffer(user_buf, nbytes, ppos, buf->data,
+ buf->count);
+}
+
+static int uvc_debugfs_stats_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations uvc_debugfs_stats_fops = {
+ .owner = THIS_MODULE,
+ .open = uvc_debugfs_stats_open,
+ .llseek = no_llseek,
+ .read = uvc_debugfs_stats_read,
+ .release = uvc_debugfs_stats_release,
+};
+
+/* -----------------------------------------------------------------------------
+ * Global and stream initialization/cleanup
+ */
+
+static struct dentry *uvc_debugfs_root_dir;
+
+void uvc_debugfs_init_stream(struct uvc_streaming *stream)
+{
+ struct usb_device *udev = stream->dev->udev;
+ char dir_name[33];
+
+ if (uvc_debugfs_root_dir == NULL)
+ return;
+
+ snprintf(dir_name, sizeof(dir_name), "%u-%u-%u", udev->bus->busnum,
+ udev->devnum, stream->intfnum);
+
+ stream->debugfs_dir = debugfs_create_dir(dir_name,
+ uvc_debugfs_root_dir);
+
+ debugfs_create_file("stats", 0444, stream->debugfs_dir, stream,
+ &uvc_debugfs_stats_fops);
+}
+
+void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream)
+{
+ debugfs_remove_recursive(stream->debugfs_dir);
+ stream->debugfs_dir = NULL;
+}
+
+void uvc_debugfs_init(void)
+{
+ uvc_debugfs_root_dir = debugfs_create_dir("uvcvideo", usb_debug_root);
+}
+
+void uvc_debugfs_cleanup(void)
+{
+ debugfs_remove_recursive(uvc_debugfs_root_dir);
+}
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
new file mode 100644
index 000000000..191db831d
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -0,0 +1,3033 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_driver.c -- USB Video Class driver
+ *
+ * Copyright (C) 2005-2010
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/atomic.h>
+#include <linux/bits.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <asm/unaligned.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-uvc.h>
+
+#include "uvcvideo.h"
+
+#define DRIVER_AUTHOR "Laurent Pinchart " \
+ "<laurent.pinchart@ideasonboard.com>"
+#define DRIVER_DESC "USB Video Class driver"
+
+unsigned int uvc_clock_param = CLOCK_MONOTONIC;
+unsigned int uvc_hw_timestamps_param;
+unsigned int uvc_no_drop_param;
+static unsigned int uvc_quirks_param = -1;
+unsigned int uvc_dbg_param;
+unsigned int uvc_timeout_param = UVC_CTRL_STREAMING_TIMEOUT;
+
+/* ------------------------------------------------------------------------
+ * Utility functions
+ */
+
+struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts,
+ u8 epaddr)
+{
+ struct usb_host_endpoint *ep;
+ unsigned int i;
+
+ for (i = 0; i < alts->desc.bNumEndpoints; ++i) {
+ ep = &alts->endpoint[i];
+ if (ep->desc.bEndpointAddress == epaddr)
+ return ep;
+ }
+
+ return NULL;
+}
+
+static enum v4l2_colorspace uvc_colorspace(const u8 primaries)
+{
+ static const enum v4l2_colorspace colorprimaries[] = {
+ V4L2_COLORSPACE_SRGB, /* Unspecified */
+ V4L2_COLORSPACE_SRGB,
+ V4L2_COLORSPACE_470_SYSTEM_M,
+ V4L2_COLORSPACE_470_SYSTEM_BG,
+ V4L2_COLORSPACE_SMPTE170M,
+ V4L2_COLORSPACE_SMPTE240M,
+ };
+
+ if (primaries < ARRAY_SIZE(colorprimaries))
+ return colorprimaries[primaries];
+
+ return V4L2_COLORSPACE_SRGB; /* Reserved */
+}
+
+static enum v4l2_xfer_func uvc_xfer_func(const u8 transfer_characteristics)
+{
+ /*
+ * V4L2 does not currently have definitions for all possible values of
+ * UVC transfer characteristics. If v4l2_xfer_func is extended with new
+ * values, the mapping below should be updated.
+ *
+ * Substitutions are taken from the mapping given for
+ * V4L2_XFER_FUNC_DEFAULT documented in videodev2.h.
+ */
+ static const enum v4l2_xfer_func xfer_funcs[] = {
+ V4L2_XFER_FUNC_DEFAULT, /* Unspecified */
+ V4L2_XFER_FUNC_709,
+ V4L2_XFER_FUNC_709, /* Substitution for BT.470-2 M */
+ V4L2_XFER_FUNC_709, /* Substitution for BT.470-2 B, G */
+ V4L2_XFER_FUNC_709, /* Substitution for SMPTE 170M */
+ V4L2_XFER_FUNC_SMPTE240M,
+ V4L2_XFER_FUNC_NONE,
+ V4L2_XFER_FUNC_SRGB,
+ };
+
+ if (transfer_characteristics < ARRAY_SIZE(xfer_funcs))
+ return xfer_funcs[transfer_characteristics];
+
+ return V4L2_XFER_FUNC_DEFAULT; /* Reserved */
+}
+
+static enum v4l2_ycbcr_encoding uvc_ycbcr_enc(const u8 matrix_coefficients)
+{
+ /*
+ * V4L2 does not currently have definitions for all possible values of
+ * UVC matrix coefficients. If v4l2_ycbcr_encoding is extended with new
+ * values, the mapping below should be updated.
+ *
+ * Substitutions are taken from the mapping given for
+ * V4L2_YCBCR_ENC_DEFAULT documented in videodev2.h.
+ *
+ * FCC is assumed to be close enough to 601.
+ */
+ static const enum v4l2_ycbcr_encoding ycbcr_encs[] = {
+ V4L2_YCBCR_ENC_DEFAULT, /* Unspecified */
+ V4L2_YCBCR_ENC_709,
+ V4L2_YCBCR_ENC_601, /* Substitution for FCC */
+ V4L2_YCBCR_ENC_601, /* Substitution for BT.470-2 B, G */
+ V4L2_YCBCR_ENC_601,
+ V4L2_YCBCR_ENC_SMPTE240M,
+ };
+
+ if (matrix_coefficients < ARRAY_SIZE(ycbcr_encs))
+ return ycbcr_encs[matrix_coefficients];
+
+ return V4L2_YCBCR_ENC_DEFAULT; /* Reserved */
+}
+
+/* ------------------------------------------------------------------------
+ * Terminal and unit management
+ */
+
+struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id)
+{
+ struct uvc_entity *entity;
+
+ list_for_each_entry(entity, &dev->entities, list) {
+ if (entity->id == id)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static struct uvc_entity *uvc_entity_by_reference(struct uvc_device *dev,
+ int id, struct uvc_entity *entity)
+{
+ unsigned int i;
+
+ if (entity == NULL)
+ entity = list_entry(&dev->entities, struct uvc_entity, list);
+
+ list_for_each_entry_continue(entity, &dev->entities, list) {
+ for (i = 0; i < entity->bNrInPins; ++i)
+ if (entity->baSourceID[i] == id)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static struct uvc_streaming *uvc_stream_by_id(struct uvc_device *dev, int id)
+{
+ struct uvc_streaming *stream;
+
+ list_for_each_entry(stream, &dev->streams, list) {
+ if (stream->header.bTerminalLink == id)
+ return stream;
+ }
+
+ return NULL;
+}
+
+/* ------------------------------------------------------------------------
+ * Streaming Object Management
+ */
+
+static void uvc_stream_delete(struct uvc_streaming *stream)
+{
+ if (stream->async_wq)
+ destroy_workqueue(stream->async_wq);
+
+ mutex_destroy(&stream->mutex);
+
+ usb_put_intf(stream->intf);
+
+ kfree(stream->format);
+ kfree(stream->header.bmaControls);
+ kfree(stream);
+}
+
+static struct uvc_streaming *uvc_stream_new(struct uvc_device *dev,
+ struct usb_interface *intf)
+{
+ struct uvc_streaming *stream;
+
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (stream == NULL)
+ return NULL;
+
+ mutex_init(&stream->mutex);
+
+ stream->dev = dev;
+ stream->intf = usb_get_intf(intf);
+ stream->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
+
+ /* Allocate a stream specific work queue for asynchronous tasks. */
+ stream->async_wq = alloc_workqueue("uvcvideo", WQ_UNBOUND | WQ_HIGHPRI,
+ 0);
+ if (!stream->async_wq) {
+ uvc_stream_delete(stream);
+ return NULL;
+ }
+
+ return stream;
+}
+
+/* ------------------------------------------------------------------------
+ * Descriptors parsing
+ */
+
+static int uvc_parse_format(struct uvc_device *dev,
+ struct uvc_streaming *streaming, struct uvc_format *format,
+ u32 **intervals, unsigned char *buffer, int buflen)
+{
+ struct usb_interface *intf = streaming->intf;
+ struct usb_host_interface *alts = intf->cur_altsetting;
+ struct uvc_format_desc *fmtdesc;
+ struct uvc_frame *frame;
+ const unsigned char *start = buffer;
+ unsigned int width_multiplier = 1;
+ unsigned int interval;
+ unsigned int i, n;
+ u8 ftype;
+
+ format->type = buffer[2];
+ format->index = buffer[3];
+
+ switch (buffer[2]) {
+ case UVC_VS_FORMAT_UNCOMPRESSED:
+ case UVC_VS_FORMAT_FRAME_BASED:
+ n = buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED ? 27 : 28;
+ if (buflen < n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d FORMAT error\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ /* Find the format descriptor from its GUID. */
+ fmtdesc = uvc_format_by_guid(&buffer[5]);
+
+ if (!fmtdesc) {
+ /*
+ * Unknown video formats are not fatal errors, the
+ * caller will skip this descriptor.
+ */
+ dev_info(&streaming->intf->dev,
+ "Unknown video format %pUl\n", &buffer[5]);
+ return 0;
+ }
+
+ format->fcc = fmtdesc->fcc;
+ format->bpp = buffer[21];
+
+ /*
+ * Some devices report a format that doesn't match what they
+ * really send.
+ */
+ if (dev->quirks & UVC_QUIRK_FORCE_Y8) {
+ if (format->fcc == V4L2_PIX_FMT_YUYV) {
+ format->fcc = V4L2_PIX_FMT_GREY;
+ format->bpp = 8;
+ width_multiplier = 2;
+ }
+ }
+
+ /* Some devices report bpp that doesn't match the format. */
+ if (dev->quirks & UVC_QUIRK_FORCE_BPP) {
+ const struct v4l2_format_info *info =
+ v4l2_format_info(format->fcc);
+
+ if (info) {
+ unsigned int div = info->hdiv * info->vdiv;
+
+ n = info->bpp[0] * div;
+ for (i = 1; i < info->comp_planes; i++)
+ n += info->bpp[i];
+
+ format->bpp = DIV_ROUND_UP(8 * n, div);
+ }
+ }
+
+ if (buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED) {
+ ftype = UVC_VS_FRAME_UNCOMPRESSED;
+ } else {
+ ftype = UVC_VS_FRAME_FRAME_BASED;
+ if (buffer[27])
+ format->flags = UVC_FMT_FLAG_COMPRESSED;
+ }
+ break;
+
+ case UVC_VS_FORMAT_MJPEG:
+ if (buflen < 11) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d FORMAT error\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ format->fcc = V4L2_PIX_FMT_MJPEG;
+ format->flags = UVC_FMT_FLAG_COMPRESSED;
+ format->bpp = 0;
+ ftype = UVC_VS_FRAME_MJPEG;
+ break;
+
+ case UVC_VS_FORMAT_DV:
+ if (buflen < 9) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d FORMAT error\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ if ((buffer[8] & 0x7f) > 2) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d: unknown DV format %u\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber, buffer[8]);
+ return -EINVAL;
+ }
+
+ format->fcc = V4L2_PIX_FMT_DV;
+ format->flags = UVC_FMT_FLAG_COMPRESSED | UVC_FMT_FLAG_STREAM;
+ format->bpp = 0;
+ ftype = 0;
+
+ /* Create a dummy frame descriptor. */
+ frame = &format->frame[0];
+ memset(&format->frame[0], 0, sizeof(format->frame[0]));
+ frame->bFrameIntervalType = 1;
+ frame->dwDefaultFrameInterval = 1;
+ frame->dwFrameInterval = *intervals;
+ *(*intervals)++ = 1;
+ format->nframes = 1;
+ break;
+
+ case UVC_VS_FORMAT_MPEG2TS:
+ case UVC_VS_FORMAT_STREAM_BASED:
+ /* Not supported yet. */
+ default:
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d unsupported format %u\n",
+ dev->udev->devnum, alts->desc.bInterfaceNumber,
+ buffer[2]);
+ return -EINVAL;
+ }
+
+ uvc_dbg(dev, DESCR, "Found format %p4cc", &format->fcc);
+
+ buflen -= buffer[0];
+ buffer += buffer[0];
+
+ /*
+ * Parse the frame descriptors. Only uncompressed, MJPEG and frame
+ * based formats have frame descriptors.
+ */
+ while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+ buffer[2] == ftype) {
+ frame = &format->frame[format->nframes];
+ if (ftype != UVC_VS_FRAME_FRAME_BASED)
+ n = buflen > 25 ? buffer[25] : 0;
+ else
+ n = buflen > 21 ? buffer[21] : 0;
+
+ n = n ? n : 3;
+
+ if (buflen < 26 + 4*n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d FRAME error\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ frame->bFrameIndex = buffer[3];
+ frame->bmCapabilities = buffer[4];
+ frame->wWidth = get_unaligned_le16(&buffer[5])
+ * width_multiplier;
+ frame->wHeight = get_unaligned_le16(&buffer[7]);
+ frame->dwMinBitRate = get_unaligned_le32(&buffer[9]);
+ frame->dwMaxBitRate = get_unaligned_le32(&buffer[13]);
+ if (ftype != UVC_VS_FRAME_FRAME_BASED) {
+ frame->dwMaxVideoFrameBufferSize =
+ get_unaligned_le32(&buffer[17]);
+ frame->dwDefaultFrameInterval =
+ get_unaligned_le32(&buffer[21]);
+ frame->bFrameIntervalType = buffer[25];
+ } else {
+ frame->dwMaxVideoFrameBufferSize = 0;
+ frame->dwDefaultFrameInterval =
+ get_unaligned_le32(&buffer[17]);
+ frame->bFrameIntervalType = buffer[21];
+ }
+ frame->dwFrameInterval = *intervals;
+
+ /*
+ * Several UVC chipsets screw up dwMaxVideoFrameBufferSize
+ * completely. Observed behaviours range from setting the
+ * value to 1.1x the actual frame size to hardwiring the
+ * 16 low bits to 0. This results in a higher than necessary
+ * memory usage as well as a wrong image size information. For
+ * uncompressed formats this can be fixed by computing the
+ * value from the frame size.
+ */
+ if (!(format->flags & UVC_FMT_FLAG_COMPRESSED))
+ frame->dwMaxVideoFrameBufferSize = format->bpp
+ * frame->wWidth * frame->wHeight / 8;
+
+ /*
+ * Some bogus devices report dwMinFrameInterval equal to
+ * dwMaxFrameInterval and have dwFrameIntervalStep set to
+ * zero. Setting all null intervals to 1 fixes the problem and
+ * some other divisions by zero that could happen.
+ */
+ for (i = 0; i < n; ++i) {
+ interval = get_unaligned_le32(&buffer[26+4*i]);
+ *(*intervals)++ = interval ? interval : 1;
+ }
+
+ /*
+ * Make sure that the default frame interval stays between
+ * the boundaries.
+ */
+ n -= frame->bFrameIntervalType ? 1 : 2;
+ frame->dwDefaultFrameInterval =
+ min(frame->dwFrameInterval[n],
+ max(frame->dwFrameInterval[0],
+ frame->dwDefaultFrameInterval));
+
+ if (dev->quirks & UVC_QUIRK_RESTRICT_FRAME_RATE) {
+ frame->bFrameIntervalType = 1;
+ frame->dwFrameInterval[0] =
+ frame->dwDefaultFrameInterval;
+ }
+
+ uvc_dbg(dev, DESCR, "- %ux%u (%u.%u fps)\n",
+ frame->wWidth, frame->wHeight,
+ 10000000 / frame->dwDefaultFrameInterval,
+ (100000000 / frame->dwDefaultFrameInterval) % 10);
+
+ format->nframes++;
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ if (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+ buffer[2] == UVC_VS_STILL_IMAGE_FRAME) {
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ if (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+ buffer[2] == UVC_VS_COLORFORMAT) {
+ if (buflen < 6) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d COLORFORMAT error\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ format->colorspace = uvc_colorspace(buffer[3]);
+ format->xfer_func = uvc_xfer_func(buffer[4]);
+ format->ycbcr_enc = uvc_ycbcr_enc(buffer[5]);
+
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ } else {
+ format->colorspace = V4L2_COLORSPACE_SRGB;
+ }
+
+ return buffer - start;
+}
+
+static int uvc_parse_streaming(struct uvc_device *dev,
+ struct usb_interface *intf)
+{
+ struct uvc_streaming *streaming = NULL;
+ struct uvc_format *format;
+ struct uvc_frame *frame;
+ struct usb_host_interface *alts = &intf->altsetting[0];
+ unsigned char *_buffer, *buffer = alts->extra;
+ int _buflen, buflen = alts->extralen;
+ unsigned int nformats = 0, nframes = 0, nintervals = 0;
+ unsigned int size, i, n, p;
+ u32 *interval;
+ u16 psize;
+ int ret = -EINVAL;
+
+ if (intf->cur_altsetting->desc.bInterfaceSubClass
+ != UVC_SC_VIDEOSTREAMING) {
+ uvc_dbg(dev, DESCR,
+ "device %d interface %d isn't a video streaming interface\n",
+ dev->udev->devnum,
+ intf->altsetting[0].desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ if (usb_driver_claim_interface(&uvc_driver.driver, intf, dev)) {
+ uvc_dbg(dev, DESCR,
+ "device %d interface %d is already claimed\n",
+ dev->udev->devnum,
+ intf->altsetting[0].desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ streaming = uvc_stream_new(dev, intf);
+ if (streaming == NULL) {
+ usb_driver_release_interface(&uvc_driver.driver, intf);
+ return -ENOMEM;
+ }
+
+ /*
+ * The Pico iMage webcam has its class-specific interface descriptors
+ * after the endpoint descriptors.
+ */
+ if (buflen == 0) {
+ for (i = 0; i < alts->desc.bNumEndpoints; ++i) {
+ struct usb_host_endpoint *ep = &alts->endpoint[i];
+
+ if (ep->extralen == 0)
+ continue;
+
+ if (ep->extralen > 2 &&
+ ep->extra[1] == USB_DT_CS_INTERFACE) {
+ uvc_dbg(dev, DESCR,
+ "trying extra data from endpoint %u\n",
+ i);
+ buffer = alts->endpoint[i].extra;
+ buflen = alts->endpoint[i].extralen;
+ break;
+ }
+ }
+ }
+
+ /* Skip the standard interface descriptors. */
+ while (buflen > 2 && buffer[1] != USB_DT_CS_INTERFACE) {
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ if (buflen <= 2) {
+ uvc_dbg(dev, DESCR,
+ "no class-specific streaming interface descriptors found\n");
+ goto error;
+ }
+
+ /* Parse the header descriptor. */
+ switch (buffer[2]) {
+ case UVC_VS_OUTPUT_HEADER:
+ streaming->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ size = 9;
+ break;
+
+ case UVC_VS_INPUT_HEADER:
+ streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ size = 13;
+ break;
+
+ default:
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d HEADER descriptor not found\n",
+ dev->udev->devnum, alts->desc.bInterfaceNumber);
+ goto error;
+ }
+
+ p = buflen >= 4 ? buffer[3] : 0;
+ n = buflen >= size ? buffer[size-1] : 0;
+
+ if (buflen < size + p*n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d HEADER descriptor is invalid\n",
+ dev->udev->devnum, alts->desc.bInterfaceNumber);
+ goto error;
+ }
+
+ streaming->header.bNumFormats = p;
+ streaming->header.bEndpointAddress = buffer[6];
+ if (buffer[2] == UVC_VS_INPUT_HEADER) {
+ streaming->header.bmInfo = buffer[7];
+ streaming->header.bTerminalLink = buffer[8];
+ streaming->header.bStillCaptureMethod = buffer[9];
+ streaming->header.bTriggerSupport = buffer[10];
+ streaming->header.bTriggerUsage = buffer[11];
+ } else {
+ streaming->header.bTerminalLink = buffer[7];
+ }
+ streaming->header.bControlSize = n;
+
+ streaming->header.bmaControls = kmemdup(&buffer[size], p * n,
+ GFP_KERNEL);
+ if (streaming->header.bmaControls == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ buflen -= buffer[0];
+ buffer += buffer[0];
+
+ _buffer = buffer;
+ _buflen = buflen;
+
+ /* Count the format and frame descriptors. */
+ while (_buflen > 2 && _buffer[1] == USB_DT_CS_INTERFACE) {
+ switch (_buffer[2]) {
+ case UVC_VS_FORMAT_UNCOMPRESSED:
+ case UVC_VS_FORMAT_MJPEG:
+ case UVC_VS_FORMAT_FRAME_BASED:
+ nformats++;
+ break;
+
+ case UVC_VS_FORMAT_DV:
+ /*
+ * DV format has no frame descriptor. We will create a
+ * dummy frame descriptor with a dummy frame interval.
+ */
+ nformats++;
+ nframes++;
+ nintervals++;
+ break;
+
+ case UVC_VS_FORMAT_MPEG2TS:
+ case UVC_VS_FORMAT_STREAM_BASED:
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d FORMAT %u is not supported\n",
+ dev->udev->devnum,
+ alts->desc.bInterfaceNumber, _buffer[2]);
+ break;
+
+ case UVC_VS_FRAME_UNCOMPRESSED:
+ case UVC_VS_FRAME_MJPEG:
+ nframes++;
+ if (_buflen > 25)
+ nintervals += _buffer[25] ? _buffer[25] : 3;
+ break;
+
+ case UVC_VS_FRAME_FRAME_BASED:
+ nframes++;
+ if (_buflen > 21)
+ nintervals += _buffer[21] ? _buffer[21] : 3;
+ break;
+ }
+
+ _buflen -= _buffer[0];
+ _buffer += _buffer[0];
+ }
+
+ if (nformats == 0) {
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d has no supported formats defined\n",
+ dev->udev->devnum, alts->desc.bInterfaceNumber);
+ goto error;
+ }
+
+ size = nformats * sizeof(*format) + nframes * sizeof(*frame)
+ + nintervals * sizeof(*interval);
+ format = kzalloc(size, GFP_KERNEL);
+ if (format == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ frame = (struct uvc_frame *)&format[nformats];
+ interval = (u32 *)&frame[nframes];
+
+ streaming->format = format;
+ streaming->nformats = 0;
+
+ /* Parse the format descriptors. */
+ while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE) {
+ switch (buffer[2]) {
+ case UVC_VS_FORMAT_UNCOMPRESSED:
+ case UVC_VS_FORMAT_MJPEG:
+ case UVC_VS_FORMAT_DV:
+ case UVC_VS_FORMAT_FRAME_BASED:
+ format->frame = frame;
+ ret = uvc_parse_format(dev, streaming, format,
+ &interval, buffer, buflen);
+ if (ret < 0)
+ goto error;
+ if (!ret)
+ break;
+
+ streaming->nformats++;
+ frame += format->nframes;
+ format++;
+
+ buflen -= ret;
+ buffer += ret;
+ continue;
+
+ default:
+ break;
+ }
+
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ if (buflen)
+ uvc_dbg(dev, DESCR,
+ "device %d videostreaming interface %d has %u bytes of trailing descriptor garbage\n",
+ dev->udev->devnum, alts->desc.bInterfaceNumber, buflen);
+
+ /* Parse the alternate settings to find the maximum bandwidth. */
+ for (i = 0; i < intf->num_altsetting; ++i) {
+ struct usb_host_endpoint *ep;
+ alts = &intf->altsetting[i];
+ ep = uvc_find_endpoint(alts,
+ streaming->header.bEndpointAddress);
+ if (ep == NULL)
+ continue;
+ psize = uvc_endpoint_max_bpi(dev->udev, ep);
+ if (psize > streaming->maxpsize)
+ streaming->maxpsize = psize;
+ }
+
+ list_add_tail(&streaming->list, &dev->streams);
+ return 0;
+
+error:
+ usb_driver_release_interface(&uvc_driver.driver, intf);
+ uvc_stream_delete(streaming);
+ return ret;
+}
+
+static const u8 uvc_camera_guid[16] = UVC_GUID_UVC_CAMERA;
+static const u8 uvc_gpio_guid[16] = UVC_GUID_EXT_GPIO_CONTROLLER;
+static const u8 uvc_media_transport_input_guid[16] =
+ UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT;
+static const u8 uvc_processing_guid[16] = UVC_GUID_UVC_PROCESSING;
+
+static struct uvc_entity *uvc_alloc_entity(u16 type, u16 id,
+ unsigned int num_pads, unsigned int extra_size)
+{
+ struct uvc_entity *entity;
+ unsigned int num_inputs;
+ unsigned int size;
+ unsigned int i;
+
+ extra_size = roundup(extra_size, sizeof(*entity->pads));
+ if (num_pads)
+ num_inputs = type & UVC_TERM_OUTPUT ? num_pads : num_pads - 1;
+ else
+ num_inputs = 0;
+ size = sizeof(*entity) + extra_size + sizeof(*entity->pads) * num_pads
+ + num_inputs;
+ entity = kzalloc(size, GFP_KERNEL);
+ if (entity == NULL)
+ return NULL;
+
+ entity->id = id;
+ entity->type = type;
+
+ /*
+ * Set the GUID for standard entity types. For extension units, the GUID
+ * is initialized by the caller.
+ */
+ switch (type) {
+ case UVC_EXT_GPIO_UNIT:
+ memcpy(entity->guid, uvc_gpio_guid, 16);
+ break;
+ case UVC_ITT_CAMERA:
+ memcpy(entity->guid, uvc_camera_guid, 16);
+ break;
+ case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+ memcpy(entity->guid, uvc_media_transport_input_guid, 16);
+ break;
+ case UVC_VC_PROCESSING_UNIT:
+ memcpy(entity->guid, uvc_processing_guid, 16);
+ break;
+ }
+
+ entity->num_links = 0;
+ entity->num_pads = num_pads;
+ entity->pads = ((void *)(entity + 1)) + extra_size;
+
+ for (i = 0; i < num_inputs; ++i)
+ entity->pads[i].flags = MEDIA_PAD_FL_SINK;
+ if (!UVC_ENTITY_IS_OTERM(entity) && num_pads)
+ entity->pads[num_pads-1].flags = MEDIA_PAD_FL_SOURCE;
+
+ entity->bNrInPins = num_inputs;
+ entity->baSourceID = (u8 *)(&entity->pads[num_pads]);
+
+ return entity;
+}
+
+/* Parse vendor-specific extensions. */
+static int uvc_parse_vendor_control(struct uvc_device *dev,
+ const unsigned char *buffer, int buflen)
+{
+ struct usb_device *udev = dev->udev;
+ struct usb_host_interface *alts = dev->intf->cur_altsetting;
+ struct uvc_entity *unit;
+ unsigned int n, p;
+ int handled = 0;
+
+ switch (le16_to_cpu(dev->udev->descriptor.idVendor)) {
+ case 0x046d: /* Logitech */
+ if (buffer[1] != 0x41 || buffer[2] != 0x01)
+ break;
+
+ /*
+ * Logitech implements several vendor specific functions
+ * through vendor specific extension units (LXU).
+ *
+ * The LXU descriptors are similar to XU descriptors
+ * (see "USB Device Video Class for Video Devices", section
+ * 3.7.2.6 "Extension Unit Descriptor") with the following
+ * differences:
+ *
+ * ----------------------------------------------------------
+ * 0 bLength 1 Number
+ * Size of this descriptor, in bytes: 24+p+n*2
+ * ----------------------------------------------------------
+ * 23+p+n bmControlsType N Bitmap
+ * Individual bits in the set are defined:
+ * 0: Absolute
+ * 1: Relative
+ *
+ * This bitset is mapped exactly the same as bmControls.
+ * ----------------------------------------------------------
+ * 23+p+n*2 bReserved 1 Boolean
+ * ----------------------------------------------------------
+ * 24+p+n*2 iExtension 1 Index
+ * Index of a string descriptor that describes this
+ * extension unit.
+ * ----------------------------------------------------------
+ */
+ p = buflen >= 22 ? buffer[21] : 0;
+ n = buflen >= 25 + p ? buffer[22+p] : 0;
+
+ if (buflen < 25 + p + 2*n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d EXTENSION_UNIT error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ break;
+ }
+
+ unit = uvc_alloc_entity(UVC_VC_EXTENSION_UNIT, buffer[3],
+ p + 1, 2*n);
+ if (unit == NULL)
+ return -ENOMEM;
+
+ memcpy(unit->guid, &buffer[4], 16);
+ unit->extension.bNumControls = buffer[20];
+ memcpy(unit->baSourceID, &buffer[22], p);
+ unit->extension.bControlSize = buffer[22+p];
+ unit->extension.bmControls = (u8 *)unit + sizeof(*unit);
+ unit->extension.bmControlsType = (u8 *)unit + sizeof(*unit)
+ + n;
+ memcpy(unit->extension.bmControls, &buffer[23+p], 2*n);
+
+ if (buffer[24+p+2*n] == 0 ||
+ usb_string(udev, buffer[24+p+2*n], unit->name, sizeof(unit->name)) < 0)
+ sprintf(unit->name, "Extension %u", buffer[3]);
+
+ list_add_tail(&unit->list, &dev->entities);
+ handled = 1;
+ break;
+ }
+
+ return handled;
+}
+
+static int uvc_parse_standard_control(struct uvc_device *dev,
+ const unsigned char *buffer, int buflen)
+{
+ struct usb_device *udev = dev->udev;
+ struct uvc_entity *unit, *term;
+ struct usb_interface *intf;
+ struct usb_host_interface *alts = dev->intf->cur_altsetting;
+ unsigned int i, n, p, len;
+ u16 type;
+
+ switch (buffer[2]) {
+ case UVC_VC_HEADER:
+ n = buflen >= 12 ? buffer[11] : 0;
+
+ if (buflen < 12 + n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d HEADER error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ dev->uvc_version = get_unaligned_le16(&buffer[3]);
+ dev->clock_frequency = get_unaligned_le32(&buffer[7]);
+
+ /* Parse all USB Video Streaming interfaces. */
+ for (i = 0; i < n; ++i) {
+ intf = usb_ifnum_to_if(udev, buffer[12+i]);
+ if (intf == NULL) {
+ uvc_dbg(dev, DESCR,
+ "device %d interface %d doesn't exists\n",
+ udev->devnum, i);
+ continue;
+ }
+
+ uvc_parse_streaming(dev, intf);
+ }
+ break;
+
+ case UVC_VC_INPUT_TERMINAL:
+ if (buflen < 8) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d INPUT_TERMINAL error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ /*
+ * Reject invalid terminal types that would cause issues:
+ *
+ * - The high byte must be non-zero, otherwise it would be
+ * confused with a unit.
+ *
+ * - Bit 15 must be 0, as we use it internally as a terminal
+ * direction flag.
+ *
+ * Other unknown types are accepted.
+ */
+ type = get_unaligned_le16(&buffer[4]);
+ if ((type & 0x7f00) == 0 || (type & 0x8000) != 0) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d INPUT_TERMINAL %d has invalid type 0x%04x, skipping\n",
+ udev->devnum, alts->desc.bInterfaceNumber,
+ buffer[3], type);
+ return 0;
+ }
+
+ n = 0;
+ p = 0;
+ len = 8;
+
+ if (type == UVC_ITT_CAMERA) {
+ n = buflen >= 15 ? buffer[14] : 0;
+ len = 15;
+
+ } else if (type == UVC_ITT_MEDIA_TRANSPORT_INPUT) {
+ n = buflen >= 9 ? buffer[8] : 0;
+ p = buflen >= 10 + n ? buffer[9+n] : 0;
+ len = 10;
+ }
+
+ if (buflen < len + n + p) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d INPUT_TERMINAL error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ term = uvc_alloc_entity(type | UVC_TERM_INPUT, buffer[3],
+ 1, n + p);
+ if (term == NULL)
+ return -ENOMEM;
+
+ if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) {
+ term->camera.bControlSize = n;
+ term->camera.bmControls = (u8 *)term + sizeof(*term);
+ term->camera.wObjectiveFocalLengthMin =
+ get_unaligned_le16(&buffer[8]);
+ term->camera.wObjectiveFocalLengthMax =
+ get_unaligned_le16(&buffer[10]);
+ term->camera.wOcularFocalLength =
+ get_unaligned_le16(&buffer[12]);
+ memcpy(term->camera.bmControls, &buffer[15], n);
+ } else if (UVC_ENTITY_TYPE(term) ==
+ UVC_ITT_MEDIA_TRANSPORT_INPUT) {
+ term->media.bControlSize = n;
+ term->media.bmControls = (u8 *)term + sizeof(*term);
+ term->media.bTransportModeSize = p;
+ term->media.bmTransportModes = (u8 *)term
+ + sizeof(*term) + n;
+ memcpy(term->media.bmControls, &buffer[9], n);
+ memcpy(term->media.bmTransportModes, &buffer[10+n], p);
+ }
+
+ if (buffer[7] == 0 ||
+ usb_string(udev, buffer[7], term->name, sizeof(term->name)) < 0) {
+ if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA)
+ sprintf(term->name, "Camera %u", buffer[3]);
+ if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT)
+ sprintf(term->name, "Media %u", buffer[3]);
+ else
+ sprintf(term->name, "Input %u", buffer[3]);
+ }
+
+ list_add_tail(&term->list, &dev->entities);
+ break;
+
+ case UVC_VC_OUTPUT_TERMINAL:
+ if (buflen < 9) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d OUTPUT_TERMINAL error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ /*
+ * Make sure the terminal type MSB is not null, otherwise it
+ * could be confused with a unit.
+ */
+ type = get_unaligned_le16(&buffer[4]);
+ if ((type & 0xff00) == 0) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d OUTPUT_TERMINAL %d has invalid type 0x%04x, skipping\n",
+ udev->devnum, alts->desc.bInterfaceNumber,
+ buffer[3], type);
+ return 0;
+ }
+
+ term = uvc_alloc_entity(type | UVC_TERM_OUTPUT, buffer[3],
+ 1, 0);
+ if (term == NULL)
+ return -ENOMEM;
+
+ memcpy(term->baSourceID, &buffer[7], 1);
+
+ if (buffer[8] == 0 ||
+ usb_string(udev, buffer[8], term->name, sizeof(term->name)) < 0)
+ sprintf(term->name, "Output %u", buffer[3]);
+
+ list_add_tail(&term->list, &dev->entities);
+ break;
+
+ case UVC_VC_SELECTOR_UNIT:
+ p = buflen >= 5 ? buffer[4] : 0;
+
+ if (buflen < 5 || buflen < 6 + p) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d SELECTOR_UNIT error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ unit = uvc_alloc_entity(buffer[2], buffer[3], p + 1, 0);
+ if (unit == NULL)
+ return -ENOMEM;
+
+ memcpy(unit->baSourceID, &buffer[5], p);
+
+ if (buffer[5+p] == 0 ||
+ usb_string(udev, buffer[5+p], unit->name, sizeof(unit->name)) < 0)
+ sprintf(unit->name, "Selector %u", buffer[3]);
+
+ list_add_tail(&unit->list, &dev->entities);
+ break;
+
+ case UVC_VC_PROCESSING_UNIT:
+ n = buflen >= 8 ? buffer[7] : 0;
+ p = dev->uvc_version >= 0x0110 ? 10 : 9;
+
+ if (buflen < p + n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d PROCESSING_UNIT error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ unit = uvc_alloc_entity(buffer[2], buffer[3], 2, n);
+ if (unit == NULL)
+ return -ENOMEM;
+
+ memcpy(unit->baSourceID, &buffer[4], 1);
+ unit->processing.wMaxMultiplier =
+ get_unaligned_le16(&buffer[5]);
+ unit->processing.bControlSize = buffer[7];
+ unit->processing.bmControls = (u8 *)unit + sizeof(*unit);
+ memcpy(unit->processing.bmControls, &buffer[8], n);
+ if (dev->uvc_version >= 0x0110)
+ unit->processing.bmVideoStandards = buffer[9+n];
+
+ if (buffer[8+n] == 0 ||
+ usb_string(udev, buffer[8+n], unit->name, sizeof(unit->name)) < 0)
+ sprintf(unit->name, "Processing %u", buffer[3]);
+
+ list_add_tail(&unit->list, &dev->entities);
+ break;
+
+ case UVC_VC_EXTENSION_UNIT:
+ p = buflen >= 22 ? buffer[21] : 0;
+ n = buflen >= 24 + p ? buffer[22+p] : 0;
+
+ if (buflen < 24 + p + n) {
+ uvc_dbg(dev, DESCR,
+ "device %d videocontrol interface %d EXTENSION_UNIT error\n",
+ udev->devnum, alts->desc.bInterfaceNumber);
+ return -EINVAL;
+ }
+
+ unit = uvc_alloc_entity(buffer[2], buffer[3], p + 1, n);
+ if (unit == NULL)
+ return -ENOMEM;
+
+ memcpy(unit->guid, &buffer[4], 16);
+ unit->extension.bNumControls = buffer[20];
+ memcpy(unit->baSourceID, &buffer[22], p);
+ unit->extension.bControlSize = buffer[22+p];
+ unit->extension.bmControls = (u8 *)unit + sizeof(*unit);
+ memcpy(unit->extension.bmControls, &buffer[23+p], n);
+
+ if (buffer[23+p+n] == 0 ||
+ usb_string(udev, buffer[23+p+n], unit->name, sizeof(unit->name)) < 0)
+ sprintf(unit->name, "Extension %u", buffer[3]);
+
+ list_add_tail(&unit->list, &dev->entities);
+ break;
+
+ default:
+ uvc_dbg(dev, DESCR,
+ "Found an unknown CS_INTERFACE descriptor (%u)\n",
+ buffer[2]);
+ break;
+ }
+
+ return 0;
+}
+
+static int uvc_parse_control(struct uvc_device *dev)
+{
+ struct usb_host_interface *alts = dev->intf->cur_altsetting;
+ unsigned char *buffer = alts->extra;
+ int buflen = alts->extralen;
+ int ret;
+
+ /*
+ * Parse the default alternate setting only, as the UVC specification
+ * defines a single alternate setting, the default alternate setting
+ * zero.
+ */
+
+ while (buflen > 2) {
+ if (uvc_parse_vendor_control(dev, buffer, buflen) ||
+ buffer[1] != USB_DT_CS_INTERFACE)
+ goto next_descriptor;
+
+ if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0)
+ return ret;
+
+next_descriptor:
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ /*
+ * Check if the optional status endpoint is present. Built-in iSight
+ * webcams have an interrupt endpoint but spit proprietary data that
+ * don't conform to the UVC status endpoint messages. Don't try to
+ * handle the interrupt endpoint for those cameras.
+ */
+ if (alts->desc.bNumEndpoints == 1 &&
+ !(dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)) {
+ struct usb_host_endpoint *ep = &alts->endpoint[0];
+ struct usb_endpoint_descriptor *desc = &ep->desc;
+
+ if (usb_endpoint_is_int_in(desc) &&
+ le16_to_cpu(desc->wMaxPacketSize) >= 8 &&
+ desc->bInterval != 0) {
+ uvc_dbg(dev, DESCR,
+ "Found a Status endpoint (addr %02x)\n",
+ desc->bEndpointAddress);
+ dev->int_ep = ep;
+ }
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Privacy GPIO
+ */
+
+static void uvc_gpio_event(struct uvc_device *dev)
+{
+ struct uvc_entity *unit = dev->gpio_unit;
+ struct uvc_video_chain *chain;
+ u8 new_val;
+
+ if (!unit)
+ return;
+
+ new_val = gpiod_get_value_cansleep(unit->gpio.gpio_privacy);
+
+ /* GPIO entities are always on the first chain. */
+ chain = list_first_entry(&dev->chains, struct uvc_video_chain, list);
+ uvc_ctrl_status_event(chain, unit->controls, &new_val);
+}
+
+static int uvc_gpio_get_cur(struct uvc_device *dev, struct uvc_entity *entity,
+ u8 cs, void *data, u16 size)
+{
+ if (cs != UVC_CT_PRIVACY_CONTROL || size < 1)
+ return -EINVAL;
+
+ *(u8 *)data = gpiod_get_value_cansleep(entity->gpio.gpio_privacy);
+
+ return 0;
+}
+
+static int uvc_gpio_get_info(struct uvc_device *dev, struct uvc_entity *entity,
+ u8 cs, u8 *caps)
+{
+ if (cs != UVC_CT_PRIVACY_CONTROL)
+ return -EINVAL;
+
+ *caps = UVC_CONTROL_CAP_GET | UVC_CONTROL_CAP_AUTOUPDATE;
+ return 0;
+}
+
+static irqreturn_t uvc_gpio_irq(int irq, void *data)
+{
+ struct uvc_device *dev = data;
+
+ uvc_gpio_event(dev);
+ return IRQ_HANDLED;
+}
+
+static int uvc_gpio_parse(struct uvc_device *dev)
+{
+ struct uvc_entity *unit;
+ struct gpio_desc *gpio_privacy;
+ int irq;
+
+ gpio_privacy = devm_gpiod_get_optional(&dev->udev->dev, "privacy",
+ GPIOD_IN);
+ if (IS_ERR_OR_NULL(gpio_privacy))
+ return PTR_ERR_OR_ZERO(gpio_privacy);
+
+ irq = gpiod_to_irq(gpio_privacy);
+ if (irq < 0) {
+ if (irq != EPROBE_DEFER)
+ dev_err(&dev->udev->dev,
+ "No IRQ for privacy GPIO (%d)\n", irq);
+ return irq;
+ }
+
+ unit = uvc_alloc_entity(UVC_EXT_GPIO_UNIT, UVC_EXT_GPIO_UNIT_ID, 0, 1);
+ if (!unit)
+ return -ENOMEM;
+
+ unit->gpio.gpio_privacy = gpio_privacy;
+ unit->gpio.irq = irq;
+ unit->gpio.bControlSize = 1;
+ unit->gpio.bmControls = (u8 *)unit + sizeof(*unit);
+ unit->gpio.bmControls[0] = 1;
+ unit->get_cur = uvc_gpio_get_cur;
+ unit->get_info = uvc_gpio_get_info;
+ strscpy(unit->name, "GPIO", sizeof(unit->name));
+
+ list_add_tail(&unit->list, &dev->entities);
+
+ dev->gpio_unit = unit;
+
+ return 0;
+}
+
+static int uvc_gpio_init_irq(struct uvc_device *dev)
+{
+ struct uvc_entity *unit = dev->gpio_unit;
+
+ if (!unit || unit->gpio.irq < 0)
+ return 0;
+
+ return devm_request_threaded_irq(&dev->udev->dev, unit->gpio.irq, NULL,
+ uvc_gpio_irq,
+ IRQF_ONESHOT | IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING,
+ "uvc_privacy_gpio", dev);
+}
+
+/* ------------------------------------------------------------------------
+ * UVC device scan
+ */
+
+/*
+ * Scan the UVC descriptors to locate a chain starting at an Output Terminal
+ * and containing the following units:
+ *
+ * - one or more Output Terminals (USB Streaming or Display)
+ * - zero or one Processing Unit
+ * - zero, one or more single-input Selector Units
+ * - zero or one multiple-input Selector Units, provided all inputs are
+ * connected to input terminals
+ * - zero, one or mode single-input Extension Units
+ * - one or more Input Terminals (Camera, External or USB Streaming)
+ *
+ * The terminal and units must match on of the following structures:
+ *
+ * ITT_*(0) -> +---------+ +---------+ +---------+ -> TT_STREAMING(0)
+ * ... | SU{0,1} | -> | PU{0,1} | -> | XU{0,n} | ...
+ * ITT_*(n) -> +---------+ +---------+ +---------+ -> TT_STREAMING(n)
+ *
+ * +---------+ +---------+ -> OTT_*(0)
+ * TT_STREAMING -> | PU{0,1} | -> | XU{0,n} | ...
+ * +---------+ +---------+ -> OTT_*(n)
+ *
+ * The Processing Unit and Extension Units can be in any order. Additional
+ * Extension Units connected to the main chain as single-unit branches are
+ * also supported. Single-input Selector Units are ignored.
+ */
+static int uvc_scan_chain_entity(struct uvc_video_chain *chain,
+ struct uvc_entity *entity)
+{
+ switch (UVC_ENTITY_TYPE(entity)) {
+ case UVC_VC_EXTENSION_UNIT:
+ uvc_dbg_cont(PROBE, " <- XU %d", entity->id);
+
+ if (entity->bNrInPins != 1) {
+ uvc_dbg(chain->dev, DESCR,
+ "Extension unit %d has more than 1 input pin\n",
+ entity->id);
+ return -1;
+ }
+
+ break;
+
+ case UVC_VC_PROCESSING_UNIT:
+ uvc_dbg_cont(PROBE, " <- PU %d", entity->id);
+
+ if (chain->processing != NULL) {
+ uvc_dbg(chain->dev, DESCR,
+ "Found multiple Processing Units in chain\n");
+ return -1;
+ }
+
+ chain->processing = entity;
+ break;
+
+ case UVC_VC_SELECTOR_UNIT:
+ uvc_dbg_cont(PROBE, " <- SU %d", entity->id);
+
+ /* Single-input selector units are ignored. */
+ if (entity->bNrInPins == 1)
+ break;
+
+ if (chain->selector != NULL) {
+ uvc_dbg(chain->dev, DESCR,
+ "Found multiple Selector Units in chain\n");
+ return -1;
+ }
+
+ chain->selector = entity;
+ break;
+
+ case UVC_ITT_VENDOR_SPECIFIC:
+ case UVC_ITT_CAMERA:
+ case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+ uvc_dbg_cont(PROBE, " <- IT %d\n", entity->id);
+
+ break;
+
+ case UVC_OTT_VENDOR_SPECIFIC:
+ case UVC_OTT_DISPLAY:
+ case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+ uvc_dbg_cont(PROBE, " OT %d", entity->id);
+
+ break;
+
+ case UVC_TT_STREAMING:
+ if (UVC_ENTITY_IS_ITERM(entity))
+ uvc_dbg_cont(PROBE, " <- IT %d\n", entity->id);
+ else
+ uvc_dbg_cont(PROBE, " OT %d", entity->id);
+
+ break;
+
+ default:
+ uvc_dbg(chain->dev, DESCR,
+ "Unsupported entity type 0x%04x found in chain\n",
+ UVC_ENTITY_TYPE(entity));
+ return -1;
+ }
+
+ list_add_tail(&entity->chain, &chain->entities);
+ return 0;
+}
+
+static int uvc_scan_chain_forward(struct uvc_video_chain *chain,
+ struct uvc_entity *entity, struct uvc_entity *prev)
+{
+ struct uvc_entity *forward;
+ int found;
+
+ /* Forward scan */
+ forward = NULL;
+ found = 0;
+
+ while (1) {
+ forward = uvc_entity_by_reference(chain->dev, entity->id,
+ forward);
+ if (forward == NULL)
+ break;
+ if (forward == prev)
+ continue;
+ if (forward->chain.next || forward->chain.prev) {
+ uvc_dbg(chain->dev, DESCR,
+ "Found reference to entity %d already in chain\n",
+ forward->id);
+ return -EINVAL;
+ }
+
+ switch (UVC_ENTITY_TYPE(forward)) {
+ case UVC_VC_EXTENSION_UNIT:
+ if (forward->bNrInPins != 1) {
+ uvc_dbg(chain->dev, DESCR,
+ "Extension unit %d has more than 1 input pin\n",
+ forward->id);
+ return -EINVAL;
+ }
+
+ /*
+ * Some devices reference an output terminal as the
+ * source of extension units. This is incorrect, as
+ * output terminals only have an input pin, and thus
+ * can't be connected to any entity in the forward
+ * direction. The resulting topology would cause issues
+ * when registering the media controller graph. To
+ * avoid this problem, connect the extension unit to
+ * the source of the output terminal instead.
+ */
+ if (UVC_ENTITY_IS_OTERM(entity)) {
+ struct uvc_entity *source;
+
+ source = uvc_entity_by_id(chain->dev,
+ entity->baSourceID[0]);
+ if (!source) {
+ uvc_dbg(chain->dev, DESCR,
+ "Can't connect extension unit %u in chain\n",
+ forward->id);
+ break;
+ }
+
+ forward->baSourceID[0] = source->id;
+ }
+
+ list_add_tail(&forward->chain, &chain->entities);
+ if (!found)
+ uvc_dbg_cont(PROBE, " (->");
+
+ uvc_dbg_cont(PROBE, " XU %d", forward->id);
+ found = 1;
+ break;
+
+ case UVC_OTT_VENDOR_SPECIFIC:
+ case UVC_OTT_DISPLAY:
+ case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+ case UVC_TT_STREAMING:
+ if (UVC_ENTITY_IS_ITERM(forward)) {
+ uvc_dbg(chain->dev, DESCR,
+ "Unsupported input terminal %u\n",
+ forward->id);
+ return -EINVAL;
+ }
+
+ if (UVC_ENTITY_IS_OTERM(entity)) {
+ uvc_dbg(chain->dev, DESCR,
+ "Unsupported connection between output terminals %u and %u\n",
+ entity->id, forward->id);
+ break;
+ }
+
+ list_add_tail(&forward->chain, &chain->entities);
+ if (!found)
+ uvc_dbg_cont(PROBE, " (->");
+
+ uvc_dbg_cont(PROBE, " OT %d", forward->id);
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ uvc_dbg_cont(PROBE, ")");
+
+ return 0;
+}
+
+static int uvc_scan_chain_backward(struct uvc_video_chain *chain,
+ struct uvc_entity **_entity)
+{
+ struct uvc_entity *entity = *_entity;
+ struct uvc_entity *term;
+ int id = -EINVAL, i;
+
+ switch (UVC_ENTITY_TYPE(entity)) {
+ case UVC_VC_EXTENSION_UNIT:
+ case UVC_VC_PROCESSING_UNIT:
+ id = entity->baSourceID[0];
+ break;
+
+ case UVC_VC_SELECTOR_UNIT:
+ /* Single-input selector units are ignored. */
+ if (entity->bNrInPins == 1) {
+ id = entity->baSourceID[0];
+ break;
+ }
+
+ uvc_dbg_cont(PROBE, " <- IT");
+
+ chain->selector = entity;
+ for (i = 0; i < entity->bNrInPins; ++i) {
+ id = entity->baSourceID[i];
+ term = uvc_entity_by_id(chain->dev, id);
+ if (term == NULL || !UVC_ENTITY_IS_ITERM(term)) {
+ uvc_dbg(chain->dev, DESCR,
+ "Selector unit %d input %d isn't connected to an input terminal\n",
+ entity->id, i);
+ return -1;
+ }
+
+ if (term->chain.next || term->chain.prev) {
+ uvc_dbg(chain->dev, DESCR,
+ "Found reference to entity %d already in chain\n",
+ term->id);
+ return -EINVAL;
+ }
+
+ uvc_dbg_cont(PROBE, " %d", term->id);
+
+ list_add_tail(&term->chain, &chain->entities);
+ uvc_scan_chain_forward(chain, term, entity);
+ }
+
+ uvc_dbg_cont(PROBE, "\n");
+
+ id = 0;
+ break;
+
+ case UVC_ITT_VENDOR_SPECIFIC:
+ case UVC_ITT_CAMERA:
+ case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+ case UVC_OTT_VENDOR_SPECIFIC:
+ case UVC_OTT_DISPLAY:
+ case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+ case UVC_TT_STREAMING:
+ id = UVC_ENTITY_IS_OTERM(entity) ? entity->baSourceID[0] : 0;
+ break;
+ }
+
+ if (id <= 0) {
+ *_entity = NULL;
+ return id;
+ }
+
+ entity = uvc_entity_by_id(chain->dev, id);
+ if (entity == NULL) {
+ uvc_dbg(chain->dev, DESCR,
+ "Found reference to unknown entity %d\n", id);
+ return -EINVAL;
+ }
+
+ *_entity = entity;
+ return 0;
+}
+
+static int uvc_scan_chain(struct uvc_video_chain *chain,
+ struct uvc_entity *term)
+{
+ struct uvc_entity *entity, *prev;
+
+ uvc_dbg(chain->dev, PROBE, "Scanning UVC chain:");
+
+ entity = term;
+ prev = NULL;
+
+ while (entity != NULL) {
+ /* Entity must not be part of an existing chain */
+ if (entity->chain.next || entity->chain.prev) {
+ uvc_dbg(chain->dev, DESCR,
+ "Found reference to entity %d already in chain\n",
+ entity->id);
+ return -EINVAL;
+ }
+
+ /* Process entity */
+ if (uvc_scan_chain_entity(chain, entity) < 0)
+ return -EINVAL;
+
+ /* Forward scan */
+ if (uvc_scan_chain_forward(chain, entity, prev) < 0)
+ return -EINVAL;
+
+ /* Backward scan */
+ prev = entity;
+ if (uvc_scan_chain_backward(chain, &entity) < 0)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static unsigned int uvc_print_terms(struct list_head *terms, u16 dir,
+ char *buffer)
+{
+ struct uvc_entity *term;
+ unsigned int nterms = 0;
+ char *p = buffer;
+
+ list_for_each_entry(term, terms, chain) {
+ if (!UVC_ENTITY_IS_TERM(term) ||
+ UVC_TERM_DIRECTION(term) != dir)
+ continue;
+
+ if (nterms)
+ p += sprintf(p, ",");
+ if (++nterms >= 4) {
+ p += sprintf(p, "...");
+ break;
+ }
+ p += sprintf(p, "%u", term->id);
+ }
+
+ return p - buffer;
+}
+
+static const char *uvc_print_chain(struct uvc_video_chain *chain)
+{
+ static char buffer[43];
+ char *p = buffer;
+
+ p += uvc_print_terms(&chain->entities, UVC_TERM_INPUT, p);
+ p += sprintf(p, " -> ");
+ uvc_print_terms(&chain->entities, UVC_TERM_OUTPUT, p);
+
+ return buffer;
+}
+
+static struct uvc_video_chain *uvc_alloc_chain(struct uvc_device *dev)
+{
+ struct uvc_video_chain *chain;
+
+ chain = kzalloc(sizeof(*chain), GFP_KERNEL);
+ if (chain == NULL)
+ return NULL;
+
+ INIT_LIST_HEAD(&chain->entities);
+ mutex_init(&chain->ctrl_mutex);
+ chain->dev = dev;
+ v4l2_prio_init(&chain->prio);
+
+ return chain;
+}
+
+/*
+ * Fallback heuristic for devices that don't connect units and terminals in a
+ * valid chain.
+ *
+ * Some devices have invalid baSourceID references, causing uvc_scan_chain()
+ * to fail, but if we just take the entities we can find and put them together
+ * in the most sensible chain we can think of, turns out they do work anyway.
+ * Note: This heuristic assumes there is a single chain.
+ *
+ * At the time of writing, devices known to have such a broken chain are
+ * - Acer Integrated Camera (5986:055a)
+ * - Realtek rtl157a7 (0bda:57a7)
+ */
+static int uvc_scan_fallback(struct uvc_device *dev)
+{
+ struct uvc_video_chain *chain;
+ struct uvc_entity *iterm = NULL;
+ struct uvc_entity *oterm = NULL;
+ struct uvc_entity *entity;
+ struct uvc_entity *prev;
+
+ /*
+ * Start by locating the input and output terminals. We only support
+ * devices with exactly one of each for now.
+ */
+ list_for_each_entry(entity, &dev->entities, list) {
+ if (UVC_ENTITY_IS_ITERM(entity)) {
+ if (iterm)
+ return -EINVAL;
+ iterm = entity;
+ }
+
+ if (UVC_ENTITY_IS_OTERM(entity)) {
+ if (oterm)
+ return -EINVAL;
+ oterm = entity;
+ }
+ }
+
+ if (iterm == NULL || oterm == NULL)
+ return -EINVAL;
+
+ /* Allocate the chain and fill it. */
+ chain = uvc_alloc_chain(dev);
+ if (chain == NULL)
+ return -ENOMEM;
+
+ if (uvc_scan_chain_entity(chain, oterm) < 0)
+ goto error;
+
+ prev = oterm;
+
+ /*
+ * Add all Processing and Extension Units with two pads. The order
+ * doesn't matter much, use reverse list traversal to connect units in
+ * UVC descriptor order as we build the chain from output to input. This
+ * leads to units appearing in the order meant by the manufacturer for
+ * the cameras known to require this heuristic.
+ */
+ list_for_each_entry_reverse(entity, &dev->entities, list) {
+ if (entity->type != UVC_VC_PROCESSING_UNIT &&
+ entity->type != UVC_VC_EXTENSION_UNIT)
+ continue;
+
+ if (entity->num_pads != 2)
+ continue;
+
+ if (uvc_scan_chain_entity(chain, entity) < 0)
+ goto error;
+
+ prev->baSourceID[0] = entity->id;
+ prev = entity;
+ }
+
+ if (uvc_scan_chain_entity(chain, iterm) < 0)
+ goto error;
+
+ prev->baSourceID[0] = iterm->id;
+
+ list_add_tail(&chain->list, &dev->chains);
+
+ uvc_dbg(dev, PROBE, "Found a video chain by fallback heuristic (%s)\n",
+ uvc_print_chain(chain));
+
+ return 0;
+
+error:
+ kfree(chain);
+ return -EINVAL;
+}
+
+/*
+ * Scan the device for video chains and register video devices.
+ *
+ * Chains are scanned starting at their output terminals and walked backwards.
+ */
+static int uvc_scan_device(struct uvc_device *dev)
+{
+ struct uvc_video_chain *chain;
+ struct uvc_entity *term;
+
+ list_for_each_entry(term, &dev->entities, list) {
+ if (!UVC_ENTITY_IS_OTERM(term))
+ continue;
+
+ /*
+ * If the terminal is already included in a chain, skip it.
+ * This can happen for chains that have multiple output
+ * terminals, where all output terminals beside the first one
+ * will be inserted in the chain in forward scans.
+ */
+ if (term->chain.next || term->chain.prev)
+ continue;
+
+ chain = uvc_alloc_chain(dev);
+ if (chain == NULL)
+ return -ENOMEM;
+
+ term->flags |= UVC_ENTITY_FLAG_DEFAULT;
+
+ if (uvc_scan_chain(chain, term) < 0) {
+ kfree(chain);
+ continue;
+ }
+
+ uvc_dbg(dev, PROBE, "Found a valid video chain (%s)\n",
+ uvc_print_chain(chain));
+
+ list_add_tail(&chain->list, &dev->chains);
+ }
+
+ if (list_empty(&dev->chains))
+ uvc_scan_fallback(dev);
+
+ if (list_empty(&dev->chains)) {
+ dev_info(&dev->udev->dev, "No valid video chain found.\n");
+ return -1;
+ }
+
+ /* Add GPIO entity to the first chain. */
+ if (dev->gpio_unit) {
+ chain = list_first_entry(&dev->chains,
+ struct uvc_video_chain, list);
+ list_add_tail(&dev->gpio_unit->chain, &chain->entities);
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * Video device registration and unregistration
+ */
+
+/*
+ * Delete the UVC device.
+ *
+ * Called by the kernel when the last reference to the uvc_device structure
+ * is released.
+ *
+ * As this function is called after or during disconnect(), all URBs have
+ * already been cancelled by the USB core. There is no need to kill the
+ * interrupt URB manually.
+ */
+static void uvc_delete(struct kref *kref)
+{
+ struct uvc_device *dev = container_of(kref, struct uvc_device, ref);
+ struct list_head *p, *n;
+
+ uvc_status_cleanup(dev);
+ uvc_ctrl_cleanup_device(dev);
+
+ usb_put_intf(dev->intf);
+ usb_put_dev(dev->udev);
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+ media_device_cleanup(&dev->mdev);
+#endif
+
+ list_for_each_safe(p, n, &dev->chains) {
+ struct uvc_video_chain *chain;
+ chain = list_entry(p, struct uvc_video_chain, list);
+ kfree(chain);
+ }
+
+ list_for_each_safe(p, n, &dev->entities) {
+ struct uvc_entity *entity;
+ entity = list_entry(p, struct uvc_entity, list);
+#ifdef CONFIG_MEDIA_CONTROLLER
+ uvc_mc_cleanup_entity(entity);
+#endif
+ kfree(entity);
+ }
+
+ list_for_each_safe(p, n, &dev->streams) {
+ struct uvc_streaming *streaming;
+ streaming = list_entry(p, struct uvc_streaming, list);
+ usb_driver_release_interface(&uvc_driver.driver,
+ streaming->intf);
+ uvc_stream_delete(streaming);
+ }
+
+ kfree(dev);
+}
+
+static void uvc_release(struct video_device *vdev)
+{
+ struct uvc_streaming *stream = video_get_drvdata(vdev);
+ struct uvc_device *dev = stream->dev;
+
+ kref_put(&dev->ref, uvc_delete);
+}
+
+/*
+ * Unregister the video devices.
+ */
+static void uvc_unregister_video(struct uvc_device *dev)
+{
+ struct uvc_streaming *stream;
+
+ list_for_each_entry(stream, &dev->streams, list) {
+ if (!video_is_registered(&stream->vdev))
+ continue;
+
+ video_unregister_device(&stream->vdev);
+ video_unregister_device(&stream->meta.vdev);
+
+ uvc_debugfs_cleanup_stream(stream);
+ }
+
+ uvc_status_unregister(dev);
+
+ if (dev->vdev.dev)
+ v4l2_device_unregister(&dev->vdev);
+#ifdef CONFIG_MEDIA_CONTROLLER
+ if (media_devnode_is_registered(dev->mdev.devnode))
+ media_device_unregister(&dev->mdev);
+#endif
+}
+
+int uvc_register_video_device(struct uvc_device *dev,
+ struct uvc_streaming *stream,
+ struct video_device *vdev,
+ struct uvc_video_queue *queue,
+ enum v4l2_buf_type type,
+ const struct v4l2_file_operations *fops,
+ const struct v4l2_ioctl_ops *ioctl_ops)
+{
+ int ret;
+
+ /* Initialize the video buffers queue. */
+ ret = uvc_queue_init(queue, type, !uvc_no_drop_param);
+ if (ret)
+ return ret;
+
+ /* Register the device with V4L. */
+
+ /*
+ * We already hold a reference to dev->udev. The video device will be
+ * unregistered before the reference is released, so we don't need to
+ * get another one.
+ */
+ vdev->v4l2_dev = &dev->vdev;
+ vdev->fops = fops;
+ vdev->ioctl_ops = ioctl_ops;
+ vdev->release = uvc_release;
+ vdev->prio = &stream->chain->prio;
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ vdev->vfl_dir = VFL_DIR_TX;
+ else
+ vdev->vfl_dir = VFL_DIR_RX;
+
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ default:
+ vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ break;
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+ break;
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
+ break;
+ }
+
+ strscpy(vdev->name, dev->name, sizeof(vdev->name));
+
+ /*
+ * Set the driver data before calling video_register_device, otherwise
+ * the file open() handler might race us.
+ */
+ video_set_drvdata(vdev, stream);
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ dev_err(&stream->intf->dev,
+ "Failed to register %s device (%d).\n",
+ v4l2_type_names[type], ret);
+ return ret;
+ }
+
+ kref_get(&dev->ref);
+ return 0;
+}
+
+static int uvc_register_video(struct uvc_device *dev,
+ struct uvc_streaming *stream)
+{
+ int ret;
+
+ /* Initialize the streaming interface with default parameters. */
+ ret = uvc_video_init(stream);
+ if (ret < 0) {
+ dev_err(&stream->intf->dev,
+ "Failed to initialize the device (%d).\n", ret);
+ return ret;
+ }
+
+ if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE
+ | V4L2_CAP_META_CAPTURE;
+ else
+ stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;
+
+ uvc_debugfs_init_stream(stream);
+
+ /* Register the device with V4L. */
+ return uvc_register_video_device(dev, stream, &stream->vdev,
+ &stream->queue, stream->type,
+ &uvc_fops, &uvc_ioctl_ops);
+}
+
+/*
+ * Register all video devices in all chains.
+ */
+static int uvc_register_terms(struct uvc_device *dev,
+ struct uvc_video_chain *chain)
+{
+ struct uvc_streaming *stream;
+ struct uvc_entity *term;
+ int ret;
+
+ list_for_each_entry(term, &chain->entities, chain) {
+ if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING)
+ continue;
+
+ stream = uvc_stream_by_id(dev, term->id);
+ if (stream == NULL) {
+ dev_info(&dev->udev->dev,
+ "No streaming interface found for terminal %u.",
+ term->id);
+ continue;
+ }
+
+ stream->chain = chain;
+ ret = uvc_register_video(dev, stream);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Register a metadata node, but ignore a possible failure,
+ * complete registration of video nodes anyway.
+ */
+ uvc_meta_register(stream);
+
+ term->vdev = &stream->vdev;
+ }
+
+ return 0;
+}
+
+static int uvc_register_chains(struct uvc_device *dev)
+{
+ struct uvc_video_chain *chain;
+ int ret;
+
+ list_for_each_entry(chain, &dev->chains, list) {
+ ret = uvc_register_terms(dev, chain);
+ if (ret < 0)
+ return ret;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+ ret = uvc_mc_register_entities(chain);
+ if (ret < 0)
+ dev_info(&dev->udev->dev,
+ "Failed to register entities (%d).\n", ret);
+#endif
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * USB probe, disconnect, suspend and resume
+ */
+
+static const struct uvc_device_info uvc_quirk_none = { 0 };
+
+static int uvc_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct uvc_device *dev;
+ const struct uvc_device_info *info =
+ (const struct uvc_device_info *)id->driver_info;
+ int function;
+ int ret;
+
+ /* Allocate memory for the device and initialize it. */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&dev->entities);
+ INIT_LIST_HEAD(&dev->chains);
+ INIT_LIST_HEAD(&dev->streams);
+ kref_init(&dev->ref);
+ atomic_set(&dev->nmappings, 0);
+ mutex_init(&dev->lock);
+
+ dev->udev = usb_get_dev(udev);
+ dev->intf = usb_get_intf(intf);
+ dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
+ dev->info = info ? info : &uvc_quirk_none;
+ dev->quirks = uvc_quirks_param == -1
+ ? dev->info->quirks : uvc_quirks_param;
+
+ if (id->idVendor && id->idProduct)
+ uvc_dbg(dev, PROBE, "Probing known UVC device %s (%04x:%04x)\n",
+ udev->devpath, id->idVendor, id->idProduct);
+ else
+ uvc_dbg(dev, PROBE, "Probing generic UVC device %s\n",
+ udev->devpath);
+
+ if (udev->product != NULL)
+ strscpy(dev->name, udev->product, sizeof(dev->name));
+ else
+ snprintf(dev->name, sizeof(dev->name),
+ "UVC Camera (%04x:%04x)",
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ /*
+ * Add iFunction or iInterface to names when available as additional
+ * distinguishers between interfaces. iFunction is prioritized over
+ * iInterface which matches Windows behavior at the point of writing.
+ */
+ if (intf->intf_assoc && intf->intf_assoc->iFunction != 0)
+ function = intf->intf_assoc->iFunction;
+ else
+ function = intf->cur_altsetting->desc.iInterface;
+ if (function != 0) {
+ size_t len;
+
+ strlcat(dev->name, ": ", sizeof(dev->name));
+ len = strlen(dev->name);
+ usb_string(udev, function, dev->name + len,
+ sizeof(dev->name) - len);
+ }
+
+ /* Initialize the media device. */
+#ifdef CONFIG_MEDIA_CONTROLLER
+ dev->mdev.dev = &intf->dev;
+ strscpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
+ if (udev->serial)
+ strscpy(dev->mdev.serial, udev->serial,
+ sizeof(dev->mdev.serial));
+ usb_make_path(udev, dev->mdev.bus_info, sizeof(dev->mdev.bus_info));
+ dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
+ media_device_init(&dev->mdev);
+
+ dev->vdev.mdev = &dev->mdev;
+#endif
+
+ /* Parse the Video Class control descriptor. */
+ if (uvc_parse_control(dev) < 0) {
+ uvc_dbg(dev, PROBE, "Unable to parse UVC descriptors\n");
+ goto error;
+ }
+
+ /* Parse the associated GPIOs. */
+ if (uvc_gpio_parse(dev) < 0) {
+ uvc_dbg(dev, PROBE, "Unable to parse UVC GPIOs\n");
+ goto error;
+ }
+
+ dev_info(&dev->udev->dev, "Found UVC %u.%02x device %s (%04x:%04x)\n",
+ dev->uvc_version >> 8, dev->uvc_version & 0xff,
+ udev->product ? udev->product : "<unnamed>",
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+
+ if (dev->quirks != dev->info->quirks) {
+ dev_info(&dev->udev->dev,
+ "Forcing device quirks to 0x%x by module parameter for testing purpose.\n",
+ dev->quirks);
+ dev_info(&dev->udev->dev,
+ "Please report required quirks to the linux-media mailing list.\n");
+ }
+
+ if (dev->info->uvc_version) {
+ dev->uvc_version = dev->info->uvc_version;
+ dev_info(&dev->udev->dev, "Forcing UVC version to %u.%02x\n",
+ dev->uvc_version >> 8, dev->uvc_version & 0xff);
+ }
+
+ /* Register the V4L2 device. */
+ if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
+ goto error;
+
+ /* Scan the device for video chains. */
+ if (uvc_scan_device(dev) < 0)
+ goto error;
+
+ /* Initialize controls. */
+ if (uvc_ctrl_init_device(dev) < 0)
+ goto error;
+
+ /* Register video device nodes. */
+ if (uvc_register_chains(dev) < 0)
+ goto error;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+ /* Register the media device node */
+ if (media_device_register(&dev->mdev) < 0)
+ goto error;
+#endif
+ /* Save our data pointer in the interface data. */
+ usb_set_intfdata(intf, dev);
+
+ /* Initialize the interrupt URB. */
+ if ((ret = uvc_status_init(dev)) < 0) {
+ dev_info(&dev->udev->dev,
+ "Unable to initialize the status endpoint (%d), status interrupt will not be supported.\n",
+ ret);
+ }
+
+ ret = uvc_gpio_init_irq(dev);
+ if (ret < 0) {
+ dev_err(&dev->udev->dev,
+ "Unable to request privacy GPIO IRQ (%d)\n", ret);
+ goto error;
+ }
+
+ uvc_dbg(dev, PROBE, "UVC device initialized\n");
+ usb_enable_autosuspend(udev);
+ return 0;
+
+error:
+ uvc_unregister_video(dev);
+ kref_put(&dev->ref, uvc_delete);
+ return -ENODEV;
+}
+
+static void uvc_disconnect(struct usb_interface *intf)
+{
+ struct uvc_device *dev = usb_get_intfdata(intf);
+
+ /*
+ * Set the USB interface data to NULL. This can be done outside the
+ * lock, as there's no other reader.
+ */
+ usb_set_intfdata(intf, NULL);
+
+ if (intf->cur_altsetting->desc.bInterfaceSubClass ==
+ UVC_SC_VIDEOSTREAMING)
+ return;
+
+ uvc_unregister_video(dev);
+ kref_put(&dev->ref, uvc_delete);
+}
+
+static int uvc_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct uvc_device *dev = usb_get_intfdata(intf);
+ struct uvc_streaming *stream;
+
+ uvc_dbg(dev, SUSPEND, "Suspending interface %u\n",
+ intf->cur_altsetting->desc.bInterfaceNumber);
+
+ /* Controls are cached on the fly so they don't need to be saved. */
+ if (intf->cur_altsetting->desc.bInterfaceSubClass ==
+ UVC_SC_VIDEOCONTROL) {
+ mutex_lock(&dev->lock);
+ if (dev->users)
+ uvc_status_stop(dev);
+ mutex_unlock(&dev->lock);
+ return 0;
+ }
+
+ list_for_each_entry(stream, &dev->streams, list) {
+ if (stream->intf == intf)
+ return uvc_video_suspend(stream);
+ }
+
+ uvc_dbg(dev, SUSPEND,
+ "Suspend: video streaming USB interface mismatch\n");
+ return -EINVAL;
+}
+
+static int __uvc_resume(struct usb_interface *intf, int reset)
+{
+ struct uvc_device *dev = usb_get_intfdata(intf);
+ struct uvc_streaming *stream;
+ int ret = 0;
+
+ uvc_dbg(dev, SUSPEND, "Resuming interface %u\n",
+ intf->cur_altsetting->desc.bInterfaceNumber);
+
+ if (intf->cur_altsetting->desc.bInterfaceSubClass ==
+ UVC_SC_VIDEOCONTROL) {
+ if (reset) {
+ ret = uvc_ctrl_restore_values(dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ mutex_lock(&dev->lock);
+ if (dev->users)
+ ret = uvc_status_start(dev, GFP_NOIO);
+ mutex_unlock(&dev->lock);
+
+ return ret;
+ }
+
+ list_for_each_entry(stream, &dev->streams, list) {
+ if (stream->intf == intf) {
+ ret = uvc_video_resume(stream, reset);
+ if (ret < 0)
+ uvc_queue_streamoff(&stream->queue,
+ stream->queue.queue.type);
+ return ret;
+ }
+ }
+
+ uvc_dbg(dev, SUSPEND,
+ "Resume: video streaming USB interface mismatch\n");
+ return -EINVAL;
+}
+
+static int uvc_resume(struct usb_interface *intf)
+{
+ return __uvc_resume(intf, 0);
+}
+
+static int uvc_reset_resume(struct usb_interface *intf)
+{
+ return __uvc_resume(intf, 1);
+}
+
+/* ------------------------------------------------------------------------
+ * Module parameters
+ */
+
+static int uvc_clock_param_get(char *buffer, const struct kernel_param *kp)
+{
+ if (uvc_clock_param == CLOCK_MONOTONIC)
+ return sprintf(buffer, "CLOCK_MONOTONIC");
+ else
+ return sprintf(buffer, "CLOCK_REALTIME");
+}
+
+static int uvc_clock_param_set(const char *val, const struct kernel_param *kp)
+{
+ if (strncasecmp(val, "clock_", strlen("clock_")) == 0)
+ val += strlen("clock_");
+
+ if (strcasecmp(val, "monotonic") == 0)
+ uvc_clock_param = CLOCK_MONOTONIC;
+ else if (strcasecmp(val, "realtime") == 0)
+ uvc_clock_param = CLOCK_REALTIME;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+module_param_call(clock, uvc_clock_param_set, uvc_clock_param_get,
+ &uvc_clock_param, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(clock, "Video buffers timestamp clock");
+module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(hwtimestamps, "Use hardware timestamps");
+module_param_named(nodrop, uvc_no_drop_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames");
+module_param_named(quirks, uvc_quirks_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(quirks, "Forced device quirks");
+module_param_named(trace, uvc_dbg_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(trace, "Trace level bitmask");
+module_param_named(timeout, uvc_timeout_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
+
+/* ------------------------------------------------------------------------
+ * Driver initialization and cleanup
+ */
+
+static const struct uvc_device_info uvc_ctrl_power_line_limited = {
+ .mappings = (const struct uvc_control_mapping *[]) {
+ &uvc_ctrl_power_line_mapping_limited,
+ NULL, /* Sentinel */
+ },
+};
+
+static const struct uvc_device_info uvc_quirk_probe_minmax = {
+ .quirks = UVC_QUIRK_PROBE_MINMAX,
+};
+
+static const struct uvc_device_info uvc_quirk_fix_bandwidth = {
+ .quirks = UVC_QUIRK_FIX_BANDWIDTH,
+};
+
+static const struct uvc_device_info uvc_quirk_probe_def = {
+ .quirks = UVC_QUIRK_PROBE_DEF,
+};
+
+static const struct uvc_device_info uvc_quirk_stream_no_fid = {
+ .quirks = UVC_QUIRK_STREAM_NO_FID,
+};
+
+static const struct uvc_device_info uvc_quirk_force_y8 = {
+ .quirks = UVC_QUIRK_FORCE_Y8,
+};
+
+#define UVC_INFO_QUIRK(q) (kernel_ulong_t)&(struct uvc_device_info){.quirks = q}
+#define UVC_INFO_META(m) (kernel_ulong_t)&(struct uvc_device_info) \
+ {.meta_format = m}
+
+/*
+ * The Logitech cameras listed below have their interface class set to
+ * VENDOR_SPEC because they don't announce themselves as UVC devices, even
+ * though they are compliant.
+ */
+static const struct usb_device_id uvc_ids[] = {
+ /* Quanta USB2.0 HD UVC Webcam */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0408,
+ .idProduct = 0x3090,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Quanta USB2.0 HD UVC Webcam */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0408,
+ .idProduct = 0x4030,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Quanta USB2.0 HD UVC Webcam */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0408,
+ .idProduct = 0x4034,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = UVC_PC_PROTOCOL_15,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* LogiLink Wireless Webcam */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0416,
+ .idProduct = 0xa91a,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Genius eFace 2025 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0458,
+ .idProduct = 0x706e,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Microsoft Lifecam NX-6000 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x045e,
+ .idProduct = 0x00f8,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Microsoft Lifecam NX-3000 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x045e,
+ .idProduct = 0x0721,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Microsoft Lifecam VX-7000 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x045e,
+ .idProduct = 0x0723,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Logitech, Webcam C910 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x0821,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)},
+ /* Logitech, Webcam B910 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x0823,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_WAKE_AUTOSUSPEND)},
+ /* Logitech Quickcam Fusion */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c1,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Logitech Quickcam Orbit MP */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Logitech Quickcam Pro for Notebook */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Logitech Quickcam Pro 5000 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c5,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Logitech Quickcam OEM Dell Notebook */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c6,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Logitech Quickcam OEM Cisco VT Camera II */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x08c7,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Logitech HD Pro Webcam C920 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x046d,
+ .idProduct = 0x082d,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_RESTORE_CTRLS_ON_INIT) },
+ /* Chicony CNF7129 (Asus EEE 100HE) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x04f2,
+ .idProduct = 0xb071,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_RESTRICT_FRAME_RATE) },
+ /* Chicony EasyCamera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x04f2,
+ .idProduct = 0xb5eb,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Chicony EasyCamera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x04f2,
+ .idProduct = 0xb6ba,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Chicony EasyCamera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x04f2,
+ .idProduct = 0xb746,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Alcor Micro AU3820 (Future Boy PC USB Webcam) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x058f,
+ .idProduct = 0x3820,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Dell XPS m1530 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05a9,
+ .idProduct = 0x2640,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Dell SP2008WFP Monitor */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05a9,
+ .idProduct = 0x2641,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Dell Alienware X51 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05a9,
+ .idProduct = 0x2643,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Dell Studio Hybrid 140g (OmniVision webcam) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05a9,
+ .idProduct = 0x264a,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Dell XPS M1330 (OmniVision OV7670 webcam) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05a9,
+ .idProduct = 0x7670,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Apple Built-In iSight */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05ac,
+ .idProduct = 0x8501,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
+ | UVC_QUIRK_BUILTIN_ISIGHT) },
+ /* Apple FaceTime HD Camera (Built-In) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05ac,
+ .idProduct = 0x8514,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Apple Built-In iSight via iBridge */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05ac,
+ .idProduct = 0x8600,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* Foxlink ("HP Webcam" on HP Mini 5103) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05c8,
+ .idProduct = 0x0403,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+ /* Genesys Logic USB 2.0 PC Camera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x05e3,
+ .idProduct = 0x0505,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Hercules Classic Silver */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x06f8,
+ .idProduct = 0x300c,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+ /* ViMicro Vega */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0ac8,
+ .idProduct = 0x332d,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+ /* ViMicro - Minoru3D */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0ac8,
+ .idProduct = 0x3410,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+ /* ViMicro Venus - Minoru3D */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0ac8,
+ .idProduct = 0x3420,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+ /* Ophir Optronics - SPCAM 620U */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0bd3,
+ .idProduct = 0x0555,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* MT6227 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x0e8d,
+ .idProduct = 0x0004,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
+ | UVC_QUIRK_PROBE_DEF) },
+ /* IMC Networks (Medion Akoya) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x13d3,
+ .idProduct = 0x5103,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* JMicron USB2.0 XGA WebCam */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x152d,
+ .idProduct = 0x0310,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Syntek (HP Spartan) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x174f,
+ .idProduct = 0x5212,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Syntek (Samsung Q310) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x174f,
+ .idProduct = 0x5931,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Syntek (Packard Bell EasyNote MX52 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x174f,
+ .idProduct = 0x8a12,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Syntek (Asus F9SG) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x174f,
+ .idProduct = 0x8a31,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Syntek (Asus U3S) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x174f,
+ .idProduct = 0x8a33,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Syntek (JAOtech Smart Terminal) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x174f,
+ .idProduct = 0x8a34,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Miricle 307K */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x17dc,
+ .idProduct = 0x0202,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Lenovo Thinkpad SL400/SL500 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x17ef,
+ .idProduct = 0x480b,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+ /* Aveo Technology USB 2.0 Camera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x1871,
+ .idProduct = 0x0306,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
+ | UVC_QUIRK_PROBE_EXTRAFIELDS) },
+ /* Aveo Technology USB 2.0 Camera (Tasco USB Microscope) */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x1871,
+ .idProduct = 0x0516,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Ecamm Pico iMage */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x18cd,
+ .idProduct = 0xcafe,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_EXTRAFIELDS) },
+ /* Manta MM-353 Plako */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x18ec,
+ .idProduct = 0x3188,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* FSC WebCam V30S */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x18ec,
+ .idProduct = 0x3288,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Arkmicro unbranded */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x18ec,
+ .idProduct = 0x3290,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_def },
+ /* The Imaging Source USB CCD cameras */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x199e,
+ .idProduct = 0x8102,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0 },
+ /* Bodelin ProScopeHR */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_DEV_HI
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x19ab,
+ .idProduct = 0x1000,
+ .bcdDevice_hi = 0x0126,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_STATUS_INTERVAL) },
+ /* MSI StarCam 370i */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x1b3b,
+ .idProduct = 0x2951,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Generalplus Technology Inc. 808 Camera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x1b3f,
+ .idProduct = 0x2002,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_probe_minmax },
+ /* Shenzhen Aoni Electronic Co.,Ltd 2K FHD camera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x1bcf,
+ .idProduct = 0x0b40,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&(const struct uvc_device_info){
+ .uvc_version = 0x010a,
+ } },
+ /* SiGma Micro USB Web Camera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x1c4f,
+ .idProduct = 0x3000,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
+ | UVC_QUIRK_IGNORE_SELECTOR_UNIT) },
+ /* Oculus VR Positional Tracker DK2 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x2833,
+ .idProduct = 0x0201,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_force_y8 },
+ /* Oculus VR Rift Sensor */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x2833,
+ .idProduct = 0x0211,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_quirk_force_y8 },
+ /* GEO Semiconductor GC6500 */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x29fe,
+ .idProduct = 0x4d53,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_QUIRK(UVC_QUIRK_FORCE_BPP) },
+ /* Sonix Technology USB 2.0 Camera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x3277,
+ .idProduct = 0x0072,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Acer EasyCamera */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x5986,
+ .idProduct = 0x1172,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = (kernel_ulong_t)&uvc_ctrl_power_line_limited },
+ /* Intel RealSense D4M */
+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
+ | USB_DEVICE_ID_MATCH_INT_INFO,
+ .idVendor = 0x8086,
+ .idProduct = 0x0b03,
+ .bInterfaceClass = USB_CLASS_VIDEO,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 0,
+ .driver_info = UVC_INFO_META(V4L2_META_FMT_D4XX) },
+ /* Generic USB Video Class */
+ { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
+ { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },
+ {}
+};
+
+MODULE_DEVICE_TABLE(usb, uvc_ids);
+
+struct uvc_driver uvc_driver = {
+ .driver = {
+ .name = "uvcvideo",
+ .probe = uvc_probe,
+ .disconnect = uvc_disconnect,
+ .suspend = uvc_suspend,
+ .resume = uvc_resume,
+ .reset_resume = uvc_reset_resume,
+ .id_table = uvc_ids,
+ .supports_autosuspend = 1,
+ },
+};
+
+static int __init uvc_init(void)
+{
+ int ret;
+
+ uvc_debugfs_init();
+
+ ret = usb_register(&uvc_driver.driver);
+ if (ret < 0) {
+ uvc_debugfs_cleanup();
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit uvc_cleanup(void)
+{
+ usb_deregister(&uvc_driver.driver);
+ uvc_debugfs_cleanup();
+}
+
+module_init(uvc_init);
+module_exit(uvc_cleanup);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
+
diff --git a/drivers/media/usb/uvc/uvc_entity.c b/drivers/media/usb/uvc/uvc_entity.c
new file mode 100644
index 000000000..cc68dd24e
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_entity.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_entity.c -- USB Video Class driver
+ *
+ * Copyright (C) 2005-2011
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+
+#include "uvcvideo.h"
+
+static int uvc_mc_create_links(struct uvc_video_chain *chain,
+ struct uvc_entity *entity)
+{
+ const u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE;
+ struct media_entity *sink;
+ unsigned int i;
+ int ret;
+
+ sink = (UVC_ENTITY_TYPE(entity) == UVC_TT_STREAMING)
+ ? (entity->vdev ? &entity->vdev->entity : NULL)
+ : &entity->subdev.entity;
+ if (sink == NULL)
+ return 0;
+
+ for (i = 0; i < entity->num_pads; ++i) {
+ struct media_entity *source;
+ struct uvc_entity *remote;
+ u8 remote_pad;
+
+ if (!(entity->pads[i].flags & MEDIA_PAD_FL_SINK))
+ continue;
+
+ remote = uvc_entity_by_id(chain->dev, entity->baSourceID[i]);
+ if (remote == NULL || remote->num_pads == 0)
+ return -EINVAL;
+
+ source = (UVC_ENTITY_TYPE(remote) == UVC_TT_STREAMING)
+ ? (remote->vdev ? &remote->vdev->entity : NULL)
+ : &remote->subdev.entity;
+ if (source == NULL)
+ continue;
+
+ remote_pad = remote->num_pads - 1;
+ ret = media_create_pad_link(source, remote_pad,
+ sink, i, flags);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_subdev_ops uvc_subdev_ops = {
+};
+
+void uvc_mc_cleanup_entity(struct uvc_entity *entity)
+{
+ if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING)
+ media_entity_cleanup(&entity->subdev.entity);
+ else if (entity->vdev != NULL)
+ media_entity_cleanup(&entity->vdev->entity);
+}
+
+static int uvc_mc_init_entity(struct uvc_video_chain *chain,
+ struct uvc_entity *entity)
+{
+ int ret;
+
+ if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING) {
+ u32 function;
+
+ v4l2_subdev_init(&entity->subdev, &uvc_subdev_ops);
+ strscpy(entity->subdev.name, entity->name,
+ sizeof(entity->subdev.name));
+
+ switch (UVC_ENTITY_TYPE(entity)) {
+ case UVC_VC_SELECTOR_UNIT:
+ function = MEDIA_ENT_F_VID_MUX;
+ break;
+ case UVC_VC_PROCESSING_UNIT:
+ case UVC_VC_EXTENSION_UNIT:
+ /* For lack of a better option. */
+ function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+ break;
+ case UVC_COMPOSITE_CONNECTOR:
+ case UVC_COMPONENT_CONNECTOR:
+ function = MEDIA_ENT_F_CONN_COMPOSITE;
+ break;
+ case UVC_SVIDEO_CONNECTOR:
+ function = MEDIA_ENT_F_CONN_SVIDEO;
+ break;
+ case UVC_ITT_CAMERA:
+ function = MEDIA_ENT_F_CAM_SENSOR;
+ break;
+ case UVC_TT_VENDOR_SPECIFIC:
+ case UVC_ITT_VENDOR_SPECIFIC:
+ case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+ case UVC_OTT_VENDOR_SPECIFIC:
+ case UVC_OTT_DISPLAY:
+ case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+ case UVC_EXTERNAL_VENDOR_SPECIFIC:
+ case UVC_EXT_GPIO_UNIT:
+ default:
+ function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
+ break;
+ }
+
+ entity->subdev.entity.function = function;
+
+ ret = media_entity_pads_init(&entity->subdev.entity,
+ entity->num_pads, entity->pads);
+
+ if (ret < 0)
+ return ret;
+
+ ret = v4l2_device_register_subdev(&chain->dev->vdev,
+ &entity->subdev);
+ } else if (entity->vdev != NULL) {
+ ret = media_entity_pads_init(&entity->vdev->entity,
+ entity->num_pads, entity->pads);
+ if (entity->flags & UVC_ENTITY_FLAG_DEFAULT)
+ entity->vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
+ } else
+ ret = 0;
+
+ return ret;
+}
+
+int uvc_mc_register_entities(struct uvc_video_chain *chain)
+{
+ struct uvc_entity *entity;
+ int ret;
+
+ list_for_each_entry(entity, &chain->entities, chain) {
+ ret = uvc_mc_init_entity(chain, entity);
+ if (ret < 0) {
+ dev_info(&chain->dev->udev->dev,
+ "Failed to initialize entity for entity %u\n",
+ entity->id);
+ return ret;
+ }
+ }
+
+ list_for_each_entry(entity, &chain->entities, chain) {
+ ret = uvc_mc_create_links(chain, entity);
+ if (ret < 0) {
+ dev_info(&chain->dev->udev->dev,
+ "Failed to create links for entity %u\n",
+ entity->id);
+ return ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/drivers/media/usb/uvc/uvc_isight.c b/drivers/media/usb/uvc/uvc_isight.c
new file mode 100644
index 000000000..43cda5e76
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_isight.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_isight.c -- USB Video Class driver - iSight support
+ *
+ * Copyright (C) 2006-2007
+ * Ivan N. Zlatev <contact@i-nz.net>
+ * Copyright (C) 2008-2009
+ * Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+#include <linux/usb.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+
+#include "uvcvideo.h"
+
+/*
+ * Built-in iSight webcams implements most of UVC 1.0 except a
+ * different packet format. Instead of sending a header at the
+ * beginning of each isochronous transfer payload, the webcam sends a
+ * single header per image (on its own in a packet), followed by
+ * packets containing data only.
+ *
+ * Offset Size (bytes) Description
+ * ------------------------------------------------------------------
+ * 0x00 1 Header length
+ * 0x01 1 Flags (UVC-compliant)
+ * 0x02 4 Always equal to '11223344'
+ * 0x06 8 Always equal to 'deadbeefdeadface'
+ * 0x0e 16 Unknown
+ *
+ * The header can be prefixed by an optional, unknown-purpose byte.
+ */
+
+static int isight_decode(struct uvc_video_queue *queue, struct uvc_buffer *buf,
+ const u8 *data, unsigned int len)
+{
+ static const u8 hdr[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xfa, 0xce
+ };
+
+ struct uvc_streaming *stream = uvc_queue_to_stream(queue);
+ unsigned int maxlen, nbytes;
+ u8 *mem;
+ int is_header = 0;
+
+ if (buf == NULL)
+ return 0;
+
+ if ((len >= 14 && memcmp(&data[2], hdr, 12) == 0) ||
+ (len >= 15 && memcmp(&data[3], hdr, 12) == 0)) {
+ uvc_dbg(stream->dev, FRAME, "iSight header found\n");
+ is_header = 1;
+ }
+
+ /* Synchronize to the input stream by waiting for a header packet. */
+ if (buf->state != UVC_BUF_STATE_ACTIVE) {
+ if (!is_header) {
+ uvc_dbg(stream->dev, FRAME,
+ "Dropping packet (out of sync)\n");
+ return 0;
+ }
+
+ buf->state = UVC_BUF_STATE_ACTIVE;
+ }
+
+ /*
+ * Mark the buffer as done if we're at the beginning of a new frame.
+ *
+ * Empty buffers (bytesused == 0) don't trigger end of frame detection
+ * as it doesn't make sense to return an empty buffer.
+ */
+ if (is_header && buf->bytesused != 0) {
+ buf->state = UVC_BUF_STATE_DONE;
+ return -EAGAIN;
+ }
+
+ /*
+ * Copy the video data to the buffer. Skip header packets, as they
+ * contain no data.
+ */
+ if (!is_header) {
+ maxlen = buf->length - buf->bytesused;
+ mem = buf->mem + buf->bytesused;
+ nbytes = min(len, maxlen);
+ memcpy(mem, data, nbytes);
+ buf->bytesused += nbytes;
+
+ if (len > maxlen || buf->bytesused == buf->length) {
+ uvc_dbg(stream->dev, FRAME,
+ "Frame complete (overflow)\n");
+ buf->state = UVC_BUF_STATE_DONE;
+ }
+ }
+
+ return 0;
+}
+
+void uvc_video_decode_isight(struct uvc_urb *uvc_urb, struct uvc_buffer *buf,
+ struct uvc_buffer *meta_buf)
+{
+ struct urb *urb = uvc_urb->urb;
+ struct uvc_streaming *stream = uvc_urb->stream;
+ int ret, i;
+
+ for (i = 0; i < urb->number_of_packets; ++i) {
+ if (urb->iso_frame_desc[i].status < 0) {
+ uvc_dbg(stream->dev, FRAME,
+ "USB isochronous frame lost (%d)\n",
+ urb->iso_frame_desc[i].status);
+ }
+
+ /*
+ * Decode the payload packet.
+ *
+ * uvc_video_decode is entered twice when a frame transition
+ * has been detected because the end of frame can only be
+ * reliably detected when the first packet of the new frame
+ * is processed. The first pass detects the transition and
+ * closes the previous frame's buffer, the second pass
+ * processes the data of the first payload of the new frame.
+ */
+ do {
+ ret = isight_decode(&stream->queue, buf,
+ urb->transfer_buffer +
+ urb->iso_frame_desc[i].offset,
+ urb->iso_frame_desc[i].actual_length);
+
+ if (buf == NULL)
+ break;
+
+ if (buf->state == UVC_BUF_STATE_DONE ||
+ buf->state == UVC_BUF_STATE_ERROR)
+ buf = uvc_queue_next_buffer(&stream->queue,
+ buf);
+ } while (ret == -EAGAIN);
+ }
+}
diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c
new file mode 100644
index 000000000..82de7781f
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_metadata.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_metadata.c -- USB Video Class driver - Metadata handling
+ *
+ * Copyright (C) 2016
+ * Guennadi Liakhovetski (guennadi.liakhovetski@intel.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "uvcvideo.h"
+
+/* -----------------------------------------------------------------------------
+ * V4L2 ioctls
+ */
+
+static int uvc_meta_v4l2_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct v4l2_fh *vfh = file->private_data;
+ struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+ struct uvc_video_chain *chain = stream->chain;
+
+ strscpy(cap->driver, "uvcvideo", sizeof(cap->driver));
+ strscpy(cap->card, stream->dev->name, sizeof(cap->card));
+ usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
+ cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
+ | chain->caps;
+
+ return 0;
+}
+
+static int uvc_meta_v4l2_get_format(struct file *file, void *fh,
+ struct v4l2_format *format)
+{
+ struct v4l2_fh *vfh = file->private_data;
+ struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+ struct v4l2_meta_format *fmt = &format->fmt.meta;
+
+ if (format->type != vfh->vdev->queue->type)
+ return -EINVAL;
+
+ memset(fmt, 0, sizeof(*fmt));
+
+ fmt->dataformat = stream->meta.format;
+ fmt->buffersize = UVC_METADATA_BUF_SIZE;
+
+ return 0;
+}
+
+static int uvc_meta_v4l2_try_format(struct file *file, void *fh,
+ struct v4l2_format *format)
+{
+ struct v4l2_fh *vfh = file->private_data;
+ struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+ struct uvc_device *dev = stream->dev;
+ struct v4l2_meta_format *fmt = &format->fmt.meta;
+ u32 fmeta = fmt->dataformat;
+
+ if (format->type != vfh->vdev->queue->type)
+ return -EINVAL;
+
+ memset(fmt, 0, sizeof(*fmt));
+
+ fmt->dataformat = fmeta == dev->info->meta_format
+ ? fmeta : V4L2_META_FMT_UVC;
+ fmt->buffersize = UVC_METADATA_BUF_SIZE;
+
+ return 0;
+}
+
+static int uvc_meta_v4l2_set_format(struct file *file, void *fh,
+ struct v4l2_format *format)
+{
+ struct v4l2_fh *vfh = file->private_data;
+ struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+ struct v4l2_meta_format *fmt = &format->fmt.meta;
+ int ret;
+
+ ret = uvc_meta_v4l2_try_format(file, fh, format);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * We could in principle switch at any time, also during streaming.
+ * Metadata buffers would still be perfectly parseable, but it's more
+ * consistent and cleaner to disallow that.
+ */
+ mutex_lock(&stream->mutex);
+
+ if (uvc_queue_allocated(&stream->queue))
+ ret = -EBUSY;
+ else
+ stream->meta.format = fmt->dataformat;
+
+ mutex_unlock(&stream->mutex);
+
+ return ret;
+}
+
+static int uvc_meta_v4l2_enum_formats(struct file *file, void *fh,
+ struct v4l2_fmtdesc *fdesc)
+{
+ struct v4l2_fh *vfh = file->private_data;
+ struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+ struct uvc_device *dev = stream->dev;
+ u32 index = fdesc->index;
+
+ if (fdesc->type != vfh->vdev->queue->type ||
+ index > 1U || (index && !dev->info->meta_format))
+ return -EINVAL;
+
+ memset(fdesc, 0, sizeof(*fdesc));
+
+ fdesc->type = vfh->vdev->queue->type;
+ fdesc->index = index;
+ fdesc->pixelformat = index ? dev->info->meta_format : V4L2_META_FMT_UVC;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops uvc_meta_ioctl_ops = {
+ .vidioc_querycap = uvc_meta_v4l2_querycap,
+ .vidioc_g_fmt_meta_cap = uvc_meta_v4l2_get_format,
+ .vidioc_s_fmt_meta_cap = uvc_meta_v4l2_set_format,
+ .vidioc_try_fmt_meta_cap = uvc_meta_v4l2_try_format,
+ .vidioc_enum_fmt_meta_cap = uvc_meta_v4l2_enum_formats,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 File Operations
+ */
+
+static const struct v4l2_file_operations uvc_meta_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+};
+
+int uvc_meta_register(struct uvc_streaming *stream)
+{
+ struct uvc_device *dev = stream->dev;
+ struct video_device *vdev = &stream->meta.vdev;
+ struct uvc_video_queue *queue = &stream->meta.queue;
+
+ stream->meta.format = V4L2_META_FMT_UVC;
+
+ /*
+ * The video interface queue uses manual locking and thus does not set
+ * the queue pointer. Set it manually here.
+ */
+ vdev->queue = &queue->queue;
+
+ return uvc_register_video_device(dev, stream, vdev, queue,
+ V4L2_BUF_TYPE_META_CAPTURE,
+ &uvc_meta_fops, &uvc_meta_ioctl_ops);
+}
diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c
new file mode 100644
index 000000000..16fa17bbd
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_queue.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_queue.c -- USB Video Class driver - Buffers management
+ *
+ * Copyright (C) 2005-2010
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * Video buffers queue management.
+ *
+ * Video queues is initialized by uvc_queue_init(). The function performs
+ * basic initialization of the uvc_video_queue struct and never fails.
+ *
+ * Video buffers are managed by videobuf2. The driver uses a mutex to protect
+ * the videobuf2 queue operations by serializing calls to videobuf2 and a
+ * spinlock to protect the IRQ queue that holds the buffers to be processed by
+ * the driver.
+ */
+
+static inline struct uvc_buffer *uvc_vbuf_to_buffer(struct vb2_v4l2_buffer *buf)
+{
+ return container_of(buf, struct uvc_buffer, buf);
+}
+
+/*
+ * Return all queued buffers to videobuf2 in the requested state.
+ *
+ * This function must be called with the queue spinlock held.
+ */
+static void uvc_queue_return_buffers(struct uvc_video_queue *queue,
+ enum uvc_buffer_state state)
+{
+ enum vb2_buffer_state vb2_state = state == UVC_BUF_STATE_ERROR
+ ? VB2_BUF_STATE_ERROR
+ : VB2_BUF_STATE_QUEUED;
+
+ while (!list_empty(&queue->irqqueue)) {
+ struct uvc_buffer *buf = list_first_entry(&queue->irqqueue,
+ struct uvc_buffer,
+ queue);
+ list_del(&buf->queue);
+ buf->state = state;
+ vb2_buffer_done(&buf->buf.vb2_buf, vb2_state);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * videobuf2 queue operations
+ */
+
+static int uvc_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+ struct uvc_streaming *stream;
+ unsigned int size;
+
+ switch (vq->type) {
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ size = UVC_METADATA_BUF_SIZE;
+ break;
+
+ default:
+ stream = uvc_queue_to_stream(queue);
+ size = stream->ctrl.dwMaxVideoFrameSize;
+ break;
+ }
+
+ /*
+ * When called with plane sizes, validate them. The driver supports
+ * single planar formats only, and requires buffers to be large enough
+ * to store a complete frame.
+ */
+ if (*nplanes)
+ return *nplanes != 1 || sizes[0] < size ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = size;
+ return 0;
+}
+
+static int uvc_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+ struct uvc_buffer *buf = uvc_vbuf_to_buffer(vbuf);
+
+ if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
+ uvc_dbg(uvc_queue_to_stream(queue)->dev, CAPTURE,
+ "[E] Bytes used out of bounds\n");
+ return -EINVAL;
+ }
+
+ if (unlikely(queue->flags & UVC_QUEUE_DISCONNECTED))
+ return -ENODEV;
+
+ buf->state = UVC_BUF_STATE_QUEUED;
+ buf->error = 0;
+ buf->mem = vb2_plane_vaddr(vb, 0);
+ buf->length = vb2_plane_size(vb, 0);
+ if (vb->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ buf->bytesused = 0;
+ else
+ buf->bytesused = vb2_get_plane_payload(vb, 0);
+
+ return 0;
+}
+
+static void uvc_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+ struct uvc_buffer *buf = uvc_vbuf_to_buffer(vbuf);
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) {
+ kref_init(&buf->ref);
+ list_add_tail(&buf->queue, &queue->irqqueue);
+ } else {
+ /*
+ * If the device is disconnected return the buffer to userspace
+ * directly. The next QBUF call will fail with -ENODEV.
+ */
+ buf->state = UVC_BUF_STATE_ERROR;
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ }
+
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+static void uvc_buffer_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+ struct uvc_streaming *stream = uvc_queue_to_stream(queue);
+ struct uvc_buffer *buf = uvc_vbuf_to_buffer(vbuf);
+
+ if (vb->state == VB2_BUF_STATE_DONE)
+ uvc_video_clock_update(stream, vbuf, buf);
+}
+
+static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+ struct uvc_streaming *stream = uvc_queue_to_stream(queue);
+ int ret;
+
+ lockdep_assert_irqs_enabled();
+
+ queue->buf_used = 0;
+
+ ret = uvc_video_start_streaming(stream);
+ if (ret == 0)
+ return 0;
+
+ spin_lock_irq(&queue->irqlock);
+ uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED);
+ spin_unlock_irq(&queue->irqlock);
+
+ return ret;
+}
+
+static void uvc_stop_streaming(struct vb2_queue *vq)
+{
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+
+ lockdep_assert_irqs_enabled();
+
+ if (vq->type != V4L2_BUF_TYPE_META_CAPTURE)
+ uvc_video_stop_streaming(uvc_queue_to_stream(queue));
+
+ spin_lock_irq(&queue->irqlock);
+ uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
+ spin_unlock_irq(&queue->irqlock);
+}
+
+static const struct vb2_ops uvc_queue_qops = {
+ .queue_setup = uvc_queue_setup,
+ .buf_prepare = uvc_buffer_prepare,
+ .buf_queue = uvc_buffer_queue,
+ .buf_finish = uvc_buffer_finish,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = uvc_start_streaming,
+ .stop_streaming = uvc_stop_streaming,
+};
+
+static const struct vb2_ops uvc_meta_queue_qops = {
+ .queue_setup = uvc_queue_setup,
+ .buf_prepare = uvc_buffer_prepare,
+ .buf_queue = uvc_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .stop_streaming = uvc_stop_streaming,
+};
+
+int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+ int drop_corrupted)
+{
+ int ret;
+
+ queue->queue.type = type;
+ queue->queue.io_modes = VB2_MMAP | VB2_USERPTR;
+ queue->queue.drv_priv = queue;
+ queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
+ queue->queue.mem_ops = &vb2_vmalloc_memops;
+ queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
+ | V4L2_BUF_FLAG_TSTAMP_SRC_SOE;
+ queue->queue.lock = &queue->mutex;
+
+ switch (type) {
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ queue->queue.ops = &uvc_meta_queue_qops;
+ break;
+ default:
+ queue->queue.io_modes |= VB2_DMABUF;
+ queue->queue.ops = &uvc_queue_qops;
+ break;
+ }
+
+ ret = vb2_queue_init(&queue->queue);
+ if (ret)
+ return ret;
+
+ mutex_init(&queue->mutex);
+ spin_lock_init(&queue->irqlock);
+ INIT_LIST_HEAD(&queue->irqqueue);
+ queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;
+
+ return 0;
+}
+
+void uvc_queue_release(struct uvc_video_queue *queue)
+{
+ mutex_lock(&queue->mutex);
+ vb2_queue_release(&queue->queue);
+ mutex_unlock(&queue->mutex);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 queue operations
+ */
+
+int uvc_request_buffers(struct uvc_video_queue *queue,
+ struct v4l2_requestbuffers *rb)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_reqbufs(&queue->queue, rb);
+ mutex_unlock(&queue->mutex);
+
+ return ret ? ret : rb->count;
+}
+
+int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_querybuf(&queue->queue, buf);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_create_buffers(struct uvc_video_queue *queue,
+ struct v4l2_create_buffers *cb)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_create_bufs(&queue->queue, cb);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_queue_buffer(struct uvc_video_queue *queue,
+ struct media_device *mdev, struct v4l2_buffer *buf)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_qbuf(&queue->queue, mdev, buf);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_export_buffer(struct uvc_video_queue *queue,
+ struct v4l2_exportbuffer *exp)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_expbuf(&queue->queue, exp);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
+ int nonblocking)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_dqbuf(&queue->queue, buf, nonblocking);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_streamon(&queue->queue, type);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type)
+{
+ int ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_streamoff(&queue->queue, type);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
+{
+ return vb2_mmap(&queue->queue, vma);
+}
+
+#ifndef CONFIG_MMU
+unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
+ unsigned long pgoff)
+{
+ return vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0);
+}
+#endif
+
+__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
+ poll_table *wait)
+{
+ __poll_t ret;
+
+ mutex_lock(&queue->mutex);
+ ret = vb2_poll(&queue->queue, file, wait);
+ mutex_unlock(&queue->mutex);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ *
+ */
+
+/*
+ * Check if buffers have been allocated.
+ */
+int uvc_queue_allocated(struct uvc_video_queue *queue)
+{
+ int allocated;
+
+ mutex_lock(&queue->mutex);
+ allocated = vb2_is_busy(&queue->queue);
+ mutex_unlock(&queue->mutex);
+
+ return allocated;
+}
+
+/*
+ * Cancel the video buffers queue.
+ *
+ * Cancelling the queue marks all buffers on the irq queue as erroneous,
+ * wakes them up and removes them from the queue.
+ *
+ * If the disconnect parameter is set, further calls to uvc_queue_buffer will
+ * fail with -ENODEV.
+ *
+ * This function acquires the irq spinlock and can be called from interrupt
+ * context.
+ */
+void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
+ /*
+ * This must be protected by the irqlock spinlock to avoid race
+ * conditions between uvc_buffer_queue and the disconnection event that
+ * could result in an interruptible wait in uvc_dequeue_buffer. Do not
+ * blindly replace this logic by checking for the UVC_QUEUE_DISCONNECTED
+ * state outside the queue code.
+ */
+ if (disconnect)
+ queue->flags |= UVC_QUEUE_DISCONNECTED;
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+/*
+ * uvc_queue_get_current_buffer: Obtain the current working output buffer
+ *
+ * Buffers may span multiple packets, and even URBs, therefore the active buffer
+ * remains on the queue until the EOF marker.
+ */
+static struct uvc_buffer *
+__uvc_queue_get_current_buffer(struct uvc_video_queue *queue)
+{
+ if (list_empty(&queue->irqqueue))
+ return NULL;
+
+ return list_first_entry(&queue->irqqueue, struct uvc_buffer, queue);
+}
+
+struct uvc_buffer *uvc_queue_get_current_buffer(struct uvc_video_queue *queue)
+{
+ struct uvc_buffer *nextbuf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ nextbuf = __uvc_queue_get_current_buffer(queue);
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+
+ return nextbuf;
+}
+
+/*
+ * uvc_queue_buffer_requeue: Requeue a buffer on our internal irqqueue
+ *
+ * Reuse a buffer through our internal queue without the need to 'prepare'.
+ * The buffer will be returned to userspace through the uvc_buffer_queue call if
+ * the device has been disconnected.
+ */
+static void uvc_queue_buffer_requeue(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf)
+{
+ buf->error = 0;
+ buf->state = UVC_BUF_STATE_QUEUED;
+ buf->bytesused = 0;
+ vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0);
+
+ uvc_buffer_queue(&buf->buf.vb2_buf);
+}
+
+static void uvc_queue_buffer_complete(struct kref *ref)
+{
+ struct uvc_buffer *buf = container_of(ref, struct uvc_buffer, ref);
+ struct vb2_buffer *vb = &buf->buf.vb2_buf;
+ struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+
+ if ((queue->flags & UVC_QUEUE_DROP_CORRUPTED) && buf->error) {
+ uvc_queue_buffer_requeue(queue, buf);
+ return;
+ }
+
+ buf->state = buf->error ? UVC_BUF_STATE_ERROR : UVC_BUF_STATE_DONE;
+ vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused);
+ vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+/*
+ * Release a reference on the buffer. Complete the buffer when the last
+ * reference is released.
+ */
+void uvc_queue_buffer_release(struct uvc_buffer *buf)
+{
+ kref_put(&buf->ref, uvc_queue_buffer_complete);
+}
+
+/*
+ * Remove this buffer from the queue. Lifetime will persist while async actions
+ * are still running (if any), and uvc_queue_buffer_release will give the buffer
+ * back to VB2 when all users have completed.
+ */
+struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf)
+{
+ struct uvc_buffer *nextbuf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->irqlock, flags);
+ list_del(&buf->queue);
+ nextbuf = __uvc_queue_get_current_buffer(queue);
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+
+ uvc_queue_buffer_release(buf);
+
+ return nextbuf;
+}
diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c
new file mode 100644
index 000000000..4a92c989c
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_status.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_status.c -- USB Video Class driver - Status endpoint
+ *
+ * Copyright (C) 2005-2009
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <asm/barrier.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+
+#include "uvcvideo.h"
+
+/* --------------------------------------------------------------------------
+ * Input device
+ */
+#ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV
+static int uvc_input_init(struct uvc_device *dev)
+{
+ struct input_dev *input;
+ int ret;
+
+ input = input_allocate_device();
+ if (input == NULL)
+ return -ENOMEM;
+
+ usb_make_path(dev->udev, dev->input_phys, sizeof(dev->input_phys));
+ strlcat(dev->input_phys, "/button", sizeof(dev->input_phys));
+
+ input->name = dev->name;
+ input->phys = dev->input_phys;
+ usb_to_input_id(dev->udev, &input->id);
+ input->dev.parent = &dev->intf->dev;
+
+ __set_bit(EV_KEY, input->evbit);
+ __set_bit(KEY_CAMERA, input->keybit);
+
+ if ((ret = input_register_device(input)) < 0)
+ goto error;
+
+ dev->input = input;
+ return 0;
+
+error:
+ input_free_device(input);
+ return ret;
+}
+
+static void uvc_input_unregister(struct uvc_device *dev)
+{
+ if (dev->input)
+ input_unregister_device(dev->input);
+}
+
+static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
+ int value)
+{
+ if (dev->input) {
+ input_report_key(dev->input, code, value);
+ input_sync(dev->input);
+ }
+}
+
+#else
+#define uvc_input_init(dev)
+#define uvc_input_unregister(dev)
+#define uvc_input_report_key(dev, code, value)
+#endif /* CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV */
+
+/* --------------------------------------------------------------------------
+ * Status interrupt endpoint
+ */
+struct uvc_streaming_status {
+ u8 bStatusType;
+ u8 bOriginator;
+ u8 bEvent;
+ u8 bValue[];
+} __packed;
+
+struct uvc_control_status {
+ u8 bStatusType;
+ u8 bOriginator;
+ u8 bEvent;
+ u8 bSelector;
+ u8 bAttribute;
+ u8 bValue[];
+} __packed;
+
+static void uvc_event_streaming(struct uvc_device *dev,
+ struct uvc_streaming_status *status, int len)
+{
+ if (len < 3) {
+ uvc_dbg(dev, STATUS,
+ "Invalid streaming status event received\n");
+ return;
+ }
+
+ if (status->bEvent == 0) {
+ if (len < 4)
+ return;
+ uvc_dbg(dev, STATUS, "Button (intf %u) %s len %d\n",
+ status->bOriginator,
+ status->bValue[0] ? "pressed" : "released", len);
+ uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]);
+ } else {
+ uvc_dbg(dev, STATUS, "Stream %u error event %02x len %d\n",
+ status->bOriginator, status->bEvent, len);
+ }
+}
+
+#define UVC_CTRL_VALUE_CHANGE 0
+#define UVC_CTRL_INFO_CHANGE 1
+#define UVC_CTRL_FAILURE_CHANGE 2
+#define UVC_CTRL_MIN_CHANGE 3
+#define UVC_CTRL_MAX_CHANGE 4
+
+static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity,
+ u8 selector)
+{
+ struct uvc_control *ctrl;
+ unsigned int i;
+
+ for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++)
+ if (ctrl->info.selector == selector)
+ return ctrl;
+
+ return NULL;
+}
+
+static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
+ const struct uvc_control_status *status,
+ struct uvc_video_chain **chain)
+{
+ list_for_each_entry((*chain), &dev->chains, list) {
+ struct uvc_entity *entity;
+ struct uvc_control *ctrl;
+
+ list_for_each_entry(entity, &(*chain)->entities, chain) {
+ if (entity->id != status->bOriginator)
+ continue;
+
+ ctrl = uvc_event_entity_find_ctrl(entity,
+ status->bSelector);
+ if (ctrl)
+ return ctrl;
+ }
+ }
+
+ return NULL;
+}
+
+static bool uvc_event_control(struct urb *urb,
+ const struct uvc_control_status *status, int len)
+{
+ static const char *attrs[] = { "value", "info", "failure", "min", "max" };
+ struct uvc_device *dev = urb->context;
+ struct uvc_video_chain *chain;
+ struct uvc_control *ctrl;
+
+ if (len < 6 || status->bEvent != 0 ||
+ status->bAttribute >= ARRAY_SIZE(attrs)) {
+ uvc_dbg(dev, STATUS, "Invalid control status event received\n");
+ return false;
+ }
+
+ uvc_dbg(dev, STATUS, "Control %u/%u %s change len %d\n",
+ status->bOriginator, status->bSelector,
+ attrs[status->bAttribute], len);
+
+ /* Find the control. */
+ ctrl = uvc_event_find_ctrl(dev, status, &chain);
+ if (!ctrl)
+ return false;
+
+ switch (status->bAttribute) {
+ case UVC_CTRL_VALUE_CHANGE:
+ return uvc_ctrl_status_event_async(urb, chain, ctrl,
+ status->bValue);
+
+ case UVC_CTRL_INFO_CHANGE:
+ case UVC_CTRL_FAILURE_CHANGE:
+ case UVC_CTRL_MIN_CHANGE:
+ case UVC_CTRL_MAX_CHANGE:
+ break;
+ }
+
+ return false;
+}
+
+static void uvc_status_complete(struct urb *urb)
+{
+ struct uvc_device *dev = urb->context;
+ int len, ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+
+ case -ENOENT: /* usb_kill_urb() called. */
+ case -ECONNRESET: /* usb_unlink_urb() called. */
+ case -ESHUTDOWN: /* The endpoint is being disabled. */
+ case -EPROTO: /* Device is disconnected (reported by some host controllers). */
+ return;
+
+ default:
+ dev_warn(&dev->udev->dev,
+ "Non-zero status (%d) in status completion handler.\n",
+ urb->status);
+ return;
+ }
+
+ len = urb->actual_length;
+ if (len > 0) {
+ switch (dev->status[0] & 0x0f) {
+ case UVC_STATUS_TYPE_CONTROL: {
+ struct uvc_control_status *status =
+ (struct uvc_control_status *)dev->status;
+
+ if (uvc_event_control(urb, status, len))
+ /* The URB will be resubmitted in work context. */
+ return;
+ break;
+ }
+
+ case UVC_STATUS_TYPE_STREAMING: {
+ struct uvc_streaming_status *status =
+ (struct uvc_streaming_status *)dev->status;
+
+ uvc_event_streaming(dev, status, len);
+ break;
+ }
+
+ default:
+ uvc_dbg(dev, STATUS, "Unknown status event type %u\n",
+ dev->status[0]);
+ break;
+ }
+ }
+
+ /* Resubmit the URB. */
+ urb->interval = dev->int_ep->desc.bInterval;
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&dev->udev->dev,
+ "Failed to resubmit status URB (%d).\n", ret);
+}
+
+int uvc_status_init(struct uvc_device *dev)
+{
+ struct usb_host_endpoint *ep = dev->int_ep;
+ unsigned int pipe;
+ int interval;
+
+ if (ep == NULL)
+ return 0;
+
+ uvc_input_init(dev);
+
+ dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL);
+ if (dev->status == NULL)
+ return -ENOMEM;
+
+ dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (dev->int_urb == NULL) {
+ kfree(dev->status);
+ return -ENOMEM;
+ }
+
+ pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress);
+
+ /*
+ * For high-speed interrupt endpoints, the bInterval value is used as
+ * an exponent of two. Some developers forgot about it.
+ */
+ interval = ep->desc.bInterval;
+ if (interval > 16 && dev->udev->speed == USB_SPEED_HIGH &&
+ (dev->quirks & UVC_QUIRK_STATUS_INTERVAL))
+ interval = fls(interval) - 1;
+
+ usb_fill_int_urb(dev->int_urb, dev->udev, pipe,
+ dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete,
+ dev, interval);
+
+ return 0;
+}
+
+void uvc_status_unregister(struct uvc_device *dev)
+{
+ usb_kill_urb(dev->int_urb);
+ uvc_input_unregister(dev);
+}
+
+void uvc_status_cleanup(struct uvc_device *dev)
+{
+ usb_free_urb(dev->int_urb);
+ kfree(dev->status);
+}
+
+int uvc_status_start(struct uvc_device *dev, gfp_t flags)
+{
+ if (dev->int_urb == NULL)
+ return 0;
+
+ return usb_submit_urb(dev->int_urb, flags);
+}
+
+void uvc_status_stop(struct uvc_device *dev)
+{
+ struct uvc_ctrl_work *w = &dev->async_ctrl;
+
+ /*
+ * Prevent the asynchronous control handler from requeing the URB. The
+ * barrier is needed so the flush_status change is visible to other
+ * CPUs running the asynchronous handler before usb_kill_urb() is
+ * called below.
+ */
+ smp_store_release(&dev->flush_status, true);
+
+ /*
+ * Cancel any pending asynchronous work. If any status event was queued,
+ * process it synchronously.
+ */
+ if (cancel_work_sync(&w->work))
+ uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
+
+ /* Kill the urb. */
+ usb_kill_urb(dev->int_urb);
+
+ /*
+ * The URB completion handler may have queued asynchronous work. This
+ * won't resubmit the URB as flush_status is set, but it needs to be
+ * cancelled before returning or it could then race with a future
+ * uvc_status_start() call.
+ */
+ if (cancel_work_sync(&w->work))
+ uvc_ctrl_status_event(w->chain, w->ctrl, w->data);
+
+ /*
+ * From this point, there are no events on the queue and the status URB
+ * is dead. No events will be queued until uvc_status_start() is called.
+ * The barrier is needed to make sure that flush_status is visible to
+ * uvc_ctrl_status_event_work() when uvc_status_start() will be called
+ * again.
+ */
+ smp_store_release(&dev->flush_status, false);
+}
diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
new file mode 100644
index 000000000..950b42d78
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_v4l2.c
@@ -0,0 +1,1555 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_v4l2.c -- USB Video Class driver - V4L2 API
+ *
+ * Copyright (C) 2005-2010
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/bits.h>
+#include <linux/compat.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/wait.h>
+#include <linux/atomic.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * UVC ioctls
+ */
+static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
+ struct uvc_xu_control_mapping *xmap)
+{
+ struct uvc_control_mapping *map;
+ unsigned int size;
+ int ret;
+
+ map = kzalloc(sizeof(*map), GFP_KERNEL);
+ if (map == NULL)
+ return -ENOMEM;
+
+ map->id = xmap->id;
+ /* Non standard control id. */
+ if (v4l2_ctrl_get_name(map->id) == NULL) {
+ if (xmap->name[0] == '\0') {
+ ret = -EINVAL;
+ goto free_map;
+ }
+ xmap->name[sizeof(xmap->name) - 1] = '\0';
+ map->name = xmap->name;
+ }
+ memcpy(map->entity, xmap->entity, sizeof(map->entity));
+ map->selector = xmap->selector;
+ map->size = xmap->size;
+ map->offset = xmap->offset;
+ map->v4l2_type = xmap->v4l2_type;
+ map->data_type = xmap->data_type;
+
+ switch (xmap->v4l2_type) {
+ case V4L2_CTRL_TYPE_INTEGER:
+ case V4L2_CTRL_TYPE_BOOLEAN:
+ case V4L2_CTRL_TYPE_BUTTON:
+ break;
+
+ case V4L2_CTRL_TYPE_MENU:
+ /*
+ * Prevent excessive memory consumption, as well as integer
+ * overflows.
+ */
+ if (xmap->menu_count == 0 ||
+ xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) {
+ ret = -EINVAL;
+ goto free_map;
+ }
+
+ size = xmap->menu_count * sizeof(*map->menu_info);
+ map->menu_info = memdup_user(xmap->menu_info, size);
+ if (IS_ERR(map->menu_info)) {
+ ret = PTR_ERR(map->menu_info);
+ goto free_map;
+ }
+
+ map->menu_mask = GENMASK(xmap->menu_count - 1, 0);
+ break;
+
+ default:
+ uvc_dbg(chain->dev, CONTROL,
+ "Unsupported V4L2 control type %u\n", xmap->v4l2_type);
+ ret = -ENOTTY;
+ goto free_map;
+ }
+
+ ret = uvc_ctrl_add_mapping(chain, map);
+
+ kfree(map->menu_info);
+free_map:
+ kfree(map);
+
+ return ret;
+}
+
+/* ------------------------------------------------------------------------
+ * V4L2 interface
+ */
+
+/*
+ * Find the frame interval closest to the requested frame interval for the
+ * given frame format and size. This should be done by the device as part of
+ * the Video Probe and Commit negotiation, but some hardware don't implement
+ * that feature.
+ */
+static u32 uvc_try_frame_interval(struct uvc_frame *frame, u32 interval)
+{
+ unsigned int i;
+
+ if (frame->bFrameIntervalType) {
+ u32 best = -1, dist;
+
+ for (i = 0; i < frame->bFrameIntervalType; ++i) {
+ dist = interval > frame->dwFrameInterval[i]
+ ? interval - frame->dwFrameInterval[i]
+ : frame->dwFrameInterval[i] - interval;
+
+ if (dist > best)
+ break;
+
+ best = dist;
+ }
+
+ interval = frame->dwFrameInterval[i-1];
+ } else {
+ const u32 min = frame->dwFrameInterval[0];
+ const u32 max = frame->dwFrameInterval[1];
+ const u32 step = frame->dwFrameInterval[2];
+
+ interval = min + (interval - min + step/2) / step * step;
+ if (interval > max)
+ interval = max;
+ }
+
+ return interval;
+}
+
+static u32 uvc_v4l2_get_bytesperline(const struct uvc_format *format,
+ const struct uvc_frame *frame)
+{
+ switch (format->fcc) {
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_YVU420:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_M420:
+ return frame->wWidth;
+
+ default:
+ return format->bpp * frame->wWidth / 8;
+ }
+}
+
+static int uvc_v4l2_try_format(struct uvc_streaming *stream,
+ struct v4l2_format *fmt, struct uvc_streaming_control *probe,
+ struct uvc_format **uvc_format, struct uvc_frame **uvc_frame)
+{
+ struct uvc_format *format = NULL;
+ struct uvc_frame *frame = NULL;
+ u16 rw, rh;
+ unsigned int d, maxd;
+ unsigned int i;
+ u32 interval;
+ int ret = 0;
+ u8 *fcc;
+
+ if (fmt->type != stream->type)
+ return -EINVAL;
+
+ fcc = (u8 *)&fmt->fmt.pix.pixelformat;
+ uvc_dbg(stream->dev, FORMAT, "Trying format 0x%08x (%c%c%c%c): %ux%u\n",
+ fmt->fmt.pix.pixelformat,
+ fcc[0], fcc[1], fcc[2], fcc[3],
+ fmt->fmt.pix.width, fmt->fmt.pix.height);
+
+ /*
+ * Check if the hardware supports the requested format, use the default
+ * format otherwise.
+ */
+ for (i = 0; i < stream->nformats; ++i) {
+ format = &stream->format[i];
+ if (format->fcc == fmt->fmt.pix.pixelformat)
+ break;
+ }
+
+ if (i == stream->nformats) {
+ format = stream->def_format;
+ fmt->fmt.pix.pixelformat = format->fcc;
+ }
+
+ /*
+ * Find the closest image size. The distance between image sizes is
+ * the size in pixels of the non-overlapping regions between the
+ * requested size and the frame-specified size.
+ */
+ rw = fmt->fmt.pix.width;
+ rh = fmt->fmt.pix.height;
+ maxd = (unsigned int)-1;
+
+ for (i = 0; i < format->nframes; ++i) {
+ u16 w = format->frame[i].wWidth;
+ u16 h = format->frame[i].wHeight;
+
+ d = min(w, rw) * min(h, rh);
+ d = w*h + rw*rh - 2*d;
+ if (d < maxd) {
+ maxd = d;
+ frame = &format->frame[i];
+ }
+
+ if (maxd == 0)
+ break;
+ }
+
+ if (frame == NULL) {
+ uvc_dbg(stream->dev, FORMAT, "Unsupported size %ux%u\n",
+ fmt->fmt.pix.width, fmt->fmt.pix.height);
+ return -EINVAL;
+ }
+
+ /* Use the default frame interval. */
+ interval = frame->dwDefaultFrameInterval;
+ uvc_dbg(stream->dev, FORMAT,
+ "Using default frame interval %u.%u us (%u.%u fps)\n",
+ interval / 10, interval % 10, 10000000 / interval,
+ (100000000 / interval) % 10);
+
+ /* Set the format index, frame index and frame interval. */
+ memset(probe, 0, sizeof(*probe));
+ probe->bmHint = 1; /* dwFrameInterval */
+ probe->bFormatIndex = format->index;
+ probe->bFrameIndex = frame->bFrameIndex;
+ probe->dwFrameInterval = uvc_try_frame_interval(frame, interval);
+ /*
+ * Some webcams stall the probe control set request when the
+ * dwMaxVideoFrameSize field is set to zero. The UVC specification
+ * clearly states that the field is read-only from the host, so this
+ * is a webcam bug. Set dwMaxVideoFrameSize to the value reported by
+ * the webcam to work around the problem.
+ *
+ * The workaround could probably be enabled for all webcams, so the
+ * quirk can be removed if needed. It's currently useful to detect
+ * webcam bugs and fix them before they hit the market (providing
+ * developers test their webcams with the Linux driver as well as with
+ * the Windows driver).
+ */
+ mutex_lock(&stream->mutex);
+ if (stream->dev->quirks & UVC_QUIRK_PROBE_EXTRAFIELDS)
+ probe->dwMaxVideoFrameSize =
+ stream->ctrl.dwMaxVideoFrameSize;
+
+ /* Probe the device. */
+ ret = uvc_probe_video(stream, probe);
+ mutex_unlock(&stream->mutex);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * After the probe, update fmt with the values returned from
+ * negotiation with the device. Some devices return invalid bFormatIndex
+ * and bFrameIndex values, in which case we can only assume they have
+ * accepted the requested format as-is.
+ */
+ for (i = 0; i < stream->nformats; ++i) {
+ if (probe->bFormatIndex == stream->format[i].index) {
+ format = &stream->format[i];
+ break;
+ }
+ }
+
+ if (i == stream->nformats)
+ uvc_dbg(stream->dev, FORMAT,
+ "Unknown bFormatIndex %u, using default\n",
+ probe->bFormatIndex);
+
+ for (i = 0; i < format->nframes; ++i) {
+ if (probe->bFrameIndex == format->frame[i].bFrameIndex) {
+ frame = &format->frame[i];
+ break;
+ }
+ }
+
+ if (i == format->nframes)
+ uvc_dbg(stream->dev, FORMAT,
+ "Unknown bFrameIndex %u, using default\n",
+ probe->bFrameIndex);
+
+ fmt->fmt.pix.width = frame->wWidth;
+ fmt->fmt.pix.height = frame->wHeight;
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+ fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame);
+ fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize;
+ fmt->fmt.pix.pixelformat = format->fcc;
+ fmt->fmt.pix.colorspace = format->colorspace;
+ fmt->fmt.pix.xfer_func = format->xfer_func;
+ fmt->fmt.pix.ycbcr_enc = format->ycbcr_enc;
+
+ if (uvc_format != NULL)
+ *uvc_format = format;
+ if (uvc_frame != NULL)
+ *uvc_frame = frame;
+
+ return ret;
+}
+
+static int uvc_v4l2_get_format(struct uvc_streaming *stream,
+ struct v4l2_format *fmt)
+{
+ struct uvc_format *format;
+ struct uvc_frame *frame;
+ int ret = 0;
+
+ if (fmt->type != stream->type)
+ return -EINVAL;
+
+ mutex_lock(&stream->mutex);
+ format = stream->cur_format;
+ frame = stream->cur_frame;
+
+ if (format == NULL || frame == NULL) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ fmt->fmt.pix.pixelformat = format->fcc;
+ fmt->fmt.pix.width = frame->wWidth;
+ fmt->fmt.pix.height = frame->wHeight;
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+ fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame);
+ fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
+ fmt->fmt.pix.colorspace = format->colorspace;
+ fmt->fmt.pix.xfer_func = format->xfer_func;
+ fmt->fmt.pix.ycbcr_enc = format->ycbcr_enc;
+
+done:
+ mutex_unlock(&stream->mutex);
+ return ret;
+}
+
+static int uvc_v4l2_set_format(struct uvc_streaming *stream,
+ struct v4l2_format *fmt)
+{
+ struct uvc_streaming_control probe;
+ struct uvc_format *format;
+ struct uvc_frame *frame;
+ int ret;
+
+ if (fmt->type != stream->type)
+ return -EINVAL;
+
+ ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&stream->mutex);
+
+ if (uvc_queue_allocated(&stream->queue)) {
+ ret = -EBUSY;
+ goto done;
+ }
+
+ stream->ctrl = probe;
+ stream->cur_format = format;
+ stream->cur_frame = frame;
+
+done:
+ mutex_unlock(&stream->mutex);
+ return ret;
+}
+
+static int uvc_v4l2_get_streamparm(struct uvc_streaming *stream,
+ struct v4l2_streamparm *parm)
+{
+ u32 numerator, denominator;
+
+ if (parm->type != stream->type)
+ return -EINVAL;
+
+ mutex_lock(&stream->mutex);
+ numerator = stream->ctrl.dwFrameInterval;
+ mutex_unlock(&stream->mutex);
+
+ denominator = 10000000;
+ v4l2_simplify_fraction(&numerator, &denominator, 8, 333);
+
+ memset(parm, 0, sizeof(*parm));
+ parm->type = stream->type;
+
+ if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ parm->parm.capture.capturemode = 0;
+ parm->parm.capture.timeperframe.numerator = numerator;
+ parm->parm.capture.timeperframe.denominator = denominator;
+ parm->parm.capture.extendedmode = 0;
+ parm->parm.capture.readbuffers = 0;
+ } else {
+ parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+ parm->parm.output.outputmode = 0;
+ parm->parm.output.timeperframe.numerator = numerator;
+ parm->parm.output.timeperframe.denominator = denominator;
+ }
+
+ return 0;
+}
+
+static int uvc_v4l2_set_streamparm(struct uvc_streaming *stream,
+ struct v4l2_streamparm *parm)
+{
+ struct uvc_streaming_control probe;
+ struct v4l2_fract timeperframe;
+ struct uvc_format *format;
+ struct uvc_frame *frame;
+ u32 interval, maxd;
+ unsigned int i;
+ int ret;
+
+ if (parm->type != stream->type)
+ return -EINVAL;
+
+ if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ timeperframe = parm->parm.capture.timeperframe;
+ else
+ timeperframe = parm->parm.output.timeperframe;
+
+ interval = v4l2_fraction_to_interval(timeperframe.numerator,
+ timeperframe.denominator);
+ uvc_dbg(stream->dev, FORMAT, "Setting frame interval to %u/%u (%u)\n",
+ timeperframe.numerator, timeperframe.denominator, interval);
+
+ mutex_lock(&stream->mutex);
+
+ if (uvc_queue_streaming(&stream->queue)) {
+ mutex_unlock(&stream->mutex);
+ return -EBUSY;
+ }
+
+ format = stream->cur_format;
+ frame = stream->cur_frame;
+ probe = stream->ctrl;
+ probe.dwFrameInterval = uvc_try_frame_interval(frame, interval);
+ maxd = abs((s32)probe.dwFrameInterval - interval);
+
+ /* Try frames with matching size to find the best frame interval. */
+ for (i = 0; i < format->nframes && maxd != 0; i++) {
+ u32 d, ival;
+
+ if (&format->frame[i] == stream->cur_frame)
+ continue;
+
+ if (format->frame[i].wWidth != stream->cur_frame->wWidth ||
+ format->frame[i].wHeight != stream->cur_frame->wHeight)
+ continue;
+
+ ival = uvc_try_frame_interval(&format->frame[i], interval);
+ d = abs((s32)ival - interval);
+ if (d >= maxd)
+ continue;
+
+ frame = &format->frame[i];
+ probe.bFrameIndex = frame->bFrameIndex;
+ probe.dwFrameInterval = ival;
+ maxd = d;
+ }
+
+ /* Probe the device with the new settings. */
+ ret = uvc_probe_video(stream, &probe);
+ if (ret < 0) {
+ mutex_unlock(&stream->mutex);
+ return ret;
+ }
+
+ stream->ctrl = probe;
+ stream->cur_frame = frame;
+ mutex_unlock(&stream->mutex);
+
+ /* Return the actual frame period. */
+ timeperframe.numerator = probe.dwFrameInterval;
+ timeperframe.denominator = 10000000;
+ v4l2_simplify_fraction(&timeperframe.numerator,
+ &timeperframe.denominator, 8, 333);
+
+ if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ parm->parm.capture.timeperframe = timeperframe;
+ parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ } else {
+ parm->parm.output.timeperframe = timeperframe;
+ parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * Privilege management
+ */
+
+/*
+ * Privilege management is the multiple-open implementation basis. The current
+ * implementation is completely transparent for the end-user and doesn't
+ * require explicit use of the VIDIOC_G_PRIORITY and VIDIOC_S_PRIORITY ioctls.
+ * Those ioctls enable finer control on the device (by making possible for a
+ * user to request exclusive access to a device), but are not mature yet.
+ * Switching to the V4L2 priority mechanism might be considered in the future
+ * if this situation changes.
+ *
+ * Each open instance of a UVC device can either be in a privileged or
+ * unprivileged state. Only a single instance can be in a privileged state at
+ * a given time. Trying to perform an operation that requires privileges will
+ * automatically acquire the required privileges if possible, or return -EBUSY
+ * otherwise. Privileges are dismissed when closing the instance or when
+ * freeing the video buffers using VIDIOC_REQBUFS.
+ *
+ * Operations that require privileges are:
+ *
+ * - VIDIOC_S_INPUT
+ * - VIDIOC_S_PARM
+ * - VIDIOC_S_FMT
+ * - VIDIOC_REQBUFS
+ */
+static int uvc_acquire_privileges(struct uvc_fh *handle)
+{
+ /* Always succeed if the handle is already privileged. */
+ if (handle->state == UVC_HANDLE_ACTIVE)
+ return 0;
+
+ /* Check if the device already has a privileged handle. */
+ if (atomic_inc_return(&handle->stream->active) != 1) {
+ atomic_dec(&handle->stream->active);
+ return -EBUSY;
+ }
+
+ handle->state = UVC_HANDLE_ACTIVE;
+ return 0;
+}
+
+static void uvc_dismiss_privileges(struct uvc_fh *handle)
+{
+ if (handle->state == UVC_HANDLE_ACTIVE)
+ atomic_dec(&handle->stream->active);
+
+ handle->state = UVC_HANDLE_PASSIVE;
+}
+
+static int uvc_has_privileges(struct uvc_fh *handle)
+{
+ return handle->state == UVC_HANDLE_ACTIVE;
+}
+
+/* ------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+
+static int uvc_v4l2_open(struct file *file)
+{
+ struct uvc_streaming *stream;
+ struct uvc_fh *handle;
+ int ret = 0;
+
+ stream = video_drvdata(file);
+ uvc_dbg(stream->dev, CALLS, "%s\n", __func__);
+
+ ret = usb_autopm_get_interface(stream->dev->intf);
+ if (ret < 0)
+ return ret;
+
+ /* Create the device handle. */
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (handle == NULL) {
+ usb_autopm_put_interface(stream->dev->intf);
+ return -ENOMEM;
+ }
+
+ mutex_lock(&stream->dev->lock);
+ if (stream->dev->users == 0) {
+ ret = uvc_status_start(stream->dev, GFP_KERNEL);
+ if (ret < 0) {
+ mutex_unlock(&stream->dev->lock);
+ usb_autopm_put_interface(stream->dev->intf);
+ kfree(handle);
+ return ret;
+ }
+ }
+
+ stream->dev->users++;
+ mutex_unlock(&stream->dev->lock);
+
+ v4l2_fh_init(&handle->vfh, &stream->vdev);
+ v4l2_fh_add(&handle->vfh);
+ handle->chain = stream->chain;
+ handle->stream = stream;
+ handle->state = UVC_HANDLE_PASSIVE;
+ file->private_data = handle;
+
+ return 0;
+}
+
+static int uvc_v4l2_release(struct file *file)
+{
+ struct uvc_fh *handle = file->private_data;
+ struct uvc_streaming *stream = handle->stream;
+
+ uvc_dbg(stream->dev, CALLS, "%s\n", __func__);
+
+ /* Only free resources if this is a privileged handle. */
+ if (uvc_has_privileges(handle))
+ uvc_queue_release(&stream->queue);
+
+ /* Release the file handle. */
+ uvc_dismiss_privileges(handle);
+ v4l2_fh_del(&handle->vfh);
+ v4l2_fh_exit(&handle->vfh);
+ kfree(handle);
+ file->private_data = NULL;
+
+ mutex_lock(&stream->dev->lock);
+ if (--stream->dev->users == 0)
+ uvc_status_stop(stream->dev);
+ mutex_unlock(&stream->dev->lock);
+
+ usb_autopm_put_interface(stream->dev->intf);
+ return 0;
+}
+
+static int uvc_ioctl_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct uvc_fh *handle = file->private_data;
+ struct uvc_video_chain *chain = handle->chain;
+ struct uvc_streaming *stream = handle->stream;
+
+ strscpy(cap->driver, "uvcvideo", sizeof(cap->driver));
+ strscpy(cap->card, handle->stream->dev->name, sizeof(cap->card));
+ usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
+ cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
+ | chain->caps;
+
+ return 0;
+}
+
+static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
+ struct v4l2_fmtdesc *fmt)
+{
+ struct uvc_format *format;
+ enum v4l2_buf_type type = fmt->type;
+ u32 index = fmt->index;
+
+ if (fmt->type != stream->type || fmt->index >= stream->nformats)
+ return -EINVAL;
+
+ memset(fmt, 0, sizeof(*fmt));
+ fmt->index = index;
+ fmt->type = type;
+
+ format = &stream->format[fmt->index];
+ fmt->flags = 0;
+ if (format->flags & UVC_FMT_FLAG_COMPRESSED)
+ fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;
+ fmt->pixelformat = format->fcc;
+ return 0;
+}
+
+static int uvc_ioctl_enum_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_fmtdesc *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ return uvc_ioctl_enum_fmt(stream, fmt);
+}
+
+static int uvc_ioctl_enum_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_fmtdesc *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ return uvc_ioctl_enum_fmt(stream, fmt);
+}
+
+static int uvc_ioctl_g_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ return uvc_v4l2_get_format(stream, fmt);
+}
+
+static int uvc_ioctl_g_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ return uvc_v4l2_get_format(stream, fmt);
+}
+
+static int uvc_ioctl_s_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ int ret;
+
+ ret = uvc_acquire_privileges(handle);
+ if (ret < 0)
+ return ret;
+
+ return uvc_v4l2_set_format(stream, fmt);
+}
+
+static int uvc_ioctl_s_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ int ret;
+
+ ret = uvc_acquire_privileges(handle);
+ if (ret < 0)
+ return ret;
+
+ return uvc_v4l2_set_format(stream, fmt);
+}
+
+static int uvc_ioctl_try_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ struct uvc_streaming_control probe;
+
+ return uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);
+}
+
+static int uvc_ioctl_try_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *fmt)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ struct uvc_streaming_control probe;
+
+ return uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);
+}
+
+static int uvc_ioctl_reqbufs(struct file *file, void *fh,
+ struct v4l2_requestbuffers *rb)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ int ret;
+
+ ret = uvc_acquire_privileges(handle);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&stream->mutex);
+ ret = uvc_request_buffers(&stream->queue, rb);
+ mutex_unlock(&stream->mutex);
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0)
+ uvc_dismiss_privileges(handle);
+
+ return 0;
+}
+
+static int uvc_ioctl_querybuf(struct file *file, void *fh,
+ struct v4l2_buffer *buf)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ if (!uvc_has_privileges(handle))
+ return -EBUSY;
+
+ return uvc_query_buffer(&stream->queue, buf);
+}
+
+static int uvc_ioctl_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ if (!uvc_has_privileges(handle))
+ return -EBUSY;
+
+ return uvc_queue_buffer(&stream->queue,
+ stream->vdev.v4l2_dev->mdev, buf);
+}
+
+static int uvc_ioctl_expbuf(struct file *file, void *fh,
+ struct v4l2_exportbuffer *exp)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ if (!uvc_has_privileges(handle))
+ return -EBUSY;
+
+ return uvc_export_buffer(&stream->queue, exp);
+}
+
+static int uvc_ioctl_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ if (!uvc_has_privileges(handle))
+ return -EBUSY;
+
+ return uvc_dequeue_buffer(&stream->queue, buf,
+ file->f_flags & O_NONBLOCK);
+}
+
+static int uvc_ioctl_create_bufs(struct file *file, void *fh,
+ struct v4l2_create_buffers *cb)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ int ret;
+
+ ret = uvc_acquire_privileges(handle);
+ if (ret < 0)
+ return ret;
+
+ return uvc_create_buffers(&stream->queue, cb);
+}
+
+static int uvc_ioctl_streamon(struct file *file, void *fh,
+ enum v4l2_buf_type type)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ int ret;
+
+ if (!uvc_has_privileges(handle))
+ return -EBUSY;
+
+ mutex_lock(&stream->mutex);
+ ret = uvc_queue_streamon(&stream->queue, type);
+ mutex_unlock(&stream->mutex);
+
+ return ret;
+}
+
+static int uvc_ioctl_streamoff(struct file *file, void *fh,
+ enum v4l2_buf_type type)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ if (!uvc_has_privileges(handle))
+ return -EBUSY;
+
+ mutex_lock(&stream->mutex);
+ uvc_queue_streamoff(&stream->queue, type);
+ mutex_unlock(&stream->mutex);
+
+ return 0;
+}
+
+static int uvc_ioctl_enum_input(struct file *file, void *fh,
+ struct v4l2_input *input)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+ const struct uvc_entity *selector = chain->selector;
+ struct uvc_entity *iterm = NULL;
+ struct uvc_entity *it;
+ u32 index = input->index;
+
+ if (selector == NULL ||
+ (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) {
+ if (index != 0)
+ return -EINVAL;
+ list_for_each_entry(it, &chain->entities, chain) {
+ if (UVC_ENTITY_IS_ITERM(it)) {
+ iterm = it;
+ break;
+ }
+ }
+ } else if (index < selector->bNrInPins) {
+ list_for_each_entry(it, &chain->entities, chain) {
+ if (!UVC_ENTITY_IS_ITERM(it))
+ continue;
+ if (it->id == selector->baSourceID[index]) {
+ iterm = it;
+ break;
+ }
+ }
+ }
+
+ if (iterm == NULL)
+ return -EINVAL;
+
+ memset(input, 0, sizeof(*input));
+ input->index = index;
+ strscpy(input->name, iterm->name, sizeof(input->name));
+ if (UVC_ENTITY_TYPE(iterm) == UVC_ITT_CAMERA)
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+
+ return 0;
+}
+
+static int uvc_ioctl_g_input(struct file *file, void *fh, unsigned int *input)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+ u8 *buf;
+ int ret;
+
+ if (chain->selector == NULL ||
+ (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) {
+ *input = 0;
+ return 0;
+ }
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, chain->selector->id,
+ chain->dev->intfnum, UVC_SU_INPUT_SELECT_CONTROL,
+ buf, 1);
+ if (!ret)
+ *input = *buf - 1;
+
+ kfree(buf);
+
+ return ret;
+}
+
+static int uvc_ioctl_s_input(struct file *file, void *fh, unsigned int input)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+ u8 *buf;
+ int ret;
+
+ ret = uvc_acquire_privileges(handle);
+ if (ret < 0)
+ return ret;
+
+ if (chain->selector == NULL ||
+ (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) {
+ if (input)
+ return -EINVAL;
+ return 0;
+ }
+
+ if (input >= chain->selector->bNrInPins)
+ return -EINVAL;
+
+ buf = kmalloc(1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ *buf = input + 1;
+ ret = uvc_query_ctrl(chain->dev, UVC_SET_CUR, chain->selector->id,
+ chain->dev->intfnum, UVC_SU_INPUT_SELECT_CONTROL,
+ buf, 1);
+ kfree(buf);
+
+ return ret;
+}
+
+static int uvc_ioctl_queryctrl(struct file *file, void *fh,
+ struct v4l2_queryctrl *qc)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+
+ return uvc_query_v4l2_ctrl(chain, qc);
+}
+
+static int uvc_ioctl_query_ext_ctrl(struct file *file, void *fh,
+ struct v4l2_query_ext_ctrl *qec)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+ struct v4l2_queryctrl qc = { qec->id };
+ int ret;
+
+ ret = uvc_query_v4l2_ctrl(chain, &qc);
+ if (ret)
+ return ret;
+
+ qec->id = qc.id;
+ qec->type = qc.type;
+ strscpy(qec->name, qc.name, sizeof(qec->name));
+ qec->minimum = qc.minimum;
+ qec->maximum = qc.maximum;
+ qec->step = qc.step;
+ qec->default_value = qc.default_value;
+ qec->flags = qc.flags;
+ qec->elem_size = 4;
+ qec->elems = 1;
+ qec->nr_of_dims = 0;
+ memset(qec->dims, 0, sizeof(qec->dims));
+ memset(qec->reserved, 0, sizeof(qec->reserved));
+
+ return 0;
+}
+
+static int uvc_ctrl_check_access(struct uvc_video_chain *chain,
+ struct v4l2_ext_controls *ctrls,
+ unsigned long ioctl)
+{
+ struct v4l2_ext_control *ctrl = ctrls->controls;
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+ ret = uvc_ctrl_is_accessible(chain, ctrl->id, ctrls, ioctl);
+ if (ret)
+ break;
+ }
+
+ ctrls->error_idx = ioctl == VIDIOC_TRY_EXT_CTRLS ? i : ctrls->count;
+
+ return ret;
+}
+
+static int uvc_ioctl_g_ext_ctrls(struct file *file, void *fh,
+ struct v4l2_ext_controls *ctrls)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+ struct v4l2_ext_control *ctrl = ctrls->controls;
+ unsigned int i;
+ int ret;
+
+ ret = uvc_ctrl_check_access(chain, ctrls, VIDIOC_G_EXT_CTRLS);
+ if (ret < 0)
+ return ret;
+
+ if (ctrls->which == V4L2_CTRL_WHICH_DEF_VAL) {
+ for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+ struct v4l2_queryctrl qc = { .id = ctrl->id };
+
+ ret = uvc_query_v4l2_ctrl(chain, &qc);
+ if (ret < 0) {
+ ctrls->error_idx = i;
+ return ret;
+ }
+
+ ctrl->value = qc.default_value;
+ }
+
+ return 0;
+ }
+
+ ret = uvc_ctrl_begin(chain);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+ ret = uvc_ctrl_get(chain, ctrl);
+ if (ret < 0) {
+ uvc_ctrl_rollback(handle);
+ ctrls->error_idx = i;
+ return ret;
+ }
+ }
+
+ ctrls->error_idx = 0;
+
+ return uvc_ctrl_rollback(handle);
+}
+
+static int uvc_ioctl_s_try_ext_ctrls(struct uvc_fh *handle,
+ struct v4l2_ext_controls *ctrls,
+ unsigned long ioctl)
+{
+ struct v4l2_ext_control *ctrl = ctrls->controls;
+ struct uvc_video_chain *chain = handle->chain;
+ unsigned int i;
+ int ret;
+
+ ret = uvc_ctrl_check_access(chain, ctrls, ioctl);
+ if (ret < 0)
+ return ret;
+
+ ret = uvc_ctrl_begin(chain);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+ ret = uvc_ctrl_set(handle, ctrl);
+ if (ret < 0) {
+ uvc_ctrl_rollback(handle);
+ ctrls->error_idx = ioctl == VIDIOC_S_EXT_CTRLS ?
+ ctrls->count : i;
+ return ret;
+ }
+ }
+
+ ctrls->error_idx = 0;
+
+ if (ioctl == VIDIOC_S_EXT_CTRLS)
+ return uvc_ctrl_commit(handle, ctrls);
+ else
+ return uvc_ctrl_rollback(handle);
+}
+
+static int uvc_ioctl_s_ext_ctrls(struct file *file, void *fh,
+ struct v4l2_ext_controls *ctrls)
+{
+ struct uvc_fh *handle = fh;
+
+ return uvc_ioctl_s_try_ext_ctrls(handle, ctrls, VIDIOC_S_EXT_CTRLS);
+}
+
+static int uvc_ioctl_try_ext_ctrls(struct file *file, void *fh,
+ struct v4l2_ext_controls *ctrls)
+{
+ struct uvc_fh *handle = fh;
+
+ return uvc_ioctl_s_try_ext_ctrls(handle, ctrls, VIDIOC_TRY_EXT_CTRLS);
+}
+
+static int uvc_ioctl_querymenu(struct file *file, void *fh,
+ struct v4l2_querymenu *qm)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+
+ return uvc_query_v4l2_menu(chain, qm);
+}
+
+static int uvc_ioctl_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *sel)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ if (sel->type != stream->type)
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ if (stream->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ break;
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ if (stream->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ sel->r.left = 0;
+ sel->r.top = 0;
+ mutex_lock(&stream->mutex);
+ sel->r.width = stream->cur_frame->wWidth;
+ sel->r.height = stream->cur_frame->wHeight;
+ mutex_unlock(&stream->mutex);
+
+ return 0;
+}
+
+static int uvc_ioctl_g_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *parm)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+
+ return uvc_v4l2_get_streamparm(stream, parm);
+}
+
+static int uvc_ioctl_s_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *parm)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ int ret;
+
+ ret = uvc_acquire_privileges(handle);
+ if (ret < 0)
+ return ret;
+
+ return uvc_v4l2_set_streamparm(stream, parm);
+}
+
+static int uvc_ioctl_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ struct uvc_format *format = NULL;
+ struct uvc_frame *frame = NULL;
+ unsigned int index;
+ unsigned int i;
+
+ /* Look for the given pixel format */
+ for (i = 0; i < stream->nformats; i++) {
+ if (stream->format[i].fcc == fsize->pixel_format) {
+ format = &stream->format[i];
+ break;
+ }
+ }
+ if (format == NULL)
+ return -EINVAL;
+
+ /* Skip duplicate frame sizes */
+ for (i = 0, index = 0; i < format->nframes; i++) {
+ if (frame && frame->wWidth == format->frame[i].wWidth &&
+ frame->wHeight == format->frame[i].wHeight)
+ continue;
+ frame = &format->frame[i];
+ if (index == fsize->index)
+ break;
+ index++;
+ }
+
+ if (i == format->nframes)
+ return -EINVAL;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = frame->wWidth;
+ fsize->discrete.height = frame->wHeight;
+ return 0;
+}
+
+static int uvc_ioctl_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *fival)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_streaming *stream = handle->stream;
+ struct uvc_format *format = NULL;
+ struct uvc_frame *frame = NULL;
+ unsigned int nintervals;
+ unsigned int index;
+ unsigned int i;
+
+ /* Look for the given pixel format and frame size */
+ for (i = 0; i < stream->nformats; i++) {
+ if (stream->format[i].fcc == fival->pixel_format) {
+ format = &stream->format[i];
+ break;
+ }
+ }
+ if (format == NULL)
+ return -EINVAL;
+
+ index = fival->index;
+ for (i = 0; i < format->nframes; i++) {
+ if (format->frame[i].wWidth == fival->width &&
+ format->frame[i].wHeight == fival->height) {
+ frame = &format->frame[i];
+ nintervals = frame->bFrameIntervalType ?: 1;
+ if (index < nintervals)
+ break;
+ index -= nintervals;
+ }
+ }
+ if (i == format->nframes)
+ return -EINVAL;
+
+ if (frame->bFrameIntervalType) {
+ fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ fival->discrete.numerator =
+ frame->dwFrameInterval[index];
+ fival->discrete.denominator = 10000000;
+ v4l2_simplify_fraction(&fival->discrete.numerator,
+ &fival->discrete.denominator, 8, 333);
+ } else {
+ fival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+ fival->stepwise.min.numerator = frame->dwFrameInterval[0];
+ fival->stepwise.min.denominator = 10000000;
+ fival->stepwise.max.numerator = frame->dwFrameInterval[1];
+ fival->stepwise.max.denominator = 10000000;
+ fival->stepwise.step.numerator = frame->dwFrameInterval[2];
+ fival->stepwise.step.denominator = 10000000;
+ v4l2_simplify_fraction(&fival->stepwise.min.numerator,
+ &fival->stepwise.min.denominator, 8, 333);
+ v4l2_simplify_fraction(&fival->stepwise.max.numerator,
+ &fival->stepwise.max.denominator, 8, 333);
+ v4l2_simplify_fraction(&fival->stepwise.step.numerator,
+ &fival->stepwise.step.denominator, 8, 333);
+ }
+
+ return 0;
+}
+
+static int uvc_ioctl_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_CTRL:
+ return v4l2_event_subscribe(fh, sub, 0, &uvc_ctrl_sub_ev_ops);
+ default:
+ return -EINVAL;
+ }
+}
+
+static long uvc_ioctl_default(struct file *file, void *fh, bool valid_prio,
+ unsigned int cmd, void *arg)
+{
+ struct uvc_fh *handle = fh;
+ struct uvc_video_chain *chain = handle->chain;
+
+ switch (cmd) {
+ /* Dynamic controls. */
+ case UVCIOC_CTRL_MAP:
+ return uvc_ioctl_ctrl_map(chain, arg);
+
+ case UVCIOC_CTRL_QUERY:
+ return uvc_xu_ctrl_query(chain, arg);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+#ifdef CONFIG_COMPAT
+struct uvc_xu_control_mapping32 {
+ u32 id;
+ u8 name[32];
+ u8 entity[16];
+ u8 selector;
+
+ u8 size;
+ u8 offset;
+ u32 v4l2_type;
+ u32 data_type;
+
+ compat_caddr_t menu_info;
+ u32 menu_count;
+
+ u32 reserved[4];
+};
+
+static int uvc_v4l2_get_xu_mapping(struct uvc_xu_control_mapping *kp,
+ const struct uvc_xu_control_mapping32 __user *up)
+{
+ struct uvc_xu_control_mapping32 *p = (void *)kp;
+ compat_caddr_t info;
+ u32 count;
+
+ if (copy_from_user(p, up, sizeof(*p)))
+ return -EFAULT;
+
+ count = p->menu_count;
+ info = p->menu_info;
+
+ memset(kp->reserved, 0, sizeof(kp->reserved));
+ kp->menu_info = count ? compat_ptr(info) : NULL;
+ kp->menu_count = count;
+ return 0;
+}
+
+static int uvc_v4l2_put_xu_mapping(const struct uvc_xu_control_mapping *kp,
+ struct uvc_xu_control_mapping32 __user *up)
+{
+ if (copy_to_user(up, kp, offsetof(typeof(*up), menu_info)) ||
+ put_user(kp->menu_count, &up->menu_count))
+ return -EFAULT;
+
+ if (clear_user(up->reserved, sizeof(up->reserved)))
+ return -EFAULT;
+
+ return 0;
+}
+
+struct uvc_xu_control_query32 {
+ u8 unit;
+ u8 selector;
+ u8 query;
+ u16 size;
+ compat_caddr_t data;
+};
+
+static int uvc_v4l2_get_xu_query(struct uvc_xu_control_query *kp,
+ const struct uvc_xu_control_query32 __user *up)
+{
+ struct uvc_xu_control_query32 v;
+
+ if (copy_from_user(&v, up, sizeof(v)))
+ return -EFAULT;
+
+ *kp = (struct uvc_xu_control_query){
+ .unit = v.unit,
+ .selector = v.selector,
+ .query = v.query,
+ .size = v.size,
+ .data = v.size ? compat_ptr(v.data) : NULL
+ };
+ return 0;
+}
+
+static int uvc_v4l2_put_xu_query(const struct uvc_xu_control_query *kp,
+ struct uvc_xu_control_query32 __user *up)
+{
+ if (copy_to_user(up, kp, offsetof(typeof(*up), data)))
+ return -EFAULT;
+ return 0;
+}
+
+#define UVCIOC_CTRL_MAP32 _IOWR('u', 0x20, struct uvc_xu_control_mapping32)
+#define UVCIOC_CTRL_QUERY32 _IOWR('u', 0x21, struct uvc_xu_control_query32)
+
+static long uvc_v4l2_compat_ioctl32(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct uvc_fh *handle = file->private_data;
+ union {
+ struct uvc_xu_control_mapping xmap;
+ struct uvc_xu_control_query xqry;
+ } karg;
+ void __user *up = compat_ptr(arg);
+ long ret;
+
+ switch (cmd) {
+ case UVCIOC_CTRL_MAP32:
+ ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up);
+ if (ret)
+ return ret;
+ ret = uvc_ioctl_ctrl_map(handle->chain, &karg.xmap);
+ if (ret)
+ return ret;
+ ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up);
+ if (ret)
+ return ret;
+
+ break;
+
+ case UVCIOC_CTRL_QUERY32:
+ ret = uvc_v4l2_get_xu_query(&karg.xqry, up);
+ if (ret)
+ return ret;
+ ret = uvc_xu_ctrl_query(handle->chain, &karg.xqry);
+ if (ret)
+ return ret;
+ ret = uvc_v4l2_put_xu_query(&karg.xqry, up);
+ if (ret)
+ return ret;
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return ret;
+}
+#endif
+
+static ssize_t uvc_v4l2_read(struct file *file, char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct uvc_fh *handle = file->private_data;
+ struct uvc_streaming *stream = handle->stream;
+
+ uvc_dbg(stream->dev, CALLS, "%s: not implemented\n", __func__);
+ return -EINVAL;
+}
+
+static int uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct uvc_fh *handle = file->private_data;
+ struct uvc_streaming *stream = handle->stream;
+
+ uvc_dbg(stream->dev, CALLS, "%s\n", __func__);
+
+ return uvc_queue_mmap(&stream->queue, vma);
+}
+
+static __poll_t uvc_v4l2_poll(struct file *file, poll_table *wait)
+{
+ struct uvc_fh *handle = file->private_data;
+ struct uvc_streaming *stream = handle->stream;
+
+ uvc_dbg(stream->dev, CALLS, "%s\n", __func__);
+
+ return uvc_queue_poll(&stream->queue, file, wait);
+}
+
+#ifndef CONFIG_MMU
+static unsigned long uvc_v4l2_get_unmapped_area(struct file *file,
+ unsigned long addr, unsigned long len, unsigned long pgoff,
+ unsigned long flags)
+{
+ struct uvc_fh *handle = file->private_data;
+ struct uvc_streaming *stream = handle->stream;
+
+ uvc_dbg(stream->dev, CALLS, "%s\n", __func__);
+
+ return uvc_queue_get_unmapped_area(&stream->queue, pgoff);
+}
+#endif
+
+const struct v4l2_ioctl_ops uvc_ioctl_ops = {
+ .vidioc_querycap = uvc_ioctl_querycap,
+ .vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
+ .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
+ .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
+ .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
+ .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
+ .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
+ .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
+ .vidioc_reqbufs = uvc_ioctl_reqbufs,
+ .vidioc_querybuf = uvc_ioctl_querybuf,
+ .vidioc_qbuf = uvc_ioctl_qbuf,
+ .vidioc_expbuf = uvc_ioctl_expbuf,
+ .vidioc_dqbuf = uvc_ioctl_dqbuf,
+ .vidioc_create_bufs = uvc_ioctl_create_bufs,
+ .vidioc_streamon = uvc_ioctl_streamon,
+ .vidioc_streamoff = uvc_ioctl_streamoff,
+ .vidioc_enum_input = uvc_ioctl_enum_input,
+ .vidioc_g_input = uvc_ioctl_g_input,
+ .vidioc_s_input = uvc_ioctl_s_input,
+ .vidioc_queryctrl = uvc_ioctl_queryctrl,
+ .vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
+ .vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
+ .vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
+ .vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
+ .vidioc_querymenu = uvc_ioctl_querymenu,
+ .vidioc_g_selection = uvc_ioctl_g_selection,
+ .vidioc_g_parm = uvc_ioctl_g_parm,
+ .vidioc_s_parm = uvc_ioctl_s_parm,
+ .vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
+ .vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,
+ .vidioc_subscribe_event = uvc_ioctl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_default = uvc_ioctl_default,
+};
+
+const struct v4l2_file_operations uvc_fops = {
+ .owner = THIS_MODULE,
+ .open = uvc_v4l2_open,
+ .release = uvc_v4l2_release,
+ .unlocked_ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl32 = uvc_v4l2_compat_ioctl32,
+#endif
+ .read = uvc_v4l2_read,
+ .mmap = uvc_v4l2_mmap,
+ .poll = uvc_v4l2_poll,
+#ifndef CONFIG_MMU
+ .get_unmapped_area = uvc_v4l2_get_unmapped_area,
+#endif
+};
+
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
new file mode 100644
index 000000000..0d3a3b697
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -0,0 +1,2251 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * uvc_video.c -- USB Video Class driver - Video handling
+ *
+ * Copyright (C) 2005-2010
+ * Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/atomic.h>
+#include <asm/unaligned.h>
+
+#include <media/v4l2-common.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * UVC Controls
+ */
+
+static int __uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
+ u8 intfnum, u8 cs, void *data, u16 size,
+ int timeout)
+{
+ u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+ unsigned int pipe;
+
+ pipe = (query & 0x80) ? usb_rcvctrlpipe(dev->udev, 0)
+ : usb_sndctrlpipe(dev->udev, 0);
+ type |= (query & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
+
+ return usb_control_msg(dev->udev, pipe, query, type, cs << 8,
+ unit << 8 | intfnum, data, size, timeout);
+}
+
+static const char *uvc_query_name(u8 query)
+{
+ switch (query) {
+ case UVC_SET_CUR:
+ return "SET_CUR";
+ case UVC_GET_CUR:
+ return "GET_CUR";
+ case UVC_GET_MIN:
+ return "GET_MIN";
+ case UVC_GET_MAX:
+ return "GET_MAX";
+ case UVC_GET_RES:
+ return "GET_RES";
+ case UVC_GET_LEN:
+ return "GET_LEN";
+ case UVC_GET_INFO:
+ return "GET_INFO";
+ case UVC_GET_DEF:
+ return "GET_DEF";
+ default:
+ return "<invalid>";
+ }
+}
+
+int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
+ u8 intfnum, u8 cs, void *data, u16 size)
+{
+ int ret;
+ u8 error;
+ u8 tmp;
+
+ ret = __uvc_query_ctrl(dev, query, unit, intfnum, cs, data, size,
+ UVC_CTRL_CONTROL_TIMEOUT);
+ if (likely(ret == size))
+ return 0;
+
+ dev_err(&dev->udev->dev,
+ "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n",
+ uvc_query_name(query), cs, unit, ret, size);
+
+ if (ret != -EPIPE)
+ return ret;
+
+ tmp = *(u8 *)data;
+
+ ret = __uvc_query_ctrl(dev, UVC_GET_CUR, 0, intfnum,
+ UVC_VC_REQUEST_ERROR_CODE_CONTROL, data, 1,
+ UVC_CTRL_CONTROL_TIMEOUT);
+
+ error = *(u8 *)data;
+ *(u8 *)data = tmp;
+
+ if (ret != 1)
+ return ret < 0 ? ret : -EPIPE;
+
+ uvc_dbg(dev, CONTROL, "Control error %u\n", error);
+
+ switch (error) {
+ case 0:
+ /* Cannot happen - we received a STALL */
+ return -EPIPE;
+ case 1: /* Not ready */
+ return -EBUSY;
+ case 2: /* Wrong state */
+ return -EILSEQ;
+ case 3: /* Power */
+ return -EREMOTE;
+ case 4: /* Out of range */
+ return -ERANGE;
+ case 5: /* Invalid unit */
+ case 6: /* Invalid control */
+ case 7: /* Invalid Request */
+ /*
+ * The firmware has not properly implemented
+ * the control or there has been a HW error.
+ */
+ return -EIO;
+ case 8: /* Invalid value within range */
+ return -EINVAL;
+ default: /* reserved or unknown */
+ break;
+ }
+
+ return -EPIPE;
+}
+
+static void uvc_fixup_video_ctrl(struct uvc_streaming *stream,
+ struct uvc_streaming_control *ctrl)
+{
+ static const struct usb_device_id elgato_cam_link_4k = {
+ USB_DEVICE(0x0fd9, 0x0066)
+ };
+ struct uvc_format *format = NULL;
+ struct uvc_frame *frame = NULL;
+ unsigned int i;
+
+ /*
+ * The response of the Elgato Cam Link 4K is incorrect: The second byte
+ * contains bFormatIndex (instead of being the second byte of bmHint).
+ * The first byte is always zero. The third byte is always 1.
+ *
+ * The UVC 1.5 class specification defines the first five bits in the
+ * bmHint bitfield. The remaining bits are reserved and should be zero.
+ * Therefore a valid bmHint will be less than 32.
+ *
+ * Latest Elgato Cam Link 4K firmware as of 2021-03-23 needs this fix.
+ * MCU: 20.02.19, FPGA: 67
+ */
+ if (usb_match_one_id(stream->dev->intf, &elgato_cam_link_4k) &&
+ ctrl->bmHint > 255) {
+ u8 corrected_format_index = ctrl->bmHint >> 8;
+
+ uvc_dbg(stream->dev, VIDEO,
+ "Correct USB video probe response from {bmHint: 0x%04x, bFormatIndex: %u} to {bmHint: 0x%04x, bFormatIndex: %u}\n",
+ ctrl->bmHint, ctrl->bFormatIndex,
+ 1, corrected_format_index);
+ ctrl->bmHint = 1;
+ ctrl->bFormatIndex = corrected_format_index;
+ }
+
+ for (i = 0; i < stream->nformats; ++i) {
+ if (stream->format[i].index == ctrl->bFormatIndex) {
+ format = &stream->format[i];
+ break;
+ }
+ }
+
+ if (format == NULL)
+ return;
+
+ for (i = 0; i < format->nframes; ++i) {
+ if (format->frame[i].bFrameIndex == ctrl->bFrameIndex) {
+ frame = &format->frame[i];
+ break;
+ }
+ }
+
+ if (frame == NULL)
+ return;
+
+ if (!(format->flags & UVC_FMT_FLAG_COMPRESSED) ||
+ (ctrl->dwMaxVideoFrameSize == 0 &&
+ stream->dev->uvc_version < 0x0110))
+ ctrl->dwMaxVideoFrameSize =
+ frame->dwMaxVideoFrameBufferSize;
+
+ /*
+ * The "TOSHIBA Web Camera - 5M" Chicony device (04f2:b50b) seems to
+ * compute the bandwidth on 16 bits and erroneously sign-extend it to
+ * 32 bits, resulting in a huge bandwidth value. Detect and fix that
+ * condition by setting the 16 MSBs to 0 when they're all equal to 1.
+ */
+ if ((ctrl->dwMaxPayloadTransferSize & 0xffff0000) == 0xffff0000)
+ ctrl->dwMaxPayloadTransferSize &= ~0xffff0000;
+
+ if (!(format->flags & UVC_FMT_FLAG_COMPRESSED) &&
+ stream->dev->quirks & UVC_QUIRK_FIX_BANDWIDTH &&
+ stream->intf->num_altsetting > 1) {
+ u32 interval;
+ u32 bandwidth;
+
+ interval = (ctrl->dwFrameInterval > 100000)
+ ? ctrl->dwFrameInterval
+ : frame->dwFrameInterval[0];
+
+ /*
+ * Compute a bandwidth estimation by multiplying the frame
+ * size by the number of video frames per second, divide the
+ * result by the number of USB frames (or micro-frames for
+ * high-speed devices) per second and add the UVC header size
+ * (assumed to be 12 bytes long).
+ */
+ bandwidth = frame->wWidth * frame->wHeight / 8 * format->bpp;
+ bandwidth *= 10000000 / interval + 1;
+ bandwidth /= 1000;
+ if (stream->dev->udev->speed == USB_SPEED_HIGH)
+ bandwidth /= 8;
+ bandwidth += 12;
+
+ /*
+ * The bandwidth estimate is too low for many cameras. Don't use
+ * maximum packet sizes lower than 1024 bytes to try and work
+ * around the problem. According to measurements done on two
+ * different camera models, the value is high enough to get most
+ * resolutions working while not preventing two simultaneous
+ * VGA streams at 15 fps.
+ */
+ bandwidth = max_t(u32, bandwidth, 1024);
+
+ ctrl->dwMaxPayloadTransferSize = bandwidth;
+ }
+}
+
+static size_t uvc_video_ctrl_size(struct uvc_streaming *stream)
+{
+ /*
+ * Return the size of the video probe and commit controls, which depends
+ * on the protocol version.
+ */
+ if (stream->dev->uvc_version < 0x0110)
+ return 26;
+ else if (stream->dev->uvc_version < 0x0150)
+ return 34;
+ else
+ return 48;
+}
+
+static int uvc_get_video_ctrl(struct uvc_streaming *stream,
+ struct uvc_streaming_control *ctrl, int probe, u8 query)
+{
+ u16 size = uvc_video_ctrl_size(stream);
+ u8 *data;
+ int ret;
+
+ if ((stream->dev->quirks & UVC_QUIRK_PROBE_DEF) &&
+ query == UVC_GET_DEF)
+ return -EIO;
+
+ data = kmalloc(size, GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ ret = __uvc_query_ctrl(stream->dev, query, 0, stream->intfnum,
+ probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
+ size, uvc_timeout_param);
+
+ if ((query == UVC_GET_MIN || query == UVC_GET_MAX) && ret == 2) {
+ /*
+ * Some cameras, mostly based on Bison Electronics chipsets,
+ * answer a GET_MIN or GET_MAX request with the wCompQuality
+ * field only.
+ */
+ uvc_warn_once(stream->dev, UVC_WARN_MINMAX, "UVC non "
+ "compliance - GET_MIN/MAX(PROBE) incorrectly "
+ "supported. Enabling workaround.\n");
+ memset(ctrl, 0, sizeof(*ctrl));
+ ctrl->wCompQuality = le16_to_cpup((__le16 *)data);
+ ret = 0;
+ goto out;
+ } else if (query == UVC_GET_DEF && probe == 1 && ret != size) {
+ /*
+ * Many cameras don't support the GET_DEF request on their
+ * video probe control. Warn once and return, the caller will
+ * fall back to GET_CUR.
+ */
+ uvc_warn_once(stream->dev, UVC_WARN_PROBE_DEF, "UVC non "
+ "compliance - GET_DEF(PROBE) not supported. "
+ "Enabling workaround.\n");
+ ret = -EIO;
+ goto out;
+ } else if (ret != size) {
+ dev_err(&stream->intf->dev,
+ "Failed to query (%u) UVC %s control : %d (exp. %u).\n",
+ query, probe ? "probe" : "commit", ret, size);
+ ret = -EIO;
+ goto out;
+ }
+
+ ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
+ ctrl->bFormatIndex = data[2];
+ ctrl->bFrameIndex = data[3];
+ ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
+ ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
+ ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
+ ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
+ ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
+ ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
+ ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
+ ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);
+
+ if (size >= 34) {
+ ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
+ ctrl->bmFramingInfo = data[30];
+ ctrl->bPreferedVersion = data[31];
+ ctrl->bMinVersion = data[32];
+ ctrl->bMaxVersion = data[33];
+ } else {
+ ctrl->dwClockFrequency = stream->dev->clock_frequency;
+ ctrl->bmFramingInfo = 0;
+ ctrl->bPreferedVersion = 0;
+ ctrl->bMinVersion = 0;
+ ctrl->bMaxVersion = 0;
+ }
+
+ /*
+ * Some broken devices return null or wrong dwMaxVideoFrameSize and
+ * dwMaxPayloadTransferSize fields. Try to get the value from the
+ * format and frame descriptors.
+ */
+ uvc_fixup_video_ctrl(stream, ctrl);
+ ret = 0;
+
+out:
+ kfree(data);
+ return ret;
+}
+
+static int uvc_set_video_ctrl(struct uvc_streaming *stream,
+ struct uvc_streaming_control *ctrl, int probe)
+{
+ u16 size = uvc_video_ctrl_size(stream);
+ u8 *data;
+ int ret;
+
+ data = kzalloc(size, GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
+ data[2] = ctrl->bFormatIndex;
+ data[3] = ctrl->bFrameIndex;
+ *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
+ *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
+ *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
+ *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
+ *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
+ *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
+ put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
+ put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);
+
+ if (size >= 34) {
+ put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
+ data[30] = ctrl->bmFramingInfo;
+ data[31] = ctrl->bPreferedVersion;
+ data[32] = ctrl->bMinVersion;
+ data[33] = ctrl->bMaxVersion;
+ }
+
+ ret = __uvc_query_ctrl(stream->dev, UVC_SET_CUR, 0, stream->intfnum,
+ probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
+ size, uvc_timeout_param);
+ if (ret != size) {
+ dev_err(&stream->intf->dev,
+ "Failed to set UVC %s control : %d (exp. %u).\n",
+ probe ? "probe" : "commit", ret, size);
+ ret = -EIO;
+ }
+
+ kfree(data);
+ return ret;
+}
+
+int uvc_probe_video(struct uvc_streaming *stream,
+ struct uvc_streaming_control *probe)
+{
+ struct uvc_streaming_control probe_min, probe_max;
+ unsigned int i;
+ int ret;
+
+ /*
+ * Perform probing. The device should adjust the requested values
+ * according to its capabilities. However, some devices, namely the
+ * first generation UVC Logitech webcams, don't implement the Video
+ * Probe control properly, and just return the needed bandwidth. For
+ * that reason, if the needed bandwidth exceeds the maximum available
+ * bandwidth, try to lower the quality.
+ */
+ ret = uvc_set_video_ctrl(stream, probe, 1);
+ if (ret < 0)
+ goto done;
+
+ /* Get the minimum and maximum values for compression settings. */
+ if (!(stream->dev->quirks & UVC_QUIRK_PROBE_MINMAX)) {
+ ret = uvc_get_video_ctrl(stream, &probe_min, 1, UVC_GET_MIN);
+ if (ret < 0)
+ goto done;
+ ret = uvc_get_video_ctrl(stream, &probe_max, 1, UVC_GET_MAX);
+ if (ret < 0)
+ goto done;
+
+ probe->wCompQuality = probe_max.wCompQuality;
+ }
+
+ for (i = 0; i < 2; ++i) {
+ ret = uvc_set_video_ctrl(stream, probe, 1);
+ if (ret < 0)
+ goto done;
+ ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);
+ if (ret < 0)
+ goto done;
+
+ if (stream->intf->num_altsetting == 1)
+ break;
+
+ if (probe->dwMaxPayloadTransferSize <= stream->maxpsize)
+ break;
+
+ if (stream->dev->quirks & UVC_QUIRK_PROBE_MINMAX) {
+ ret = -ENOSPC;
+ goto done;
+ }
+
+ /* TODO: negotiate compression parameters */
+ probe->wKeyFrameRate = probe_min.wKeyFrameRate;
+ probe->wPFrameRate = probe_min.wPFrameRate;
+ probe->wCompQuality = probe_max.wCompQuality;
+ probe->wCompWindowSize = probe_min.wCompWindowSize;
+ }
+
+done:
+ return ret;
+}
+
+static int uvc_commit_video(struct uvc_streaming *stream,
+ struct uvc_streaming_control *probe)
+{
+ return uvc_set_video_ctrl(stream, probe, 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Clocks and timestamps
+ */
+
+static inline ktime_t uvc_video_get_time(void)
+{
+ if (uvc_clock_param == CLOCK_MONOTONIC)
+ return ktime_get();
+ else
+ return ktime_get_real();
+}
+
+static void
+uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
+ const u8 *data, int len)
+{
+ struct uvc_clock_sample *sample;
+ unsigned int header_size;
+ bool has_pts = false;
+ bool has_scr = false;
+ unsigned long flags;
+ ktime_t time;
+ u16 host_sof;
+ u16 dev_sof;
+
+ switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) {
+ case UVC_STREAM_PTS | UVC_STREAM_SCR:
+ header_size = 12;
+ has_pts = true;
+ has_scr = true;
+ break;
+ case UVC_STREAM_PTS:
+ header_size = 6;
+ has_pts = true;
+ break;
+ case UVC_STREAM_SCR:
+ header_size = 8;
+ has_scr = true;
+ break;
+ default:
+ header_size = 2;
+ break;
+ }
+
+ /* Check for invalid headers. */
+ if (len < header_size)
+ return;
+
+ /*
+ * Extract the timestamps:
+ *
+ * - store the frame PTS in the buffer structure
+ * - if the SCR field is present, retrieve the host SOF counter and
+ * kernel timestamps and store them with the SCR STC and SOF fields
+ * in the ring buffer
+ */
+ if (has_pts && buf != NULL)
+ buf->pts = get_unaligned_le32(&data[2]);
+
+ if (!has_scr)
+ return;
+
+ /*
+ * To limit the amount of data, drop SCRs with an SOF identical to the
+ * previous one.
+ */
+ dev_sof = get_unaligned_le16(&data[header_size - 2]);
+ if (dev_sof == stream->clock.last_sof)
+ return;
+
+ stream->clock.last_sof = dev_sof;
+
+ host_sof = usb_get_current_frame_number(stream->dev->udev);
+ time = uvc_video_get_time();
+
+ /*
+ * The UVC specification allows device implementations that can't obtain
+ * the USB frame number to keep their own frame counters as long as they
+ * match the size and frequency of the frame number associated with USB
+ * SOF tokens. The SOF values sent by such devices differ from the USB
+ * SOF tokens by a fixed offset that needs to be estimated and accounted
+ * for to make timestamp recovery as accurate as possible.
+ *
+ * The offset is estimated the first time a device SOF value is received
+ * as the difference between the host and device SOF values. As the two
+ * SOF values can differ slightly due to transmission delays, consider
+ * that the offset is null if the difference is not higher than 10 ms
+ * (negative differences can not happen and are thus considered as an
+ * offset). The video commit control wDelay field should be used to
+ * compute a dynamic threshold instead of using a fixed 10 ms value, but
+ * devices don't report reliable wDelay values.
+ *
+ * See uvc_video_clock_host_sof() for an explanation regarding why only
+ * the 8 LSBs of the delta are kept.
+ */
+ if (stream->clock.sof_offset == (u16)-1) {
+ u16 delta_sof = (host_sof - dev_sof) & 255;
+ if (delta_sof >= 10)
+ stream->clock.sof_offset = delta_sof;
+ else
+ stream->clock.sof_offset = 0;
+ }
+
+ dev_sof = (dev_sof + stream->clock.sof_offset) & 2047;
+
+ spin_lock_irqsave(&stream->clock.lock, flags);
+
+ sample = &stream->clock.samples[stream->clock.head];
+ sample->dev_stc = get_unaligned_le32(&data[header_size - 6]);
+ sample->dev_sof = dev_sof;
+ sample->host_sof = host_sof;
+ sample->host_time = time;
+
+ /* Update the sliding window head and count. */
+ stream->clock.head = (stream->clock.head + 1) % stream->clock.size;
+
+ if (stream->clock.count < stream->clock.size)
+ stream->clock.count++;
+
+ spin_unlock_irqrestore(&stream->clock.lock, flags);
+}
+
+static void uvc_video_clock_reset(struct uvc_streaming *stream)
+{
+ struct uvc_clock *clock = &stream->clock;
+
+ clock->head = 0;
+ clock->count = 0;
+ clock->last_sof = -1;
+ clock->sof_offset = -1;
+}
+
+static int uvc_video_clock_init(struct uvc_streaming *stream)
+{
+ struct uvc_clock *clock = &stream->clock;
+
+ spin_lock_init(&clock->lock);
+ clock->size = 32;
+
+ clock->samples = kmalloc_array(clock->size, sizeof(*clock->samples),
+ GFP_KERNEL);
+ if (clock->samples == NULL)
+ return -ENOMEM;
+
+ uvc_video_clock_reset(stream);
+
+ return 0;
+}
+
+static void uvc_video_clock_cleanup(struct uvc_streaming *stream)
+{
+ kfree(stream->clock.samples);
+ stream->clock.samples = NULL;
+}
+
+/*
+ * uvc_video_clock_host_sof - Return the host SOF value for a clock sample
+ *
+ * Host SOF counters reported by usb_get_current_frame_number() usually don't
+ * cover the whole 11-bits SOF range (0-2047) but are limited to the HCI frame
+ * schedule window. They can be limited to 8, 9 or 10 bits depending on the host
+ * controller and its configuration.
+ *
+ * We thus need to recover the SOF value corresponding to the host frame number.
+ * As the device and host frame numbers are sampled in a short interval, the
+ * difference between their values should be equal to a small delta plus an
+ * integer multiple of 256 caused by the host frame number limited precision.
+ *
+ * To obtain the recovered host SOF value, compute the small delta by masking
+ * the high bits of the host frame counter and device SOF difference and add it
+ * to the device SOF value.
+ */
+static u16 uvc_video_clock_host_sof(const struct uvc_clock_sample *sample)
+{
+ /* The delta value can be negative. */
+ s8 delta_sof;
+
+ delta_sof = (sample->host_sof - sample->dev_sof) & 255;
+
+ return (sample->dev_sof + delta_sof) & 2047;
+}
+
+/*
+ * uvc_video_clock_update - Update the buffer timestamp
+ *
+ * This function converts the buffer PTS timestamp to the host clock domain by
+ * going through the USB SOF clock domain and stores the result in the V4L2
+ * buffer timestamp field.
+ *
+ * The relationship between the device clock and the host clock isn't known.
+ * However, the device and the host share the common USB SOF clock which can be
+ * used to recover that relationship.
+ *
+ * The relationship between the device clock and the USB SOF clock is considered
+ * to be linear over the clock samples sliding window and is given by
+ *
+ * SOF = m * PTS + p
+ *
+ * Several methods to compute the slope (m) and intercept (p) can be used. As
+ * the clock drift should be small compared to the sliding window size, we
+ * assume that the line that goes through the points at both ends of the window
+ * is a good approximation. Naming those points P1 and P2, we get
+ *
+ * SOF = (SOF2 - SOF1) / (STC2 - STC1) * PTS
+ * + (SOF1 * STC2 - SOF2 * STC1) / (STC2 - STC1)
+ *
+ * or
+ *
+ * SOF = ((SOF2 - SOF1) * PTS + SOF1 * STC2 - SOF2 * STC1) / (STC2 - STC1) (1)
+ *
+ * to avoid losing precision in the division. Similarly, the host timestamp is
+ * computed with
+ *
+ * TS = ((TS2 - TS1) * SOF + TS1 * SOF2 - TS2 * SOF1) / (SOF2 - SOF1) (2)
+ *
+ * SOF values are coded on 11 bits by USB. We extend their precision with 16
+ * decimal bits, leading to a 11.16 coding.
+ *
+ * TODO: To avoid surprises with device clock values, PTS/STC timestamps should
+ * be normalized using the nominal device clock frequency reported through the
+ * UVC descriptors.
+ *
+ * Both the PTS/STC and SOF counters roll over, after a fixed but device
+ * specific amount of time for PTS/STC and after 2048ms for SOF. As long as the
+ * sliding window size is smaller than the rollover period, differences computed
+ * on unsigned integers will produce the correct result. However, the p term in
+ * the linear relations will be miscomputed.
+ *
+ * To fix the issue, we subtract a constant from the PTS and STC values to bring
+ * PTS to half the 32 bit STC range. The sliding window STC values then fit into
+ * the 32 bit range without any rollover.
+ *
+ * Similarly, we add 2048 to the device SOF values to make sure that the SOF
+ * computed by (1) will never be smaller than 0. This offset is then compensated
+ * by adding 2048 to the SOF values used in (2). However, this doesn't prevent
+ * rollovers between (1) and (2): the SOF value computed by (1) can be slightly
+ * lower than 4096, and the host SOF counters can have rolled over to 2048. This
+ * case is handled by subtracting 2048 from the SOF value if it exceeds the host
+ * SOF value at the end of the sliding window.
+ *
+ * Finally we subtract a constant from the host timestamps to bring the first
+ * timestamp of the sliding window to 1s.
+ */
+void uvc_video_clock_update(struct uvc_streaming *stream,
+ struct vb2_v4l2_buffer *vbuf,
+ struct uvc_buffer *buf)
+{
+ struct uvc_clock *clock = &stream->clock;
+ struct uvc_clock_sample *first;
+ struct uvc_clock_sample *last;
+ unsigned long flags;
+ u64 timestamp;
+ u32 delta_stc;
+ u32 y1, y2;
+ u32 x1, x2;
+ u32 mean;
+ u32 sof;
+ u64 y;
+
+ if (!uvc_hw_timestamps_param)
+ return;
+
+ /*
+ * We will get called from __vb2_queue_cancel() if there are buffers
+ * done but not dequeued by the user, but the sample array has already
+ * been released at that time. Just bail out in that case.
+ */
+ if (!clock->samples)
+ return;
+
+ spin_lock_irqsave(&clock->lock, flags);
+
+ if (clock->count < clock->size)
+ goto done;
+
+ first = &clock->samples[clock->head];
+ last = &clock->samples[(clock->head - 1) % clock->size];
+
+ /* First step, PTS to SOF conversion. */
+ delta_stc = buf->pts - (1UL << 31);
+ x1 = first->dev_stc - delta_stc;
+ x2 = last->dev_stc - delta_stc;
+ if (x1 == x2)
+ goto done;
+
+ y1 = (first->dev_sof + 2048) << 16;
+ y2 = (last->dev_sof + 2048) << 16;
+ if (y2 < y1)
+ y2 += 2048 << 16;
+
+ y = (u64)(y2 - y1) * (1ULL << 31) + (u64)y1 * (u64)x2
+ - (u64)y2 * (u64)x1;
+ y = div_u64(y, x2 - x1);
+
+ sof = y;
+
+ uvc_dbg(stream->dev, CLOCK,
+ "%s: PTS %u y %llu.%06llu SOF %u.%06llu (x1 %u x2 %u y1 %u y2 %u SOF offset %u)\n",
+ stream->dev->name, buf->pts,
+ y >> 16, div_u64((y & 0xffff) * 1000000, 65536),
+ sof >> 16, div_u64(((u64)sof & 0xffff) * 1000000LLU, 65536),
+ x1, x2, y1, y2, clock->sof_offset);
+
+ /* Second step, SOF to host clock conversion. */
+ x1 = (uvc_video_clock_host_sof(first) + 2048) << 16;
+ x2 = (uvc_video_clock_host_sof(last) + 2048) << 16;
+ if (x2 < x1)
+ x2 += 2048 << 16;
+ if (x1 == x2)
+ goto done;
+
+ y1 = NSEC_PER_SEC;
+ y2 = (u32)ktime_to_ns(ktime_sub(last->host_time, first->host_time)) + y1;
+
+ /*
+ * Interpolated and host SOF timestamps can wrap around at slightly
+ * different times. Handle this by adding or removing 2048 to or from
+ * the computed SOF value to keep it close to the SOF samples mean
+ * value.
+ */
+ mean = (x1 + x2) / 2;
+ if (mean - (1024 << 16) > sof)
+ sof += 2048 << 16;
+ else if (sof > mean + (1024 << 16))
+ sof -= 2048 << 16;
+
+ y = (u64)(y2 - y1) * (u64)sof + (u64)y1 * (u64)x2
+ - (u64)y2 * (u64)x1;
+ y = div_u64(y, x2 - x1);
+
+ timestamp = ktime_to_ns(first->host_time) + y - y1;
+
+ uvc_dbg(stream->dev, CLOCK,
+ "%s: SOF %u.%06llu y %llu ts %llu buf ts %llu (x1 %u/%u/%u x2 %u/%u/%u y1 %u y2 %u)\n",
+ stream->dev->name,
+ sof >> 16, div_u64(((u64)sof & 0xffff) * 1000000LLU, 65536),
+ y, timestamp, vbuf->vb2_buf.timestamp,
+ x1, first->host_sof, first->dev_sof,
+ x2, last->host_sof, last->dev_sof, y1, y2);
+
+ /* Update the V4L2 buffer. */
+ vbuf->vb2_buf.timestamp = timestamp;
+
+done:
+ spin_unlock_irqrestore(&clock->lock, flags);
+}
+
+/* ------------------------------------------------------------------------
+ * Stream statistics
+ */
+
+static void uvc_video_stats_decode(struct uvc_streaming *stream,
+ const u8 *data, int len)
+{
+ unsigned int header_size;
+ bool has_pts = false;
+ bool has_scr = false;
+ u16 scr_sof;
+ u32 scr_stc;
+ u32 pts;
+
+ if (stream->stats.stream.nb_frames == 0 &&
+ stream->stats.frame.nb_packets == 0)
+ stream->stats.stream.start_ts = ktime_get();
+
+ switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) {
+ case UVC_STREAM_PTS | UVC_STREAM_SCR:
+ header_size = 12;
+ has_pts = true;
+ has_scr = true;
+ break;
+ case UVC_STREAM_PTS:
+ header_size = 6;
+ has_pts = true;
+ break;
+ case UVC_STREAM_SCR:
+ header_size = 8;
+ has_scr = true;
+ break;
+ default:
+ header_size = 2;
+ break;
+ }
+
+ /* Check for invalid headers. */
+ if (len < header_size || data[0] < header_size) {
+ stream->stats.frame.nb_invalid++;
+ return;
+ }
+
+ /* Extract the timestamps. */
+ if (has_pts)
+ pts = get_unaligned_le32(&data[2]);
+
+ if (has_scr) {
+ scr_stc = get_unaligned_le32(&data[header_size - 6]);
+ scr_sof = get_unaligned_le16(&data[header_size - 2]);
+ }
+
+ /* Is PTS constant through the whole frame ? */
+ if (has_pts && stream->stats.frame.nb_pts) {
+ if (stream->stats.frame.pts != pts) {
+ stream->stats.frame.nb_pts_diffs++;
+ stream->stats.frame.last_pts_diff =
+ stream->stats.frame.nb_packets;
+ }
+ }
+
+ if (has_pts) {
+ stream->stats.frame.nb_pts++;
+ stream->stats.frame.pts = pts;
+ }
+
+ /*
+ * Do all frames have a PTS in their first non-empty packet, or before
+ * their first empty packet ?
+ */
+ if (stream->stats.frame.size == 0) {
+ if (len > header_size)
+ stream->stats.frame.has_initial_pts = has_pts;
+ if (len == header_size && has_pts)
+ stream->stats.frame.has_early_pts = true;
+ }
+
+ /* Do the SCR.STC and SCR.SOF fields vary through the frame ? */
+ if (has_scr && stream->stats.frame.nb_scr) {
+ if (stream->stats.frame.scr_stc != scr_stc)
+ stream->stats.frame.nb_scr_diffs++;
+ }
+
+ if (has_scr) {
+ /* Expand the SOF counter to 32 bits and store its value. */
+ if (stream->stats.stream.nb_frames > 0 ||
+ stream->stats.frame.nb_scr > 0)
+ stream->stats.stream.scr_sof_count +=
+ (scr_sof - stream->stats.stream.scr_sof) % 2048;
+ stream->stats.stream.scr_sof = scr_sof;
+
+ stream->stats.frame.nb_scr++;
+ stream->stats.frame.scr_stc = scr_stc;
+ stream->stats.frame.scr_sof = scr_sof;
+
+ if (scr_sof < stream->stats.stream.min_sof)
+ stream->stats.stream.min_sof = scr_sof;
+ if (scr_sof > stream->stats.stream.max_sof)
+ stream->stats.stream.max_sof = scr_sof;
+ }
+
+ /* Record the first non-empty packet number. */
+ if (stream->stats.frame.size == 0 && len > header_size)
+ stream->stats.frame.first_data = stream->stats.frame.nb_packets;
+
+ /* Update the frame size. */
+ stream->stats.frame.size += len - header_size;
+
+ /* Update the packets counters. */
+ stream->stats.frame.nb_packets++;
+ if (len <= header_size)
+ stream->stats.frame.nb_empty++;
+
+ if (data[1] & UVC_STREAM_ERR)
+ stream->stats.frame.nb_errors++;
+}
+
+static void uvc_video_stats_update(struct uvc_streaming *stream)
+{
+ struct uvc_stats_frame *frame = &stream->stats.frame;
+
+ uvc_dbg(stream->dev, STATS,
+ "frame %u stats: %u/%u/%u packets, %u/%u/%u pts (%searly %sinitial), %u/%u scr, last pts/stc/sof %u/%u/%u\n",
+ stream->sequence, frame->first_data,
+ frame->nb_packets - frame->nb_empty, frame->nb_packets,
+ frame->nb_pts_diffs, frame->last_pts_diff, frame->nb_pts,
+ frame->has_early_pts ? "" : "!",
+ frame->has_initial_pts ? "" : "!",
+ frame->nb_scr_diffs, frame->nb_scr,
+ frame->pts, frame->scr_stc, frame->scr_sof);
+
+ stream->stats.stream.nb_frames++;
+ stream->stats.stream.nb_packets += stream->stats.frame.nb_packets;
+ stream->stats.stream.nb_empty += stream->stats.frame.nb_empty;
+ stream->stats.stream.nb_errors += stream->stats.frame.nb_errors;
+ stream->stats.stream.nb_invalid += stream->stats.frame.nb_invalid;
+
+ if (frame->has_early_pts)
+ stream->stats.stream.nb_pts_early++;
+ if (frame->has_initial_pts)
+ stream->stats.stream.nb_pts_initial++;
+ if (frame->last_pts_diff <= frame->first_data)
+ stream->stats.stream.nb_pts_constant++;
+ if (frame->nb_scr >= frame->nb_packets - frame->nb_empty)
+ stream->stats.stream.nb_scr_count_ok++;
+ if (frame->nb_scr_diffs + 1 == frame->nb_scr)
+ stream->stats.stream.nb_scr_diffs_ok++;
+
+ memset(&stream->stats.frame, 0, sizeof(stream->stats.frame));
+}
+
+size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf,
+ size_t size)
+{
+ unsigned int scr_sof_freq;
+ unsigned int duration;
+ size_t count = 0;
+
+ /*
+ * Compute the SCR.SOF frequency estimate. At the nominal 1kHz SOF
+ * frequency this will not overflow before more than 1h.
+ */
+ duration = ktime_ms_delta(stream->stats.stream.stop_ts,
+ stream->stats.stream.start_ts);
+ if (duration != 0)
+ scr_sof_freq = stream->stats.stream.scr_sof_count * 1000
+ / duration;
+ else
+ scr_sof_freq = 0;
+
+ count += scnprintf(buf + count, size - count,
+ "frames: %u\npackets: %u\nempty: %u\n"
+ "errors: %u\ninvalid: %u\n",
+ stream->stats.stream.nb_frames,
+ stream->stats.stream.nb_packets,
+ stream->stats.stream.nb_empty,
+ stream->stats.stream.nb_errors,
+ stream->stats.stream.nb_invalid);
+ count += scnprintf(buf + count, size - count,
+ "pts: %u early, %u initial, %u ok\n",
+ stream->stats.stream.nb_pts_early,
+ stream->stats.stream.nb_pts_initial,
+ stream->stats.stream.nb_pts_constant);
+ count += scnprintf(buf + count, size - count,
+ "scr: %u count ok, %u diff ok\n",
+ stream->stats.stream.nb_scr_count_ok,
+ stream->stats.stream.nb_scr_diffs_ok);
+ count += scnprintf(buf + count, size - count,
+ "sof: %u <= sof <= %u, freq %u.%03u kHz\n",
+ stream->stats.stream.min_sof,
+ stream->stats.stream.max_sof,
+ scr_sof_freq / 1000, scr_sof_freq % 1000);
+
+ return count;
+}
+
+static void uvc_video_stats_start(struct uvc_streaming *stream)
+{
+ memset(&stream->stats, 0, sizeof(stream->stats));
+ stream->stats.stream.min_sof = 2048;
+}
+
+static void uvc_video_stats_stop(struct uvc_streaming *stream)
+{
+ stream->stats.stream.stop_ts = ktime_get();
+}
+
+/* ------------------------------------------------------------------------
+ * Video codecs
+ */
+
+/*
+ * Video payload decoding is handled by uvc_video_decode_start(),
+ * uvc_video_decode_data() and uvc_video_decode_end().
+ *
+ * uvc_video_decode_start is called with URB data at the start of a bulk or
+ * isochronous payload. It processes header data and returns the header size
+ * in bytes if successful. If an error occurs, it returns a negative error
+ * code. The following error codes have special meanings.
+ *
+ * - EAGAIN informs the caller that the current video buffer should be marked
+ * as done, and that the function should be called again with the same data
+ * and a new video buffer. This is used when end of frame conditions can be
+ * reliably detected at the beginning of the next frame only.
+ *
+ * If an error other than -EAGAIN is returned, the caller will drop the current
+ * payload. No call to uvc_video_decode_data and uvc_video_decode_end will be
+ * made until the next payload. -ENODATA can be used to drop the current
+ * payload if no other error code is appropriate.
+ *
+ * uvc_video_decode_data is called for every URB with URB data. It copies the
+ * data to the video buffer.
+ *
+ * uvc_video_decode_end is called with header data at the end of a bulk or
+ * isochronous payload. It performs any additional header data processing and
+ * returns 0 or a negative error code if an error occurred. As header data have
+ * already been processed by uvc_video_decode_start, this functions isn't
+ * required to perform sanity checks a second time.
+ *
+ * For isochronous transfers where a payload is always transferred in a single
+ * URB, the three functions will be called in a row.
+ *
+ * To let the decoder process header data and update its internal state even
+ * when no video buffer is available, uvc_video_decode_start must be prepared
+ * to be called with a NULL buf parameter. uvc_video_decode_data and
+ * uvc_video_decode_end will never be called with a NULL buffer.
+ */
+static int uvc_video_decode_start(struct uvc_streaming *stream,
+ struct uvc_buffer *buf, const u8 *data, int len)
+{
+ u8 fid;
+
+ /*
+ * Sanity checks:
+ * - packet must be at least 2 bytes long
+ * - bHeaderLength value must be at least 2 bytes (see above)
+ * - bHeaderLength value can't be larger than the packet size.
+ */
+ if (len < 2 || data[0] < 2 || data[0] > len) {
+ stream->stats.frame.nb_invalid++;
+ return -EINVAL;
+ }
+
+ fid = data[1] & UVC_STREAM_FID;
+
+ /*
+ * Increase the sequence number regardless of any buffer states, so
+ * that discontinuous sequence numbers always indicate lost frames.
+ */
+ if (stream->last_fid != fid) {
+ stream->sequence++;
+ if (stream->sequence)
+ uvc_video_stats_update(stream);
+ }
+
+ uvc_video_clock_decode(stream, buf, data, len);
+ uvc_video_stats_decode(stream, data, len);
+
+ /*
+ * Store the payload FID bit and return immediately when the buffer is
+ * NULL.
+ */
+ if (buf == NULL) {
+ stream->last_fid = fid;
+ return -ENODATA;
+ }
+
+ /* Mark the buffer as bad if the error bit is set. */
+ if (data[1] & UVC_STREAM_ERR) {
+ uvc_dbg(stream->dev, FRAME,
+ "Marking buffer as bad (error bit set)\n");
+ buf->error = 1;
+ }
+
+ /*
+ * Synchronize to the input stream by waiting for the FID bit to be
+ * toggled when the buffer state is not UVC_BUF_STATE_ACTIVE.
+ * stream->last_fid is initialized to -1, so the first isochronous
+ * frame will always be in sync.
+ *
+ * If the device doesn't toggle the FID bit, invert stream->last_fid
+ * when the EOF bit is set to force synchronisation on the next packet.
+ */
+ if (buf->state != UVC_BUF_STATE_ACTIVE) {
+ if (fid == stream->last_fid) {
+ uvc_dbg(stream->dev, FRAME,
+ "Dropping payload (out of sync)\n");
+ if ((stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID) &&
+ (data[1] & UVC_STREAM_EOF))
+ stream->last_fid ^= UVC_STREAM_FID;
+ return -ENODATA;
+ }
+
+ buf->buf.field = V4L2_FIELD_NONE;
+ buf->buf.sequence = stream->sequence;
+ buf->buf.vb2_buf.timestamp = ktime_to_ns(uvc_video_get_time());
+
+ /* TODO: Handle PTS and SCR. */
+ buf->state = UVC_BUF_STATE_ACTIVE;
+ }
+
+ /*
+ * Mark the buffer as done if we're at the beginning of a new frame.
+ * End of frame detection is better implemented by checking the EOF
+ * bit (FID bit toggling is delayed by one frame compared to the EOF
+ * bit), but some devices don't set the bit at end of frame (and the
+ * last payload can be lost anyway). We thus must check if the FID has
+ * been toggled.
+ *
+ * stream->last_fid is initialized to -1, so the first isochronous
+ * frame will never trigger an end of frame detection.
+ *
+ * Empty buffers (bytesused == 0) don't trigger end of frame detection
+ * as it doesn't make sense to return an empty buffer. This also
+ * avoids detecting end of frame conditions at FID toggling if the
+ * previous payload had the EOF bit set.
+ */
+ if (fid != stream->last_fid && buf->bytesused != 0) {
+ uvc_dbg(stream->dev, FRAME,
+ "Frame complete (FID bit toggled)\n");
+ buf->state = UVC_BUF_STATE_READY;
+ return -EAGAIN;
+ }
+
+ stream->last_fid = fid;
+
+ return data[0];
+}
+
+static inline enum dma_data_direction uvc_stream_dir(
+ struct uvc_streaming *stream)
+{
+ if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return DMA_FROM_DEVICE;
+ else
+ return DMA_TO_DEVICE;
+}
+
+static inline struct device *uvc_stream_to_dmadev(struct uvc_streaming *stream)
+{
+ return bus_to_hcd(stream->dev->udev->bus)->self.sysdev;
+}
+
+static int uvc_submit_urb(struct uvc_urb *uvc_urb, gfp_t mem_flags)
+{
+ /* Sync DMA. */
+ dma_sync_sgtable_for_device(uvc_stream_to_dmadev(uvc_urb->stream),
+ uvc_urb->sgt,
+ uvc_stream_dir(uvc_urb->stream));
+ return usb_submit_urb(uvc_urb->urb, mem_flags);
+}
+
+/*
+ * uvc_video_decode_data_work: Asynchronous memcpy processing
+ *
+ * Copy URB data to video buffers in process context, releasing buffer
+ * references and requeuing the URB when done.
+ */
+static void uvc_video_copy_data_work(struct work_struct *work)
+{
+ struct uvc_urb *uvc_urb = container_of(work, struct uvc_urb, work);
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < uvc_urb->async_operations; i++) {
+ struct uvc_copy_op *op = &uvc_urb->copy_operations[i];
+
+ memcpy(op->dst, op->src, op->len);
+
+ /* Release reference taken on this buffer. */
+ uvc_queue_buffer_release(op->buf);
+ }
+
+ ret = uvc_submit_urb(uvc_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&uvc_urb->stream->intf->dev,
+ "Failed to resubmit video URB (%d).\n", ret);
+}
+
+static void uvc_video_decode_data(struct uvc_urb *uvc_urb,
+ struct uvc_buffer *buf, const u8 *data, int len)
+{
+ unsigned int active_op = uvc_urb->async_operations;
+ struct uvc_copy_op *op = &uvc_urb->copy_operations[active_op];
+ unsigned int maxlen;
+
+ if (len <= 0)
+ return;
+
+ maxlen = buf->length - buf->bytesused;
+
+ /* Take a buffer reference for async work. */
+ kref_get(&buf->ref);
+
+ op->buf = buf;
+ op->src = data;
+ op->dst = buf->mem + buf->bytesused;
+ op->len = min_t(unsigned int, len, maxlen);
+
+ buf->bytesused += op->len;
+
+ /* Complete the current frame if the buffer size was exceeded. */
+ if (len > maxlen) {
+ uvc_dbg(uvc_urb->stream->dev, FRAME,
+ "Frame complete (overflow)\n");
+ buf->error = 1;
+ buf->state = UVC_BUF_STATE_READY;
+ }
+
+ uvc_urb->async_operations++;
+}
+
+static void uvc_video_decode_end(struct uvc_streaming *stream,
+ struct uvc_buffer *buf, const u8 *data, int len)
+{
+ /* Mark the buffer as done if the EOF marker is set. */
+ if (data[1] & UVC_STREAM_EOF && buf->bytesused != 0) {
+ uvc_dbg(stream->dev, FRAME, "Frame complete (EOF found)\n");
+ if (data[0] == len)
+ uvc_dbg(stream->dev, FRAME, "EOF in empty payload\n");
+ buf->state = UVC_BUF_STATE_READY;
+ if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID)
+ stream->last_fid ^= UVC_STREAM_FID;
+ }
+}
+
+/*
+ * Video payload encoding is handled by uvc_video_encode_header() and
+ * uvc_video_encode_data(). Only bulk transfers are currently supported.
+ *
+ * uvc_video_encode_header is called at the start of a payload. It adds header
+ * data to the transfer buffer and returns the header size. As the only known
+ * UVC output device transfers a whole frame in a single payload, the EOF bit
+ * is always set in the header.
+ *
+ * uvc_video_encode_data is called for every URB and copies the data from the
+ * video buffer to the transfer buffer.
+ */
+static int uvc_video_encode_header(struct uvc_streaming *stream,
+ struct uvc_buffer *buf, u8 *data, int len)
+{
+ data[0] = 2; /* Header length */
+ data[1] = UVC_STREAM_EOH | UVC_STREAM_EOF
+ | (stream->last_fid & UVC_STREAM_FID);
+ return 2;
+}
+
+static int uvc_video_encode_data(struct uvc_streaming *stream,
+ struct uvc_buffer *buf, u8 *data, int len)
+{
+ struct uvc_video_queue *queue = &stream->queue;
+ unsigned int nbytes;
+ void *mem;
+
+ /* Copy video data to the URB buffer. */
+ mem = buf->mem + queue->buf_used;
+ nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used);
+ nbytes = min(stream->bulk.max_payload_size - stream->bulk.payload_size,
+ nbytes);
+ memcpy(data, mem, nbytes);
+
+ queue->buf_used += nbytes;
+
+ return nbytes;
+}
+
+/* ------------------------------------------------------------------------
+ * Metadata
+ */
+
+/*
+ * Additionally to the payload headers we also want to provide the user with USB
+ * Frame Numbers and system time values. The resulting buffer is thus composed
+ * of blocks, containing a 64-bit timestamp in nanoseconds, a 16-bit USB Frame
+ * Number, and a copy of the payload header.
+ *
+ * Ideally we want to capture all payload headers for each frame. However, their
+ * number is unknown and unbound. We thus drop headers that contain no vendor
+ * data and that either contain no SCR value or an SCR value identical to the
+ * previous header.
+ */
+static void uvc_video_decode_meta(struct uvc_streaming *stream,
+ struct uvc_buffer *meta_buf,
+ const u8 *mem, unsigned int length)
+{
+ struct uvc_meta_buf *meta;
+ size_t len_std = 2;
+ bool has_pts, has_scr;
+ unsigned long flags;
+ unsigned int sof;
+ ktime_t time;
+ const u8 *scr;
+
+ if (!meta_buf || length == 2)
+ return;
+
+ if (meta_buf->length - meta_buf->bytesused <
+ length + sizeof(meta->ns) + sizeof(meta->sof)) {
+ meta_buf->error = 1;
+ return;
+ }
+
+ has_pts = mem[1] & UVC_STREAM_PTS;
+ has_scr = mem[1] & UVC_STREAM_SCR;
+
+ if (has_pts) {
+ len_std += 4;
+ scr = mem + 6;
+ } else {
+ scr = mem + 2;
+ }
+
+ if (has_scr)
+ len_std += 6;
+
+ if (stream->meta.format == V4L2_META_FMT_UVC)
+ length = len_std;
+
+ if (length == len_std && (!has_scr ||
+ !memcmp(scr, stream->clock.last_scr, 6)))
+ return;
+
+ meta = (struct uvc_meta_buf *)((u8 *)meta_buf->mem + meta_buf->bytesused);
+ local_irq_save(flags);
+ time = uvc_video_get_time();
+ sof = usb_get_current_frame_number(stream->dev->udev);
+ local_irq_restore(flags);
+ put_unaligned(ktime_to_ns(time), &meta->ns);
+ put_unaligned(sof, &meta->sof);
+
+ if (has_scr)
+ memcpy(stream->clock.last_scr, scr, 6);
+
+ meta->length = mem[0];
+ meta->flags = mem[1];
+ memcpy(meta->buf, &mem[2], length - 2);
+ meta_buf->bytesused += length + sizeof(meta->ns) + sizeof(meta->sof);
+
+ uvc_dbg(stream->dev, FRAME,
+ "%s(): t-sys %lluns, SOF %u, len %u, flags 0x%x, PTS %u, STC %u frame SOF %u\n",
+ __func__, ktime_to_ns(time), meta->sof, meta->length,
+ meta->flags,
+ has_pts ? *(u32 *)meta->buf : 0,
+ has_scr ? *(u32 *)scr : 0,
+ has_scr ? *(u32 *)(scr + 4) & 0x7ff : 0);
+}
+
+/* ------------------------------------------------------------------------
+ * URB handling
+ */
+
+/*
+ * Set error flag for incomplete buffer.
+ */
+static void uvc_video_validate_buffer(const struct uvc_streaming *stream,
+ struct uvc_buffer *buf)
+{
+ if (stream->ctrl.dwMaxVideoFrameSize != buf->bytesused &&
+ !(stream->cur_format->flags & UVC_FMT_FLAG_COMPRESSED))
+ buf->error = 1;
+}
+
+/*
+ * Completion handler for video URBs.
+ */
+
+static void uvc_video_next_buffers(struct uvc_streaming *stream,
+ struct uvc_buffer **video_buf, struct uvc_buffer **meta_buf)
+{
+ uvc_video_validate_buffer(stream, *video_buf);
+
+ if (*meta_buf) {
+ struct vb2_v4l2_buffer *vb2_meta = &(*meta_buf)->buf;
+ const struct vb2_v4l2_buffer *vb2_video = &(*video_buf)->buf;
+
+ vb2_meta->sequence = vb2_video->sequence;
+ vb2_meta->field = vb2_video->field;
+ vb2_meta->vb2_buf.timestamp = vb2_video->vb2_buf.timestamp;
+
+ (*meta_buf)->state = UVC_BUF_STATE_READY;
+ if (!(*meta_buf)->error)
+ (*meta_buf)->error = (*video_buf)->error;
+ *meta_buf = uvc_queue_next_buffer(&stream->meta.queue,
+ *meta_buf);
+ }
+ *video_buf = uvc_queue_next_buffer(&stream->queue, *video_buf);
+}
+
+static void uvc_video_decode_isoc(struct uvc_urb *uvc_urb,
+ struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+ struct urb *urb = uvc_urb->urb;
+ struct uvc_streaming *stream = uvc_urb->stream;
+ u8 *mem;
+ int ret, i;
+
+ for (i = 0; i < urb->number_of_packets; ++i) {
+ if (urb->iso_frame_desc[i].status < 0) {
+ uvc_dbg(stream->dev, FRAME,
+ "USB isochronous frame lost (%d)\n",
+ urb->iso_frame_desc[i].status);
+ /* Mark the buffer as faulty. */
+ if (buf != NULL)
+ buf->error = 1;
+ continue;
+ }
+
+ /* Decode the payload header. */
+ mem = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+ do {
+ ret = uvc_video_decode_start(stream, buf, mem,
+ urb->iso_frame_desc[i].actual_length);
+ if (ret == -EAGAIN)
+ uvc_video_next_buffers(stream, &buf, &meta_buf);
+ } while (ret == -EAGAIN);
+
+ if (ret < 0)
+ continue;
+
+ uvc_video_decode_meta(stream, meta_buf, mem, ret);
+
+ /* Decode the payload data. */
+ uvc_video_decode_data(uvc_urb, buf, mem + ret,
+ urb->iso_frame_desc[i].actual_length - ret);
+
+ /* Process the header again. */
+ uvc_video_decode_end(stream, buf, mem,
+ urb->iso_frame_desc[i].actual_length);
+
+ if (buf->state == UVC_BUF_STATE_READY)
+ uvc_video_next_buffers(stream, &buf, &meta_buf);
+ }
+}
+
+static void uvc_video_decode_bulk(struct uvc_urb *uvc_urb,
+ struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+ struct urb *urb = uvc_urb->urb;
+ struct uvc_streaming *stream = uvc_urb->stream;
+ u8 *mem;
+ int len, ret;
+
+ /*
+ * Ignore ZLPs if they're not part of a frame, otherwise process them
+ * to trigger the end of payload detection.
+ */
+ if (urb->actual_length == 0 && stream->bulk.header_size == 0)
+ return;
+
+ mem = urb->transfer_buffer;
+ len = urb->actual_length;
+ stream->bulk.payload_size += len;
+
+ /*
+ * If the URB is the first of its payload, decode and save the
+ * header.
+ */
+ if (stream->bulk.header_size == 0 && !stream->bulk.skip_payload) {
+ do {
+ ret = uvc_video_decode_start(stream, buf, mem, len);
+ if (ret == -EAGAIN)
+ uvc_video_next_buffers(stream, &buf, &meta_buf);
+ } while (ret == -EAGAIN);
+
+ /* If an error occurred skip the rest of the payload. */
+ if (ret < 0 || buf == NULL) {
+ stream->bulk.skip_payload = 1;
+ } else {
+ memcpy(stream->bulk.header, mem, ret);
+ stream->bulk.header_size = ret;
+
+ uvc_video_decode_meta(stream, meta_buf, mem, ret);
+
+ mem += ret;
+ len -= ret;
+ }
+ }
+
+ /*
+ * The buffer queue might have been cancelled while a bulk transfer
+ * was in progress, so we can reach here with buf equal to NULL. Make
+ * sure buf is never dereferenced if NULL.
+ */
+
+ /* Prepare video data for processing. */
+ if (!stream->bulk.skip_payload && buf != NULL)
+ uvc_video_decode_data(uvc_urb, buf, mem, len);
+
+ /*
+ * Detect the payload end by a URB smaller than the maximum size (or
+ * a payload size equal to the maximum) and process the header again.
+ */
+ if (urb->actual_length < urb->transfer_buffer_length ||
+ stream->bulk.payload_size >= stream->bulk.max_payload_size) {
+ if (!stream->bulk.skip_payload && buf != NULL) {
+ uvc_video_decode_end(stream, buf, stream->bulk.header,
+ stream->bulk.payload_size);
+ if (buf->state == UVC_BUF_STATE_READY)
+ uvc_video_next_buffers(stream, &buf, &meta_buf);
+ }
+
+ stream->bulk.header_size = 0;
+ stream->bulk.skip_payload = 0;
+ stream->bulk.payload_size = 0;
+ }
+}
+
+static void uvc_video_encode_bulk(struct uvc_urb *uvc_urb,
+ struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+ struct urb *urb = uvc_urb->urb;
+ struct uvc_streaming *stream = uvc_urb->stream;
+
+ u8 *mem = urb->transfer_buffer;
+ int len = stream->urb_size, ret;
+
+ if (buf == NULL) {
+ urb->transfer_buffer_length = 0;
+ return;
+ }
+
+ /* If the URB is the first of its payload, add the header. */
+ if (stream->bulk.header_size == 0) {
+ ret = uvc_video_encode_header(stream, buf, mem, len);
+ stream->bulk.header_size = ret;
+ stream->bulk.payload_size += ret;
+ mem += ret;
+ len -= ret;
+ }
+
+ /* Process video data. */
+ ret = uvc_video_encode_data(stream, buf, mem, len);
+
+ stream->bulk.payload_size += ret;
+ len -= ret;
+
+ if (buf->bytesused == stream->queue.buf_used ||
+ stream->bulk.payload_size == stream->bulk.max_payload_size) {
+ if (buf->bytesused == stream->queue.buf_used) {
+ stream->queue.buf_used = 0;
+ buf->state = UVC_BUF_STATE_READY;
+ buf->buf.sequence = ++stream->sequence;
+ uvc_queue_next_buffer(&stream->queue, buf);
+ stream->last_fid ^= UVC_STREAM_FID;
+ }
+
+ stream->bulk.header_size = 0;
+ stream->bulk.payload_size = 0;
+ }
+
+ urb->transfer_buffer_length = stream->urb_size - len;
+}
+
+static void uvc_video_complete(struct urb *urb)
+{
+ struct uvc_urb *uvc_urb = urb->context;
+ struct uvc_streaming *stream = uvc_urb->stream;
+ struct uvc_video_queue *queue = &stream->queue;
+ struct uvc_video_queue *qmeta = &stream->meta.queue;
+ struct vb2_queue *vb2_qmeta = stream->meta.vdev.queue;
+ struct uvc_buffer *buf = NULL;
+ struct uvc_buffer *buf_meta = NULL;
+ unsigned long flags;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ break;
+
+ default:
+ dev_warn(&stream->intf->dev,
+ "Non-zero status (%d) in video completion handler.\n",
+ urb->status);
+ fallthrough;
+ case -ENOENT: /* usb_poison_urb() called. */
+ if (stream->frozen)
+ return;
+ fallthrough;
+ case -ECONNRESET: /* usb_unlink_urb() called. */
+ case -ESHUTDOWN: /* The endpoint is being disabled. */
+ uvc_queue_cancel(queue, urb->status == -ESHUTDOWN);
+ if (vb2_qmeta)
+ uvc_queue_cancel(qmeta, urb->status == -ESHUTDOWN);
+ return;
+ }
+
+ buf = uvc_queue_get_current_buffer(queue);
+
+ if (vb2_qmeta) {
+ spin_lock_irqsave(&qmeta->irqlock, flags);
+ if (!list_empty(&qmeta->irqqueue))
+ buf_meta = list_first_entry(&qmeta->irqqueue,
+ struct uvc_buffer, queue);
+ spin_unlock_irqrestore(&qmeta->irqlock, flags);
+ }
+
+ /* Re-initialise the URB async work. */
+ uvc_urb->async_operations = 0;
+
+ /* Sync DMA and invalidate vmap range. */
+ dma_sync_sgtable_for_cpu(uvc_stream_to_dmadev(uvc_urb->stream),
+ uvc_urb->sgt, uvc_stream_dir(stream));
+ invalidate_kernel_vmap_range(uvc_urb->buffer,
+ uvc_urb->stream->urb_size);
+
+ /*
+ * Process the URB headers, and optionally queue expensive memcpy tasks
+ * to be deferred to a work queue.
+ */
+ stream->decode(uvc_urb, buf, buf_meta);
+
+ /* If no async work is needed, resubmit the URB immediately. */
+ if (!uvc_urb->async_operations) {
+ ret = uvc_submit_urb(uvc_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&stream->intf->dev,
+ "Failed to resubmit video URB (%d).\n", ret);
+ return;
+ }
+
+ queue_work(stream->async_wq, &uvc_urb->work);
+}
+
+/*
+ * Free transfer buffers.
+ */
+static void uvc_free_urb_buffers(struct uvc_streaming *stream)
+{
+ struct device *dma_dev = uvc_stream_to_dmadev(stream);
+ struct uvc_urb *uvc_urb;
+
+ for_each_uvc_urb(uvc_urb, stream) {
+ if (!uvc_urb->buffer)
+ continue;
+
+ dma_vunmap_noncontiguous(dma_dev, uvc_urb->buffer);
+ dma_free_noncontiguous(dma_dev, stream->urb_size, uvc_urb->sgt,
+ uvc_stream_dir(stream));
+
+ uvc_urb->buffer = NULL;
+ uvc_urb->sgt = NULL;
+ }
+
+ stream->urb_size = 0;
+}
+
+static bool uvc_alloc_urb_buffer(struct uvc_streaming *stream,
+ struct uvc_urb *uvc_urb, gfp_t gfp_flags)
+{
+ struct device *dma_dev = uvc_stream_to_dmadev(stream);
+
+ uvc_urb->sgt = dma_alloc_noncontiguous(dma_dev, stream->urb_size,
+ uvc_stream_dir(stream),
+ gfp_flags, 0);
+ if (!uvc_urb->sgt)
+ return false;
+ uvc_urb->dma = uvc_urb->sgt->sgl->dma_address;
+
+ uvc_urb->buffer = dma_vmap_noncontiguous(dma_dev, stream->urb_size,
+ uvc_urb->sgt);
+ if (!uvc_urb->buffer) {
+ dma_free_noncontiguous(dma_dev, stream->urb_size,
+ uvc_urb->sgt,
+ uvc_stream_dir(stream));
+ uvc_urb->sgt = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Allocate transfer buffers. This function can be called with buffers
+ * already allocated when resuming from suspend, in which case it will
+ * return without touching the buffers.
+ *
+ * Limit the buffer size to UVC_MAX_PACKETS bulk/isochronous packets. If the
+ * system is too low on memory try successively smaller numbers of packets
+ * until allocation succeeds.
+ *
+ * Return the number of allocated packets on success or 0 when out of memory.
+ */
+static int uvc_alloc_urb_buffers(struct uvc_streaming *stream,
+ unsigned int size, unsigned int psize, gfp_t gfp_flags)
+{
+ unsigned int npackets;
+ unsigned int i;
+
+ /* Buffers are already allocated, bail out. */
+ if (stream->urb_size)
+ return stream->urb_size / psize;
+
+ /*
+ * Compute the number of packets. Bulk endpoints might transfer UVC
+ * payloads across multiple URBs.
+ */
+ npackets = DIV_ROUND_UP(size, psize);
+ if (npackets > UVC_MAX_PACKETS)
+ npackets = UVC_MAX_PACKETS;
+
+ /* Retry allocations until one succeed. */
+ for (; npackets > 1; npackets /= 2) {
+ stream->urb_size = psize * npackets;
+
+ for (i = 0; i < UVC_URBS; ++i) {
+ struct uvc_urb *uvc_urb = &stream->uvc_urb[i];
+
+ if (!uvc_alloc_urb_buffer(stream, uvc_urb, gfp_flags)) {
+ uvc_free_urb_buffers(stream);
+ break;
+ }
+
+ uvc_urb->stream = stream;
+ }
+
+ if (i == UVC_URBS) {
+ uvc_dbg(stream->dev, VIDEO,
+ "Allocated %u URB buffers of %ux%u bytes each\n",
+ UVC_URBS, npackets, psize);
+ return npackets;
+ }
+ }
+
+ uvc_dbg(stream->dev, VIDEO,
+ "Failed to allocate URB buffers (%u bytes per packet)\n",
+ psize);
+ return 0;
+}
+
+/*
+ * Uninitialize isochronous/bulk URBs and free transfer buffers.
+ */
+static void uvc_video_stop_transfer(struct uvc_streaming *stream,
+ int free_buffers)
+{
+ struct uvc_urb *uvc_urb;
+
+ uvc_video_stats_stop(stream);
+
+ /*
+ * We must poison the URBs rather than kill them to ensure that even
+ * after the completion handler returns, any asynchronous workqueues
+ * will be prevented from resubmitting the URBs.
+ */
+ for_each_uvc_urb(uvc_urb, stream)
+ usb_poison_urb(uvc_urb->urb);
+
+ flush_workqueue(stream->async_wq);
+
+ for_each_uvc_urb(uvc_urb, stream) {
+ usb_free_urb(uvc_urb->urb);
+ uvc_urb->urb = NULL;
+ }
+
+ if (free_buffers)
+ uvc_free_urb_buffers(stream);
+}
+
+/*
+ * Compute the maximum number of bytes per interval for an endpoint.
+ */
+u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep)
+{
+ u16 psize;
+
+ switch (dev->speed) {
+ case USB_SPEED_SUPER:
+ case USB_SPEED_SUPER_PLUS:
+ return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
+ default:
+ psize = usb_endpoint_maxp(&ep->desc);
+ psize *= usb_endpoint_maxp_mult(&ep->desc);
+ return psize;
+ }
+}
+
+/*
+ * Initialize isochronous URBs and allocate transfer buffers. The packet size
+ * is given by the endpoint.
+ */
+static int uvc_init_video_isoc(struct uvc_streaming *stream,
+ struct usb_host_endpoint *ep, gfp_t gfp_flags)
+{
+ struct urb *urb;
+ struct uvc_urb *uvc_urb;
+ unsigned int npackets, i;
+ u16 psize;
+ u32 size;
+
+ psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
+ size = stream->ctrl.dwMaxVideoFrameSize;
+
+ npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
+ if (npackets == 0)
+ return -ENOMEM;
+
+ size = npackets * psize;
+
+ for_each_uvc_urb(uvc_urb, stream) {
+ urb = usb_alloc_urb(npackets, gfp_flags);
+ if (urb == NULL) {
+ uvc_video_stop_transfer(stream, 1);
+ return -ENOMEM;
+ }
+
+ urb->dev = stream->dev->udev;
+ urb->context = uvc_urb;
+ urb->pipe = usb_rcvisocpipe(stream->dev->udev,
+ ep->desc.bEndpointAddress);
+ urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+ urb->transfer_dma = uvc_urb->dma;
+ urb->interval = ep->desc.bInterval;
+ urb->transfer_buffer = uvc_urb->buffer;
+ urb->complete = uvc_video_complete;
+ urb->number_of_packets = npackets;
+ urb->transfer_buffer_length = size;
+
+ for (i = 0; i < npackets; ++i) {
+ urb->iso_frame_desc[i].offset = i * psize;
+ urb->iso_frame_desc[i].length = psize;
+ }
+
+ uvc_urb->urb = urb;
+ }
+
+ return 0;
+}
+
+/*
+ * Initialize bulk URBs and allocate transfer buffers. The packet size is
+ * given by the endpoint.
+ */
+static int uvc_init_video_bulk(struct uvc_streaming *stream,
+ struct usb_host_endpoint *ep, gfp_t gfp_flags)
+{
+ struct urb *urb;
+ struct uvc_urb *uvc_urb;
+ unsigned int npackets, pipe;
+ u16 psize;
+ u32 size;
+
+ psize = usb_endpoint_maxp(&ep->desc);
+ size = stream->ctrl.dwMaxPayloadTransferSize;
+ stream->bulk.max_payload_size = size;
+
+ npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
+ if (npackets == 0)
+ return -ENOMEM;
+
+ size = npackets * psize;
+
+ if (usb_endpoint_dir_in(&ep->desc))
+ pipe = usb_rcvbulkpipe(stream->dev->udev,
+ ep->desc.bEndpointAddress);
+ else
+ pipe = usb_sndbulkpipe(stream->dev->udev,
+ ep->desc.bEndpointAddress);
+
+ if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ size = 0;
+
+ for_each_uvc_urb(uvc_urb, stream) {
+ urb = usb_alloc_urb(0, gfp_flags);
+ if (urb == NULL) {
+ uvc_video_stop_transfer(stream, 1);
+ return -ENOMEM;
+ }
+
+ usb_fill_bulk_urb(urb, stream->dev->udev, pipe, uvc_urb->buffer,
+ size, uvc_video_complete, uvc_urb);
+ urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ urb->transfer_dma = uvc_urb->dma;
+
+ uvc_urb->urb = urb;
+ }
+
+ return 0;
+}
+
+/*
+ * Initialize isochronous/bulk URBs and allocate transfer buffers.
+ */
+static int uvc_video_start_transfer(struct uvc_streaming *stream,
+ gfp_t gfp_flags)
+{
+ struct usb_interface *intf = stream->intf;
+ struct usb_host_endpoint *ep;
+ struct uvc_urb *uvc_urb;
+ unsigned int i;
+ int ret;
+
+ stream->sequence = -1;
+ stream->last_fid = -1;
+ stream->bulk.header_size = 0;
+ stream->bulk.skip_payload = 0;
+ stream->bulk.payload_size = 0;
+
+ uvc_video_stats_start(stream);
+
+ if (intf->num_altsetting > 1) {
+ struct usb_host_endpoint *best_ep = NULL;
+ unsigned int best_psize = UINT_MAX;
+ unsigned int bandwidth;
+ unsigned int altsetting;
+ int intfnum = stream->intfnum;
+
+ /* Isochronous endpoint, select the alternate setting. */
+ bandwidth = stream->ctrl.dwMaxPayloadTransferSize;
+
+ if (bandwidth == 0) {
+ uvc_dbg(stream->dev, VIDEO,
+ "Device requested null bandwidth, defaulting to lowest\n");
+ bandwidth = 1;
+ } else {
+ uvc_dbg(stream->dev, VIDEO,
+ "Device requested %u B/frame bandwidth\n",
+ bandwidth);
+ }
+
+ for (i = 0; i < intf->num_altsetting; ++i) {
+ struct usb_host_interface *alts;
+ unsigned int psize;
+
+ alts = &intf->altsetting[i];
+ ep = uvc_find_endpoint(alts,
+ stream->header.bEndpointAddress);
+ if (ep == NULL)
+ continue;
+
+ /* Check if the bandwidth is high enough. */
+ psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
+ if (psize >= bandwidth && psize <= best_psize) {
+ altsetting = alts->desc.bAlternateSetting;
+ best_psize = psize;
+ best_ep = ep;
+ }
+ }
+
+ if (best_ep == NULL) {
+ uvc_dbg(stream->dev, VIDEO,
+ "No fast enough alt setting for requested bandwidth\n");
+ return -EIO;
+ }
+
+ uvc_dbg(stream->dev, VIDEO,
+ "Selecting alternate setting %u (%u B/frame bandwidth)\n",
+ altsetting, best_psize);
+
+ /*
+ * Some devices, namely the Logitech C910 and B910, are unable
+ * to recover from a USB autosuspend, unless the alternate
+ * setting of the streaming interface is toggled.
+ */
+ if (stream->dev->quirks & UVC_QUIRK_WAKE_AUTOSUSPEND) {
+ usb_set_interface(stream->dev->udev, intfnum,
+ altsetting);
+ usb_set_interface(stream->dev->udev, intfnum, 0);
+ }
+
+ ret = usb_set_interface(stream->dev->udev, intfnum, altsetting);
+ if (ret < 0)
+ return ret;
+
+ ret = uvc_init_video_isoc(stream, best_ep, gfp_flags);
+ } else {
+ /* Bulk endpoint, proceed to URB initialization. */
+ ep = uvc_find_endpoint(&intf->altsetting[0],
+ stream->header.bEndpointAddress);
+ if (ep == NULL)
+ return -EIO;
+
+ /* Reject broken descriptors. */
+ if (usb_endpoint_maxp(&ep->desc) == 0)
+ return -EIO;
+
+ ret = uvc_init_video_bulk(stream, ep, gfp_flags);
+ }
+
+ if (ret < 0)
+ return ret;
+
+ /* Submit the URBs. */
+ for_each_uvc_urb(uvc_urb, stream) {
+ ret = uvc_submit_urb(uvc_urb, gfp_flags);
+ if (ret < 0) {
+ dev_err(&stream->intf->dev,
+ "Failed to submit URB %u (%d).\n",
+ uvc_urb_index(uvc_urb), ret);
+ uvc_video_stop_transfer(stream, 1);
+ return ret;
+ }
+ }
+
+ /*
+ * The Logitech C920 temporarily forgets that it should not be adjusting
+ * Exposure Absolute during init so restore controls to stored values.
+ */
+ if (stream->dev->quirks & UVC_QUIRK_RESTORE_CTRLS_ON_INIT)
+ uvc_ctrl_restore_values(stream->dev);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+/*
+ * Stop streaming without disabling the video queue.
+ *
+ * To let userspace applications resume without trouble, we must not touch the
+ * video buffers in any way. We mark the device as frozen to make sure the URB
+ * completion handler won't try to cancel the queue when we kill the URBs.
+ */
+int uvc_video_suspend(struct uvc_streaming *stream)
+{
+ if (!uvc_queue_streaming(&stream->queue))
+ return 0;
+
+ stream->frozen = 1;
+ uvc_video_stop_transfer(stream, 0);
+ usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+ return 0;
+}
+
+/*
+ * Reconfigure the video interface and restart streaming if it was enabled
+ * before suspend.
+ *
+ * If an error occurs, disable the video queue. This will wake all pending
+ * buffers, making sure userspace applications are notified of the problem
+ * instead of waiting forever.
+ */
+int uvc_video_resume(struct uvc_streaming *stream, int reset)
+{
+ int ret;
+
+ /*
+ * If the bus has been reset on resume, set the alternate setting to 0.
+ * This should be the default value, but some devices crash or otherwise
+ * misbehave if they don't receive a SET_INTERFACE request before any
+ * other video control request.
+ */
+ if (reset)
+ usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+
+ stream->frozen = 0;
+
+ uvc_video_clock_reset(stream);
+
+ if (!uvc_queue_streaming(&stream->queue))
+ return 0;
+
+ ret = uvc_commit_video(stream, &stream->ctrl);
+ if (ret < 0)
+ return ret;
+
+ return uvc_video_start_transfer(stream, GFP_NOIO);
+}
+
+/* ------------------------------------------------------------------------
+ * Video device
+ */
+
+/*
+ * Initialize the UVC video device by switching to alternate setting 0 and
+ * retrieve the default format.
+ *
+ * Some cameras (namely the Fuji Finepix) set the format and frame
+ * indexes to zero. The UVC standard doesn't clearly make this a spec
+ * violation, so try to silently fix the values if possible.
+ *
+ * This function is called before registering the device with V4L.
+ */
+int uvc_video_init(struct uvc_streaming *stream)
+{
+ struct uvc_streaming_control *probe = &stream->ctrl;
+ struct uvc_format *format = NULL;
+ struct uvc_frame *frame = NULL;
+ struct uvc_urb *uvc_urb;
+ unsigned int i;
+ int ret;
+
+ if (stream->nformats == 0) {
+ dev_info(&stream->intf->dev,
+ "No supported video formats found.\n");
+ return -EINVAL;
+ }
+
+ atomic_set(&stream->active, 0);
+
+ /*
+ * Alternate setting 0 should be the default, yet the XBox Live Vision
+ * Cam (and possibly other devices) crash or otherwise misbehave if
+ * they don't receive a SET_INTERFACE request before any other video
+ * control request.
+ */
+ usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+
+ /*
+ * Set the streaming probe control with default streaming parameters
+ * retrieved from the device. Webcams that don't support GET_DEF
+ * requests on the probe control will just keep their current streaming
+ * parameters.
+ */
+ if (uvc_get_video_ctrl(stream, probe, 1, UVC_GET_DEF) == 0)
+ uvc_set_video_ctrl(stream, probe, 1);
+
+ /*
+ * Initialize the streaming parameters with the probe control current
+ * value. This makes sure SET_CUR requests on the streaming commit
+ * control will always use values retrieved from a successful GET_CUR
+ * request on the probe control, as required by the UVC specification.
+ */
+ ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Check if the default format descriptor exists. Use the first
+ * available format otherwise.
+ */
+ for (i = stream->nformats; i > 0; --i) {
+ format = &stream->format[i-1];
+ if (format->index == probe->bFormatIndex)
+ break;
+ }
+
+ if (format->nframes == 0) {
+ dev_info(&stream->intf->dev,
+ "No frame descriptor found for the default format.\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Zero bFrameIndex might be correct. Stream-based formats (including
+ * MPEG-2 TS and DV) do not support frames but have a dummy frame
+ * descriptor with bFrameIndex set to zero. If the default frame
+ * descriptor is not found, use the first available frame.
+ */
+ for (i = format->nframes; i > 0; --i) {
+ frame = &format->frame[i-1];
+ if (frame->bFrameIndex == probe->bFrameIndex)
+ break;
+ }
+
+ probe->bFormatIndex = format->index;
+ probe->bFrameIndex = frame->bFrameIndex;
+
+ stream->def_format = format;
+ stream->cur_format = format;
+ stream->cur_frame = frame;
+
+ /* Select the video decoding function */
+ if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ if (stream->dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)
+ stream->decode = uvc_video_decode_isight;
+ else if (stream->intf->num_altsetting > 1)
+ stream->decode = uvc_video_decode_isoc;
+ else
+ stream->decode = uvc_video_decode_bulk;
+ } else {
+ if (stream->intf->num_altsetting == 1)
+ stream->decode = uvc_video_encode_bulk;
+ else {
+ dev_info(&stream->intf->dev,
+ "Isochronous endpoints are not supported for video output devices.\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Prepare asynchronous work items. */
+ for_each_uvc_urb(uvc_urb, stream)
+ INIT_WORK(&uvc_urb->work, uvc_video_copy_data_work);
+
+ return 0;
+}
+
+int uvc_video_start_streaming(struct uvc_streaming *stream)
+{
+ int ret;
+
+ ret = uvc_video_clock_init(stream);
+ if (ret < 0)
+ return ret;
+
+ /* Commit the streaming parameters. */
+ ret = uvc_commit_video(stream, &stream->ctrl);
+ if (ret < 0)
+ goto error_commit;
+
+ ret = uvc_video_start_transfer(stream, GFP_KERNEL);
+ if (ret < 0)
+ goto error_video;
+
+ return 0;
+
+error_video:
+ usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+error_commit:
+ uvc_video_clock_cleanup(stream);
+
+ return ret;
+}
+
+void uvc_video_stop_streaming(struct uvc_streaming *stream)
+{
+ uvc_video_stop_transfer(stream, 1);
+
+ if (stream->intf->num_altsetting > 1) {
+ usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+ } else {
+ /*
+ * UVC doesn't specify how to inform a bulk-based device
+ * when the video stream is stopped. Windows sends a
+ * CLEAR_FEATURE(HALT) request to the video streaming
+ * bulk endpoint, mimic the same behaviour.
+ */
+ unsigned int epnum = stream->header.bEndpointAddress
+ & USB_ENDPOINT_NUMBER_MASK;
+ unsigned int dir = stream->header.bEndpointAddress
+ & USB_ENDPOINT_DIR_MASK;
+ unsigned int pipe;
+
+ pipe = usb_sndbulkpipe(stream->dev->udev, epnum) | dir;
+ usb_clear_halt(stream->dev->udev, pipe);
+ }
+
+ uvc_video_clock_cleanup(stream);
+}
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
new file mode 100644
index 000000000..33e7475d4
--- /dev/null
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -0,0 +1,790 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _USB_VIDEO_H_
+#define _USB_VIDEO_H_
+
+#ifndef __KERNEL__
+#error "The uvcvideo.h header is deprecated, use linux/uvcvideo.h instead."
+#endif /* __KERNEL__ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/poll.h>
+#include <linux/usb.h>
+#include <linux/usb/video.h>
+#include <linux/uvcvideo.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <media/media-device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-v4l2.h>
+
+/* --------------------------------------------------------------------------
+ * UVC constants
+ */
+
+#define UVC_TERM_INPUT 0x0000
+#define UVC_TERM_OUTPUT 0x8000
+#define UVC_TERM_DIRECTION(term) ((term)->type & 0x8000)
+
+#define UVC_ENTITY_TYPE(entity) ((entity)->type & 0x7fff)
+#define UVC_ENTITY_IS_UNIT(entity) (((entity)->type & 0xff00) == 0)
+#define UVC_ENTITY_IS_TERM(entity) (((entity)->type & 0xff00) != 0)
+#define UVC_ENTITY_IS_ITERM(entity) \
+ (UVC_ENTITY_IS_TERM(entity) && \
+ ((entity)->type & 0x8000) == UVC_TERM_INPUT)
+#define UVC_ENTITY_IS_OTERM(entity) \
+ (UVC_ENTITY_IS_TERM(entity) && \
+ ((entity)->type & 0x8000) == UVC_TERM_OUTPUT)
+
+#define UVC_EXT_GPIO_UNIT 0x7ffe
+#define UVC_EXT_GPIO_UNIT_ID 0x100
+
+/* ------------------------------------------------------------------------
+ * Driver specific constants.
+ */
+
+#define DRIVER_VERSION "1.1.1"
+
+/* Number of isochronous URBs. */
+#define UVC_URBS 5
+/* Maximum number of packets per URB. */
+#define UVC_MAX_PACKETS 32
+/* Maximum status buffer size in bytes of interrupt URB. */
+#define UVC_MAX_STATUS_SIZE 16
+
+#define UVC_CTRL_CONTROL_TIMEOUT 5000
+#define UVC_CTRL_STREAMING_TIMEOUT 5000
+
+/* Maximum allowed number of control mappings per device */
+#define UVC_MAX_CONTROL_MAPPINGS 1024
+#define UVC_MAX_CONTROL_MENU_ENTRIES 32
+
+/* Devices quirks */
+#define UVC_QUIRK_STATUS_INTERVAL 0x00000001
+#define UVC_QUIRK_PROBE_MINMAX 0x00000002
+#define UVC_QUIRK_PROBE_EXTRAFIELDS 0x00000004
+#define UVC_QUIRK_BUILTIN_ISIGHT 0x00000008
+#define UVC_QUIRK_STREAM_NO_FID 0x00000010
+#define UVC_QUIRK_IGNORE_SELECTOR_UNIT 0x00000020
+#define UVC_QUIRK_FIX_BANDWIDTH 0x00000080
+#define UVC_QUIRK_PROBE_DEF 0x00000100
+#define UVC_QUIRK_RESTRICT_FRAME_RATE 0x00000200
+#define UVC_QUIRK_RESTORE_CTRLS_ON_INIT 0x00000400
+#define UVC_QUIRK_FORCE_Y8 0x00000800
+#define UVC_QUIRK_FORCE_BPP 0x00001000
+#define UVC_QUIRK_WAKE_AUTOSUSPEND 0x00002000
+
+/* Format flags */
+#define UVC_FMT_FLAG_COMPRESSED 0x00000001
+#define UVC_FMT_FLAG_STREAM 0x00000002
+
+/* ------------------------------------------------------------------------
+ * Structures.
+ */
+
+struct gpio_desc;
+struct sg_table;
+struct uvc_device;
+
+/*
+ * TODO: Put the most frequently accessed fields at the beginning of
+ * structures to maximize cache efficiency.
+ */
+struct uvc_control_info {
+ struct list_head mappings;
+
+ u8 entity[16];
+ u8 index; /* Bit index in bmControls */
+ u8 selector;
+
+ u16 size;
+ u32 flags;
+};
+
+struct uvc_control_mapping {
+ struct list_head list;
+ struct list_head ev_subs;
+
+ u32 id;
+ char *name;
+ u8 entity[16];
+ u8 selector;
+
+ u8 size;
+ u8 offset;
+ enum v4l2_ctrl_type v4l2_type;
+ u32 data_type;
+
+ const struct uvc_menu_info *menu_info;
+ unsigned long menu_mask;
+
+ u32 master_id;
+ s32 master_manual;
+ u32 slave_ids[2];
+
+ s32 (*get)(struct uvc_control_mapping *mapping, u8 query,
+ const u8 *data);
+ void (*set)(struct uvc_control_mapping *mapping, s32 value,
+ u8 *data);
+};
+
+struct uvc_control {
+ struct uvc_entity *entity;
+ struct uvc_control_info info;
+
+ u8 index; /* Used to match the uvc_control entry with a uvc_control_info. */
+ u8 dirty:1,
+ loaded:1,
+ modified:1,
+ cached:1,
+ initialized:1;
+
+ u8 *uvc_data;
+
+ struct uvc_fh *handle; /* File handle that last changed the control. */
+};
+
+/*
+ * The term 'entity' refers to both UVC units and UVC terminals.
+ *
+ * The type field is either the terminal type (wTerminalType in the terminal
+ * descriptor), or the unit type (bDescriptorSubtype in the unit descriptor).
+ * As the bDescriptorSubtype field is one byte long, the type value will
+ * always have a null MSB for units. All terminal types defined by the UVC
+ * specification have a non-null MSB, so it is safe to use the MSB to
+ * differentiate between units and terminals as long as the descriptor parsing
+ * code makes sure terminal types have a non-null MSB.
+ *
+ * For terminals, the type's most significant bit stores the terminal
+ * direction (either UVC_TERM_INPUT or UVC_TERM_OUTPUT). The type field should
+ * always be accessed with the UVC_ENTITY_* macros and never directly.
+ */
+
+#define UVC_ENTITY_FLAG_DEFAULT (1 << 0)
+
+struct uvc_entity {
+ struct list_head list; /* Entity as part of a UVC device. */
+ struct list_head chain; /* Entity as part of a video device chain. */
+ unsigned int flags;
+
+ /*
+ * Entities exposed by the UVC device use IDs 0-255, extra entities
+ * implemented by the driver (such as the GPIO entity) use IDs 256 and
+ * up.
+ */
+ u16 id;
+ u16 type;
+ char name[64];
+ u8 guid[16];
+
+ /* Media controller-related fields. */
+ struct video_device *vdev;
+ struct v4l2_subdev subdev;
+ unsigned int num_pads;
+ unsigned int num_links;
+ struct media_pad *pads;
+
+ union {
+ struct {
+ u16 wObjectiveFocalLengthMin;
+ u16 wObjectiveFocalLengthMax;
+ u16 wOcularFocalLength;
+ u8 bControlSize;
+ u8 *bmControls;
+ } camera;
+
+ struct {
+ u8 bControlSize;
+ u8 *bmControls;
+ u8 bTransportModeSize;
+ u8 *bmTransportModes;
+ } media;
+
+ struct {
+ } output;
+
+ struct {
+ u16 wMaxMultiplier;
+ u8 bControlSize;
+ u8 *bmControls;
+ u8 bmVideoStandards;
+ } processing;
+
+ struct {
+ } selector;
+
+ struct {
+ u8 bNumControls;
+ u8 bControlSize;
+ u8 *bmControls;
+ u8 *bmControlsType;
+ } extension;
+
+ struct {
+ u8 bControlSize;
+ u8 *bmControls;
+ struct gpio_desc *gpio_privacy;
+ int irq;
+ } gpio;
+ };
+
+ u8 bNrInPins;
+ u8 *baSourceID;
+
+ int (*get_info)(struct uvc_device *dev, struct uvc_entity *entity,
+ u8 cs, u8 *caps);
+ int (*get_cur)(struct uvc_device *dev, struct uvc_entity *entity,
+ u8 cs, void *data, u16 size);
+
+ unsigned int ncontrols;
+ struct uvc_control *controls;
+};
+
+struct uvc_frame {
+ u8 bFrameIndex;
+ u8 bmCapabilities;
+ u16 wWidth;
+ u16 wHeight;
+ u32 dwMinBitRate;
+ u32 dwMaxBitRate;
+ u32 dwMaxVideoFrameBufferSize;
+ u8 bFrameIntervalType;
+ u32 dwDefaultFrameInterval;
+ u32 *dwFrameInterval;
+};
+
+struct uvc_format {
+ u8 type;
+ u8 index;
+ u8 bpp;
+ enum v4l2_colorspace colorspace;
+ enum v4l2_xfer_func xfer_func;
+ enum v4l2_ycbcr_encoding ycbcr_enc;
+ u32 fcc;
+ u32 flags;
+
+ unsigned int nframes;
+ struct uvc_frame *frame;
+};
+
+struct uvc_streaming_header {
+ u8 bNumFormats;
+ u8 bEndpointAddress;
+ u8 bTerminalLink;
+ u8 bControlSize;
+ u8 *bmaControls;
+ /* The following fields are used by input headers only. */
+ u8 bmInfo;
+ u8 bStillCaptureMethod;
+ u8 bTriggerSupport;
+ u8 bTriggerUsage;
+};
+
+enum uvc_buffer_state {
+ UVC_BUF_STATE_IDLE = 0,
+ UVC_BUF_STATE_QUEUED = 1,
+ UVC_BUF_STATE_ACTIVE = 2,
+ UVC_BUF_STATE_READY = 3,
+ UVC_BUF_STATE_DONE = 4,
+ UVC_BUF_STATE_ERROR = 5,
+};
+
+struct uvc_buffer {
+ struct vb2_v4l2_buffer buf;
+ struct list_head queue;
+
+ enum uvc_buffer_state state;
+ unsigned int error;
+
+ void *mem;
+ unsigned int length;
+ unsigned int bytesused;
+
+ u32 pts;
+
+ /* Asynchronous buffer handling. */
+ struct kref ref;
+};
+
+#define UVC_QUEUE_DISCONNECTED (1 << 0)
+#define UVC_QUEUE_DROP_CORRUPTED (1 << 1)
+
+struct uvc_video_queue {
+ struct vb2_queue queue;
+ struct mutex mutex; /* Protects queue */
+
+ unsigned int flags;
+ unsigned int buf_used;
+
+ spinlock_t irqlock; /* Protects irqqueue */
+ struct list_head irqqueue;
+};
+
+struct uvc_video_chain {
+ struct uvc_device *dev;
+ struct list_head list;
+
+ struct list_head entities; /* All entities */
+ struct uvc_entity *processing; /* Processing unit */
+ struct uvc_entity *selector; /* Selector unit */
+
+ struct mutex ctrl_mutex; /* Protects ctrl.info */
+
+ struct v4l2_prio_state prio; /* V4L2 priority state */
+ u32 caps; /* V4L2 chain-wide caps */
+ u8 ctrl_class_bitmap; /* Bitmap of valid classes */
+};
+
+struct uvc_stats_frame {
+ unsigned int size; /* Number of bytes captured */
+ unsigned int first_data; /* Index of the first non-empty packet */
+
+ unsigned int nb_packets; /* Number of packets */
+ unsigned int nb_empty; /* Number of empty packets */
+ unsigned int nb_invalid; /* Number of packets with an invalid header */
+ unsigned int nb_errors; /* Number of packets with the error bit set */
+
+ unsigned int nb_pts; /* Number of packets with a PTS timestamp */
+ unsigned int nb_pts_diffs; /* Number of PTS differences inside a frame */
+ unsigned int last_pts_diff; /* Index of the last PTS difference */
+ bool has_initial_pts; /* Whether the first non-empty packet has a PTS */
+ bool has_early_pts; /* Whether a PTS is present before the first non-empty packet */
+ u32 pts; /* PTS of the last packet */
+
+ unsigned int nb_scr; /* Number of packets with a SCR timestamp */
+ unsigned int nb_scr_diffs; /* Number of SCR.STC differences inside a frame */
+ u16 scr_sof; /* SCR.SOF of the last packet */
+ u32 scr_stc; /* SCR.STC of the last packet */
+};
+
+struct uvc_stats_stream {
+ ktime_t start_ts; /* Stream start timestamp */
+ ktime_t stop_ts; /* Stream stop timestamp */
+
+ unsigned int nb_frames; /* Number of frames */
+
+ unsigned int nb_packets; /* Number of packets */
+ unsigned int nb_empty; /* Number of empty packets */
+ unsigned int nb_invalid; /* Number of packets with an invalid header */
+ unsigned int nb_errors; /* Number of packets with the error bit set */
+
+ unsigned int nb_pts_constant; /* Number of frames with constant PTS */
+ unsigned int nb_pts_early; /* Number of frames with early PTS */
+ unsigned int nb_pts_initial; /* Number of frames with initial PTS */
+
+ unsigned int nb_scr_count_ok; /* Number of frames with at least one SCR per non empty packet */
+ unsigned int nb_scr_diffs_ok; /* Number of frames with varying SCR.STC */
+ unsigned int scr_sof_count; /* STC.SOF counter accumulated since stream start */
+ unsigned int scr_sof; /* STC.SOF of the last packet */
+ unsigned int min_sof; /* Minimum STC.SOF value */
+ unsigned int max_sof; /* Maximum STC.SOF value */
+};
+
+#define UVC_METADATA_BUF_SIZE 10240
+
+/**
+ * struct uvc_copy_op: Context structure to schedule asynchronous memcpy
+ *
+ * @buf: active buf object for this operation
+ * @dst: copy destination address
+ * @src: copy source address
+ * @len: copy length
+ */
+struct uvc_copy_op {
+ struct uvc_buffer *buf;
+ void *dst;
+ const __u8 *src;
+ size_t len;
+};
+
+/**
+ * struct uvc_urb - URB context management structure
+ *
+ * @urb: the URB described by this context structure
+ * @stream: UVC streaming context
+ * @buffer: memory storage for the URB
+ * @dma: Allocated DMA handle
+ * @sgt: sgt_table with the urb locations in memory
+ * @async_operations: counter to indicate the number of copy operations
+ * @copy_operations: work descriptors for asynchronous copy operations
+ * @work: work queue entry for asynchronous decode
+ */
+struct uvc_urb {
+ struct urb *urb;
+ struct uvc_streaming *stream;
+
+ char *buffer;
+ dma_addr_t dma;
+ struct sg_table *sgt;
+
+ unsigned int async_operations;
+ struct uvc_copy_op copy_operations[UVC_MAX_PACKETS];
+ struct work_struct work;
+};
+
+struct uvc_streaming {
+ struct list_head list;
+ struct uvc_device *dev;
+ struct video_device vdev;
+ struct uvc_video_chain *chain;
+ atomic_t active;
+
+ struct usb_interface *intf;
+ int intfnum;
+ u16 maxpsize;
+
+ struct uvc_streaming_header header;
+ enum v4l2_buf_type type;
+
+ unsigned int nformats;
+ struct uvc_format *format;
+
+ struct uvc_streaming_control ctrl;
+ struct uvc_format *def_format;
+ struct uvc_format *cur_format;
+ struct uvc_frame *cur_frame;
+
+ /*
+ * Protect access to ctrl, cur_format, cur_frame and hardware video
+ * probe control.
+ */
+ struct mutex mutex;
+
+ /* Buffers queue. */
+ unsigned int frozen : 1;
+ struct uvc_video_queue queue;
+ struct workqueue_struct *async_wq;
+ void (*decode)(struct uvc_urb *uvc_urb, struct uvc_buffer *buf,
+ struct uvc_buffer *meta_buf);
+
+ struct {
+ struct video_device vdev;
+ struct uvc_video_queue queue;
+ u32 format;
+ } meta;
+
+ /* Context data used by the bulk completion handler. */
+ struct {
+ u8 header[256];
+ unsigned int header_size;
+ int skip_payload;
+ u32 payload_size;
+ u32 max_payload_size;
+ } bulk;
+
+ struct uvc_urb uvc_urb[UVC_URBS];
+ unsigned int urb_size;
+
+ u32 sequence;
+ u8 last_fid;
+
+ /* debugfs */
+ struct dentry *debugfs_dir;
+ struct {
+ struct uvc_stats_frame frame;
+ struct uvc_stats_stream stream;
+ } stats;
+
+ /* Timestamps support. */
+ struct uvc_clock {
+ struct uvc_clock_sample {
+ u32 dev_stc;
+ u16 dev_sof;
+ u16 host_sof;
+ ktime_t host_time;
+ } *samples;
+
+ unsigned int head;
+ unsigned int count;
+ unsigned int size;
+
+ u16 last_sof;
+ u16 sof_offset;
+
+ u8 last_scr[6];
+
+ spinlock_t lock;
+ } clock;
+};
+
+#define for_each_uvc_urb(uvc_urb, uvc_streaming) \
+ for ((uvc_urb) = &(uvc_streaming)->uvc_urb[0]; \
+ (uvc_urb) < &(uvc_streaming)->uvc_urb[UVC_URBS]; \
+ ++(uvc_urb))
+
+static inline u32 uvc_urb_index(const struct uvc_urb *uvc_urb)
+{
+ return uvc_urb - &uvc_urb->stream->uvc_urb[0];
+}
+
+struct uvc_device_info {
+ u32 quirks;
+ u32 meta_format;
+ u16 uvc_version;
+ const struct uvc_control_mapping **mappings;
+};
+
+struct uvc_device {
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ unsigned long warnings;
+ u32 quirks;
+ int intfnum;
+ char name[32];
+
+ const struct uvc_device_info *info;
+
+ struct mutex lock; /* Protects users */
+ unsigned int users;
+ atomic_t nmappings;
+
+ /* Video control interface */
+#ifdef CONFIG_MEDIA_CONTROLLER
+ struct media_device mdev;
+#endif
+ struct v4l2_device vdev;
+ u16 uvc_version;
+ u32 clock_frequency;
+
+ struct list_head entities;
+ struct list_head chains;
+
+ /* Video Streaming interfaces */
+ struct list_head streams;
+ struct kref ref;
+
+ /* Status Interrupt Endpoint */
+ struct usb_host_endpoint *int_ep;
+ struct urb *int_urb;
+ bool flush_status;
+ u8 *status;
+ struct input_dev *input;
+ char input_phys[64];
+
+ struct uvc_ctrl_work {
+ struct work_struct work;
+ struct urb *urb;
+ struct uvc_video_chain *chain;
+ struct uvc_control *ctrl;
+ const void *data;
+ } async_ctrl;
+
+ struct uvc_entity *gpio_unit;
+};
+
+enum uvc_handle_state {
+ UVC_HANDLE_PASSIVE = 0,
+ UVC_HANDLE_ACTIVE = 1,
+};
+
+struct uvc_fh {
+ struct v4l2_fh vfh;
+ struct uvc_video_chain *chain;
+ struct uvc_streaming *stream;
+ enum uvc_handle_state state;
+};
+
+struct uvc_driver {
+ struct usb_driver driver;
+};
+
+/* ------------------------------------------------------------------------
+ * Debugging, printing and logging
+ */
+
+#define UVC_DBG_PROBE (1 << 0)
+#define UVC_DBG_DESCR (1 << 1)
+#define UVC_DBG_CONTROL (1 << 2)
+#define UVC_DBG_FORMAT (1 << 3)
+#define UVC_DBG_CAPTURE (1 << 4)
+#define UVC_DBG_CALLS (1 << 5)
+#define UVC_DBG_FRAME (1 << 7)
+#define UVC_DBG_SUSPEND (1 << 8)
+#define UVC_DBG_STATUS (1 << 9)
+#define UVC_DBG_VIDEO (1 << 10)
+#define UVC_DBG_STATS (1 << 11)
+#define UVC_DBG_CLOCK (1 << 12)
+
+#define UVC_WARN_MINMAX 0
+#define UVC_WARN_PROBE_DEF 1
+#define UVC_WARN_XU_GET_RES 2
+
+extern unsigned int uvc_clock_param;
+extern unsigned int uvc_no_drop_param;
+extern unsigned int uvc_dbg_param;
+extern unsigned int uvc_timeout_param;
+extern unsigned int uvc_hw_timestamps_param;
+
+#define uvc_dbg(_dev, flag, fmt, ...) \
+do { \
+ if (uvc_dbg_param & UVC_DBG_##flag) \
+ dev_printk(KERN_DEBUG, &(_dev)->udev->dev, fmt, \
+ ##__VA_ARGS__); \
+} while (0)
+
+#define uvc_dbg_cont(flag, fmt, ...) \
+do { \
+ if (uvc_dbg_param & UVC_DBG_##flag) \
+ pr_cont(fmt, ##__VA_ARGS__); \
+} while (0)
+
+#define uvc_warn_once(_dev, warn, fmt, ...) \
+do { \
+ if (!test_and_set_bit(warn, &(_dev)->warnings)) \
+ dev_info(&(_dev)->udev->dev, fmt, ##__VA_ARGS__); \
+} while (0)
+
+/* --------------------------------------------------------------------------
+ * Internal functions.
+ */
+
+/* Core driver */
+extern struct uvc_driver uvc_driver;
+
+struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id);
+
+/* Video buffers queue management. */
+int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+ int drop_corrupted);
+void uvc_queue_release(struct uvc_video_queue *queue);
+int uvc_request_buffers(struct uvc_video_queue *queue,
+ struct v4l2_requestbuffers *rb);
+int uvc_query_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf);
+int uvc_create_buffers(struct uvc_video_queue *queue,
+ struct v4l2_create_buffers *v4l2_cb);
+int uvc_queue_buffer(struct uvc_video_queue *queue,
+ struct media_device *mdev,
+ struct v4l2_buffer *v4l2_buf);
+int uvc_export_buffer(struct uvc_video_queue *queue,
+ struct v4l2_exportbuffer *exp);
+int uvc_dequeue_buffer(struct uvc_video_queue *queue,
+ struct v4l2_buffer *v4l2_buf, int nonblocking);
+int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type);
+int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type);
+void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect);
+struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+ struct uvc_buffer *buf);
+struct uvc_buffer *uvc_queue_get_current_buffer(struct uvc_video_queue *queue);
+void uvc_queue_buffer_release(struct uvc_buffer *buf);
+int uvc_queue_mmap(struct uvc_video_queue *queue,
+ struct vm_area_struct *vma);
+__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
+ poll_table *wait);
+#ifndef CONFIG_MMU
+unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
+ unsigned long pgoff);
+#endif
+int uvc_queue_allocated(struct uvc_video_queue *queue);
+static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
+{
+ return vb2_is_streaming(&queue->queue);
+}
+
+static inline struct uvc_streaming *
+uvc_queue_to_stream(struct uvc_video_queue *queue)
+{
+ return container_of(queue, struct uvc_streaming, queue);
+}
+
+/* V4L2 interface */
+extern const struct v4l2_ioctl_ops uvc_ioctl_ops;
+extern const struct v4l2_file_operations uvc_fops;
+
+/* Media controller */
+int uvc_mc_register_entities(struct uvc_video_chain *chain);
+void uvc_mc_cleanup_entity(struct uvc_entity *entity);
+
+/* Video */
+int uvc_video_init(struct uvc_streaming *stream);
+int uvc_video_suspend(struct uvc_streaming *stream);
+int uvc_video_resume(struct uvc_streaming *stream, int reset);
+int uvc_video_start_streaming(struct uvc_streaming *stream);
+void uvc_video_stop_streaming(struct uvc_streaming *stream);
+int uvc_probe_video(struct uvc_streaming *stream,
+ struct uvc_streaming_control *probe);
+int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
+ u8 intfnum, u8 cs, void *data, u16 size);
+void uvc_video_clock_update(struct uvc_streaming *stream,
+ struct vb2_v4l2_buffer *vbuf,
+ struct uvc_buffer *buf);
+int uvc_meta_register(struct uvc_streaming *stream);
+
+int uvc_register_video_device(struct uvc_device *dev,
+ struct uvc_streaming *stream,
+ struct video_device *vdev,
+ struct uvc_video_queue *queue,
+ enum v4l2_buf_type type,
+ const struct v4l2_file_operations *fops,
+ const struct v4l2_ioctl_ops *ioctl_ops);
+
+/* Status */
+int uvc_status_init(struct uvc_device *dev);
+void uvc_status_unregister(struct uvc_device *dev);
+void uvc_status_cleanup(struct uvc_device *dev);
+int uvc_status_start(struct uvc_device *dev, gfp_t flags);
+void uvc_status_stop(struct uvc_device *dev);
+
+/* Controls */
+extern const struct uvc_control_mapping uvc_ctrl_power_line_mapping_limited;
+extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops;
+
+int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
+ struct v4l2_queryctrl *v4l2_ctrl);
+int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
+ struct v4l2_querymenu *query_menu);
+
+int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
+ const struct uvc_control_mapping *mapping);
+int uvc_ctrl_init_device(struct uvc_device *dev);
+void uvc_ctrl_cleanup_device(struct uvc_device *dev);
+int uvc_ctrl_restore_values(struct uvc_device *dev);
+bool uvc_ctrl_status_event_async(struct urb *urb, struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, const u8 *data);
+void uvc_ctrl_status_event(struct uvc_video_chain *chain,
+ struct uvc_control *ctrl, const u8 *data);
+
+int uvc_ctrl_begin(struct uvc_video_chain *chain);
+int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
+ struct v4l2_ext_controls *ctrls);
+static inline int uvc_ctrl_commit(struct uvc_fh *handle,
+ struct v4l2_ext_controls *ctrls)
+{
+ return __uvc_ctrl_commit(handle, 0, ctrls);
+}
+static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
+{
+ return __uvc_ctrl_commit(handle, 1, NULL);
+}
+
+int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl);
+int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl);
+int uvc_ctrl_is_accessible(struct uvc_video_chain *chain, u32 v4l2_id,
+ const struct v4l2_ext_controls *ctrls,
+ unsigned long ioctl);
+
+int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
+ struct uvc_xu_control_query *xqry);
+
+/* Utility functions */
+struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts,
+ u8 epaddr);
+u16 uvc_endpoint_max_bpi(struct usb_device *dev, struct usb_host_endpoint *ep);
+
+/* Quirks support */
+void uvc_video_decode_isight(struct uvc_urb *uvc_urb,
+ struct uvc_buffer *buf,
+ struct uvc_buffer *meta_buf);
+
+/* debugfs and statistics */
+void uvc_debugfs_init(void);
+void uvc_debugfs_cleanup(void);
+void uvc_debugfs_init_stream(struct uvc_streaming *stream);
+void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream);
+
+size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf,
+ size_t size);
+
+#endif