f2debec892
Previously, on a 30fps YUV422 640x480 webcam, we were putting over 250 USB (micro)frames per video frame in the host controller queue. xhci(4) is currently limited to 256-1 TRBs per xHC Transfer Ring, and as such, trying to place 3 xfers each of 250+ microframes in the queue fails. As there is no UVC requirement that whole video frames be in one logical chunk of isoc transactions, and there doesn't seem to be compelling reason to keep the xfer completion rate slower than 1 in 10ms, we can limit each of the 3 uvideo xfers to 80 (micro)frames of bus time, and solve the Transfer Ring constraint for upcoming xhci(4) Isochronous pipe support. This works out to using only 240 TRBs on the 255-usable-TRB Transfer Ring.
3040 lines
81 KiB
C
3040 lines
81 KiB
C
/* $NetBSD: uvideo.c,v 1.58 2020/05/24 17:28:20 jakllsch Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2008 Patrick Mahoney
|
|
* All rights reserved.
|
|
*
|
|
* This code was written by Patrick Mahoney (pat@polycrystal.org) as
|
|
* part of Google Summer of Code 2008.
|
|
*
|
|
* 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. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the NetBSD
|
|
* Foundation, Inc. and its contributors.
|
|
* 4. Neither the name of The NetBSD Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
|
|
*/
|
|
|
|
/*
|
|
* USB video specs:
|
|
* http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: uvideo.c,v 1.58 2020/05/24 17:28:20 jakllsch Exp $");
|
|
|
|
#ifdef _KERNEL_OPT
|
|
#include "opt_usb.h"
|
|
#endif
|
|
|
|
#ifdef _MODULE
|
|
#include <sys/module.h>
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/device.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/file.h>
|
|
#include <sys/select.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/queue.h> /* SLIST */
|
|
#include <sys/kthread.h>
|
|
#include <sys/bus.h>
|
|
|
|
#include <sys/videoio.h>
|
|
#include <dev/video_if.h>
|
|
|
|
#include <dev/usb/usb.h>
|
|
#include <dev/usb/usbdi.h>
|
|
#include <dev/usb/usbdivar.h>
|
|
#include <dev/usb/usbdi_util.h>
|
|
#include <dev/usb/usb_quirks.h>
|
|
|
|
#include <dev/usb/uvideoreg.h>
|
|
|
|
#define UVIDEO_NXFERS 3
|
|
#define UVIDEO_NFRAMES_MAX 80
|
|
#define PRI_UVIDEO PRI_BIO
|
|
|
|
/* #define UVIDEO_DISABLE_MJPEG */
|
|
|
|
#ifdef UVIDEO_DEBUG
|
|
#define DPRINTF(x) do { if (uvideodebug) printf x; } while (0)
|
|
#define DPRINTFN(n,x) do { if (uvideodebug>(n)) printf x; } while (0)
|
|
int uvideodebug = 20;
|
|
#else
|
|
#define DPRINTF(x)
|
|
#define DPRINTFN(n,x)
|
|
#endif
|
|
|
|
typedef enum {
|
|
UVIDEO_STATE_CLOSED,
|
|
UVIDEO_STATE_OPENING,
|
|
UVIDEO_STATE_IDLE
|
|
} uvideo_state;
|
|
|
|
struct uvideo_camera_terminal {
|
|
uint16_t ct_objective_focal_min;
|
|
uint16_t ct_objective_focal_max;
|
|
uint16_t ct_ocular_focal_length;
|
|
};
|
|
|
|
struct uvideo_processing_unit {
|
|
uint16_t pu_max_multiplier; /* digital zoom */
|
|
uint8_t pu_video_standards;
|
|
};
|
|
|
|
struct uvideo_extension_unit {
|
|
guid_t xu_guid;
|
|
};
|
|
|
|
/* For simplicity, we consider a Terminal a special case of Unit
|
|
* rather than a separate entity. */
|
|
struct uvideo_unit {
|
|
uint8_t vu_id;
|
|
uint8_t vu_type;
|
|
uint8_t vu_dst_id;
|
|
uint8_t vu_nsrcs;
|
|
union {
|
|
uint8_t vu_src_id; /* vu_nsrcs = 1 */
|
|
uint8_t *vu_src_id_ary; /* vu_nsrcs > 1 */
|
|
} s;
|
|
|
|
/* fields for individual unit/terminal types */
|
|
union {
|
|
struct uvideo_camera_terminal vu_camera;
|
|
struct uvideo_processing_unit vu_processing;
|
|
struct uvideo_extension_unit vu_extension;
|
|
} u;
|
|
|
|
/* Used by camera terminal, processing and extention units. */
|
|
uint8_t vu_control_size; /* number of bytes in vu_controls */
|
|
uint8_t *vu_controls; /* array of bytes. bits are
|
|
* numbered from 0 at least
|
|
* significant bit to
|
|
* (8*vu_control_size - 1)*/
|
|
};
|
|
|
|
struct uvideo_alternate {
|
|
uint8_t altno;
|
|
uint8_t interval;
|
|
uint16_t max_packet_size;
|
|
SLIST_ENTRY(uvideo_alternate) entries;
|
|
};
|
|
SLIST_HEAD(altlist, uvideo_alternate);
|
|
|
|
#define UVIDEO_FORMAT_GET_FORMAT_INDEX(fmt) \
|
|
((fmt)->format.priv & 0xff)
|
|
#define UVIDEO_FORMAT_GET_FRAME_INDEX(fmt) \
|
|
(((fmt)->format.priv >> 8) & 0xff)
|
|
/* TODO: find a better way to set bytes within this 32 bit value? */
|
|
#define UVIDEO_FORMAT_SET_FORMAT_INDEX(fmt, index) do { \
|
|
(fmt)->format.priv &= ~0xff; \
|
|
(fmt)->format.priv |= ((index) & 0xff); \
|
|
} while (0)
|
|
#define UVIDEO_FORMAT_SET_FRAME_INDEX(fmt, index) do { \
|
|
(fmt)->format.priv &= ~(0xff << 8); \
|
|
((fmt)->format.priv |= (((index) & 0xff) << 8)); \
|
|
} while (0)
|
|
|
|
struct uvideo_pixel_format {
|
|
enum video_pixel_format pixel_format;
|
|
SIMPLEQ_ENTRY(uvideo_pixel_format) entries;
|
|
};
|
|
SIMPLEQ_HEAD(uvideo_pixel_format_list, uvideo_pixel_format);
|
|
|
|
struct uvideo_format {
|
|
struct video_format format;
|
|
SIMPLEQ_ENTRY(uvideo_format) entries;
|
|
};
|
|
SIMPLEQ_HEAD(uvideo_format_list, uvideo_format);
|
|
|
|
struct uvideo_isoc_xfer;
|
|
struct uvideo_stream;
|
|
|
|
struct uvideo_isoc {
|
|
struct uvideo_isoc_xfer *i_ix;
|
|
struct uvideo_stream *i_vs;
|
|
struct usbd_xfer *i_xfer;
|
|
uint8_t *i_buf;
|
|
uint16_t *i_frlengths;
|
|
};
|
|
|
|
struct uvideo_isoc_xfer {
|
|
uint8_t ix_endpt;
|
|
struct usbd_pipe *ix_pipe;
|
|
struct uvideo_isoc ix_i[UVIDEO_NXFERS];
|
|
uint32_t ix_nframes;
|
|
uint32_t ix_uframe_len;
|
|
|
|
struct altlist ix_altlist;
|
|
};
|
|
|
|
struct uvideo_bulk_xfer {
|
|
uint8_t bx_endpt;
|
|
struct usbd_pipe *bx_pipe;
|
|
struct usbd_xfer *bx_xfer;
|
|
uint8_t *bx_buffer;
|
|
int bx_buflen;
|
|
bool bx_running;
|
|
kcondvar_t bx_cv;
|
|
kmutex_t bx_lock;
|
|
};
|
|
|
|
struct uvideo_stream {
|
|
struct uvideo_softc *vs_parent;
|
|
struct usbd_interface *vs_iface;
|
|
uint8_t vs_ifaceno;
|
|
uint8_t vs_subtype; /* input or output */
|
|
uint16_t vs_probelen; /* length of probe and
|
|
* commit data; varies
|
|
* depending on version
|
|
* of spec. */
|
|
struct uvideo_format_list vs_formats;
|
|
struct uvideo_pixel_format_list vs_pixel_formats;
|
|
struct video_format *vs_default_format;
|
|
struct video_format vs_current_format;
|
|
|
|
/* usb transfer details */
|
|
uint8_t vs_xfer_type;
|
|
union {
|
|
struct uvideo_bulk_xfer bulk;
|
|
struct uvideo_isoc_xfer isoc;
|
|
} vs_xfer;
|
|
|
|
int vs_frameno; /* toggles between 0 and 1 */
|
|
|
|
/* current video format */
|
|
uint32_t vs_max_payload_size;
|
|
uint32_t vs_frame_interval;
|
|
SLIST_ENTRY(uvideo_stream) entries;
|
|
};
|
|
SLIST_HEAD(uvideo_stream_list, uvideo_stream);
|
|
|
|
struct uvideo_softc {
|
|
device_t sc_dev; /* base device */
|
|
struct usbd_device *sc_udev; /* device */
|
|
struct usbd_interface *sc_iface; /* interface handle */
|
|
int sc_ifaceno; /* interface number */
|
|
char *sc_devname;
|
|
|
|
device_t sc_videodev;
|
|
|
|
int sc_dying;
|
|
uvideo_state sc_state;
|
|
|
|
uint8_t sc_nunits;
|
|
struct uvideo_unit **sc_unit;
|
|
|
|
struct uvideo_stream *sc_stream_in;
|
|
|
|
struct uvideo_stream_list sc_stream_list;
|
|
|
|
char sc_businfo[32];
|
|
};
|
|
|
|
static int uvideo_match(device_t, cfdata_t, void *);
|
|
static void uvideo_attach(device_t, device_t, void *);
|
|
static int uvideo_detach(device_t, int);
|
|
static void uvideo_childdet(device_t, device_t);
|
|
static int uvideo_activate(device_t, enum devact);
|
|
|
|
static int uvideo_open(void *, int);
|
|
static void uvideo_close(void *);
|
|
static const char * uvideo_get_devname(void *);
|
|
static const char * uvideo_get_businfo(void *);
|
|
|
|
static int uvideo_enum_format(void *, uint32_t, struct video_format *);
|
|
static int uvideo_get_format(void *, struct video_format *);
|
|
static int uvideo_set_format(void *, struct video_format *);
|
|
static int uvideo_try_format(void *, struct video_format *);
|
|
static int uvideo_get_framerate(void *, struct video_fract *);
|
|
static int uvideo_set_framerate(void *, struct video_fract *);
|
|
static int uvideo_start_transfer(void *);
|
|
static int uvideo_stop_transfer(void *);
|
|
|
|
static int uvideo_get_control_group(void *,
|
|
struct video_control_group *);
|
|
static int uvideo_set_control_group(void *,
|
|
const struct video_control_group *);
|
|
|
|
static usbd_status uvideo_init_control(
|
|
struct uvideo_softc *,
|
|
const usb_interface_descriptor_t *,
|
|
usbd_desc_iter_t *);
|
|
static usbd_status uvideo_init_collection(
|
|
struct uvideo_softc *,
|
|
const usb_interface_descriptor_t *,
|
|
usbd_desc_iter_t *);
|
|
|
|
/* Functions for unit & terminal descriptors */
|
|
static struct uvideo_unit * uvideo_unit_alloc(const uvideo_descriptor_t *);
|
|
static usbd_status uvideo_unit_init(struct uvideo_unit *,
|
|
const uvideo_descriptor_t *);
|
|
static void uvideo_unit_free(struct uvideo_unit *);
|
|
static usbd_status uvideo_unit_alloc_controls(struct uvideo_unit *,
|
|
uint8_t,
|
|
const uint8_t *);
|
|
static void uvideo_unit_free_controls(struct uvideo_unit *);
|
|
static usbd_status uvideo_unit_alloc_sources(struct uvideo_unit *,
|
|
uint8_t,
|
|
const uint8_t *);
|
|
static void uvideo_unit_free_sources(struct uvideo_unit *);
|
|
|
|
|
|
|
|
|
|
/* Functions for uvideo_stream, primary unit associated with a video
|
|
* driver or device file. */
|
|
static struct uvideo_stream * uvideo_find_stream(struct uvideo_softc *,
|
|
uint8_t);
|
|
#if 0
|
|
static struct uvideo_format * uvideo_stream_find_format(
|
|
struct uvideo_stream *,
|
|
uint8_t, uint8_t);
|
|
#endif
|
|
static struct uvideo_format * uvideo_stream_guess_format(
|
|
struct uvideo_stream *,
|
|
enum video_pixel_format, uint32_t, uint32_t);
|
|
static struct uvideo_stream * uvideo_stream_alloc(void);
|
|
static usbd_status uvideo_stream_init(
|
|
struct uvideo_stream *,
|
|
struct uvideo_softc *,
|
|
const usb_interface_descriptor_t *,
|
|
uint8_t);
|
|
static usbd_status uvideo_stream_init_desc(
|
|
struct uvideo_stream *,
|
|
const usb_interface_descriptor_t *,
|
|
usbd_desc_iter_t *);
|
|
static usbd_status uvideo_stream_init_frame_based_format(
|
|
struct uvideo_stream *,
|
|
const uvideo_descriptor_t *,
|
|
usbd_desc_iter_t *);
|
|
static void uvideo_stream_free(struct uvideo_stream *);
|
|
|
|
static int uvideo_stream_start_xfer(struct uvideo_stream *);
|
|
static int uvideo_stream_stop_xfer(struct uvideo_stream *);
|
|
static usbd_status uvideo_stream_recv_process(struct uvideo_stream *,
|
|
uint8_t *, uint32_t);
|
|
static usbd_status uvideo_stream_recv_isoc_start(struct uvideo_stream *);
|
|
static usbd_status uvideo_stream_recv_isoc_start1(struct uvideo_isoc *);
|
|
static void uvideo_stream_recv_isoc_complete(struct usbd_xfer *,
|
|
void *,
|
|
usbd_status);
|
|
static void uvideo_stream_recv_bulk_transfer(void *);
|
|
|
|
/* format probe and commit */
|
|
#define uvideo_stream_probe(vs, act, data) \
|
|
(uvideo_stream_probe_and_commit((vs), (act), \
|
|
UVIDEO_VS_PROBE_CONTROL, (data)))
|
|
#define uvideo_stream_commit(vs, act, data) \
|
|
(uvideo_stream_probe_and_commit((vs), (act), \
|
|
UVIDEO_VS_COMMIT_CONTROL, (data)))
|
|
static usbd_status uvideo_stream_probe_and_commit(struct uvideo_stream *,
|
|
uint8_t, uint8_t,
|
|
void *);
|
|
static void uvideo_init_probe_data(uvideo_probe_and_commit_data_t *);
|
|
|
|
|
|
static int usb_guid_cmp(const usb_guid_t *, const guid_t *);
|
|
|
|
|
|
CFATTACH_DECL2_NEW(uvideo, sizeof(struct uvideo_softc),
|
|
uvideo_match, uvideo_attach, uvideo_detach, uvideo_activate, NULL,
|
|
uvideo_childdet);
|
|
|
|
|
|
|
|
|
|
static const struct video_hw_if uvideo_hw_if = {
|
|
.open = uvideo_open,
|
|
.close = uvideo_close,
|
|
.get_devname = uvideo_get_devname,
|
|
.get_businfo = uvideo_get_businfo,
|
|
.enum_format = uvideo_enum_format,
|
|
.get_format = uvideo_get_format,
|
|
.set_format = uvideo_set_format,
|
|
.try_format = uvideo_try_format,
|
|
.get_framerate = uvideo_get_framerate,
|
|
.set_framerate = uvideo_set_framerate,
|
|
.start_transfer = uvideo_start_transfer,
|
|
.stop_transfer = uvideo_stop_transfer,
|
|
.control_iter_init = NULL,
|
|
.control_iter_next = NULL,
|
|
.get_control_desc_group = NULL,
|
|
.get_control_group = uvideo_get_control_group,
|
|
.set_control_group = uvideo_set_control_group,
|
|
};
|
|
|
|
#ifdef UVIDEO_DEBUG
|
|
/* Some functions to print out descriptors. Mostly useless other than
|
|
* debugging/exploration purposes. */
|
|
static void usb_guid_print(const usb_guid_t *);
|
|
static void print_descriptor(const usb_descriptor_t *);
|
|
static void print_interface_descriptor(const usb_interface_descriptor_t *);
|
|
static void print_endpoint_descriptor(const usb_endpoint_descriptor_t *);
|
|
|
|
static void print_vc_descriptor(const usb_descriptor_t *);
|
|
static void print_vs_descriptor(const usb_descriptor_t *);
|
|
|
|
static void print_vc_header_descriptor(
|
|
const uvideo_vc_header_descriptor_t *);
|
|
static void print_input_terminal_descriptor(
|
|
const uvideo_input_terminal_descriptor_t *);
|
|
static void print_output_terminal_descriptor(
|
|
const uvideo_output_terminal_descriptor_t *);
|
|
static void print_camera_terminal_descriptor(
|
|
const uvideo_camera_terminal_descriptor_t *);
|
|
static void print_selector_unit_descriptor(
|
|
const uvideo_selector_unit_descriptor_t *);
|
|
static void print_processing_unit_descriptor(
|
|
const uvideo_processing_unit_descriptor_t *);
|
|
static void print_extension_unit_descriptor(
|
|
const uvideo_extension_unit_descriptor_t *);
|
|
static void print_interrupt_endpoint_descriptor(
|
|
const uvideo_vc_interrupt_endpoint_descriptor_t *);
|
|
|
|
static void print_vs_input_header_descriptor(
|
|
const uvideo_vs_input_header_descriptor_t *);
|
|
static void print_vs_output_header_descriptor(
|
|
const uvideo_vs_output_header_descriptor_t *);
|
|
|
|
static void print_vs_format_uncompressed_descriptor(
|
|
const uvideo_vs_format_uncompressed_descriptor_t *);
|
|
static void print_vs_frame_uncompressed_descriptor(
|
|
const uvideo_vs_frame_uncompressed_descriptor_t *);
|
|
static void print_vs_format_mjpeg_descriptor(
|
|
const uvideo_vs_format_mjpeg_descriptor_t *);
|
|
static void print_vs_frame_mjpeg_descriptor(
|
|
const uvideo_vs_frame_mjpeg_descriptor_t *);
|
|
static void print_vs_format_dv_descriptor(
|
|
const uvideo_vs_format_dv_descriptor_t *);
|
|
#endif /* !UVIDEO_DEBUG */
|
|
|
|
#define GET(type, descp, field) (((const type *)(descp))->field)
|
|
#define GETP(type, descp, field) (&(((const type *)(descp))->field))
|
|
|
|
/* Given a format descriptor and frame descriptor, copy values common
|
|
* to all formats into a struct uvideo_format. */
|
|
#define UVIDEO_FORMAT_INIT_FRAME_BASED(format_type, format_desc, \
|
|
frame_type, frame_desc, \
|
|
format) \
|
|
do { \
|
|
UVIDEO_FORMAT_SET_FORMAT_INDEX( \
|
|
format, \
|
|
GET(format_type, format_desc, bFormatIndex)); \
|
|
UVIDEO_FORMAT_SET_FRAME_INDEX( \
|
|
format, \
|
|
GET(frame_type, frame_desc, bFrameIndex)); \
|
|
format->format.width = \
|
|
UGETW(GET(frame_type, frame_desc, wWidth)); \
|
|
format->format.height = \
|
|
UGETW(GET(frame_type, frame_desc, wHeight)); \
|
|
format->format.aspect_x = \
|
|
GET(format_type, format_desc, bAspectRatioX); \
|
|
format->format.aspect_y = \
|
|
GET(format_type, format_desc, bAspectRatioY); \
|
|
} while (0)
|
|
|
|
|
|
static int
|
|
uvideo_match(device_t parent, cfdata_t match, void *aux)
|
|
{
|
|
struct usbif_attach_arg *uiaa = aux;
|
|
|
|
/* TODO: May need to change in the future to work with
|
|
* Interface Association Descriptor. */
|
|
|
|
/* Trigger on the Video Control Interface which must be present */
|
|
if (uiaa->uiaa_class == UICLASS_VIDEO &&
|
|
uiaa->uiaa_subclass == UISUBCLASS_VIDEOCONTROL)
|
|
return UMATCH_IFACECLASS_IFACESUBCLASS;
|
|
|
|
return UMATCH_NONE;
|
|
}
|
|
|
|
static void
|
|
uvideo_attach(device_t parent, device_t self, void *aux)
|
|
{
|
|
struct uvideo_softc *sc = device_private(self);
|
|
struct usbif_attach_arg *uiaa = aux;
|
|
usbd_desc_iter_t iter;
|
|
const usb_interface_descriptor_t *ifdesc;
|
|
struct uvideo_stream *vs;
|
|
usbd_status err;
|
|
uint8_t ifaceidx;
|
|
|
|
sc->sc_dev = self;
|
|
|
|
sc->sc_devname = usbd_devinfo_alloc(uiaa->uiaa_device, 0);
|
|
|
|
aprint_naive("\n");
|
|
aprint_normal(": %s\n", sc->sc_devname);
|
|
|
|
sc->sc_udev = uiaa->uiaa_device;
|
|
sc->sc_iface = uiaa->uiaa_iface;
|
|
sc->sc_ifaceno = uiaa->uiaa_ifaceno;
|
|
sc->sc_dying = 0;
|
|
sc->sc_state = UVIDEO_STATE_CLOSED;
|
|
SLIST_INIT(&sc->sc_stream_list);
|
|
snprintf(sc->sc_businfo, sizeof(sc->sc_businfo), "usb:%08x",
|
|
sc->sc_udev->ud_cookie.cookie);
|
|
|
|
#ifdef UVIDEO_DEBUG
|
|
/* Debugging dump of descriptors. TODO: move this to userspace
|
|
* via a custom IOCTL or something. */
|
|
const usb_descriptor_t *desc;
|
|
usb_desc_iter_init(sc->sc_udev, &iter);
|
|
while ((desc = usb_desc_iter_next(&iter)) != NULL) {
|
|
/* print out all descriptors */
|
|
printf("uvideo_attach: ");
|
|
print_descriptor(desc);
|
|
}
|
|
#endif /* !UVIDEO_DEBUG */
|
|
|
|
/* iterate through interface descriptors and initialize softc */
|
|
usb_desc_iter_init(sc->sc_udev, &iter);
|
|
for (ifaceidx = 0;
|
|
(ifdesc = usb_desc_iter_next_interface(&iter)) != NULL;
|
|
++ifaceidx)
|
|
{
|
|
if (ifdesc->bLength < USB_INTERFACE_DESCRIPTOR_SIZE) {
|
|
DPRINTFN(50, ("uvideo_attach: "
|
|
"ignoring incorrect descriptor len=%d\n",
|
|
ifdesc->bLength));
|
|
continue;
|
|
}
|
|
if (ifdesc->bInterfaceClass != UICLASS_VIDEO) {
|
|
DPRINTFN(50, ("uvideo_attach: "
|
|
"ignoring non-uvc interface: "
|
|
"len=%d type=0x%02x "
|
|
"class=0x%02x subclass=0x%02x\n",
|
|
ifdesc->bLength,
|
|
ifdesc->bDescriptorType,
|
|
ifdesc->bInterfaceClass,
|
|
ifdesc->bInterfaceSubClass));
|
|
continue;
|
|
}
|
|
|
|
switch (ifdesc->bInterfaceSubClass) {
|
|
case UISUBCLASS_VIDEOCONTROL:
|
|
err = uvideo_init_control(sc, ifdesc, &iter);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_attach: error with interface "
|
|
"%d, VideoControl, "
|
|
"descriptor len=%d type=0x%02x: "
|
|
"%s (%d)\n",
|
|
ifdesc->bInterfaceNumber,
|
|
ifdesc->bLength,
|
|
ifdesc->bDescriptorType,
|
|
usbd_errstr(err), err));
|
|
}
|
|
break;
|
|
case UISUBCLASS_VIDEOSTREAMING:
|
|
vs = uvideo_find_stream(sc, ifdesc->bInterfaceNumber);
|
|
if (vs == NULL) {
|
|
vs = uvideo_stream_alloc();
|
|
err = uvideo_stream_init(vs, sc, ifdesc,
|
|
ifaceidx);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_attach: "
|
|
"error initializing stream: "
|
|
"%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
goto bad;
|
|
}
|
|
}
|
|
err = uvideo_stream_init_desc(vs, ifdesc, &iter);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_attach: "
|
|
"error initializing stream descriptor: "
|
|
"%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
goto bad;
|
|
}
|
|
/* TODO: for now, set (each) stream to stream_in. */
|
|
sc->sc_stream_in = vs;
|
|
break;
|
|
case UISUBCLASS_VIDEOCOLLECTION:
|
|
err = uvideo_init_collection(sc, ifdesc, &iter);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_attach: error with interface "
|
|
"%d, VideoCollection, "
|
|
"descriptor len=%d type=0x%02x: "
|
|
"%s (%d)\n",
|
|
ifdesc->bInterfaceNumber,
|
|
ifdesc->bLength,
|
|
ifdesc->bDescriptorType,
|
|
usbd_errstr(err), err));
|
|
goto bad;
|
|
}
|
|
break;
|
|
default:
|
|
DPRINTF(("uvideo_attach: unknown UICLASS_VIDEO "
|
|
"subclass=0x%02x\n",
|
|
ifdesc->bInterfaceSubClass));
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev);
|
|
|
|
if (!pmf_device_register(self, NULL, NULL))
|
|
aprint_error_dev(self, "couldn't establish power handler\n");
|
|
|
|
sc->sc_videodev = video_attach_mi(&uvideo_hw_if, sc->sc_dev);
|
|
DPRINTF(("uvideo_attach: attached video driver at %p\n",
|
|
sc->sc_videodev));
|
|
|
|
return;
|
|
|
|
bad:
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_attach: error: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
static int
|
|
uvideo_activate(device_t self, enum devact act)
|
|
{
|
|
struct uvideo_softc *sc = device_private(self);
|
|
|
|
switch (act) {
|
|
case DVACT_DEACTIVATE:
|
|
DPRINTF(("uvideo_activate: deactivating\n"));
|
|
sc->sc_dying = 1;
|
|
return 0;
|
|
default:
|
|
return EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
|
|
/* Detach child (video interface) */
|
|
static void
|
|
uvideo_childdet(device_t self, device_t child)
|
|
{
|
|
struct uvideo_softc *sc = device_private(self);
|
|
|
|
KASSERT(sc->sc_videodev == child);
|
|
sc->sc_videodev = NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
uvideo_detach(device_t self, int flags)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
struct uvideo_stream *vs;
|
|
int rv;
|
|
|
|
sc = device_private(self);
|
|
rv = 0;
|
|
|
|
sc->sc_dying = 1;
|
|
|
|
pmf_device_deregister(self);
|
|
|
|
/* TODO: close the device if it is currently opened? Or will
|
|
* close be called automatically? */
|
|
|
|
while (!SLIST_EMPTY(&sc->sc_stream_list)) {
|
|
vs = SLIST_FIRST(&sc->sc_stream_list);
|
|
SLIST_REMOVE_HEAD(&sc->sc_stream_list, entries);
|
|
uvideo_stream_stop_xfer(vs);
|
|
uvideo_stream_free(vs);
|
|
}
|
|
|
|
#if 0
|
|
/* Wait for outstanding request to complete. TODO: what is
|
|
* appropriate here? */
|
|
usbd_delay_ms(sc->sc_udev, 1000);
|
|
#endif
|
|
|
|
DPRINTFN(15, ("uvideo: detaching from %s\n",
|
|
device_xname(sc->sc_dev)));
|
|
|
|
if (sc->sc_videodev != NULL)
|
|
rv = config_detach(sc->sc_videodev, flags);
|
|
|
|
usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev);
|
|
|
|
usbd_devinfo_free(sc->sc_devname);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Search the stream list for a stream matching the interface number.
|
|
* This is an O(n) search, but most devices should have only one or at
|
|
* most two streams. */
|
|
static struct uvideo_stream *
|
|
uvideo_find_stream(struct uvideo_softc *sc, uint8_t ifaceno)
|
|
{
|
|
struct uvideo_stream *vs;
|
|
|
|
SLIST_FOREACH(vs, &sc->sc_stream_list, entries) {
|
|
if (vs->vs_ifaceno == ifaceno)
|
|
return vs;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Search the format list for the given format and frame index. This
|
|
* might be improved through indexing, but the format and frame count
|
|
* is unknown ahead of time (only after iterating through the
|
|
* usb device descriptors). */
|
|
#if 0
|
|
static struct uvideo_format *
|
|
uvideo_stream_find_format(struct uvideo_stream *vs,
|
|
uint8_t format_index, uint8_t frame_index)
|
|
{
|
|
struct uvideo_format *format;
|
|
|
|
SIMPLEQ_FOREACH(format, &vs->vs_formats, entries) {
|
|
if (UVIDEO_FORMAT_GET_FORMAT_INDEX(format) == format_index &&
|
|
UVIDEO_FORMAT_GET_FRAME_INDEX(format) == frame_index)
|
|
return format;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static struct uvideo_format *
|
|
uvideo_stream_guess_format(struct uvideo_stream *vs,
|
|
enum video_pixel_format pixel_format,
|
|
uint32_t width, uint32_t height)
|
|
{
|
|
struct uvideo_format *format, *gformat = NULL;
|
|
|
|
SIMPLEQ_FOREACH(format, &vs->vs_formats, entries) {
|
|
if (format->format.pixel_format != pixel_format)
|
|
continue;
|
|
if (format->format.width <= width &&
|
|
format->format.height <= height) {
|
|
if (gformat == NULL ||
|
|
(gformat->format.width < format->format.width &&
|
|
gformat->format.height < format->format.height))
|
|
gformat = format;
|
|
}
|
|
}
|
|
|
|
return gformat;
|
|
}
|
|
|
|
static struct uvideo_stream *
|
|
uvideo_stream_alloc(void)
|
|
{
|
|
return kmem_alloc(sizeof(struct uvideo_stream), KM_SLEEP);
|
|
}
|
|
|
|
|
|
static usbd_status
|
|
uvideo_init_control(struct uvideo_softc *sc,
|
|
const usb_interface_descriptor_t *ifdesc,
|
|
usbd_desc_iter_t *iter)
|
|
{
|
|
const usb_descriptor_t *desc;
|
|
const uvideo_descriptor_t *uvdesc;
|
|
usbd_desc_iter_t orig;
|
|
uint8_t i, j, nunits;
|
|
|
|
/* save original iterator state */
|
|
memcpy(&orig, iter, sizeof(orig));
|
|
|
|
/* count number of units and terminals */
|
|
nunits = 0;
|
|
while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
|
|
uvdesc = (const uvideo_descriptor_t *)desc;
|
|
|
|
if (uvdesc->bDescriptorType != UDESC_CS_INTERFACE)
|
|
continue;
|
|
if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
|
|
uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
|
|
continue;
|
|
++nunits;
|
|
}
|
|
|
|
if (nunits == 0) {
|
|
DPRINTF(("uvideo_init_control: no units\n"));
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
i = 0;
|
|
|
|
/* allocate space for units */
|
|
sc->sc_nunits = nunits;
|
|
sc->sc_unit = kmem_alloc(sizeof(*sc->sc_unit) * nunits, KM_SLEEP);
|
|
|
|
/* restore original iterator state */
|
|
memcpy(iter, &orig, sizeof(orig));
|
|
|
|
/* iterate again, initializing the units */
|
|
while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
|
|
uvdesc = (const uvideo_descriptor_t *)desc;
|
|
|
|
if (uvdesc->bDescriptorType != UDESC_CS_INTERFACE)
|
|
continue;
|
|
if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
|
|
uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
|
|
continue;
|
|
|
|
sc->sc_unit[i] = uvideo_unit_alloc(uvdesc);
|
|
/* TODO: free other units before returning? */
|
|
if (sc->sc_unit[i] == NULL)
|
|
goto enomem;
|
|
++i;
|
|
}
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
|
|
enomem:
|
|
if (sc->sc_unit != NULL) {
|
|
for (j = 0; j < i; ++j) {
|
|
uvideo_unit_free(sc->sc_unit[j]);
|
|
sc->sc_unit[j] = NULL;
|
|
}
|
|
kmem_free(sc->sc_unit, sizeof(*sc->sc_unit) * nunits);
|
|
sc->sc_unit = NULL;
|
|
}
|
|
sc->sc_nunits = 0;
|
|
|
|
return USBD_NOMEM;
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_init_collection(struct uvideo_softc *sc,
|
|
const usb_interface_descriptor_t *ifdesc,
|
|
usbd_desc_iter_t *iter)
|
|
{
|
|
DPRINTF(("uvideo: ignoring Video Collection\n"));
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
/* Allocates space for and initializes a uvideo unit based on the
|
|
* given descriptor. Returns NULL with bad descriptor or ENOMEM. */
|
|
static struct uvideo_unit *
|
|
uvideo_unit_alloc(const uvideo_descriptor_t *desc)
|
|
{
|
|
struct uvideo_unit *vu;
|
|
usbd_status err;
|
|
|
|
if (desc->bDescriptorType != UDESC_CS_INTERFACE)
|
|
return NULL;
|
|
|
|
vu = kmem_alloc(sizeof(*vu), KM_SLEEP);
|
|
err = uvideo_unit_init(vu, desc);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_unit_alloc: error initializing unit: "
|
|
"%s (%d)\n", usbd_errstr(err), err));
|
|
kmem_free(vu, sizeof(*vu));
|
|
return NULL;
|
|
}
|
|
|
|
return vu;
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_unit_init(struct uvideo_unit *vu, const uvideo_descriptor_t *desc)
|
|
{
|
|
struct uvideo_camera_terminal *ct;
|
|
struct uvideo_processing_unit *pu;
|
|
|
|
const uvideo_input_terminal_descriptor_t *input;
|
|
const uvideo_camera_terminal_descriptor_t *camera;
|
|
const uvideo_selector_unit_descriptor_t *selector;
|
|
const uvideo_processing_unit_descriptor_t *processing;
|
|
const uvideo_extension_unit_descriptor_t *extension;
|
|
|
|
memset(vu, 0, sizeof(*vu));
|
|
|
|
switch (desc->bDescriptorSubtype) {
|
|
case UDESC_INPUT_TERMINAL:
|
|
if (desc->bLength < sizeof(*input))
|
|
return USBD_INVAL;
|
|
input = (const uvideo_input_terminal_descriptor_t *)desc;
|
|
switch (UGETW(input->wTerminalType)) {
|
|
case UVIDEO_ITT_CAMERA:
|
|
if (desc->bLength < sizeof(*camera))
|
|
return USBD_INVAL;
|
|
camera =
|
|
(const uvideo_camera_terminal_descriptor_t *)desc;
|
|
|
|
ct = &vu->u.vu_camera;
|
|
ct->ct_objective_focal_min =
|
|
UGETW(camera->wObjectiveFocalLengthMin);
|
|
ct->ct_objective_focal_max =
|
|
UGETW(camera->wObjectiveFocalLengthMax);
|
|
ct->ct_ocular_focal_length =
|
|
UGETW(camera->wOcularFocalLength);
|
|
|
|
uvideo_unit_alloc_controls(vu, camera->bControlSize,
|
|
camera->bmControls);
|
|
break;
|
|
default:
|
|
DPRINTF(("uvideo_unit_init: "
|
|
"unknown input terminal type 0x%04x\n",
|
|
UGETW(input->wTerminalType)));
|
|
return USBD_INVAL;
|
|
}
|
|
break;
|
|
case UDESC_OUTPUT_TERMINAL:
|
|
break;
|
|
case UDESC_SELECTOR_UNIT:
|
|
if (desc->bLength < sizeof(*selector))
|
|
return USBD_INVAL;
|
|
selector = (const uvideo_selector_unit_descriptor_t *)desc;
|
|
|
|
uvideo_unit_alloc_sources(vu, selector->bNrInPins,
|
|
selector->baSourceID);
|
|
break;
|
|
case UDESC_PROCESSING_UNIT:
|
|
if (desc->bLength < sizeof(*processing))
|
|
return USBD_INVAL;
|
|
processing = (const uvideo_processing_unit_descriptor_t *)desc;
|
|
pu = &vu->u.vu_processing;
|
|
|
|
pu->pu_video_standards = PU_GET_VIDEO_STANDARDS(processing);
|
|
pu->pu_max_multiplier = UGETW(processing->wMaxMultiplier);
|
|
|
|
uvideo_unit_alloc_sources(vu, 1, &processing->bSourceID);
|
|
uvideo_unit_alloc_controls(vu, processing->bControlSize,
|
|
processing->bmControls);
|
|
break;
|
|
case UDESC_EXTENSION_UNIT:
|
|
if (desc->bLength < sizeof(*extension))
|
|
return USBD_INVAL;
|
|
extension = (const uvideo_extension_unit_descriptor_t *)desc;
|
|
/* TODO: copy guid */
|
|
|
|
uvideo_unit_alloc_sources(vu, extension->bNrInPins,
|
|
extension->baSourceID);
|
|
uvideo_unit_alloc_controls(vu, XU_GET_CONTROL_SIZE(extension),
|
|
XU_GET_CONTROLS(extension));
|
|
break;
|
|
default:
|
|
DPRINTF(("uvideo_unit_alloc: unknown descriptor "
|
|
"type=0x%02x subtype=0x%02x\n",
|
|
desc->bDescriptorType, desc->bDescriptorSubtype));
|
|
return USBD_INVAL;
|
|
}
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
static void
|
|
uvideo_unit_free(struct uvideo_unit *vu)
|
|
{
|
|
uvideo_unit_free_sources(vu);
|
|
uvideo_unit_free_controls(vu);
|
|
kmem_free(vu, sizeof(*vu));
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_unit_alloc_sources(struct uvideo_unit *vu,
|
|
uint8_t nsrcs, const uint8_t *src_ids)
|
|
{
|
|
vu->vu_nsrcs = nsrcs;
|
|
|
|
if (nsrcs == 0) {
|
|
/* do nothing */
|
|
} else if (nsrcs == 1) {
|
|
vu->s.vu_src_id = src_ids[0];
|
|
} else {
|
|
vu->s.vu_src_id_ary =
|
|
kmem_alloc(sizeof(*vu->s.vu_src_id_ary) * nsrcs, KM_SLEEP);
|
|
memcpy(vu->s.vu_src_id_ary, src_ids, nsrcs);
|
|
}
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
static void
|
|
uvideo_unit_free_sources(struct uvideo_unit *vu)
|
|
{
|
|
if (vu->vu_nsrcs == 1)
|
|
return;
|
|
|
|
kmem_free(vu->s.vu_src_id_ary,
|
|
sizeof(*vu->s.vu_src_id_ary) * vu->vu_nsrcs);
|
|
vu->vu_nsrcs = 0;
|
|
vu->s.vu_src_id_ary = NULL;
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_unit_alloc_controls(struct uvideo_unit *vu, uint8_t size,
|
|
const uint8_t *controls)
|
|
{
|
|
if (size == 0)
|
|
return USBD_INVAL;
|
|
|
|
vu->vu_controls = kmem_alloc(sizeof(*vu->vu_controls) * size, KM_SLEEP);
|
|
vu->vu_control_size = size;
|
|
memcpy(vu->vu_controls, controls, size);
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
static void
|
|
uvideo_unit_free_controls(struct uvideo_unit *vu)
|
|
{
|
|
kmem_free(vu->vu_controls,
|
|
sizeof(*vu->vu_controls) * vu->vu_control_size);
|
|
vu->vu_controls = NULL;
|
|
vu->vu_control_size = 0;
|
|
}
|
|
|
|
|
|
/* Initialize a stream from a Video Streaming interface
|
|
* descriptor. Adds the stream to the stream_list in uvideo_softc.
|
|
* This should be called once for new streams, and
|
|
* uvideo_stream_init_desc() should then be called for this and each
|
|
* additional interface with the same interface number. */
|
|
static usbd_status
|
|
uvideo_stream_init(struct uvideo_stream *vs,
|
|
struct uvideo_softc *sc,
|
|
const usb_interface_descriptor_t *ifdesc,
|
|
uint8_t idx)
|
|
{
|
|
uWord len;
|
|
usbd_status err;
|
|
|
|
SLIST_INSERT_HEAD(&sc->sc_stream_list, vs, entries);
|
|
memset(vs, 0, sizeof(*vs));
|
|
vs->vs_parent = sc;
|
|
vs->vs_ifaceno = ifdesc->bInterfaceNumber;
|
|
vs->vs_subtype = 0;
|
|
SIMPLEQ_INIT(&vs->vs_formats);
|
|
SIMPLEQ_INIT(&vs->vs_pixel_formats);
|
|
vs->vs_default_format = NULL;
|
|
vs->vs_current_format.priv = -1;
|
|
vs->vs_xfer_type = 0;
|
|
|
|
err = usbd_device2interface_handle(sc->sc_udev, idx, &vs->vs_iface);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_stream_init: "
|
|
"error getting vs interface: "
|
|
"%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return err;
|
|
}
|
|
|
|
/* For Xbox Live Vision camera, linux-uvc folk say we need to
|
|
* set an alternate interface and wait ~3 seconds prior to
|
|
* doing the format probe/commit. We set to alternate
|
|
* interface 0, which is the default, zero bandwidth
|
|
* interface. This should not have adverse affects on other
|
|
* cameras. Errors are ignored. */
|
|
err = usbd_set_interface(vs->vs_iface, 0);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_stream_init: error setting alt interface: "
|
|
"%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
}
|
|
|
|
/* Initialize probe and commit data size. This value is
|
|
* dependent on the version of the spec the hardware
|
|
* implements. */
|
|
err = uvideo_stream_probe(vs, UR_GET_LEN, &len);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_stream_init: "
|
|
"error getting probe data len: "
|
|
"%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
vs->vs_probelen = 26; /* conservative v1.0 length */
|
|
} else if (UGETW(len) <= sizeof(uvideo_probe_and_commit_data_t)) {
|
|
DPRINTFN(15,("uvideo_stream_init: probelen=%d\n", UGETW(len)));
|
|
vs->vs_probelen = UGETW(len);
|
|
} else {
|
|
DPRINTFN(15,("uvideo_stream_init: device returned invalid probe"
|
|
" len %d, using default\n", UGETW(len)));
|
|
vs->vs_probelen = 26;
|
|
}
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
/* Further stream initialization based on a Video Streaming interface
|
|
* descriptor and following descriptors belonging to that interface.
|
|
* Iterates through all descriptors belonging to this particular
|
|
* interface descriptor, modifying the iterator. This may be called
|
|
* multiple times because there may be several alternate interfaces
|
|
* associated with the same interface number. */
|
|
/*
|
|
* XXX XXX XXX: This function accesses descriptors in an unsafe manner.
|
|
*/
|
|
static usbd_status
|
|
uvideo_stream_init_desc(struct uvideo_stream *vs,
|
|
const usb_interface_descriptor_t *ifdesc,
|
|
usbd_desc_iter_t *iter)
|
|
{
|
|
const usb_descriptor_t *desc;
|
|
const uvideo_descriptor_t *uvdesc;
|
|
struct uvideo_bulk_xfer *bx;
|
|
struct uvideo_isoc_xfer *ix;
|
|
struct uvideo_alternate *alt;
|
|
uint8_t xfer_type, xfer_dir;
|
|
uint8_t bmAttributes, bEndpointAddress;
|
|
int i;
|
|
|
|
/* Iterate until the next interface descriptor. All
|
|
* descriptors until then belong to this streaming
|
|
* interface. */
|
|
while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
|
|
uvdesc = (const uvideo_descriptor_t *)desc;
|
|
|
|
switch (uvdesc->bDescriptorType) {
|
|
case UDESC_ENDPOINT:
|
|
bmAttributes = GET(usb_endpoint_descriptor_t,
|
|
desc, bmAttributes);
|
|
bEndpointAddress = GET(usb_endpoint_descriptor_t,
|
|
desc, bEndpointAddress);
|
|
xfer_type = UE_GET_XFERTYPE(bmAttributes);
|
|
xfer_dir = UE_GET_DIR(bEndpointAddress);
|
|
if (xfer_type == UE_BULK && xfer_dir == UE_DIR_IN) {
|
|
bx = &vs->vs_xfer.bulk;
|
|
if (vs->vs_xfer_type == 0) {
|
|
DPRINTFN(15, ("uvideo_attach: "
|
|
"BULK stream *\n"));
|
|
vs->vs_xfer_type = UE_BULK;
|
|
bx->bx_endpt = bEndpointAddress;
|
|
DPRINTF(("uvideo_attach: BULK "
|
|
"endpoint %x\n",
|
|
bx->bx_endpt));
|
|
bx->bx_running = false;
|
|
cv_init(&bx->bx_cv,
|
|
device_xname(vs->vs_parent->sc_dev)
|
|
);
|
|
mutex_init(&bx->bx_lock,
|
|
MUTEX_DEFAULT, IPL_NONE);
|
|
}
|
|
} else if (xfer_type == UE_ISOCHRONOUS) {
|
|
ix = &vs->vs_xfer.isoc;
|
|
for (i = 0; i < UVIDEO_NXFERS; i++) {
|
|
ix->ix_i[i].i_ix = ix;
|
|
ix->ix_i[i].i_vs = vs;
|
|
}
|
|
if (vs->vs_xfer_type == 0) {
|
|
DPRINTFN(15, ("uvideo_attach: "
|
|
"ISOC stream *\n"));
|
|
SLIST_INIT(&ix->ix_altlist);
|
|
vs->vs_xfer_type = UE_ISOCHRONOUS;
|
|
ix->ix_endpt =
|
|
GET(usb_endpoint_descriptor_t,
|
|
desc, bEndpointAddress);
|
|
}
|
|
|
|
alt = kmem_alloc(sizeof(*alt), KM_SLEEP);
|
|
alt->altno = ifdesc->bAlternateSetting;
|
|
alt->interval =
|
|
GET(usb_endpoint_descriptor_t,
|
|
desc, bInterval);
|
|
|
|
alt->max_packet_size =
|
|
UE_GET_SIZE(UGETW(GET(usb_endpoint_descriptor_t,
|
|
desc, wMaxPacketSize)));
|
|
alt->max_packet_size *=
|
|
(UE_GET_TRANS(UGETW(GET(
|
|
usb_endpoint_descriptor_t, desc,
|
|
wMaxPacketSize)))) + 1;
|
|
|
|
SLIST_INSERT_HEAD(&ix->ix_altlist,
|
|
alt, entries);
|
|
}
|
|
break;
|
|
case UDESC_CS_INTERFACE:
|
|
if (ifdesc->bAlternateSetting != 0) {
|
|
DPRINTF(("uvideo_stream_init_alternate: "
|
|
"unexpected class-specific descriptor "
|
|
"len=%d type=0x%02x subtype=0x%02x\n",
|
|
uvdesc->bLength,
|
|
uvdesc->bDescriptorType,
|
|
uvdesc->bDescriptorSubtype));
|
|
break;
|
|
}
|
|
|
|
switch (uvdesc->bDescriptorSubtype) {
|
|
case UDESC_VS_INPUT_HEADER:
|
|
vs->vs_subtype = UDESC_VS_INPUT_HEADER;
|
|
break;
|
|
case UDESC_VS_OUTPUT_HEADER:
|
|
/* TODO: handle output stream */
|
|
DPRINTF(("uvideo: VS output not implemented\n"));
|
|
vs->vs_subtype = UDESC_VS_OUTPUT_HEADER;
|
|
return USBD_INVAL;
|
|
case UDESC_VS_FORMAT_UNCOMPRESSED:
|
|
case UDESC_VS_FORMAT_FRAME_BASED:
|
|
case UDESC_VS_FORMAT_MJPEG:
|
|
uvideo_stream_init_frame_based_format(vs,
|
|
uvdesc,
|
|
iter);
|
|
break;
|
|
case UDESC_VS_FORMAT_MPEG2TS:
|
|
case UDESC_VS_FORMAT_DV:
|
|
case UDESC_VS_FORMAT_STREAM_BASED:
|
|
default:
|
|
DPRINTF(("uvideo: unimplemented VS CS "
|
|
"descriptor len=%d type=0x%02x "
|
|
"subtype=0x%02x\n",
|
|
uvdesc->bLength,
|
|
uvdesc->bDescriptorType,
|
|
uvdesc->bDescriptorSubtype));
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
DPRINTF(("uvideo_stream_init_desc: "
|
|
"unknown descriptor "
|
|
"len=%d type=0x%02x\n",
|
|
uvdesc->bLength,
|
|
uvdesc->bDescriptorType));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
/* Finialize and free memory associated with this stream. */
|
|
static void
|
|
uvideo_stream_free(struct uvideo_stream *vs)
|
|
{
|
|
struct uvideo_alternate *alt;
|
|
struct uvideo_pixel_format *pixel_format;
|
|
struct uvideo_format *format;
|
|
|
|
/* free linked list of alternate interfaces */
|
|
if (vs->vs_xfer_type == UE_ISOCHRONOUS) {
|
|
while (!SLIST_EMPTY(&vs->vs_xfer.isoc.ix_altlist)) {
|
|
alt = SLIST_FIRST(&vs->vs_xfer.isoc.ix_altlist);
|
|
SLIST_REMOVE_HEAD(&vs->vs_xfer.isoc.ix_altlist,
|
|
entries);
|
|
kmem_free(alt, sizeof(*alt));
|
|
}
|
|
}
|
|
|
|
/* free linked-list of formats and pixel formats */
|
|
while ((format = SIMPLEQ_FIRST(&vs->vs_formats)) != NULL) {
|
|
SIMPLEQ_REMOVE_HEAD(&vs->vs_formats, entries);
|
|
kmem_free(format, sizeof(struct uvideo_format));
|
|
}
|
|
while ((pixel_format = SIMPLEQ_FIRST(&vs->vs_pixel_formats)) != NULL) {
|
|
SIMPLEQ_REMOVE_HEAD(&vs->vs_pixel_formats, entries);
|
|
kmem_free(pixel_format, sizeof(struct uvideo_pixel_format));
|
|
}
|
|
|
|
kmem_free(vs, sizeof(*vs));
|
|
}
|
|
|
|
|
|
static usbd_status
|
|
uvideo_stream_init_frame_based_format(struct uvideo_stream *vs,
|
|
const uvideo_descriptor_t *format_desc,
|
|
usbd_desc_iter_t *iter)
|
|
{
|
|
struct uvideo_pixel_format *pformat, *pfiter;
|
|
enum video_pixel_format pixel_format;
|
|
struct uvideo_format *format;
|
|
const uvideo_descriptor_t *uvdesc;
|
|
uint8_t subtype, default_index, index;
|
|
uint32_t frame_interval;
|
|
const usb_guid_t *guid;
|
|
|
|
pixel_format = VIDEO_FORMAT_UNDEFINED;
|
|
|
|
switch (format_desc->bDescriptorSubtype) {
|
|
case UDESC_VS_FORMAT_UNCOMPRESSED:
|
|
subtype = UDESC_VS_FRAME_UNCOMPRESSED;
|
|
default_index = GET(uvideo_vs_format_uncompressed_descriptor_t,
|
|
format_desc,
|
|
bDefaultFrameIndex);
|
|
guid = GETP(uvideo_vs_format_uncompressed_descriptor_t,
|
|
format_desc,
|
|
guidFormat);
|
|
if (usb_guid_cmp(guid, &uvideo_guid_format_yuy2) == 0)
|
|
pixel_format = VIDEO_FORMAT_YUY2;
|
|
else if (usb_guid_cmp(guid, &uvideo_guid_format_nv12) == 0)
|
|
pixel_format = VIDEO_FORMAT_NV12;
|
|
else if (usb_guid_cmp(guid, &uvideo_guid_format_uyvy) == 0)
|
|
pixel_format = VIDEO_FORMAT_UYVY;
|
|
break;
|
|
case UDESC_VS_FORMAT_FRAME_BASED:
|
|
subtype = UDESC_VS_FRAME_FRAME_BASED;
|
|
default_index = GET(uvideo_format_frame_based_descriptor_t,
|
|
format_desc,
|
|
bDefaultFrameIndex);
|
|
break;
|
|
case UDESC_VS_FORMAT_MJPEG:
|
|
subtype = UDESC_VS_FRAME_MJPEG;
|
|
default_index = GET(uvideo_vs_format_mjpeg_descriptor_t,
|
|
format_desc,
|
|
bDefaultFrameIndex);
|
|
pixel_format = VIDEO_FORMAT_MJPEG;
|
|
break;
|
|
default:
|
|
DPRINTF(("uvideo: unknown frame based format %d\n",
|
|
format_desc->bDescriptorSubtype));
|
|
return USBD_INVAL;
|
|
}
|
|
|
|
pformat = NULL;
|
|
SIMPLEQ_FOREACH(pfiter, &vs->vs_pixel_formats, entries) {
|
|
if (pfiter->pixel_format == pixel_format) {
|
|
pformat = pfiter;
|
|
break;
|
|
}
|
|
}
|
|
if (pixel_format != VIDEO_FORMAT_UNDEFINED && pformat == NULL) {
|
|
pformat = kmem_zalloc(sizeof(*pformat), KM_SLEEP);
|
|
pformat->pixel_format = pixel_format;
|
|
DPRINTF(("uvideo: Adding pixel format %d\n",
|
|
pixel_format));
|
|
SIMPLEQ_INSERT_TAIL(&vs->vs_pixel_formats,
|
|
pformat, entries);
|
|
}
|
|
|
|
/* Iterate through frame descriptors directly following the
|
|
* format descriptor, and add a format to the format list for
|
|
* each frame descriptor. */
|
|
while ((uvdesc = (const uvideo_descriptor_t *)usb_desc_iter_peek(iter)) &&
|
|
(uvdesc != NULL) && (uvdesc->bDescriptorSubtype == subtype))
|
|
{
|
|
uvdesc = (const uvideo_descriptor_t *) usb_desc_iter_next(iter);
|
|
|
|
format = kmem_zalloc(sizeof(struct uvideo_format), KM_SLEEP);
|
|
format->format.pixel_format = pixel_format;
|
|
|
|
switch (format_desc->bDescriptorSubtype) {
|
|
case UDESC_VS_FORMAT_UNCOMPRESSED:
|
|
#ifdef UVIDEO_DEBUG
|
|
if (pixel_format == VIDEO_FORMAT_UNDEFINED &&
|
|
uvideodebug) {
|
|
guid = GETP(
|
|
uvideo_vs_format_uncompressed_descriptor_t,
|
|
format_desc,
|
|
guidFormat);
|
|
|
|
DPRINTF(("uvideo: format undefined "));
|
|
usb_guid_print(guid);
|
|
DPRINTF(("\n"));
|
|
}
|
|
#endif
|
|
|
|
UVIDEO_FORMAT_INIT_FRAME_BASED(
|
|
uvideo_vs_format_uncompressed_descriptor_t,
|
|
format_desc,
|
|
uvideo_vs_frame_uncompressed_descriptor_t,
|
|
uvdesc,
|
|
format);
|
|
format->format.sample_size =
|
|
UGETDW(
|
|
GET(uvideo_vs_frame_uncompressed_descriptor_t,
|
|
uvdesc, dwMaxVideoFrameBufferSize));
|
|
format->format.stride =
|
|
format->format.sample_size / format->format.height;
|
|
index = GET(uvideo_vs_frame_uncompressed_descriptor_t,
|
|
uvdesc,
|
|
bFrameIndex);
|
|
frame_interval =
|
|
UGETDW(
|
|
GET(uvideo_vs_frame_uncompressed_descriptor_t,
|
|
uvdesc,
|
|
dwDefaultFrameInterval));
|
|
break;
|
|
case UDESC_VS_FORMAT_MJPEG:
|
|
UVIDEO_FORMAT_INIT_FRAME_BASED(
|
|
uvideo_vs_format_mjpeg_descriptor_t,
|
|
format_desc,
|
|
uvideo_vs_frame_mjpeg_descriptor_t,
|
|
uvdesc,
|
|
format);
|
|
format->format.sample_size =
|
|
UGETDW(
|
|
GET(uvideo_vs_frame_mjpeg_descriptor_t,
|
|
uvdesc, dwMaxVideoFrameBufferSize));
|
|
format->format.stride =
|
|
format->format.sample_size / format->format.height;
|
|
index = GET(uvideo_vs_frame_mjpeg_descriptor_t,
|
|
uvdesc,
|
|
bFrameIndex);
|
|
frame_interval =
|
|
UGETDW(
|
|
GET(uvideo_vs_frame_mjpeg_descriptor_t,
|
|
uvdesc,
|
|
dwDefaultFrameInterval));
|
|
break;
|
|
case UDESC_VS_FORMAT_FRAME_BASED:
|
|
format->format.pixel_format = VIDEO_FORMAT_UNDEFINED;
|
|
UVIDEO_FORMAT_INIT_FRAME_BASED(
|
|
uvideo_format_frame_based_descriptor_t,
|
|
format_desc,
|
|
uvideo_frame_frame_based_descriptor_t,
|
|
uvdesc,
|
|
format);
|
|
index = GET(uvideo_frame_frame_based_descriptor_t,
|
|
uvdesc,
|
|
bFrameIndex);
|
|
format->format.stride =
|
|
UGETDW(
|
|
GET(uvideo_frame_frame_based_descriptor_t,
|
|
uvdesc, dwBytesPerLine));
|
|
format->format.sample_size =
|
|
format->format.stride * format->format.height;
|
|
frame_interval =
|
|
UGETDW(
|
|
GET(uvideo_frame_frame_based_descriptor_t,
|
|
uvdesc, dwDefaultFrameInterval));
|
|
break;
|
|
default:
|
|
/* shouldn't ever get here */
|
|
DPRINTF(("uvideo: unknown frame based format %d\n",
|
|
format_desc->bDescriptorSubtype));
|
|
kmem_free(format, sizeof(struct uvideo_format));
|
|
return USBD_INVAL;
|
|
}
|
|
|
|
DPRINTF(("uvideo: found format (index %d) type %d "
|
|
"size %ux%u size %u stride %u interval %u\n",
|
|
index, format->format.pixel_format, format->format.width,
|
|
format->format.height, format->format.sample_size,
|
|
format->format.stride, frame_interval));
|
|
|
|
SIMPLEQ_INSERT_TAIL(&vs->vs_formats, format, entries);
|
|
|
|
if (vs->vs_default_format == NULL && index == default_index
|
|
#ifdef UVIDEO_DISABLE_MJPEG
|
|
&& subtype != UDESC_VS_FRAME_MJPEG
|
|
#endif
|
|
) {
|
|
DPRINTF((" ^ picking this one\n"));
|
|
vs->vs_default_format = &format->format;
|
|
vs->vs_frame_interval = frame_interval;
|
|
}
|
|
|
|
}
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
static int
|
|
uvideo_stream_start_xfer(struct uvideo_stream *vs)
|
|
{
|
|
struct uvideo_softc *sc = vs->vs_parent;
|
|
struct uvideo_bulk_xfer *bx;
|
|
struct uvideo_isoc_xfer *ix;
|
|
uint32_t vframe_len; /* rough bytes per video frame */
|
|
uint32_t uframe_len; /* bytes per usb frame (TODO: or microframe?) */
|
|
uint32_t nframes; /* number of usb frames (TODO: or microframs?) */
|
|
int i, ret;
|
|
int error;
|
|
|
|
struct uvideo_alternate *alt, *alt_maybe;
|
|
usbd_status err;
|
|
|
|
switch (vs->vs_xfer_type) {
|
|
case UE_BULK:
|
|
ret = 0;
|
|
bx = &vs->vs_xfer.bulk;
|
|
|
|
err = usbd_open_pipe(vs->vs_iface, bx->bx_endpt, 0,
|
|
&bx->bx_pipe);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo: error opening pipe: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
DPRINTF(("uvideo: pipe %p\n", bx->bx_pipe));
|
|
|
|
error = usbd_create_xfer(bx->bx_pipe, vs->vs_max_payload_size,
|
|
0, 0, &bx->bx_xfer);
|
|
if (error) {
|
|
DPRINTF(("uvideo: couldn't allocate xfer\n"));
|
|
return error;
|
|
}
|
|
DPRINTF(("uvideo: xfer %p\n", bx->bx_xfer));
|
|
|
|
bx->bx_buflen = vs->vs_max_payload_size;
|
|
bx->bx_buffer = usbd_get_buffer(bx->bx_xfer);
|
|
|
|
mutex_enter(&bx->bx_lock);
|
|
if (bx->bx_running == false) {
|
|
bx->bx_running = true;
|
|
ret = kthread_create(PRI_UVIDEO, 0, NULL,
|
|
uvideo_stream_recv_bulk_transfer, vs,
|
|
NULL, "%s", device_xname(sc->sc_dev));
|
|
if (ret) {
|
|
DPRINTF(("uvideo: couldn't create kthread:"
|
|
" %d\n", err));
|
|
bx->bx_running = false;
|
|
mutex_exit(&bx->bx_lock);
|
|
return err;
|
|
}
|
|
} else
|
|
aprint_error_dev(sc->sc_dev,
|
|
"transfer already in progress\n");
|
|
mutex_exit(&bx->bx_lock);
|
|
|
|
DPRINTF(("uvideo: thread created\n"));
|
|
|
|
return 0;
|
|
case UE_ISOCHRONOUS:
|
|
ix = &vs->vs_xfer.isoc;
|
|
|
|
/* Choose an alternate interface most suitable for
|
|
* this format. Choose the smallest size that can
|
|
* contain max_payload_size.
|
|
*
|
|
* It is assumed that the list is sorted in descending
|
|
* order from largest to smallest packet size.
|
|
*
|
|
* TODO: what should the strategy be for choosing an
|
|
* alt interface?
|
|
*/
|
|
alt = NULL;
|
|
SLIST_FOREACH(alt_maybe, &ix->ix_altlist, entries) {
|
|
/* TODO: define "packet" and "payload". I think
|
|
* several packets can make up one payload which would
|
|
* call into question this method of selecting an
|
|
* alternate interface... */
|
|
|
|
if (alt_maybe->max_packet_size > vs->vs_max_payload_size)
|
|
continue;
|
|
|
|
if (alt == NULL ||
|
|
alt_maybe->max_packet_size >= alt->max_packet_size)
|
|
alt = alt_maybe;
|
|
}
|
|
|
|
if (alt == NULL) {
|
|
DPRINTF(("uvideo_stream_start_xfer: "
|
|
"no suitable alternate interface found\n"));
|
|
return EINVAL;
|
|
}
|
|
|
|
DPRINTFN(15,("uvideo_stream_start_xfer: "
|
|
"choosing alternate interface "
|
|
"%d wMaxPacketSize=%d bInterval=%d\n",
|
|
alt->altno, alt->max_packet_size, alt->interval));
|
|
|
|
err = usbd_set_interface(vs->vs_iface, alt->altno);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_stream_start_xfer: "
|
|
"error setting alt interface: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
|
|
/* TODO: "packet" not same as frame */
|
|
vframe_len = vs->vs_current_format.sample_size;
|
|
uframe_len = alt->max_packet_size;
|
|
nframes = (vframe_len + uframe_len - 1) / uframe_len;
|
|
nframes = (nframes + 7) & ~7; /*round up for ehci inefficiency*/
|
|
nframes = uimin(UVIDEO_NFRAMES_MAX, nframes);
|
|
DPRINTF(("uvideo_stream_start_xfer: nframes=%d\n", nframes));
|
|
|
|
ix->ix_nframes = nframes;
|
|
ix->ix_uframe_len = uframe_len;
|
|
for (i = 0; i < UVIDEO_NXFERS; i++) {
|
|
struct uvideo_isoc *isoc = &ix->ix_i[i];
|
|
isoc->i_frlengths =
|
|
kmem_alloc(sizeof(isoc->i_frlengths[0]) * nframes,
|
|
KM_SLEEP);
|
|
}
|
|
|
|
err = usbd_open_pipe(vs->vs_iface, ix->ix_endpt,
|
|
USBD_EXCLUSIVE_USE, &ix->ix_pipe);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo: error opening pipe: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
|
|
for (i = 0; i < UVIDEO_NXFERS; i++) {
|
|
struct uvideo_isoc *isoc = &ix->ix_i[i];
|
|
error = usbd_create_xfer(ix->ix_pipe,
|
|
nframes * uframe_len, 0, ix->ix_nframes,
|
|
&isoc->i_xfer);
|
|
if (error) {
|
|
DPRINTF(("uvideo: "
|
|
"couldn't allocate xfer (%d)\n", error));
|
|
return error;
|
|
}
|
|
|
|
isoc->i_buf = usbd_get_buffer(isoc->i_xfer);
|
|
}
|
|
|
|
uvideo_stream_recv_isoc_start(vs);
|
|
|
|
return 0;
|
|
default:
|
|
/* should never get here */
|
|
DPRINTF(("uvideo_stream_start_xfer: unknown xfer type %#x\n",
|
|
vs->vs_xfer_type));
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
uvideo_stream_stop_xfer(struct uvideo_stream *vs)
|
|
{
|
|
struct uvideo_bulk_xfer *bx;
|
|
struct uvideo_isoc_xfer *ix;
|
|
usbd_status err;
|
|
int i;
|
|
|
|
switch (vs->vs_xfer_type) {
|
|
case UE_BULK:
|
|
bx = &vs->vs_xfer.bulk;
|
|
|
|
DPRINTF(("uvideo_stream_stop_xfer: UE_BULK: "
|
|
"waiting for thread to complete\n"));
|
|
mutex_enter(&bx->bx_lock);
|
|
if (bx->bx_running == true) {
|
|
bx->bx_running = false;
|
|
cv_wait_sig(&bx->bx_cv, &bx->bx_lock);
|
|
}
|
|
mutex_exit(&bx->bx_lock);
|
|
|
|
DPRINTF(("uvideo_stream_stop_xfer: UE_BULK: cleaning up\n"));
|
|
|
|
if (bx->bx_pipe) {
|
|
usbd_abort_pipe(bx->bx_pipe);
|
|
}
|
|
|
|
if (bx->bx_xfer) {
|
|
usbd_destroy_xfer(bx->bx_xfer);
|
|
bx->bx_xfer = NULL;
|
|
}
|
|
|
|
if (bx->bx_pipe) {
|
|
usbd_close_pipe(bx->bx_pipe);
|
|
bx->bx_pipe = NULL;
|
|
}
|
|
|
|
DPRINTF(("uvideo_stream_stop_xfer: UE_BULK: done\n"));
|
|
|
|
return 0;
|
|
case UE_ISOCHRONOUS:
|
|
ix = &vs->vs_xfer.isoc;
|
|
if (ix->ix_pipe != NULL) {
|
|
usbd_abort_pipe(ix->ix_pipe);
|
|
}
|
|
|
|
for (i = 0; i < UVIDEO_NXFERS; i++) {
|
|
struct uvideo_isoc *isoc = &ix->ix_i[i];
|
|
if (isoc->i_xfer != NULL) {
|
|
usbd_destroy_xfer(isoc->i_xfer);
|
|
isoc->i_xfer = NULL;
|
|
}
|
|
|
|
if (isoc->i_frlengths != NULL) {
|
|
kmem_free(isoc->i_frlengths,
|
|
sizeof(isoc->i_frlengths[0]) *
|
|
ix->ix_nframes);
|
|
isoc->i_frlengths = NULL;
|
|
}
|
|
}
|
|
|
|
if (ix->ix_pipe != NULL) {
|
|
usbd_close_pipe(ix->ix_pipe);
|
|
ix->ix_pipe = NULL;
|
|
}
|
|
/* Give it some time to settle */
|
|
usbd_delay_ms(vs->vs_parent->sc_udev, 1000);
|
|
|
|
/* Set to zero bandwidth alternate interface zero */
|
|
err = usbd_set_interface(vs->vs_iface, 0);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_stream_stop_transfer: "
|
|
"error setting zero bandwidth interface: "
|
|
"%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
|
|
return 0;
|
|
default:
|
|
/* should never get here */
|
|
DPRINTF(("uvideo_stream_stop_xfer: unknown xfer type %#x\n",
|
|
vs->vs_xfer_type));
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_stream_recv_isoc_start(struct uvideo_stream *vs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < UVIDEO_NXFERS; i++)
|
|
uvideo_stream_recv_isoc_start1(&vs->vs_xfer.isoc.ix_i[i]);
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
/* Initiate a usb transfer. */
|
|
static usbd_status
|
|
uvideo_stream_recv_isoc_start1(struct uvideo_isoc *isoc)
|
|
{
|
|
struct uvideo_isoc_xfer *ix;
|
|
usbd_status err;
|
|
int i;
|
|
|
|
ix = isoc->i_ix;
|
|
|
|
for (i = 0; i < ix->ix_nframes; ++i)
|
|
isoc->i_frlengths[i] = ix->ix_uframe_len;
|
|
|
|
usbd_setup_isoc_xfer(isoc->i_xfer,
|
|
isoc,
|
|
isoc->i_frlengths,
|
|
ix->ix_nframes,
|
|
USBD_SHORT_XFER_OK,
|
|
uvideo_stream_recv_isoc_complete);
|
|
|
|
err = usbd_transfer(isoc->i_xfer);
|
|
if (err != USBD_IN_PROGRESS) {
|
|
DPRINTF(("uvideo_stream_recv_start: "
|
|
"usbd_transfer status=%s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_stream_recv_process(struct uvideo_stream *vs, uint8_t *buf, uint32_t len)
|
|
{
|
|
uvideo_payload_header_t *hdr;
|
|
struct video_payload payload;
|
|
|
|
if (len < sizeof(uvideo_payload_header_t)) {
|
|
DPRINTF(("uvideo_stream_recv_process: len %d < payload hdr\n",
|
|
len));
|
|
return USBD_SHORT_XFER;
|
|
}
|
|
|
|
hdr = (uvideo_payload_header_t *)buf;
|
|
|
|
if (hdr->bHeaderLength > UVIDEO_PAYLOAD_HEADER_SIZE ||
|
|
hdr->bHeaderLength < sizeof(uvideo_payload_header_t))
|
|
return USBD_INVAL;
|
|
if (hdr->bHeaderLength == len && !(hdr->bmHeaderInfo & UV_END_OF_FRAME))
|
|
return USBD_INVAL;
|
|
if (hdr->bmHeaderInfo & UV_ERROR)
|
|
return USBD_IOERROR;
|
|
|
|
payload.data = buf + hdr->bHeaderLength;
|
|
payload.size = len - hdr->bHeaderLength;
|
|
payload.frameno = hdr->bmHeaderInfo & UV_FRAME_ID;
|
|
payload.end_of_frame = hdr->bmHeaderInfo & UV_END_OF_FRAME;
|
|
|
|
video_submit_payload(vs->vs_parent->sc_videodev, &payload);
|
|
|
|
return USBD_NORMAL_COMPLETION;
|
|
}
|
|
|
|
/* Callback on completion of usb isoc transfer */
|
|
static void
|
|
uvideo_stream_recv_isoc_complete(struct usbd_xfer *xfer,
|
|
void *priv,
|
|
usbd_status status)
|
|
{
|
|
struct uvideo_stream *vs;
|
|
struct uvideo_isoc_xfer *ix;
|
|
struct uvideo_isoc *isoc;
|
|
int i;
|
|
uint32_t count;
|
|
uint8_t *buf;
|
|
|
|
isoc = priv;
|
|
vs = isoc->i_vs;
|
|
ix = isoc->i_ix;
|
|
|
|
if (status != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_stream_recv_isoc_complete: status=%s (%d)\n",
|
|
usbd_errstr(status), status));
|
|
|
|
if (status == USBD_STALLED)
|
|
usbd_clear_endpoint_stall_async(ix->ix_pipe);
|
|
else
|
|
return;
|
|
} else {
|
|
usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
|
|
|
|
if (count == 0) {
|
|
/* DPRINTF(("uvideo: zero length transfer\n")); */
|
|
goto next;
|
|
}
|
|
|
|
|
|
for (i = 0, buf = isoc->i_buf;
|
|
i < ix->ix_nframes;
|
|
++i, buf += ix->ix_uframe_len)
|
|
{
|
|
status = uvideo_stream_recv_process(vs, buf,
|
|
isoc->i_frlengths[i]);
|
|
if (status == USBD_IOERROR)
|
|
break;
|
|
}
|
|
}
|
|
|
|
next:
|
|
uvideo_stream_recv_isoc_start1(isoc);
|
|
}
|
|
|
|
static void
|
|
uvideo_stream_recv_bulk_transfer(void *addr)
|
|
{
|
|
struct uvideo_stream *vs = addr;
|
|
struct uvideo_bulk_xfer *bx = &vs->vs_xfer.bulk;
|
|
usbd_status err;
|
|
uint32_t len;
|
|
|
|
DPRINTF(("uvideo_stream_recv_bulk_transfer: "
|
|
"vs %p sc %p bx %p buffer %p\n", vs, vs->vs_parent, bx,
|
|
bx->bx_buffer));
|
|
|
|
while (bx->bx_running) {
|
|
len = bx->bx_buflen;
|
|
err = usbd_bulk_transfer(bx->bx_xfer, bx->bx_pipe,
|
|
USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT,
|
|
bx->bx_buffer, &len);
|
|
|
|
if (err == USBD_NORMAL_COMPLETION) {
|
|
uvideo_stream_recv_process(vs, bx->bx_buffer, len);
|
|
} else {
|
|
DPRINTF(("uvideo_stream_recv_bulk_transfer: %s\n",
|
|
usbd_errstr(err)));
|
|
}
|
|
}
|
|
|
|
DPRINTF(("uvideo_stream_recv_bulk_transfer: notify complete\n"));
|
|
|
|
mutex_enter(&bx->bx_lock);
|
|
cv_broadcast(&bx->bx_cv);
|
|
mutex_exit(&bx->bx_lock);
|
|
|
|
DPRINTF(("uvideo_stream_recv_bulk_transfer: return\n"));
|
|
|
|
kthread_exit(0);
|
|
}
|
|
|
|
/*
|
|
* uvideo_open - probe and commit video format and start receiving
|
|
* video data
|
|
*/
|
|
static int
|
|
uvideo_open(void *addr, int flags)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
struct uvideo_stream *vs;
|
|
struct video_format fmt;
|
|
|
|
sc = addr;
|
|
vs = sc->sc_stream_in;
|
|
|
|
DPRINTF(("uvideo_open: sc=%p\n", sc));
|
|
if (sc->sc_dying)
|
|
return EIO;
|
|
|
|
/* XXX select default format */
|
|
fmt = *vs->vs_default_format;
|
|
return uvideo_set_format(addr, &fmt);
|
|
}
|
|
|
|
|
|
static void
|
|
uvideo_close(void *addr)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
|
|
sc = addr;
|
|
|
|
uvideo_stop_transfer(addr);
|
|
|
|
if (sc->sc_state != UVIDEO_STATE_CLOSED) {
|
|
sc->sc_state = UVIDEO_STATE_CLOSED;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
uvideo_get_devname(void *addr)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
return sc->sc_devname;
|
|
}
|
|
|
|
static const char *
|
|
uvideo_get_businfo(void *addr)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
return sc->sc_businfo;
|
|
}
|
|
|
|
static int
|
|
uvideo_enum_format(void *addr, uint32_t index, struct video_format *format)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
struct uvideo_stream *vs = sc->sc_stream_in;
|
|
struct uvideo_pixel_format *pixel_format;
|
|
int off;
|
|
|
|
if (sc->sc_dying)
|
|
return EIO;
|
|
|
|
off = 0;
|
|
SIMPLEQ_FOREACH(pixel_format, &vs->vs_pixel_formats, entries) {
|
|
if (off++ != index)
|
|
continue;
|
|
format->pixel_format = pixel_format->pixel_format;
|
|
return 0;
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* uvideo_get_format
|
|
*/
|
|
static int
|
|
uvideo_get_format(void *addr, struct video_format *format)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
struct uvideo_stream *vs = sc->sc_stream_in;
|
|
|
|
if (sc->sc_dying)
|
|
return EIO;
|
|
|
|
*format = vs->vs_current_format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* uvideo_set_format - TODO: this is broken and does nothing
|
|
*/
|
|
static int
|
|
uvideo_set_format(void *addr, struct video_format *format)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
struct uvideo_stream *vs;
|
|
struct uvideo_format *uvfmt;
|
|
uvideo_probe_and_commit_data_t probe, maxprobe;
|
|
usbd_status err;
|
|
|
|
sc = addr;
|
|
|
|
DPRINTF(("uvideo_set_format: sc=%p\n", sc));
|
|
if (sc->sc_dying)
|
|
return EIO;
|
|
|
|
vs = sc->sc_stream_in;
|
|
|
|
uvfmt = uvideo_stream_guess_format(vs, format->pixel_format,
|
|
format->width, format->height);
|
|
if (uvfmt == NULL) {
|
|
DPRINTF(("uvideo: uvideo_stream_guess_format couldn't find "
|
|
"%dx%d format %d\n", format->width, format->height,
|
|
format->pixel_format));
|
|
return EINVAL;
|
|
}
|
|
|
|
uvideo_init_probe_data(&probe);
|
|
probe.bFormatIndex = UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt);
|
|
probe.bFrameIndex = UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt);
|
|
USETDW(probe.dwFrameInterval, vs->vs_frame_interval); /* XXX */
|
|
|
|
maxprobe = probe;
|
|
err = uvideo_stream_probe(vs, UR_GET_MAX, &maxprobe);
|
|
if (err) {
|
|
DPRINTF(("uvideo: error probe/GET_MAX: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
} else {
|
|
USETW(probe.wCompQuality, UGETW(maxprobe.wCompQuality));
|
|
}
|
|
|
|
err = uvideo_stream_probe(vs, UR_SET_CUR, &probe);
|
|
if (err) {
|
|
DPRINTF(("uvideo: error commit/SET_CUR: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
|
|
uvideo_init_probe_data(&probe);
|
|
err = uvideo_stream_probe(vs, UR_GET_CUR, &probe);
|
|
if (err) {
|
|
DPRINTF(("uvideo: error commit/SET_CUR: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
|
|
if (probe.bFormatIndex != UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt)) {
|
|
DPRINTF(("uvideo: probe/GET_CUR returned format index %d "
|
|
"(expected %d)\n", probe.bFormatIndex,
|
|
UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt)));
|
|
probe.bFormatIndex = UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt);
|
|
}
|
|
if (probe.bFrameIndex != UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt)) {
|
|
DPRINTF(("uvideo: probe/GET_CUR returned frame index %d "
|
|
"(expected %d)\n", probe.bFrameIndex,
|
|
UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt)));
|
|
probe.bFrameIndex = UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt);
|
|
}
|
|
USETDW(probe.dwFrameInterval, vs->vs_frame_interval); /* XXX */
|
|
|
|
/* commit/SET_CUR. Fourth step is to set the alternate
|
|
* interface. Currently the fourth step is in
|
|
* uvideo_start_transfer. Maybe move it here? */
|
|
err = uvideo_stream_commit(vs, UR_SET_CUR, &probe);
|
|
if (err) {
|
|
DPRINTF(("uvideo: error commit/SET_CUR: %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO;
|
|
}
|
|
|
|
DPRINTFN(15, ("uvideo_set_format: committing to format: "
|
|
"bmHint=0x%04x bFormatIndex=%d bFrameIndex=%d "
|
|
"dwFrameInterval=%u wKeyFrameRate=%d wPFrameRate=%d "
|
|
"wCompQuality=%d wCompWindowSize=%d wDelay=%d "
|
|
"dwMaxVideoFrameSize=%u dwMaxPayloadTransferSize=%u",
|
|
UGETW(probe.bmHint),
|
|
probe.bFormatIndex,
|
|
probe.bFrameIndex,
|
|
UGETDW(probe.dwFrameInterval),
|
|
UGETW(probe.wKeyFrameRate),
|
|
UGETW(probe.wPFrameRate),
|
|
UGETW(probe.wCompQuality),
|
|
UGETW(probe.wCompWindowSize),
|
|
UGETW(probe.wDelay),
|
|
UGETDW(probe.dwMaxVideoFrameSize),
|
|
UGETDW(probe.dwMaxPayloadTransferSize)));
|
|
if (vs->vs_probelen == 34) {
|
|
DPRINTFN(15, (" dwClockFrequency=%u bmFramingInfo=0x%02x "
|
|
"bPreferedVersion=%d bMinVersion=%d "
|
|
"bMaxVersion=%d",
|
|
UGETDW(probe.dwClockFrequency),
|
|
probe.bmFramingInfo,
|
|
probe.bPreferedVersion,
|
|
probe.bMinVersion,
|
|
probe.bMaxVersion));
|
|
}
|
|
DPRINTFN(15, ("\n"));
|
|
|
|
vs->vs_frame_interval = UGETDW(probe.dwFrameInterval);
|
|
vs->vs_max_payload_size = UGETDW(probe.dwMaxPayloadTransferSize);
|
|
|
|
*format = uvfmt->format;
|
|
vs->vs_current_format = *format;
|
|
DPRINTF(("uvideo_set_format: pixeltype is %d\n", format->pixel_format));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
uvideo_try_format(void *addr, struct video_format *format)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
struct uvideo_stream *vs = sc->sc_stream_in;
|
|
struct uvideo_format *uvfmt;
|
|
|
|
uvfmt = uvideo_stream_guess_format(vs, format->pixel_format,
|
|
format->width, format->height);
|
|
if (uvfmt == NULL)
|
|
return EINVAL;
|
|
|
|
*format = uvfmt->format;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
uvideo_get_framerate(void *addr, struct video_fract *fract)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
struct uvideo_stream *vs = sc->sc_stream_in;
|
|
|
|
switch (vs->vs_frame_interval) {
|
|
case 41666: /* 240 */
|
|
case 83333: /* 120 */
|
|
case 166666: /* 60 */
|
|
case 200000: /* 50 */
|
|
case 333333: /* 30 */
|
|
case 400000: /* 25 */
|
|
case 500000: /* 20 */
|
|
case 666666: /* 15 */
|
|
case 1000000: /* 10 */
|
|
fract->numerator = 1;
|
|
fract->denominator = 10000000 / vs->vs_frame_interval;
|
|
break;
|
|
case 166833: /* 59.94 */
|
|
fract->numerator = 60;
|
|
fract->denominator = 1001;
|
|
break;
|
|
case 333667: /* 29.97 */
|
|
fract->numerator = 30;
|
|
fract->denominator = 1001;
|
|
break;
|
|
default:
|
|
fract->numerator = vs->vs_frame_interval;
|
|
fract->denominator = 10000000;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
uvideo_set_framerate(void *addr, struct video_fract *fract)
|
|
{
|
|
/* XXX setting framerate is not supported yet, return actual rate */
|
|
return uvideo_get_framerate(addr, fract);
|
|
}
|
|
|
|
static int
|
|
uvideo_start_transfer(void *addr)
|
|
{
|
|
struct uvideo_softc *sc = addr;
|
|
struct uvideo_stream *vs;
|
|
int s, err;
|
|
|
|
/* FIXME: this function should be stream specific */
|
|
vs = SLIST_FIRST(&sc->sc_stream_list);
|
|
s = splusb();
|
|
err = uvideo_stream_start_xfer(vs);
|
|
splx(s);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
uvideo_stop_transfer(void *addr)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
int err, s;
|
|
|
|
sc = addr;
|
|
|
|
s = splusb();
|
|
err = uvideo_stream_stop_xfer(sc->sc_stream_in);
|
|
splx(s);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int
|
|
uvideo_get_control_group(void *addr, struct video_control_group *group)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
usb_device_request_t req;
|
|
usbd_status err;
|
|
uint8_t control_id, ent_id, data[16];
|
|
uint16_t len;
|
|
int s;
|
|
|
|
sc = addr;
|
|
|
|
/* request setup */
|
|
switch (group->group_id) {
|
|
case VIDEO_CONTROL_PANTILT_RELATIVE:
|
|
if (group->length != 4)
|
|
return EINVAL;
|
|
|
|
return EINVAL;
|
|
case VIDEO_CONTROL_SHARPNESS:
|
|
if (group->length != 1)
|
|
return EINVAL;
|
|
|
|
control_id = UVIDEO_PU_SHARPNESS_CONTROL;
|
|
ent_id = 2; /* TODO: hardcoded logitech processing unit */
|
|
len = 2;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
/* do request */
|
|
req.bmRequestType = UVIDEO_REQUEST_TYPE_INTERFACE |
|
|
UVIDEO_REQUEST_TYPE_CLASS_SPECIFIC |
|
|
UVIDEO_REQUEST_TYPE_GET;
|
|
req.bRequest = UR_GET_CUR;
|
|
USETW(req.wValue, control_id << 8);
|
|
USETW(req.wIndex, (ent_id << 8) | sc->sc_ifaceno);
|
|
USETW(req.wLength, len);
|
|
|
|
s = splusb();
|
|
err = usbd_do_request(sc->sc_udev, &req, data);
|
|
splx(s);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_set_control: error %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO; /* TODO: more detail here? */
|
|
}
|
|
|
|
/* extract request data */
|
|
switch (group->group_id) {
|
|
case VIDEO_CONTROL_SHARPNESS:
|
|
group->control[0].value = UGETW(data);
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
uvideo_set_control_group(void *addr, const struct video_control_group *group)
|
|
{
|
|
struct uvideo_softc *sc;
|
|
usb_device_request_t req;
|
|
usbd_status err;
|
|
uint8_t control_id, ent_id, data[16]; /* long enough for all controls */
|
|
uint16_t len;
|
|
int s;
|
|
|
|
sc = addr;
|
|
|
|
switch (group->group_id) {
|
|
case VIDEO_CONTROL_PANTILT_RELATIVE:
|
|
if (group->length != 4)
|
|
return EINVAL;
|
|
|
|
if (group->control[0].value != 0 ||
|
|
group->control[0].value != 1 ||
|
|
group->control[0].value != 0xff)
|
|
return ERANGE;
|
|
|
|
if (group->control[2].value != 0 ||
|
|
group->control[2].value != 1 ||
|
|
group->control[2].value != 0xff)
|
|
return ERANGE;
|
|
|
|
control_id = UVIDEO_CT_PANTILT_RELATIVE_CONTROL;
|
|
ent_id = 1; /* TODO: hardcoded logitech camera terminal */
|
|
len = 4;
|
|
data[0] = group->control[0].value;
|
|
data[1] = group->control[1].value;
|
|
data[2] = group->control[2].value;
|
|
data[3] = group->control[3].value;
|
|
break;
|
|
case VIDEO_CONTROL_BRIGHTNESS:
|
|
if (group->length != 1)
|
|
return EINVAL;
|
|
control_id = UVIDEO_PU_BRIGHTNESS_CONTROL;
|
|
ent_id = 2;
|
|
len = 2;
|
|
USETW(data, group->control[0].value);
|
|
break;
|
|
case VIDEO_CONTROL_GAIN:
|
|
if (group->length != 1)
|
|
return EINVAL;
|
|
control_id = UVIDEO_PU_GAIN_CONTROL;
|
|
ent_id = 2;
|
|
len = 2;
|
|
USETW(data, group->control[0].value);
|
|
break;
|
|
case VIDEO_CONTROL_SHARPNESS:
|
|
if (group->length != 1)
|
|
return EINVAL;
|
|
control_id = UVIDEO_PU_SHARPNESS_CONTROL;
|
|
ent_id = 2; /* TODO: hardcoded logitech processing unit */
|
|
len = 2;
|
|
USETW(data, group->control[0].value);
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
req.bmRequestType = UVIDEO_REQUEST_TYPE_INTERFACE |
|
|
UVIDEO_REQUEST_TYPE_CLASS_SPECIFIC |
|
|
UVIDEO_REQUEST_TYPE_SET;
|
|
req.bRequest = UR_SET_CUR;
|
|
USETW(req.wValue, control_id << 8);
|
|
USETW(req.wIndex, (ent_id << 8) | sc->sc_ifaceno);
|
|
USETW(req.wLength, len);
|
|
|
|
s = splusb();
|
|
err = usbd_do_request(sc->sc_udev, &req, data);
|
|
splx(s);
|
|
if (err != USBD_NORMAL_COMPLETION) {
|
|
DPRINTF(("uvideo_set_control: error %s (%d)\n",
|
|
usbd_errstr(err), err));
|
|
return EIO; /* TODO: more detail here? */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static usbd_status
|
|
uvideo_stream_probe_and_commit(struct uvideo_stream *vs,
|
|
uint8_t action, uint8_t control,
|
|
void *data)
|
|
{
|
|
usb_device_request_t req;
|
|
|
|
switch (action) {
|
|
case UR_SET_CUR:
|
|
req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
|
|
USETW(req.wLength, vs->vs_probelen);
|
|
break;
|
|
case UR_GET_CUR:
|
|
case UR_GET_MIN:
|
|
case UR_GET_MAX:
|
|
case UR_GET_DEF:
|
|
req.bmRequestType = UT_READ_CLASS_INTERFACE;
|
|
USETW(req.wLength, vs->vs_probelen);
|
|
break;
|
|
case UR_GET_INFO:
|
|
req.bmRequestType = UT_READ_CLASS_INTERFACE;
|
|
USETW(req.wLength, sizeof(uByte));
|
|
break;
|
|
case UR_GET_LEN:
|
|
req.bmRequestType = UT_READ_CLASS_INTERFACE;
|
|
USETW(req.wLength, sizeof(uWord)); /* is this right? */
|
|
break;
|
|
default:
|
|
DPRINTF(("uvideo_probe_and_commit: "
|
|
"unknown request action %d\n", action));
|
|
return USBD_NOT_STARTED;
|
|
}
|
|
|
|
req.bRequest = action;
|
|
USETW2(req.wValue, control, 0);
|
|
USETW2(req.wIndex, 0, vs->vs_ifaceno);
|
|
|
|
return (usbd_do_request_flags(vs->vs_parent->sc_udev, &req, data,
|
|
0, 0,
|
|
USBD_DEFAULT_TIMEOUT));
|
|
}
|
|
|
|
static void
|
|
uvideo_init_probe_data(uvideo_probe_and_commit_data_t *probe)
|
|
{
|
|
/* all zeroes tells camera to choose what it wants */
|
|
memset(probe, 0, sizeof(*probe));
|
|
}
|
|
|
|
|
|
#ifdef _MODULE
|
|
|
|
MODULE(MODULE_CLASS_DRIVER, uvideo, NULL);
|
|
static const struct cfiattrdata videobuscf_iattrdata = {
|
|
"videobus", 0, {
|
|
{ NULL, NULL, 0 },
|
|
}
|
|
};
|
|
static const struct cfiattrdata * const uvideo_attrs[] = {
|
|
&videobuscf_iattrdata, NULL
|
|
};
|
|
CFDRIVER_DECL(uvideo, DV_DULL, uvideo_attrs);
|
|
extern struct cfattach uvideo_ca;
|
|
extern struct cfattach uvideo_ca;
|
|
static int uvideoloc[6] = { -1, -1, -1, -1, -1, -1 };
|
|
static struct cfparent uhubparent = {
|
|
"usbifif", NULL, DVUNIT_ANY
|
|
};
|
|
static struct cfdata uvideo_cfdata[] = {
|
|
{
|
|
.cf_name = "uvideo",
|
|
.cf_atname = "uvideo",
|
|
.cf_unit = 0,
|
|
.cf_fstate = FSTATE_STAR,
|
|
.cf_loc = uvideoloc,
|
|
.cf_flags = 0,
|
|
.cf_pspec = &uhubparent,
|
|
},
|
|
{ NULL, NULL, 0, 0, NULL, 0, NULL },
|
|
};
|
|
|
|
static int
|
|
uvideo_modcmd(modcmd_t cmd, void *arg)
|
|
{
|
|
int err;
|
|
|
|
|
|
switch (cmd) {
|
|
case MODULE_CMD_INIT:
|
|
DPRINTF(("uvideo: attempting to load\n"));
|
|
|
|
err = config_cfdriver_attach(&uvideo_cd);
|
|
if (err)
|
|
return err;
|
|
err = config_cfattach_attach("uvideo", &uvideo_ca);
|
|
if (err) {
|
|
config_cfdriver_detach(&uvideo_cd);
|
|
return err;
|
|
}
|
|
err = config_cfdata_attach(uvideo_cfdata, 1);
|
|
if (err) {
|
|
config_cfattach_detach("uvideo", &uvideo_ca);
|
|
config_cfdriver_detach(&uvideo_cd);
|
|
return err;
|
|
}
|
|
DPRINTF(("uvideo: loaded module\n"));
|
|
return 0;
|
|
case MODULE_CMD_FINI:
|
|
DPRINTF(("uvideo: attempting to unload module\n"));
|
|
err = config_cfdata_detach(uvideo_cfdata);
|
|
if (err)
|
|
return err;
|
|
config_cfattach_detach("uvideo", &uvideo_ca);
|
|
config_cfdriver_detach(&uvideo_cd);
|
|
DPRINTF(("uvideo: module unload\n"));
|
|
return 0;
|
|
default:
|
|
return ENOTTY;
|
|
}
|
|
}
|
|
|
|
#endif /* _MODULE */
|
|
|
|
|
|
#ifdef UVIDEO_DEBUG
|
|
/* Some functions to print out descriptors. Mostly useless other than
|
|
* debugging/exploration purposes. */
|
|
|
|
|
|
static void
|
|
print_bitmap(const uByte *start, uByte nbytes)
|
|
{
|
|
int byte, bit;
|
|
|
|
/* most significant first */
|
|
for (byte = nbytes-1; byte >= 0; --byte) {
|
|
if (byte < nbytes-1) printf("-");
|
|
for (bit = 7; bit >= 0; --bit)
|
|
printf("%01d", (start[byte] >> bit) &1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_descriptor(const usb_descriptor_t *desc)
|
|
{
|
|
static int current_class = -1;
|
|
static int current_subclass = -1;
|
|
|
|
if (desc->bDescriptorType == UDESC_INTERFACE) {
|
|
const usb_interface_descriptor_t *id;
|
|
id = (const usb_interface_descriptor_t *)desc;
|
|
current_class = id->bInterfaceClass;
|
|
current_subclass = id->bInterfaceSubClass;
|
|
print_interface_descriptor(id);
|
|
printf("\n");
|
|
return;
|
|
}
|
|
|
|
printf(" "); /* indent */
|
|
|
|
if (current_class == UICLASS_VIDEO) {
|
|
switch (current_subclass) {
|
|
case UISUBCLASS_VIDEOCONTROL:
|
|
print_vc_descriptor(desc);
|
|
break;
|
|
case UISUBCLASS_VIDEOSTREAMING:
|
|
print_vs_descriptor(desc);
|
|
break;
|
|
case UISUBCLASS_VIDEOCOLLECTION:
|
|
printf("uvc collection: len=%d type=0x%02x",
|
|
desc->bLength, desc->bDescriptorType);
|
|
break;
|
|
}
|
|
} else {
|
|
printf("non uvc descriptor len=%d type=0x%02x",
|
|
desc->bLength, desc->bDescriptorType);
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
print_vc_descriptor(const usb_descriptor_t *desc)
|
|
{
|
|
const uvideo_descriptor_t *vcdesc;
|
|
|
|
printf("VC ");
|
|
|
|
switch (desc->bDescriptorType) {
|
|
case UDESC_ENDPOINT:
|
|
print_endpoint_descriptor(
|
|
(const usb_endpoint_descriptor_t *)desc);
|
|
break;
|
|
case UDESC_CS_INTERFACE:
|
|
vcdesc = (const uvideo_descriptor_t *)desc;
|
|
switch (vcdesc->bDescriptorSubtype) {
|
|
case UDESC_VC_HEADER:
|
|
print_vc_header_descriptor(
|
|
(const uvideo_vc_header_descriptor_t *)
|
|
vcdesc);
|
|
break;
|
|
case UDESC_INPUT_TERMINAL:
|
|
switch (UGETW(
|
|
((const uvideo_input_terminal_descriptor_t *)
|
|
vcdesc)->wTerminalType)) {
|
|
case UVIDEO_ITT_CAMERA:
|
|
print_camera_terminal_descriptor(
|
|
(const uvideo_camera_terminal_descriptor_t *)vcdesc);
|
|
break;
|
|
default:
|
|
print_input_terminal_descriptor(
|
|
(const uvideo_input_terminal_descriptor_t *)vcdesc);
|
|
break;
|
|
}
|
|
break;
|
|
case UDESC_OUTPUT_TERMINAL:
|
|
print_output_terminal_descriptor(
|
|
(const uvideo_output_terminal_descriptor_t *)
|
|
vcdesc);
|
|
break;
|
|
case UDESC_SELECTOR_UNIT:
|
|
print_selector_unit_descriptor(
|
|
(const uvideo_selector_unit_descriptor_t *)
|
|
vcdesc);
|
|
break;
|
|
case UDESC_PROCESSING_UNIT:
|
|
print_processing_unit_descriptor(
|
|
(const uvideo_processing_unit_descriptor_t *)
|
|
vcdesc);
|
|
break;
|
|
case UDESC_EXTENSION_UNIT:
|
|
print_extension_unit_descriptor(
|
|
(const uvideo_extension_unit_descriptor_t *)
|
|
vcdesc);
|
|
break;
|
|
default:
|
|
printf("class specific interface "
|
|
"len=%d type=0x%02x subtype=0x%02x",
|
|
vcdesc->bLength,
|
|
vcdesc->bDescriptorType,
|
|
vcdesc->bDescriptorSubtype);
|
|
break;
|
|
}
|
|
break;
|
|
case UDESC_CS_ENDPOINT:
|
|
vcdesc = (const uvideo_descriptor_t *)desc;
|
|
switch (vcdesc->bDescriptorSubtype) {
|
|
case UDESC_VC_INTERRUPT_ENDPOINT:
|
|
print_interrupt_endpoint_descriptor(
|
|
(const uvideo_vc_interrupt_endpoint_descriptor_t *)
|
|
vcdesc);
|
|
break;
|
|
default:
|
|
printf("class specific endpoint "
|
|
"len=%d type=0x%02x subtype=0x%02x",
|
|
vcdesc->bLength,
|
|
vcdesc->bDescriptorType,
|
|
vcdesc->bDescriptorSubtype);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
printf("unknown: len=%d type=0x%02x",
|
|
desc->bLength, desc->bDescriptorType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_vs_descriptor(const usb_descriptor_t *desc)
|
|
{
|
|
const uvideo_descriptor_t * vsdesc;
|
|
printf("VS ");
|
|
|
|
switch (desc->bDescriptorType) {
|
|
case UDESC_ENDPOINT:
|
|
print_endpoint_descriptor(
|
|
(const usb_endpoint_descriptor_t *)desc);
|
|
break;
|
|
case UDESC_CS_INTERFACE:
|
|
vsdesc = (const uvideo_descriptor_t *)desc;
|
|
switch (vsdesc->bDescriptorSubtype) {
|
|
case UDESC_VS_INPUT_HEADER:
|
|
print_vs_input_header_descriptor(
|
|
(const uvideo_vs_input_header_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
case UDESC_VS_OUTPUT_HEADER:
|
|
print_vs_output_header_descriptor(
|
|
(const uvideo_vs_output_header_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
case UDESC_VS_FORMAT_UNCOMPRESSED:
|
|
print_vs_format_uncompressed_descriptor(
|
|
(const uvideo_vs_format_uncompressed_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
case UDESC_VS_FRAME_UNCOMPRESSED:
|
|
print_vs_frame_uncompressed_descriptor(
|
|
(const uvideo_vs_frame_uncompressed_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
case UDESC_VS_FORMAT_MJPEG:
|
|
print_vs_format_mjpeg_descriptor(
|
|
(const uvideo_vs_format_mjpeg_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
case UDESC_VS_FRAME_MJPEG:
|
|
print_vs_frame_mjpeg_descriptor(
|
|
(const uvideo_vs_frame_mjpeg_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
case UDESC_VS_FORMAT_DV:
|
|
print_vs_format_dv_descriptor(
|
|
(const uvideo_vs_format_dv_descriptor_t *)
|
|
vsdesc);
|
|
break;
|
|
default:
|
|
printf("unknown cs interface: len=%d type=0x%02x "
|
|
"subtype=0x%02x",
|
|
vsdesc->bLength, vsdesc->bDescriptorType,
|
|
vsdesc->bDescriptorSubtype);
|
|
}
|
|
break;
|
|
default:
|
|
printf("unknown: len=%d type=0x%02x",
|
|
desc->bLength, desc->bDescriptorType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_interface_descriptor(const usb_interface_descriptor_t *id)
|
|
{
|
|
printf("Interface: Len=%d Type=0x%02x "
|
|
"bInterfaceNumber=0x%02x "
|
|
"bAlternateSetting=0x%02x bNumEndpoints=0x%02x "
|
|
"bInterfaceClass=0x%02x bInterfaceSubClass=0x%02x "
|
|
"bInterfaceProtocol=0x%02x iInterface=0x%02x",
|
|
id->bLength,
|
|
id->bDescriptorType,
|
|
id->bInterfaceNumber,
|
|
id->bAlternateSetting,
|
|
id->bNumEndpoints,
|
|
id->bInterfaceClass,
|
|
id->bInterfaceSubClass,
|
|
id->bInterfaceProtocol,
|
|
id->iInterface);
|
|
}
|
|
|
|
static void
|
|
print_endpoint_descriptor(const usb_endpoint_descriptor_t *desc)
|
|
{
|
|
printf("Endpoint: Len=%d Type=0x%02x "
|
|
"bEndpointAddress=0x%02x ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bEndpointAddress);
|
|
printf("bmAttributes=");
|
|
print_bitmap(&desc->bmAttributes, 1);
|
|
printf(" wMaxPacketSize=%d bInterval=%d",
|
|
UGETW(desc->wMaxPacketSize),
|
|
desc->bInterval);
|
|
}
|
|
|
|
static void
|
|
print_vc_header_descriptor(
|
|
const uvideo_vc_header_descriptor_t *desc)
|
|
{
|
|
printf("Interface Header: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bcdUVC=%d wTotalLength=%d "
|
|
"dwClockFrequency=%u bInCollection=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
UGETW(desc->bcdUVC),
|
|
UGETW(desc->wTotalLength),
|
|
UGETDW(desc->dwClockFrequency),
|
|
desc->bInCollection);
|
|
}
|
|
|
|
static void
|
|
print_input_terminal_descriptor(
|
|
const uvideo_input_terminal_descriptor_t *desc)
|
|
{
|
|
printf("Input Terminal: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bTerminalID=%d wTerminalType=%x bAssocTerminal=%d "
|
|
"iTerminal=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bTerminalID,
|
|
UGETW(desc->wTerminalType),
|
|
desc->bAssocTerminal,
|
|
desc->iTerminal);
|
|
}
|
|
|
|
static void
|
|
print_output_terminal_descriptor(
|
|
const uvideo_output_terminal_descriptor_t *desc)
|
|
{
|
|
printf("Output Terminal: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bTerminalID=%d wTerminalType=%x bAssocTerminal=%d "
|
|
"bSourceID=%d iTerminal=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bTerminalID,
|
|
UGETW(desc->wTerminalType),
|
|
desc->bAssocTerminal,
|
|
desc->bSourceID,
|
|
desc->iTerminal);
|
|
}
|
|
|
|
static void
|
|
print_camera_terminal_descriptor(
|
|
const uvideo_camera_terminal_descriptor_t *desc)
|
|
{
|
|
printf("Camera Terminal: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bTerminalID=%d wTerminalType=%x bAssocTerminal=%d "
|
|
"iTerminal=%d "
|
|
"wObjectiveFocalLengthMin/Max=%d/%d "
|
|
"wOcularFocalLength=%d "
|
|
"bControlSize=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bTerminalID,
|
|
UGETW(desc->wTerminalType),
|
|
desc->bAssocTerminal,
|
|
desc->iTerminal,
|
|
UGETW(desc->wObjectiveFocalLengthMin),
|
|
UGETW(desc->wObjectiveFocalLengthMax),
|
|
UGETW(desc->wOcularFocalLength),
|
|
desc->bControlSize);
|
|
printf("bmControls=");
|
|
print_bitmap(desc->bmControls, desc->bControlSize);
|
|
}
|
|
|
|
static void
|
|
print_selector_unit_descriptor(
|
|
const uvideo_selector_unit_descriptor_t *desc)
|
|
{
|
|
int i;
|
|
const uByte *b;
|
|
printf("Selector Unit: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bUnitID=%d bNrInPins=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bUnitID,
|
|
desc->bNrInPins);
|
|
printf("baSourceIDs=");
|
|
b = &desc->baSourceID[0];
|
|
for (i = 0; i < desc->bNrInPins; ++i)
|
|
printf("%d ", *b++);
|
|
printf("iSelector=%d", *b);
|
|
}
|
|
|
|
static void
|
|
print_processing_unit_descriptor(
|
|
const uvideo_processing_unit_descriptor_t *desc)
|
|
{
|
|
const uByte *b;
|
|
|
|
printf("Processing Unit: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bUnitID=%d bSourceID=%d wMaxMultiplier=%d bControlSize=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bUnitID,
|
|
desc->bSourceID,
|
|
UGETW(desc->wMaxMultiplier),
|
|
desc->bControlSize);
|
|
printf("bmControls=");
|
|
print_bitmap(desc->bmControls, desc->bControlSize);
|
|
b = &desc->bControlSize + desc->bControlSize + 1;
|
|
printf(" iProcessing=%d bmVideoStandards=", *b);
|
|
b += 1;
|
|
print_bitmap(b, 1);
|
|
}
|
|
|
|
static void
|
|
print_extension_unit_descriptor(
|
|
const uvideo_extension_unit_descriptor_t *desc)
|
|
{
|
|
const uByte * byte;
|
|
uByte controlbytes;
|
|
int i;
|
|
|
|
printf("Extension Unit: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bUnitID=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bUnitID);
|
|
|
|
printf("guidExtensionCode=");
|
|
usb_guid_print(&desc->guidExtensionCode);
|
|
printf(" ");
|
|
|
|
printf("bNumControls=%d bNrInPins=%d ",
|
|
desc->bNumControls,
|
|
desc->bNrInPins);
|
|
|
|
printf("baSourceIDs=");
|
|
byte = &desc->baSourceID[0];
|
|
for (i = 0; i < desc->bNrInPins; ++i)
|
|
printf("%d ", *byte++);
|
|
|
|
controlbytes = *byte++;
|
|
printf("bControlSize=%d ", controlbytes);
|
|
printf("bmControls=");
|
|
print_bitmap(byte, controlbytes);
|
|
|
|
byte += controlbytes;
|
|
printf(" iExtension=%d", *byte);
|
|
}
|
|
|
|
static void
|
|
print_interrupt_endpoint_descriptor(
|
|
const uvideo_vc_interrupt_endpoint_descriptor_t *desc)
|
|
{
|
|
printf("Interrupt Endpoint: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"wMaxTransferSize=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
UGETW(desc->wMaxTransferSize));
|
|
}
|
|
|
|
|
|
static void
|
|
print_vs_output_header_descriptor(
|
|
const uvideo_vs_output_header_descriptor_t *desc)
|
|
{
|
|
printf("Interface Output Header: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bNumFormats=%d wTotalLength=%d bEndpointAddress=%d "
|
|
"bTerminalLink=%d bControlSize=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bNumFormats,
|
|
UGETW(desc->wTotalLength),
|
|
desc->bEndpointAddress,
|
|
desc->bTerminalLink,
|
|
desc->bControlSize);
|
|
}
|
|
|
|
static void
|
|
print_vs_input_header_descriptor(
|
|
const uvideo_vs_input_header_descriptor_t *desc)
|
|
{
|
|
printf("Interface Input Header: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bNumFormats=%d wTotalLength=%d bEndpointAddress=%d "
|
|
"bmInfo=%x bTerminalLink=%d bStillCaptureMethod=%d "
|
|
"bTriggerSupport=%d bTriggerUsage=%d bControlSize=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bNumFormats,
|
|
UGETW(desc->wTotalLength),
|
|
desc->bEndpointAddress,
|
|
desc->bmInfo,
|
|
desc->bTerminalLink,
|
|
desc->bStillCaptureMethod,
|
|
desc->bTriggerSupport,
|
|
desc->bTriggerUsage,
|
|
desc->bControlSize);
|
|
print_bitmap(desc->bmaControls, desc->bControlSize);
|
|
}
|
|
|
|
static void
|
|
print_vs_format_uncompressed_descriptor(
|
|
const uvideo_vs_format_uncompressed_descriptor_t *desc)
|
|
{
|
|
printf("Format Uncompressed: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bFormatIndex=%d bNumFrameDescriptors=%d ",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bFormatIndex,
|
|
desc->bNumFrameDescriptors);
|
|
usb_guid_print(&desc->guidFormat);
|
|
printf(" bBitsPerPixel=%d bDefaultFrameIndex=%d "
|
|
"bAspectRatioX=%d bAspectRatioY=%d "
|
|
"bmInterlaceFlags=0x%02x bCopyProtect=%d",
|
|
desc->bBitsPerPixel,
|
|
desc->bDefaultFrameIndex,
|
|
desc->bAspectRatioX,
|
|
desc->bAspectRatioY,
|
|
desc->bmInterlaceFlags,
|
|
desc->bCopyProtect);
|
|
}
|
|
|
|
static void
|
|
print_vs_frame_uncompressed_descriptor(
|
|
const uvideo_vs_frame_uncompressed_descriptor_t *desc)
|
|
{
|
|
printf("Frame Uncompressed: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bFrameIndex=%d bmCapabilities=0x%02x "
|
|
"wWidth=%d wHeight=%d dwMinBitRate=%u dwMaxBitRate=%u "
|
|
"dwMaxVideoFrameBufferSize=%u dwDefaultFrameInterval=%u "
|
|
"bFrameIntervalType=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bFrameIndex,
|
|
desc->bmCapabilities,
|
|
UGETW(desc->wWidth),
|
|
UGETW(desc->wHeight),
|
|
UGETDW(desc->dwMinBitRate),
|
|
UGETDW(desc->dwMaxBitRate),
|
|
UGETDW(desc->dwMaxVideoFrameBufferSize),
|
|
UGETDW(desc->dwDefaultFrameInterval),
|
|
desc->bFrameIntervalType);
|
|
}
|
|
|
|
static void
|
|
print_vs_format_mjpeg_descriptor(
|
|
const uvideo_vs_format_mjpeg_descriptor_t *desc)
|
|
{
|
|
printf("MJPEG format: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bFormatIndex=%d bNumFrameDescriptors=%d bmFlags=0x%02x "
|
|
"bDefaultFrameIndex=%d bAspectRatioX=%d bAspectRatioY=%d "
|
|
"bmInterlaceFlags=0x%02x bCopyProtect=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bFormatIndex,
|
|
desc->bNumFrameDescriptors,
|
|
desc->bmFlags,
|
|
desc->bDefaultFrameIndex,
|
|
desc->bAspectRatioX,
|
|
desc->bAspectRatioY,
|
|
desc->bmInterlaceFlags,
|
|
desc->bCopyProtect);
|
|
}
|
|
|
|
static void
|
|
print_vs_frame_mjpeg_descriptor(
|
|
const uvideo_vs_frame_mjpeg_descriptor_t *desc)
|
|
{
|
|
printf("MJPEG frame: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bFrameIndex=%d bmCapabilities=0x%02x "
|
|
"wWidth=%d wHeight=%d dwMinBitRate=%u dwMaxBitRate=%u "
|
|
"dwMaxVideoFrameBufferSize=%u dwDefaultFrameInterval=%u "
|
|
"bFrameIntervalType=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bFrameIndex,
|
|
desc->bmCapabilities,
|
|
UGETW(desc->wWidth),
|
|
UGETW(desc->wHeight),
|
|
UGETDW(desc->dwMinBitRate),
|
|
UGETDW(desc->dwMaxBitRate),
|
|
UGETDW(desc->dwMaxVideoFrameBufferSize),
|
|
UGETDW(desc->dwDefaultFrameInterval),
|
|
desc->bFrameIntervalType);
|
|
}
|
|
|
|
static void
|
|
print_vs_format_dv_descriptor(
|
|
const uvideo_vs_format_dv_descriptor_t *desc)
|
|
{
|
|
printf("MJPEG format: "
|
|
"Len=%d Type=0x%02x Subtype=0x%02x "
|
|
"bFormatIndex=%d dwMaxVideoFrameBufferSize=%u "
|
|
"bFormatType/Rate=%d bFormatType/Format=%d",
|
|
desc->bLength,
|
|
desc->bDescriptorType,
|
|
desc->bDescriptorSubtype,
|
|
desc->bFormatIndex,
|
|
UGETDW(desc->dwMaxVideoFrameBufferSize),
|
|
UVIDEO_GET_DV_FREQ(desc->bFormatType),
|
|
UVIDEO_GET_DV_FORMAT(desc->bFormatType));
|
|
}
|
|
|
|
#endif /* !UVIDEO_DEBUG */
|
|
|
|
#ifdef UVIDEO_DEBUG
|
|
static void
|
|
usb_guid_print(const usb_guid_t *guid)
|
|
{
|
|
printf("%04X-%02X-%02X-",
|
|
UGETDW(guid->data1),
|
|
UGETW(guid->data2),
|
|
UGETW(guid->data3));
|
|
printf("%02X%02X-",
|
|
guid->data4[0],
|
|
guid->data4[1]);
|
|
printf("%02X%02X%02X%02X%02X%02X",
|
|
guid->data4[2],
|
|
guid->data4[3],
|
|
guid->data4[4],
|
|
guid->data4[5],
|
|
guid->data4[6],
|
|
guid->data4[7]);
|
|
}
|
|
#endif /* !UVIDEO_DEBUG */
|
|
|
|
/* Returns less than zero, zero, or greater than zero if uguid is less
|
|
* than, equal to, or greater than guid. */
|
|
static int
|
|
usb_guid_cmp(const usb_guid_t *uguid, const guid_t *guid)
|
|
{
|
|
if (guid->data1 > UGETDW(uguid->data1))
|
|
return 1;
|
|
else if (guid->data1 < UGETDW(uguid->data1))
|
|
return -1;
|
|
|
|
if (guid->data2 > UGETW(uguid->data2))
|
|
return 1;
|
|
else if (guid->data2 < UGETW(uguid->data2))
|
|
return -1;
|
|
|
|
if (guid->data3 > UGETW(uguid->data3))
|
|
return 1;
|
|
else if (guid->data3 < UGETW(uguid->data3))
|
|
return -1;
|
|
|
|
return memcmp(guid->data4, uguid->data4, 8);
|
|
}
|