root/sys/dev/usb/ugen.c
/*      $NetBSD: ugen.c,v 1.178 2025/04/26 07:08:48 skrll Exp $ */

/*
 * Copyright (c) 1998, 2004 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Lennart Augustsson (lennart@augustsson.net) at
 * Carlstedt Research & Technology.
 *
 * Copyright (c) 2006 BBN Technologies Corp.  All rights reserved.
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and the Department of the Interior National Business
 * Center under agreement number NBCHC050166.
 *
 * 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.
 *
 * 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.
 */


#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ugen.c,v 1.178 2025/04/26 07:08:48 skrll Exp $");

#ifdef _KERNEL_OPT
#include "opt_compat_netbsd.h"
#include "opt_usb.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/conf.h>
#include <sys/tty.h>
#include <sys/file.h>
#include <sys/select.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/poll.h>
#include <sys/compat_stub.h>
#include <sys/module.h>
#include <sys/rbtree.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbhist.h>

#include "ioconf.h"

#ifdef USB_DEBUG
#ifndef UGEN_DEBUG
#define ugendebug 0
#else

#ifndef UGEN_DEBUG_DEFAULT
#define UGEN_DEBUG_DEFAULT 0
#endif

int     ugendebug = UGEN_DEBUG_DEFAULT;

SYSCTL_SETUP(sysctl_hw_ugen_setup, "sysctl hw.ugen setup")
{
        int err;
        const struct sysctlnode *rnode;
        const struct sysctlnode *cnode;

        err = sysctl_createv(clog, 0, NULL, &rnode,
            CTLFLAG_PERMANENT, CTLTYPE_NODE, "ugen",
            SYSCTL_DESCR("ugen global controls"),
            NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL);

        if (err)
                goto fail;

        /* control debugging printfs */
        err = sysctl_createv(clog, 0, &rnode, &cnode,
            CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT,
            "debug", SYSCTL_DESCR("Enable debugging output"),
            NULL, 0, &ugendebug, sizeof(ugendebug), CTL_CREATE, CTL_EOL);
        if (err)
                goto fail;

        return;
fail:
        aprint_error("%s: sysctl_createv failed (err = %d)\n", __func__, err);
}

#endif /* UGEN_DEBUG */
#endif /* USB_DEBUG */

#define DPRINTF(FMT,A,B,C,D)    USBHIST_LOGN(ugendebug,1,FMT,A,B,C,D)
#define DPRINTFN(N,FMT,A,B,C,D) USBHIST_LOGN(ugendebug,N,FMT,A,B,C,D)
#define UGENHIST_FUNC()         USBHIST_FUNC()
#define UGENHIST_CALLED(name)   USBHIST_CALLED(ugendebug)
#define UGENHIST_CALLARGS(FMT,A,B,C,D) \
                                USBHIST_CALLARGS(ugendebug,FMT,A,B,C,D)
#define UGENHIST_CALLARGSN(N,FMT,A,B,C,D) \
                                USBHIST_CALLARGSN(ugendebug,N,FMT,A,B,C,D)

#define UGEN_CHUNK      128     /* chunk size for read */
#define UGEN_IBSIZE     1020    /* buffer size */
#define UGEN_BBSIZE     1024

#define UGEN_NISOREQS   4       /* number of outstanding xfer requests */
#define UGEN_NISORFRMS  8       /* number of transactions per req */
#define UGEN_NISOFRAMES (UGEN_NISORFRMS * UGEN_NISOREQS)

#define UGEN_BULK_RA_WB_BUFSIZE 16384           /* default buffer size */
#define UGEN_BULK_RA_WB_BUFMAX  (1 << 20)       /* maximum allowed buffer */

struct isoreq {
        struct ugen_endpoint *sce;
        struct usbd_xfer *xfer;
        void *dmabuf;
        uint16_t sizes[UGEN_NISORFRMS];
};

struct ugen_endpoint {
        struct ugen_softc *sc;
        usb_endpoint_descriptor_t *edesc;
        struct usbd_interface *iface;
        int state;
#define UGEN_SHORT_OK   0x04    /* short xfers are OK */
#define UGEN_BULK_RA    0x08    /* in bulk read-ahead mode */
#define UGEN_BULK_WB    0x10    /* in bulk write-behind mode */
#define UGEN_RA_WB_STOP 0x20    /* RA/WB xfer is stopped (buffer full/empty) */
        struct usbd_pipe *pipeh;
        struct clist q;
        u_char *ibuf;           /* start of buffer (circular for isoc) */
        u_char *fill;           /* location for input (isoc) */
        u_char *limit;          /* end of circular buffer (isoc) */
        u_char *cur;            /* current read location (isoc) */
        uint32_t timeout;
        uint32_t ra_wb_bufsize; /* requested size for RA/WB buffer */
        uint32_t ra_wb_reqsize; /* requested xfer length for RA/WB */
        uint32_t ra_wb_used;     /* how much is in buffer */
        uint32_t ra_wb_xferlen; /* current xfer length for RA/WB */
        struct usbd_xfer *ra_wb_xfer;
        struct isoreq isoreqs[UGEN_NISOREQS];
        /* Keep these last; we don't overwrite them in ugen_set_config() */
#define UGEN_ENDPOINT_NONZERO_CRUFT     offsetof(struct ugen_endpoint, rsel)
        struct selinfo rsel;
        kcondvar_t cv;
};

struct ugen_softc {
        device_t sc_dev;                /* base device */
        struct usbd_device *sc_udev;
        struct rb_node sc_node;
        unsigned sc_unit;

        kmutex_t                sc_lock;
        kcondvar_t              sc_detach_cv;

        char sc_is_open[USB_MAX_ENDPOINTS];
        struct ugen_endpoint sc_endpoints[USB_MAX_ENDPOINTS][2];
#define OUT 0
#define IN  1

        int sc_refcnt;
        char sc_buffer[UGEN_BBSIZE];
        u_char sc_dying;
        u_char sc_attached;
};

static struct {
        kmutex_t        lock;
        rb_tree_t       tree;
} ugenif __cacheline_aligned;

static int
compare_ugen(void *cookie, const void *vsca, const void *vscb)
{
        const struct ugen_softc *sca = vsca;
        const struct ugen_softc *scb = vscb;

        if (sca->sc_unit < scb->sc_unit)
                return -1;
        if (sca->sc_unit > scb->sc_unit)
                return +1;
        return 0;
}

static int
compare_ugen_key(void *cookie, const void *vsc, const void *vk)
{
        const struct ugen_softc *sc = vsc;
        const unsigned *k = vk;

        if (sc->sc_unit < *k)
                return -1;
        if (sc->sc_unit > *k)
                return +1;
        return 0;
}

static const rb_tree_ops_t ugenif_tree_ops = {
        .rbto_compare_nodes = compare_ugen,
        .rbto_compare_key = compare_ugen_key,
        .rbto_node_offset = offsetof(struct ugen_softc, sc_node),
};

static void
ugenif_get_unit(struct ugen_softc *sc)
{
        struct ugen_softc *sc0;
        unsigned i;

        mutex_enter(&ugenif.lock);
        for (i = 0, sc0 = RB_TREE_MIN(&ugenif.tree);
             sc0 != NULL && i == sc0->sc_unit;
             i++, sc0 = RB_TREE_NEXT(&ugenif.tree, sc0))
                KASSERT(i < UINT_MAX);
        KASSERT(rb_tree_find_node(&ugenif.tree, &i) == NULL);
        sc->sc_unit = i;
        sc0 = rb_tree_insert_node(&ugenif.tree, sc);
        KASSERT(sc0 == sc);
        KASSERT(rb_tree_find_node(&ugenif.tree, &i) == sc);
        mutex_exit(&ugenif.lock);

        prop_dictionary_set_uint(device_properties(sc->sc_dev),
            "ugen-unit", sc->sc_unit);
}

static void
ugenif_put_unit(struct ugen_softc *sc)
{

        prop_dictionary_remove(device_properties(sc->sc_dev),
            "ugen-unit");

        mutex_enter(&ugenif.lock);
        KASSERT(rb_tree_find_node(&ugenif.tree, &sc->sc_unit) == sc);
        rb_tree_remove_node(&ugenif.tree, sc);
        sc->sc_unit = -1;
        mutex_exit(&ugenif.lock);
}

static struct ugen_softc *
ugenif_acquire(unsigned unit)
{
        struct ugen_softc *sc;

        mutex_enter(&ugenif.lock);
        sc = rb_tree_find_node(&ugenif.tree, &unit);
        if (sc == NULL)
                goto out;
        mutex_enter(&sc->sc_lock);
        if (sc->sc_dying) {
                mutex_exit(&sc->sc_lock);
                sc = NULL;
                goto out;
        }
        KASSERT(sc->sc_refcnt < INT_MAX);
        sc->sc_refcnt++;
        mutex_exit(&sc->sc_lock);
out:    mutex_exit(&ugenif.lock);

        return sc;
}

static void
ugenif_release(struct ugen_softc *sc)
{

        mutex_enter(&sc->sc_lock);
        if (--sc->sc_refcnt < 0)
                cv_broadcast(&sc->sc_detach_cv);
        mutex_exit(&sc->sc_lock);
}

static dev_type_open(ugenopen);
static dev_type_close(ugenclose);
static dev_type_read(ugenread);
static dev_type_write(ugenwrite);
static dev_type_ioctl(ugenioctl);
static dev_type_poll(ugenpoll);
static dev_type_kqfilter(ugenkqfilter);

const struct cdevsw ugen_cdevsw = {
        .d_open = ugenopen,
        .d_close = ugenclose,
        .d_read = ugenread,
        .d_write = ugenwrite,
        .d_ioctl = ugenioctl,
        .d_stop = nostop,
        .d_tty = notty,
        .d_poll = ugenpoll,
        .d_mmap = nommap,
        .d_kqfilter = ugenkqfilter,
        .d_discard = nodiscard,
        .d_flag = D_OTHER,
};

Static void ugenintr(struct usbd_xfer *, void *,
                     usbd_status);
Static void ugen_isoc_rintr(struct usbd_xfer *, void *,
                            usbd_status);
Static void ugen_bulkra_intr(struct usbd_xfer *, void *,
                             usbd_status);
Static void ugen_bulkwb_intr(struct usbd_xfer *, void *,
                             usbd_status);
