git: b711ef9c75ba - main - uvideo: import uvideo(4) driver from OpenBSD

From: Baptiste Daroussin <bapt_at_FreeBSD.org>
Date: Tue, 16 Jun 2026 11:23:02 UTC
The branch main has been updated by bapt:

URL: https://cgit.FreeBSD.org/src/commit/?id=b711ef9c75ba71d6258d028aaf7b119ccfffaa0a

commit b711ef9c75ba71d6258d028aaf7b119ccfffaa0a
Author:     Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-05-05 20:29:47 +0000
Commit:     Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-06-16 11:20:52 +0000

    uvideo: import uvideo(4) driver from OpenBSD
    
    Port the uvideo(4) driver from OpenBSD. This provides
    native USB Video Class (UVC) support for webcams and video capture
    devices.
    
    The main changes are adaptation for:
    - USB transfer callback model
    - isoc data extraction via usbd_copy_out(),
    - V4L2 struct alignment for ABI compatibility with v4l_compat.
    
    Note that this implementation can coexist with webcamd.
    
    Reviewed by:    manu
    Differential Revision:  https://reviews.freebsd.org/D56960
---
 sys/conf/files                  |    1 +
 sys/dev/usb/usb.h               |    6 +
 sys/dev/usb/video/uvideo.c      | 3493 +++++++++++++++++++++++++++++++++++++++
 sys/dev/usb/video/uvideo.h      |  687 ++++++++
 sys/dev/usb/video/uvideo_v4l2.h |  483 ++++++
 sys/modules/usb/Makefile        |    1 +
 sys/modules/usb/uvideo/Makefile |   10 +
 7 files changed, 4681 insertions(+)

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);
*** 3786 LINES SKIPPED ***