root/sys/dev/usb/uvideo.c
/*      $NetBSD: uvideo.c,v 1.85 2023/04/10 15:27:51 mlelstv Exp $      */

/*
 * Copyright (c) 2008 Patrick Mahoney
 * All rights reserved.
 *
 * This code was written by Patrick Mahoney (pat@polycrystal.org) as
 * part of Google Summer of Code 2008.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * USB video specs:
 *      http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvideo.c,v 1.85 2023/04/10 15:27:51 mlelstv Exp $");

#ifdef _KERNEL_OPT
#include "opt_usb.h"
#endif

#ifdef _MODULE
#include <sys/module.h>
#endif

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <sys/select.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/vnode.h>
#include <sys/poll.h>
#include <sys/queue.h>  /* SLIST */
#include <sys/kthread.h>
#include <sys/bus.h>

#include <sys/videoio.h>
#include <dev/video_if.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usb_quirks.h>

#include <dev/usb/uvideoreg.h>

#define UVIDEO_NXFERS   3
#define UVIDEO_NFRAMES_MAX 80
#define PRI_UVIDEO      PRI_BIO

/* #define UVIDEO_DISABLE_MJPEG */

#ifdef UVIDEO_DEBUG
#define DPRINTF(x)      do { if (uvideodebug) printf x; } while (0)
#define DPRINTFN(n,x)   do { if (uvideodebug>(n)) printf x; } while (0)
int     uvideodebug = 20;
#else
#define DPRINTF(x)      __nothing
#define DPRINTFN(n,x)   __nothing
#endif

typedef enum {
        UVIDEO_STATE_CLOSED,
        UVIDEO_STATE_OPENING,
        UVIDEO_STATE_IDLE
} uvideo_state;

struct uvideo_camera_terminal {
        uint16_t        ct_objective_focal_min;
        uint16_t        ct_objective_focal_max;
        uint16_t        ct_ocular_focal_length;
};

struct uvideo_processing_unit {
        uint16_t        pu_max_multiplier; /* digital zoom */
        uint8_t         pu_video_standards;
};

struct uvideo_extension_unit {
        guid_t          xu_guid;
};

/*
 * For simplicity, we consider a Terminal a special case of Unit
 * rather than a separate entity.
 */
struct uvideo_unit {
        uint8_t         vu_id;
        uint8_t         vu_type;
        uint8_t         vu_dst_id;
        uint8_t         vu_nsrcs;
        union {
                uint8_t vu_src_id;      /* vu_nsrcs = 1 */
                uint8_t *vu_src_id_ary; /* vu_nsrcs > 1 */
        } s;

        /* fields for individual unit/terminal types */
        union {
                struct uvideo_camera_terminal   vu_camera;
                struct uvideo_processing_unit   vu_processing;
                struct uvideo_extension_unit    vu_extension;
        } u;

        /* Used by camera terminal, processing and extension units. */
        uint8_t         vu_control_size; /* number of bytes in vu_controls */
        uint8_t         *vu_controls;    /* array of bytes. bits are
                                          * numbered from 0 at least
                                          * significant bit to
                                          * (8*vu_control_size - 1)*/
};

struct uvideo_alternate {
        uint8_t         altno;
        uint8_t         interval;
        uint16_t        max_packet_size;
        SLIST_ENTRY(uvideo_alternate)   entries;
};
SLIST_HEAD(altlist, uvideo_alternate);

#define UVIDEO_FORMAT_GET_FORMAT_INDEX(fmt)     \
        ((fmt)->format.priv & 0xff)
#define UVIDEO_FORMAT_GET_FRAME_INDEX(fmt)      \
        (((fmt)->format.priv >> 8) & 0xff)
/* TODO: find a better way to set bytes within this 32 bit value? */
#define UVIDEO_FORMAT_SET_FORMAT_INDEX(fmt, index) do { \
                (fmt)->format.priv &= ~0xff;            \
                (fmt)->format.priv |= ((index) & 0xff); \
        } while (0)
#define UVIDEO_FORMAT_SET_FRAME_INDEX(fmt, index) do {                  \
                (fmt)->format.priv &= ~(0xff << 8);                     \
                ((fmt)->format.priv |= (((index) & 0xff) << 8));        \
        } while (0)

struct uvideo_pixel_format {
        enum video_pixel_format pixel_format;
        SIMPLEQ_ENTRY(uvideo_pixel_format) entries;
};
SIMPLEQ_HEAD(uvideo_pixel_format_list, uvideo_pixel_format);

struct uvideo_format {
        struct video_format     format;
        SIMPLEQ_ENTRY(uvideo_format) entries;
};
SIMPLEQ_HEAD(uvideo_format_list, uvideo_format);

struct uvideo_isoc_xfer;
struct uvideo_stream;

struct uvideo_isoc {
        struct uvideo_isoc_xfer *i_ix;
        struct uvideo_stream    *i_vs;
        struct usbd_xfer        *i_xfer;
        uint8_t                 *i_buf;
        uint16_t                *i_frlengths;
};

struct uvideo_isoc_xfer {
        uint8_t                 ix_endpt;
        struct usbd_pipe        *ix_pipe;
        struct uvideo_isoc      ix_i[UVIDEO_NXFERS];
        uint32_t                ix_nframes;
        uint32_t                ix_uframe_len;

        struct altlist          ix_altlist;
};

struct uvideo_bulk_xfer {
        uint8_t                 bx_endpt;
        struct usbd_pipe        *bx_pipe;
        struct usbd_xfer        *bx_xfer;
        uint8_t                 *bx_buffer;
        int                     bx_buflen;
        bool                    bx_running;
        kcondvar_t              bx_cv;
        kmutex_t                bx_lock;
};

struct uvideo_stream {
        device_t                vs_videodev;
        struct uvideo_softc     *vs_parent;
        struct usbd_interface   *vs_iface;
        uint8_t                 vs_ifaceno;
        uint8_t                 vs_subtype;  /* input or output */
        uint16_t                vs_probelen; /* length of probe and
                                              * commit data; varies
                                              * depending on version
                                              * of spec. */
        struct uvideo_format_list vs_formats;
        struct uvideo_pixel_format_list vs_pixel_formats;
        struct video_format     *vs_default_format;
        struct video_format     vs_current_format;

        /* usb transfer details */
        uint8_t                 vs_xfer_type;
        union {
                struct uvideo_bulk_xfer bulk;
                struct uvideo_isoc_xfer isoc;
        } vs_xfer;

        int                     vs_frameno;     /* toggles between 0 and 1 */

        /* current video format */
        uint32_t                vs_max_payload_size;
        uint32_t                vs_frame_interval;
        SLIST_ENTRY(uvideo_stream) entries;

        uvideo_state            vs_state;
};
SLIST_HEAD(uvideo_stream_list, uvideo_stream);

struct uvideo_softc {
        device_t        sc_dev;         /* base device */
        struct usbd_device      *sc_udev;       /* device */
        struct usbd_interface   *sc_iface;      /* interface handle */
        int                     sc_ifaceno;     /* interface number */
        char                    *sc_devname;

        int                     sc_dying;

        uint8_t                 sc_nunits;
        struct uvideo_unit      **sc_unit;

        struct uvideo_stream_list sc_stream_list;

        char                    sc_businfo[32];
};

static int      uvideo_match(device_t, cfdata_t, void *);
static void     uvideo_attach(device_t, device_t, void *);
static int      uvideo_detach(device_t, int);
static void     uvideo_childdet(device_t, device_t);
static int      uvideo_activate(device_t, enum devact);

static int      uvideo_open(void *, int);
static void     uvideo_close(void *);
static const char * uvideo_get_devname(void *);
static const char * uvideo_get_businfo(void *);

static int      uvideo_enum_format(void *, uint32_t, struct video_format *);
static int      uvideo_get_format(void *, struct video_format *);
static int      uvideo_set_format(void *, struct video_format *);
static int      uvideo_try_format(void *, struct video_format *);
static int      uvideo_get_framerate(void *, struct video_fract *);
static int      uvideo_set_framerate(void *, struct video_fract *);
static int      uvideo_start_transfer(void *);
static int      uvideo_stop_transfer(void *);

static int      uvideo_get_control_group(void *,
                                         struct video_control_group *);
static int      uvideo_set_control_group(void *,
                                         const struct video_control_group *);

static usbd_status      uvideo_init_control(
        struct uvideo_softc *,
        const usb_interface_descriptor_t *,
        usbd_desc_iter_t *);
static usbd_status      uvideo_init_collection(
        struct uvideo_softc *,
        const usb_interface_descriptor_t *,
        usbd_desc_iter_t *);

/* Functions for unit & terminal descriptors */
static struct uvideo_unit *     uvideo_unit_alloc(const uvideo_descriptor_t *);
static usbd_status              uvideo_unit_init(struct uvideo_unit *,
                                                 const uvideo_descriptor_t *);
static void                     uvideo_unit_free(struct uvideo_unit *);
static void                     uvideo_unit_alloc_controls(struct uvideo_unit *,
                                                           uint8_t,
                                                           const uint8_t *);
static void                     uvideo_unit_free_controls(struct uvideo_unit *);
static void                     uvideo_unit_alloc_sources(struct uvideo_unit *,
                                                          uint8_t,
                                                          const uint8_t *);
static void                     uvideo_unit_free_sources(struct uvideo_unit *);




/*
 * Functions for uvideo_stream, primary unit associated with a video
 * driver or device file.
 */
static struct uvideo_stream *   uvideo_find_stream(struct uvideo_softc *,
                                                   uint8_t);
#if 0
static struct uvideo_format *   uvideo_stream_find_format(
        struct uvideo_stream *,
        uint8_t, uint8_t);
#endif
static struct uvideo_format *   uvideo_stream_guess_format(
        struct uvideo_stream *,
        enum video_pixel_format, uint32_t, uint32_t);
static struct uvideo_stream *   uvideo_stream_alloc(void);
static usbd_status              uvideo_stream_init(
        struct uvideo_stream *,
        struct uvideo_softc *,
        const usb_interface_descriptor_t *);
static usbd_status              uvideo_stream_init_desc(
        struct uvideo_stream *,
        const usb_interface_descriptor_t *,
        usbd_desc_iter_t *);
static usbd_status              uvideo_stream_init_frame_based_format(
        struct uvideo_stream *,
        const uvideo_descriptor_t *,
        usbd_desc_iter_t *);
static void                     uvideo_stream_free(struct uvideo_stream *);

static int              uvideo_stream_start_xfer(struct uvideo_stream *);
static int              uvideo_stream_stop_xfer(struct uvideo_stream *);
static usbd_status      uvideo_stream_recv_process(struct uvideo_stream *,
                                                   uint8_t *, uint32_t);
static usbd_status      uvideo_stream_recv_isoc_start(struct uvideo_stream *);
static usbd_status      uvideo_stream_recv_isoc_start1(struct uvideo_isoc *);
static void             uvideo_stream_recv_isoc_complete(struct usbd_xfer *,
                                                         void *,
                                                         usbd_status);
static void             uvideo_stream_recv_bulk_transfer(void *);

/* format probe and commit */
#define uvideo_stream_probe(vs, act, data)                              \
        (uvideo_stream_probe_and_commit((vs), (act),                    \
                                        UVIDEO_VS_PROBE_CONTROL, (data)))
#define uvideo_stream_commit(vs, act, data)                             \
        (uvideo_stream_probe_and_commit((vs), (act),                    \
                                        UVIDEO_VS_COMMIT_CONTROL, (data)))
static usbd_status      uvideo_stream_probe_and_commit(struct uvideo_stream *,
                                                       uint8_t, uint8_t,
                                                       void *);
static void             uvideo_init_probe_data(uvideo_probe_and_commit_data_t *);


static int      usb_guid_cmp(const usb_guid_t *, const guid_t *);


CFATTACH_DECL2_NEW(uvideo, sizeof(struct uvideo_softc),
    uvideo_match, uvideo_attach, uvideo_detach, uvideo_activate, NULL,
    uvideo_childdet);




static const struct video_hw_if uvideo_hw_if = {
        .open = uvideo_open,
        .close = uvideo_close,
        .get_devname = uvideo_get_devname,
        .get_businfo = uvideo_get_businfo,
        .enum_format = uvideo_enum_format,
        .get_format = uvideo_get_format,
        .set_format = uvideo_set_format,
        .try_format = uvideo_try_format,
        .get_framerate = uvideo_get_framerate,
        .set_framerate = uvideo_set_framerate,
        .start_transfer = uvideo_start_transfer,
        .stop_transfer = uvideo_stop_transfer,
        .control_iter_init = NULL,
        .control_iter_next = NULL,
        .get_control_desc_group = NULL,
        .get_control_group = uvideo_get_control_group,
        .set_control_group = uvideo_set_control_group,
};

#ifdef UVIDEO_DEBUG
/*
 * Some functions to print out descriptors.  Mostly useless other than
 * debugging/exploration purposes.
 */
static void usb_guid_print(const usb_guid_t *);
static void print_descriptor(const usb_descriptor_t *);
static void print_interface_descriptor(const usb_interface_descriptor_t *);
static void print_endpoint_descriptor(const usb_endpoint_descriptor_t *);

static void print_vc_descriptor(const usb_descriptor_t *);
static void print_vs_descriptor(const usb_descriptor_t *);

static void print_vc_header_descriptor(
        const uvideo_vc_header_descriptor_t *);
static void print_input_terminal_descriptor(
        const uvideo_input_terminal_descriptor_t *);
static void print_output_terminal_descriptor(
        const uvideo_output_terminal_descriptor_t *);
static void print_camera_terminal_descriptor(
        const uvideo_camera_terminal_descriptor_t *);