Static int ugen_do_read(struct ugen_softc *, int, struct uio *, int);
Static int ugen_do_write(struct ugen_softc *, int, struct uio *, int);
Static int ugen_do_ioctl(struct ugen_softc *, int, u_long,
                         void *, int, struct lwp *);
Static int ugen_set_config(struct ugen_softc *, int, int);
Static usb_config_descriptor_t *ugen_get_cdesc(struct ugen_softc *,
                                               int, int *);
Static usbd_status ugen_set_interface(struct ugen_softc *, int, int);
Static int ugen_get_alt_index(struct ugen_softc *, int);
Static void ugen_clear_endpoints(struct ugen_softc *);

#define UGENUNIT(n) ((minor(n) >> 4) & 0xf)
#define UGENENDPOINT(n) (minor(n) & 0xf)
#define UGENDEV(u, e) (makedev(0, ((u) << 4) | (e)))

static int      ugenif_match(device_t, cfdata_t, void *);
static void     ugenif_attach(device_t, device_t, void *);
static int      ugen_match(device_t, cfdata_t, void *);
static void     ugen_attach(device_t, device_t, void *);
static int      ugen_detach(device_t, int);
static int      ugen_activate(device_t, enum devact);

CFATTACH_DECL_NEW(ugen, sizeof(struct ugen_softc), ugen_match,
    ugen_attach, ugen_detach, ugen_activate);
CFATTACH_DECL_NEW(ugenif, sizeof(struct ugen_softc), ugenif_match,
    ugenif_attach, ugen_detach, ugen_activate);

/* toggle to control attach priority. -1 means "let autoconf decide" */
int ugen_override = -1;

static int
ugen_match(device_t parent, cfdata_t match, void *aux)
{
        struct usb_attach_arg *uaa = aux;
        int override;

        if (ugen_override != -1)
                override = ugen_override;
        else
                override = match->cf_flags & 1;

        if (override)
                return UMATCH_HIGHEST;
        else if (uaa->uaa_usegeneric)
                return UMATCH_GENERIC;
        else
                return UMATCH_NONE;
}

static int
ugenif_match(device_t parent, cfdata_t match, void *aux)
{
        /*
         * Like ugen(4), ugenif(4) also has an override flag.  It has the
         * opposite effect, however, causing us to match with GENERIC
         * priority rather than HIGHEST.
         */
        return (match->cf_flags & 1) ? UMATCH_GENERIC : UMATCH_HIGHEST;
}

static void
ugen_attach(device_t parent, device_t self, void *aux)
{
        struct usb_attach_arg *uaa = aux;
        struct usbif_attach_arg uiaa;

        memset(&uiaa, 0, sizeof(uiaa));
        uiaa.uiaa_port = uaa->uaa_port;
        uiaa.uiaa_vendor = uaa->uaa_vendor;
        uiaa.uiaa_product = uaa->uaa_product;
        uiaa.uiaa_release = uaa->uaa_release;
        uiaa.uiaa_device = uaa->uaa_device;
        uiaa.uiaa_configno = -1;
        uiaa.uiaa_ifaceno = -1;

        ugenif_attach(parent, self, &uiaa);
}

static void
ugenif_attach(device_t parent, device_t self, void *aux)
{
        struct ugen_softc *sc = device_private(self);
        struct usbif_attach_arg *uiaa = aux;
        struct usbd_device *udev;
        char *devinfop;
        usbd_status err;
        int i, dir, conf;

        aprint_naive("\n");
        aprint_normal("\n");

        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB);
        cv_init(&sc->sc_detach_cv, "ugendet");

        devinfop = usbd_devinfo_alloc(uiaa->uiaa_device, 0);
        aprint_normal_dev(self, "%s\n", devinfop);
        usbd_devinfo_free(devinfop);

        sc->sc_dev = self;
        sc->sc_udev = udev = uiaa->uiaa_device;

        for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
                for (dir = OUT; dir <= IN; dir++) {
                        struct ugen_endpoint *sce;

                        sce = &sc->sc_endpoints[i][dir];
                        selinit(&sce->rsel);
                        cv_init(&sce->cv, "ugensce");
                }
        }

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

        if (uiaa->uiaa_ifaceno < 0) {
                /*
                 * If we attach the whole device,
                 * set configuration index 0, the default one.
                 */
                err = usbd_set_config_index(udev, 0, 0);
                if (err) {
                        aprint_error_dev(self,
                            "setting configuration index 0 failed\n");
                        return;
                }
        }

        /* Get current configuration */
        conf = usbd_get_config_descriptor(udev)->bConfigurationValue;

        /* Set up all the local state for this configuration. */
        err = ugen_set_config(sc, conf, uiaa->uiaa_ifaceno < 0);
        if (err) {
                aprint_error_dev(self, "setting configuration %d failed\n",
                    conf);
                return;
        }

        ugenif_get_unit(sc);
        usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev);
        sc->sc_attached = 1;
}

Static void
ugen_clear_endpoints(struct ugen_softc *sc)
{

        /* Clear out the old info, but leave the selinfo and cv initialised. */
        for (int i = 0; i < USB_MAX_ENDPOINTS; i++) {
                for (int dir = OUT; dir <= IN; dir++) {
                        struct ugen_endpoint *sce = &sc->sc_endpoints[i][dir];
                        memset(sce, 0, UGEN_ENDPOINT_NONZERO_CRUFT);
                }
        }
}

Static int
ugen_set_config(struct ugen_softc *sc, int configno, int chkopen)
{
        struct usbd_device *dev = sc->sc_udev;
        usb_config_descriptor_t *cdesc;
        struct usbd_interface *iface;
        usb_endpoint_descriptor_t *ed;
        struct ugen_endpoint *sce;
        uint8_t niface, nendpt;
        int ifaceno, endptno, endpt;
        usbd_status err;
        int dir;

        UGENHIST_FUNC();
        UGENHIST_CALLARGSN(1, "ugen%jd: to configno %jd, sc=%jx",
            device_unit(sc->sc_dev), configno, (uintptr_t)sc, 0);

        KASSERT(KERNEL_LOCKED_P()); /* sc_is_open */

        if (chkopen) {
                /*
                 * We start at 1, not 0, because we don't care whether the
                 * control endpoint is open or not. It is always present.
                 */
                for (endptno = 1; endptno < USB_MAX_ENDPOINTS; endptno++)
                        if (sc->sc_is_open[endptno]) {
                                DPRINTFN(1,
                                     "ugen%jd - endpoint %jd is open",
                                      device_unit(sc->sc_dev), endptno, 0, 0);
                                return USBD_IN_USE;
                        }

                /* Prevent opening while we're setting the config.  */
                for (endptno = 1; endptno < USB_MAX_ENDPOINTS; endptno++) {
                        KASSERT(!sc->sc_is_open[endptno]);
                        sc->sc_is_open[endptno] = 1;
                }
        }

        /* Avoid setting the current value. */
        cdesc = usbd_get_config_descriptor(dev);
        if (!cdesc || cdesc->bConfigurationValue != configno) {
                err = usbd_set_config_no(dev, configno, 1);
                if (err)
                        goto out;
        }

        ugen_clear_endpoints(sc);

        err = usbd_interface_count(dev, &niface);
        if (err)
                goto out;

        for (ifaceno = 0; ifaceno < niface; ifaceno++) {
                DPRINTFN(1, "ifaceno %jd", ifaceno, 0, 0, 0);
                err = usbd_device2interface_handle(dev, ifaceno, &iface);
                if (err)
                        goto out;
                err = usbd_endpoint_count(iface, &nendpt);
                if (err)
                        goto out;
                for (endptno = 0; endptno < nendpt; endptno++) {
                        ed = usbd_interface2endpoint_descriptor(iface, endptno);
                        KASSERT(ed != NULL);
                        endpt = ed->bEndpointAddress;
                        dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT;
                        sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir];
                        DPRINTFN(1, "endptno %jd, endpt=0x%02jx (%jd,%jd)",
                                 endptno, endpt, UE_GET_ADDR(endpt),
                                 UE_GET_DIR(endpt));
                        sce->sc = sc;
                        sce->edesc = ed;
                        sce->iface = iface;
                }
        }
        err = USBD_NORMAL_COMPLETION;

out:    if (chkopen) {
                /*
                 * Allow open again now that we're done trying to set
                 * the config.
                 */
                for (endptno = 1; endptno < USB_MAX_ENDPOINTS; endptno++) {
                        KASSERT(sc->sc_is_open[endptno]);
                        sc->sc_is_open[endptno] = 0;
                }
        }
        return err;
}

