/*      $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 ([email protected]) 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);
}