static void print_selector_unit_descriptor(
        const uvideo_selector_unit_descriptor_t *);
static void print_processing_unit_descriptor(
        const uvideo_processing_unit_descriptor_t *);
static void print_extension_unit_descriptor(
        const uvideo_extension_unit_descriptor_t *);
static void print_interrupt_endpoint_descriptor(
        const uvideo_vc_interrupt_endpoint_descriptor_t *);

static void print_vs_input_header_descriptor(
        const uvideo_vs_input_header_descriptor_t *);
static void print_vs_output_header_descriptor(
        const uvideo_vs_output_header_descriptor_t *);

static void print_vs_format_uncompressed_descriptor(
        const uvideo_vs_format_uncompressed_descriptor_t *);
static void print_vs_frame_uncompressed_descriptor(
        const uvideo_vs_frame_uncompressed_descriptor_t *);
static void print_vs_format_mjpeg_descriptor(
        const uvideo_vs_format_mjpeg_descriptor_t *);
static void print_vs_frame_mjpeg_descriptor(
        const uvideo_vs_frame_mjpeg_descriptor_t *);
static void print_vs_format_dv_descriptor(
        const uvideo_vs_format_dv_descriptor_t *);
#endif /* !UVIDEO_DEBUG */

#define GET(type, descp, field) (((const type *)(descp))->field)
#define GETP(type, descp, field) (&(((const type *)(descp))->field))

/*
 * Given a format descriptor and frame descriptor, copy values common
 * to all formats into a struct uvideo_format.
 */
#define UVIDEO_FORMAT_INIT_FRAME_BASED(format_type, format_desc,        \
                                       frame_type, frame_desc,          \
                                       format)                          \
        do {                                                            \
                UVIDEO_FORMAT_SET_FORMAT_INDEX(                         \
                        format,                                         \
                        GET(format_type, format_desc, bFormatIndex));   \
                UVIDEO_FORMAT_SET_FRAME_INDEX(                          \
                        format,                                         \
                        GET(frame_type, frame_desc, bFrameIndex));      \
                format->format.width =                                  \
                    UGETW(GET(frame_type, frame_desc, wWidth));         \
                format->format.height =                                 \
                    UGETW(GET(frame_type, frame_desc, wHeight));        \
                format->format.aspect_x =                               \
                    GET(format_type, format_desc, bAspectRatioX);       \
                format->format.aspect_y =                               \
                    GET(format_type, format_desc, bAspectRatioY);       \
        } while (0)


static int
uvideo_match(device_t parent, cfdata_t match, void *aux)
{
        struct usbif_attach_arg *uiaa = aux;

        /*
         * TODO: May need to change in the future to work with
         * Interface Association Descriptor.
         */

        /* Trigger on the Video Control Interface which must be present */
        if (uiaa->uiaa_class == UICLASS_VIDEO &&
            uiaa->uiaa_subclass == UISUBCLASS_VIDEOCONTROL)
                return UMATCH_IFACECLASS_IFACESUBCLASS;

        return UMATCH_NONE;
}

static void
uvideo_attach(device_t parent, device_t self, void *aux)
{
        struct uvideo_softc *sc = device_private(self);
        struct usbif_attach_arg *uiaa = aux;
        usbd_desc_iter_t iter;
        const usb_interface_descriptor_t *ifdesc;
        struct uvideo_stream *vs;
        usbd_status err;

        sc->sc_dev = self;

        sc->sc_devname = usbd_devinfo_alloc(uiaa->uiaa_device, 0);

        aprint_naive("\n");
        aprint_normal(": %s\n", sc->sc_devname);

        sc->sc_udev = uiaa->uiaa_device;
        sc->sc_iface = uiaa->uiaa_iface;
        sc->sc_ifaceno = uiaa->uiaa_ifaceno;
        sc->sc_dying = 0;
        SLIST_INIT(&sc->sc_stream_list);
        snprintf(sc->sc_businfo, sizeof(sc->sc_businfo), "usb:%08x",
            sc->sc_udev->ud_cookie.cookie);

#ifdef UVIDEO_DEBUG
        /*
         * Debugging dump of descriptors. TODO: move this to userspace
         * via a custom IOCTL or something.
         */
        const usb_descriptor_t *desc;
        usb_desc_iter_init(sc->sc_udev, &iter);
        while ((desc = usb_desc_iter_next(&iter)) != NULL) {
                /* print out all descriptors */
                printf("uvideo_attach: ");
                print_descriptor(desc);
        }
#endif /* !UVIDEO_DEBUG */

        /* iterate through interface descriptors and initialize softc */
        usb_desc_iter_init(sc->sc_udev, &iter);
        while ((ifdesc = usb_desc_iter_next_interface(&iter)) != NULL) {
                KASSERT(ifdesc->bLength >= USB_INTERFACE_DESCRIPTOR_SIZE);
                if (ifdesc->bInterfaceClass != UICLASS_VIDEO) {
                        DPRINTFN(50, ("uvideo_attach: "
                                      "ignoring non-uvc interface: "
                                      "len=%d type=0x%02x "
                                      "class=0x%02x subclass=0x%02x\n",
                                      ifdesc->bLength,
                                      ifdesc->bDescriptorType,
                                      ifdesc->bInterfaceClass,
                                      ifdesc->bInterfaceSubClass));
                        continue;
                }

                switch (ifdesc->bInterfaceSubClass) {
                case UISUBCLASS_VIDEOCONTROL:
                        err = uvideo_init_control(sc, ifdesc, &iter);
                        if (err != USBD_NORMAL_COMPLETION) {
                                DPRINTF(("uvideo_attach: error with interface "
                                         "%d, VideoControl, "
                                         "descriptor len=%d type=0x%02x: "
                                         "%s (%d)\n",
                                         ifdesc->bInterfaceNumber,
                                         ifdesc->bLength,
                                         ifdesc->bDescriptorType,
                                         usbd_errstr(err), err));
                        }
                        break;
                case UISUBCLASS_VIDEOSTREAMING:
                        vs = uvideo_find_stream(sc, ifdesc->bInterfaceNumber);
                        if (vs == NULL) {
                                vs = uvideo_stream_alloc();
                                err = uvideo_stream_init(vs, sc, ifdesc);
                                if (err != USBD_NORMAL_COMPLETION) {
                                        DPRINTF(("uvideo_attach: "
                                                 "error initializing stream: "
                                                 "%s (%d)\n",
                                                 usbd_errstr(err), err));
                                        goto bad;
                                }
                        }
                        err = uvideo_stream_init_desc(vs, ifdesc, &iter);
                        if (err != USBD_NORMAL_COMPLETION) {
                                DPRINTF(("uvideo_attach: "
                                         "error initializing stream descriptor: "
                                         "%s (%d)\n",
                                         usbd_errstr(err), err));
                                goto bad;
                        }
                        break;
                case UISUBCLASS_VIDEOCOLLECTION:
                        err = uvideo_init_collection(sc, ifdesc, &iter);
                        if (err != USBD_NORMAL_COMPLETION) {
                                DPRINTF(("uvideo_attach: error with interface "
                                       "%d, VideoCollection, "
                                       "descriptor len=%d type=0x%02x: "
                                       "%s (%d)\n",
                                       ifdesc->bInterfaceNumber,
                                       ifdesc->bLength,
                                       ifdesc->bDescriptorType,
                                       usbd_errstr(err), err));
                                goto bad;
                        }
                        break;
                default:
                        DPRINTF(("uvideo_attach: unknown UICLASS_VIDEO "
                                 "subclass=0x%02x\n",
                                 ifdesc->bInterfaceSubClass));
                        break;
                }

        }


        usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev);

        if (!pmf_device_register(self, NULL, NULL))
                aprint_error_dev(self, "couldn't establish power handler\n");

        SLIST_FOREACH(vs, &sc->sc_stream_list, entries) {
                /*
                 * If the descriptor is invalid, there may be no
                 * default format.
                 *
                 * XXX Maybe this should just be removed from the list
                 * at some other point, but finding the right other
                 * point is not trivial.
                 */
                if (vs->vs_default_format == NULL)
                        continue;
                /* XXX initialization of vs_videodev is racy */
                vs->vs_videodev = video_attach_mi(&uvideo_hw_if, sc->sc_dev,
                    vs);
        }

        return;

bad:
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_attach: error: %s (%d)\n",
                         usbd_errstr(err), err));
        }
        return;
}


static int
uvideo_activate(device_t self, enum devact act)
{
        struct uvideo_softc *sc = device_private(self);

        switch (act) {
        case DVACT_DEACTIVATE:
                DPRINTF(("uvideo_activate: deactivating\n"));
                sc->sc_dying = 1;
                return 0;
        default:
                return EOPNOTSUPP;
        }
}


/* Detach child (video interface) */
static void
uvideo_childdet(device_t self, device_t child)
{
        struct uvideo_softc *sc = device_private(self);
        struct uvideo_stream *vs;

        SLIST_FOREACH(vs, &sc->sc_stream_list, entries) {
                if (child == vs->vs_videodev) {
                        vs->vs_videodev = NULL;
                        break;
                }
        }
        KASSERTMSG(vs != NULL, "unknown child of %s detached: %s @ %p",
            device_xname(self), device_xname(child), child);
}


static int
uvideo_detach(device_t self, int flags)
{
        struct uvideo_softc *sc = device_private(self);
        struct uvideo_stream *vs;
        int error;

        error = config_detach_children(self, flags);
        if (error)
                return error;

        sc->sc_dying = 1;

        pmf_device_deregister(self);

        /*
         * TODO: close the device if it is currently opened?  Or will
         * close be called automatically?
         */

        while (!SLIST_EMPTY(&sc->sc_stream_list)) {
                vs = SLIST_FIRST(&sc->sc_stream_list);
                SLIST_REMOVE_HEAD(&sc->sc_stream_list, entries);
                uvideo_stream_stop_xfer(vs);
                uvideo_stream_free(vs);
        }

#if 0
        /*
         * Wait for outstanding request to complete.  TODO: what is
         * appropriate here?
         */
        usbd_delay_ms(sc->sc_udev, 1000);
#endif

        DPRINTFN(15, ("uvideo: detaching from %s\n",
                device_xname(sc->sc_dev)));

        usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev);

        usbd_devinfo_free(sc->sc_devname);

        return 0;
}

/*
 * Search the stream list for a stream matching the interface number.
 * This is an O(n) search, but most devices should have only one or at
 * most two streams.
 */
static struct uvideo_stream *
uvideo_find_stream(struct uvideo_softc *sc, uint8_t ifaceno)
{
        struct uvideo_stream *vs;

        SLIST_FOREACH(vs, &sc->sc_stream_list, entries) {
                if (vs->vs_ifaceno == ifaceno)
                        return vs;
        }

        return NULL;
}

/*
 * Search the format list for the given format and frame index.  This
 * might be improved through indexing, but the format and frame count
 * is unknown ahead of time (only after iterating through the
 * usb device descriptors).
 */
#if 0
static struct uvideo_format *
uvideo_stream_find_format(struct uvideo_stream *vs,
                          uint8_t format_index, uint8_t frame_index)
{
        struct uvideo_format *format;

        SIMPLEQ_FOREACH(format, &vs->vs_formats, entries) {
                if (UVIDEO_FORMAT_GET_FORMAT_INDEX(format) == format_index &&
                    UVIDEO_FORMAT_GET_FRAME_INDEX(format) == frame_index)
                        return format;
        }
        return NULL;
}
#endif

static struct uvideo_format *
uvideo_stream_guess_format(struct uvideo_stream *vs,
                           enum video_pixel_format pixel_format,
                           uint32_t width, uint32_t height)
{
        struct uvideo_format *format, *gformat = NULL;

        SIMPLEQ_FOREACH(format, &vs->vs_formats, entries) {
                if (format->format.pixel_format != pixel_format)
                        continue;
                if (format->format.width <= width &&
                    format->format.height <= height) {
                        if (gformat == NULL ||
                            (gformat->format.width < format->format.width &&
                             gformat->format.height < format->format.height))
                                gformat = format;
                }
        }

        return gformat;
}

static struct uvideo_stream *
uvideo_stream_alloc(void)
{
        return kmem_zalloc(sizeof(*uvideo_stream_alloc()), KM_SLEEP);
}


static usbd_status
uvideo_init_control(struct uvideo_softc *sc,
                    const usb_interface_descriptor_t *ifdesc,
                    usbd_desc_iter_t *iter)
{
        const usb_descriptor_t *desc;
        const uvideo_descriptor_t *uvdesc;
        usbd_desc_iter_t orig;
        uint8_t i, j, nunits;

        /* save original iterator state */
        memcpy(&orig, iter, sizeof(orig));

        /* count number of units and terminals */
        nunits = 0;
        while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
                if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
                    desc->bLength < sizeof(*uvdesc))
                        continue;
                uvdesc = (const uvideo_descriptor_t *)desc;

                if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
                    uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
                        continue;
                KASSERT(nunits < 255);
                ++nunits;
        }

        if (nunits == 0) {
                DPRINTF(("uvideo_init_control: no units\n"));
                return USBD_NORMAL_COMPLETION;
        }

        i = 0;

        /* allocate space for units */
        sc->sc_nunits = nunits;
        sc->sc_unit = kmem_alloc(sizeof(*sc->sc_unit) * nunits, KM_SLEEP);

        /* restore original iterator state */
        memcpy(iter, &orig, sizeof(orig));

        /* iterate again, initializing the units */
        while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
                if (desc->bDescriptorType != UDESC_CS_INTERFACE ||
                    desc->bLength < sizeof(*uvdesc))
                        continue;
                uvdesc = (const uvideo_descriptor_t *)desc;

                if (uvdesc->bDescriptorSubtype < UDESC_INPUT_TERMINAL ||
                    uvdesc->bDescriptorSubtype > UDESC_EXTENSION_UNIT)
                        continue;

                sc->sc_unit[i] = uvideo_unit_alloc(uvdesc);
                /* TODO: free other units before returning? */
                if (sc->sc_unit[i] == NULL)
                        goto enomem;
                KASSERT(i < 255);
                ++i;
        }

        return USBD_NORMAL_COMPLETION;