static int
ugenopen(dev_t dev, int flag, int mode, struct lwp *l)
{
        struct ugen_softc *sc;
        int unit = UGENUNIT(dev);
        int endpt = UGENENDPOINT(dev);
        usb_endpoint_descriptor_t *edesc;
        struct ugen_endpoint *sce;
        int dir, isize;
        usbd_status err;
        struct usbd_xfer *xfer;
        int i, j;
        int error;
        int opened = 0;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("flag=%jd, mode=%jd, unit=%jd endpt=%jd",
            flag, mode, unit, endpt);

        KASSERT(KERNEL_LOCKED_P()); /* sc_is_open */

        if ((sc = ugenif_acquire(unit)) == NULL)
                return ENXIO;

        /* The control endpoint allows multiple opens. */
        if (endpt == USB_CONTROL_ENDPOINT) {
                opened = sc->sc_is_open[USB_CONTROL_ENDPOINT] = 1;
                error = 0;
                goto out;
        }

        if (sc->sc_is_open[endpt]) {
                error = EBUSY;
                goto out;
        }
        opened = sc->sc_is_open[endpt] = 1;

        /* Make sure there are pipes for all directions. */
        for (dir = OUT; dir <= IN; dir++) {
                if (flag & (dir == OUT ? FWRITE : FREAD)) {
                        sce = &sc->sc_endpoints[endpt][dir];
                        if (sce->edesc == NULL) {
                                error = ENXIO;
                                goto out;
                        }
                }
        }

        /* Actually open the pipes. */
        /* XXX Should back out properly if it fails. */
        for (dir = OUT; dir <= IN; dir++) {
                if (!(flag & (dir == OUT ? FWRITE : FREAD)))
                        continue;
                sce = &sc->sc_endpoints[endpt][dir];
                sce->state = 0;
                sce->timeout = USBD_NO_TIMEOUT;
                DPRINTFN(5, "sc=%jx, endpt=%jd, dir=%jd, sce=%#jx",
                             (uintptr_t)sc, endpt, dir, (uintptr_t)sce);
                edesc = sce->edesc;
                switch (edesc->bmAttributes & UE_XFERTYPE) {
                case UE_INTERRUPT:
                        if (dir == OUT) {
                                err = usbd_open_pipe(sce->iface,
                                    edesc->bEndpointAddress, 0, &sce->pipeh);
                                if (err) {
                                        error = EIO;
                                        goto out;
                                }
                                break;
                        }
                        isize = UGETW(edesc->wMaxPacketSize);
                        if (isize == 0) {       /* shouldn't happen */
                                error = EINVAL;
                                goto out;
                        }
                        sce->ibuf = kmem_alloc(isize, KM_SLEEP);
                        DPRINTFN(5, "intr endpt=%jd, isize=%jd",
                                     endpt, isize, 0, 0);
                        if (clalloc(&sce->q, UGEN_IBSIZE, 0) == -1) {
                                kmem_free(sce->ibuf, isize);
                                sce->ibuf = NULL;
                                error = ENOMEM;
                                goto out;
                        }
                        err = usbd_open_pipe_intr(sce->iface,
                                  edesc->bEndpointAddress,
                                  USBD_SHORT_XFER_OK, &sce->pipeh, sce,
                                  sce->ibuf, isize, ugenintr,
                                  USBD_DEFAULT_INTERVAL);
                        if (err) {
                                clfree(&sce->q);
                                kmem_free(sce->ibuf, isize);
                                sce->ibuf = NULL;
                                error = EIO;
                                goto out;
                        }
                        DPRINTFN(5, "interrupt open done", 0, 0, 0, 0);
                        break;
                case UE_BULK:
                        err = usbd_open_pipe(sce->iface,
                                  edesc->bEndpointAddress, 0, &sce->pipeh);
                        if (err) {
                                error = EIO;
                                goto out;
                        }
                        sce->ra_wb_bufsize = UGEN_BULK_RA_WB_BUFSIZE;
                        /*
                         * Use request size for non-RA/WB transfers
                         * as the default.
                         */
                        sce->ra_wb_reqsize = UGEN_BBSIZE;
                        break;
                case UE_ISOCHRONOUS:
                        if (dir == OUT) {
                                error = EINVAL;
                                goto out;
                        }
                        isize = UGETW(edesc->wMaxPacketSize);
                        if (isize == 0) {       /* shouldn't happen */
                                error = EINVAL;
                                goto out;
                        }
                        sce->ibuf = kmem_alloc(isize * UGEN_NISOFRAMES,
                                KM_SLEEP);
                        sce->cur = sce->fill = sce->ibuf;
                        sce->limit = sce->ibuf + isize * UGEN_NISOFRAMES;
                        DPRINTFN(5, "isoc endpt=%jd, isize=%jd",
                                     endpt, isize, 0, 0);
                        err = usbd_open_pipe(sce->iface,
                                  edesc->bEndpointAddress, 0, &sce->pipeh);
                        if (err) {
                                kmem_free(sce->ibuf, isize * UGEN_NISOFRAMES);
                                sce->ibuf = NULL;
                                error = EIO;
                                goto out;
                        }
                        for (i = 0; i < UGEN_NISOREQS; ++i) {
                                sce->isoreqs[i].sce = sce;
                                err = usbd_create_xfer(sce->pipeh,
                                    isize * UGEN_NISORFRMS, 0, UGEN_NISORFRMS,
                                    &xfer);
                                if (err)
                                        goto bad;
                                sce->isoreqs[i].xfer = xfer;
                                sce->isoreqs[i].dmabuf = usbd_get_buffer(xfer);
                                for (j = 0; j < UGEN_NISORFRMS; ++j)
                                        sce->isoreqs[i].sizes[j] = isize;
                                usbd_setup_isoc_xfer(xfer, &sce->isoreqs[i],
                                    sce->isoreqs[i].sizes, UGEN_NISORFRMS, 0,
                                    ugen_isoc_rintr);
                                (void)usbd_transfer(xfer);
                        }
                        DPRINTFN(5, "isoc open done", 0, 0, 0, 0);
                        break;
                bad:
                        while (--i >= 0) { /* implicit buffer free */
                                usbd_destroy_xfer(sce->isoreqs[i].xfer);
                                sce->isoreqs[i].xfer = NULL;
                        }
                        usbd_close_pipe(sce->pipeh);
                        sce->pipeh = NULL;
                        kmem_free(sce->ibuf, isize * UGEN_NISOFRAMES);
                        sce->ibuf = NULL;
                        error = ENOMEM;
                        goto out;
                case UE_CONTROL:
                        sce->timeout = USBD_DEFAULT_TIMEOUT;
                        error = EINVAL;
                        goto out;
                }
        }
        error = 0;
out:    if (error && opened)
                sc->sc_is_open[endpt] = 0;
        ugenif_release(sc);
        return error;
}

static void
ugen_do_close(struct ugen_softc *sc, int flag, int endpt)
{
        struct ugen_endpoint *sce;
        int dir;
        int i;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("flag=%jd endpt=%jd", flag, endpt, 0, 0);

        KASSERT(KERNEL_LOCKED_P()); /* sc_is_open */

        if (!sc->sc_is_open[endpt])
                goto out;

        if (endpt == USB_CONTROL_ENDPOINT) {
                DPRINTFN(5, "close control", 0, 0, 0, 0);
                goto out;
        }

        for (dir = OUT; dir <= IN; dir++) {
                if (!(flag & (dir == OUT ? FWRITE : FREAD)))
                        continue;
                sce = &sc->sc_endpoints[endpt][dir];
                if (sce->pipeh == NULL)
                        continue;
                DPRINTFN(5, "endpt=%jd dir=%jd sce=%jx",
                             endpt, dir, (uintptr_t)sce, 0);

                usbd_abort_pipe(sce->pipeh);

                int isize = UGETW(sce->edesc->wMaxPacketSize);
                int msize = 0;

                switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
                case UE_INTERRUPT:
                        ndflush(&sce->q, sce->q.c_cc);
                        clfree(&sce->q);
                        msize = isize;
                        break;
                case UE_ISOCHRONOUS:
                        for (i = 0; i < UGEN_NISOREQS; ++i) {
                                usbd_destroy_xfer(sce->isoreqs[i].xfer);
                                sce->isoreqs[i].xfer = NULL;
                        }
                        msize = isize * UGEN_NISOFRAMES;
                        break;
                case UE_BULK:
                        if (sce->state & (UGEN_BULK_RA | UGEN_BULK_WB)) {
                                usbd_destroy_xfer(sce->ra_wb_xfer);
                                sce->ra_wb_xfer = NULL;
                                msize = sce->ra_wb_bufsize;
                        }
                        break;
                default:
                        break;
                }
                usbd_close_pipe(sce->pipeh);
                sce->pipeh = NULL;
                if (sce->ibuf != NULL) {
                        kmem_free(sce->ibuf, msize);
                        sce->ibuf = NULL;
                }
        }

out:    sc->sc_is_open[endpt] = 0;
        for (dir = OUT; dir <= IN; dir++) {
                sce = &sc->sc_endpoints[endpt][dir];
                KASSERT(sce->pipeh == NULL);
                KASSERT(sce->ibuf == NULL);
                KASSERT(sce->ra_wb_xfer == NULL);
                for (i = 0; i < UGEN_NISOREQS; i++)
                        KASSERT(sce->isoreqs[i].xfer == NULL);
        }
}

static int
ugenclose(dev_t dev, int flag, int mode, struct lwp *l)
{
        int endpt = UGENENDPOINT(dev);
        struct ugen_softc *sc;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("flag=%jd, mode=%jd, unit=%jd, endpt=%jd",
            flag, mode, UGENUNIT(dev), endpt);

        KASSERT(KERNEL_LOCKED_P()); /* ugen_do_close */

        if ((sc = ugenif_acquire(UGENUNIT(dev))) == NULL)
                return ENXIO;

        KASSERT(sc->sc_is_open[endpt]);
        ugen_do_close(sc, flag, endpt);
        KASSERT(!sc->sc_is_open[endpt]);

        ugenif_release(sc);

        return 0;
}

