aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
Diffstat (limited to 'sys')
-rw-r--r--sys/conf/files1
-rw-r--r--sys/dev/usb/usb.h6
-rw-r--r--sys/dev/usb/video/uvideo.c3493
-rw-r--r--sys/dev/usb/video/uvideo.h687
-rw-r--r--sys/dev/usb/video/uvideo_v4l2.h483
-rw-r--r--sys/modules/usb/Makefile1
-rw-r--r--sys/modules/usb/uvideo/Makefile10
7 files changed, 4681 insertions, 0 deletions
diff --git a/sys/conf/files b/sys/conf/files
index 1332f4bd8cc6..126a08d697b4 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3497,6 +3497,7 @@ dev/usb/template/usb_template_cdceem.c optional usb_template
# USB video drivers
#
dev/usb/video/udl.c optional udl
+dev/usb/video/uvideo.c optional uvideo
#
# USB END
#
diff --git a/sys/dev/usb/usb.h b/sys/dev/usb/usb.h
index 3e972f876c6a..da27c97bd5c7 100644
--- a/sys/dev/usb/usb.h
+++ b/sys/dev/usb/usb.h
@@ -459,6 +459,12 @@ typedef struct usb_interface_assoc_descriptor usb_interface_assoc_descriptor_t;
#define UICLASS_PHYSICAL 0x05
#define UICLASS_IMAGE 0x06
#define UISUBCLASS_SIC 1 /* still image class */
+
+#define UICLASS_VIDEO 0x0e /* video */
+#define UISUBCLASS_VIDEOCONTROL 1
+#define UISUBCLASS_VIDEOSTREAMING 2
+#define UISUBCLASS_VIDEO_IFACE_COLL 3
+
#define UICLASS_PRINTER 0x07
#define UISUBCLASS_PRINTER 1
#define UIPROTO_PRINTER_UNI 1
diff --git a/sys/dev/usb/video/uvideo.c b/sys/dev/usb/video/uvideo.c
new file mode 100644
index 000000000000..310c66db2846
--- /dev/null
+++ b/sys/dev/usb/video/uvideo.c
@@ -0,0 +1,3493 @@
+/*-
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2008 Robert Nagy <robert@openbsd.org>
+ * Copyright (c) 2008 Marcus Glocker <mglocker@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Ported from OpenBSD to FreeBSD by Baptiste Daroussin <bapt@FreeBSD.org>
+ */
+
+/*
+ * USB Video Class (UVC) driver.
+ *
+ * Implements standard UVC 1.0/1.1/1.5 devices only.
+ * Creates /dev/videoN character devices with V4L2 ioctl interface.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/selinfo.h>
+#include <sys/limits.h>
+#include <sys/sysctl.h>
+#include <sys/uio.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_request.h>
+#include "usbdevs.h"
+
+#include <dev/usb/video/uvideo.h>
+
+#define USB_DEBUG_VAR uvideo_debug
+#include <dev/usb/usb_debug.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uvideo, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uvideo");
+
+#ifdef USB_DEBUG
+static int uvideo_debug = 0;
+
+SYSCTL_INT(_hw_usb_uvideo, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uvideo_debug, 0, "Debug level");
+#endif
+
+#define byteof(x) ((x) >> 3)
+#define bitof(x) (1L << ((x) & 0x7))
+
+/* OpenBSD macros not present in FreeBSD USB headers */
+#define UE_GET_SIZE(x) ((x) & 0x7FF)
+#define UE_GET_TRANS(x) (((x) >> 11) & 0x03)
+
+/* IO_NDELAY from sys/vnode.h - avoid pulling in vnode_if.h dependency */
+#ifndef IO_NDELAY
+#define IO_NDELAY 0x0004
+#endif
+
+/* Forward declarations */
+struct uvideo_softc;
+
+static device_probe_t uvideo_probe;
+static device_attach_t uvideo_attach;
+static device_detach_t uvideo_detach;
+
+static usb_callback_t uvideo_isoc_callback;
+static usb_callback_t uvideo_bulk_callback;
+
+static usb_error_t uvideo_vc_parse_desc(struct uvideo_softc *);
+static usb_error_t uvideo_vc_parse_desc_header(struct uvideo_softc *,
+ const struct usb_descriptor *);
+static usb_error_t uvideo_vc_parse_desc_pu(struct uvideo_softc *,
+ const struct usb_descriptor *);
+static usb_error_t uvideo_vc_get_ctrl(struct uvideo_softc *, uint8_t *,
+ uint8_t, uint8_t, uint16_t, uint16_t);
+static usb_error_t uvideo_vc_set_ctrl(struct uvideo_softc *, uint8_t *,
+ uint8_t, uint8_t, uint16_t, uint16_t);
+static int uvideo_find_ctrl(struct uvideo_softc *, int);
+static int uvideo_has_ctrl(struct usb_video_vc_processing_desc *,
+ int);
+
+static usb_error_t uvideo_vs_parse_desc(struct uvideo_softc *,
+ struct usb_config_descriptor *);
+static usb_error_t uvideo_vs_parse_desc_input_header(struct uvideo_softc *,
+ const struct usb_descriptor *);
+static usb_error_t uvideo_vs_parse_desc_format(struct uvideo_softc *);
+static void uvideo_vs_parse_desc_colorformat(struct uvideo_softc *,
+ const struct usb_descriptor *);
+static void uvideo_vs_parse_desc_format_frame_based(
+ struct uvideo_softc *,
+ const struct usb_descriptor *);
+static void uvideo_vs_parse_desc_format_h264(struct uvideo_softc *,
+ const struct usb_descriptor *);
+static void uvideo_vs_parse_desc_format_mjpeg(struct uvideo_softc *,
+ const struct usb_descriptor *);
+static void uvideo_vs_parse_desc_format_uncompressed(
+ struct uvideo_softc *,
+ const struct usb_descriptor *);
+static usb_error_t uvideo_vs_parse_desc_frame(struct uvideo_softc *);
+static usb_error_t uvideo_vs_parse_desc_frame_buffer_size(
+ struct uvideo_softc *,
+ const struct usb_descriptor *);
+static usb_error_t uvideo_vs_parse_desc_frame_max_rate(
+ struct uvideo_softc *,
+ const struct usb_descriptor *);
+static usb_error_t uvideo_vs_parse_desc_alt(struct uvideo_softc *, int,
+ int, int);
+static int uvideo_desc_len(const struct usb_descriptor *, int,
+ int, int, int);
+static void uvideo_find_res(struct uvideo_softc *, int, int, int,
+ struct uvideo_res *);
+static usb_error_t uvideo_vs_negotiation(struct uvideo_softc *, int);
+static usb_error_t uvideo_vs_set_probe(struct uvideo_softc *, uint8_t *);
+static usb_error_t uvideo_vs_get_probe(struct uvideo_softc *, uint8_t *,
+ uint8_t);
+static usb_error_t uvideo_vs_set_commit(struct uvideo_softc *, uint8_t *);
+static usb_error_t uvideo_vs_alloc_frame(struct uvideo_softc *);
+static void uvideo_vs_free_frame(struct uvideo_softc *);
+static usb_error_t uvideo_vs_open(struct uvideo_softc *);
+static void uvideo_vs_close(struct uvideo_softc *);
+static usb_error_t uvideo_vs_init(struct uvideo_softc *);
+static void uvideo_vs_decode_stream_header(struct uvideo_softc *,
+ uint8_t *, int);
+static void uvideo_isoc_decode(struct uvideo_softc *,
+ struct usb_page_cache *, int, int);
+static uint8_t *uvideo_mmap_getbuf(struct uvideo_softc *);
+static void uvideo_mmap_queue(struct uvideo_softc *, int, int);
+static void uvideo_read_frame(struct uvideo_softc *, uint8_t *, int);
+
+static d_open_t uvideo_cdev_open;
+static d_close_t uvideo_cdev_close;
+static d_read_t uvideo_cdev_read;
+static d_ioctl_t uvideo_cdev_ioctl;
+static d_poll_t uvideo_cdev_poll;
+static d_mmap_t uvideo_cdev_mmap;
+
+static int uvideo_querycap(struct uvideo_softc *, struct v4l2_capability *);
+static int uvideo_enum_fmt(struct uvideo_softc *, struct v4l2_fmtdesc *);
+static int uvideo_enum_fsizes(struct uvideo_softc *,
+ struct v4l2_frmsizeenum *);
+static int uvideo_enum_fivals(struct uvideo_softc *,
+ struct v4l2_frmivalenum *);
+static int uvideo_s_fmt(struct uvideo_softc *, struct v4l2_format *);
+static int uvideo_g_fmt(struct uvideo_softc *, struct v4l2_format *);
+static int uvideo_s_parm(struct uvideo_softc *, struct v4l2_streamparm *);
+static int uvideo_g_parm(struct uvideo_softc *, struct v4l2_streamparm *);
+static int uvideo_enum_input(struct uvideo_softc *, struct v4l2_input *);
+static int uvideo_s_input(struct uvideo_softc *, int);
+static int uvideo_g_input(struct uvideo_softc *, int *);
+static int uvideo_reqbufs(struct uvideo_softc *,
+ struct v4l2_requestbuffers *);
+static int uvideo_querybuf(struct uvideo_softc *, struct v4l2_buffer *);
+static int uvideo_qbuf(struct uvideo_softc *, struct v4l2_buffer *);
+static int uvideo_dqbuf(struct uvideo_softc *, struct v4l2_buffer *);
+static int uvideo_streamon(struct uvideo_softc *, int);
+static int uvideo_streamoff(struct uvideo_softc *, int);
+static int uvideo_try_fmt(struct uvideo_softc *, struct v4l2_format *);
+static int uvideo_queryctrl(struct uvideo_softc *,
+ struct v4l2_queryctrl *);
+static int uvideo_g_ctrl(struct uvideo_softc *, struct v4l2_control *);
+static int uvideo_s_ctrl(struct uvideo_softc *, struct v4l2_control *);
+
+/*
+ * Transfer configuration indices.
+ */
+enum {
+ UVIDEO_ISOC_RX_0,
+ UVIDEO_ISOC_RX_1,
+ UVIDEO_ISOC_RX_2,
+ UVIDEO_BULK_RX,
+ UVIDEO_N_XFER
+};
+
+/*
+ * The softc structure.
+ */
+struct uvideo_softc {
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+ struct cdev *sc_cdev;
+ int sc_unit;
+
+ uint8_t sc_iface_index;
+ uint8_t sc_nifaces;
+ int sc_dying;
+ int sc_open;
+ uint32_t sc_priority;
+ struct proc *sc_owner;
+
+ struct usb_xfer *sc_xfer[UVIDEO_N_XFER];
+ int sc_streaming;
+
+ int sc_max_ctrl_size;
+ int sc_max_fbuf_size;
+ int sc_negotiated_flag;
+ int sc_frame_rate;
+
+ struct uvideo_frame_buffer sc_frame_buffer;
+
+ struct uvideo_mmap sc_mmap[UVIDEO_MAX_BUFFERS];
+ struct uvideo_mmap *sc_mmap_cur;
+ uint8_t *sc_mmap_buffer;
+ size_t sc_mmap_buffer_size;
+ int sc_mmap_buffer_idx;
+ q_mmap sc_mmap_q;
+ int sc_mmap_count;
+ int sc_mmap_flag;
+
+ uint8_t *sc_tmpbuf;
+ int sc_tmpbuf_size;
+
+ int sc_nframes;
+ struct usb_video_probe_commit sc_desc_probe;
+ struct usb_video_header_desc_all sc_desc_vc_header;
+ struct usb_video_input_header_desc_all sc_desc_vs_input_header;
+
+#define UVIDEO_MAX_PU 8
+ int sc_desc_vc_pu_num;
+ struct usb_video_vc_processing_desc *sc_desc_vc_pu_cur;
+ struct usb_video_vc_processing_desc *sc_desc_vc_pu[UVIDEO_MAX_PU];
+
+#define UVIDEO_MAX_FORMAT 8
+ int sc_fmtgrp_idx;
+ int sc_fmtgrp_num;
+ struct uvideo_format_group *sc_fmtgrp_cur;
+ struct uvideo_format_group sc_fmtgrp[UVIDEO_MAX_FORMAT];
+
+#define UVIDEO_MAX_VS_NUM 8
+ struct uvideo_vs_iface *sc_vs_cur;
+ struct uvideo_vs_iface sc_vs_coll[UVIDEO_MAX_VS_NUM];
+
+ int sc_fsize;
+ uint8_t *sc_fbuffer;
+ size_t sc_fbufferlen;
+ int sc_vidmode;
+#define VIDMODE_NONE 0
+#define VIDMODE_MMAP 1
+#define VIDMODE_READ 2
+ int sc_frames_ready;
+
+ struct selinfo sc_selinfo;
+
+ void (*sc_decode_stream_header)(
+ struct uvideo_softc *, uint8_t *, int);
+};
+
+/*
+ * Processing Unit control descriptors
+ */
+static struct uvideo_controls uvideo_ctrls[] = {
+ {
+ V4L2_CID_BRIGHTNESS,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Brightness",
+ 0,
+ PU_BRIGHTNESS_CONTROL,
+ 2,
+ 1
+ },
+ {
+ V4L2_CID_CONTRAST,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Contrast",
+ 1,
+ PU_CONTRAST_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_HUE,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Hue",
+ 2,
+ PU_HUE_CONTROL,
+ 2,
+ 1
+ },
+ {
+ V4L2_CID_SATURATION,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Saturation",
+ 3,
+ PU_SATURATION_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_SHARPNESS,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Sharpness",
+ 4,
+ PU_SHARPNESS_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_GAMMA,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Gamma",
+ 5,
+ PU_GAMMA_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_WHITE_BALANCE_TEMPERATURE,
+ V4L2_CTRL_TYPE_INTEGER,
+ "White Balance Temperature",
+ 6,
+ PU_WHITE_BALANCE_TEMPERATURE_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_BACKLIGHT_COMPENSATION,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Backlight Compensation",
+ 8,
+ PU_BACKLIGHT_COMPENSATION_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_GAIN,
+ V4L2_CTRL_TYPE_INTEGER,
+ "Gain",
+ 9,
+ PU_GAIN_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_POWER_LINE_FREQUENCY,
+ V4L2_CTRL_TYPE_MENU,
+ "Power Line Frequency",
+ 10,
+ PU_POWER_LINE_FREQUENCY_CONTROL,
+ 2,
+ 0
+ },
+ {
+ V4L2_CID_HUE_AUTO,
+ V4L2_CTRL_TYPE_BOOLEAN,
+ "Hue Auto",
+ 11,
+ PU_HUE_AUTO_CONTROL,
+ 1,
+ 0
+ },
+ {
+ V4L2_CID_AUTO_WHITE_BALANCE,
+ V4L2_CTRL_TYPE_BOOLEAN,
+ "White Balance Temperature Auto",
+ 12,
+ PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL,
+ 1,
+ 0
+ },
+ {
+ V4L2_CID_AUTO_WHITE_BALANCE,
+ V4L2_CTRL_TYPE_BOOLEAN,
+ "White Balance Component Auto",
+ 13,
+ PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL,
+ 1,
+ 0
+ },
+ { 0, 0, "", 0, 0, 0, 0 }
+};
+
+/*
+ * Format GUID to V4L2 pixel format mapping
+ */
+static const struct {
+ uint8_t guidFormat[16];
+ uint32_t pixelformat;
+} uvideo_map_fmts[] = {
+ { UVIDEO_FORMAT_GUID_YUY2, V4L2_PIX_FMT_YUYV },
+ { UVIDEO_FORMAT_GUID_YV12, V4L2_PIX_FMT_YVU420 },
+ { UVIDEO_FORMAT_GUID_I420, V4L2_PIX_FMT_YUV420 },
+ { UVIDEO_FORMAT_GUID_Y800, V4L2_PIX_FMT_GREY },
+ { UVIDEO_FORMAT_GUID_Y8, V4L2_PIX_FMT_GREY },
+ { UVIDEO_FORMAT_GUID_D3DFMT_L8, V4L2_PIX_FMT_GREY },
+ { UVIDEO_FORMAT_GUID_KSMEDIA_L8_IR, V4L2_PIX_FMT_GREY },
+ { UVIDEO_FORMAT_GUID_BY8, V4L2_PIX_FMT_SBGGR8 },
+ { UVIDEO_FORMAT_GUID_BA81, V4L2_PIX_FMT_SBGGR8 },
+ { UVIDEO_FORMAT_GUID_GBRG, V4L2_PIX_FMT_SGBRG8 },
+ { UVIDEO_FORMAT_GUID_GRBG, V4L2_PIX_FMT_SGRBG8 },
+ { UVIDEO_FORMAT_GUID_RGGB, V4L2_PIX_FMT_SRGGB8 },
+ { UVIDEO_FORMAT_GUID_RGBP, V4L2_PIX_FMT_RGB565 },
+ { UVIDEO_FORMAT_GUID_D3DFMT_R5G6B5, V4L2_PIX_FMT_RGB565 },
+ { UVIDEO_FORMAT_GUID_BGR3, V4L2_PIX_FMT_BGR24 },
+ { UVIDEO_FORMAT_GUID_BGR4, V4L2_PIX_FMT_XBGR32 },
+ { UVIDEO_FORMAT_GUID_H265, V4L2_PIX_FMT_HEVC },
+ { UVIDEO_FORMAT_GUID_RW10, V4L2_PIX_FMT_SRGGB10P },
+ { UVIDEO_FORMAT_GUID_BG16, V4L2_PIX_FMT_SBGGR16 },
+ { UVIDEO_FORMAT_GUID_GB16, V4L2_PIX_FMT_SGBRG16 },
+ { UVIDEO_FORMAT_GUID_RG16, V4L2_PIX_FMT_SRGGB16 },
+ { UVIDEO_FORMAT_GUID_GR16, V4L2_PIX_FMT_SGRBG16 },
+ { UVIDEO_FORMAT_GUID_INVZ, V4L2_PIX_FMT_Z16 },
+ { UVIDEO_FORMAT_GUID_INVI, V4L2_PIX_FMT_Y10 },
+};
+
+/*
+ * Color matching tables from UVC spec
+ */
+static const enum v4l2_colorspace uvideo_color_primaries[] = {
+ V4L2_COLORSPACE_SRGB, /* Unspecified */
+ V4L2_COLORSPACE_SRGB,
+ V4L2_COLORSPACE_470_SYSTEM_M,
+ V4L2_COLORSPACE_470_SYSTEM_BG,
+ V4L2_COLORSPACE_SMPTE170M,
+ V4L2_COLORSPACE_SMPTE240M,
+};
+
+static const enum v4l2_xfer_func uvideo_xfer_characteristics[] = {
+ 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,
+};
+
+static const enum v4l2_ycbcr_encoding uvideo_matrix_coefficients[] = {
+ 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,
+};
+
+/*
+ * USB device ID table - match standard UVC devices
+ */
+static const STRUCT_USB_HOST_ID uvideo_devs[] = {
+ {USB_IFACE_CLASS(UICLASS_VIDEO),
+ USB_IFACE_SUBCLASS(UISUBCLASS_VIDEOCONTROL),},
+};
+
+/*
+ * Device methods
+ */
+static device_method_t uvideo_methods[] = {
+ DEVMETHOD(device_probe, uvideo_probe),
+ DEVMETHOD(device_attach, uvideo_attach),
+ DEVMETHOD(device_detach, uvideo_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uvideo_driver = {
+ .name = "uvideo",
+ .methods = uvideo_methods,
+ .size = sizeof(struct uvideo_softc),
+};
+
+DRIVER_MODULE(uvideo, uhub, uvideo_driver, NULL, NULL);
+MODULE_DEPEND(uvideo, usb, 1, 1, 1);
+MODULE_VERSION(uvideo, 1);
+USB_PNP_HOST_INFO(uvideo_devs);
+
+/*
+ * Transfer configuration: triple-buffered isochronous + single bulk
+ */
+static const struct usb_config uvideo_isoc_config[UVIDEO_IXFERS] = {
+ [0] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0, /* use wMaxPacketSize * frames */
+ .frames = UVIDEO_NFRAMES_MAX,
+ .flags = {.short_xfer_ok = 1, .short_frames_ok = 1,},
+ .callback = &uvideo_isoc_callback,
+ },
+ [1] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0,
+ .frames = UVIDEO_NFRAMES_MAX,
+ .flags = {.short_xfer_ok = 1, .short_frames_ok = 1,},
+ .callback = &uvideo_isoc_callback,
+ },
+ [2] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0,
+ .frames = UVIDEO_NFRAMES_MAX,
+ .flags = {.short_xfer_ok = 1, .short_frames_ok = 1,},
+ .callback = &uvideo_isoc_callback,
+ },
+};
+
+static const struct usb_config uvideo_bulk_config[1] = {
+ [0] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 65536,
+ .flags = {.short_xfer_ok = 1, .pipe_bof = 1,},
+ .callback = &uvideo_bulk_callback,
+ },
+};
+
+/*
+ * Character device switch
+ */
+static struct cdevsw uvideo_cdevsw = {
+ .d_version = D_VERSION,
+ .d_open = uvideo_cdev_open,
+ .d_close = uvideo_cdev_close,
+ .d_read = uvideo_cdev_read,
+ .d_ioctl = uvideo_cdev_ioctl,
+ .d_poll = uvideo_cdev_poll,
+ .d_mmap = uvideo_cdev_mmap,
+ .d_name = "video",
+};
+
+/*
+ * Unit number allocator
+ */
+/* Unit number allocation is handled by scanning for free /dev/videoN names */
+
+/* ---------------------------------------------------------------- */
+/* Probe / Attach / Detach */
+/* ---------------------------------------------------------------- */
+
+static int
+uvideo_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (uaa->info.bInterfaceClass != UICLASS_VIDEO)
+ return (ENXIO);
+
+ if (uaa->info.bInterfaceSubClass != UISUBCLASS_VIDEOCONTROL)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(uvideo_devs, sizeof(uvideo_devs), uaa));
+}
+
+static int
+uvideo_attach(device_t dev)
+{
+ struct uvideo_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_config_descriptor *cdesc;
+ struct usb_descriptor *desc;
+ struct usb_interface_assoc_descriptor *iad;
+ struct make_dev_args args;
+ usb_error_t error;
+ int first_iface, nifaces;
+ int i;
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uvideo", NULL, MTX_DEF);
+ knlist_init_mtx(&sc->sc_selinfo.si_note, &sc->sc_mtx);
+
+ /* Get the config descriptor to iterate */
+ cdesc = usbd_get_config_descriptor(sc->sc_udev);
+ if (cdesc == NULL) {
+ device_printf(dev, "failed to get config descriptor\n");
+ goto detach;
+ }
+
+ /*
+ * Find the Interface Association Descriptor (IAD) that groups
+ * the video control and video streaming interfaces.
+ */
+ iad = NULL;
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ if (desc->bDescriptorType != UDESC_IFACE_ASSOC)
+ continue;
+ iad = (struct usb_interface_assoc_descriptor *)desc;
+ if (uaa->info.bIfaceIndex >= iad->bFirstInterface &&
+ uaa->info.bIfaceIndex <
+ iad->bFirstInterface + iad->bInterfaceCount)
+ break;
+ iad = NULL;
+ }
+ if (iad == NULL) {
+ device_printf(dev, "can't find interface association\n");
+ goto detach;
+ }
+
+ first_iface = iad->bFirstInterface;
+ nifaces = iad->bInterfaceCount;
+
+ /* Claim all interfaces in this association */
+ for (i = first_iface; i < first_iface + nifaces; i++) {
+ if (i == uaa->info.bIfaceIndex)
+ continue;
+ usbd_set_parent_iface(sc->sc_udev, i, uaa->info.bIfaceIndex);
+ }
+
+ sc->sc_iface_index = first_iface;
+ sc->sc_nifaces = nifaces;
+
+ /* Standard UVC stream header decode */
+ sc->sc_decode_stream_header = uvideo_vs_decode_stream_header;
+
+ /* Parse video control descriptors */
+ error = uvideo_vc_parse_desc(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(dev, "failed to parse VC descriptors\n");
+ goto detach;
+ }
+
+ /* Parse video stream descriptors */
+ error = uvideo_vs_parse_desc(sc, cdesc);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(dev, "failed to parse VS descriptors\n");
+ goto detach;
+ }
+
+ /* Set default video stream interface to alt 0 */
+ if (sc->sc_vs_cur != NULL) {
+ error = usbd_set_alt_interface_index(sc->sc_udev,
+ sc->sc_vs_cur->iface_index, 0);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(dev,
+ "failed to set default alt interface\n");
+ goto detach;
+ }
+ }
+
+ /* Do device negotiation without commit */
+ error = uvideo_vs_negotiation(sc, 0);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(dev, "initial negotiation failed\n");
+ goto detach;
+ }
+
+ /* Report what we found */
+ if (sc->sc_vs_cur != NULL) {
+ device_printf(dev, "%d format(s), iface_index=%d, "
+ "endpoint=0x%02x, psize=%u, %s\n",
+ sc->sc_fmtgrp_num,
+ sc->sc_vs_cur->iface_index,
+ sc->sc_vs_cur->endpoint,
+ sc->sc_vs_cur->psize,
+ sc->sc_vs_cur->bulk_endpoint ? "bulk" : "isoc");
+ if (sc->sc_fmtgrp_cur != NULL) {
+ struct usb_video_frame_desc *fr =
+ sc->sc_fmtgrp_cur->frame_cur;
+ device_printf(dev, "default format: pixfmt=0x%08x, "
+ "%dx%d, max_fbuf=%d\n",
+ sc->sc_fmtgrp_cur->pixelformat,
+ fr ? UGETW(UVIDEO_FRAME_FIELD(fr, wWidth)) : 0,
+ fr ? UGETW(UVIDEO_FRAME_FIELD(fr, wHeight)) : 0,
+ sc->sc_max_fbuf_size);
+ }
+ }
+
+ /* Init mmap queue */
+ STAILQ_INIT(&sc->sc_mmap_q);
+ sc->sc_mmap_count = 0;
+
+ /* Allocate unit number and create character device */
+ make_dev_args_init(&args);
+ args.mda_devsw = &uvideo_cdevsw;
+ args.mda_uid = UID_ROOT;
+ args.mda_gid = GID_VIDEO;
+ args.mda_mode = 0660;
+ args.mda_si_drv1 = sc;
+ args.mda_flags = MAKEDEV_CHECKNAME;
+
+ sc->sc_unit = -1;
+ for (i = 0; i < 256; i++) {
+ if (make_dev_s(&args, &sc->sc_cdev, "video%d", i) == 0) {
+ sc->sc_unit = i;
+ break;
+ }
+ }
+ if (sc->sc_unit < 0) {
+ device_printf(dev, "failed to create /dev/video device\n");
+ goto detach;
+ }
+
+ device_printf(dev, "UVC camera on /dev/video%d\n", sc->sc_unit);
+
+ return (0);
+
+detach:
+ uvideo_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uvideo_detach(device_t dev)
+{
+ struct uvideo_softc *sc = device_get_softc(dev);
+
+ sc->sc_dying = 1;
+
+ /* Stop any active streaming */
+ if (sc->sc_streaming) {
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_streaming = 0;
+ mtx_unlock(&sc->sc_mtx);
+ uvideo_vs_close(sc);
+ }
+
+ /* Destroy character device */
+ if (sc->sc_cdev != NULL) {
+ destroy_dev(sc->sc_cdev);
+ sc->sc_cdev = NULL;
+ }
+
+ /* Unit number is implicitly freed when the cdev is destroyed */
+
+ /* Free frame buffers */
+ uvideo_vs_free_frame(sc);
+
+ /* Unsetup USB transfers */
+ usbd_transfer_unsetup(sc->sc_xfer, UVIDEO_N_XFER);
+
+ seldrain(&sc->sc_selinfo);
+ knlist_destroy(&sc->sc_selinfo.si_note);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/* ---------------------------------------------------------------- */
+/* Descriptor Parsing */
+/* ---------------------------------------------------------------- */
+
+static usb_error_t
+uvideo_vc_parse_desc(struct uvideo_softc *sc)
+{
+ struct usb_config_descriptor *cdesc;
+ struct usb_descriptor *desc;
+ struct usb_interface_descriptor *id;
+ int vc_header_found;
+ usb_error_t error;
+ int past_our_iface;
+
+ DPRINTFN(1, "uvideo_vc_parse_desc\n");
+
+ vc_header_found = 0;
+ past_our_iface = 0;
+
+ cdesc = usbd_get_config_descriptor(sc->sc_udev);
+ if (cdesc == NULL)
+ return (USB_ERR_INVAL);
+
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ /* Look for our VC interface */
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ id = (struct usb_interface_descriptor *)desc;
+ if (id->bInterfaceNumber == sc->sc_iface_index) {
+ past_our_iface = 1;
+ continue;
+ } else if (past_our_iface &&
+ id->bInterfaceNumber != sc->sc_iface_index) {
+ /*
+ * We have left our VC interface;
+ * stop if we hit a new IAD or unrelated iface.
+ */
+ }
+ }
+ if (desc->bDescriptorType == UDESC_IFACE_ASSOC &&
+ past_our_iface)
+ break;
+
+ if (!past_our_iface)
+ continue;
+
+ if (desc->bDescriptorType != UDESC_CS_INTERFACE)
+ continue;
+
+ switch (desc->bDescriptorSubtype) {
+ case UDESCSUB_VC_HEADER:
+ if (!uvideo_desc_len(desc, 12, 11, 1, 0))
+ break;
+ if (vc_header_found) {
+ device_printf(sc->sc_dev,
+ "too many VC_HEADERs!\n");
+ return (USB_ERR_INVAL);
+ }
+ error = uvideo_vc_parse_desc_header(sc, desc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ vc_header_found = 1;
+ break;
+ case UDESCSUB_VC_PROCESSING_UNIT:
+ (void)uvideo_vc_parse_desc_pu(sc, desc);
+ break;
+ }
+ }
+
+ if (vc_header_found == 0) {
+ device_printf(sc->sc_dev, "no VC_HEADER found!\n");
+ return (USB_ERR_INVAL);
+ }
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vc_parse_desc_header(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_header_desc *d;
+
+ d = __DECONST(struct usb_video_header_desc *, desc);
+
+ if (d->bInCollection == 0) {
+ device_printf(sc->sc_dev, "no VS interface found!\n");
+ return (USB_ERR_INVAL);
+ }
+
+ sc->sc_desc_vc_header.fix = d;
+ sc->sc_desc_vc_header.baInterfaceNr = (uByte *)(d + 1);
+ if (UGETW(d->bcdUVC) < 0x0110)
+ sc->sc_max_ctrl_size = 26;
+ else if (UGETW(d->bcdUVC) < 0x0150)
+ sc->sc_max_ctrl_size = 34;
+ else
+ sc->sc_max_ctrl_size = 48;
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vc_parse_desc_pu(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_vc_processing_desc *d;
+
+ d = __DECONST(struct usb_video_vc_processing_desc *, desc);
+
+ if (sc->sc_desc_vc_pu_num == UVIDEO_MAX_PU) {
+ device_printf(sc->sc_dev,
+ "too many PU descriptors found!\n");
+ return (USB_ERR_INVAL);
+ }
+
+ sc->sc_desc_vc_pu[sc->sc_desc_vc_pu_num] = d;
+ sc->sc_desc_vc_pu_num++;
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vc_get_ctrl(struct uvideo_softc *sc, uint8_t *ctrl_data,
+ uint8_t request, uint8_t unitid, uint16_t ctrl_selector, uint16_t ctrl_len)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+
+ req.bmRequestType = UVIDEO_GET_IF;
+ req.bRequest = request;
+ USETW(req.wValue, (ctrl_selector << 8));
+ USETW(req.wIndex, (unitid << 8));
+ USETW(req.wLength, ctrl_len);
+
+ error = usbd_do_request(sc->sc_udev, NULL, &req, ctrl_data);
+ if (error) {
+ DPRINTFN(1, "could not GET ctrl: %s\n",
+ usbd_errstr(error));
+ return (USB_ERR_INVAL);
+ }
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vc_set_ctrl(struct uvideo_softc *sc, uint8_t *ctrl_data,
+ uint8_t request, uint8_t unitid, uint16_t ctrl_selector, uint16_t ctrl_len)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+
+ req.bmRequestType = UVIDEO_SET_IF;
+ req.bRequest = request;
+ USETW(req.wValue, (ctrl_selector << 8));
+ USETW(req.wIndex, (unitid << 8));
+ USETW(req.wLength, ctrl_len);
+
+ error = usbd_do_request(sc->sc_udev, NULL, &req, ctrl_data);
+ if (error) {
+ DPRINTFN(1, "could not SET ctrl: %s\n",
+ usbd_errstr(error));
+ return (USB_ERR_INVAL);
+ }
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static int
+uvideo_find_ctrl(struct uvideo_softc *sc, int id)
+{
+ int i, j, found;
+
+ if (sc->sc_desc_vc_pu_num == 0) {
+ DPRINTFN(1, "no processing unit descriptors found!\n");
+ return (EINVAL);
+ }
+
+ /* do we support this control? */
+ for (found = 0, i = 0; uvideo_ctrls[i].cid != 0; i++) {
+ if (id == uvideo_ctrls[i].cid) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0) {
+ DPRINTFN(1, "control not supported by driver!\n");
+ return (EINVAL);
+ }
+
+ /* does the device support this control? */
+ for (found = 0, j = 0; j < sc->sc_desc_vc_pu_num; j++) {
+ if (uvideo_has_ctrl(sc->sc_desc_vc_pu[j],
+ uvideo_ctrls[i].ctrl_bit) != 0) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0) {
+ DPRINTFN(1, "control not supported by device!\n");
+ return (EINVAL);
+ }
+ sc->sc_desc_vc_pu_cur = sc->sc_desc_vc_pu[j];
+
+ return (i);
+}
+
+static int
+uvideo_has_ctrl(struct usb_video_vc_processing_desc *desc, int ctrl_bit)
+{
+
+ if (desc->bControlSize * 8 <= ctrl_bit)
+ return (0);
+
+ return (desc->bmControls[byteof(ctrl_bit)] & bitof(ctrl_bit));
+}
+
+static usb_error_t
+uvideo_vs_parse_desc(struct uvideo_softc *sc,
+ struct usb_config_descriptor *cdesc)
+{
+ struct usb_descriptor *desc;
+ struct usb_interface_descriptor *id;
+ struct usb_interface *iface;
+ int i, iface_num, numalts;
+ usb_error_t error;
+ int past_our_iface;
+
+ DPRINTFN(1, "number of total interfaces=%d\n", sc->sc_nifaces);
+ DPRINTFN(1, "number of VS interfaces=%d\n",
+ sc->sc_desc_vc_header.fix->bInCollection);
+
+ /* First pass: find VS_INPUT_HEADER */
+ past_our_iface = 0;
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ id = (struct usb_interface_descriptor *)desc;
+ if (id->bInterfaceNumber == sc->sc_iface_index) {
+ past_our_iface = 1;
+ continue;
+ }
+ }
+ if (desc->bDescriptorType == UDESC_IFACE_ASSOC &&
+ past_our_iface)
+ break;
+ if (!past_our_iface)
+ continue;
+ if (desc->bDescriptorType != UDESC_CS_INTERFACE)
+ continue;
+
+ switch (desc->bDescriptorSubtype) {
+ case UDESCSUB_VS_INPUT_HEADER:
+ if (!uvideo_desc_len(desc, 13, 3, 0, 12))
+ break;
+ error = uvideo_vs_parse_desc_input_header(sc, desc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ break;
+ }
+ }
+
+ /* Parse video stream format descriptors */
+ error = uvideo_vs_parse_desc_format(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+
+ /* Parse video stream frame descriptors */
+ error = uvideo_vs_parse_desc_frame(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+
+ /* Parse interface collection (alternates for each VS interface) */
+ for (i = 0; i < sc->sc_desc_vc_header.fix->bInCollection; i++) {
+ iface_num = sc->sc_desc_vc_header.baInterfaceNr[i];
+
+ iface = usbd_get_iface(sc->sc_udev, iface_num);
+ if (iface == NULL) {
+ device_printf(sc->sc_dev,
+ "can't get VS interface %d!\n", iface_num);
+ return (USB_ERR_INVAL);
+ }
+
+ id = usbd_get_interface_descriptor(iface);
+ if (id == NULL) {
+ device_printf(sc->sc_dev,
+ "can't get VS iface descriptor %d!\n", iface_num);
+ return (USB_ERR_INVAL);
+ }
+
+ /* Claim this interface */
+ usbd_set_parent_iface(sc->sc_udev, iface_num,
+ sc->sc_iface_index);
+
+ /* Count alternates by iterating descriptors */
+ numalts = usbd_get_no_alts(cdesc, id);
+
+ DPRINTFN(1, "VS interface %d, bInterfaceNumber=0x%02x, "
+ "numalts=%d\n", i, id->bInterfaceNumber, numalts);
+
+ error = uvideo_vs_parse_desc_alt(sc, i, iface_num, numalts);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ }
+
+ /* For now always use the first video stream */
+ sc->sc_vs_cur = &sc->sc_vs_coll[0];
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_parse_desc_input_header(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_input_header_desc *d;
+
+ d = __DECONST(struct usb_video_input_header_desc *, desc);
+
+ if (d->bNumFormats == 0) {
+ device_printf(sc->sc_dev,
+ "no INPUT FORMAT descriptors found!\n");
+ return (USB_ERR_INVAL);
+ }
+
+ sc->sc_desc_vs_input_header.fix = d;
+ sc->sc_desc_vs_input_header.bmaControls = (uByte *)(d + 1);
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_parse_desc_format(struct uvideo_softc *sc)
+{
+ struct usb_config_descriptor *cdesc;
+ struct usb_descriptor *desc;
+ struct usb_interface_descriptor *id;
+ int past_our_iface;
+
+ DPRINTFN(1, "uvideo_vs_parse_desc_format\n");
+
+ cdesc = usbd_get_config_descriptor(sc->sc_udev);
+ if (cdesc == NULL)
+ return (USB_ERR_INVAL);
+
+ past_our_iface = 0;
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ id = (struct usb_interface_descriptor *)desc;
+ if (id->bInterfaceNumber == sc->sc_iface_index) {
+ past_our_iface = 1;
+ continue;
+ }
+ }
+ if (desc->bDescriptorType == UDESC_IFACE_ASSOC &&
+ past_our_iface)
+ break;
+ if (!past_our_iface)
+ continue;
+
+ if (desc->bDescriptorType != UDESC_CS_INTERFACE)
+ continue;
+
+ if (desc->bLength != UVIDEO_FORMAT_LEN(desc))
+ continue;
+
+ switch (desc->bDescriptorSubtype) {
+ case UDESCSUB_VS_COLORFORMAT:
+ uvideo_vs_parse_desc_colorformat(sc, desc);
+ break;
+ case UDESCSUB_VS_FORMAT_MJPEG:
+ uvideo_vs_parse_desc_format_mjpeg(sc, desc);
+ break;
+ case UDESCSUB_VS_FORMAT_UNCOMPRESSED:
+ uvideo_vs_parse_desc_format_uncompressed(sc, desc);
+ break;
+ case UDESCSUB_VS_FORMAT_FRAME_BASED:
+ uvideo_vs_parse_desc_format_frame_based(sc, desc);
+ break;
+ case UDESCSUB_VS_FORMAT_H264:
+ case UDESCSUB_VS_FORMAT_H264_SIMULCAST:
+ uvideo_vs_parse_desc_format_h264(sc, desc);
+ break;
+ }
+ }
+
+ sc->sc_fmtgrp_idx = 0;
+
+ if (sc->sc_fmtgrp_num == 0) {
+ device_printf(sc->sc_dev, "no format descriptors found!\n");
+ return (USB_ERR_INVAL);
+ }
+ DPRINTFN(1, "number of total format descriptors=%d\n",
+ sc->sc_fmtgrp_num);
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uvideo_vs_parse_desc_colorformat(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ int fmtidx;
+ struct usb_video_colorformat_desc *d;
+
+ d = __DECONST(struct usb_video_colorformat_desc *, desc);
+
+ fmtidx = sc->sc_fmtgrp_idx - 1;
+ if (fmtidx < 0 || sc->sc_fmtgrp[fmtidx].has_colorformat)
+ return;
+
+ if (d->bColorPrimaries < nitems(uvideo_color_primaries))
+ sc->sc_fmtgrp[fmtidx].colorspace =
+ uvideo_color_primaries[d->bColorPrimaries];
+ else
+ sc->sc_fmtgrp[fmtidx].colorspace = V4L2_COLORSPACE_SRGB;
+
+ if (d->bTransferCharacteristics < nitems(uvideo_xfer_characteristics))
+ sc->sc_fmtgrp[fmtidx].xfer_func =
+ uvideo_xfer_characteristics[d->bTransferCharacteristics];
+ else
+ sc->sc_fmtgrp[fmtidx].xfer_func = V4L2_XFER_FUNC_DEFAULT;
+
+ if (d->bMatrixCoefficients < nitems(uvideo_matrix_coefficients))
+ sc->sc_fmtgrp[fmtidx].ycbcr_enc =
+ uvideo_matrix_coefficients[d->bMatrixCoefficients];
+ else
+ sc->sc_fmtgrp[fmtidx].ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+
+ sc->sc_fmtgrp[fmtidx].has_colorformat = 1;
+}
+
+static void
+uvideo_vs_parse_desc_format_mjpeg(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_format_desc *d;
+
+ d = __DECONST(struct usb_video_format_desc *, desc);
+
+ if (d->bNumFrameDescriptors == 0) {
+ device_printf(sc->sc_dev,
+ "no MJPEG frame descriptors available!\n");
+ return;
+ }
+
+ if (sc->sc_fmtgrp_idx >= UVIDEO_MAX_FORMAT) {
+ device_printf(sc->sc_dev,
+ "too many format descriptors found!\n");
+ return;
+ }
+
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format = d;
+ if (d->u.mjpeg.bDefaultFrameIndex > d->bNumFrameDescriptors ||
+ d->u.mjpeg.bDefaultFrameIndex < 1)
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx = 1;
+ else
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx =
+ d->u.mjpeg.bDefaultFrameIndex;
+
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].pixelformat = V4L2_PIX_FMT_MJPEG;
+
+ if (sc->sc_fmtgrp_cur == NULL)
+ sc->sc_fmtgrp_cur = &sc->sc_fmtgrp[sc->sc_fmtgrp_idx];
+
+ sc->sc_fmtgrp_idx++;
+ sc->sc_fmtgrp_num++;
+}
+
+static void
+uvideo_vs_parse_desc_format_h264(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_format_desc *d;
+
+ d = __DECONST(struct usb_video_format_desc *, desc);
+
+ if (d->bNumFrameDescriptors == 0) {
+ device_printf(sc->sc_dev,
+ "no H264 frame descriptors available!\n");
+ return;
+ }
+
+ if (sc->sc_fmtgrp_idx >= UVIDEO_MAX_FORMAT) {
+ device_printf(sc->sc_dev,
+ "too many format descriptors found!\n");
+ return;
+ }
+
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format = d;
+ if (d->u.h264.bDefaultFrameIndex > d->bNumFrameDescriptors ||
+ d->u.h264.bDefaultFrameIndex < 1)
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx = 1;
+ else
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx =
+ d->u.h264.bDefaultFrameIndex;
+
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].pixelformat = V4L2_PIX_FMT_H264;
+
+ if (sc->sc_fmtgrp_cur == NULL)
+ sc->sc_fmtgrp_cur = &sc->sc_fmtgrp[sc->sc_fmtgrp_idx];
+
+ sc->sc_fmtgrp_idx++;
+ sc->sc_fmtgrp_num++;
+}
+
+static void
+uvideo_vs_parse_desc_format_frame_based(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_format_desc *d;
+ int i, j, nent;
+
+ d = __DECONST(struct usb_video_format_desc *, desc);
+
+ if (d->bNumFrameDescriptors == 0) {
+ device_printf(sc->sc_dev,
+ "no frame-based frame descriptors available!\n");
+ return;
+ }
+
+ if (sc->sc_fmtgrp_idx >= UVIDEO_MAX_FORMAT) {
+ device_printf(sc->sc_dev,
+ "too many format descriptors found!\n");
+ return;
+ }
+
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format = d;
+ if (d->u.fb.bDefaultFrameIndex > d->bNumFrameDescriptors ||
+ d->u.fb.bDefaultFrameIndex < 1)
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx = 1;
+ else
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx =
+ d->u.fb.bDefaultFrameIndex;
+
+ i = sc->sc_fmtgrp_idx;
+
+ /* Map GUID to pixel format */
+ nent = nitems(uvideo_map_fmts);
+ for (j = 0; j < nent; j++) {
+ if (!memcmp(sc->sc_fmtgrp[i].format->u.uc.guidFormat,
+ uvideo_map_fmts[j].guidFormat, 16)) {
+ sc->sc_fmtgrp[i].pixelformat =
+ uvideo_map_fmts[j].pixelformat;
+ break;
+ }
+ }
+ if (j == nent)
+ memcpy(&sc->sc_fmtgrp[i].pixelformat,
+ sc->sc_fmtgrp[i].format->u.uc.guidFormat,
+ sizeof(uint32_t));
+
+ if (sc->sc_fmtgrp_cur == NULL)
+ sc->sc_fmtgrp_cur = &sc->sc_fmtgrp[sc->sc_fmtgrp_idx];
+
+ sc->sc_fmtgrp_idx++;
+ sc->sc_fmtgrp_num++;
+}
+
+static void
+uvideo_vs_parse_desc_format_uncompressed(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_format_desc *d;
+ int i, j, nent;
+
+ d = __DECONST(struct usb_video_format_desc *, desc);
+
+ if (d->bNumFrameDescriptors == 0) {
+ device_printf(sc->sc_dev,
+ "no UNCOMPRESSED frame descriptors available!\n");
+ return;
+ }
+
+ if (sc->sc_fmtgrp_idx >= UVIDEO_MAX_FORMAT) {
+ device_printf(sc->sc_dev,
+ "too many format descriptors found!\n");
+ return;
+ }
+
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format = d;
+ if (d->u.uc.bDefaultFrameIndex > d->bNumFrameDescriptors ||
+ d->u.uc.bDefaultFrameIndex < 1)
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx = 1;
+ else
+ sc->sc_fmtgrp[sc->sc_fmtgrp_idx].format_dfidx =
+ d->u.uc.bDefaultFrameIndex;
+
+ i = sc->sc_fmtgrp_idx;
+
+ /* Map GUID to pixel format */
+ nent = nitems(uvideo_map_fmts);
+ for (j = 0; j < nent; j++) {
+ if (!memcmp(sc->sc_fmtgrp[i].format->u.uc.guidFormat,
+ uvideo_map_fmts[j].guidFormat, 16)) {
+ sc->sc_fmtgrp[i].pixelformat =
+ uvideo_map_fmts[j].pixelformat;
+ break;
+ }
+ }
+ if (j == nent)
+ memcpy(&sc->sc_fmtgrp[i].pixelformat,
+ sc->sc_fmtgrp[i].format->u.uc.guidFormat,
+ sizeof(uint32_t));
+
+ if (sc->sc_fmtgrp_cur == NULL)
+ sc->sc_fmtgrp_cur = &sc->sc_fmtgrp[sc->sc_fmtgrp_idx];
+
+ sc->sc_fmtgrp_idx++;
+ sc->sc_fmtgrp_num++;
+}
+
+static usb_error_t
+uvideo_vs_parse_desc_frame(struct uvideo_softc *sc)
+{
+ struct usb_config_descriptor *cdesc;
+ struct usb_descriptor *desc;
+ struct usb_interface_descriptor *id;
+ usb_error_t error;
+ int past_our_iface;
+
+ DPRINTFN(1, "uvideo_vs_parse_desc_frame\n");
+
+ cdesc = usbd_get_config_descriptor(sc->sc_udev);
+ if (cdesc == NULL)
+ return (USB_ERR_INVAL);
+
+ past_our_iface = 0;
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ id = (struct usb_interface_descriptor *)desc;
+ if (id->bInterfaceNumber == sc->sc_iface_index) {
+ past_our_iface = 1;
+ continue;
+ }
+ }
+ if (desc->bDescriptorType == UDESC_IFACE_ASSOC &&
+ past_our_iface)
+ break;
+ if (!past_our_iface)
+ continue;
+
+ if (desc->bDescriptorType == UDESC_CS_INTERFACE &&
+ desc->bLength > UVIDEO_FRAME_MIN_LEN(desc) &&
+ (desc->bDescriptorSubtype == UDESCSUB_VS_FRAME_MJPEG ||
+ desc->bDescriptorSubtype ==
+ UDESCSUB_VS_FRAME_UNCOMPRESSED)) {
+ error = uvideo_vs_parse_desc_frame_buffer_size(sc,
+ desc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ }
+ if (desc->bDescriptorType == UDESC_CS_INTERFACE &&
+ desc->bLength > UVIDEO_FRAME_MIN_LEN(desc) &&
+ (desc->bDescriptorSubtype == UDESCSUB_VS_FRAME_H264 ||
+ desc->bDescriptorSubtype ==
+ UDESCSUB_VS_FRAME_FRAME_BASED)) {
+ error = uvideo_vs_parse_desc_frame_max_rate(sc, desc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ }
+ }
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_parse_desc_frame_buffer_size(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_frame_desc *fd =
+ __DECONST(struct usb_video_frame_desc *, desc);
+ int fmtidx, frame_num;
+ uint32_t fbuf_size;
+
+ fmtidx = sc->sc_fmtgrp_idx;
+ frame_num = sc->sc_fmtgrp[fmtidx].frame_num;
+ if (frame_num >= UVIDEO_MAX_FRAME) {
+ device_printf(sc->sc_dev,
+ "too many %s frame descriptors found!\n",
+ desc->bDescriptorSubtype == UDESCSUB_VS_FRAME_MJPEG ?
+ "MJPEG" : "UNCOMPRESSED");
+ return (USB_ERR_INVAL);
+ }
+ sc->sc_fmtgrp[fmtidx].frame[frame_num] = fd;
+
+ if (sc->sc_fmtgrp[fmtidx].frame_cur == NULL ||
+ sc->sc_fmtgrp[fmtidx].format_dfidx == fd->bFrameIndex)
+ sc->sc_fmtgrp[fmtidx].frame_cur = fd;
+
+ /*
+ * For uncompressed formats, compute the frame buffer size from
+ * width * height * bpp since dwMaxVideoFrameBufferSize may be wrong.
+ */
+ if (desc->bDescriptorSubtype == UDESCSUB_VS_FRAME_UNCOMPRESSED) {
+ fbuf_size = UGETW(fd->u.uc.wWidth) *
+ UGETW(fd->u.uc.wHeight) *
+ sc->sc_fmtgrp[fmtidx].format->u.uc.bBitsPerPixel / NBBY;
+ } else
+ fbuf_size = UGETDW(fd->u.uc.dwMaxVideoFrameBufferSize);
+
+ if (fbuf_size > sc->sc_max_fbuf_size)
+ sc->sc_max_fbuf_size = fbuf_size;
+
+ if (++sc->sc_fmtgrp[fmtidx].frame_num ==
+ sc->sc_fmtgrp[fmtidx].format->bNumFrameDescriptors)
+ sc->sc_fmtgrp_idx++;
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_parse_desc_frame_max_rate(struct uvideo_softc *sc,
+ const struct usb_descriptor *desc)
+{
+ struct usb_video_frame_desc *fd =
+ __DECONST(struct usb_video_frame_desc *, desc);
+ uint8_t *p;
+ int i, fmtidx, frame_num, length, nivals;
+ uint32_t fbuf_size, frame_ival, next_frame_ival;
+
+ fmtidx = sc->sc_fmtgrp_idx;
+ frame_num = sc->sc_fmtgrp[fmtidx].frame_num;
+ if (frame_num >= UVIDEO_MAX_FRAME) {
+ device_printf(sc->sc_dev,
+ "too many %s frame descriptors found!\n",
+ desc->bDescriptorSubtype == UDESCSUB_VS_FRAME_H264 ?
+ "H264" : "FRAME BASED");
+ return (USB_ERR_INVAL);
+ }
+ sc->sc_fmtgrp[fmtidx].frame[frame_num] = fd;
+
+ if (sc->sc_fmtgrp[fmtidx].frame_cur == NULL ||
+ sc->sc_fmtgrp[fmtidx].format_dfidx == fd->bFrameIndex)
+ sc->sc_fmtgrp[fmtidx].frame_cur = fd;
+
+ /*
+ * Frame Based and H264 frames don't have dwMaxVideoFrameBufferSize;
+ * compute required buffer from dwMaxBitRate and dwFrameInterval.
+ */
+ frame_ival = UGETDW(fd->u.h264.dwDefaultFrameInterval);
+
+ p = __DECONST(uint8_t *, desc) + UVIDEO_FRAME_MIN_LEN(fd);
+ length = fd->bLength - UVIDEO_FRAME_MIN_LEN(fd);
+
+ nivals = UVIDEO_FRAME_NUM_INTERVALS(fd);
+
+ for (i = 0; i < nivals; i++) {
+ if (length <= 0)
+ break;
+ next_frame_ival = UGETDW(p);
+ if (next_frame_ival > frame_ival)
+ frame_ival = next_frame_ival;
+ p += sizeof(uDWord);
+ length -= sizeof(uDWord);
+ }
+
+ fbuf_size = UGETDW(UVIDEO_FRAME_FIELD(fd, dwMaxBitRate)) * frame_ival;
+ fbuf_size /= 8 * 10000000;
+
+ if (fbuf_size > sc->sc_max_fbuf_size)
+ sc->sc_max_fbuf_size = fbuf_size;
+
+ if (++sc->sc_fmtgrp[fmtidx].frame_num ==
+ sc->sc_fmtgrp[fmtidx].format->bNumFrameDescriptors)
+ sc->sc_fmtgrp_idx++;
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_parse_desc_alt(struct uvideo_softc *sc, int vs_nr, int iface,
+ int numalts)
+{
+ struct uvideo_vs_iface *vs;
+ struct usb_config_descriptor *cdesc;
+ struct usb_descriptor *desc;
+ struct usb_interface_descriptor *id;
+ struct usb_endpoint_descriptor *ed;
+ uint8_t ep_dir, ep_type;
+ int bulk_endpoint;
+ uint32_t psize;
+ int past_our_iface;
+
+ vs = &sc->sc_vs_coll[vs_nr];
+
+ cdesc = usbd_get_config_descriptor(sc->sc_udev);
+ if (cdesc == NULL)
+ return (USB_ERR_INVAL);
+
+ vs->bulk_endpoint = 1;
+ past_our_iface = 0;
+
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ id = (struct usb_interface_descriptor *)desc;
+ if (id->bInterfaceNumber == sc->sc_iface_index) {
+ past_our_iface = 1;
+ continue;
+ }
+ }
+ if (desc->bDescriptorType == UDESC_IFACE_ASSOC &&
+ past_our_iface)
+ break;
+ if (!past_our_iface)
+ continue;
+
+ /* Find video stream interface */
+ if (desc->bDescriptorType != UDESC_INTERFACE)
+ continue;
+ id = (struct usb_interface_descriptor *)(uint8_t *)desc;
+ if (id->bInterfaceNumber != iface)
+ continue;
+
+ DPRINTFN(1, "bAlternateSetting=0x%02x\n",
+ id->bAlternateSetting);
+ if (id->bNumEndpoints == 0)
+ continue;
+
+ /* Jump to the endpoint descriptor */
+ while ((desc = usb_desc_foreach(cdesc, desc)) != NULL) {
+ if (desc->bDescriptorType == UDESC_ENDPOINT)
+ break;
+ }
+ if (desc == NULL)
+ break;
+ ed = (struct usb_endpoint_descriptor *)(uint8_t *)desc;
+
+ /* Locate endpoint type */
+ ep_dir = UE_GET_DIR(ed->bEndpointAddress);
+ ep_type = UE_GET_XFERTYPE(ed->bmAttributes);
+ if (ep_dir == UE_DIR_IN && ep_type == UE_ISOCHRONOUS)
+ bulk_endpoint = 0;
+ else if (ep_dir == UE_DIR_IN && ep_type == UE_BULK)
+ bulk_endpoint = 1;
+ else
+ continue;
+
+ if (bulk_endpoint && !vs->bulk_endpoint)
+ continue;
+
+ psize = UGETW(ed->wMaxPacketSize);
+ psize = UE_GET_SIZE(psize) * (1 + UE_GET_TRANS(psize));
+
+ /* Save endpoint with largest bandwidth */
+ if (psize > vs->psize) {
+ vs->endpoint = ed->bEndpointAddress;
+ vs->numalts = numalts;
+ vs->curalt = id->bAlternateSetting;
+ vs->psize = psize;
+ vs->iface_index = iface;
+ vs->bulk_endpoint = bulk_endpoint;
+ }
+ }
+
+ /* Check if we found a valid alternate interface */
+ if (vs->psize == 0) {
+ device_printf(sc->sc_dev,
+ "no valid alternate interface found!\n");
+ return (USB_ERR_INVAL);
+ }
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+/*
+ * Validate a variable-length descriptor.
+ */
+static int
+uvideo_desc_len(const struct usb_descriptor *desc,
+ int size_fix, int off_num_elements, int size_element, int off_size_element)
+{
+ uint8_t *buf;
+ int size_elements, size_total;
+
+ if (desc->bLength < size_fix)
+ return (0);
+
+ buf = __DECONST(uint8_t *, desc);
+
+ if (size_element == 0)
+ size_element = buf[off_size_element];
+
+ size_elements = buf[off_num_elements] * size_element;
+ size_total = size_fix + size_elements;
+
+ if (desc->bLength == size_total && size_elements != 0)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Find the best matching resolution for a given format group.
+ */
+static void
+uvideo_find_res(struct uvideo_softc *sc, int idx, int width, int height,
+ struct uvideo_res *r)
+{
+ int i, w, h, diff, diff_best, size_want, size_is;
+ struct usb_video_frame_desc *frame;
+
+ size_want = width * height;
+
+ for (i = 0; i < sc->sc_fmtgrp[idx].frame_num; i++) {
+ frame = sc->sc_fmtgrp[idx].frame[i];
+ w = UGETW(UVIDEO_FRAME_FIELD(frame, wWidth));
+ h = UGETW(UVIDEO_FRAME_FIELD(frame, wHeight));
+ size_is = w * h;
+ if (size_is > size_want)
+ diff = size_is - size_want;
+ else
+ diff = size_want - size_is;
+ if (i == 0)
+ diff_best = diff;
+ if (diff <= diff_best) {
+ diff_best = diff;
+ r->width = w;
+ r->height = h;
+ r->fidx = i;
+ }
+ }
+}
+
+/* ---------------------------------------------------------------- */
+/* UVC Protocol (Negotiation, Probe/Commit) */
+/* ---------------------------------------------------------------- */
+
+static usb_error_t
+uvideo_vs_negotiation(struct uvideo_softc *sc, int commit)
+{
+ struct usb_video_probe_commit *pc;
+ struct uvideo_format_group *fmtgrp;
+ struct usb_video_header_desc *hd;
+ struct usb_video_frame_desc *frame;
+ uint8_t *p, *cur;
+ uint8_t probe_data[48];
+ uint32_t frame_ival, nivals, min, max, step, diff;
+ usb_error_t error;
+ int i, ival_bytes, changed = 0;
+ size_t len;
+
+ pc = (struct usb_video_probe_commit *)probe_data;
+
+ fmtgrp = sc->sc_fmtgrp_cur;
+
+ if (fmtgrp->frame_num == 0) {
+ device_printf(sc->sc_dev,
+ "negotiation: no frame descriptors found!\n");
+ return (USB_ERR_INVAL);
+ }
+
+ /* Set probe */
+ bzero(probe_data, sizeof(probe_data));
+ USETW(pc->bmHint, 0x1);
+ pc->bFormatIndex = fmtgrp->format->bFormatIndex;
+ pc->bFrameIndex = fmtgrp->frame_cur->bFrameIndex;
+
+ frame = fmtgrp->frame_cur;
+ frame_ival = UGETDW(UVIDEO_FRAME_FIELD(frame, dwDefaultFrameInterval));
+ if (sc->sc_frame_rate != 0) {
+ frame_ival = 10000000 / sc->sc_frame_rate;
+ /* Find closest matching interval */
+ len = UVIDEO_FRAME_MIN_LEN(frame);
+ nivals = UVIDEO_FRAME_NUM_INTERVALS(frame);
+ p = (uint8_t *)fmtgrp->frame_cur;
+ p += len;
+ ival_bytes = frame->bLength - len;
+ if (!nivals && (ival_bytes >= (int)sizeof(uDWord) * 3)) {
+ /* continuous */
+ min = UGETDW(p);
+ p += sizeof(uDWord);
+ max = UGETDW(p);
+ p += sizeof(uDWord);
+ step = UGETDW(p);
+ if (frame_ival <= min)
+ frame_ival = min;
+ else if (frame_ival >= max)
+ frame_ival = max;
+ else {
+ for (i = min;
+ i + step / 2 < frame_ival;
+ i += step)
+ ;
+ frame_ival = i;
+ }
+ } else if (nivals > 0 &&
+ ival_bytes >= (int)sizeof(uDWord)) {
+ /* discrete */
+ cur = p;
+ min = UINT_MAX;
+ for (i = 0; i < (int)nivals; i++) {
+ if (ival_bytes < (int)sizeof(uDWord))
+ break;
+ diff = abs((int)UGETDW(p) -
+ (int)frame_ival);
+ if (diff < min) {
+ min = diff;
+ cur = p;
+ if (diff == 0)
+ break;
+ }
+ p += sizeof(uDWord);
+ ival_bytes -= sizeof(uDWord);
+ }
+ frame_ival = UGETDW(cur);
+ }
+ }
+ USETDW(pc->dwFrameInterval, frame_ival);
+ error = uvideo_vs_set_probe(sc, probe_data);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+
+ /* Get probe */
+ bzero(probe_data, sizeof(probe_data));
+ error = uvideo_vs_get_probe(sc, probe_data, GET_CUR);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+
+ /* Check that the format/frame indexes match */
+ if (pc->bFormatIndex != fmtgrp->format->bFormatIndex) {
+ changed++;
+ DPRINTFN(1, "wanted format 0x%x, got 0x%x\n",
+ fmtgrp->format->bFormatIndex, pc->bFormatIndex);
+ for (i = 0; i < sc->sc_fmtgrp_num; i++) {
+ if (sc->sc_fmtgrp[i].format->bFormatIndex ==
+ pc->bFormatIndex) {
+ fmtgrp = &sc->sc_fmtgrp[i];
+ break;
+ }
+ }
+ if (i == sc->sc_fmtgrp_num) {
+ DPRINTFN(1, "invalid format index 0x%x\n",
+ pc->bFormatIndex);
+ return (USB_ERR_INVAL);
+ }
+ }
+ if (pc->bFrameIndex != fmtgrp->frame_cur->bFrameIndex) {
+ changed++;
+ DPRINTFN(1, "wanted frame 0x%x, got 0x%x\n",
+ fmtgrp->frame_cur->bFrameIndex, pc->bFrameIndex);
+ for (i = 0; i < fmtgrp->frame_num; i++) {
+ if (fmtgrp->frame[i]->bFrameIndex ==
+ pc->bFrameIndex) {
+ frame = fmtgrp->frame[i];
+ break;
+ }
+ }
+ if (i == fmtgrp->frame_num) {
+ DPRINTFN(1, "invalid frame index 0x%x\n",
+ pc->bFrameIndex);
+ return (USB_ERR_INVAL);
+ }
+ } else
+ frame = fmtgrp->frame_cur;
+
+ /* Fix uncompressed frame sizes */
+ if (frame->bDescriptorSubtype == UDESCSUB_VS_FRAME_UNCOMPRESSED) {
+ USETDW(pc->dwMaxVideoFrameSize,
+ UGETW(frame->u.uc.wWidth) *
+ UGETW(frame->u.uc.wHeight) *
+ fmtgrp->format->u.uc.bBitsPerPixel / NBBY);
+ } else {
+ hd = sc->sc_desc_vc_header.fix;
+ if (UGETDW(pc->dwMaxVideoFrameSize) == 0 &&
+ UGETW(hd->bcdUVC) < 0x0110) {
+ USETDW(pc->dwMaxVideoFrameSize,
+ UGETDW(frame->u.uc.dwMaxVideoFrameBufferSize));
+ }
+ }
+
+ /* Commit */
+ if (commit) {
+ if (changed > 0)
+ return (USB_ERR_INVAL);
+ error = uvideo_vs_set_commit(sc, probe_data);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ }
+
+ /* Save a copy of probe commit */
+ bcopy(pc, &sc->sc_desc_probe, sizeof(sc->sc_desc_probe));
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_set_probe(struct uvideo_softc *sc, uint8_t *probe_data)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+ uint16_t tmp;
+
+ req.bmRequestType = UVIDEO_SET_IF;
+ req.bRequest = SET_CUR;
+ tmp = VS_PROBE_CONTROL;
+ tmp = tmp << 8;
+ USETW(req.wValue, tmp);
+ USETW(req.wIndex, sc->sc_vs_cur->iface_index);
+ USETW(req.wLength, sc->sc_max_ctrl_size);
+
+ error = usbd_do_request(sc->sc_udev, NULL, &req, probe_data);
+ if (error) {
+ device_printf(sc->sc_dev, "could not SET probe: %s\n",
+ usbd_errstr(error));
+ return (USB_ERR_INVAL);
+ }
+
+ DPRINTFN(1, "SET probe OK\n");
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_get_probe(struct uvideo_softc *sc, uint8_t *probe_data,
+ uint8_t request)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+ uint16_t tmp, actlen;
+
+ req.bmRequestType = UVIDEO_GET_IF;
+ req.bRequest = request;
+ tmp = VS_PROBE_CONTROL;
+ tmp = tmp << 8;
+ USETW(req.wValue, tmp);
+ USETW(req.wIndex, sc->sc_vs_cur->iface_index);
+ USETW(req.wLength, sc->sc_max_ctrl_size);
+
+ error = usbd_do_request_flags(sc->sc_udev, NULL, &req,
+ probe_data, USB_SHORT_XFER_OK, &actlen, 5000);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(sc->sc_dev, "could not GET probe: %s\n",
+ usbd_errstr(error));
+ return (USB_ERR_INVAL);
+ }
+
+ /* Zero unused portion */
+ if (actlen < sizeof(struct usb_video_probe_commit))
+ bzero(probe_data + actlen,
+ sizeof(struct usb_video_probe_commit) - actlen);
+
+ DPRINTFN(1, "GET probe OK, length=%d\n", actlen);
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uvideo_vs_set_commit(struct uvideo_softc *sc, uint8_t *probe_data)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+ uint16_t tmp;
+
+ req.bmRequestType = UVIDEO_SET_IF;
+ req.bRequest = SET_CUR;
+ tmp = VS_COMMIT_CONTROL;
+ tmp = tmp << 8;
+ USETW(req.wValue, tmp);
+ USETW(req.wIndex, sc->sc_vs_cur->iface_index);
+ USETW(req.wLength, sc->sc_max_ctrl_size);
+
+ error = usbd_do_request(sc->sc_udev, NULL, &req, probe_data);
+ if (error) {
+ device_printf(sc->sc_dev, "could not SET commit: %s\n",
+ usbd_errstr(error));
+ return (USB_ERR_INVAL);
+ }
+
+ DPRINTFN(1, "SET commit OK\n");
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+/* ---------------------------------------------------------------- */
+/* Stream Management */
+/* ---------------------------------------------------------------- */
+
+static usb_error_t
+uvideo_vs_alloc_frame(struct uvideo_softc *sc)
+{
+ struct uvideo_frame_buffer *fb = &sc->sc_frame_buffer;
+
+ fb->buf_size = UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize);
+
+ if (sc->sc_max_fbuf_size < fb->buf_size && sc->sc_mmap_flag == 0) {
+ device_printf(sc->sc_dev,
+ "software video buffer too small!\n");
+ return (USB_ERR_NOMEM);
+ }
+
+ fb->buf = malloc(fb->buf_size, M_USBDEV, M_WAITOK | M_ZERO);
+ if (fb->buf == NULL) {
+ device_printf(sc->sc_dev,
+ "can't allocate frame buffer!\n");
+ return (USB_ERR_NOMEM);
+ }
+
+ DPRINTFN(1, "allocated %d bytes frame buffer\n", fb->buf_size);
+
+ fb->sample = 0;
+ fb->fid = 0;
+ fb->offset = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ fb->fmt_flags = sc->sc_fmtgrp_cur->frame_cur->bDescriptorSubtype ==
+ UDESCSUB_VS_FRAME_UNCOMPRESSED ? 0 : V4L2_FMT_FLAG_COMPRESSED;
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uvideo_vs_free_frame(struct uvideo_softc *sc)
+{
+ struct uvideo_frame_buffer *fb = &sc->sc_frame_buffer;
+
+ if (fb->buf != NULL) {
+ free(fb->buf, M_USBDEV);
+ fb->buf = NULL;
+ }
+
+ if (sc->sc_mmap_buffer != NULL) {
+ contigfree(sc->sc_mmap_buffer, sc->sc_mmap_buffer_size,
+ M_USBDEV);
+ sc->sc_mmap_buffer = NULL;
+ sc->sc_mmap_buffer_size = 0;
+ }
+
+ while (!STAILQ_EMPTY(&sc->sc_mmap_q))
+ STAILQ_REMOVE_HEAD(&sc->sc_mmap_q, q_frames);
+
+ sc->sc_mmap_count = 0;
+}
+
+static usb_error_t
+uvideo_vs_open(struct uvideo_softc *sc)
+{
+ usb_error_t error;
+ uint32_t dwMaxVideoFrameSize;
+ uint8_t iface_index;
+
+ DPRINTFN(1, "uvideo_vs_open\n");
+
+ if (sc->sc_negotiated_flag == 0) {
+ error = uvideo_vs_negotiation(sc, 1);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ }
+
+ /* For bulk endpoints, alt 0 is always used */
+ if (!sc->sc_vs_cur->bulk_endpoint) {
+ /*
+ * Set alternate interface to the one matching
+ * dwMaxPayloadTransferSize.
+ */
+ error = usbd_set_alt_interface_index(sc->sc_udev,
+ sc->sc_vs_cur->iface_index, sc->sc_vs_cur->curalt);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(sc->sc_dev,
+ "could not set alt interface %d!\n",
+ sc->sc_vs_cur->curalt);
+ return (error);
+ }
+ }
+
+ /*
+ * Setup USB transfers. FreeBSD uses declarative config + callback.
+ */
+ iface_index = sc->sc_vs_cur->iface_index;
+ if (sc->sc_vs_cur->bulk_endpoint) {
+ error = usbd_transfer_setup(sc->sc_udev, &iface_index,
+ sc->sc_xfer, uvideo_bulk_config, 1, sc, &sc->sc_mtx);
+ } else {
+ error = usbd_transfer_setup(sc->sc_udev, &iface_index,
+ sc->sc_xfer, uvideo_isoc_config, UVIDEO_IXFERS, sc,
+ &sc->sc_mtx);
+ }
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(sc->sc_dev, "transfer setup failed: %s\n",
+ usbd_errstr(error));
+ return (error);
+ }
+
+ /* Calculate optimal isoc transfer size */
+ dwMaxVideoFrameSize = UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize);
+ sc->sc_nframes = (dwMaxVideoFrameSize + sc->sc_vs_cur->psize - 1) /
+ sc->sc_vs_cur->psize;
+ if (sc->sc_nframes > UVIDEO_NFRAMES_MAX)
+ sc->sc_nframes = UVIDEO_NFRAMES_MAX;
+
+ /* Pre-allocate scratch buffer for bulk USB callbacks */
+ if (sc->sc_vs_cur->bulk_endpoint) {
+ sc->sc_tmpbuf_size = 65536;
+ sc->sc_tmpbuf = malloc(sc->sc_tmpbuf_size, M_USBDEV, M_WAITOK);
+ }
+
+ device_printf(sc->sc_dev, "stream open: nframes=%d, psize=%u, "
+ "maxVideoFrameSize=%u, maxPayloadSize=%u, alt=%d, %s\n",
+ sc->sc_nframes, sc->sc_vs_cur->psize,
+ UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize),
+ UGETDW(sc->sc_desc_probe.dwMaxPayloadTransferSize),
+ sc->sc_vs_cur->curalt,
+ sc->sc_vs_cur->bulk_endpoint ? "bulk" : "isoc");
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uvideo_vs_close(struct uvideo_softc *sc)
+{
+
+ DPRINTFN(1, "uvideo_vs_close\n");
+
+ /* Stop and drain all transfers */
+ usbd_transfer_unsetup(sc->sc_xfer, UVIDEO_N_XFER);
+
+ if (sc->sc_tmpbuf != NULL) {
+ free(sc->sc_tmpbuf, M_USBDEV);
+ sc->sc_tmpbuf = NULL;
+ sc->sc_tmpbuf_size = 0;
+ }
+
+ if (sc->sc_dying)
+ return;
+
+ if (!sc->sc_vs_cur->bulk_endpoint) {
+ /* Switch back to alt 0 (turns off camera LED) */
+ usbd_set_alt_interface_index(sc->sc_udev,
+ sc->sc_vs_cur->iface_index, 0);
+ }
+}
+
+static usb_error_t
+uvideo_vs_init(struct uvideo_softc *sc)
+{
+ usb_error_t error;
+
+ error = uvideo_vs_open(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (USB_ERR_INVAL);
+
+ error = uvideo_vs_alloc_frame(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (USB_ERR_INVAL);
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+/* ---------------------------------------------------------------- */
+/* Transfer Callbacks */
+/* ---------------------------------------------------------------- */
+
+/*
+ * Zero-copy isochronous decode: read only the 2-byte UVC stream header
+ * from the USB page cache, then copy the payload directly into the
+ * destination frame buffer, skipping the intermediate staging buffer.
+ */
+static void
+uvideo_isoc_decode(struct uvideo_softc *sc, struct usb_page_cache *pc,
+ int offset, int len)
+{
+ struct uvideo_frame_buffer *fb = &sc->sc_frame_buffer;
+ uint8_t shdr[2];
+ uint8_t flags;
+ uint8_t *buf;
+ int hdrlen, payload_len;
+
+ if (len < UVIDEO_SH_MIN_LEN)
+ return;
+
+ /* Read only bLength and bFlags (2 bytes) */
+ usbd_copy_out(pc, offset, shdr, sizeof(shdr));
+
+ hdrlen = shdr[0];
+ flags = shdr[1];
+ if (hdrlen > len || hdrlen < UVIDEO_SH_MIN_LEN)
+ return;
+
+ if (fb->sample == 0) {
+ fb->sample = 1;
+ fb->fid = flags & UVIDEO_SH_FLAG_FID;
+ fb->offset = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ } else if (fb->fid != (flags & UVIDEO_SH_FLAG_FID)) {
+ DPRINTFN(1, "wrong FID, ignoring last frame\n");
+ fb->sample = 1;
+ fb->fid = flags & UVIDEO_SH_FLAG_FID;
+ fb->offset = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ }
+
+ if (flags & UVIDEO_SH_FLAG_ERR) {
+ DPRINTFN(1, "stream error!\n");
+ fb->error = 1;
+ }
+
+ /* Get destination buffer */
+ if (sc->sc_mmap_flag) {
+ if (!fb->mmap_q_full) {
+ buf = uvideo_mmap_getbuf(sc);
+ if (buf == NULL)
+ fb->mmap_q_full = 1;
+ }
+ } else
+ buf = fb->buf;
+
+ /* Copy payload directly from USB DMA into frame buffer */
+ payload_len = len - hdrlen;
+ if (payload_len > fb->buf_size - fb->offset) {
+ DPRINTFN(1, "frame too large, marked as error\n");
+ payload_len = fb->buf_size - fb->offset;
+ fb->error = 1;
+ }
+ if (!fb->mmap_q_full && payload_len > 0) {
+ usbd_copy_out(pc, offset + hdrlen,
+ buf + fb->offset, payload_len);
+ fb->offset += payload_len;
+ }
+
+ if (flags & UVIDEO_SH_FLAG_EOF) {
+ DPRINTFN(2, "EOF (frame size=%d bytes)\n", fb->offset);
+
+ if (fb->offset < fb->buf_size &&
+ !(fb->fmt_flags & V4L2_FMT_FLAG_COMPRESSED)) {
+ DPRINTFN(1, "frame too small, marked as error\n");
+ fb->error = 1;
+ }
+
+ if (sc->sc_mmap_flag) {
+ if (!fb->mmap_q_full)
+ uvideo_mmap_queue(sc, fb->offset, fb->error);
+ } else if (fb->error) {
+ DPRINTFN(1, "error frame, skipped\n");
+ } else {
+ uvideo_read_frame(sc, fb->buf, fb->offset);
+ }
+
+ fb->sample = 0;
+ fb->fid = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ }
+}
+
+static void
+uvideo_isoc_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvideo_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int nframes, i, offset, len;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ nframes = usbd_xfer_max_frames(xfer);
+ offset = 0;
+ for (i = 0; i < nframes; i++) {
+ len = usbd_xfer_frame_len(xfer, i);
+ if (len > 0)
+ uvideo_isoc_decode(sc, pc, offset, len);
+ offset += usbd_xfer_old_frame_length(xfer, i);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ nframes = usbd_xfer_max_frames(xfer);
+ usbd_xfer_set_frames(xfer, nframes);
+ for (i = 0; i < nframes; i++)
+ usbd_xfer_set_frame_len(xfer, i,
+ sc->sc_vs_cur->psize);
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+uvideo_bulk_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvideo_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen > 0 && actlen <= sc->sc_tmpbuf_size) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, sc->sc_tmpbuf, actlen);
+ sc->sc_decode_stream_header(sc, sc->sc_tmpbuf,
+ actlen);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0,
+ usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+/* ---------------------------------------------------------------- */
+/* Frame Assembly */
+/* ---------------------------------------------------------------- */
+
+static void
+uvideo_vs_decode_stream_header(struct uvideo_softc *sc, uint8_t *frame,
+ int frame_size)
+{
+ struct uvideo_frame_buffer *fb = &sc->sc_frame_buffer;
+ struct usb_video_stream_header *sh;
+ int sample_len;
+ uint8_t *buf;
+
+ if (frame_size < UVIDEO_SH_MIN_LEN)
+ return;
+
+ sh = (struct usb_video_stream_header *)frame;
+
+ if (sh->bLength > frame_size || sh->bLength < UVIDEO_SH_MIN_LEN)
+ return;
+
+ if (fb->sample == 0) {
+ /* First sample for a frame */
+ fb->sample = 1;
+ fb->fid = sh->bFlags & UVIDEO_SH_FLAG_FID;
+ fb->offset = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ } else {
+ /* Continuation sample; check FID consistency */
+ if (fb->fid != (sh->bFlags & UVIDEO_SH_FLAG_FID)) {
+ DPRINTFN(1, "wrong FID, ignoring last frame\n");
+ fb->sample = 1;
+ fb->fid = sh->bFlags & UVIDEO_SH_FLAG_FID;
+ fb->offset = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ }
+ }
+
+ if (sh->bFlags & UVIDEO_SH_FLAG_ERR) {
+ DPRINTFN(1, "stream error!\n");
+ fb->error = 1;
+ }
+
+ if (sc->sc_mmap_flag) {
+ if (!fb->mmap_q_full) {
+ buf = uvideo_mmap_getbuf(sc);
+ if (buf == NULL)
+ fb->mmap_q_full = 1;
+ }
+ } else
+ buf = sc->sc_frame_buffer.buf;
+
+ /* Save sample data */
+ sample_len = frame_size - sh->bLength;
+ if (sample_len > fb->buf_size - fb->offset) {
+ DPRINTFN(1, "frame too large, marked as error\n");
+ sample_len = fb->buf_size - fb->offset;
+ fb->error = 1;
+ }
+ if (!fb->mmap_q_full && sample_len > 0) {
+ bcopy(frame + sh->bLength, buf + fb->offset, sample_len);
+ fb->offset += sample_len;
+ }
+
+ if (sh->bFlags & UVIDEO_SH_FLAG_EOF) {
+ /* Got a full frame */
+ DPRINTFN(2, "EOF (frame size=%d bytes)\n", fb->offset);
+
+ if (fb->offset < fb->buf_size &&
+ !(fb->fmt_flags & V4L2_FMT_FLAG_COMPRESSED)) {
+ DPRINTFN(1, "frame too small, marked as error\n");
+ fb->error = 1;
+ }
+
+ if (sc->sc_mmap_flag) {
+ if (!fb->mmap_q_full)
+ uvideo_mmap_queue(sc, fb->offset, fb->error);
+ } else if (fb->error) {
+ DPRINTFN(1, "error frame, skipped\n");
+ } else {
+ uvideo_read_frame(sc, fb->buf, fb->offset);
+ }
+
+ fb->sample = 0;
+ fb->fid = 0;
+ fb->error = 0;
+ fb->mmap_q_full = 0;
+ }
+}
+
+static uint8_t *
+uvideo_mmap_getbuf(struct uvideo_softc *sc)
+{
+ int i, idx;
+
+ /*
+ * Multiple frames per transfer / multiple transfers per frame.
+ */
+ if (sc->sc_mmap_cur != NULL)
+ return (sc->sc_mmap_cur->buf);
+
+ if (sc->sc_mmap_count == 0 || sc->sc_mmap_buffer == NULL)
+ return (NULL);
+
+ idx = sc->sc_mmap_buffer_idx;
+
+ /* Find a buffer which is queued and ready */
+ for (i = 0; i < sc->sc_mmap_count; i++) {
+ if (sc->sc_mmap[sc->sc_mmap_buffer_idx].v4l2_buf.flags &
+ V4L2_BUF_FLAG_QUEUED) {
+ idx = sc->sc_mmap_buffer_idx;
+ if (++sc->sc_mmap_buffer_idx == sc->sc_mmap_count)
+ sc->sc_mmap_buffer_idx = 0;
+ break;
+ }
+ if (++sc->sc_mmap_buffer_idx == sc->sc_mmap_count)
+ sc->sc_mmap_buffer_idx = 0;
+ }
+
+ if (i == sc->sc_mmap_count) {
+ DPRINTFN(1, "mmap queue is full!\n");
+ return (NULL);
+ }
+
+ sc->sc_mmap_cur = &sc->sc_mmap[idx];
+ return (sc->sc_mmap_cur->buf);
+}
+
+static void
+uvideo_mmap_queue(struct uvideo_softc *sc, int len, int err)
+{
+
+ if (sc->sc_mmap_cur == NULL)
+ return;
+
+ sc->sc_mmap_cur->v4l2_buf.bytesused = len;
+
+ getmicrouptime(&sc->sc_mmap_cur->v4l2_buf.timestamp);
+ sc->sc_mmap_cur->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TIMESTAMP_MASK;
+ sc->sc_mmap_cur->v4l2_buf.flags |= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ sc->sc_mmap_cur->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ sc->sc_mmap_cur->v4l2_buf.flags |= V4L2_BUF_FLAG_TSTAMP_SRC_EOF;
+ sc->sc_mmap_cur->v4l2_buf.flags &= ~V4L2_BUF_FLAG_TIMECODE;
+
+ sc->sc_mmap_cur->v4l2_buf.flags &= ~V4L2_BUF_FLAG_ERROR;
+ if (err)
+ sc->sc_mmap_cur->v4l2_buf.flags |= V4L2_BUF_FLAG_ERROR;
+
+ sc->sc_mmap_cur->v4l2_buf.flags |= V4L2_BUF_FLAG_DONE;
+ sc->sc_mmap_cur->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED;
+ STAILQ_INSERT_TAIL(&sc->sc_mmap_q, sc->sc_mmap_cur, q_frames);
+ sc->sc_mmap_cur = NULL;
+
+ DPRINTFN(2, "frame queued\n");
+
+ wakeup(sc);
+ selwakeup(&sc->sc_selinfo);
+}
+
+static void
+uvideo_read_frame(struct uvideo_softc *sc, uint8_t *buf, int len)
+{
+
+ /*
+ * In read mode, copy the frame into the upper-layer buffer
+ * so the USB callback can start assembling the next frame
+ * without racing with the cdev read.
+ */
+ if (sc->sc_fbuffer == NULL || len > sc->sc_fbufferlen)
+ return;
+
+ bcopy(buf, sc->sc_fbuffer, len);
+ sc->sc_fsize = len;
+ sc->sc_frames_ready++;
+
+ wakeup(sc);
+ selwakeup(&sc->sc_selinfo);
+}
+
+/* ---------------------------------------------------------------- */
+/* Character Device Operations */
+/* ---------------------------------------------------------------- */
+
+static int
+uvideo_cdev_open(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+ struct uvideo_softc *sc = dev->si_drv1;
+
+ if (sc == NULL || sc->sc_dying)
+ return (ENXIO);
+
+ if (sc->sc_vs_cur == NULL)
+ return (EIO);
+
+ mtx_lock(&sc->sc_mtx);
+ if (sc->sc_open == 0) {
+ /* First open: initialize state */
+ sc->sc_owner = td->td_proc;
+ sc->sc_mmap_flag = 0;
+ sc->sc_negotiated_flag = 0;
+ sc->sc_vidmode = VIDMODE_NONE;
+ sc->sc_frames_ready = 0;
+ sc->sc_priority = 1; /* V4L2_PRIORITY_DEFAULT */
+ }
+ sc->sc_open++;
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+uvideo_cdev_close(struct cdev *dev, int flags, int fmt, struct thread *td)
+{
+ struct uvideo_softc *sc = dev->si_drv1;
+
+ if (sc == NULL)
+ return (0);
+
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_open--;
+ if (sc->sc_open > 0) {
+ mtx_unlock(&sc->sc_mtx);
+ return (0);
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ /* Last close: stop streaming if active */
+ if (sc->sc_streaming) {
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_streaming = 0;
+ mtx_unlock(&sc->sc_mtx);
+ uvideo_vs_close(sc);
+ uvideo_vs_free_frame(sc);
+ }
+
+ if (sc->sc_fbuffer != NULL) {
+ free(sc->sc_fbuffer, M_USBDEV);
+ sc->sc_fbuffer = NULL;
+ sc->sc_fbufferlen = 0;
+ }
+
+ sc->sc_open = 0;
+ sc->sc_owner = NULL;
+ sc->sc_vidmode = VIDMODE_NONE;
+
+ return (0);
+}
+
+static int
+uvideo_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct uvideo_softc *sc = dev->si_drv1;
+ usb_error_t error;
+ int ret;
+
+ if (sc == NULL || sc->sc_dying)
+ return (ENXIO);
+
+ if (sc->sc_vs_cur == NULL)
+ return (EIO);
+
+ /* Start streaming in read mode if not already running */
+ if (sc->sc_vidmode == VIDMODE_NONE) {
+ sc->sc_mmap_flag = 0;
+ sc->sc_vidmode = VIDMODE_READ;
+
+ error = uvideo_vs_init(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ sc->sc_vidmode = VIDMODE_NONE;
+ return (EIO);
+ }
+
+ /* Allocate a separate read buffer for frame delivery */
+ sc->sc_fbufferlen = sc->sc_max_fbuf_size;
+ if (sc->sc_fbufferlen == 0)
+ sc->sc_fbufferlen =
+ UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize);
+ if (sc->sc_fbuffer == NULL) {
+ sc->sc_fbuffer = malloc(sc->sc_fbufferlen, M_USBDEV,
+ M_WAITOK | M_ZERO);
+ if (sc->sc_fbuffer == NULL) {
+ sc->sc_vidmode = VIDMODE_NONE;
+ return (ENOMEM);
+ }
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_streaming = 1;
+ if (sc->sc_vs_cur->bulk_endpoint)
+ usbd_transfer_start(sc->sc_xfer[0]);
+ else {
+ int i;
+ for (i = 0; i < UVIDEO_IXFERS; i++)
+ usbd_transfer_start(sc->sc_xfer[i]);
+ }
+ mtx_unlock(&sc->sc_mtx);
+ }
+
+ if (sc->sc_vidmode != VIDMODE_READ)
+ return (EBUSY);
+
+ /* Wait for a frame */
+ while (sc->sc_frames_ready == 0) {
+ if (ioflag & IO_NDELAY)
+ return (EWOULDBLOCK);
+ ret = tsleep(sc, PCATCH, "uvread", hz * 10);
+ if (ret != 0)
+ return (ret);
+ if (sc->sc_dying)
+ return (ENXIO);
+ }
+
+ sc->sc_frames_ready--;
+
+ if (sc->sc_fsize == 0)
+ return (0);
+
+ return (uiomove(sc->sc_fbuffer, MIN(uio->uio_resid, sc->sc_fsize),
+ uio));
+}
+
+static int
+uvideo_cdev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
+ struct thread *td)
+{
+ struct uvideo_softc *sc = dev->si_drv1;
+
+ if (sc == NULL || sc->sc_dying)
+ return (ENXIO);
+
+ DPRINTFN(2, "ioctl cmd=0x%08lx\n", cmd);
+
+ switch (cmd) {
+ case VIDIOC_QUERYCAP:
+ return (uvideo_querycap(sc, (struct v4l2_capability *)data));
+ case VIDIOC_ENUM_FMT:
+ return (uvideo_enum_fmt(sc, (struct v4l2_fmtdesc *)data));
+ case VIDIOC_ENUM_FRAMESIZES:
+ return (uvideo_enum_fsizes(sc,
+ (struct v4l2_frmsizeenum *)data));
+ case VIDIOC_ENUM_FRAMEINTERVALS:
+ return (uvideo_enum_fivals(sc,
+ (struct v4l2_frmivalenum *)data));
+ case VIDIOC_S_FMT:
+ return (uvideo_s_fmt(sc, (struct v4l2_format *)data));
+ case VIDIOC_G_FMT:
+ return (uvideo_g_fmt(sc, (struct v4l2_format *)data));
+ case VIDIOC_TRY_FMT:
+ return (uvideo_try_fmt(sc, (struct v4l2_format *)data));
+ case VIDIOC_S_PARM:
+ return (uvideo_s_parm(sc, (struct v4l2_streamparm *)data));
+ case VIDIOC_G_PARM:
+ return (uvideo_g_parm(sc, (struct v4l2_streamparm *)data));
+ case VIDIOC_ENUMINPUT:
+ return (uvideo_enum_input(sc, (struct v4l2_input *)data));
+ case VIDIOC_S_INPUT:
+ return (uvideo_s_input(sc, *(int *)data));
+ case VIDIOC_G_INPUT:
+ return (uvideo_g_input(sc, (int *)data));
+ case VIDIOC_REQBUFS:
+ return (uvideo_reqbufs(sc,
+ (struct v4l2_requestbuffers *)data));
+ case VIDIOC_QUERYBUF:
+ return (uvideo_querybuf(sc, (struct v4l2_buffer *)data));
+ case VIDIOC_QBUF:
+ return (uvideo_qbuf(sc, (struct v4l2_buffer *)data));
+ case VIDIOC_DQBUF:
+ return (uvideo_dqbuf(sc, (struct v4l2_buffer *)data));
+ case VIDIOC_STREAMON:
+ return (uvideo_streamon(sc, *(int *)data));
+ case VIDIOC_STREAMOFF:
+ return (uvideo_streamoff(sc, *(int *)data));
+ case VIDIOC_QUERYCTRL:
+ return (uvideo_queryctrl(sc,
+ (struct v4l2_queryctrl *)data));
+ case VIDIOC_G_CTRL:
+ return (uvideo_g_ctrl(sc, (struct v4l2_control *)data));
+ case VIDIOC_S_CTRL:
+ return (uvideo_s_ctrl(sc, (struct v4l2_control *)data));
+ case VIDIOC_G_PRIORITY:
+ *(uint32_t *)data = sc->sc_priority;
+ return (0);
+ case VIDIOC_S_PRIORITY:
+ sc->sc_priority = *(uint32_t *)data;
+ return (0);
+ default:
+ return (ENOTTY);
+ }
+}
+
+static int
+uvideo_cdev_poll(struct cdev *dev, int events, struct thread *td)
+{
+ struct uvideo_softc *sc = dev->si_drv1;
+ int revents = 0;
+
+ if (sc == NULL || sc->sc_dying)
+ return (POLLHUP);
+
+ if (events & (POLLIN | POLLRDNORM)) {
+ if (sc->sc_mmap_flag) {
+ if (!STAILQ_EMPTY(&sc->sc_mmap_q))
+ revents |= events & (POLLIN | POLLRDNORM);
+ } else {
+ if (sc->sc_frames_ready > 0)
+ revents |= events & (POLLIN | POLLRDNORM);
+ }
+ if (revents == 0)
+ selrecord(td, &sc->sc_selinfo);
+ }
+
+ return (revents);
+}
+
+static int
+uvideo_cdev_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr,
+ int nprot, vm_memattr_t *memattr)
+{
+ struct uvideo_softc *sc = dev->si_drv1;
+
+ if (sc == NULL || sc->sc_dying)
+ return (ENXIO);
+
+ if (offset >= sc->sc_mmap_buffer_size)
+ return (EINVAL);
+
+ if (sc->sc_mmap_buffer == NULL)
+ return (EINVAL);
+
+ if (!sc->sc_mmap_flag)
+ sc->sc_mmap_flag = 1;
+
+ *paddr = vtophys(sc->sc_mmap_buffer + offset);
+
+ return (0);
+}
+
+/* ---------------------------------------------------------------- */
+/* V4L2 Ioctl Handlers */
+/* ---------------------------------------------------------------- */
+
+static int
+uvideo_querycap(struct uvideo_softc *sc, struct v4l2_capability *caps)
+{
+
+ bzero(caps, sizeof(*caps));
+ strlcpy(caps->driver, "uvideo", sizeof(caps->driver));
+ strlcpy(caps->card, usb_get_product(sc->sc_udev),
+ sizeof(caps->card));
+ snprintf(caps->bus_info, sizeof(caps->bus_info), "usb-%s",
+ device_get_nameunit(sc->sc_dev));
+
+ caps->version = (5 << 16) | (0 << 8) | 0; /* 5.0.0 */
+ caps->device_caps = V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_STREAMING | V4L2_CAP_READWRITE |
+ V4L2_CAP_EXT_PIX_FORMAT;
+ caps->capabilities = caps->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return (0);
+}
+
+/*
+ * Map pixel format to canonical V4L2 description string.
+ * v4l2-compliance checks these names against an internal table.
+ */
+static const struct {
+ uint32_t pixfmt;
+ const char *name;
+ uint32_t flags;
+} uvideo_fmt_names[] = {
+ { V4L2_PIX_FMT_MJPEG, "Motion-JPEG", V4L2_FMT_FLAG_COMPRESSED },
+ { V4L2_PIX_FMT_YUYV, "YUYV 4:2:2", 0 },
+ { V4L2_PIX_FMT_YVU420, "Planar YVU 4:2:0", 0 },
+ { V4L2_PIX_FMT_YUV420, "Planar YUV 4:2:0", 0 },
+ { V4L2_PIX_FMT_GREY, "8-bit Greyscale", 0 },
+ { V4L2_PIX_FMT_RGB565, "16-bit RGB 5-6-5", 0 },
+ { V4L2_PIX_FMT_BGR24, "24-bit BGR 8-8-8", 0 },
+ { V4L2_PIX_FMT_XBGR32, "32-bit BGRX 8-8-8-8", 0 },
+ { V4L2_PIX_FMT_H264, "H.264", V4L2_FMT_FLAG_COMPRESSED },
+ { V4L2_PIX_FMT_HEVC, "HEVC", V4L2_FMT_FLAG_COMPRESSED },
+ { V4L2_PIX_FMT_SBGGR8, "8-bit Bayer BGBG/GRGR", 0 },
+ { V4L2_PIX_FMT_SGBRG8, "8-bit Bayer GBGB/RGRG", 0 },
+ { V4L2_PIX_FMT_SGRBG8, "8-bit Bayer GRGR/BGBG", 0 },
+ { V4L2_PIX_FMT_SRGGB8, "8-bit Bayer RGRG/GBGB", 0 },
+ { 0, NULL, 0 }
+};
+
+static int
+uvideo_enum_fmt(struct uvideo_softc *sc, struct v4l2_fmtdesc *fmtdesc)
+{
+ uint32_t idx, type, pixfmt, flags;
+ const char *name;
+ int i;
+
+ type = fmtdesc->type;
+ idx = fmtdesc->index;
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ if (idx >= (uint32_t)sc->sc_fmtgrp_num)
+ return (EINVAL);
+
+ pixfmt = sc->sc_fmtgrp[idx].pixelformat;
+ flags = 0;
+ name = "Unknown Format";
+
+ /* Look up canonical name and flags */
+ for (i = 0; uvideo_fmt_names[i].name != NULL; i++) {
+ if (uvideo_fmt_names[i].pixfmt == pixfmt) {
+ name = uvideo_fmt_names[i].name;
+ flags = uvideo_fmt_names[i].flags;
+ break;
+ }
+ }
+
+ /* Override flags for special descriptor subtypes */
+ switch (sc->sc_fmtgrp[idx].format->bDescriptorSubtype) {
+ case UDESCSUB_VS_FORMAT_MJPEG:
+ pixfmt = V4L2_PIX_FMT_MJPEG;
+ flags = V4L2_FMT_FLAG_COMPRESSED;
+ name = "Motion-JPEG";
+ break;
+ case UDESCSUB_VS_FORMAT_H264:
+ case UDESCSUB_VS_FORMAT_H264_SIMULCAST:
+ pixfmt = V4L2_PIX_FMT_H264;
+ flags = V4L2_FMT_FLAG_COMPRESSED;
+ name = "H.264";
+ break;
+ case UDESCSUB_VS_FORMAT_FRAME_BASED:
+ if (sc->sc_fmtgrp[idx].format->u.fb.bVariableSize)
+ flags = V4L2_FMT_FLAG_COMPRESSED;
+ break;
+ }
+
+ bzero(fmtdesc, sizeof(*fmtdesc));
+ fmtdesc->index = idx;
+ fmtdesc->type = type;
+ fmtdesc->flags = flags;
+ fmtdesc->pixelformat = pixfmt;
+ strlcpy(fmtdesc->description, name, sizeof(fmtdesc->description));
+
+ return (0);
+}
+
+static int
+uvideo_enum_fsizes(struct uvideo_softc *sc, struct v4l2_frmsizeenum *fsizes)
+{
+ int idx, found = 0;
+ uint32_t index, pixel_format;
+ struct usb_video_frame_desc *frame;
+
+ index = fsizes->index;
+ pixel_format = fsizes->pixel_format;
+
+ for (idx = 0; idx < sc->sc_fmtgrp_num; idx++) {
+ if (sc->sc_fmtgrp[idx].pixelformat == pixel_format) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0)
+ return (EINVAL);
+
+ if (index >= (uint32_t)sc->sc_fmtgrp[idx].frame_num)
+ return (EINVAL);
+
+ bzero(fsizes, sizeof(*fsizes));
+ fsizes->index = index;
+ fsizes->pixel_format = pixel_format;
+ fsizes->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ frame = sc->sc_fmtgrp[idx].frame[index];
+ fsizes->discrete.width = UGETW(UVIDEO_FRAME_FIELD(frame, wWidth));
+ fsizes->discrete.height = UGETW(UVIDEO_FRAME_FIELD(frame, wHeight));
+
+ return (0);
+}
+
+static int
+uvideo_enum_fivals(struct uvideo_softc *sc, struct v4l2_frmivalenum *fivals)
+{
+ int idx;
+ struct uvideo_format_group *fmtgrp = NULL;
+ struct usb_video_frame_desc *frame = NULL;
+ uint8_t *p;
+ uint32_t fi_index, fi_pixfmt, fi_width, fi_height;
+
+ fi_index = fivals->index;
+ fi_pixfmt = fivals->pixel_format;
+ fi_width = fivals->width;
+ fi_height = fivals->height;
+
+ for (idx = 0; idx < sc->sc_fmtgrp_num; idx++) {
+ if (sc->sc_fmtgrp[idx].pixelformat == fi_pixfmt) {
+ fmtgrp = &sc->sc_fmtgrp[idx];
+ break;
+ }
+ }
+ if (fmtgrp == NULL)
+ return (EINVAL);
+
+ for (idx = 0; idx < fmtgrp->frame_num; idx++) {
+ if (UGETW(UVIDEO_FRAME_FIELD(fmtgrp->frame[idx], wWidth))
+ == fi_width &&
+ UGETW(UVIDEO_FRAME_FIELD(fmtgrp->frame[idx], wHeight))
+ == fi_height) {
+ frame = fmtgrp->frame[idx];
+ break;
+ }
+ }
+ if (frame == NULL)
+ return (EINVAL);
+
+ p = (uint8_t *)frame + UVIDEO_FRAME_MIN_LEN(frame);
+
+ bzero(fivals, sizeof(*fivals));
+ fivals->index = fi_index;
+ fivals->pixel_format = fi_pixfmt;
+ fivals->width = fi_width;
+ fivals->height = fi_height;
+
+ if (UVIDEO_FRAME_NUM_INTERVALS(frame) == 0) {
+ if (fi_index != 0)
+ return (EINVAL);
+ fivals->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+ fivals->stepwise.min.numerator = UGETDW(p);
+ fivals->stepwise.min.denominator = 10000000;
+ p += sizeof(uDWord);
+ fivals->stepwise.max.numerator = UGETDW(p);
+ fivals->stepwise.max.denominator = 10000000;
+ p += sizeof(uDWord);
+ fivals->stepwise.step.numerator = UGETDW(p);
+ fivals->stepwise.step.denominator = 10000000;
+ } else {
+ if (fi_index >= (uint32_t)UVIDEO_FRAME_NUM_INTERVALS(frame))
+ return (EINVAL);
+ p += sizeof(uDWord) * fi_index;
+ if (p > frame->bLength + (uint8_t *)frame) {
+ device_printf(sc->sc_dev,
+ "frame desc too short?\n");
+ return (EINVAL);
+ }
+ fivals->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ fivals->discrete.numerator = UGETDW(p);
+ fivals->discrete.denominator = 10000000;
+ }
+
+ return (0);
+}
+
+static int
+uvideo_s_fmt(struct uvideo_softc *sc, struct v4l2_format *fmt)
+{
+ struct uvideo_format_group *fmtgrp_save;
+ struct usb_video_frame_desc *frame_save;
+ struct uvideo_res r;
+ int found, i;
+ usb_error_t error;
+
+ if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ DPRINTFN(1, "s_fmt: requested %dx%d\n",
+ fmt->fmt.pix.width, fmt->fmt.pix.height);
+
+ /* Search requested pixel format */
+ for (found = 0, i = 0; i < sc->sc_fmtgrp_num; i++) {
+ if (fmt->fmt.pix.pixelformat == sc->sc_fmtgrp[i].pixelformat) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0)
+ return (EINVAL);
+
+ if (sc->sc_fmtgrp[i].frame_num == 0) {
+ device_printf(sc->sc_dev, "no frame descriptors!\n");
+ return (EINVAL);
+ }
+
+ uvideo_find_res(sc, i, fmt->fmt.pix.width, fmt->fmt.pix.height, &r);
+
+ /* Save current format in case negotiation fails */
+ fmtgrp_save = sc->sc_fmtgrp_cur;
+ frame_save = sc->sc_fmtgrp_cur->frame_cur;
+
+ sc->sc_fmtgrp_cur = &sc->sc_fmtgrp[i];
+ sc->sc_fmtgrp[i].frame_cur = sc->sc_fmtgrp[i].frame[r.fidx];
+
+ error = uvideo_vs_negotiation(sc, 1);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ sc->sc_fmtgrp_cur = fmtgrp_save;
+ sc->sc_fmtgrp_cur->frame_cur = frame_save;
+ return (EINVAL);
+ }
+ sc->sc_negotiated_flag = 1;
+
+ fmt->fmt.pix.width = r.width;
+ fmt->fmt.pix.height = r.height;
+ fmt->fmt.pix.sizeimage =
+ UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize);
+
+ DPRINTFN(1, "s_fmt: offered %dx%d\n", r.width, r.height);
+
+ return (0);
+}
+
+static int
+uvideo_g_fmt(struct uvideo_softc *sc, struct v4l2_format *fmt)
+{
+ struct usb_video_frame_desc *frame;
+ uint32_t type;
+
+ type = fmt->type;
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ bzero(fmt, sizeof(*fmt));
+ fmt->type = type;
+ fmt->fmt.pix.pixelformat = sc->sc_fmtgrp_cur->pixelformat;
+ fmt->fmt.pix.field = V4L2_FIELD_NONE;
+
+ frame = sc->sc_fmtgrp_cur->frame_cur;
+ fmt->fmt.pix.width = UGETW(UVIDEO_FRAME_FIELD(frame, wWidth));
+ fmt->fmt.pix.height = UGETW(UVIDEO_FRAME_FIELD(frame, wHeight));
+ fmt->fmt.pix.sizeimage =
+ UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize);
+
+ if (sc->sc_fmtgrp_cur->has_colorformat) {
+ fmt->fmt.pix.colorspace = sc->sc_fmtgrp_cur->colorspace;
+ fmt->fmt.pix.xfer_func = sc->sc_fmtgrp_cur->xfer_func;
+ fmt->fmt.pix.ycbcr_enc = sc->sc_fmtgrp_cur->ycbcr_enc;
+ }
+
+
+ return (0);
+}
+
+static int
+uvideo_s_parm(struct uvideo_softc *sc, struct v4l2_streamparm *parm)
+{
+ usb_error_t error;
+
+ if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ if (parm->parm.capture.timeperframe.numerator == 0 ||
+ parm->parm.capture.timeperframe.denominator == 0)
+ sc->sc_frame_rate = 0;
+ else
+ sc->sc_frame_rate =
+ parm->parm.capture.timeperframe.denominator /
+ parm->parm.capture.timeperframe.numerator;
+ } else
+ return (EINVAL);
+
+ /* Renegotiate if needed */
+ if (sc->sc_negotiated_flag) {
+ error = uvideo_vs_negotiation(sc, 1);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (EINVAL);
+ }
+
+ /* Return current parameters (zeroes reserved fields) */
+ return (uvideo_g_parm(sc, parm));
+}
+
+static int
+uvideo_g_parm(struct uvideo_softc *sc, struct v4l2_streamparm *parm)
+{
+ uint32_t type;
+
+ type = parm->type;
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ bzero(parm, sizeof(*parm));
+ parm->type = type;
+ parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+ parm->parm.capture.capturemode = 0;
+ parm->parm.capture.readbuffers = UVIDEO_MAX_BUFFERS;
+ parm->parm.capture.timeperframe.numerator =
+ UGETDW(sc->sc_desc_probe.dwFrameInterval);
+ parm->parm.capture.timeperframe.denominator = 10000000;
+
+ return (0);
+}
+
+static int
+uvideo_enum_input(struct uvideo_softc *sc, struct v4l2_input *input)
+{
+ uint32_t idx;
+
+ idx = input->index;
+ if (idx != 0)
+ return (EINVAL);
+
+ bzero(input, sizeof(*input));
+ input->index = idx;
+ strlcpy(input->name, "Camera Terminal", sizeof(input->name));
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ input->status = 0; /* no error */
+ input->std = 0; /* no standard TV norms */
+
+ return (0);
+}
+
+static int
+uvideo_s_input(struct uvideo_softc *sc, int input)
+{
+
+ if (input != 0)
+ return (EINVAL);
+
+ return (0);
+}
+
+static int
+uvideo_g_input(struct uvideo_softc *sc, int *input)
+{
+
+ *input = 0;
+ return (0);
+}
+
+static int
+uvideo_reqbufs(struct uvideo_softc *sc, struct v4l2_requestbuffers *rb)
+{
+ int i, buf_size, buf_size_total;
+
+ DPRINTFN(1, "reqbufs: count=%d\n", rb->count);
+
+ if (rb->count == 0)
+ return (EINVAL);
+
+ if (sc->sc_mmap_count > 0 || sc->sc_mmap_buffer != NULL) {
+ DPRINTFN(1, "mmap buffers already allocated\n");
+ return (EINVAL);
+ }
+
+ if (rb->count > UVIDEO_MAX_BUFFERS)
+ sc->sc_mmap_count = UVIDEO_MAX_BUFFERS;
+ else
+ sc->sc_mmap_count = rb->count;
+
+ buf_size = UGETDW(sc->sc_desc_probe.dwMaxVideoFrameSize);
+ buf_size_total = sc->sc_mmap_count * buf_size;
+ buf_size_total = round_page(buf_size_total);
+
+ sc->sc_mmap_buffer = contigmalloc(buf_size_total, M_USBDEV,
+ M_WAITOK | M_ZERO, 0, ~0UL, PAGE_SIZE, 0);
+ if (sc->sc_mmap_buffer == NULL) {
+ device_printf(sc->sc_dev, "can't allocate mmap buffer!\n");
+ sc->sc_mmap_count = 0;
+ return (ENOMEM);
+ }
+ sc->sc_mmap_buffer_size = buf_size_total;
+
+ DPRINTFN(1, "allocated %d bytes mmap buffer\n", buf_size_total);
+
+ for (i = 0; i < sc->sc_mmap_count; i++) {
+ sc->sc_mmap[i].buf = sc->sc_mmap_buffer + (i * buf_size);
+
+ sc->sc_mmap[i].v4l2_buf.index = i;
+ sc->sc_mmap[i].v4l2_buf.m.offset = i * buf_size;
+ sc->sc_mmap[i].v4l2_buf.length = buf_size;
+ sc->sc_mmap[i].v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ sc->sc_mmap[i].v4l2_buf.sequence = 0;
+ sc->sc_mmap[i].v4l2_buf.field = V4L2_FIELD_NONE;
+ sc->sc_mmap[i].v4l2_buf.memory = V4L2_MEMORY_MMAP;
+ sc->sc_mmap[i].v4l2_buf.flags = V4L2_BUF_FLAG_MAPPED;
+ }
+
+ sc->sc_mmap_buffer_idx = 0;
+ sc->sc_mmap_cur = NULL;
+
+ rb->count = sc->sc_mmap_count;
+ rb->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP;
+
+ return (0);
+}
+
+static int
+uvideo_querybuf(struct uvideo_softc *sc, struct v4l2_buffer *qb)
+{
+
+ if (qb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ qb->memory != V4L2_MEMORY_MMAP ||
+ qb->index >= sc->sc_mmap_count)
+ return (EINVAL);
+
+ bcopy(&sc->sc_mmap[qb->index].v4l2_buf, qb,
+ sizeof(struct v4l2_buffer));
+
+ return (0);
+}
+
+static int
+uvideo_qbuf(struct uvideo_softc *sc, struct v4l2_buffer *qb)
+{
+
+ if (qb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ qb->memory != V4L2_MEMORY_MMAP ||
+ qb->index >= sc->sc_mmap_count)
+ return (EINVAL);
+
+ sc->sc_mmap[qb->index].v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE;
+ sc->sc_mmap[qb->index].v4l2_buf.flags |= V4L2_BUF_FLAG_QUEUED;
+
+ DPRINTFN(2, "buffer %d ready for queueing\n", qb->index);
+
+ return (0);
+}
+
+static int
+uvideo_dqbuf(struct uvideo_softc *sc, struct v4l2_buffer *dqb)
+{
+ struct uvideo_mmap *mmap;
+ int error;
+
+ if (dqb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ dqb->memory != V4L2_MEMORY_MMAP)
+ return (EINVAL);
+
+ if (STAILQ_EMPTY(&sc->sc_mmap_q)) {
+ error = tsleep(sc, PCATCH, "uvdqbuf", hz * 10);
+ if (error)
+ return (EINVAL);
+ }
+
+ mmap = STAILQ_FIRST(&sc->sc_mmap_q);
+ if (mmap == NULL)
+ return (EINVAL);
+
+ bcopy(&mmap->v4l2_buf, dqb, sizeof(struct v4l2_buffer));
+
+ mmap->v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE;
+ mmap->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED;
+
+ DPRINTFN(2, "frame dequeued from index %d\n",
+ mmap->v4l2_buf.index);
+ STAILQ_REMOVE_HEAD(&sc->sc_mmap_q, q_frames);
+
+ return (0);
+}
+
+static int
+uvideo_streamon(struct uvideo_softc *sc, int type)
+{
+ usb_error_t error;
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ if (sc->sc_streaming)
+ return (0);
+
+ sc->sc_vidmode = VIDMODE_MMAP;
+
+ error = uvideo_vs_init(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (EINVAL);
+
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_streaming = 1;
+ if (sc->sc_vs_cur->bulk_endpoint)
+ usbd_transfer_start(sc->sc_xfer[0]);
+ else {
+ int i;
+ for (i = 0; i < UVIDEO_IXFERS; i++)
+ usbd_transfer_start(sc->sc_xfer[i]);
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+uvideo_streamoff(struct uvideo_softc *sc, int type)
+{
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ if (!sc->sc_streaming)
+ return (0);
+
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_streaming = 0;
+ mtx_unlock(&sc->sc_mtx);
+
+ uvideo_vs_close(sc);
+ uvideo_vs_free_frame(sc);
+
+ return (0);
+}
+
+static int
+uvideo_try_fmt(struct uvideo_softc *sc, struct v4l2_format *fmt)
+{
+ struct uvideo_res r;
+ int found, i;
+
+ if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return (EINVAL);
+
+ for (found = 0, i = 0; i < sc->sc_fmtgrp_num; i++) {
+ if (fmt->fmt.pix.pixelformat == sc->sc_fmtgrp[i].pixelformat) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0)
+ return (EINVAL);
+
+ uvideo_find_res(sc, i, fmt->fmt.pix.width, fmt->fmt.pix.height, &r);
+
+ fmt->fmt.pix.width = r.width;
+ fmt->fmt.pix.height = r.height;
+ fmt->fmt.pix.sizeimage = sc->sc_frame_buffer.buf_size;
+
+ return (0);
+}
+
+static int
+uvideo_queryctrl(struct uvideo_softc *sc, struct v4l2_queryctrl *qctrl)
+{
+ int i, ret = 0;
+ usb_error_t error;
+ uint8_t *ctrl_data;
+ uint16_t ctrl_len;
+
+ i = uvideo_find_ctrl(sc, qctrl->id);
+ if (i == EINVAL)
+ return (i);
+
+ ctrl_len = uvideo_ctrls[i].ctrl_len;
+ if (ctrl_len < 1 || ctrl_len > 2) {
+ device_printf(sc->sc_dev,
+ "invalid control length: %d\n", ctrl_len);
+ return (EINVAL);
+ }
+
+ ctrl_data = malloc(ctrl_len, M_USBDEV, M_WAITOK | M_ZERO);
+ if (ctrl_data == NULL)
+ return (ENOMEM);
+
+ qctrl->type = uvideo_ctrls[i].type;
+ strlcpy(qctrl->name, uvideo_ctrls[i].name, sizeof(qctrl->name));
+
+ /* get minimum */
+ error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_MIN,
+ sc->sc_desc_vc_pu_cur->bUnitID,
+ uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ ret = EINVAL;
+ goto out;
+ }
+ switch (ctrl_len) {
+ case 1:
+ qctrl->minimum = uvideo_ctrls[i].sig ?
+ *(int8_t *)ctrl_data : *ctrl_data;
+ break;
+ case 2:
+ qctrl->minimum = uvideo_ctrls[i].sig ?
+ (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
+ break;
+ }
+
+ /* get maximum */
+ error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_MAX,
+ sc->sc_desc_vc_pu_cur->bUnitID,
+ uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ ret = EINVAL;
+ goto out;
+ }
+ switch (ctrl_len) {
+ case 1:
+ qctrl->maximum = uvideo_ctrls[i].sig ?
+ *(int8_t *)ctrl_data : *ctrl_data;
+ break;
+ case 2:
+ qctrl->maximum = uvideo_ctrls[i].sig ?
+ (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
+ break;
+ }
+
+ /* get resolution/step */
+ error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_RES,
+ sc->sc_desc_vc_pu_cur->bUnitID,
+ uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ ret = EINVAL;
+ goto out;
+ }
+ switch (ctrl_len) {
+ case 1:
+ qctrl->step = uvideo_ctrls[i].sig ?
+ *(int8_t *)ctrl_data : *ctrl_data;
+ break;
+ case 2:
+ qctrl->step = uvideo_ctrls[i].sig ?
+ (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
+ break;
+ }
+
+ /* get default */
+ error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_DEF,
+ sc->sc_desc_vc_pu_cur->bUnitID,
+ uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ ret = EINVAL;
+ goto out;
+ }
+ switch (ctrl_len) {
+ case 1:
+ qctrl->default_value = uvideo_ctrls[i].sig ?
+ *(int8_t *)ctrl_data : *ctrl_data;
+ break;
+ case 2:
+ qctrl->default_value = uvideo_ctrls[i].sig ?
+ (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
+ break;
+ }
+
+ qctrl->flags = 0;
+
+out:
+ free(ctrl_data, M_USBDEV);
+ return (ret);
+}
+
+static int
+uvideo_g_ctrl(struct uvideo_softc *sc, struct v4l2_control *gctrl)
+{
+ int i, ret = 0;
+ usb_error_t error;
+ uint8_t *ctrl_data;
+ uint16_t ctrl_len;
+
+ i = uvideo_find_ctrl(sc, gctrl->id);
+ if (i == EINVAL)
+ return (i);
+
+ ctrl_len = uvideo_ctrls[i].ctrl_len;
+ if (ctrl_len < 1 || ctrl_len > 2)
+ return (EINVAL);
+
+ ctrl_data = malloc(ctrl_len, M_USBDEV, M_WAITOK | M_ZERO);
+ if (ctrl_data == NULL)
+ return (ENOMEM);
+
+ error = uvideo_vc_get_ctrl(sc, ctrl_data, GET_CUR,
+ sc->sc_desc_vc_pu_cur->bUnitID,
+ uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ ret = EINVAL;
+ goto out;
+ }
+ switch (ctrl_len) {
+ case 1:
+ gctrl->value = uvideo_ctrls[i].sig ?
+ *(int8_t *)ctrl_data : *ctrl_data;
+ break;
+ case 2:
+ gctrl->value = uvideo_ctrls[i].sig ?
+ (int16_t)UGETW(ctrl_data) : UGETW(ctrl_data);
+ break;
+ }
+
+out:
+ free(ctrl_data, M_USBDEV);
+ return (ret);
+}
+
+static int
+uvideo_s_ctrl(struct uvideo_softc *sc, struct v4l2_control *sctrl)
+{
+ int i, ret = 0;
+ usb_error_t error;
+ uint8_t *ctrl_data;
+ uint16_t ctrl_len;
+
+ i = uvideo_find_ctrl(sc, sctrl->id);
+ if (i == EINVAL)
+ return (i);
+
+ ctrl_len = uvideo_ctrls[i].ctrl_len;
+ if (ctrl_len < 1 || ctrl_len > 2)
+ return (EINVAL);
+
+ ctrl_data = malloc(ctrl_len, M_USBDEV, M_WAITOK | M_ZERO);
+ if (ctrl_data == NULL)
+ return (ENOMEM);
+
+ switch (ctrl_len) {
+ case 1:
+ if (uvideo_ctrls[i].sig)
+ *(int8_t *)ctrl_data = sctrl->value;
+ else
+ *ctrl_data = sctrl->value;
+ break;
+ case 2:
+ USETW(ctrl_data, sctrl->value);
+ break;
+ }
+
+ error = uvideo_vc_set_ctrl(sc, ctrl_data, SET_CUR,
+ sc->sc_desc_vc_pu_cur->bUnitID,
+ uvideo_ctrls[i].ctrl_selector, uvideo_ctrls[i].ctrl_len);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ ret = EINVAL;
+
+ free(ctrl_data, M_USBDEV);
+ return (ret);
+}
diff --git a/sys/dev/usb/video/uvideo.h b/sys/dev/usb/video/uvideo.h
new file mode 100644
index 000000000000..bccaf4cf0216
--- /dev/null
+++ b/sys/dev/usb/video/uvideo.h
@@ -0,0 +1,687 @@
+/* $OpenBSD: uvideo.h,v 1.71 2025/09/06 13:45:41 kirill Exp $ */
+
+/*
+ * Copyright (c) 2007 Robert Nagy <robert@openbsd.org>
+ * Copyright (c) 2008 Marcus Glocker <mglocker@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _UVIDEO_H_
+#define _UVIDEO_H_
+
+#include <sys/queue.h>
+#include "uvideo_v4l2.h"
+
+/*
+ * USB Video Class
+ */
+/* Table A-7: Video Class-Specific Endpoint Descriptor Subtypes */
+#define EP_UNDEFINED 0x00
+#define EP_GENERAL 0x01
+#define EP_ENDPOINT 0x02
+#define EP_INTERRUPT 0x03
+
+/* Table A-5: Video Class-Specific VC Interface Descriptor Subtypes */
+#define UDESCSUB_VC_DESCRIPTOR_UNDEFINED 0x00
+#define UDESCSUB_VC_HEADER 0x01
+#define UDESCSUB_VC_INPUT_TERMINAL 0x02
+#define UDESCSUB_VC_OUTPUT_TERMINAL 0x03
+#define UDESCSUB_VC_SELECTOR_UNIT 0x04
+#define UDESCSUB_VC_PROCESSING_UNIT 0x05
+#define UDESCSUB_VC_EXTENSION_UNIT 0x06
+
+/* Table A-6: Video Class-Specific VS Interface Descriptor Subtypes */
+#define UDESCSUB_VS_UNDEFINED 0x00
+#define UDESCSUB_VS_INPUT_HEADER 0x01
+#define UDESCSUB_VS_OUTPUT_HEADER 0x02
+#define UDESCSUB_VS_STILL_IMAGE_FRAME 0x03
+#define UDESCSUB_VS_FORMAT_UNCOMPRESSED 0x04
+#define UDESCSUB_VS_FRAME_UNCOMPRESSED 0x05
+#define UDESCSUB_VS_FORMAT_MJPEG 0x06
+#define UDESCSUB_VS_FRAME_MJPEG 0x07
+#define UDESCSUB_VS_FORMAT_MPEG2TS 0x0a
+#define UDESCSUB_VS_FORMAT_DV 0x0c
+#define UDESCSUB_VS_COLORFORMAT 0x0d
+#define UDESCSUB_VS_FORMAT_FRAME_BASED 0x10
+#define UDESCSUB_VS_FRAME_FRAME_BASED 0x11
+#define UDESCSUB_VS_FORMAT_STREAM_BASED 0x12
+#define UDESCSUB_VS_FORMAT_H264 0x13
+#define UDESCSUB_VS_FRAME_H264 0x14
+#define UDESCSUB_VS_FORMAT_H264_SIMULCAST 0x15
+
+/* Table A-8: Video Class-Specific Request Codes */
+#define RC_UNDEFINED 0x00
+#define SET_CUR 0x01
+#define GET_CUR 0x81
+#define GET_MIN 0x82
+#define GET_MAX 0x83
+#define GET_RES 0x84
+#define GET_LEN 0x85
+#define GET_INFO 0x86
+#define GET_DEF 0x87
+
+/* Table A-9: Video Control Interface Control Selectors */
+#define VC_CONTROL_UNDEFINED 0x00
+#define VC_VIDEO_POWER_MODE_CONTROL 0x01
+#define VC_REQUEST_ERROR_CODE_CONTROL 0x02
+
+/* Table A-11: Selector Unit Control Selectors */
+#define SU_CONTROL_UNDEFINED 0x00
+#define SU_INPUT_SELECT_CONTROL 0x01
+
+/* Table A-12: Camera Terminal Control Selectors */
+#define CT_CONTROL_UNDEFINED 0x00
+#define CT_SCANNING_MODE_CONTROL 0x01
+#define CT_AE_MODE_CONTROL 0x02
+#define CT_AE_PRIORITY_CONTROL 0x03
+#define CT_EXPOSURE_TIME_ABSOLUTE_CONTROL 0x04
+#define CT_EXPOSURE_TIME_RELATIVE_CONTROL 0x05
+#define CT_FOCUS_ABSOLUTE_CONTROL 0x06
+#define CT_FOCUS_RELATIVE_CONTROL 0x07
+#define CT_FOCUS_AUTO_CONTROL 0x08
+#define CT_IRIS_ABSOLUTE_CONTROL 0x09
+#define CT_IRIS_RELATIVE_CONTROL 0x0a
+#define CT_ZOOM_ABSOLUTE_CONTROL 0x0b
+#define CT_ZOOM_RELATIVE_CONTROL 0x0c
+#define CT_PANTILT_ABSOLUTE_CONTROL 0x0d
+#define CT_PANTILT_RELATIVE_CONTROL 0x0e
+#define CT_ROLL_ABSOLUTE_CONTROL 0x0f
+#define CT_ROLL_RELATIVE_CONTROL 0x10
+#define CT_PRIVACY_CONTROL 0x11
+
+/* Table A-13: Processing Unit Control Selectors */
+#define PU_CONTROL_UNDEFINED 0x00
+#define PU_BACKLIGHT_COMPENSATION_CONTROL 0x01
+#define PU_BRIGHTNESS_CONTROL 0x02
+#define PU_CONTRAST_CONTROL 0x03
+#define PU_GAIN_CONTROL 0x04
+#define PU_POWER_LINE_FREQUENCY_CONTROL 0x05
+#define PU_HUE_CONTROL 0x06
+#define PU_SATURATION_CONTROL 0x07
+#define PU_SHARPNESS_CONTROL 0x08
+#define PU_GAMMA_CONTROL 0x09
+#define PU_WHITE_BALANCE_TEMPERATURE_CONTROL 0x0a
+#define PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL 0x0b
+#define PU_WHITE_BALANCE_COMPONENT_CONTROL 0x0c
+#define PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL 0x0d
+#define PU_DIGITAL_MULTIPLIER_CONTROL 0x0e
+#define PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL 0x0f
+#define PU_HUE_AUTO_CONTROL 0x10
+#define PU_ANALOG_VIDEO_STANDARD_CONTROL 0x11
+#define PU_ANALOG_LOCK_STATUS_CONTROL 0x12
+
+/* Table A-15: VideoStreaming Interface Control Selectors */
+#define VS_CONTROL_UNDEFINED 0x00
+#define VS_PROBE_CONTROL 0x01
+#define VS_COMMIT_CONTROL 0x02
+#define VS_STILL_PROBE_CONTROL 0x03
+#define VS_STILL_COMMIT_CONTROL 0x04
+#define VS_STILL_IMAGE_TRIGGER_CONTROL 0x05
+#define VS_STREAM_ERROR_CODE_CONTROL 0x06
+#define VS_GENERATE_KEY_FRAME_CONTROL 0x07
+#define VS_UPDATE_FRAME_SEGMENT_CONTROL 0x08
+#define VS_SYNC_DELAY_CONTROL 0x09
+
+/* probe commit bmRequests */
+#define UVIDEO_SET_IF 0x21
+#define UVIDEO_GET_IF 0xa1
+#define UVIDEO_SET_EP 0x22
+#define UVIDEO_GET_EP 0xa2
+
+/* Table B-1: USB Terminal Types */
+#define TT_VENDOR_SPECIFIC 0x0100
+#define TT_STREAMING 0x0101
+
+/* Table B-2: Input Terminal Types */
+#define ITT_VENDOR_SPECIFIC 0x0200
+#define ITT_CAMERA 0x0201
+#define ITT_MEDIA_TRANSPORT_INPUT 0x0202
+
+/* Table B-3: Output Terminal Types */
+#define OTT_VENDOR_SPECIFIC 0x0300
+#define OTT_DISPLAY 0x0301
+#define OTT_MEDIA_TRANSPORT_OUTPUT 0x0302
+
+/* Table B-4: External Terminal Types */
+#define EXTERNAL_VENDOR_SPECIFIC 0x0400
+#define COMPOSITE_CONNECTOR 0x0401
+#define SVIDEO_CONNECTOR 0x0402
+#define COMPONENT_CONNECTOR 0x0403
+
+/* Table 3-3: VC Interface Header Descriptor */
+struct usb_video_header_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uWord bcdUVC;
+ uWord wTotalLength;
+ uDWord dwClockFrequency;
+ uByte bInCollection;
+} __packed;
+
+struct usb_video_header_desc_all {
+ struct usb_video_header_desc *fix;
+ uByte *baInterfaceNr;
+};
+
+/* Table 3-4: Input Terminal Descriptor */
+struct usb_video_input_terminal_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bTerminalID;
+ uWord wTerminalType;
+ uByte bAssocTerminal;
+ uByte iTerminal;
+} __packed;
+
+/* Table 3-5: Output Terminal Descriptor */
+struct usb_video_output_terminal_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bTerminalID;
+ uWord wTerminalType;
+ uByte bAssocTerminal;
+ uByte bSourceID;
+ uByte iTerminal;
+} __packed;
+
+/* Table 3-6: Camera Terminal Descriptor */
+struct usb_video_camera_terminal_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bTerminalID;
+ uWord wTerminalType;
+ uByte bAssocTerminal;
+ uByte iTerminal;
+ uWord wObjectiveFocalLengthMin;
+ uWord wObjectiveFocalLengthMax;
+ uWord wOcularFocalLength;
+ uByte bControlSize;
+ uByte *bmControls;
+} __packed;
+
+/* Table 3-8: VC Processing Unit Descriptor */
+struct usb_video_vc_processing_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bUnitID;
+ uByte bSourceID;
+ uWord wMaxMultiplier;
+ uByte bControlSize;
+ uByte bmControls[255]; /* [bControlSize] */
+ /* uByte iProcessing; */
+ /* uByte bmVideoStandards; */
+} __packed;
+
+/* Table 3-9: VC Extension Unit Descriptor */
+struct usb_video_vc_extension_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bUnitID;
+ uByte guidExtensionCode[16];
+ uByte bNumControls;
+ uByte bNrInPins;
+} __packed;
+
+/* Table 3-11: VC Endpoint Descriptor */
+struct usb_video_vc_endpoint_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uWord wMaxTransferSize;
+} __packed;
+
+/* Table 3-13: Interface Input Header Descriptor */
+struct usb_video_input_header_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bNumFormats;
+ uWord wTotalLength;
+ uByte bEndpointAddress;
+ uByte bmInfo;
+ uByte bTerminalLink;
+ uByte bStillCaptureMethod;
+ uByte bTriggerSupport;
+ uByte bTriggerUsage;
+ uByte bControlSize;
+} __packed;
+
+struct usb_video_input_header_desc_all {
+ struct usb_video_input_header_desc *fix;
+ uByte *bmaControls;
+};
+
+/* Table 3-18: Color Matching Descriptor */
+struct usb_video_color_matching_descr {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bColorPrimaries;
+ uByte bTransferCharacteristics;
+ uByte bMatrixCoefficients;
+} __packed;
+
+/* Table 4-75: Video Probe and Commit Controls */
+struct usb_video_probe_commit {
+ uWord bmHint;
+ uByte bFormatIndex;
+ uByte bFrameIndex;
+ uDWord dwFrameInterval;
+ uWord wKeyFrameRate;
+ uWord wPFrameRate;
+ uWord wCompQuality;
+ uWord wCompWindowSize;
+ uWord wDelay;
+ uDWord dwMaxVideoFrameSize;
+ uDWord dwMaxPayloadTransferSize;
+ uDWord dwClockFrequency;
+ uByte bmFramingInfo;
+ uByte bPreferedVersion;
+ uByte bMinVersion;
+ uByte bMaxVersion;
+ uByte bUsage;
+ uByte bBitDepthLuma;
+ uByte bmSettings;
+ uByte bMaxNumberOfRefFramesPlus1;
+ uWord bmRateControlModes;
+ uByte bmLayoutPerStream[8];
+} __packed;
+
+/*
+ * USB Video Payload Uncompressed
+ */
+/* Table 2-1: Compression Formats */
+#define UVIDEO_FORMAT_GUID_YUY2 { \
+ 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_YV12 { \
+ 'Y', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_I420 { \
+ 'I', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_Y800 { \
+ 'Y', '8', '0', '0', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_Y8 { \
+ 'Y', '8', ' ', ' ', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_D3DFMT_L8 { \
+ 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_KSMEDIA_L8_IR { \
+ 0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_BY8 { \
+ 'B', 'Y', '8', ' ', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_BA81 { \
+ 'B', 'A', '8', '1', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_GBRG { \
+ 'G', 'B', 'R', 'G', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_GRBG { \
+ 'G', 'R', 'B', 'G', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_RGGB { \
+ 'R', 'G', 'G', 'B', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_RGBP { \
+ 'R', 'G', 'B', 'P', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_D3DFMT_R5G6B5 { \
+ 0x7b, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \
+ 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70 }
+
+#define UVIDEO_FORMAT_GUID_BGR3 { \
+ 0x7d, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \
+ 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70 }
+
+#define UVIDEO_FORMAT_GUID_BGR4 { \
+ 0x7e, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \
+ 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70 }
+
+#define UVIDEO_FORMAT_GUID_H265 { \
+ 'H', '2', '6', '5', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_RW10 { \
+ 'R', 'W', '1', '0', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_BG16 { \
+ 'B', 'G', '1', '6', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_GB16 { \
+ 'G', 'B', '1', '6', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_RG16 { \
+ 'R', 'G', '1', '6', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_GR16 { \
+ 'G', 'R', '1', '6', 0x00, 0x00, 0x10, 0x00, \
+ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
+
+#define UVIDEO_FORMAT_GUID_INVZ { \
+ 'I', 'N', 'V', 'Z', 0x90, 0x2d, 0x58, 0x4a, \
+ 0x92, 0x0b, 0x77, 0x3f, 0x1f, 0x2c, 0x55, 0x6b }
+
+#define UVIDEO_FORMAT_GUID_INVI { \
+ 'I', 'N', 'V', 'I', 0xdb, 0x57, 0x49, 0x5e, \
+ 0x8e, 0x3f, 0xf4, 0x79, 0x53, 0x2b, 0x94, 0x6f }
+
+/*
+ * USB Video Payload MJPEG
+ */
+/* Table 2-1: Stream Header Format for the Motion-JPEG */
+#define UVIDEO_SH_MAX_LEN 12
+#define UVIDEO_SH_MIN_LEN 2
+struct usb_video_stream_header {
+ uByte bLength;
+ uByte bFlags;
+#define UVIDEO_SH_FLAG_FID (1 << 0)
+#define UVIDEO_SH_FLAG_EOF (1 << 1)
+#define UVIDEO_SH_FLAG_PTS (1 << 2)
+#define UVIDEO_SH_FLAG_SCR (1 << 3)
+#define UVIDEO_SH_FLAG_RES (1 << 4)
+#define UVIDEO_SH_FLAG_STI (1 << 5)
+#define UVIDEO_SH_FLAG_ERR (1 << 6)
+#define UVIDEO_SH_FLAG_EOH (1 << 7)
+ /* TODO complete struct */
+} __packed;
+
+/* Table 3-19: Color Matching Descriptor */
+struct usb_video_colorformat_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bColorPrimaries;
+ uByte bTransferCharacteristics;
+ uByte bMatrixCoefficients;
+} __packed;
+
+struct usb_video_frame_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bFrameIndex;
+ union {
+ /*
+ * Table 3-2: Video Frame Descriptor
+ * (same for mjpeg and uncompressed)
+ */
+ struct {
+ uByte bmCapabilities;
+ uWord wWidth;
+ uWord wHeight;
+ uDWord dwMinBitRate;
+ uDWord dwMaxBitRate;
+ uDWord dwMaxVideoFrameBufferSize;
+ uDWord dwDefaultFrameInterval;
+ uByte bFrameIntervalType;
+ } uc;
+
+ /*
+ * Table 3-2 Frame Based Payload Video Frame Descriptors */
+ struct {
+ uByte bmCapabilities;
+ uWord wWidth;
+ uWord wHeight;
+ uDWord dwMinBitRate;
+ uDWord dwMaxBitRate;
+ uDWord dwDefaultFrameInterval;
+ uByte bFrameIntervalType;
+ uDWord dwBytesPerLine;
+ } fb;
+
+ /* Table 3-2: H.264 Payload Video Frame Descriptor */
+ struct {
+ uWord wWidth;
+ uWord wHeight;
+ uWord wSARwidth;
+ uWord wSARheight;
+ uWord wProfile;
+ uByte bLevelIDC;
+ uWord wConstrainedToolset;
+ uDWord bmSupportedUsages;
+ uWord bmCapabilities;
+ uDWord bmSVCCapabilities;
+ uDWord bmMVCCapabilities;
+ uDWord dwMinBitRate;
+ uDWord dwMaxBitRate;
+ uDWord dwDefaultFrameInterval;
+ uByte bNumFrameIntervals;
+ } h264;
+
+ } u;
+
+#define UVIDEO_FRAME_MIN_LEN(frm) \
+ (offsetof(struct usb_video_frame_desc, u) + \
+ ( \
+ ((frm)->bDescriptorSubtype == UDESCSUB_VS_FRAME_H264) ? \
+ sizeof(((struct usb_video_frame_desc *)0)->u.h264) : \
+ ((frm)->bDescriptorSubtype == UDESCSUB_VS_FRAME_FRAME_BASED) ? \
+ sizeof(((struct usb_video_frame_desc *)0)->u.fb) : \
+ sizeof(((struct usb_video_frame_desc *)0)->u.uc) \
+ ) \
+ )
+
+#define UVIDEO_FRAME_FIELD(frm, field) \
+ ( \
+ ((frm)->bDescriptorSubtype == UDESCSUB_VS_FRAME_H264) ? \
+ (frm)->u.h264.field : \
+ ((frm)->bDescriptorSubtype == UDESCSUB_VS_FRAME_FRAME_BASED) ? \
+ (frm)->u.fb.field : \
+ (frm)->u.uc.field \
+ )
+
+#define UVIDEO_FRAME_NUM_INTERVALS(frm) \
+ ( \
+ ((frm)->bDescriptorSubtype == UDESCSUB_VS_FRAME_H264) ? \
+ (frm)->u.h264.bNumFrameIntervals : \
+ ((frm)->bDescriptorSubtype == UDESCSUB_VS_FRAME_FRAME_BASED) ? \
+ (frm)->u.fb.bFrameIntervalType : \
+ (frm)->u.uc.bFrameIntervalType \
+ )
+
+ /* uDWord ivals[]; frame intervals, length varies */
+} __packed;
+
+struct usb_video_format_desc {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bFormatIndex;
+ uByte bNumFrameDescriptors;
+ union {
+ /* Table 3-1: Motion-JPEG Video Format Descriptor */
+ struct {
+ uByte bmFlags;
+ uByte bDefaultFrameIndex;
+ uByte bAspectRatioX;
+ uByte bAspectRatioY;
+ uByte bmInterlaceFlags;
+ uByte bCopyProtect;
+ } mjpeg;
+
+ /* Table 3-1: Uncompressed Video Format Descriptor */
+ struct {
+ uByte guidFormat[16];
+ uByte bBitsPerPixel;
+ uByte bDefaultFrameIndex;
+ uByte bAspectRatioX;
+ uByte bAspectRatioY;
+ uByte bmInterlaceFlags;
+ uByte bCopyProtect;
+ } uc;
+
+ /* Table 3-1: Frame Based Payload Video Format Descriptor */
+ struct {
+ uByte guidFormat[16];
+ uByte bBitsPerPixel;
+ uByte bDefaultFrameIndex;
+ uByte bAspectRatioX;
+ uByte bAspectRatioY;
+ uByte bmInterlaceFlags;
+ uByte bCopyProtect;
+ uByte bVariableSize;
+ } fb;
+
+ /* Table 3-1: H.264 Payload Video Format Descriptor */
+ struct {
+ uByte bDefaultFrameIndex;
+ uByte bMaxCodecConfigDelay;
+ uByte bmSupportedSliceModes;
+ uByte bmSupportedSyncFrameTypes;
+ uByte bResolutionScaling;
+ uByte _reserved1;
+ uByte bmSupportedRateControlModes;
+ uWord wMaxMBperSecOneResolutionNoScalability;
+ uWord wMaxMBperSecTwoResolutionsNoScalability;
+ uWord wMaxMBperSecThreeResolutionsNoScalability;
+ uWord wMaxMBperSecFourResolutionsNoScalability;
+ uWord wMaxMBperSecOneResolutionTemporalScalability;
+ uWord wMaxMBperSecTwoResolutionsTemporalScalablility;
+ uWord wMaxMBperSecThreeResolutionsTemporalScalability;
+ uWord wMaxMBperSecFourResolutionsTemporalScalability;
+ uWord wMaxMBperSecOneResolutionTemporalQualityScalability;
+ uWord wMaxMBperSecTwoResolutionsTemporalQualityScalability;
+ uWord wMaxMBperSecThreeResolutionsTemporalQualityScalablity;
+ uWord wMaxMBperSecFourResolutionsTemporalQualityScalability;
+ uWord wMaxMBperSecOneResolutionTemporalSpatialScalability;
+ uWord wMaxMBperSecTwoResolutionsTemporalSpatialScalability;
+ uWord wMaxMBperSecThreeResolutionsTemporalSpatialScalablity;
+ uWord wMaxMBperSecFourResolutionsTemporalSpatialScalability;
+ uWord wMaxMBperSecOneResolutionFullScalability;
+ uWord wMaxMBperSecTwoResolutionsFullScalability;
+ uWord wMaxMBperSecThreeResolutionsFullScalability;
+ uWord wMaxMBperSecFourResolutionsFullScalability;
+ } h264;
+ } u;
+
+#define UVIDEO_FORMAT_LEN(fmt) \
+ ( \
+ (((fmt)->bDescriptorSubtype == UDESCSUB_VS_FORMAT_H264) || \
+ ((fmt)->bDescriptorSubtype == UDESCSUB_VS_FORMAT_H264_SIMULCAST)) ? \
+ (offsetof(struct usb_video_format_desc, u) + \
+ sizeof(((struct usb_video_format_desc *)0)->u.h264)) : \
+ ((fmt)->bDescriptorSubtype == UDESCSUB_VS_FORMAT_FRAME_BASED) ? \
+ (offsetof(struct usb_video_format_desc, u) + \
+ sizeof(((struct usb_video_format_desc *)0)->u.fb)) : \
+ ((fmt)->bDescriptorSubtype == UDESCSUB_VS_FORMAT_UNCOMPRESSED) ? \
+ (offsetof(struct usb_video_format_desc, u) + \
+ sizeof(((struct usb_video_format_desc *)0)->u.uc)) : \
+ ((fmt)->bDescriptorSubtype == UDESCSUB_VS_FORMAT_MJPEG) ? \
+ (offsetof(struct usb_video_format_desc, u) + \
+ sizeof(((struct usb_video_format_desc *)0)->u.mjpeg)) : \
+ sizeof(struct usb_video_colorformat_desc) \
+ )
+
+} __packed;
+
+/*
+ * Driver specific private definitions.
+ */
+#define UVIDEO_NFRAMES_MAX 40
+
+#define UVIDEO_IXFERS 3
+struct uvideo_vs_iface {
+ uint8_t iface_index;
+ int numalts;
+ int curalt;
+ int endpoint;
+ uint32_t psize;
+ int bulk_endpoint;
+ int bulk_running;
+};
+
+struct uvideo_frame_buffer {
+ int sample;
+ uint8_t fid;
+ uint8_t error;
+ uint8_t mmap_q_full;
+ int offset;
+ int buf_size;
+ uint8_t *buf;
+ uint32_t fmt_flags;
+};
+
+/*
+ * 1920x1080 uncompressed (e.g. YUYV422) requires ~4MB image data per frame.
+ * 4MB * 8 frame buffers = 32MB kernel memory required.
+ * With 8 frame buffers we are pretty safe not to run out of kernel memory.
+ */
+#define UVIDEO_MAX_BUFFERS 8
+struct uvideo_mmap {
+ STAILQ_ENTRY(uvideo_mmap) q_frames;
+ uint8_t *buf;
+ struct v4l2_buffer v4l2_buf;
+};
+typedef STAILQ_HEAD(, uvideo_mmap) q_mmap;
+
+struct uvideo_format_group {
+ uint32_t pixelformat;
+ int has_colorformat;
+ uint32_t colorspace;
+ uint32_t xfer_func;
+ uint32_t ycbcr_enc;
+ uint8_t format_dfidx;
+ struct usb_video_format_desc *format;
+ /* frame descriptors for mjpeg and uncompressed are identical */
+#define UVIDEO_MAX_FRAME 32
+ struct usb_video_frame_desc *frame_cur;
+ struct usb_video_frame_desc *frame[UVIDEO_MAX_FRAME];
+ int frame_num;
+};
+
+struct uvideo_res {
+ int width;
+ int height;
+ int fidx;
+};
+
+struct uvideo_controls {
+ int cid;
+ int type;
+ char name[32];
+ uint8_t ctrl_bit;
+ uint16_t ctrl_selector;
+ uint16_t ctrl_len;
+ int sig;
+};
+
+#endif /* _UVIDEO_H_ */
diff --git a/sys/dev/usb/video/uvideo_v4l2.h b/sys/dev/usb/video/uvideo_v4l2.h
new file mode 100644
index 000000000000..05736bff3e9c
--- /dev/null
+++ b/sys/dev/usb/video/uvideo_v4l2.h
@@ -0,0 +1,483 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Video for Linux Two header file
+ *
+ * Copyright (C) 1999-2012 the contributors
+ * Copyright (C) 2012 Nokia Corporation
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The names of its contributors may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Minimal V4L2 definitions for the FreeBSD UVC driver.
+ * Extracted from OpenBSD sys/videoio.h.
+ */
+
+#ifndef _UVIDEO_V4L2_H_
+#define _UVIDEO_V4L2_H_
+
+#include <sys/types.h>
+#include <sys/ioccom.h>
+#include <sys/time.h>
+
+/*
+ * Four-character-code (FOURCC)
+ */
+#define v4l2_fourcc(a, b, c, d) \
+ ((u_int32_t)(a) | ((u_int32_t)(b) << 8) | \
+ ((u_int32_t)(c) << 16) | ((u_int32_t)(d) << 24))
+
+/*
+ * Enums
+ */
+enum v4l2_field {
+ V4L2_FIELD_ANY = 0,
+ V4L2_FIELD_NONE = 1,
+ V4L2_FIELD_TOP = 2,
+ V4L2_FIELD_BOTTOM = 3,
+ V4L2_FIELD_INTERLACED = 4,
+ V4L2_FIELD_SEQ_TB = 5,
+ V4L2_FIELD_SEQ_BT = 6,
+ V4L2_FIELD_ALTERNATE = 7,
+ V4L2_FIELD_INTERLACED_TB = 8,
+ V4L2_FIELD_INTERLACED_BT = 9,
+};
+
+enum v4l2_buf_type {
+ V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
+ V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
+ V4L2_BUF_TYPE_VBI_CAPTURE = 4,
+ V4L2_BUF_TYPE_VBI_OUTPUT = 5,
+ V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
+ V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
+ V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10,
+ V4L2_BUF_TYPE_SDR_CAPTURE = 11,
+ V4L2_BUF_TYPE_SDR_OUTPUT = 12,
+ V4L2_BUF_TYPE_META_CAPTURE = 13,
+ V4L2_BUF_TYPE_META_OUTPUT = 14,
+ /* Deprecated, do not use */
+ V4L2_BUF_TYPE_PRIVATE = 0x80,
+};
+
+enum v4l2_memory {
+ V4L2_MEMORY_MMAP = 1,
+ V4L2_MEMORY_USERPTR = 2,
+ V4L2_MEMORY_OVERLAY = 3,
+ V4L2_MEMORY_DMABUF = 4,
+};
+
+enum v4l2_colorspace {
+ V4L2_COLORSPACE_DEFAULT = 0,
+ V4L2_COLORSPACE_SMPTE170M = 1,
+ V4L2_COLORSPACE_SMPTE240M = 2,
+ V4L2_COLORSPACE_REC709 = 3,
+ V4L2_COLORSPACE_BT878 = 4,
+ V4L2_COLORSPACE_470_SYSTEM_M = 5,
+ V4L2_COLORSPACE_470_SYSTEM_BG = 6,
+ V4L2_COLORSPACE_JPEG = 7,
+ V4L2_COLORSPACE_SRGB = 8,
+ V4L2_COLORSPACE_OPRGB = 9,
+ V4L2_COLORSPACE_BT2020 = 10,
+ V4L2_COLORSPACE_RAW = 11,
+ V4L2_COLORSPACE_DCI_P3 = 12,
+};
+
+enum v4l2_xfer_func {
+ V4L2_XFER_FUNC_DEFAULT = 0,
+ V4L2_XFER_FUNC_709 = 1,
+ V4L2_XFER_FUNC_SRGB = 2,
+ V4L2_XFER_FUNC_OPRGB = 3,
+ V4L2_XFER_FUNC_SMPTE240M = 4,
+ V4L2_XFER_FUNC_NONE = 5,
+ V4L2_XFER_FUNC_DCI_P3 = 6,
+ V4L2_XFER_FUNC_SMPTE2084 = 7,
+};
+
+enum v4l2_ycbcr_encoding {
+ V4L2_YCBCR_ENC_DEFAULT = 0,
+ V4L2_YCBCR_ENC_601 = 1,
+ V4L2_YCBCR_ENC_709 = 2,
+ V4L2_YCBCR_ENC_XV601 = 3,
+ V4L2_YCBCR_ENC_XV709 = 4,
+ V4L2_YCBCR_ENC_SYCC = 5,
+ V4L2_YCBCR_ENC_BT2020 = 6,
+ V4L2_YCBCR_ENC_BT2020_CONST_LUM = 7,
+ V4L2_YCBCR_ENC_SMPTE240M = 8,
+};
+
+enum v4l2_ctrl_type {
+ V4L2_CTRL_TYPE_INTEGER = 1,
+ V4L2_CTRL_TYPE_BOOLEAN = 2,
+ V4L2_CTRL_TYPE_MENU = 3,
+ V4L2_CTRL_TYPE_BUTTON = 4,
+ V4L2_CTRL_TYPE_INTEGER64 = 5,
+ V4L2_CTRL_TYPE_CTRL_CLASS = 6,
+ V4L2_CTRL_TYPE_STRING = 7,
+ V4L2_CTRL_TYPE_BITMASK = 8,
+ V4L2_CTRL_TYPE_INTEGER_MENU = 9,
+};
+
+enum v4l2_frmsizetypes {
+ V4L2_FRMSIZE_TYPE_DISCRETE = 1,
+ V4L2_FRMSIZE_TYPE_CONTINUOUS = 2,
+ V4L2_FRMSIZE_TYPE_STEPWISE = 3,
+};
+
+enum v4l2_frmivaltypes {
+ V4L2_FRMIVAL_TYPE_DISCRETE = 1,
+ V4L2_FRMIVAL_TYPE_CONTINUOUS = 2,
+ V4L2_FRMIVAL_TYPE_STEPWISE = 3,
+};
+
+/*
+ * Structures
+ */
+struct v4l2_fract {
+ u_int32_t numerator;
+ u_int32_t denominator;
+};
+
+struct v4l2_capability {
+ u_int8_t driver[16];
+ u_int8_t card[32];
+ u_int8_t bus_info[32];
+ u_int32_t version;
+ u_int32_t capabilities;
+ u_int32_t device_caps;
+ u_int32_t reserved[3];
+};
+
+struct v4l2_pix_format {
+ u_int32_t width;
+ u_int32_t height;
+ u_int32_t pixelformat;
+ u_int32_t field; /* enum v4l2_field */
+ u_int32_t bytesperline; /* for padding, zero if unused */
+ u_int32_t sizeimage;
+ u_int32_t colorspace; /* enum v4l2_colorspace */
+ u_int32_t priv; /* private data, depends on pixelformat */
+ u_int32_t flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */
+ union {
+ /* enum v4l2_ycbcr_encoding */
+ u_int32_t ycbcr_enc;
+ /* enum v4l2_hsv_encoding */
+ u_int32_t hsv_enc;
+ };
+ u_int32_t quantization; /* enum v4l2_quantization */
+ u_int32_t xfer_func; /* enum v4l2_xfer_func */
+};
+
+/*
+ * v4l2_format: the system header (v4l_compat) includes struct v4l2_window
+ * (which contains a pointer) in the fmt union, forcing 8-byte alignment.
+ * This creates 4 bytes of implicit padding after 'type' on LP64.
+ * We must match this layout for ioctl ABI compatibility.
+ * Total size: 4 (type) + 4 (pad) + 200 (union) = 208.
+ */
+struct v4l2_format {
+ u_int32_t type;
+ u_int32_t _pad;
+ union {
+ struct v4l2_pix_format pix;
+ u_int8_t raw_data[200];
+ } fmt;
+};
+
+struct v4l2_fmtdesc {
+ u_int32_t index; /* Format number */
+ u_int32_t type; /* enum v4l2_buf_type */
+ u_int32_t flags;
+ u_int8_t description[32]; /* Description string */
+ u_int32_t pixelformat; /* Format fourcc */
+ u_int32_t mbus_code; /* Media bus code */
+ u_int32_t reserved[3];
+};
+
+struct v4l2_timecode {
+ u_int32_t type;
+ u_int32_t flags;
+ u_int8_t frames;
+ u_int8_t seconds;
+ u_int8_t minutes;
+ u_int8_t hours;
+ u_int8_t userbits[4];
+};
+
+struct v4l2_buffer {
+ u_int32_t index;
+ u_int32_t type;
+ u_int32_t bytesused;
+ u_int32_t flags;
+ u_int32_t field;
+ struct timeval timestamp;
+ struct v4l2_timecode timecode;
+ u_int32_t sequence;
+
+ /* memory location */
+ u_int32_t memory;
+ union {
+ u_int32_t offset;
+ unsigned long userptr;
+ int32_t fd;
+ } m;
+ u_int32_t length;
+ u_int32_t reserved2;
+ union {
+ int32_t request_fd;
+ u_int32_t reserved;
+ };
+};
+
+struct v4l2_requestbuffers {
+ u_int32_t count;
+ u_int32_t type; /* enum v4l2_buf_type */
+ u_int32_t memory; /* enum v4l2_memory */
+ u_int32_t capabilities;
+ u_int8_t flags;
+ u_int8_t reserved[3];
+};
+
+struct v4l2_captureparm {
+ u_int32_t capability; /* Supported modes */
+ u_int32_t capturemode; /* Current mode */
+ struct v4l2_fract timeperframe; /* Time per frame in seconds */
+ u_int32_t extendedmode; /* Driver-specific extensions */
+ u_int32_t readbuffers; /* # of buffers for read */
+ u_int32_t reserved[4];
+};
+
+/* Simplified v4l2_streamparm: only the capture member is included */
+struct v4l2_streamparm {
+ u_int32_t type; /* enum v4l2_buf_type */
+ union {
+ struct v4l2_captureparm capture;
+ u_int8_t raw_data[200];
+ } parm;
+};
+
+struct v4l2_input {
+ u_int32_t index; /* Which input */
+ u_int8_t name[32]; /* Label */
+ u_int32_t type; /* Type of input */
+ u_int32_t audioset; /* Associated audios (bitfield) */
+ u_int32_t tuner; /* Tuner index */
+ u_int64_t std;
+ u_int32_t status;
+ u_int32_t capabilities;
+ u_int32_t reserved[3];
+};
+
+struct v4l2_control {
+ u_int32_t id;
+ int32_t value;
+};
+
+struct v4l2_queryctrl {
+ u_int32_t id;
+ u_int32_t type; /* enum v4l2_ctrl_type */
+ u_int8_t name[32]; /* Whatever */
+ int32_t minimum; /* Note signedness */
+ int32_t maximum;
+ int32_t step;
+ int32_t default_value;
+ u_int32_t flags;
+ u_int32_t reserved[2];
+};
+
+struct v4l2_frmsize_discrete {
+ u_int32_t width; /* Frame width [pixel] */
+ u_int32_t height; /* Frame height [pixel] */
+};
+
+struct v4l2_frmsize_stepwise {
+ u_int32_t min_width; /* Minimum frame width [pixel] */
+ u_int32_t max_width; /* Maximum frame width [pixel] */
+ u_int32_t step_width; /* Frame width step size [pixel] */
+ u_int32_t min_height; /* Minimum frame height [pixel] */
+ u_int32_t max_height; /* Maximum frame height [pixel] */
+ u_int32_t step_height; /* Frame height step size [pixel] */
+};
+
+struct v4l2_frmsizeenum {
+ u_int32_t index; /* Frame size number */
+ u_int32_t pixel_format; /* Pixel format */
+ u_int32_t type; /* Frame size type the device supports. */
+
+ union { /* Frame size */
+ struct v4l2_frmsize_discrete discrete;
+ struct v4l2_frmsize_stepwise stepwise;
+ };
+
+ u_int32_t reserved[2]; /* Reserved space for future use */
+};
+
+struct v4l2_frmival_stepwise {
+ struct v4l2_fract min; /* Minimum frame interval [s] */
+ struct v4l2_fract max; /* Maximum frame interval [s] */
+ struct v4l2_fract step; /* Frame interval step size [s] */
+};
+
+struct v4l2_frmivalenum {
+ u_int32_t index; /* Frame format index */
+ u_int32_t pixel_format; /* Pixel format */
+ u_int32_t width; /* Frame width */
+ u_int32_t height; /* Frame height */
+ u_int32_t type; /* Frame interval type the device supports. */
+
+ union { /* Frame interval */
+ struct v4l2_fract discrete;
+ struct v4l2_frmival_stepwise stepwise;
+ };
+
+ u_int32_t reserved[2]; /* Reserved space for future use */
+};
+
+/*
+ * Pixel formats
+ */
+
+/* Luminance+Chrominance formats */
+#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V')
+#define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y', 'V', '1', '2')
+#define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y', 'U', '1', '2')
+
+/* Compressed formats */
+#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G')
+#define V4L2_PIX_FMT_H264 v4l2_fourcc('H', '2', '6', '4')
+#define V4L2_PIX_FMT_HEVC v4l2_fourcc('H', 'E', 'V', 'C')
+
+/* Grey formats */
+#define V4L2_PIX_FMT_GREY v4l2_fourcc('G', 'R', 'E', 'Y')
+#define V4L2_PIX_FMT_Y10 v4l2_fourcc('Y', '1', '0', ' ')
+
+/* RGB formats */
+#define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P')
+#define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B', 'G', 'R', '3')
+#define V4L2_PIX_FMT_XBGR32 v4l2_fourcc('X', 'R', '2', '4')
+
+/* Bayer formats */
+#define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B', 'A', '8', '1')
+#define V4L2_PIX_FMT_SGBRG8 v4l2_fourcc('G', 'B', 'R', 'G')
+#define V4L2_PIX_FMT_SGRBG8 v4l2_fourcc('G', 'R', 'B', 'G')
+#define V4L2_PIX_FMT_SRGGB8 v4l2_fourcc('R', 'G', 'G', 'B')
+#define V4L2_PIX_FMT_SBGGR16 v4l2_fourcc('B', 'Y', 'R', '2')
+#define V4L2_PIX_FMT_SGBRG16 v4l2_fourcc('G', 'B', '1', '6')
+#define V4L2_PIX_FMT_SRGGB16 v4l2_fourcc('R', 'G', '1', '6')
+#define V4L2_PIX_FMT_SGRBG16 v4l2_fourcc('G', 'R', '1', '6')
+#define V4L2_PIX_FMT_SRGGB10P v4l2_fourcc('p', 'R', 'A', 'A')
+
+/* Depth format */
+#define V4L2_PIX_FMT_Z16 v4l2_fourcc('Z', '1', '6', ' ')
+
+/*
+ * Capability flags
+ */
+#define V4L2_CAP_VIDEO_CAPTURE 0x00000001
+#define V4L2_CAP_STREAMING 0x04000000
+#define V4L2_CAP_EXT_PIX_FORMAT 0x00200000
+#define V4L2_CAP_READWRITE 0x01000000
+#define V4L2_CAP_DEVICE_CAPS 0x80000000
+#define V4L2_CAP_TIMEPERFRAME 0x1000
+
+/*
+ * Buffer flags
+ */
+#define V4L2_BUF_FLAG_MAPPED 0x00000001
+#define V4L2_BUF_FLAG_QUEUED 0x00000002
+#define V4L2_BUF_FLAG_DONE 0x00000004
+#define V4L2_BUF_FLAG_ERROR 0x00000040
+#define V4L2_BUF_FLAG_TIMESTAMP_MASK 0x0000e000
+#define V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC 0x00002000
+#define V4L2_BUF_FLAG_TSTAMP_SRC_MASK 0x00070000
+#define V4L2_BUF_FLAG_TSTAMP_SRC_EOF 0x00000000
+#define V4L2_BUF_FLAG_TIMECODE 0x00000100
+
+/*
+ * Format flags
+ */
+#define V4L2_FMT_FLAG_COMPRESSED 0x0001
+
+/*
+ * Buffer capabilities
+ */
+#define V4L2_BUF_CAP_SUPPORTS_MMAP (1 << 0)
+
+/*
+ * Input types
+ */
+#define V4L2_INPUT_TYPE_CAMERA 2
+
+/*
+ * Control IDs
+ */
+#define V4L2_CTRL_CLASS_USER 0x00980000
+#define V4L2_CID_BASE (V4L2_CTRL_CLASS_USER | 0x900)
+#define V4L2_CID_BRIGHTNESS (V4L2_CID_BASE + 0)
+#define V4L2_CID_CONTRAST (V4L2_CID_BASE + 1)
+#define V4L2_CID_SATURATION (V4L2_CID_BASE + 2)
+#define V4L2_CID_HUE (V4L2_CID_BASE + 3)
+#define V4L2_CID_AUTO_WHITE_BALANCE (V4L2_CID_BASE + 12)
+#define V4L2_CID_RED_BALANCE (V4L2_CID_BASE + 14)
+#define V4L2_CID_BLUE_BALANCE (V4L2_CID_BASE + 15)
+#define V4L2_CID_GAMMA (V4L2_CID_BASE + 16)
+#define V4L2_CID_GAIN (V4L2_CID_BASE + 19)
+#define V4L2_CID_POWER_LINE_FREQUENCY (V4L2_CID_BASE + 24)
+#define V4L2_CID_HUE_AUTO (V4L2_CID_BASE + 25)
+#define V4L2_CID_WHITE_BALANCE_TEMPERATURE (V4L2_CID_BASE + 26)
+#define V4L2_CID_SHARPNESS (V4L2_CID_BASE + 27)
+#define V4L2_CID_BACKLIGHT_COMPENSATION (V4L2_CID_BASE + 28)
+
+/*
+ * V4L2 ioctl definitions
+ */
+#define VIDIOC_QUERYCAP _IOR('V', 0, struct v4l2_capability)
+#define VIDIOC_ENUM_FMT _IOWR('V', 2, struct v4l2_fmtdesc)
+#define VIDIOC_G_FMT _IOWR('V', 4, struct v4l2_format)
+#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)
+#define VIDIOC_REQBUFS _IOWR('V', 8, struct v4l2_requestbuffers)
+#define VIDIOC_QUERYBUF _IOWR('V', 9, struct v4l2_buffer)
+#define VIDIOC_QBUF _IOWR('V', 15, struct v4l2_buffer)
+#define VIDIOC_DQBUF _IOWR('V', 17, struct v4l2_buffer)
+#define VIDIOC_STREAMON _IOW('V', 18, int)
+#define VIDIOC_STREAMOFF _IOW('V', 19, int)
+#define VIDIOC_G_PARM _IOWR('V', 21, struct v4l2_streamparm)
+#define VIDIOC_S_PARM _IOWR('V', 22, struct v4l2_streamparm)
+#define VIDIOC_ENUMINPUT _IOWR('V', 26, struct v4l2_input)
+#define VIDIOC_G_CTRL _IOWR('V', 27, struct v4l2_control)
+#define VIDIOC_S_CTRL _IOWR('V', 28, struct v4l2_control)
+#define VIDIOC_QUERYCTRL _IOWR('V', 36, struct v4l2_queryctrl)
+#define VIDIOC_G_INPUT _IOR('V', 38, int)
+#define VIDIOC_S_INPUT _IOWR('V', 39, int)
+#define VIDIOC_G_PRIORITY _IOR('V', 67, u_int32_t)
+#define VIDIOC_S_PRIORITY _IOW('V', 68, u_int32_t)
+#define VIDIOC_TRY_FMT _IOWR('V', 64, struct v4l2_format)
+#define VIDIOC_ENUM_FRAMESIZES _IOWR('V', 74, struct v4l2_frmsizeenum)
+#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
+
+#endif /* _UVIDEO_V4L2_H_ */
diff --git a/sys/modules/usb/Makefile b/sys/modules/usb/Makefile
index d9b1c8635b30..033c83ad1bdf 100644
--- a/sys/modules/usb/Makefile
+++ b/sys/modules/usb/Makefile
@@ -52,6 +52,7 @@ SUBDIR += ulpt umb umct umcs umodem umoscom uplcom uslcom uvisor uvscom
SUBDIR += i2ctinyusb
SUBDIR += cp2112
SUBDIR += udl
+SUBDIR += uvideo
SUBDIR += uether aue axe axge cdce cdceem cue ${_kue} mos rue smsc udav uhso \
ipheth
SUBDIR += muge
diff --git a/sys/modules/usb/uvideo/Makefile b/sys/modules/usb/uvideo/Makefile
new file mode 100644
index 000000000000..ccc1bfe64aa6
--- /dev/null
+++ b/sys/modules/usb/uvideo/Makefile
@@ -0,0 +1,10 @@
+S= ${SRCTOP}/sys
+
+.PATH: $S/dev/usb/video
+
+KMOD= uvideo
+SRCS= opt_bus.h opt_usb.h device_if.h \
+ bus_if.h usb_if.h usbdevs.h \
+ uvideo.c
+
+.include <bsd.kmod.mk>