enomem:
        if (sc->sc_unit != NULL) {
                for (j = 0; j < i; ++j) {
                        uvideo_unit_free(sc->sc_unit[j]);
                        sc->sc_unit[j] = NULL;
                }
                kmem_free(sc->sc_unit, sizeof(*sc->sc_unit) * nunits);
                sc->sc_unit = NULL;
        }
        sc->sc_nunits = 0;

        return USBD_NOMEM;
}

static usbd_status
uvideo_init_collection(struct uvideo_softc *sc,
                       const usb_interface_descriptor_t *ifdesc,
                       usbd_desc_iter_t *iter)
{
        DPRINTF(("uvideo: ignoring Video Collection\n"));
        return USBD_NORMAL_COMPLETION;
}

/*
 * Allocates space for and initializes a uvideo unit based on the
 * given descriptor.  Returns NULL with bad descriptor or ENOMEM.
 */
static struct uvideo_unit *
uvideo_unit_alloc(const uvideo_descriptor_t *desc)
{
        struct uvideo_unit *vu;
        usbd_status err;

        KASSERT(desc->bDescriptorType == UDESC_CS_INTERFACE);

        vu = kmem_zalloc(sizeof(*vu), KM_SLEEP);
        err = uvideo_unit_init(vu, desc);
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_unit_alloc: error initializing unit: "
                         "%s (%d)\n", usbd_errstr(err), err));
                kmem_free(vu, sizeof(*vu));
                return NULL;
        }

        return vu;
}

static usbd_status
uvideo_unit_init(struct uvideo_unit *vu, const uvideo_descriptor_t *desc)
{
        struct uvideo_camera_terminal *ct;
        struct uvideo_processing_unit *pu;

        const uvideo_input_terminal_descriptor_t *input;
        const uvideo_camera_terminal_descriptor_t *camera;
        const uvideo_selector_unit_descriptor_t *selector;
        const uvideo_processing_unit_descriptor_t *processing;
        const uvideo_extension_unit_descriptor_t *extension;

        switch (desc->bDescriptorSubtype) {
        case UDESC_INPUT_TERMINAL:
                if (desc->bLength < sizeof(*input))
                        return USBD_INVAL;
                input = (const uvideo_input_terminal_descriptor_t *)desc;
                switch (UGETW(input->wTerminalType)) {
                case UVIDEO_ITT_CAMERA:
                        if (desc->bLength < sizeof(*camera))
                                return USBD_INVAL;
                        camera =
                            (const uvideo_camera_terminal_descriptor_t *)desc;

                        ct = &vu->u.vu_camera;
                        ct->ct_objective_focal_min =
                            UGETW(camera->wObjectiveFocalLengthMin);
                        ct->ct_objective_focal_max =
                            UGETW(camera->wObjectiveFocalLengthMax);
                        ct->ct_ocular_focal_length =
                            UGETW(camera->wOcularFocalLength);

                        uvideo_unit_alloc_controls(vu, camera->bControlSize,
                                                   camera->bmControls);
                        break;
                default:
                        DPRINTF(("uvideo_unit_init: "
                                 "unknown input terminal type 0x%04x\n",
                                 UGETW(input->wTerminalType)));
                        return USBD_INVAL;
                }
                break;
        case UDESC_OUTPUT_TERMINAL:
                break;
        case UDESC_SELECTOR_UNIT:
                if (desc->bLength < sizeof(*selector))
                        return USBD_INVAL;
                selector = (const uvideo_selector_unit_descriptor_t *)desc;

                uvideo_unit_alloc_sources(vu, selector->bNrInPins,
                                          selector->baSourceID);
                break;
        case UDESC_PROCESSING_UNIT:
                if (desc->bLength < sizeof(*processing))
                        return USBD_INVAL;
                processing = (const uvideo_processing_unit_descriptor_t *)desc;
                pu = &vu->u.vu_processing;

                pu->pu_video_standards = PU_GET_VIDEO_STANDARDS(processing);
                pu->pu_max_multiplier = UGETW(processing->wMaxMultiplier);

                uvideo_unit_alloc_sources(vu, 1, &processing->bSourceID);
                uvideo_unit_alloc_controls(vu, processing->bControlSize,
                                           processing->bmControls);
                break;
        case UDESC_EXTENSION_UNIT:
                if (desc->bLength < sizeof(*extension))
                        return USBD_INVAL;
                extension = (const uvideo_extension_unit_descriptor_t *)desc;
                /* TODO: copy guid */

                uvideo_unit_alloc_sources(vu, extension->bNrInPins,
                                          extension->baSourceID);
                uvideo_unit_alloc_controls(vu, XU_GET_CONTROL_SIZE(extension),
                                           XU_GET_CONTROLS(extension));
                break;
        default:
                DPRINTF(("uvideo_unit_alloc: unknown descriptor "
                         "type=0x%02x subtype=0x%02x\n",
                         desc->bDescriptorType, desc->bDescriptorSubtype));
                return USBD_INVAL;
        }

        return USBD_NORMAL_COMPLETION;
}

static void
uvideo_unit_free(struct uvideo_unit *vu)
{
        uvideo_unit_free_sources(vu);
        uvideo_unit_free_controls(vu);
        kmem_free(vu, sizeof(*vu));
}

static void
uvideo_unit_alloc_sources(struct uvideo_unit *vu,
                          uint8_t nsrcs, const uint8_t *src_ids)
{

        vu->vu_nsrcs = nsrcs;
        if (nsrcs == 0) {
                /* do nothing */
        } else if (nsrcs == 1) {
                vu->s.vu_src_id = src_ids[0];
        } else {
                vu->s.vu_src_id_ary =
                    kmem_alloc(sizeof(*vu->s.vu_src_id_ary) * nsrcs, KM_SLEEP);
                memcpy(vu->s.vu_src_id_ary, src_ids, nsrcs);
        }
}

static void
uvideo_unit_free_sources(struct uvideo_unit *vu)
{

        if (vu->vu_nsrcs <= 1)
                return;

        kmem_free(vu->s.vu_src_id_ary,
            sizeof(*vu->s.vu_src_id_ary) * vu->vu_nsrcs);
        vu->s.vu_src_id_ary = NULL;
        vu->vu_nsrcs = 0;
}

static void
uvideo_unit_alloc_controls(struct uvideo_unit *vu, uint8_t size,
                           const uint8_t *controls)
{

        vu->vu_control_size = size;
        if (size == 0)
                return;

        vu->vu_controls = kmem_alloc(sizeof(*vu->vu_controls) * size, KM_SLEEP);
        memcpy(vu->vu_controls, controls, size);
}

static void
uvideo_unit_free_controls(struct uvideo_unit *vu)
{

        if (vu->vu_control_size == 0)
                return;

        kmem_free(vu->vu_controls,
            sizeof(*vu->vu_controls) * vu->vu_control_size);
        vu->vu_controls = NULL;
        vu->vu_control_size = 0;
}


/*
 * Initialize a stream from a Video Streaming interface
 * descriptor. Adds the stream to the stream_list in uvideo_softc.
 * This should be called once for new streams, and
 * uvideo_stream_init_desc() should then be called for this and each
 * additional interface with the same interface number.
 */
static usbd_status
uvideo_stream_init(struct uvideo_stream *vs,
                   struct uvideo_softc *sc,
                   const usb_interface_descriptor_t *ifdesc)
{
        uWord len;
        usbd_status err;

        DPRINTF(("%s: %s ifaceno=%d vs=%p\n", __func__,
                device_xname(sc->sc_dev),
                ifdesc->bInterfaceNumber,
                vs));

        SLIST_INSERT_HEAD(&sc->sc_stream_list, vs, entries);
        vs->vs_parent = sc;
        vs->vs_ifaceno = ifdesc->bInterfaceNumber;
        vs->vs_subtype = 0;
        SIMPLEQ_INIT(&vs->vs_formats);
        SIMPLEQ_INIT(&vs->vs_pixel_formats);
        vs->vs_default_format = NULL;
        vs->vs_current_format.priv = -1;
        vs->vs_xfer_type = 0;
        vs->vs_state = UVIDEO_STATE_CLOSED;

        err = usbd_device2interface_handle(sc->sc_udev, vs->vs_ifaceno,
            &vs->vs_iface);
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_stream_init: "
                         "error getting vs interface: "
                         "%s (%d)\n",
                         usbd_errstr(err), err));
                return err;
        }

        /*
         * For Xbox Live Vision camera, linux-uvc folk say we need to
         * set an alternate interface and wait ~3 seconds prior to
         * doing the format probe/commit.  We set to alternate
         * interface 0, which is the default, zero bandwidth
         * interface.  This should not have adverse affects on other
         * cameras.  Errors are ignored.
         */
        err = usbd_set_interface(vs->vs_iface, 0);
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_stream_init: error setting alt interface: "
                         "%s (%d)\n",
                         usbd_errstr(err), err));
        }

        /*
         * Initialize probe and commit data size.  This value is
         * dependent on the version of the spec the hardware
         * implements.
         */
        err = uvideo_stream_probe(vs, UR_GET_LEN, &len);
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_stream_init: "
                         "error getting probe data len: "
                         "%s (%d)\n",
                         usbd_errstr(err), err));
                vs->vs_probelen = 26; /* conservative v1.0 length */
        } else if (UGETW(len) <= sizeof(uvideo_probe_and_commit_data_t)) {
                DPRINTFN(15,("uvideo_stream_init: probelen=%d\n", UGETW(len)));
                vs->vs_probelen = UGETW(len);
        } else {
                DPRINTFN(15,("uvideo_stream_init: device returned invalid probe"
                                " len %d, using default\n", UGETW(len)));
                vs->vs_probelen = 26;
        }

        return USBD_NORMAL_COMPLETION;
}

/*
 * Further stream initialization based on a Video Streaming interface
 * descriptor and following descriptors belonging to that interface.
 * Iterates through all descriptors belonging to this particular
 * interface descriptor, modifying the iterator.  This may be called
 * multiple times because there may be several alternate interfaces
 * associated with the same interface number.
 */
static usbd_status
uvideo_stream_init_desc(struct uvideo_stream *vs,
                        const usb_interface_descriptor_t *ifdesc,
                        usbd_desc_iter_t *iter)
{
        const usb_descriptor_t *desc;
        const uvideo_descriptor_t *uvdesc;
        struct uvideo_bulk_xfer *bx;
        struct uvideo_isoc_xfer *ix;
        struct uvideo_alternate *alt;
        uint8_t xfer_type, xfer_dir;
        uint8_t bmAttributes, bEndpointAddress;
        int i;

        DPRINTF(("%s: bInterfaceNumber=%d bAlternateSetting=%d\n", __func__,
                ifdesc->bInterfaceNumber, ifdesc->bAlternateSetting));

        /*
         * Iterate until the next interface descriptor.  All
         * descriptors until then belong to this streaming
         * interface.
         */
        while ((desc = usb_desc_iter_next_non_interface(iter)) != NULL) {
                switch (desc->bDescriptorType) {
                case UDESC_ENDPOINT:
                        if (desc->bLength < sizeof(usb_endpoint_descriptor_t))
                                goto baddesc;
                        bmAttributes = GET(usb_endpoint_descriptor_t,
                                           desc, bmAttributes);
                        bEndpointAddress = GET(usb_endpoint_descriptor_t,
                                               desc, bEndpointAddress);
                        xfer_type = UE_GET_XFERTYPE(bmAttributes);
                        xfer_dir = UE_GET_DIR(bEndpointAddress);
                        if (xfer_type == UE_BULK && xfer_dir == UE_DIR_IN) {
                                bx = &vs->vs_xfer.bulk;
                                if (vs->vs_xfer_type == 0) {
                                        DPRINTFN(15, ("uvideo_attach: "
                                                      "BULK stream *\n"));
                                        vs->vs_xfer_type = UE_BULK;
                                        bx->bx_endpt = bEndpointAddress;
                                        DPRINTF(("uvideo_attach: BULK "
                                                 "endpoint %x\n",
                                                 bx->bx_endpt));
                                        bx->bx_running = false;
                                        cv_init(&bx->bx_cv,
                                            device_xname(vs->vs_parent->sc_dev)
                                            );
                                        mutex_init(&bx->bx_lock,
                                          MUTEX_DEFAULT, IPL_NONE);
                                }
                        } else if (xfer_type == UE_ISOCHRONOUS) {
                                ix = &vs->vs_xfer.isoc;
                                for (i = 0; i < UVIDEO_NXFERS; i++) {
                                        ix->ix_i[i].i_ix = ix;
                                        ix->ix_i[i].i_vs = vs;
                                }
                                if (vs->vs_xfer_type == 0) {
                                        DPRINTFN(15, ("uvideo_attach: "
                                                      "ISOC stream *\n"));
                                        SLIST_INIT(&ix->ix_altlist);
                                        vs->vs_xfer_type = UE_ISOCHRONOUS;
                                        ix->ix_endpt =
                                            GET(usb_endpoint_descriptor_t,
                                                desc, bEndpointAddress);
                                }

                                alt = kmem_alloc(sizeof(*alt), KM_SLEEP);
                                alt->altno = ifdesc->bAlternateSetting;
                                alt->interval =
                                    GET(usb_endpoint_descriptor_t,
                                        desc, bInterval);

                                alt->max_packet_size =
                                UE_GET_SIZE(UGETW(GET(usb_endpoint_descriptor_t,
                                        desc, wMaxPacketSize)));
                                alt->max_packet_size *=
                                        (UE_GET_TRANS(UGETW(GET(
                                                usb_endpoint_descriptor_t, desc,
                                                wMaxPacketSize)))) + 1;

                                SLIST_INSERT_HEAD(&ix->ix_altlist,
                                                  alt, entries);
                        }
                        break;
                case UDESC_CS_INTERFACE:
                        if (desc->bLength < sizeof(*uvdesc))
                                goto baddesc;
                        uvdesc = (const uvideo_descriptor_t *)desc;
                        if (ifdesc->bAlternateSetting != 0) {
                                DPRINTF(("uvideo_stream_init_alternate: "
                                         "unexpected class-specific descriptor "
                                         "len=%d type=0x%02x subtype=0x%02x\n",
                                         uvdesc->bLength,
                                         uvdesc->bDescriptorType,
                                         uvdesc->bDescriptorSubtype));
                                break;
                        }

                        switch (uvdesc->bDescriptorSubtype) {
                        case UDESC_VS_INPUT_HEADER:
                                vs->vs_subtype = UDESC_VS_INPUT_HEADER;
                                break;
                        case UDESC_VS_OUTPUT_HEADER:
                                /* TODO: handle output stream */
                                DPRINTF(("uvideo: VS output not implemented\n"));
                                vs->vs_subtype = UDESC_VS_OUTPUT_HEADER;
                                return USBD_INVAL;
                        case UDESC_VS_FORMAT_UNCOMPRESSED:
                        case UDESC_VS_FORMAT_FRAME_BASED:
                        case UDESC_VS_FORMAT_MJPEG:
                                uvideo_stream_init_frame_based_format(vs,
                                                                      uvdesc,
                                                                      iter);
                                break;
                        case UDESC_VS_FORMAT_MPEG2TS:
                        case UDESC_VS_FORMAT_DV:
                        case UDESC_VS_FORMAT_STREAM_BASED:
                        default:
                                DPRINTF(("uvideo: unimplemented VS CS "
                                         "descriptor len=%d type=0x%02x "
                                         "subtype=0x%02x\n",
                                         uvdesc->bLength,
                                         uvdesc->bDescriptorType,
                                         uvdesc->bDescriptorSubtype));
                                break;
                        }
                        break;
                default:
                baddesc:
                        DPRINTF(("uvideo_stream_init_desc: "
                                 "bad descriptor "
                                 "len=%d type=0x%02x\n",
                                 desc->bLength,
                                 desc->bDescriptorType));
                        break;
                }
        }

        DPRINTF(("%s: bInterfaceNumber=%d bAlternateSetting=%d done\n",
                __func__,
                ifdesc->bInterfaceNumber, ifdesc->bAlternateSetting));

        return USBD_NORMAL_COMPLETION;
}