Static int
ugen_do_read(struct ugen_softc *sc, int endpt, struct uio *uio, int flag)
{
        struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][IN];
        uint32_t n, tn;
        struct usbd_xfer *xfer;
        usbd_status err;
        int error = 0;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("ugen%d: %jd", device_unit(sc->sc_dev), endpt, 0, 0);

        if (endpt == USB_CONTROL_ENDPOINT)
                return ENODEV;

        KASSERT(sce->edesc);
        KASSERT(sce->pipeh);

        switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
        case UE_INTERRUPT:
                /* Block until activity occurred. */
                mutex_enter(&sc->sc_lock);
                while (sce->q.c_cc == 0) {
                        if (flag & IO_NDELAY) {
                                mutex_exit(&sc->sc_lock);
                                return EWOULDBLOCK;
                        }
                        DPRINTFN(5, "sleep on %jx", (uintptr_t)sce, 0, 0, 0);
                        /* "ugenri" */
                        error = cv_timedwait_sig(&sce->cv, &sc->sc_lock,
                            mstohz(sce->timeout));
                        DPRINTFN(5, "woke, error=%jd",
                                    error, 0, 0, 0);
                        if (sc->sc_dying)
                                error = EIO;
                        if (error)
                                break;
                }
                mutex_exit(&sc->sc_lock);

                /* Transfer as many chunks as possible. */
                while (sce->q.c_cc > 0 && uio->uio_resid > 0 && !error) {
                        n = uimin(sce->q.c_cc, uio->uio_resid);
                        if (n > sizeof(sc->sc_buffer))
                                n = sizeof(sc->sc_buffer);

                        /* Remove a small chunk from the input queue. */
                        q_to_b(&sce->q, sc->sc_buffer, n);
                        DPRINTFN(5, "got %jd chars", n, 0, 0, 0);

                        /* Copy the data to the user process. */
                        error = uiomove(sc->sc_buffer, n, uio);
                        if (error)
                                break;
                }
                break;
        case UE_BULK:
                if (sce->state & UGEN_BULK_RA) {
                        DPRINTFN(5, "BULK_RA req: %jd used: %jd",
                                     uio->uio_resid, sce->ra_wb_used, 0, 0);
                        xfer = sce->ra_wb_xfer;

                        mutex_enter(&sc->sc_lock);
                        if (sce->ra_wb_used == 0 && flag & IO_NDELAY) {
                                mutex_exit(&sc->sc_lock);
                                return EWOULDBLOCK;
                        }
                        while (uio->uio_resid > 0 && !error) {
                                while (sce->ra_wb_used == 0) {
                                        DPRINTFN(5, "sleep on %jx",
                                                    (uintptr_t)sce, 0, 0, 0);
                                        /* "ugenrb" */
                                        error = cv_timedwait_sig(&sce->cv,
                                            &sc->sc_lock, mstohz(sce->timeout));
                                        DPRINTFN(5, "woke, error=%jd",
                                                    error, 0, 0, 0);
                                        if (sc->sc_dying)
                                                error = EIO;
                                        if (error)
                                                break;
                                }

                                /* Copy data to the process. */
                                while (uio->uio_resid > 0
                                       && sce->ra_wb_used > 0) {
                                        n = uimin(uio->uio_resid,
                                                sce->ra_wb_used);
                                        n = uimin(n, sce->limit - sce->cur);
                                        error = uiomove(sce->cur, n, uio);
                                        if (error)
                                                break;
                                        sce->cur += n;
                                        sce->ra_wb_used -= n;
                                        if (sce->cur == sce->limit)
                                                sce->cur = sce->ibuf;
                                }

                                /*
                                 * If the transfers stopped because the
                                 * buffer was full, restart them.
                                 */
                                if (sce->state & UGEN_RA_WB_STOP &&
                                    sce->ra_wb_used < sce->limit - sce->ibuf) {
                                        n = (sce->limit - sce->ibuf)
                                            - sce->ra_wb_used;
                                        usbd_setup_xfer(xfer, sce, NULL,
                                            uimin(n, sce->ra_wb_xferlen),
                                            0, USBD_NO_TIMEOUT,
                                            ugen_bulkra_intr);
                                        sce->state &= ~UGEN_RA_WB_STOP;
                                        err = usbd_transfer(xfer);
                                        if (err != USBD_IN_PROGRESS)
                                                /*
                                                 * The transfer has not been
                                                 * queued.  Setting STOP
                                                 * will make us try
                                                 * again at the next read.
                                                 */
                                                sce->state |= UGEN_RA_WB_STOP;
                                }
                        }
                        mutex_exit(&sc->sc_lock);
                        break;
                }
                error = usbd_create_xfer(sce->pipeh, UGEN_BBSIZE,
                    0, 0, &xfer);
                if (error)
                        return error;
                while ((n = uimin(UGEN_BBSIZE, uio->uio_resid)) != 0) {
                        DPRINTFN(1, "start transfer %jd bytes", n, 0, 0, 0);
                        tn = n;
                        err = usbd_bulk_transfer(xfer, sce->pipeh,
                            sce->state & UGEN_SHORT_OK ? USBD_SHORT_XFER_OK : 0,
                            sce->timeout, sc->sc_buffer, &tn);
                        if (err) {
                                if (err == USBD_INTERRUPTED)
                                        error = EINTR;
                                else if (err == USBD_TIMEOUT)
                                        error = ETIMEDOUT;
                                else
                                        error = EIO;
                                break;
                        }
                        DPRINTFN(1, "got %jd bytes", tn, 0, 0, 0);
                        error = uiomove(sc->sc_buffer, tn, uio);
                        if (error || tn < n)
                                break;
                }
                usbd_destroy_xfer(xfer);
                break;
        case UE_ISOCHRONOUS:
                mutex_enter(&sc->sc_lock);
                while (sce->cur == sce->fill) {
                        if (flag & IO_NDELAY) {
                                mutex_exit(&sc->sc_lock);
                                return EWOULDBLOCK;
                        }
                        /* "ugenri" */
                        DPRINTFN(5, "sleep on %jx", (uintptr_t)sce, 0, 0, 0);
                        error = cv_timedwait_sig(&sce->cv, &sc->sc_lock,
                            mstohz(sce->timeout));
                        DPRINTFN(5, "woke, error=%jd", error, 0, 0, 0);
                        if (sc->sc_dying)
                                error = EIO;
                        if (error)
                                break;
                }

                while (sce->cur != sce->fill && uio->uio_resid > 0 && !error) {
                        if(sce->fill > sce->cur)
                                n = uimin(sce->fill - sce->cur, uio->uio_resid);
                        else
                                n = uimin(sce->limit - sce->cur, uio->uio_resid);

                        DPRINTFN(5, "isoc got %jd chars", n, 0, 0, 0);

                        /* Copy the data to the user process. */
                        error = uiomove(sce->cur, n, uio);
                        if (error)
                                break;
                        sce->cur += n;
                        if (sce->cur >= sce->limit)
                                sce->cur = sce->ibuf;
                }
                mutex_exit(&sc->sc_lock);
                break;


        default:
                return ENXIO;
        }
        return error;
}

static int
ugenread(dev_t dev, struct uio *uio, int flag)
{
        int endpt = UGENENDPOINT(dev);
        struct ugen_softc *sc;
        int error;

        if ((sc = ugenif_acquire(UGENUNIT(dev))) == NULL)
                return ENXIO;
        error = ugen_do_read(sc, endpt, uio, flag);
        ugenif_release(sc);

        return error;
}

Static int
ugen_do_write(struct ugen_softc *sc, int endpt, struct uio *uio,
        int flag)
{
        struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][OUT];
        uint32_t n;
        int error = 0;
        uint32_t tn;
        char *dbuf;
        struct usbd_xfer *xfer;
        usbd_status err;

        UGENHIST_FUNC();
        UGENHIST_CALLARGSN(5, "ugen%jd: %jd",
            device_unit(sc->sc_dev), endpt, 0, 0);

        if (endpt == USB_CONTROL_ENDPOINT)
                return ENODEV;

        KASSERT(sce->edesc);
        KASSERT(sce->pipeh);

        switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
        case UE_BULK:
                if (sce->state & UGEN_BULK_WB) {
                        DPRINTFN(5, "BULK_WB req: %jd used: %jd",
                                     uio->uio_resid, sce->ra_wb_used, 0, 0);
                        xfer = sce->ra_wb_xfer;

                        mutex_enter(&sc->sc_lock);
                        if (sce->ra_wb_used == sce->limit - sce->ibuf &&
                            flag & IO_NDELAY) {
                                mutex_exit(&sc->sc_lock);
                                return EWOULDBLOCK;
                        }
                        while (uio->uio_resid > 0 && !error) {
                                while (sce->ra_wb_used ==
                                       sce->limit - sce->ibuf) {
                                        DPRINTFN(5, "sleep on %#jx",
                                                     (uintptr_t)sce, 0, 0, 0);
                                        /* "ugenwb" */
                                        error = cv_timedwait_sig(&sce->cv,
                                            &sc->sc_lock, mstohz(sce->timeout));
                                        DPRINTFN(5, "woke, error=%jd",
                                                    error, 0, 0, 0);
                                        if (sc->sc_dying)
                                                error = EIO;
                                        if (error)
                                                break;
                                }

                                /* Copy data from the process. */
                                while (uio->uio_resid > 0 &&
                                    sce->ra_wb_used < sce->limit - sce->ibuf) {
                                        n = uimin(uio->uio_resid,
                                                (sce->limit - sce->ibuf)
                                                 - sce->ra_wb_used);
                                        n = uimin(n, sce->limit - sce->fill);
                                        error = uiomove(sce->fill, n, uio);
                                        if (error)
                                                break;
                                        sce->fill += n;
                                        sce->ra_wb_used += n;
                                        if (sce->fill == sce->limit)
                                                sce->fill = sce->ibuf;
                                }

                                /*
                                 * If the transfers stopped because the
                                 * buffer was empty, restart them.
                                 */
                                if (sce->state & UGEN_RA_WB_STOP &&
                                    sce->ra_wb_used > 0) {
                                        dbuf = (char *)usbd_get_buffer(xfer);
                                        n = uimin(sce->ra_wb_used,
                                                sce->ra_wb_xferlen);
                                        tn = uimin(n, sce->limit - sce->cur);
                                        memcpy(dbuf, sce->cur, tn);
                                        dbuf += tn;
                                        if (n - tn > 0)
                                                memcpy(dbuf, sce->ibuf,
                                                       n - tn);
                                        usbd_setup_xfer(xfer, sce, NULL, n,
                                            0, USBD_NO_TIMEOUT,
                                            ugen_bulkwb_intr);
                                        sce->state &= ~UGEN_RA_WB_STOP;
                                        err = usbd_transfer(xfer);
                                        if (err != USBD_IN_PROGRESS)
                                                /*
                                                 * The transfer has not been
                                                 * queued.  Setting STOP
                                                 * will make us try again
                                                 * at the next read.
                                                 */
                                                sce->state |= UGEN_RA_WB_STOP;
                                }
                        }
                        mutex_exit(&sc->sc_lock);
                        break;
                }
                error = usbd_create_xfer(sce->pipeh, UGEN_BBSIZE,
                    0, 0, &xfer);
                if (error)
                        return error;
                while ((n = uimin(UGEN_BBSIZE, uio->uio_resid)) != 0) {
                        error = uiomove(sc->sc_buffer, n, uio);
                        if (error)
                                break;
                        DPRINTFN(1, "transfer %jd bytes", n, 0, 0, 0);
                        err = usbd_bulk_transfer(xfer, sce->pipeh, 0, sce->timeout,
                            sc->sc_buffer, &n);
                        if (err) {
                                if (err == USBD_INTERRUPTED)
                                        error = EINTR;
                                else if (err == USBD_TIMEOUT)
                                        error = ETIMEDOUT;
                                else
                                        error = EIO;
                                break;
                        }
                }
                usbd_destroy_xfer(xfer);
                break;
        case UE_INTERRUPT:
                error = usbd_create_xfer(sce->pipeh,
                    UGETW(sce->edesc->wMaxPacketSize), 0, 0, &xfer);
                if (error)
                        return error;
                while ((n = uimin(UGETW(sce->edesc->wMaxPacketSize),
                    uio->uio_resid)) != 0) {
                        error = uiomove(sc->sc_buffer, n, uio);
                        if (error)
                                break;
                        DPRINTFN(1, "transfer %jd bytes", n, 0, 0, 0);
                        err = usbd_intr_transfer(xfer, sce->pipeh, 0,
                            sce->timeout, sc->sc_buffer, &n);
                        if (err) {
                                if (err == USBD_INTERRUPTED)
                                        error = EINTR;
                                else if (err == USBD_TIMEOUT)
                                        error = ETIMEDOUT;
                                else
                                        error = EIO;
                                break;
                        }
                }
                usbd_destroy_xfer(xfer);
                break;
        default:
                return ENXIO;
        }
        return error;
}

