/*	$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;
	}
}