/* Finialize and free memory associated with this stream. */
static void
uvideo_stream_free(struct uvideo_stream *vs)
{
        struct uvideo_alternate *alt;
        struct uvideo_pixel_format *pixel_format;
        struct uvideo_format *format;

        /* free linked list of alternate interfaces */
        if (vs->vs_xfer_type == UE_ISOCHRONOUS) {
                while (!SLIST_EMPTY(&vs->vs_xfer.isoc.ix_altlist)) {
                        alt = SLIST_FIRST(&vs->vs_xfer.isoc.ix_altlist);
                        SLIST_REMOVE_HEAD(&vs->vs_xfer.isoc.ix_altlist,
                                          entries);
                        kmem_free(alt, sizeof(*alt));
                }
        }

        /* free linked-list of formats and pixel formats */
        while ((format = SIMPLEQ_FIRST(&vs->vs_formats)) != NULL) {
                SIMPLEQ_REMOVE_HEAD(&vs->vs_formats, entries);
                kmem_free(format, sizeof(*format));
        }
        while ((pixel_format = SIMPLEQ_FIRST(&vs->vs_pixel_formats)) != NULL) {
                SIMPLEQ_REMOVE_HEAD(&vs->vs_pixel_formats, entries);
                kmem_free(pixel_format, sizeof(*pixel_format));
        }

        kmem_free(vs, sizeof(*vs));
}

#define framedesc_size(T, d) ( \
        offsetof(T, uFrameInterval) + \
        ((T *)(d))->bFrameIntervalType \
            ? ((T *)(d))->bFrameIntervalType \
                * sizeof(((T *)(d))->uFrameInterval.discrete) \
            : sizeof(((T *)(d))->uFrameInterval.continuous) \
)

static usbd_status
uvideo_stream_init_frame_based_format(struct uvideo_stream *vs,
                                      const uvideo_descriptor_t *format_desc,
                                      usbd_desc_iter_t *iter)
{
        struct uvideo_pixel_format *pformat, *pfiter;
        enum video_pixel_format pixel_format;
        struct uvideo_format *format;
        const usb_descriptor_t *desc;
        const uvideo_descriptor_t *uvdesc;
        uint8_t subtype, subtypelen, default_index, index;
        uint32_t frame_interval;
        const usb_guid_t *guid;

        DPRINTF(("%s: ifaceno=%d subtype=%d probelen=%d\n", __func__,
                vs->vs_ifaceno, vs->vs_subtype, vs->vs_probelen));

        pixel_format = VIDEO_FORMAT_UNDEFINED;

        switch (format_desc->bDescriptorSubtype) {
        case UDESC_VS_FORMAT_UNCOMPRESSED:
                DPRINTF(("%s: uncompressed\n", __func__));
                if (format_desc->bLength <
                    sizeof(uvideo_vs_format_uncompressed_descriptor_t)) {
                        DPRINTF(("uvideo: truncated uncompressed format: %d\n",
                                format_desc->bLength));
                        return USBD_INVAL;
                }
                subtype = UDESC_VS_FRAME_UNCOMPRESSED;
                default_index = GET(uvideo_vs_format_uncompressed_descriptor_t,
                                    format_desc,
                                    bDefaultFrameIndex);
                guid = GETP(uvideo_vs_format_uncompressed_descriptor_t,
                            format_desc,
                            guidFormat);
                if (usb_guid_cmp(guid, &uvideo_guid_format_yuy2) == 0)
                        pixel_format = VIDEO_FORMAT_YUY2;
                else if (usb_guid_cmp(guid, &uvideo_guid_format_nv12) == 0)
                        pixel_format = VIDEO_FORMAT_NV12;
                else if (usb_guid_cmp(guid, &uvideo_guid_format_uyvy) == 0)
                        pixel_format = VIDEO_FORMAT_UYVY;
                else {
#ifdef UVIDEO_DEBUG
                        DPRINTF(("%s: unknown format: ", __func__));
                        usb_guid_print(guid);
                        DPRINTF(("\n"));
#endif
                }
                break;
        case UDESC_VS_FORMAT_FRAME_BASED:
                DPRINTF(("%s: frame-based\n", __func__));
                if (format_desc->bLength <
                    sizeof(uvideo_format_frame_based_descriptor_t)) {
                        DPRINTF(("uvideo: truncated frame-based format: %d\n",
                                format_desc->bLength));
                        return USBD_INVAL;
                }
                subtype = UDESC_VS_FRAME_FRAME_BASED;
                default_index = GET(uvideo_format_frame_based_descriptor_t,
                                    format_desc,
                                    bDefaultFrameIndex);
                break;
        case UDESC_VS_FORMAT_MJPEG:
                DPRINTF(("%s: mjpeg\n", __func__));
                if (format_desc->bLength <
                    sizeof(uvideo_vs_format_mjpeg_descriptor_t)) {
                        DPRINTF(("uvideo: truncated mjpeg format: %d\n",
                                format_desc->bLength));
                        return USBD_INVAL;
                }
                subtype = UDESC_VS_FRAME_MJPEG;
                default_index = GET(uvideo_vs_format_mjpeg_descriptor_t,
                                    format_desc,
                                    bDefaultFrameIndex);
                pixel_format = VIDEO_FORMAT_MJPEG;
                break;
        default:
                DPRINTF(("uvideo: unknown frame based format %d\n",
                         format_desc->bDescriptorSubtype));
                return USBD_INVAL;
        }

        pformat = NULL;
        SIMPLEQ_FOREACH(pfiter, &vs->vs_pixel_formats, entries) {
                if (pfiter->pixel_format == pixel_format) {
                        pformat = pfiter;
                        break;
                }
        }
        if (pixel_format != VIDEO_FORMAT_UNDEFINED && pformat == NULL) {
                pformat = kmem_zalloc(sizeof(*pformat), KM_SLEEP);
                pformat->pixel_format = pixel_format;
                DPRINTF(("uvideo: Adding pixel format %d\n",
                    pixel_format));
                SIMPLEQ_INSERT_TAIL(&vs->vs_pixel_formats,
                    pformat, entries);
        }

        /*
         * Iterate through frame descriptors directly following the
         * format descriptor, and add a format to the format list for
         * each frame descriptor.
         */
        while ((desc = usb_desc_iter_peek(iter)) != NULL) {
                if (desc->bDescriptorType != UDESC_CS_INTERFACE)
                        break;
                if (desc->bLength < sizeof(*uvdesc)) {
                        DPRINTF(("uvideo: truncated CS descriptor, length %d\n",
                                desc->bLength));
                        break;
                }
                uvdesc = (const uvideo_descriptor_t *)desc;
                if (uvdesc->bDescriptorSubtype != subtype)
                        break;

                switch (format_desc->bDescriptorSubtype) {
                case UDESC_VS_FORMAT_UNCOMPRESSED:
                        subtypelen = framedesc_size(
                            const uvideo_vs_frame_uncompressed_descriptor_t,
                            uvdesc);
                        break;
                case UDESC_VS_FORMAT_MJPEG:
                        subtypelen = framedesc_size(
                            const uvideo_vs_frame_mjpeg_descriptor_t,
                            uvdesc);
                        break;
                case UDESC_VS_FORMAT_FRAME_BASED:
                        subtypelen = framedesc_size(
                            const uvideo_frame_frame_based_descriptor_t,
                            uvdesc);
                        break;
                default:
                        /* will bail out below */
                        subtypelen = uvdesc->bLength;
                        break;
                }

                if (uvdesc->bLength < subtypelen) {
                        DPRINTF(("uvideo:"
                                " truncated CS subtype-0x%x descriptor,"
                                " length %d < %d\n",
                                uvdesc->bDescriptorSubtype,
                                uvdesc->bLength, subtypelen));
                        break;
                }

                /* We peeked; now consume.  */
                (void)usb_desc_iter_next(iter);

                format = kmem_zalloc(sizeof(*format), KM_SLEEP);
                format->format.pixel_format = pixel_format;

                switch (format_desc->bDescriptorSubtype) {
                case UDESC_VS_FORMAT_UNCOMPRESSED:
#ifdef UVIDEO_DEBUG
                        if (pixel_format == VIDEO_FORMAT_UNDEFINED &&
                            uvideodebug) {
                                guid = GETP(
                                    uvideo_vs_format_uncompressed_descriptor_t,
                                    format_desc,
                                    guidFormat);

                                DPRINTF(("uvideo: format undefined "));
                                usb_guid_print(guid);
                                DPRINTF(("\n"));
                        }
#endif

                        UVIDEO_FORMAT_INIT_FRAME_BASED(
                                uvideo_vs_format_uncompressed_descriptor_t,
                                format_desc,
                                uvideo_vs_frame_uncompressed_descriptor_t,
                                uvdesc,
                                format);
                        format->format.sample_size =
                            UGETDW(
                              GET(uvideo_vs_frame_uncompressed_descriptor_t,
                              uvdesc, dwMaxVideoFrameBufferSize));
                        format->format.stride =
                            format->format.sample_size / format->format.height;
                        index = GET(uvideo_vs_frame_uncompressed_descriptor_t,
                                    uvdesc,
                                    bFrameIndex);
                        frame_interval =
                            UGETDW(
                                GET(uvideo_vs_frame_uncompressed_descriptor_t,
                                uvdesc,
                                dwDefaultFrameInterval));
                        break;
                case UDESC_VS_FORMAT_MJPEG:
                        UVIDEO_FORMAT_INIT_FRAME_BASED(
                                uvideo_vs_format_mjpeg_descriptor_t,
                                format_desc,
                                uvideo_vs_frame_mjpeg_descriptor_t,
                                uvdesc,
                                format);
                        format->format.sample_size =
                            UGETDW(
                                GET(uvideo_vs_frame_mjpeg_descriptor_t,
                                uvdesc, dwMaxVideoFrameBufferSize));
                        format->format.stride =
                            format->format.sample_size / format->format.height;
                        index = GET(uvideo_vs_frame_mjpeg_descriptor_t,
                                    uvdesc,
                                    bFrameIndex);
                        frame_interval =
                            UGETDW(
                                GET(uvideo_vs_frame_mjpeg_descriptor_t,
                                uvdesc,
                                dwDefaultFrameInterval));
                        break;
                case UDESC_VS_FORMAT_FRAME_BASED:
                        format->format.pixel_format = VIDEO_FORMAT_UNDEFINED;
                        UVIDEO_FORMAT_INIT_FRAME_BASED(
                                uvideo_format_frame_based_descriptor_t,
                                format_desc,
                                uvideo_frame_frame_based_descriptor_t,
                                uvdesc,
                                format);
                        index = GET(uvideo_frame_frame_based_descriptor_t,
                                    uvdesc,
                                    bFrameIndex);
                        format->format.stride =
                            UGETDW(
                                GET(uvideo_frame_frame_based_descriptor_t,
                                uvdesc, dwBytesPerLine));
                        format->format.sample_size =
                            format->format.stride * format->format.height;
                        frame_interval =
                            UGETDW(
                                GET(uvideo_frame_frame_based_descriptor_t,
                                uvdesc, dwDefaultFrameInterval));
                        break;
                default:
                        /* shouldn't ever get here */
                        DPRINTF(("uvideo: unknown frame based format %d\n",
                                 format_desc->bDescriptorSubtype));
                        kmem_free(format, sizeof(*format));
                        return USBD_INVAL;
                }

                DPRINTF(("uvideo: found format (index %d) type %d "
                    "size %ux%u size %u stride %u interval %u\n",
                    index, format->format.pixel_format, format->format.width,
                    format->format.height, format->format.sample_size,
                    format->format.stride, frame_interval));

                SIMPLEQ_INSERT_TAIL(&vs->vs_formats, format, entries);

                if (vs->vs_default_format == NULL && index == default_index
#ifdef UVIDEO_DISABLE_MJPEG
                    && subtype != UDESC_VS_FRAME_MJPEG
#endif
                    ) {
                        DPRINTF((" ^ picking this one\n"));
                        vs->vs_default_format = &format->format;
                        vs->vs_frame_interval = frame_interval;
                }

        }

        return USBD_NORMAL_COMPLETION;
}