static int
ugenwrite(dev_t dev, struct uio *uio, int flag)
{
        int endpt = UGENENDPOINT(dev);
        struct ugen_softc *sc;
        int error;

        if ((sc = ugenif_acquire(UGENUNIT(dev))) == NULL)
                return ENXIO;
        error = ugen_do_write(sc, endpt, uio, flag);
        ugenif_release(sc);

        return error;
}

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

        switch (act) {
        case DVACT_DEACTIVATE:
                sc->sc_dying = 1;
                return 0;
        default:
                return EOPNOTSUPP;
        }
}

static int
ugen_detach(device_t self, int flags)
{
        struct ugen_softc *sc = device_private(self);
        struct ugen_endpoint *sce;
        int i, dir;
        int maj, mn;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("sc=%ju flags=%ju", (uintptr_t)sc, flags, 0, 0);

        KASSERT(KERNEL_LOCKED_P()); /* sc_is_open */

        /*
         * Fail if we're not forced to detach and userland has any
         * endpoints open.
         */
        if ((flags & DETACH_FORCE) == 0) {
                for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
                        if (sc->sc_is_open[i])
                                return EBUSY;
                }
        }

        /* Prevent new users.  Prevent suspend/resume.  */
        sc->sc_dying = 1;
        pmf_device_deregister(self);

        /*
         * If we never finished attaching, skip nixing endpoints and
         * users because there aren't any.
         */
        if (!sc->sc_attached)
                goto out;

        /* Abort all pipes.  */
        for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
                for (dir = OUT; dir <= IN; dir++) {
                        sce = &sc->sc_endpoints[i][dir];
                        if (sce->pipeh)
                                usbd_abort_pipe(sce->pipeh);
                }
        }

        /*
         * Wait for users to drain.  Before this point there can be no
         * more I/O operations started because we set sc_dying; after
         * this, there can be no more I/O operations in progress, so it
         * will be safe to free things.
         */
        mutex_enter(&sc->sc_lock);
        if (--sc->sc_refcnt >= 0) {
                /* Wake everyone */
                for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
                        for (dir = OUT; dir <= IN; dir++)
                                cv_broadcast(&sc->sc_endpoints[i][dir].cv);
                }
                /* Wait for processes to go away. */
                do {
                        cv_wait(&sc->sc_detach_cv, &sc->sc_lock);
                } while (sc->sc_refcnt >= 0);
        }
        mutex_exit(&sc->sc_lock);

        /* locate the major number */
        maj = cdevsw_lookup_major(&ugen_cdevsw);

        /*
         * Nuke the vnodes for any open instances (calls ugenclose, but
         * with no effect because we already set sc_dying).
         */
        mn = sc->sc_unit * USB_MAX_ENDPOINTS;
        vdevgone(maj, mn, mn + USB_MAX_ENDPOINTS - 1, VCHR);

        /* Actually close any lingering pipes.  */
        for (i = 0; i < USB_MAX_ENDPOINTS; i++)
                ugen_do_close(sc, FREAD|FWRITE, i);

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

out:    for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
                for (dir = OUT; dir <= IN; dir++) {
                        sce = &sc->sc_endpoints[i][dir];
                        seldestroy(&sce->rsel);
                        cv_destroy(&sce->cv);
                }
        }

        cv_destroy(&sc->sc_detach_cv);
        mutex_destroy(&sc->sc_lock);

        return 0;
}

Static void
ugenintr(struct usbd_xfer *xfer, void *addr, usbd_status status)
{
        struct ugen_endpoint *sce = addr;
        struct ugen_softc *sc = sce->sc;
        uint32_t count;
        u_char *ibuf;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("xfer %jx status %d", (uintptr_t)xfer, status, 0, 0);

        if (status == USBD_CANCELLED)
                return;

        if (status != USBD_NORMAL_COMPLETION) {
                DPRINTF("status=%jd", status, 0, 0, 0);
                if (status == USBD_STALLED)
                    usbd_clear_endpoint_stall_async(sce->pipeh);
                return;
        }

        usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
        ibuf = sce->ibuf;

        DPRINTFN(5, "xfer=%#jx status=%jd count=%jd",
                     (uintptr_t)xfer, status, count, 0);
        DPRINTFN(5, "          data = %02jx %02jx %02jx",
                     ibuf[0], ibuf[1], ibuf[2], 0);

        mutex_enter(&sc->sc_lock);
        (void)b_to_q(ibuf, count, &sce->q);
        cv_signal(&sce->cv);
        mutex_exit(&sc->sc_lock);
        selnotify(&sce->rsel, 0, 0);
}

Static void
ugen_isoc_rintr(struct usbd_xfer *xfer, void *addr,
                usbd_status status)
{
        struct isoreq *req = addr;
        struct ugen_endpoint *sce = req->sce;
        struct ugen_softc *sc = sce->sc;
        uint32_t count, n;
        int i, isize;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("xfer=%jx status=%jd", (uintptr_t)xfer, status, 0, 0);

        /* Return if we are aborting. */
        if (status == USBD_CANCELLED)
                return;

        usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
        DPRINTFN(5, "xfer %jd, count=%jd",
            (intmax_t)(req - sce->isoreqs), count, 0, 0);

        mutex_enter(&sc->sc_lock);

        /* throw away oldest input if the buffer is full */
        if (sce->fill < sce->cur && sce->cur <= sce->fill + count) {
                sce->cur += count;
                if (sce->cur >= sce->limit)
                        sce->cur = sce->ibuf + (sce->limit - sce->cur);
                DPRINTFN(5, "throwing away %jd bytes",
                             count, 0, 0, 0);
        }

        isize = UGETW(sce->edesc->wMaxPacketSize);
        for (i = 0; i < UGEN_NISORFRMS; i++) {
                uint32_t actlen = req->sizes[i];
                char const *tbuf = (char const *)req->dmabuf + isize * i;

                /* copy data to buffer */
                while (actlen > 0) {
                        n = uimin(actlen, sce->limit - sce->fill);
                        memcpy(sce->fill, tbuf, n);

                        tbuf += n;
                        actlen -= n;
                        sce->fill += n;
                        if (sce->fill == sce->limit)
                                sce->fill = sce->ibuf;
                }

                /* setup size for next transfer */
                req->sizes[i] = isize;
        }

        usbd_setup_isoc_xfer(xfer, req, req->sizes, UGEN_NISORFRMS, 0,
            ugen_isoc_rintr);
        (void)usbd_transfer(xfer);

        cv_signal(&sce->cv);
        mutex_exit(&sc->sc_lock);
        selnotify(&sce->rsel, 0, 0);
}

Static void
ugen_bulkra_intr(struct usbd_xfer *xfer, void *addr,
                 usbd_status status)
{
        struct ugen_endpoint *sce = addr;
        struct ugen_softc *sc = sce->sc;
        uint32_t count, n;
        char const *tbuf;
        usbd_status err;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("xfer=%jx status=%jd", (uintptr_t)xfer, status, 0, 0);

        /* Return if we are aborting. */
        if (status == USBD_CANCELLED)
                return;

        if (status != USBD_NORMAL_COMPLETION) {
                DPRINTF("status=%jd", status, 0, 0, 0);
                sce->state |= UGEN_RA_WB_STOP;
                if (status == USBD_STALLED)
                    usbd_clear_endpoint_stall_async(sce->pipeh);
                return;
        }

        usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);

        mutex_enter(&sc->sc_lock);

        /* Keep track of how much is in the buffer. */
        sce->ra_wb_used += count;

        /* Copy data to buffer. */
        tbuf = (char const *)usbd_get_buffer(sce->ra_wb_xfer);
        n = uimin(count, sce->limit - sce->fill);
        memcpy(sce->fill, tbuf, n);
        tbuf += n;
        count -= n;
        sce->fill += n;
        if (sce->fill == sce->limit)
                sce->fill = sce->ibuf;
        if (count > 0) {
                memcpy(sce->fill, tbuf, count);
                sce->fill += count;
        }

        /* Set up the next request if necessary. */
        n = (sce->limit - sce->ibuf) - sce->ra_wb_used;
        if (n > 0) {
                usbd_setup_xfer(xfer, sce, NULL, uimin(n, sce->ra_wb_xferlen), 0,
                    USBD_NO_TIMEOUT, ugen_bulkra_intr);
                err = usbd_transfer(xfer);
                if (err != USBD_IN_PROGRESS) {
                        printf("error=%d", err);
                        /*
                         * The transfer has not been queued.  Setting STOP
                         * will make us try again at the next read.
                         */
                        sce->state |= UGEN_RA_WB_STOP;
                }
        }
        else
                sce->state |= UGEN_RA_WB_STOP;

        cv_signal(&sce->cv);
        mutex_exit(&sc->sc_lock);
        selnotify(&sce->rsel, 0, 0);
}

