diff options
Diffstat (limited to 'sys')
| -rw-r--r-- | sys/conf/files | 1 | ||||
| -rw-r--r-- | sys/dev/usb/usb.h | 6 | ||||
| -rw-r--r-- | sys/dev/usb/video/uvideo.c | 3493 | ||||
| -rw-r--r-- | sys/dev/usb/video/uvideo.h | 687 | ||||
| -rw-r--r-- | sys/dev/usb/video/uvideo_v4l2.h | 483 | ||||
| -rw-r--r-- | sys/modules/usb/Makefile | 1 | ||||
| -rw-r--r-- | sys/modules/usb/uvideo/Makefile | 10 |
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> |