static int
uvideo_stream_start_xfer(struct uvideo_stream *vs)
{
        struct uvideo_softc *sc = vs->vs_parent;
        struct uvideo_bulk_xfer *bx;
        struct uvideo_isoc_xfer *ix;
        uint32_t vframe_len;    /* rough bytes per video frame */
        uint32_t uframe_len;    /* bytes per usb frame (TODO: or microframe?) */
        uint32_t nframes;       /* number of usb frames (TODO: or microframs?) */
        int i, ret;
        int error;

        struct uvideo_alternate *alt, *alt_maybe;
        usbd_status err;

        switch (vs->vs_xfer_type) {
        case UE_BULK:
                ret = 0;
                bx = &vs->vs_xfer.bulk;

                err = usbd_open_pipe(vs->vs_iface, bx->bx_endpt, 0,
                    &bx->bx_pipe);
                if (err != USBD_NORMAL_COMPLETION) {
                        DPRINTF(("uvideo: error opening pipe: %s (%d)\n",
                                 usbd_errstr(err), err));
                        return EIO;
                }
                DPRINTF(("uvideo: pipe %p\n", bx->bx_pipe));

                error = usbd_create_xfer(bx->bx_pipe, vs->vs_max_payload_size,
                    0, 0, &bx->bx_xfer);
                if (error) {
                        DPRINTF(("uvideo: couldn't allocate xfer\n"));
                        return error;
                }
                DPRINTF(("uvideo: xfer %p\n", bx->bx_xfer));

                bx->bx_buflen = vs->vs_max_payload_size;
                bx->bx_buffer = usbd_get_buffer(bx->bx_xfer);

                mutex_enter(&bx->bx_lock);
                if (bx->bx_running == false) {
                        bx->bx_running = true;
                        ret = kthread_create(PRI_UVIDEO, 0, NULL,
                            uvideo_stream_recv_bulk_transfer, vs,
                            NULL, "%s", device_xname(sc->sc_dev));
                        if (ret) {
                                DPRINTF(("uvideo: couldn't create kthread:"
                                         " %d\n", err));
                                bx->bx_running = false;
                                mutex_exit(&bx->bx_lock);
                                return err;
                        }
                } else
                        aprint_error_dev(sc->sc_dev,
                            "transfer already in progress\n");
                mutex_exit(&bx->bx_lock);

                DPRINTF(("uvideo: thread created\n"));

                return 0;
        case UE_ISOCHRONOUS:
                ix = &vs->vs_xfer.isoc;

                /*
                 * Choose an alternate interface most suitable for
                 * this format.  Choose the smallest size that can
                 * contain max_payload_size.
                 *
                 * It is assumed that the list is sorted in descending
                 * order from largest to smallest packet size.
                 *
                 * TODO: what should the strategy be for choosing an
                 * alt interface?
                 */
                alt = NULL;
                SLIST_FOREACH(alt_maybe, &ix->ix_altlist, entries) {
                        /*
                         * TODO: define "packet" and "payload".  I think
                         * several packets can make up one payload which would
                         * call into question this method of selecting an
                         * alternate interface...
                         */

                        if (alt_maybe->max_packet_size > vs->vs_max_payload_size)
                                continue;

                        if (alt == NULL ||
                            alt_maybe->max_packet_size >= alt->max_packet_size)
                                alt = alt_maybe;
                }

                if (alt == NULL) {
                        DPRINTF(("uvideo_stream_start_xfer: "
                                 "no suitable alternate interface found\n"));
                        return EINVAL;
                }

                DPRINTFN(15,("uvideo_stream_start_xfer: "
                             "choosing alternate interface "
                             "%d wMaxPacketSize=%d bInterval=%d\n",
                             alt->altno, alt->max_packet_size, alt->interval));

                err = usbd_set_interface(vs->vs_iface, alt->altno);
                if (err != USBD_NORMAL_COMPLETION) {
                        DPRINTF(("uvideo_stream_start_xfer: "
                                 "error setting alt interface: %s (%d)\n",
                                 usbd_errstr(err), err));
                        return EIO;
                }

                /* TODO: "packet" not same as frame */
                vframe_len = vs->vs_current_format.sample_size;
                uframe_len = alt->max_packet_size;
                nframes = (vframe_len + uframe_len - 1) / uframe_len;
                nframes = (nframes + 7) & ~7; /*round up for ehci inefficiency*/
                nframes = uimin(UVIDEO_NFRAMES_MAX, nframes);
                DPRINTF(("uvideo_stream_start_xfer: nframes=%d\n", nframes));

                ix->ix_nframes = nframes;
                ix->ix_uframe_len = uframe_len;
                for (i = 0; i < UVIDEO_NXFERS; i++) {
                        struct uvideo_isoc *isoc = &ix->ix_i[i];
                        isoc->i_frlengths =
                            kmem_alloc(sizeof(isoc->i_frlengths[0]) * nframes,
                                KM_SLEEP);
                }

                err = usbd_open_pipe(vs->vs_iface, ix->ix_endpt,
                                     USBD_EXCLUSIVE_USE, &ix->ix_pipe);
                if (err != USBD_NORMAL_COMPLETION) {
                        DPRINTF(("uvideo: error opening pipe: %s (%d)\n",
                                 usbd_errstr(err), err));
                        return EIO;
                }

                for (i = 0; i < UVIDEO_NXFERS; i++) {
                        struct uvideo_isoc *isoc = &ix->ix_i[i];
                        error = usbd_create_xfer(ix->ix_pipe,
                            nframes * uframe_len, 0, ix->ix_nframes,
                            &isoc->i_xfer);
                        if (error) {
                                DPRINTF(("uvideo: "
                                    "couldn't allocate xfer (%d)\n", error));
                                return error;
                        }

                        isoc->i_buf = usbd_get_buffer(isoc->i_xfer);
                }

                uvideo_stream_recv_isoc_start(vs);

                return 0;
        default:
                /* should never get here */
                DPRINTF(("uvideo_stream_start_xfer: unknown xfer type %#x\n",
                         vs->vs_xfer_type));
                return EINVAL;
        }
}

static int
uvideo_stream_stop_xfer(struct uvideo_stream *vs)
{
        struct uvideo_bulk_xfer *bx;
        struct uvideo_isoc_xfer *ix;
        usbd_status err;
        int i;

        switch (vs->vs_xfer_type) {
        case UE_BULK:
                bx = &vs->vs_xfer.bulk;

                DPRINTF(("uvideo_stream_stop_xfer: UE_BULK: "
                         "waiting for thread to complete\n"));
                mutex_enter(&bx->bx_lock);
                if (bx->bx_running == true) {
                        bx->bx_running = false;
                        cv_wait_sig(&bx->bx_cv, &bx->bx_lock);
                }
                mutex_exit(&bx->bx_lock);

                DPRINTF(("uvideo_stream_stop_xfer: UE_BULK: cleaning up\n"));

                if (bx->bx_pipe) {
                        usbd_abort_pipe(bx->bx_pipe);
                }

                if (bx->bx_xfer) {
                        usbd_destroy_xfer(bx->bx_xfer);
                        bx->bx_xfer = NULL;
                }

                if (bx->bx_pipe) {
                        usbd_close_pipe(bx->bx_pipe);
                        bx->bx_pipe = NULL;
                }

                DPRINTF(("uvideo_stream_stop_xfer: UE_BULK: done\n"));

                return 0;
        case UE_ISOCHRONOUS:
                ix = &vs->vs_xfer.isoc;
                if (ix->ix_pipe != NULL) {
                        usbd_abort_pipe(ix->ix_pipe);
                }

                for (i = 0; i < UVIDEO_NXFERS; i++) {
                        struct uvideo_isoc *isoc = &ix->ix_i[i];
                        if (isoc->i_xfer != NULL) {
                                usbd_destroy_xfer(isoc->i_xfer);
                                isoc->i_xfer = NULL;
                        }
                }

                if (ix->ix_pipe != NULL) {
                        usbd_close_pipe(ix->ix_pipe);
                        ix->ix_pipe = NULL;
                }

                for (i = 0; i < UVIDEO_NXFERS; i++) {
                        struct uvideo_isoc *isoc = &ix->ix_i[i];
                        if (isoc->i_frlengths != NULL) {
                                kmem_free(isoc->i_frlengths,
                                  sizeof(isoc->i_frlengths[0]) *
                                  ix->ix_nframes);
                                isoc->i_frlengths = NULL;
                        }
                }

                /* Give it some time to settle */
                usbd_delay_ms(vs->vs_parent->sc_udev, 20);

                /* Set to zero bandwidth alternate interface zero */
                err = usbd_set_interface(vs->vs_iface, 0);
                if (err != USBD_NORMAL_COMPLETION) {
                        DPRINTF(("uvideo_stream_stop_transfer: "
                                 "error setting zero bandwidth interface: "
                                 "%s (%d)\n",
                                 usbd_errstr(err), err));
                        return EIO;
                }

                return 0;
        default:
                /* should never get here */
                DPRINTF(("uvideo_stream_stop_xfer: unknown xfer type %#x\n",
                         vs->vs_xfer_type));
                return EINVAL;
        }
}

static usbd_status
uvideo_stream_recv_isoc_start(struct uvideo_stream *vs)
{
        int i;

        for (i = 0; i < UVIDEO_NXFERS; i++)
                uvideo_stream_recv_isoc_start1(&vs->vs_xfer.isoc.ix_i[i]);

        return USBD_NORMAL_COMPLETION;
}

/* Initiate a usb transfer. */
static usbd_status
uvideo_stream_recv_isoc_start1(struct uvideo_isoc *isoc)
{
        struct uvideo_isoc_xfer *ix;
        usbd_status err;
        int i;

        ix = isoc->i_ix;

        for (i = 0; i < ix->ix_nframes; ++i)
                isoc->i_frlengths[i] = ix->ix_uframe_len;

        usbd_setup_isoc_xfer(isoc->i_xfer,
                             isoc,
                             isoc->i_frlengths,
                             ix->ix_nframes,
                             USBD_SHORT_XFER_OK,
                             uvideo_stream_recv_isoc_complete);

        err = usbd_transfer(isoc->i_xfer);
        if (err != USBD_IN_PROGRESS) {
                DPRINTF(("uvideo_stream_recv_start: "
                         "usbd_transfer status=%s (%d)\n",
                         usbd_errstr(err), err));
        }
        return err;
}

static usbd_status
uvideo_stream_recv_process(struct uvideo_stream *vs, uint8_t *buf, uint32_t len)
{
        uvideo_payload_header_t *hdr;
        struct video_payload payload;

        if (len < sizeof(uvideo_payload_header_t)) {
                DPRINTF(("uvideo_stream_recv_process: len %d < payload hdr\n",
                         len));
                return USBD_SHORT_XFER;
        }

        hdr = (uvideo_payload_header_t *)buf;

        if (hdr->bHeaderLength > UVIDEO_PAYLOAD_HEADER_SIZE ||
            hdr->bHeaderLength < sizeof(uvideo_payload_header_t))
                return USBD_INVAL;
        if (hdr->bHeaderLength == len && !(hdr->bmHeaderInfo & UV_END_OF_FRAME))
                return USBD_INVAL;
        if (hdr->bmHeaderInfo & UV_ERROR)
                return USBD_IOERROR;

        payload.data = buf + hdr->bHeaderLength;
        payload.size = len - hdr->bHeaderLength;
        payload.frameno = hdr->bmHeaderInfo & UV_FRAME_ID;
        payload.end_of_frame = hdr->bmHeaderInfo & UV_END_OF_FRAME;

        video_submit_payload(vs->vs_videodev, &payload);

        return USBD_NORMAL_COMPLETION;
}

/* Callback on completion of usb isoc transfer */
static void
uvideo_stream_recv_isoc_complete(struct usbd_xfer *xfer,
                                 void *priv,
                                 usbd_status status)
{
        struct uvideo_stream *vs;
        struct uvideo_isoc_xfer *ix;
        struct uvideo_isoc *isoc;
        int i;
        uint32_t count;
        uint8_t *buf;

        isoc = priv;
        vs = isoc->i_vs;
        ix = isoc->i_ix;

        if (status != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_stream_recv_isoc_complete: status=%s (%d)\n",
                        usbd_errstr(status), status));

                if (status == USBD_STALLED)
                        usbd_clear_endpoint_stall_async(ix->ix_pipe);
                else
                        return;
        } else {
                usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);

                if (count == 0) {
                        /* DPRINTF(("uvideo: zero length transfer\n")); */
                        goto next;
                }


                for (i = 0, buf = isoc->i_buf;
                     i < ix->ix_nframes;
                     ++i, buf += ix->ix_uframe_len)
                {
                        status = uvideo_stream_recv_process(vs, buf,
                            isoc->i_frlengths[i]);
                        if (status == USBD_IOERROR)
                                break;
                }
        }