Static void
ugen_bulkwb_intr(struct usbd_xfer *xfer, void *addr,
                 usbd_status status)
{
        struct ugen_endpoint *sce = addr;
        struct ugen_softc *sc = sce->sc;
        uint32_t count, n;
        char *tbuf;
        usbd_status err;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("xfer=%jx status=%jd", (uintptr_t)xfer, status, 0, 0);

        /* Return if we are aborting. */
        if (status == USBD_CANCELLED)
                return;

        if (status != USBD_NORMAL_COMPLETION) {
                DPRINTF("status=%jd", status, 0, 0, 0);
                sce->state |= UGEN_RA_WB_STOP;
                if (status == USBD_STALLED)
                    usbd_clear_endpoint_stall_async(sce->pipeh);
                return;
        }

        usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);

        mutex_enter(&sc->sc_lock);

        /* Keep track of how much is in the buffer. */
        sce->ra_wb_used -= count;

        /* Update buffer pointers. */
        sce->cur += count;
        if (sce->cur >= sce->limit)
                sce->cur = sce->ibuf + (sce->cur - sce->limit);

        /* Set up next request if necessary. */
        if (sce->ra_wb_used > 0) {
                /* copy data from buffer */
                tbuf = (char *)usbd_get_buffer(sce->ra_wb_xfer);
                count = uimin(sce->ra_wb_used, sce->ra_wb_xferlen);
                n = uimin(count, sce->limit - sce->cur);
                memcpy(tbuf, sce->cur, n);
                tbuf += n;
                if (count - n > 0)
                        memcpy(tbuf, sce->ibuf, count - n);

                usbd_setup_xfer(xfer, sce, NULL, count, 0, USBD_NO_TIMEOUT,
                    ugen_bulkwb_intr);
                err = usbd_transfer(xfer);
                if (err != USBD_IN_PROGRESS) {
                        printf("error=%d", err);
                        /*
                         * The transfer has not been queued.  Setting STOP
                         * will make us try again at the next write.
                         */
                        sce->state |= UGEN_RA_WB_STOP;
                }
        }
        else
                sce->state |= UGEN_RA_WB_STOP;

        cv_signal(&sce->cv);
        mutex_exit(&sc->sc_lock);
        selnotify(&sce->rsel, 0, 0);
}

Static usbd_status
ugen_set_interface(struct ugen_softc *sc, int ifaceidx, int altno)
{
        struct usbd_interface *iface;
        usb_endpoint_descriptor_t *ed;
        usbd_status err;
        struct ugen_endpoint *sce;
        uint8_t niface, nendpt, endptno, endpt;
        int dir;

        UGENHIST_FUNC();
        UGENHIST_CALLARGSN(15, "ifaceidx=%jd altno=%jd", ifaceidx, altno, 0, 0);

        err = usbd_interface_count(sc->sc_udev, &niface);
        if (err)
                return err;
        if (ifaceidx < 0 || ifaceidx >= niface)
                return USBD_INVAL;

        err = usbd_device2interface_handle(sc->sc_udev, ifaceidx, &iface);
        if (err)
                return err;
        err = usbd_endpoint_count(iface, &nendpt);
        if (err)
                return err;

        /* change setting */
        err = usbd_set_interface(iface, altno);
        if (err)
                return err;

        err = usbd_endpoint_count(iface, &nendpt);
        if (err)
                return err;

        ugen_clear_endpoints(sc);

        for (endptno = 0; endptno < nendpt; endptno++) {
                ed = usbd_interface2endpoint_descriptor(iface, endptno);
                KASSERT(ed != NULL);
                endpt = ed->bEndpointAddress;
                dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT;
                sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir];
                sce->sc = sc;
                sce->edesc = ed;
                sce->iface = iface;
        }
        return 0;
}

/* Retrieve a complete descriptor for a certain device and index. */
Static usb_config_descriptor_t *
ugen_get_cdesc(struct ugen_softc *sc, int index, int *lenp)
{
        usb_config_descriptor_t *cdesc = NULL, *tdesc, cdescr;
        int len = 0;
        usbd_status err;

        UGENHIST_FUNC(); UGENHIST_CALLARGS("index=%jd", index, 0, 0, 0);

        switch (index) {
        case USB_CURRENT_CONFIG_INDEX:
                tdesc = usbd_get_config_descriptor(sc->sc_udev);
                if (tdesc == NULL)
                        break;
                len = UGETW(tdesc->wTotalLength);
                cdesc = kmem_alloc(len, KM_SLEEP);
                memcpy(cdesc, tdesc, len);
                break;
        default:
                err = usbd_get_config_desc(sc->sc_udev, index, &cdescr);
                if (err)
                        break;
                len = UGETW(cdescr.wTotalLength);
                cdesc = kmem_alloc(len, KM_SLEEP);
                err = usbd_get_config_desc_full(sc->sc_udev, index, cdesc, len);
                if (err) {
                        kmem_free(cdesc, len);
                        cdesc = NULL;
                }
                break;
        }
        DPRINTFN(5, "req len=%jd cdesc=%jx", len, (uintptr_t)cdesc, 0, 0);
        if (cdesc && lenp)
                *lenp = len;
        return cdesc;
}

Static int
ugen_get_alt_index(struct ugen_softc *sc, int ifaceidx)
{
        struct usbd_interface *iface;
        usbd_status err;

        err = usbd_device2interface_handle(sc->sc_udev, ifaceidx, &iface);
        if (err)
                return -1;
        return usbd_get_interface_altindex(iface);
}

