3279 lines
86 KiB
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);
|
|
}
|