next:
        uvideo_stream_recv_isoc_start1(isoc);
}

static void
uvideo_stream_recv_bulk_transfer(void *addr)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_bulk_xfer *bx = &vs->vs_xfer.bulk;
        usbd_status err;
        uint32_t len;

        DPRINTF(("uvideo_stream_recv_bulk_transfer: "
                 "vs %p sc %p bx %p buffer %p\n", vs, vs->vs_parent, bx,
                 bx->bx_buffer));

        while (bx->bx_running) {
                len = bx->bx_buflen;
                err = usbd_bulk_transfer(bx->bx_xfer, bx->bx_pipe,
                    USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT,
                    bx->bx_buffer, &len);

                if (err == USBD_NORMAL_COMPLETION) {
                        uvideo_stream_recv_process(vs, bx->bx_buffer, len);
                } else {
                        DPRINTF(("uvideo_stream_recv_bulk_transfer: %s\n",
                                 usbd_errstr(err)));
                }
        }

        DPRINTF(("uvideo_stream_recv_bulk_transfer: notify complete\n"));

        mutex_enter(&bx->bx_lock);
        cv_broadcast(&bx->bx_cv);
        mutex_exit(&bx->bx_lock);

        DPRINTF(("uvideo_stream_recv_bulk_transfer: return\n"));

        kthread_exit(0);
}

/*
 * uvideo_open - probe and commit video format and start receiving
 * video data
 */
static int
uvideo_open(void *addr, int flags)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_softc *sc = vs->vs_parent;
        struct video_format fmt;

        DPRINTF(("uvideo_open: sc=%p\n", sc));
        if (sc->sc_dying)
                return EIO;

        /* XXX select default format */
        if (vs->vs_default_format == NULL)
                return EINVAL;
        fmt = *vs->vs_default_format;

        return uvideo_set_format(addr, &fmt);
}


static void
uvideo_close(void *addr)
{
        struct uvideo_stream *vs = addr;

        uvideo_stop_transfer(addr);

        if (vs->vs_state != UVIDEO_STATE_CLOSED) {
                vs->vs_state = UVIDEO_STATE_CLOSED;
        }
}

static const char *
uvideo_get_devname(void *addr)
{
        struct uvideo_stream *vs = addr;

        return vs->vs_parent->sc_devname;
}

static const char *
uvideo_get_businfo(void *addr)
{
        struct uvideo_stream *vs = addr;

        return vs->vs_parent->sc_businfo;
}

static int
uvideo_enum_format(void *addr, uint32_t index, struct video_format *format)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_softc *sc = vs->vs_parent;
        struct uvideo_format *video_format;
        int off;

        if (sc->sc_dying)
                return EIO;

        off = 0;
        SIMPLEQ_FOREACH(video_format, &vs->vs_formats, entries) {
                if (off++ != index)
                        continue;
                format->pixel_format = video_format->format.pixel_format;
                format->width = video_format->format.width;
                format->height = video_format->format.height;
                return 0;
        }

        return EINVAL;
}

/*
 * uvideo_get_format
 */
static int
uvideo_get_format(void *addr, struct video_format *format)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_softc *sc = vs->vs_parent;

        if (sc->sc_dying)
                return EIO;

        *format = vs->vs_current_format;

        return 0;
}

/*
 * uvideo_set_format - TODO: this is broken and does nothing
 */
static int
uvideo_set_format(void *addr, struct video_format *format)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_softc *sc = vs->vs_parent;
        struct uvideo_format *uvfmt;
        uvideo_probe_and_commit_data_t probe, maxprobe;
        usbd_status err;

        DPRINTF(("uvideo_set_format: sc=%p\n", sc));
        if (sc->sc_dying)
                return EIO;

        uvfmt = uvideo_stream_guess_format(vs, format->pixel_format,
                                           format->width, format->height);
        if (uvfmt == NULL) {
                DPRINTF(("uvideo: uvideo_stream_guess_format couldn't find "
                         "%dx%d format %d\n", format->width, format->height,
                         format->pixel_format));
                return EINVAL;
        }

        uvideo_init_probe_data(&probe);
        probe.bFormatIndex = UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt);
        probe.bFrameIndex = UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt);
        USETDW(probe.dwFrameInterval, vs->vs_frame_interval);   /* XXX */

        maxprobe = probe;
        err = uvideo_stream_probe(vs, UR_GET_MAX, &maxprobe);
        if (err) {
                DPRINTF(("uvideo: error probe/GET_MAX: %s (%d)\n",
                         usbd_errstr(err), err));
        } else {
                USETW(probe.wCompQuality, UGETW(maxprobe.wCompQuality));
        }

        err = uvideo_stream_probe(vs, UR_SET_CUR, &probe);
        if (err) {
                DPRINTF(("uvideo: error commit/SET_CUR: %s (%d)\n",
                         usbd_errstr(err), err));
                return EIO;
        }

        uvideo_init_probe_data(&probe);
        err = uvideo_stream_probe(vs, UR_GET_CUR, &probe);
        if (err) {
                DPRINTF(("uvideo: error commit/SET_CUR: %s (%d)\n",
                         usbd_errstr(err), err));
                return EIO;
        }

        if (probe.bFormatIndex != UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt)) {
                DPRINTF(("uvideo: probe/GET_CUR returned format index %d "
                         "(expected %d)\n", probe.bFormatIndex,
                         UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt)));
                probe.bFormatIndex = UVIDEO_FORMAT_GET_FORMAT_INDEX(uvfmt);
        }
        if (probe.bFrameIndex != UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt)) {
                DPRINTF(("uvideo: probe/GET_CUR returned frame index %d "
                         "(expected %d)\n", probe.bFrameIndex,
                         UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt)));
                probe.bFrameIndex = UVIDEO_FORMAT_GET_FRAME_INDEX(uvfmt);
        }
        USETDW(probe.dwFrameInterval, vs->vs_frame_interval);   /* XXX */

        /*
         * commit/SET_CUR. Fourth step is to set the alternate
         * interface.  Currently the fourth step is in
         * uvideo_start_transfer.  Maybe move it here?
         */
        err = uvideo_stream_commit(vs, UR_SET_CUR, &probe);
        if (err) {
                DPRINTF(("uvideo: error commit/SET_CUR: %s (%d)\n",
                         usbd_errstr(err), err));
                return EIO;
        }

        DPRINTFN(15, ("uvideo_set_format: committing to format: "
                      "bmHint=0x%04x bFormatIndex=%d bFrameIndex=%d "
                      "dwFrameInterval=%u wKeyFrameRate=%d wPFrameRate=%d "
                      "wCompQuality=%d wCompWindowSize=%d wDelay=%d "
                      "dwMaxVideoFrameSize=%u dwMaxPayloadTransferSize=%u",
                      UGETW(probe.bmHint),
                      probe.bFormatIndex,
                      probe.bFrameIndex,
                      UGETDW(probe.dwFrameInterval),
                      UGETW(probe.wKeyFrameRate),
                      UGETW(probe.wPFrameRate),
                      UGETW(probe.wCompQuality),
                      UGETW(probe.wCompWindowSize),
                      UGETW(probe.wDelay),
                      UGETDW(probe.dwMaxVideoFrameSize),
                      UGETDW(probe.dwMaxPayloadTransferSize)));
        if (vs->vs_probelen == 34) {
                DPRINTFN(15, (" dwClockFrequency=%u bmFramingInfo=0x%02x "
                              "bPreferedVersion=%d bMinVersion=%d "
                              "bMaxVersion=%d",
                              UGETDW(probe.dwClockFrequency),
                              probe.bmFramingInfo,
                              probe.bPreferedVersion,
                              probe.bMinVersion,
                              probe.bMaxVersion));
        }
        DPRINTFN(15, ("\n"));

        vs->vs_frame_interval = UGETDW(probe.dwFrameInterval);
        vs->vs_max_payload_size = UGETDW(probe.dwMaxPayloadTransferSize);

        *format = uvfmt->format;
        vs->vs_current_format = *format;
        DPRINTF(("uvideo_set_format: pixeltype is %d\n", format->pixel_format));

        return 0;
}

static int
uvideo_try_format(void *addr, struct video_format *format)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_format *uvfmt;

        uvfmt = uvideo_stream_guess_format(vs, format->pixel_format,
                                           format->width, format->height);
        if (uvfmt == NULL)
                return EINVAL;

        *format = uvfmt->format;
        return 0;
}

static int
uvideo_get_framerate(void *addr, struct video_fract *fract)
{
        struct uvideo_stream *vs = addr;

        switch (vs->vs_frame_interval) {
        case 41666:     /* 240 */
        case 83333:     /* 120 */
        case 166666:    /* 60 */
        case 200000:    /* 50 */
        case 333333:    /* 30 */
        case 400000:    /* 25 */
        case 500000:    /* 20 */
        case 666666:    /* 15 */
        case 1000000:   /* 10 */
                fract->numerator = 1;
                fract->denominator = 10000000 / vs->vs_frame_interval;
                break;
        case 166833:    /* 59.94 */
                fract->numerator = 60;
                fract->denominator = 1001;
                break;
        case 333667:    /* 29.97 */
                fract->numerator = 30;
                fract->denominator = 1001;
                break;
        default:
                fract->numerator = vs->vs_frame_interval;
                fract->denominator = 10000000;
                break;
        }

        return 0;
}

static int
uvideo_set_framerate(void *addr, struct video_fract *fract)
{
        /* XXX setting framerate is not supported yet, return actual rate */
        return uvideo_get_framerate(addr, fract);
}

static int
uvideo_start_transfer(void *addr)
{
        struct uvideo_stream *vs = addr;
        int s, err;

        s = splusb();
        err = uvideo_stream_start_xfer(vs);
        splx(s);

        return err;
}

static int
uvideo_stop_transfer(void *addr)
{
        struct uvideo_stream *vs = addr;
        int err, s;

        s = splusb();
        err = uvideo_stream_stop_xfer(vs);
        splx(s);

        return err;
}


static int
uvideo_get_control_group(void *addr, struct video_control_group *group)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_softc *sc = vs->vs_parent;
        usb_device_request_t req;
        usbd_status err;
        uint8_t control_id, ent_id, data[16];
        uint16_t len;
        int s;

        /* request setup */
        switch (group->group_id) {
        case VIDEO_CONTROL_PANTILT_RELATIVE:
                if (group->length != 4)
                        return EINVAL;

                return EINVAL;
        case VIDEO_CONTROL_SHARPNESS:
                if (group->length != 1)
                        return EINVAL;

                control_id = UVIDEO_PU_SHARPNESS_CONTROL;
                ent_id = 2; /* TODO: hardcoded logitech processing unit */
                len = 2;
                break;
        default:
                return EINVAL;
        }

        /* do request */
        req.bmRequestType = UVIDEO_REQUEST_TYPE_INTERFACE |
            UVIDEO_REQUEST_TYPE_CLASS_SPECIFIC |
            UVIDEO_REQUEST_TYPE_GET;
        req.bRequest = UR_GET_CUR;
        USETW(req.wValue, control_id << 8);
        USETW(req.wIndex, (ent_id << 8) | sc->sc_ifaceno);
        USETW(req.wLength, len);

        s = splusb();
        err = usbd_do_request(sc->sc_udev, &req, data);
        splx(s);
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_set_control: error %s (%d)\n",
                         usbd_errstr(err), err));
                return EIO;     /* TODO: more detail here? */
        }

        /* extract request data */
        switch (group->group_id) {
        case VIDEO_CONTROL_SHARPNESS:
                group->control[0].value = UGETW(data);
                break;
        default:
                return EINVAL;
        }

        return 0;
}


static int
uvideo_set_control_group(void *addr, const struct video_control_group *group)
{
        struct uvideo_stream *vs = addr;
        struct uvideo_softc *sc = vs->vs_parent;
        usb_device_request_t req;
        usbd_status err;
        uint8_t control_id, ent_id, data[16]; /* long enough for all controls */
        uint16_t len;
        int s;

        switch (group->group_id) {
        case VIDEO_CONTROL_PANTILT_RELATIVE:
                if (group->length != 4)
                        return EINVAL;

                if (group->control[0].value != 0 ||
                    group->control[0].value != 1 ||
                    group->control[0].value != 0xff)
                        return ERANGE;

                if (group->control[2].value != 0 ||
                    group->control[2].value != 1 ||
                    group->control[2].value != 0xff)
                        return ERANGE;

                control_id = UVIDEO_CT_PANTILT_RELATIVE_CONTROL;
                ent_id = 1;     /* TODO: hardcoded logitech camera terminal  */
                len = 4;
                data[0] = group->control[0].value;
                data[1] = group->control[1].value;
                data[2] = group->control[2].value;
                data[3] = group->control[3].value;
                break;
        case VIDEO_CONTROL_BRIGHTNESS:
                if (group->length != 1)
                        return EINVAL;
                control_id = UVIDEO_PU_BRIGHTNESS_CONTROL;
                ent_id = 2;
                len = 2;
                USETW(data, group->control[0].value);
                break;
        case VIDEO_CONTROL_GAIN:
                if (group->length != 1)
                        return EINVAL;
                control_id = UVIDEO_PU_GAIN_CONTROL;
                ent_id = 2;
                len = 2;
                USETW(data, group->control[0].value);
                break;
        case VIDEO_CONTROL_SHARPNESS:
                if (group->length != 1)
                        return EINVAL;
                control_id = UVIDEO_PU_SHARPNESS_CONTROL;
                ent_id = 2; /* TODO: hardcoded logitech processing unit */
                len = 2;
                USETW(data, group->control[0].value);
                break;
        default:
                return EINVAL;
        }

        req.bmRequestType = UVIDEO_REQUEST_TYPE_INTERFACE |
            UVIDEO_REQUEST_TYPE_CLASS_SPECIFIC |
            UVIDEO_REQUEST_TYPE_SET;
        req.bRequest = UR_SET_CUR;
        USETW(req.wValue, control_id << 8);
        USETW(req.wIndex, (ent_id << 8) | sc->sc_ifaceno);
        USETW(req.wLength, len);

        s = splusb();
        err = usbd_do_request(sc->sc_udev, &req, data);
        splx(s);
        if (err != USBD_NORMAL_COMPLETION) {
                DPRINTF(("uvideo_set_control: error %s (%d)\n",
                         usbd_errstr(err), err));
                return EIO;     /* TODO: more detail here? */
        }

        return 0;
}