Static int
ugen_do_ioctl(struct ugen_softc *sc, int endpt, u_long cmd,
              void *addr, int flag, struct lwp *l)
{
        struct ugen_endpoint *sce;
        usbd_status err;
        struct usbd_interface *iface;
        struct usb_config_desc *cd;
        usb_config_descriptor_t *cdesc;
        struct usb_interface_desc *id;
        usb_interface_descriptor_t *idesc;
        struct usb_endpoint_desc *ed;
        usb_endpoint_descriptor_t *edesc;
        struct usb_alt_interface *ai;
        struct usb_string_desc *si;
        uint8_t conf, alt;
        int cdesclen;
        int error;
        int dir;

        UGENHIST_FUNC();
        UGENHIST_CALLARGS("ugen%d: endpt=%ju cmd=%08jx flag=%jx",
            device_unit(sc->sc_dev), endpt, cmd, flag);

        KASSERT(KERNEL_LOCKED_P()); /* ugen_set_config */

        switch (cmd) {
        case FIONBIO:
                /* All handled in the upper FS layer. */
                return 0;
        case USB_SET_SHORT_XFER:
                if (endpt == USB_CONTROL_ENDPOINT)
                        return EINVAL;
                /* This flag only affects read */
                sce = &sc->sc_endpoints[endpt][IN];
                if (sce == NULL || sce->pipeh == NULL)
                        return EINVAL;
                if (*(int *)addr)
                        sce->state |= UGEN_SHORT_OK;
                else
                        sce->state &= ~UGEN_SHORT_OK;
                DPRINTFN(5, "pipe=%jx short xfer=%ju",
                    (uintptr_t)sce->pipeh, sce->state & UGEN_SHORT_OK, 0, 0);
                return 0;
        case USB_SET_TIMEOUT:
                for (dir = OUT; dir <= IN; dir++) {
                        sce = &sc->sc_endpoints[endpt][dir];
                        if (sce == NULL)
                                return EINVAL;

                        sce->timeout = *(int *)addr;
                        DPRINTFN(5, "pipe=%jx timeout[dir=%ju] timeout=%ju",
                            (uintptr_t)sce->pipeh, dir, sce->timeout, 0);
                }
                return 0;
        case USB_SET_BULK_RA:
                if (endpt == USB_CONTROL_ENDPOINT)
                        return EINVAL;
                sce = &sc->sc_endpoints[endpt][IN];
                if (sce == NULL || sce->pipeh == NULL)
                        return EINVAL;
                edesc = sce->edesc;
                if ((edesc->bmAttributes & UE_XFERTYPE) != UE_BULK)
                        return EINVAL;

                if (*(int *)addr) {
                        /* Only turn RA on if it's currently off. */
                        if (sce->state & UGEN_BULK_RA)
                                return 0;
                        KASSERT(sce->ra_wb_xfer == NULL);
                        KASSERT(sce->ibuf == NULL);

                        if (sce->ra_wb_bufsize == 0 || sce->ra_wb_reqsize == 0)
                                /* shouldn't happen */
                                return EINVAL;
                        error = usbd_create_xfer(sce->pipeh,
                            sce->ra_wb_reqsize, 0, 0, &sce->ra_wb_xfer);
                        if (error)
                                return error;
                        sce->ra_wb_xferlen = sce->ra_wb_reqsize;
                        sce->ibuf = kmem_alloc(sce->ra_wb_bufsize, KM_SLEEP);
                        sce->fill = sce->cur = sce->ibuf;
                        sce->limit = sce->ibuf + sce->ra_wb_bufsize;
                        sce->ra_wb_used = 0;
                        sce->state |= UGEN_BULK_RA;
                        sce->state &= ~UGEN_RA_WB_STOP;
                        /* Now start reading. */
                        usbd_setup_xfer(sce->ra_wb_xfer, sce, NULL,
                            uimin(sce->ra_wb_xferlen, sce->ra_wb_bufsize),
                             0, USBD_NO_TIMEOUT, ugen_bulkra_intr);
                        err = usbd_transfer(sce->ra_wb_xfer);
                        if (err != USBD_IN_PROGRESS) {
                                sce->state &= ~UGEN_BULK_RA;
                                kmem_free(sce->ibuf, sce->ra_wb_bufsize);
                                sce->ibuf = NULL;
                                usbd_destroy_xfer(sce->ra_wb_xfer);
                                sce->ra_wb_xfer = NULL;
                                return EIO;
                        }
                } else {
                        /* Only turn RA off if it's currently on. */
                        if (!(sce->state & UGEN_BULK_RA))
                                return 0;

                        sce->state &= ~UGEN_BULK_RA;
                        usbd_abort_pipe(sce->pipeh);
                        usbd_destroy_xfer(sce->ra_wb_xfer);
                        sce->ra_wb_xfer = NULL;
                        /*
                         * XXX Discard whatever's in the buffer, but we
                         * should keep it around and drain the buffer
                         * instead.
                         */
                        kmem_free(sce->ibuf, sce->ra_wb_bufsize);
                        sce->ibuf = NULL;
                }
                return 0;
        case USB_SET_BULK_WB:
                if (endpt == USB_CONTROL_ENDPOINT)
                        return EINVAL;
                sce = &sc->sc_endpoints[endpt][OUT];
                if (sce == NULL || sce->pipeh == NULL)
                        return EINVAL;
                edesc = sce->edesc;
                if ((edesc->bmAttributes & UE_XFERTYPE) != UE_BULK)
                        return EINVAL;

                if (*(int *)addr) {
                        /* Only turn WB on if it's currently off. */
                        if (sce->state & UGEN_BULK_WB)
                                return 0;
                        KASSERT(sce->ra_wb_xfer == NULL);
                        KASSERT(sce->ibuf == NULL);

                        if (sce->ra_wb_bufsize == 0 || sce->ra_wb_reqsize == 0)
                                /* shouldn't happen */
                                return EINVAL;
                        error = usbd_create_xfer(sce->pipeh, sce->ra_wb_reqsize,
                            0, 0, &sce->ra_wb_xfer);
                        /* XXX check error???  */
                        sce->ra_wb_xferlen = sce->ra_wb_reqsize;
                        sce->ibuf = kmem_alloc(sce->ra_wb_bufsize, KM_SLEEP);
                        sce->fill = sce->cur = sce->ibuf;
                        sce->limit = sce->ibuf + sce->ra_wb_bufsize;
                        sce->ra_wb_used = 0;
                        sce->state |= UGEN_BULK_WB | UGEN_RA_WB_STOP;
                } else {
                        /* Only turn WB off if it's currently on. */
                        if (!(sce->state & UGEN_BULK_WB))
                                return 0;

                        sce->state &= ~UGEN_BULK_WB;
                        /*
                         * XXX Discard whatever's in the buffer, but we
                         * should keep it around and keep writing to
                         * drain the buffer instead.
                         */
                        usbd_abort_pipe(sce->pipeh);
                        usbd_destroy_xfer(sce->ra_wb_xfer);
                        sce->ra_wb_xfer = NULL;
                        kmem_free(sce->ibuf, sce->ra_wb_bufsize);
                        sce->ibuf = NULL;
                }
                return 0;
        case USB_SET_BULK_RA_OPT:
        case USB_SET_BULK_WB_OPT:
        {
                struct usb_bulk_ra_wb_opt *opt;

                if (endpt == USB_CONTROL_ENDPOINT)
                        return EINVAL;
                opt = (struct usb_bulk_ra_wb_opt *)addr;
                if (cmd == USB_SET_BULK_RA_OPT)
                        sce = &sc->sc_endpoints[endpt][IN];
                else
                        sce = &sc->sc_endpoints[endpt][OUT];
                if (sce == NULL || sce->pipeh == NULL)
                        return EINVAL;
                if (opt->ra_wb_buffer_size < 1 ||
                    opt->ra_wb_buffer_size > UGEN_BULK_RA_WB_BUFMAX ||
                    opt->ra_wb_request_size < 1 ||
                    opt->ra_wb_request_size > opt->ra_wb_buffer_size)
                        return EINVAL;
                /*
                 * XXX These changes do not take effect until the
                 * next time RA/WB mode is enabled but they ought to
                 * take effect immediately.
                 */
                sce->ra_wb_bufsize = opt->ra_wb_buffer_size;
                sce->ra_wb_reqsize = opt->ra_wb_request_size;
                return 0;
        }
        default:
                break;
        }

        if (endpt != USB_CONTROL_ENDPOINT)
                return EINVAL;

        switch (cmd) {
#ifdef UGEN_DEBUG
        case USB_SETDEBUG:
                ugendebug = *(int *)addr;
                break;
#endif
        case USB_GET_CONFIG:
                err = usbd_get_config(sc->sc_udev, &conf);
                if (err)
                        return EIO;
                *(int *)addr = conf;
                break;
        case USB_SET_CONFIG:
                if (!(flag & FWRITE))
                        return EPERM;
                err = ugen_set_config(sc, *(int *)addr, 1);
                switch (err) {
                case USBD_NORMAL_COMPLETION:
                        break;
                case USBD_IN_USE:
                        return EBUSY;
                default:
                        return EIO;
                }
                break;
        case USB_GET_ALTINTERFACE:
                ai = (struct usb_alt_interface *)addr;
                err = usbd_device2interface_handle(sc->sc_udev,
                          ai->uai_interface_index, &iface);
                if (err)
                        return EINVAL;
                idesc = usbd_get_interface_descriptor(iface);
                if (idesc == NULL)
                        return EIO;
                ai->uai_alt_no = idesc->bAlternateSetting;
                break;
        case USB_SET_ALTINTERFACE:
                if (!(flag & FWRITE))
                        return EPERM;
                ai = (struct usb_alt_interface *)addr;
                err = usbd_device2interface_handle(sc->sc_udev,
                          ai->uai_interface_index, &iface);
                if (err)
                        return EINVAL;
                err = ugen_set_interface(sc, ai->uai_interface_index,
                    ai->uai_alt_no);
                if (err)
                        return EINVAL;
                break;
        case USB_GET_NO_ALT:
                ai = (struct usb_alt_interface *)addr;
                cdesc = ugen_get_cdesc(sc, ai->uai_config_index, &cdesclen);
                if (cdesc == NULL)
                        return EINVAL;
                idesc = usbd_find_idesc(cdesc, ai->uai_interface_index, 0);
                if (idesc == NULL) {
                        kmem_free(cdesc, cdesclen);
                        return EINVAL;
                }
                ai->uai_alt_no = usbd_get_no_alts(cdesc,
                    idesc->bInterfaceNumber);
                kmem_free(cdesc, cdesclen);
                break;
        case USB_GET_DEVICE_DESC:
                *(usb_device_descriptor_t *)addr =
                        *usbd_get_device_descriptor(sc->sc_udev);
                break;
        case USB_GET_CONFIG_DESC:
                cd = (struct usb_config_desc *)addr;
                cdesc = ugen_get_cdesc(sc, cd->ucd_config_index, &cdesclen);
                if (cdesc == NULL)
                        return EINVAL;
                cd->ucd_desc = *cdesc;
                kmem_free(cdesc, cdesclen);
                break;
        case USB_GET_INTERFACE_DESC:
                id = (struct usb_interface_desc *)addr;
                cdesc = ugen_get_cdesc(sc, id->uid_config_index, &cdesclen);
                if (cdesc == NULL)
                        return EINVAL;
                if (id->uid_config_index == USB_CURRENT_CONFIG_INDEX &&
                    id->uid_alt_index == USB_CURRENT_ALT_INDEX)
                        alt = ugen_get_alt_index(sc, id->uid_interface_index);
                else
                        alt = id->uid_alt_index;
                idesc = usbd_find_idesc(cdesc, id->uid_interface_index, alt);
                if (idesc == NULL) {
                        kmem_free(cdesc, cdesclen);
                        return EINVAL;
                }
                id->uid_desc = *idesc;
                kmem_free(cdesc, cdesclen);
                break;
        case USB_GET_ENDPOINT_DESC:
                ed = (struct usb_endpoint_desc *)addr;
                cdesc = ugen_get_cdesc(sc, ed->ued_config_index, &cdesclen);
                if (cdesc == NULL)
                        return EINVAL;
                if (ed->ued_config_index == USB_CURRENT_CONFIG_INDEX &&
                    ed->ued_alt_index == USB_CURRENT_ALT_INDEX)
                        alt = ugen_get_alt_index(sc, ed->ued_interface_index);
                else
                        alt = ed->ued_alt_index;
                edesc = usbd_find_edesc(cdesc, ed->ued_interface_index,
                                        alt, ed->ued_endpoint_index);
                if (edesc == NULL) {
                        kmem_free(cdesc, cdesclen);
                        return EINVAL;
                }
                ed->ued_desc = *edesc;
                kmem_free(cdesc, cdesclen);
                break;
        case USB_GET_FULL_DESC:
        {
                int len;
                struct iovec iov;
                struct uio uio;
                struct usb_full_desc *fd = (struct usb_full_desc *)addr;

                cdesc = ugen_get_cdesc(sc, fd->ufd_config_index, &cdesclen);
                if (cdesc == NULL)
                        return EINVAL;
                len = cdesclen;
                if (len > fd->ufd_size)
                        len = fd->ufd_size;
                iov.iov_base = (void *)fd->ufd_data;
                iov.iov_len = len;
                uio.uio_iov = &iov;
                uio.uio_iovcnt = 1;
                uio.uio_resid = len;
                uio.uio_offset = 0;
                uio.uio_rw = UIO_READ;
                uio.uio_vmspace = l->l_proc->p_vmspace;
                error = uiomove((void *)cdesc, len, &uio);
                kmem_free(cdesc, cdesclen);
                return error;
        }
        case USB_GET_STRING_DESC: {
                int len;
                si = (struct usb_string_desc *)addr;
                err = usbd_get_string_desc(sc->sc_udev, si->usd_string_index,
                          si->usd_language_id, &si->usd_desc, &len);
                if (err)
                        return EINVAL;
                break;
        }
        case USB_DO_REQUEST:
        {
                struct usb_ctl_request *ur = (void *)addr;
                int len = UGETW(ur->ucr_request.wLength);
                struct iovec iov;
                struct uio uio;
                void *ptr = 0;
                usbd_status xerr;

                error = 0;

                if (!(flag & FWRITE))
                        return EPERM;
                /* Avoid requests that would damage the bus integrity. */
                if ((ur->ucr_request.bmRequestType == UT_WRITE_DEVICE &&
                     ur->ucr_request.bRequest == UR_SET_ADDRESS) ||
                    (ur->ucr_request.bmRequestType == UT_WRITE_DEVICE &&
                     ur->ucr_request.bRequest == UR_SET_CONFIG) ||
                    (ur->ucr_request.bmRequestType == UT_WRITE_INTERFACE &&
                     ur->ucr_request.bRequest == UR_SET_INTERFACE))
                        return EINVAL;

                if (len < 0 || len > 32767)
                        return EINVAL;
                if (len != 0) {
                        iov.iov_base = (void *)ur->ucr_data;
                        iov.iov_len = len;
                        uio.uio_iov = &iov;
                        uio.uio_iovcnt = 1;
                        uio.uio_resid = len;
                        uio.uio_offset = 0;
                        uio.uio_rw =
                                ur->ucr_request.bmRequestType & UT_READ ?
                                UIO_READ : UIO_WRITE;
                        uio.uio_vmspace = l->l_proc->p_vmspace;
                        ptr = kmem_alloc(len, KM_SLEEP);
                        if (uio.uio_rw == UIO_WRITE) {
                                error = uiomove(ptr, len, &uio);
                                if (error)
                                        goto ret;
                        }
                }
                sce = &sc->sc_endpoints[endpt][IN];
                xerr = usbd_do_request_flags(sc->sc_udev, &ur->ucr_request,
                          ptr, ur->ucr_flags, &ur->ucr_actlen, sce->timeout);
                if (xerr) {
                        error = EIO;
                        goto ret;
                }
                if (len != 0) {
                        if (uio.uio_rw == UIO_READ) {
                                size_t alen = uimin(len, ur->ucr_actlen);
                                error = uiomove(ptr, alen, &uio);
                                if (error)
                                        goto ret;
                        }
                }
        ret:
                if (ptr)
                        kmem_free(ptr, len);
                return error;
        }
        case USB_GET_DEVICEINFO:
                usbd_fill_deviceinfo(sc->sc_udev,
                                     (struct usb_device_info *)addr, 0);
                break;
        case USB_GET_DEVICEINFO_30:
        {
                int ret;
                MODULE_HOOK_CALL(usb_subr_fill_30_hook,
                    (sc->sc_udev, (struct usb_device_info30 *)addr, 0,
                      usbd_devinfo_vp, usbd_printBCD),
                    enosys(), ret);
                if (ret == 0)
                        return 0;
                return EINVAL;
        }
        default:
                return EINVAL;
        }
        return 0;
}

