NetBSD/sys/dev/usb/uvideo.c

3279 lines
86 KiB
C

/* $NetBSD: uvideo.c,v 1.85 2023/04/10 15:27:51 mlelstv 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.85 2023/04/10 15:27:51 mlelstv 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) __nothing
#define DPRINTFN(n,x) __nothing
#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 extension 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 {
device_t vs_videodev;
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;
uvideo_state vs_state;
};
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;
int sc_dying;
uint8_t sc_nunits;
struct uvideo_unit **sc_unit;
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 void uvideo_unit_alloc_controls(struct uvideo_unit *,
uint8_t,
const uint8_t *);
static void uvideo_unit_free_controls(struct uvideo_unit *);
static void 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 *);
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;
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;
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);
while ((ifdesc = usb_desc_iter_next_interface(&iter)) != NULL) {
KASSERT(ifdesc->bLength >= USB_INTERFACE_DESCRIPTOR_SIZE);
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);
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;
}
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");
SLIST_FOREACH(vs, &sc->sc_stream_list, entries) {
/*
* If the descriptor is invalid, there may be no
* default format.
*
* XXX Maybe this should just be removed from the list
* at some other point, but finding the right other
* point is not trivial.
*/
if (vs->vs_default_format == NULL)
continue;
/* XXX initialization of vs_videodev is racy */
vs->vs_videodev = video_attach_mi(&uvideo_hw_if, sc->sc_dev,
vs);
}
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);
struct uvideo_stream *vs;
SLIST_FOREACH(vs, &sc->sc_stream_list, entries) {
if (child == vs->vs_videodev) {
vs->vs_videodev = NULL;
break;
}
}
KASSERTMSG(vs != NULL, "unknown child of %s detached: %s @ %p",
device_xname(self), device_xname(child), child);
}
static int
uvideo_detach(device_t self, int flags)
{
struct uvideo_softc *sc = device_private(self);
struct uvideo_stream *vs;
int error;
error = config_detach_children(self, flags);
if (error)
return error;
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)));
usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev);
usbd_devinfo_free(sc->sc_devname);
return 0;
}
/*
* 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_zalloc(sizeof(*uvideo_stream_alloc()), 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) {
if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
desc->bLength < sizeof(*uvdesc))
continue;
uvdesc = (const uvideo_descriptor_t *)desc;
if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
continue;
KASSERT(nunits < 255);
++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) {
if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
desc->bLength < sizeof(*uvdesc))
continue;
uvdesc = (const uvideo_descriptor_t *)desc;
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;
KASSERT(i < 255);
++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;
KASSERT(desc->bDescriptorType == UDESC_CS_INTERFACE);
vu = kmem_zalloc(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;
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 void
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);
}
}
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->s.vu_src_id_ary = NULL;
vu->vu_nsrcs = 0;
}
static void
uvideo_unit_alloc_controls(struct uvideo_unit *vu, uint8_t size,
const uint8_t *controls)
{
vu->vu_control_size = size;
if (size == 0)
return;
vu->vu_controls = kmem_alloc(sizeof(*vu->vu_controls) * size, KM_SLEEP);
memcpy(vu->vu_controls, controls, size);
}
static void
uvideo_unit_free_controls(struct uvideo_unit *vu)
{
if (vu->vu_control_size == 0)
return;
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)
{
uWord len;
usbd_status err;
DPRINTF(("%s: %s ifaceno=%d vs=%p\n", __func__,
device_xname(sc->sc_dev),
ifdesc->bInterfaceNumber,
vs));
SLIST_INSERT_HEAD(&sc->sc_stream_list, vs, entries);
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;
vs->vs_state = UVIDEO_STATE_CLOSED;
err = usbd_device2interface_handle(sc->sc_udev, vs->vs_ifaceno,
&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.
*/
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;
DPRINTF(("%s: bInterfaceNumber=%d bAlternateSetting=%d\n", __func__,
ifdesc->bInterfaceNumber, ifdesc->bAlternateSetting));
/*
* 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) {
switch (desc->bDescriptorType) {
case UDESC_ENDPOINT:
if (desc->bLength < sizeof(usb_endpoint_descriptor_t))
goto baddesc;
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 (desc->bLength < sizeof(*uvdesc))
goto baddesc;
uvdesc = (const uvideo_descriptor_t *)desc;
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:
baddesc:
DPRINTF(("uvideo_stream_init_desc: "
"bad descriptor "
"len=%d type=0x%02x\n",
desc->bLength,
desc->bDescriptorType));
break;
}
}
DPRINTF(("%s: bInterfaceNumber=%d bAlternateSetting=%d done\n",
__func__,
ifdesc->bInterfaceNumber, ifdesc->bAlternateSetting));
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(*format));
}
while ((pixel_format = SIMPLEQ_FIRST(&vs->vs_pixel_formats)) != NULL) {
SIMPLEQ_REMOVE_HEAD(&vs->vs_pixel_formats, entries);
kmem_free(pixel_format, sizeof(*pixel_format));
}
kmem_free(vs, sizeof(*vs));
}
#define framedesc_size(T, d) ( \
offsetof(T, uFrameInterval) + \
((T *)(d))->bFrameIntervalType \
? ((T *)(d))->bFrameIntervalType \
* sizeof(((T *)(d))->uFrameInterval.discrete) \
: sizeof(((T *)(d))->uFrameInterval.continuous) \
)
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 usb_descriptor_t *desc;
const uvideo_descriptor_t *uvdesc;
uint8_t subtype, subtypelen, default_index, index;
uint32_t frame_interval;
const usb_guid_t *guid;
DPRINTF(("%s: ifaceno=%d subtype=%d probelen=%d\n", __func__,
vs->vs_ifaceno, vs->vs_subtype, vs->vs_probelen));
pixel_format = VIDEO_FORMAT_UNDEFINED;
switch (format_desc->bDescriptorSubtype) {
case UDESC_VS_FORMAT_UNCOMPRESSED:
DPRINTF(("%s: uncompressed\n", __func__));
if (format_desc->bLength <
sizeof(uvideo_vs_format_uncompressed_descriptor_t)) {
DPRINTF(("uvideo: truncated uncompressed format: %d\n",
format_desc->bLength));
return USBD_INVAL;
}
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;
else {
#ifdef UVIDEO_DEBUG
DPRINTF(("%s: unknown format: ", __func__));
usb_guid_print(guid);
DPRINTF(("\n"));
#endif
}
break;
case UDESC_VS_FORMAT_FRAME_BASED:
DPRINTF(("%s: frame-based\n", __func__));
if (format_desc->bLength <
sizeof(uvideo_format_frame_based_descriptor_t)) {
DPRINTF(("uvideo: truncated frame-based format: %d\n",
format_desc->bLength));
return USBD_INVAL;
}
subtype = UDESC_VS_FRAME_FRAME_BASED;
default_index = GET(uvideo_format_frame_based_descriptor_t,
format_desc,
bDefaultFrameIndex);
break;
case UDESC_VS_FORMAT_MJPEG:
DPRINTF(("%s: mjpeg\n", __func__));
if (format_desc->bLength <
sizeof(uvideo_vs_format_mjpeg_descriptor_t)) {
DPRINTF(("uvideo: truncated mjpeg format: %d\n",
format_desc->bLength));
return USBD_INVAL;
}
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 ((desc = usb_desc_iter_peek(iter)) != NULL) {
if (desc->bDescriptorType != UDESC_CS_INTERFACE)
break;
if (desc->bLength < sizeof(*uvdesc)) {
DPRINTF(("uvideo: truncated CS descriptor, length %d\n",
desc->bLength));
break;
}
uvdesc = (const uvideo_descriptor_t *)desc;
if (uvdesc->bDescriptorSubtype != subtype)
break;
switch (format_desc->bDescriptorSubtype) {
case UDESC_VS_FORMAT_UNCOMPRESSED:
subtypelen = framedesc_size(
const uvideo_vs_frame_uncompressed_descriptor_t,
uvdesc);
break;
case UDESC_VS_FORMAT_MJPEG:
subtypelen = framedesc_size(
const uvideo_vs_frame_mjpeg_descriptor_t,
uvdesc);
break;
case UDESC_VS_FORMAT_FRAME_BASED:
subtypelen = framedesc_size(
const uvideo_frame_frame_based_descriptor_t,
uvdesc);
break;
default:
/* will bail out below */
subtypelen = uvdesc->bLength;
break;
}
if (uvdesc->bLength < subtypelen) {
DPRINTF(("uvideo:"
" truncated CS subtype-0x%x descriptor,"
" length %d < %d\n",
uvdesc->bDescriptorSubtype,
uvdesc->bLength, subtypelen));
break;
}
/* We peeked; now consume. */
(void)usb_desc_iter_next(iter);
format = kmem_zalloc(sizeof(*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(*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 (ix->ix_pipe != NULL) {
usbd_close_pipe(ix->ix_pipe);
ix->ix_pipe = NULL;
}
for (i = 0; i < UVIDEO_NXFERS; i++) {
struct uvideo_isoc *isoc = &ix->ix_i[i];
if (isoc->i_frlengths != NULL) {
kmem_free(isoc->i_frlengths,
sizeof(isoc->i_frlengths[0]) *
ix->ix_nframes);
isoc->i_frlengths = NULL;
}
}
/* Give it some time to settle */
usbd_delay_ms(vs->vs_parent->sc_udev, 20);
/* 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_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_stream *vs = addr;
struct uvideo_softc *sc = vs->vs_parent;
struct video_format fmt;
DPRINTF(("uvideo_open: sc=%p\n", sc));
if (sc->sc_dying)
return EIO;
/* XXX select default format */
if (vs->vs_default_format == NULL)
return EINVAL;
fmt = *vs->vs_default_format;
return uvideo_set_format(addr, &fmt);
}
static void
uvideo_close(void *addr)
{
struct uvideo_stream *vs = addr;
uvideo_stop_transfer(addr);
if (vs->vs_state != UVIDEO_STATE_CLOSED) {
vs->vs_state = UVIDEO_STATE_CLOSED;
}
}
static const char *
uvideo_get_devname(void *addr)
{
struct uvideo_stream *vs = addr;
return vs->vs_parent->sc_devname;
}
static const char *
uvideo_get_businfo(void *addr)
{
struct uvideo_stream *vs = addr;
return vs->vs_parent->sc_businfo;
}
static int
uvideo_enum_format(void *addr, uint32_t index, struct video_format *format)
{
struct uvideo_stream *vs = addr;
struct uvideo_softc *sc = vs->vs_parent;
struct uvideo_format *video_format;
int off;
if (sc->sc_dying)
return EIO;
off = 0;
SIMPLEQ_FOREACH(video_format, &vs->vs_formats, entries) {
if (off++ != index)
continue;
format->pixel_format = video_format->format.pixel_format;
format->width = video_format->format.width;
format->height = video_format->format.height;
return 0;
}
return EINVAL;
}
/*
* uvideo_get_format
*/
static int
uvideo_get_format(void *addr, struct video_format *format)
{
struct uvideo_stream *vs = addr;
struct uvideo_softc *sc = vs->vs_parent;
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_stream *vs = addr;
struct uvideo_softc *sc = vs->vs_parent;
struct uvideo_format *uvfmt;
uvideo_probe_and_commit_data_t probe, maxprobe;
usbd_status err;
DPRINTF(("uvideo_set_format: sc=%p\n", sc));
if (sc->sc_dying)
return EIO;
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_stream *vs = addr;
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_stream *vs = addr;
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_stream *vs = addr;
int s, err;
s = splusb();
err = uvideo_stream_start_xfer(vs);
splx(s);
return err;
}
static int
uvideo_stop_transfer(void *addr)
{
struct uvideo_stream *vs = addr;
int err, s;
s = splusb();
err = uvideo_stream_stop_xfer(vs);
splx(s);
return err;
}
static int
uvideo_get_control_group(void *addr, struct video_control_group *group)
{
struct uvideo_stream *vs = addr;
struct uvideo_softc *sc = vs->vs_parent;
usb_device_request_t req;
usbd_status err;
uint8_t control_id, ent_id, data[16];
uint16_t len;
int s;
/* 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_stream *vs = addr;
struct uvideo_softc *sc = vs->vs_parent;
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;
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;
if (desc->bLength < sizeof(*id)) {
printf("[truncated interface]\n");
return;
}
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:
if (desc->bLength < sizeof(usb_endpoint_descriptor_t)) {
printf("[truncated endpoint]");
break;
}
print_endpoint_descriptor(
(const usb_endpoint_descriptor_t *)desc);
break;
case UDESC_CS_INTERFACE:
if (desc->bLength < sizeof(*vcdesc)) {
printf("[truncated class-specific]");
break;
}
vcdesc = (const uvideo_descriptor_t *)desc;
switch (vcdesc->bDescriptorSubtype) {
case UDESC_VC_HEADER:
if (desc->bLength <
sizeof(uvideo_vc_header_descriptor_t)) {
printf("[truncated videocontrol header]");
break;
}
print_vc_header_descriptor(
(const uvideo_vc_header_descriptor_t *)
vcdesc);
break;
case UDESC_INPUT_TERMINAL:
if (desc->bLength <
sizeof(uvideo_input_terminal_descriptor_t)) {
printf("[truncated input terminal]");
break;
}
switch (UGETW(
((const uvideo_input_terminal_descriptor_t *)
vcdesc)->wTerminalType)) {
case UVIDEO_ITT_CAMERA:
if (desc->bLength <
sizeof(uvideo_camera_terminal_descriptor_t)) {
printf("[truncated camera terminal]");
break;
}
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:
if (desc->bLength <
sizeof(uvideo_output_terminal_descriptor_t)) {
printf("[truncated output terminal]");
break;
}
print_output_terminal_descriptor(
(const uvideo_output_terminal_descriptor_t *)
vcdesc);
break;
case UDESC_SELECTOR_UNIT:
if (desc->bLength <
sizeof(uvideo_selector_unit_descriptor_t)) {
printf("[truncated selector unit]");
break;
}
print_selector_unit_descriptor(
(const uvideo_selector_unit_descriptor_t *)
vcdesc);
break;
case UDESC_PROCESSING_UNIT:
if (desc->bLength <
sizeof(uvideo_processing_unit_descriptor_t)) {
printf("[truncated processing unit]");
break;
}
print_processing_unit_descriptor(
(const uvideo_processing_unit_descriptor_t *)
vcdesc);
break;
case UDESC_EXTENSION_UNIT:
if (desc->bLength <
sizeof(uvideo_extension_unit_descriptor_t)) {
printf("[truncated extension unit]");
break;
}
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:
if (desc->bLength < sizeof(*vcdesc)) {
printf("[truncated class-specific]");
break;
}
vcdesc = (const uvideo_descriptor_t *)desc;
switch (vcdesc->bDescriptorSubtype) {
case UDESC_VC_INTERRUPT_ENDPOINT:
if (desc->bLength <
sizeof(uvideo_vc_interrupt_endpoint_descriptor_t)) {
printf("[truncated "
"videocontrol interrupt endpoint]");
break;
}
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:
if (desc->bLength < sizeof(usb_endpoint_descriptor_t)) {
printf("[truncated endpoint]");
break;
}
print_endpoint_descriptor(
(const usb_endpoint_descriptor_t *)desc);
break;
case UDESC_CS_INTERFACE:
if (desc->bLength < sizeof(*vsdesc)) {
printf("[truncated class-specific]");
break;
}
vsdesc = (const uvideo_descriptor_t *)desc;
switch (vsdesc->bDescriptorSubtype) {
case UDESC_VS_INPUT_HEADER:
if (desc->bLength <
sizeof(uvideo_vs_input_header_descriptor_t)) {
printf("[truncated videostream input header]");
break;
}
print_vs_input_header_descriptor(
(const uvideo_vs_input_header_descriptor_t *)
vsdesc);
break;
case UDESC_VS_OUTPUT_HEADER:
if (desc->bLength <
sizeof(uvideo_vs_output_header_descriptor_t)) {
printf("[truncated "
"videostream output header]");
break;
}
print_vs_output_header_descriptor(
(const uvideo_vs_output_header_descriptor_t *)
vsdesc);
break;
case UDESC_VS_FORMAT_UNCOMPRESSED:
if (desc->bLength <
sizeof(uvideo_vs_format_uncompressed_descriptor_t))
{
printf("[truncated "
"videostream format uncompressed]");
break;
}
print_vs_format_uncompressed_descriptor(
(const uvideo_vs_format_uncompressed_descriptor_t *)
vsdesc);
break;
case UDESC_VS_FRAME_UNCOMPRESSED:
if (desc->bLength <
sizeof(uvideo_vs_frame_uncompressed_descriptor_t))
{
printf("[truncated "
"videostream frame uncompressed]");
break;
}
print_vs_frame_uncompressed_descriptor(
(const uvideo_vs_frame_uncompressed_descriptor_t *)
vsdesc);
break;
case UDESC_VS_FORMAT_MJPEG:
if (desc->bLength <
sizeof(uvideo_vs_format_mjpeg_descriptor_t)) {
printf("[truncated videostream format mjpeg]");
break;
}
print_vs_format_mjpeg_descriptor(
(const uvideo_vs_format_mjpeg_descriptor_t *)
vsdesc);
break;
case UDESC_VS_FRAME_MJPEG:
if (desc->bLength <
sizeof(uvideo_vs_frame_mjpeg_descriptor_t)) {
printf("[truncated videostream frame mjpeg]");
break;
}
print_vs_frame_mjpeg_descriptor(
(const uvideo_vs_frame_mjpeg_descriptor_t *)
vsdesc);
break;
case UDESC_VS_FORMAT_DV:
if (desc->bLength <
sizeof(uvideo_vs_format_dv_descriptor_t)) {
printf("[truncated videostream format dv]");
break;
}
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);
}