static usbd_status
uvideo_stream_probe_and_commit(struct uvideo_stream *vs,
                               uint8_t action, uint8_t control,
                               void *data)
{
        usb_device_request_t req;

        switch (action) {
        case UR_SET_CUR:
                req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
                USETW(req.wLength, vs->vs_probelen);
                break;
        case UR_GET_CUR:
        case UR_GET_MIN:
        case UR_GET_MAX:
        case UR_GET_DEF:
                req.bmRequestType = UT_READ_CLASS_INTERFACE;
                USETW(req.wLength, vs->vs_probelen);
                break;
        case UR_GET_INFO:
                req.bmRequestType = UT_READ_CLASS_INTERFACE;
                USETW(req.wLength, sizeof(uByte));
                break;
        case UR_GET_LEN:
                req.bmRequestType = UT_READ_CLASS_INTERFACE;
                USETW(req.wLength, sizeof(uWord)); /* is this right? */
                break;
        default:
                DPRINTF(("uvideo_probe_and_commit: "
                         "unknown request action %d\n", action));
                return USBD_NOT_STARTED;
        }

        req.bRequest = action;
        USETW2(req.wValue, control, 0);
        USETW2(req.wIndex, 0, vs->vs_ifaceno);

        return (usbd_do_request_flags(vs->vs_parent->sc_udev, &req, data,
                                      0, 0,
                                      USBD_DEFAULT_TIMEOUT));
}

static void
uvideo_init_probe_data(uvideo_probe_and_commit_data_t *probe)
{
        /* all zeroes tells camera to choose what it wants */
        memset(probe, 0, sizeof(*probe));
}


#ifdef _MODULE

MODULE(MODULE_CLASS_DRIVER, uvideo, NULL);
static const struct cfiattrdata videobuscf_iattrdata = {
        "videobus", 0, {
                { NULL, NULL, 0 },
        }
};
static const struct cfiattrdata * const uvideo_attrs[] = {
        &videobuscf_iattrdata, NULL
};
CFDRIVER_DECL(uvideo, DV_DULL, uvideo_attrs);
extern struct cfattach uvideo_ca;
extern struct cfattach uvideo_ca;
static int uvideoloc[6] = { -1, -1, -1, -1, -1, -1 };
static struct cfparent uhubparent = {
        "usbifif", NULL, DVUNIT_ANY
};
static struct cfdata uvideo_cfdata[] = {
        {
                .cf_name = "uvideo",
                .cf_atname = "uvideo",
                .cf_unit = 0,
                .cf_fstate = FSTATE_STAR,
                .cf_loc = uvideoloc,
                .cf_flags = 0,
                .cf_pspec = &uhubparent,
        },
        { NULL, NULL, 0, 0, NULL, 0, NULL },
};

static int
uvideo_modcmd(modcmd_t cmd, void *arg)
{
        int err;


        switch (cmd) {
        case MODULE_CMD_INIT:
                DPRINTF(("uvideo: attempting to load\n"));

                err = config_cfdriver_attach(&uvideo_cd);
                if (err)
                        return err;
                err = config_cfattach_attach("uvideo", &uvideo_ca);
                if (err) {
                        config_cfdriver_detach(&uvideo_cd);
                        return err;
                }
                err = config_cfdata_attach(uvideo_cfdata, 1);
                if (err) {
                        config_cfattach_detach("uvideo", &uvideo_ca);
                        config_cfdriver_detach(&uvideo_cd);
                        return err;
                }
                DPRINTF(("uvideo: loaded module\n"));
                return 0;
        case MODULE_CMD_FINI:
                DPRINTF(("uvideo: attempting to unload module\n"));
                err = config_cfdata_detach(uvideo_cfdata);
                if (err)
                        return err;
                config_cfattach_detach("uvideo", &uvideo_ca);
                config_cfdriver_detach(&uvideo_cd);
                DPRINTF(("uvideo: module unload\n"));
                return 0;
        default:
                return ENOTTY;
        }
}

#endif  /* _MODULE */


#ifdef UVIDEO_DEBUG
/*
 * Some functions to print out descriptors.  Mostly useless other than
 * debugging/exploration purposes.
 */


static void
print_bitmap(const uByte *start, uByte nbytes)
{
        int byte, bit;

        /* most significant first */
        for (byte = nbytes-1; byte >= 0; --byte) {
                if (byte < nbytes-1) printf("-");
                for (bit = 7; bit >= 0; --bit)
                        printf("%01d", (start[byte] >> bit) &1);
        }
}

static void
print_descriptor(const usb_descriptor_t *desc)
{
        static int current_class = -1;
        static int current_subclass = -1;

        if (desc->bDescriptorType == UDESC_INTERFACE) {
                const usb_interface_descriptor_t *id;

                if (desc->bLength < sizeof(*id)) {
                        printf("[truncated interface]\n");
                        return;
                }
                id = (const usb_interface_descriptor_t *)desc;
                current_class = id->bInterfaceClass;
                current_subclass = id->bInterfaceSubClass;
                print_interface_descriptor(id);
                printf("\n");
                return;
        }

        printf("  ");           /* indent */

        if (current_class == UICLASS_VIDEO) {
                switch (current_subclass) {
                case UISUBCLASS_VIDEOCONTROL:
                        print_vc_descriptor(desc);
                        break;
                case UISUBCLASS_VIDEOSTREAMING:
                        print_vs_descriptor(desc);
                        break;
                case UISUBCLASS_VIDEOCOLLECTION:
                        printf("uvc collection: len=%d type=0x%02x",
                            desc->bLength, desc->bDescriptorType);
                        break;
                }
        } else {
                printf("non uvc descriptor len=%d type=0x%02x",
                    desc->bLength, desc->bDescriptorType);
        }

        printf("\n");
}

static void
print_vc_descriptor(const usb_descriptor_t *desc)
{
        const uvideo_descriptor_t *vcdesc;

        printf("VC ");

        switch (desc->bDescriptorType) {
        case UDESC_ENDPOINT:
                if (desc->bLength < sizeof(usb_endpoint_descriptor_t)) {
                        printf("[truncated endpoint]");
                        break;
                }
                print_endpoint_descriptor(
                        (const usb_endpoint_descriptor_t *)desc);
                break;
        case UDESC_CS_INTERFACE:
                if (desc->bLength < sizeof(*vcdesc)) {
                        printf("[truncated class-specific]");
                        break;
                }
                vcdesc = (const uvideo_descriptor_t *)desc;
                switch (vcdesc->bDescriptorSubtype) {
                case UDESC_VC_HEADER:
                        if (desc->bLength <
                            sizeof(uvideo_vc_header_descriptor_t)) {
                                printf("[truncated videocontrol header]");
                                break;
                        }
                        print_vc_header_descriptor(
                          (const uvideo_vc_header_descriptor_t *)
                                vcdesc);
                        break;
                case UDESC_INPUT_TERMINAL:
                        if (desc->bLength <
                            sizeof(uvideo_input_terminal_descriptor_t)) {
                                printf("[truncated input terminal]");
                                break;
                        }
                        switch (UGETW(
                           ((const uvideo_input_terminal_descriptor_t *)
                                    vcdesc)->wTerminalType)) {
                        case UVIDEO_ITT_CAMERA:
                                if (desc->bLength <
                                    sizeof(uvideo_camera_terminal_descriptor_t)) {
                                        printf("[truncated camera terminal]");
                                        break;
                                }
                                print_camera_terminal_descriptor(
                          (const uvideo_camera_terminal_descriptor_t *)vcdesc);
                                break;
                        default:
                                print_input_terminal_descriptor(
                          (const uvideo_input_terminal_descriptor_t *)vcdesc);
                                break;
                        }
                        break;
                case UDESC_OUTPUT_TERMINAL:
                        if (desc->bLength <
                            sizeof(uvideo_output_terminal_descriptor_t)) {
                                printf("[truncated output terminal]");
                                break;
                        }
                        print_output_terminal_descriptor(
                                (const uvideo_output_terminal_descriptor_t *)
                                vcdesc);
                        break;
                case UDESC_SELECTOR_UNIT:
                        if (desc->bLength <
                            sizeof(uvideo_selector_unit_descriptor_t)) {
                                printf("[truncated selector unit]");
                                break;
                        }
                        print_selector_unit_descriptor(
                                (const uvideo_selector_unit_descriptor_t *)
                                vcdesc);
                        break;
                case UDESC_PROCESSING_UNIT:
                        if (desc->bLength <
                            sizeof(uvideo_processing_unit_descriptor_t)) {
                                printf("[truncated processing unit]");
                                break;
                        }
                        print_processing_unit_descriptor(
                                (const uvideo_processing_unit_descriptor_t *)
                                vcdesc);
                        break;
                case UDESC_EXTENSION_UNIT:
                        if (desc->bLength <
                            sizeof(uvideo_extension_unit_descriptor_t)) {
                                printf("[truncated extension unit]");
                                break;
                        }
                        print_extension_unit_descriptor(
                                (const uvideo_extension_unit_descriptor_t *)
                                vcdesc);
                        break;
                default:
                        printf("class specific interface "
                            "len=%d type=0x%02x subtype=0x%02x",
                            vcdesc->bLength,
                            vcdesc->bDescriptorType,
                            vcdesc->bDescriptorSubtype);
                        break;
                }
                break;
        case UDESC_CS_ENDPOINT:
                if (desc->bLength < sizeof(*vcdesc)) {
                        printf("[truncated class-specific]");
                        break;
                }
                vcdesc = (const uvideo_descriptor_t *)desc;
                switch (vcdesc->bDescriptorSubtype) {
                case UDESC_VC_INTERRUPT_ENDPOINT:
                        if (desc->bLength <
                            sizeof(uvideo_vc_interrupt_endpoint_descriptor_t)) {
                                printf("[truncated "
                                    "videocontrol interrupt endpoint]");
                                break;
                        }
                        print_interrupt_endpoint_descriptor(
                            (const uvideo_vc_interrupt_endpoint_descriptor_t *)
                                vcdesc);
                        break;
                default:
                        printf("class specific endpoint "
                            "len=%d type=0x%02x subtype=0x%02x",
                            vcdesc->bLength,
                            vcdesc->bDescriptorType,
                            vcdesc->bDescriptorSubtype);
                        break;
                }
                break;
        default:
                printf("unknown: len=%d type=0x%02x",
                    desc->bLength, desc->bDescriptorType);
                break;
        }
}

static void
print_vs_descriptor(const usb_descriptor_t *desc)
{
        const uvideo_descriptor_t * vsdesc;
        printf("VS ");

        switch (desc->bDescriptorType) {
        case UDESC_ENDPOINT:
                if (desc->bLength < sizeof(usb_endpoint_descriptor_t)) {
                        printf("[truncated endpoint]");
                        break;
                }
                print_endpoint_descriptor(
                        (const usb_endpoint_descriptor_t *)desc);
                break;
        case UDESC_CS_INTERFACE:
                if (desc->bLength < sizeof(*vsdesc)) {
                        printf("[truncated class-specific]");
                        break;
                }
                vsdesc = (const uvideo_descriptor_t *)desc;
                switch (vsdesc->bDescriptorSubtype) {
                case UDESC_VS_INPUT_HEADER:
                        if (desc->bLength <
                            sizeof(uvideo_vs_input_header_descriptor_t)) {
                                printf("[truncated videostream input header]");
                                break;
                        }
                        print_vs_input_header_descriptor(
                         (const uvideo_vs_input_header_descriptor_t *)
                                vsdesc);
                        break;
                case UDESC_VS_OUTPUT_HEADER:
                        if (desc->bLength <
                            sizeof(uvideo_vs_output_header_descriptor_t)) {
                                printf("[truncated "
                                    "videostream output header]");
                                break;
                        }
                        print_vs_output_header_descriptor(
                        (const uvideo_vs_output_header_descriptor_t *)
                                vsdesc);
                        break;
                case UDESC_VS_FORMAT_UNCOMPRESSED:
                        if (desc->bLength <
                            sizeof(uvideo_vs_format_uncompressed_descriptor_t))
                        {
                                printf("[truncated "
                                    "videostream format uncompressed]");
                                break;
                        }
                        print_vs_format_uncompressed_descriptor(
                           (const uvideo_vs_format_uncompressed_descriptor_t *)
                                vsdesc);
                        break;
                case UDESC_VS_FRAME_UNCOMPRESSED:
                        if (desc->bLength <
                            sizeof(uvideo_vs_frame_uncompressed_descriptor_t))
                        {
                                printf("[truncated "
                                    "videostream frame uncompressed]");
                                break;
                        }
                        print_vs_frame_uncompressed_descriptor(
                            (const uvideo_vs_frame_uncompressed_descriptor_t *)
                                vsdesc);
                        break;
                case UDESC_VS_FORMAT_MJPEG:
                        if (desc->bLength <
                            sizeof(uvideo_vs_format_mjpeg_descriptor_t)) {
                                printf("[truncated videostream format mjpeg]");
                                break;
                        }
                        print_vs_format_mjpeg_descriptor(
                                (const uvideo_vs_format_mjpeg_descriptor_t *)
                                vsdesc);
                        break;
                case UDESC_VS_FRAME_MJPEG:
                        if (desc->bLength <
                            sizeof(uvideo_vs_frame_mjpeg_descriptor_t)) {
                                printf("[truncated videostream frame mjpeg]");
                                break;
                        }
                        print_vs_frame_mjpeg_descriptor(
                                (const uvideo_vs_frame_mjpeg_descriptor_t *)
                                vsdesc);
                        break;
                case UDESC_VS_FORMAT_DV:
                        if (desc->bLength <
                            sizeof(uvideo_vs_format_dv_descriptor_t)) {
                                printf("[truncated videostream format dv]");
                                break;
                        }
                        print_vs_format_dv_descriptor(
                                (const uvideo_vs_format_dv_descriptor_t *)
                                vsdesc);
                        break;
                default:
                        printf("unknown cs interface: len=%d type=0x%02x "
                            "subtype=0x%02x",
                            vsdesc->bLength, vsdesc->bDescriptorType,
                            vsdesc->bDescriptorSubtype);
                }
                break;
        default:
                printf("unknown: len=%d type=0x%02x",
                    desc->bLength, desc->bDescriptorType);
                break;
        }
}