static int
ugenioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
{
        int endpt = UGENENDPOINT(dev);
        struct ugen_softc *sc;
        int error;

        if ((sc = ugenif_acquire(UGENUNIT(dev))) == 0)
                return ENXIO;
        error = ugen_do_ioctl(sc, endpt, cmd, addr, flag, l);
        ugenif_release(sc);

        return error;
}

static int
ugenpoll(dev_t dev, int events, struct lwp *l)
{
        struct ugen_softc *sc;
        struct ugen_endpoint *sce_in, *sce_out;
        int revents = 0;

        if ((sc = ugenif_acquire(UGENUNIT(dev))) == NULL)
                return POLLHUP;

        if (UGENENDPOINT(dev) == USB_CONTROL_ENDPOINT) {
                revents |= POLLERR;
                goto out;
        }

        sce_in = &sc->sc_endpoints[UGENENDPOINT(dev)][IN];
        sce_out = &sc->sc_endpoints[UGENENDPOINT(dev)][OUT];
        KASSERT(sce_in->edesc || sce_out->edesc);
        KASSERT(sce_in->pipeh || sce_out->pipeh);

        mutex_enter(&sc->sc_lock);
        if (sce_in && sce_in->pipeh && (events & (POLLIN | POLLRDNORM)))
                switch (sce_in->edesc->bmAttributes & UE_XFERTYPE) {
                case UE_INTERRUPT:
                        if (sce_in->q.c_cc > 0)
                                revents |= events & (POLLIN | POLLRDNORM);
                        else
                                selrecord(l, &sce_in->rsel);
                        break;
                case UE_ISOCHRONOUS:
                        if (sce_in->cur != sce_in->fill)
                                revents |= events & (POLLIN | POLLRDNORM);
                        else
                                selrecord(l, &sce_in->rsel);
                        break;
                case UE_BULK:
                        if (sce_in->state & UGEN_BULK_RA) {
                                if (sce_in->ra_wb_used > 0)
                                        revents |= events &
                                            (POLLIN | POLLRDNORM);
                                else
                                        selrecord(l, &sce_in->rsel);
                                break;
                        }
                        /*
                         * We have no easy way of determining if a read will
                         * yield any data or a write will happen.
                         * Pretend they will.
                         */
                        revents |= events & (POLLIN | POLLRDNORM);
                        break;
                default:
                        break;
                }
        if (sce_out && sce_out->pipeh && (events & (POLLOUT | POLLWRNORM)))
                switch (sce_out->edesc->bmAttributes & UE_XFERTYPE) {
                case UE_INTERRUPT:
                case UE_ISOCHRONOUS:
                        /* XXX unimplemented */
                        break;
                case UE_BULK:
                        if (sce_out->state & UGEN_BULK_WB) {
                                if (sce_out->ra_wb_used <
                                    sce_out->limit - sce_out->ibuf)
                                        revents |= events &
                                            (POLLOUT | POLLWRNORM);
                                else
                                        selrecord(l, &sce_out->rsel);
                                break;
                        }
                        /*
                         * We have no easy way of determining if a read will
                         * yield any data or a write will happen.
                         * Pretend they will.
                         */
                         revents |= events & (POLLOUT | POLLWRNORM);
                         break;
                default:
                        break;
                }

        mutex_exit(&sc->sc_lock);

out:    ugenif_release(sc);
        return revents;
}

static void
filt_ugenrdetach(struct knote *kn)
{
        struct ugen_endpoint *sce = kn->kn_hook;
        struct ugen_softc *sc = sce->sc;

        mutex_enter(&sc->sc_lock);
        selremove_knote(&sce->rsel, kn);
        mutex_exit(&sc->sc_lock);
}

static int
filt_ugenread_intr(struct knote *kn, long hint)
{
        struct ugen_endpoint *sce = kn->kn_hook;
        struct ugen_softc *sc = sce->sc;
        int ret;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_dying) {
                ret = 0;
        } else {
                kn->kn_data = sce->q.c_cc;
                ret = kn->kn_data > 0;
        }
        mutex_exit(&sc->sc_lock);

        return ret;
}

static int
filt_ugenread_isoc(struct knote *kn, long hint)
{
        struct ugen_endpoint *sce = kn->kn_hook;
        struct ugen_softc *sc = sce->sc;
        int ret;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_dying) {
                ret = 0;
        } else if (sce->cur == sce->fill) {
                ret = 0;
        } else if (sce->cur < sce->fill) {
                kn->kn_data = sce->fill - sce->cur;
                ret = 1;
        } else {
                kn->kn_data = (sce->limit - sce->cur) +
                    (sce->fill - sce->ibuf);
                ret = 1;
        }
        mutex_exit(&sc->sc_lock);

        return ret;
}

static int
filt_ugenread_bulk(struct knote *kn, long hint)
{
        struct ugen_endpoint *sce = kn->kn_hook;
        struct ugen_softc *sc = sce->sc;
        int ret;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_dying) {
                ret = 0;
        } else if (!(sce->state & UGEN_BULK_RA)) {
                /*
                 * We have no easy way of determining if a read will
                 * yield any data or a write will happen.
                 * So, emulate "seltrue".
                 */
                ret = filt_seltrue(kn, hint);
        } else if (sce->ra_wb_used == 0) {
                ret = 0;
        } else {
                kn->kn_data = sce->ra_wb_used;
                ret = 1;
        }
        mutex_exit(&sc->sc_lock);

        return ret;
}

static int
filt_ugenwrite_bulk(struct knote *kn, long hint)
{
        struct ugen_endpoint *sce = kn->kn_hook;
        struct ugen_softc *sc = sce->sc;
        int ret;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_dying) {
                ret = 0;
        } else if (!(sce->state & UGEN_BULK_WB)) {
                /*
                 * We have no easy way of determining if a read will
                 * yield any data or a write will happen.
                 * So, emulate "seltrue".
                 */
                ret = filt_seltrue(kn, hint);
        } else if (sce->ra_wb_used == sce->limit - sce->ibuf) {
                ret = 0;
        } else {
                kn->kn_data = (sce->limit - sce->ibuf) - sce->ra_wb_used;
                ret = 1;
        }
        mutex_exit(&sc->sc_lock);

        return ret;
}

static const struct filterops ugenread_intr_filtops = {
        .f_flags = FILTEROP_ISFD,
        .f_attach = NULL,
        .f_detach = filt_ugenrdetach,
        .f_event = filt_ugenread_intr,
};

static const struct filterops ugenread_isoc_filtops = {
        .f_flags = FILTEROP_ISFD,
        .f_attach = NULL,
        .f_detach = filt_ugenrdetach,
        .f_event = filt_ugenread_isoc,
};

static const struct filterops ugenread_bulk_filtops = {
        .f_flags = FILTEROP_ISFD,
        .f_attach = NULL,
        .f_detach = filt_ugenrdetach,
        .f_event = filt_ugenread_bulk,
};

static const struct filterops ugenwrite_bulk_filtops = {
        .f_flags = FILTEROP_ISFD,
        .f_attach = NULL,
        .f_detach = filt_ugenrdetach,
        .f_event = filt_ugenwrite_bulk,
};

static int
ugenkqfilter(dev_t dev, struct knote *kn)
{
        struct ugen_softc *sc;
        struct ugen_endpoint *sce;
        struct selinfo *sip;
        int error;

        if ((sc = ugenif_acquire(UGENUNIT(dev))) == NULL)
                return ENXIO;

        if (UGENENDPOINT(dev) == USB_CONTROL_ENDPOINT) {
                error = ENODEV;
                goto out;
        }

        switch (kn->kn_filter) {
        case EVFILT_READ:
                sce = &sc->sc_endpoints[UGENENDPOINT(dev)][IN];
                if (sce == NULL) {
                        error = EINVAL;
                        goto out;
                }

                sip = &sce->rsel;
                switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
                case UE_INTERRUPT:
                        kn->kn_fop = &ugenread_intr_filtops;
                        break;
                case UE_ISOCHRONOUS:
                        kn->kn_fop = &ugenread_isoc_filtops;
                        break;
                case UE_BULK:
                        kn->kn_fop = &ugenread_bulk_filtops;
                        break;
                default:
                        error = EINVAL;
                        goto out;
                }
                break;

        case EVFILT_WRITE:
                sce = &sc->sc_endpoints[UGENENDPOINT(dev)][OUT];
                if (sce == NULL) {
                        error = EINVAL;
                        goto out;
                }

                sip = &sce->rsel;
                switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
                case UE_INTERRUPT:
                case UE_ISOCHRONOUS:
                        /* XXX poll doesn't support this */
                        error = EINVAL;
                        goto out;

                case UE_BULK:
                        kn->kn_fop = &ugenwrite_bulk_filtops;
                        break;
                default:
                        error = EINVAL;
                        goto out;
                }
                break;

        default:
                error = EINVAL;
                goto out;
        }

        kn->kn_hook = sce;

        mutex_enter(&sc->sc_lock);
        selrecord_knote(sip, kn);
        mutex_exit(&sc->sc_lock);

        error = 0;

out:    ugenif_release(sc);
        return error;
}

MODULE(MODULE_CLASS_DRIVER, ugen, NULL);

static int
ugen_modcmd(modcmd_t cmd, void *aux)
{

        switch (cmd) {
        case MODULE_CMD_INIT:
                mutex_init(&ugenif.lock, MUTEX_DEFAULT, IPL_NONE);
                rb_tree_init(&ugenif.tree, &ugenif_tree_ops);
                return 0;
        default:
                return ENOTTY;
        }
}