The branch main has been updated by bapt:

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

commit b711ef9c75ba71d6258d028aaf7b119ccfffaa0a
Author:     Baptiste Daroussin <[email protected]>
AuthorDate: 2026-05-05 20:29:47 +0000
Commit:     Baptiste Daroussin <[email protected]>
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 <[email protected]>
+ * Copyright (c) 2008 Marcus Glocker <[email protected]>
+ *
+ * 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 <[email protected]>
+ */
+
+/*
+ * 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 ***

Reply via email to