static void
print_interface_descriptor(const usb_interface_descriptor_t *id)
{
        printf("Interface: Len=%d Type=0x%02x "
            "bInterfaceNumber=0x%02x "
            "bAlternateSetting=0x%02x bNumEndpoints=0x%02x "
            "bInterfaceClass=0x%02x bInterfaceSubClass=0x%02x "
            "bInterfaceProtocol=0x%02x iInterface=0x%02x",
            id->bLength,
            id->bDescriptorType,
            id->bInterfaceNumber,
            id->bAlternateSetting,
            id->bNumEndpoints,
            id->bInterfaceClass,
            id->bInterfaceSubClass,
            id->bInterfaceProtocol,
            id->iInterface);
}

static void
print_endpoint_descriptor(const usb_endpoint_descriptor_t *desc)
{
        printf("Endpoint: Len=%d Type=0x%02x "
            "bEndpointAddress=0x%02x ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bEndpointAddress);
        printf("bmAttributes=");
        print_bitmap(&desc->bmAttributes, 1);
        printf(" wMaxPacketSize=%d bInterval=%d",
            UGETW(desc->wMaxPacketSize),
            desc->bInterval);
}

static void
print_vc_header_descriptor(
        const uvideo_vc_header_descriptor_t *desc)
{
        printf("Interface Header: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bcdUVC=%d wTotalLength=%d "
            "dwClockFrequency=%u bInCollection=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            UGETW(desc->bcdUVC),
            UGETW(desc->wTotalLength),
            UGETDW(desc->dwClockFrequency),
            desc->bInCollection);
}

static void
print_input_terminal_descriptor(
        const uvideo_input_terminal_descriptor_t *desc)
{
        printf("Input Terminal: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bTerminalID=%d wTerminalType=%x bAssocTerminal=%d "
            "iTerminal=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bTerminalID,
            UGETW(desc->wTerminalType),
            desc->bAssocTerminal,
            desc->iTerminal);
}

static void
print_output_terminal_descriptor(
        const uvideo_output_terminal_descriptor_t *desc)
{
        printf("Output Terminal: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bTerminalID=%d wTerminalType=%x bAssocTerminal=%d "
            "bSourceID=%d iTerminal=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bTerminalID,
            UGETW(desc->wTerminalType),
            desc->bAssocTerminal,
            desc->bSourceID,
            desc->iTerminal);
}

static void
print_camera_terminal_descriptor(
        const uvideo_camera_terminal_descriptor_t *desc)
{
        printf("Camera Terminal: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bTerminalID=%d wTerminalType=%x bAssocTerminal=%d "
            "iTerminal=%d "
            "wObjectiveFocalLengthMin/Max=%d/%d "
            "wOcularFocalLength=%d "
            "bControlSize=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bTerminalID,
            UGETW(desc->wTerminalType),
            desc->bAssocTerminal,
            desc->iTerminal,
            UGETW(desc->wObjectiveFocalLengthMin),
            UGETW(desc->wObjectiveFocalLengthMax),
            UGETW(desc->wOcularFocalLength),
            desc->bControlSize);
        printf("bmControls=");
        print_bitmap(desc->bmControls, desc->bControlSize);
}

static void
print_selector_unit_descriptor(
        const uvideo_selector_unit_descriptor_t *desc)
{
        int i;
        const uByte *b;
        printf("Selector Unit: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bUnitID=%d bNrInPins=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bUnitID,
            desc->bNrInPins);
        printf("baSourceIDs=");
        b = &desc->baSourceID[0];
        for (i = 0; i < desc->bNrInPins; ++i)
                printf("%d ", *b++);
        printf("iSelector=%d", *b);
}

static void
print_processing_unit_descriptor(
        const uvideo_processing_unit_descriptor_t *desc)
{
        const uByte *b;

        printf("Processing Unit: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bUnitID=%d bSourceID=%d wMaxMultiplier=%d bControlSize=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bUnitID,
            desc->bSourceID,
            UGETW(desc->wMaxMultiplier),
            desc->bControlSize);
        printf("bmControls=");
        print_bitmap(desc->bmControls, desc->bControlSize);
        b = &desc->bControlSize + desc->bControlSize + 1;
        printf(" iProcessing=%d bmVideoStandards=", *b);
        b += 1;
        print_bitmap(b, 1);
}

static void
print_extension_unit_descriptor(
        const uvideo_extension_unit_descriptor_t *desc)
{
        const uByte * byte;
        uByte controlbytes;
        int i;

        printf("Extension Unit: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bUnitID=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bUnitID);

        printf("guidExtensionCode=");
        usb_guid_print(&desc->guidExtensionCode);
        printf(" ");

        printf("bNumControls=%d bNrInPins=%d ",
            desc->bNumControls,
            desc->bNrInPins);

        printf("baSourceIDs=");
        byte = &desc->baSourceID[0];
        for (i = 0; i < desc->bNrInPins; ++i)
                printf("%d ", *byte++);

        controlbytes = *byte++;
        printf("bControlSize=%d ", controlbytes);
        printf("bmControls=");
        print_bitmap(byte, controlbytes);

        byte += controlbytes;
        printf(" iExtension=%d", *byte);
}

static void
print_interrupt_endpoint_descriptor(
        const uvideo_vc_interrupt_endpoint_descriptor_t *desc)
{
        printf("Interrupt Endpoint: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "wMaxTransferSize=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            UGETW(desc->wMaxTransferSize));
}


static void
print_vs_output_header_descriptor(
        const uvideo_vs_output_header_descriptor_t *desc)
{
        printf("Interface Output Header: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bNumFormats=%d wTotalLength=%d bEndpointAddress=%d "
            "bTerminalLink=%d bControlSize=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bNumFormats,
            UGETW(desc->wTotalLength),
            desc->bEndpointAddress,
            desc->bTerminalLink,
            desc->bControlSize);
}

static void
print_vs_input_header_descriptor(
        const uvideo_vs_input_header_descriptor_t *desc)
{
        printf("Interface Input Header: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bNumFormats=%d wTotalLength=%d bEndpointAddress=%d "
            "bmInfo=%x bTerminalLink=%d bStillCaptureMethod=%d "
            "bTriggerSupport=%d bTriggerUsage=%d bControlSize=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bNumFormats,
            UGETW(desc->wTotalLength),
            desc->bEndpointAddress,
            desc->bmInfo,
            desc->bTerminalLink,
            desc->bStillCaptureMethod,
            desc->bTriggerSupport,
            desc->bTriggerUsage,
            desc->bControlSize);
        print_bitmap(desc->bmaControls, desc->bControlSize);
}

static void
print_vs_format_uncompressed_descriptor(
        const uvideo_vs_format_uncompressed_descriptor_t *desc)
{
        printf("Format Uncompressed: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bFormatIndex=%d bNumFrameDescriptors=%d ",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bFormatIndex,
            desc->bNumFrameDescriptors);
        usb_guid_print(&desc->guidFormat);
        printf(" bBitsPerPixel=%d bDefaultFrameIndex=%d "
            "bAspectRatioX=%d bAspectRatioY=%d "
            "bmInterlaceFlags=0x%02x bCopyProtect=%d",
            desc->bBitsPerPixel,
            desc->bDefaultFrameIndex,
            desc->bAspectRatioX,
            desc->bAspectRatioY,
            desc->bmInterlaceFlags,
            desc->bCopyProtect);
}

static void
print_vs_frame_uncompressed_descriptor(
        const uvideo_vs_frame_uncompressed_descriptor_t *desc)
{
        printf("Frame Uncompressed: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bFrameIndex=%d bmCapabilities=0x%02x "
            "wWidth=%d wHeight=%d dwMinBitRate=%u dwMaxBitRate=%u "
            "dwMaxVideoFrameBufferSize=%u dwDefaultFrameInterval=%u "
            "bFrameIntervalType=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bFrameIndex,
            desc->bmCapabilities,
            UGETW(desc->wWidth),
            UGETW(desc->wHeight),
            UGETDW(desc->dwMinBitRate),
            UGETDW(desc->dwMaxBitRate),
            UGETDW(desc->dwMaxVideoFrameBufferSize),
            UGETDW(desc->dwDefaultFrameInterval),
            desc->bFrameIntervalType);
}

static void
print_vs_format_mjpeg_descriptor(
        const uvideo_vs_format_mjpeg_descriptor_t *desc)
{
        printf("MJPEG format: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bFormatIndex=%d bNumFrameDescriptors=%d bmFlags=0x%02x "
            "bDefaultFrameIndex=%d bAspectRatioX=%d bAspectRatioY=%d "
            "bmInterlaceFlags=0x%02x bCopyProtect=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bFormatIndex,
            desc->bNumFrameDescriptors,
            desc->bmFlags,
            desc->bDefaultFrameIndex,
            desc->bAspectRatioX,
            desc->bAspectRatioY,
            desc->bmInterlaceFlags,
            desc->bCopyProtect);
}

static void
print_vs_frame_mjpeg_descriptor(
        const uvideo_vs_frame_mjpeg_descriptor_t *desc)
{
        printf("MJPEG frame: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bFrameIndex=%d bmCapabilities=0x%02x "
            "wWidth=%d wHeight=%d dwMinBitRate=%u dwMaxBitRate=%u "
            "dwMaxVideoFrameBufferSize=%u dwDefaultFrameInterval=%u "
            "bFrameIntervalType=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bFrameIndex,
            desc->bmCapabilities,
            UGETW(desc->wWidth),
            UGETW(desc->wHeight),
            UGETDW(desc->dwMinBitRate),
            UGETDW(desc->dwMaxBitRate),
            UGETDW(desc->dwMaxVideoFrameBufferSize),
            UGETDW(desc->dwDefaultFrameInterval),
            desc->bFrameIntervalType);
}

static void
print_vs_format_dv_descriptor(
        const uvideo_vs_format_dv_descriptor_t *desc)
{
        printf("MJPEG format: "
            "Len=%d Type=0x%02x Subtype=0x%02x "
            "bFormatIndex=%d dwMaxVideoFrameBufferSize=%u "
            "bFormatType/Rate=%d bFormatType/Format=%d",
            desc->bLength,
            desc->bDescriptorType,
            desc->bDescriptorSubtype,
            desc->bFormatIndex,
            UGETDW(desc->dwMaxVideoFrameBufferSize),
            UVIDEO_GET_DV_FREQ(desc->bFormatType),
            UVIDEO_GET_DV_FORMAT(desc->bFormatType));
}

#endif /* !UVIDEO_DEBUG */

#ifdef UVIDEO_DEBUG
static void
usb_guid_print(const usb_guid_t *guid)
{
        printf("%04X-%02X-%02X-",
               UGETDW(guid->data1),
               UGETW(guid->data2),
               UGETW(guid->data3));
        printf("%02X%02X-",
               guid->data4[0],
               guid->data4[1]);
        printf("%02X%02X%02X%02X%02X%02X",
               guid->data4[2],
               guid->data4[3],
               guid->data4[4],
               guid->data4[5],
               guid->data4[6],
               guid->data4[7]);
}
#endif /* !UVIDEO_DEBUG */

/*
 * Returns less than zero, zero, or greater than zero if uguid is less
 * than, equal to, or greater than guid.
 */
static int
usb_guid_cmp(const usb_guid_t *uguid, const guid_t *guid)
{
        if (guid->data1 > UGETDW(uguid->data1))
                return 1;
        else if (guid->data1 < UGETDW(uguid->data1))
                return -1;

        if (guid->data2 > UGETW(uguid->data2))
                return 1;
        else if (guid->data2 < UGETW(uguid->data2))
                return -1;

        if (guid->data3 > UGETW(uguid->data3))
                return 1;
        else if (guid->data3 < UGETW(uguid->data3))
                return -1;

        return memcmp(guid->data4, uguid->data4, 8);
}