git: b711ef9c75ba - main - uvideo: import uvideo(4) driver from OpenBSD
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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 ***