Edit

IABSD.fr/src/sys/dev/usb/usb.c

Branch :

  • Show log

    Commit

  • Author : kirill
    Date : 2024-12-22 22:36:23
    Hash : 30208656
    Message : sys/usb: fix ISOCHRONOUS IN transfer offsets Offsets for ISOCHRONOUS IN frames are now based on a fixed packet size (xfer->length / xfer->nframes), ensuring accuracy even with shorter frames. OK: mpi@

  • sys/dev/usb/usb.c
  • /*	$OpenBSD: usb.c,v 1.134 2024/12/22 22:36:23 kirill Exp $	*/
    /*	$NetBSD: usb.c,v 1.77 2003/01/01 00:10:26 thorpej Exp $	*/
    
    /*
     * Copyright (c) 1998, 2002 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.
     *
     * 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.
     */
    
    /*
     * USB specifications and other documentation can be found at
     * https://www.usb.org/documents
     */
    
    #include "ohci.h"
    #include "uhci.h"
    #include "ehci.h"
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/malloc.h>
    #include <sys/device.h>
    #include <sys/kthread.h>
    #include <sys/conf.h>
    #include <sys/fcntl.h>
    #include <sys/time.h>
    #include <sys/rwlock.h>
    
    #include <dev/usb/usb.h>
    #include <dev/usb/usbdi.h>
    
    #include <machine/bus.h>
    
    #include <dev/usb/usbdivar.h>
    #include <dev/usb/usb_mem.h>
    #include <dev/usb/usbpcap.h>
    
    #ifdef USB_DEBUG
    #define DPRINTF(x)	do { if (usbdebug) printf x; } while (0)
    #define DPRINTFN(n,x)	do { if (usbdebug>(n)) printf x; } while (0)
    int	usbdebug = 0;
    #if defined(UHCI_DEBUG) && NUHCI > 0
    extern int	uhcidebug;
    #endif
    #if defined(OHCI_DEBUG) && NOHCI > 0
    extern int	ohcidebug;
    #endif
    #if defined(EHCI_DEBUG) && NEHCI > 0
    extern int	ehcidebug;
    #endif
    /*
     * 0  - do usual exploration
     * !0 - do no exploration
     */
    int	usb_noexplore = 0;
    #else
    #define DPRINTF(x)
    #define DPRINTFN(n,x)
    #endif
    
    struct usb_softc {
    	struct device	 sc_dev;	/* base device */
    	struct usbd_bus  *sc_bus;	/* USB controller */
    	struct usbd_port sc_port;	/* dummy port for root hub */
    	int		 sc_speed;
    
    	struct usb_task	 sc_explore_task;
    
    	struct timeval	 sc_ptime;
    };
    
    struct rwlock usbpalock;
    
    TAILQ_HEAD(, usb_task) usb_abort_tasks;
    TAILQ_HEAD(, usb_task) usb_explore_tasks;
    TAILQ_HEAD(, usb_task) usb_generic_tasks;
    
    static int usb_nbuses = 0;
    static int usb_run_tasks, usb_run_abort_tasks;
    int explore_pending;
    const char *usbrev_str[] = USBREV_STR;
    
    void		 usb_explore(void *);
    void		 usb_create_task_threads(void *);
    void		 usb_task_thread(void *);
    struct proc	*usb_task_thread_proc = NULL;
    void		 usb_abort_task_thread(void *);
    struct proc	*usb_abort_task_thread_proc = NULL;
    
    void		 usb_fill_udc_task(void *);
    void		 usb_fill_udf_task(void *);
    
    int		 usb_match(struct device *, void *, void *);
    void		 usb_attach(struct device *, struct device *, void *);
    int		 usb_detach(struct device *, int);
    int		 usb_activate(struct device *, int);
    
    int		 usb_attach_roothub(struct usb_softc *);
    void		 usb_detach_roothub(struct usb_softc *);
    
    struct cfdriver usb_cd = {
    	NULL, "usb", DV_DULL
    };
    
    const struct cfattach usb_ca = {
    	sizeof(struct usb_softc), usb_match, usb_attach, usb_detach,
    	usb_activate,
    };
    
    int
    usb_match(struct device *parent, void *match, void *aux)
    {
    	return (1);
    }
    
    void
    usb_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct usb_softc *sc = (struct usb_softc *)self;
    	int usbrev;
    
    	if (usb_nbuses == 0) {
    		rw_init(&usbpalock, "usbpalock");
    		TAILQ_INIT(&usb_abort_tasks);
    		TAILQ_INIT(&usb_explore_tasks);
    		TAILQ_INIT(&usb_generic_tasks);
    		usb_run_tasks = usb_run_abort_tasks = 1;
    		kthread_create_deferred(usb_create_task_threads, NULL);
    	}
    	usb_nbuses++;
    
    	sc->sc_bus = aux;
    	sc->sc_bus->usbctl = self;
    	sc->sc_port.power = USB_MAX_POWER;
    
    	usbrev = sc->sc_bus->usbrev;
    	printf(": USB revision %s", usbrev_str[usbrev]);
    	switch (usbrev) {
    	case USBREV_1_0:
    	case USBREV_1_1:
    		sc->sc_speed = USB_SPEED_FULL;
    		break;
    	case USBREV_2_0:
    		sc->sc_speed = USB_SPEED_HIGH;
    		break;
    	case USBREV_3_0:
    		sc->sc_speed = USB_SPEED_SUPER;
    		break;
    	default:
    		printf(", not supported\n");
    		sc->sc_bus->dying = 1;
    		return;
    	}
    	printf("\n");
    
    #if NBPFILTER > 0
    	sc->sc_bus->bpfif = bpfsattach(&sc->sc_bus->bpf, sc->sc_dev.dv_xname,
    	    DLT_USBPCAP, sizeof(struct usbpcap_pkt_hdr));
    #endif
    
    	/* Make sure not to use tsleep() if we are cold booting. */
    	if (cold)
    		sc->sc_bus->use_polling++;
    
    	/* Don't let hub interrupts cause explore until ready. */
    	sc->sc_bus->flags |= USB_BUS_CONFIG_PENDING;
    
    	/* explore task */
    	usb_init_task(&sc->sc_explore_task, usb_explore, sc,
    	    USB_TASK_TYPE_EXPLORE);
    
    	sc->sc_bus->soft = softintr_establish(IPL_SOFTUSB,
    	    sc->sc_bus->methods->soft_intr, sc->sc_bus);
    	if (sc->sc_bus->soft == NULL) {
    		printf("%s: can't register softintr\n", sc->sc_dev.dv_xname);
    		sc->sc_bus->dying = 1;
    		return;
    	}
    
    	if (!usb_attach_roothub(sc)) {
    		struct usbd_device *dev = sc->sc_bus->root_hub;
    #if 1
    		/*
    		 * Turning this code off will delay attachment of USB devices
    		 * until the USB task thread is running, which means that
    		 * the keyboard will not work until after cold boot.
    		 */
    		if (cold && (sc->sc_dev.dv_cfdata->cf_flags & 1))
    			dev->hub->explore(sc->sc_bus->root_hub);
    #endif
    	}
    
    	if (cold)
    		sc->sc_bus->use_polling--;
    
    	if (!sc->sc_bus->dying) {
    		getmicrouptime(&sc->sc_ptime);
    		if (sc->sc_bus->usbrev == USBREV_2_0)
    			explore_pending++;
    		config_pending_incr();
    		usb_needs_explore(sc->sc_bus->root_hub, 1);
    	}
    }
    
    int
    usb_attach_roothub(struct usb_softc *sc)
    {
    	struct usbd_device *dev;
    
    	if (usbd_new_device(&sc->sc_dev, sc->sc_bus, 0, sc->sc_speed, 0,
    	    &sc->sc_port)) {
    		printf("%s: root hub problem\n", sc->sc_dev.dv_xname);
    		sc->sc_bus->dying = 1;
    		return (1);
    	}
    
    	dev = sc->sc_port.device;
    	if (dev->hub == NULL) {
    		printf("%s: root device is not a hub\n", sc->sc_dev.dv_xname);
    		sc->sc_bus->dying = 1;
    		return (1);
    	}
    	sc->sc_bus->root_hub = dev;
    
    	return (0);
    }
    
    void
    usb_detach_roothub(struct usb_softc *sc)
    {
    	/*
    	 * To avoid races with the usb task thread, mark the root hub
    	 * as disconnecting and schedule an exploration task to detach
    	 * it.
    	 */
    	sc->sc_bus->flags |= USB_BUS_DISCONNECTING;
    	/*
    	 * Reset the dying flag in case it has been set by the interrupt
    	 * handler when unplugging an HC card otherwise the task won't be
    	 * scheduled.  This is safe since a dead HC should not trigger
    	 * new interrupt.
    	 */
    	sc->sc_bus->dying = 0;
    	usb_needs_explore(sc->sc_bus->root_hub, 0);
    
    	usb_wait_task(sc->sc_bus->root_hub, &sc->sc_explore_task);
    
    	sc->sc_bus->root_hub = NULL;
    }
    
    void
    usb_create_task_threads(void *arg)
    {
    	if (kthread_create(usb_abort_task_thread, NULL,
    	    &usb_abort_task_thread_proc, "usbatsk"))
    		panic("unable to create usb abort task thread");
    
    	if (kthread_create(usb_task_thread, NULL,
    	    &usb_task_thread_proc, "usbtask"))
    		panic("unable to create usb task thread");
    }
    
    /*
     * Add a task to be performed by the task thread.  This function can be
     * called from any context and the task will be executed in a process
     * context ASAP.
     */
    void
    usb_add_task(struct usbd_device *dev, struct usb_task *task)
    {
    	int s;
    
    	/*
    	 * If the thread detaching ``dev'' is sleeping, waiting
    	 * for all submitted transfers to finish, we must be able
    	 * to enqueue abort tasks.  Otherwise timeouts can't give
    	 * back submitted transfers to the stack.
    	 */
    	if (usbd_is_dying(dev) && (task->type != USB_TASK_TYPE_ABORT))
    		return;
    
    	DPRINTFN(2,("%s: task=%p state=%d type=%d\n", __func__, task,
    	    task->state, task->type));
    
    	s = splusb();
    	if (!(task->state & USB_TASK_STATE_ONQ)) {
    		switch (task->type) {
    		case USB_TASK_TYPE_ABORT:
    			TAILQ_INSERT_TAIL(&usb_abort_tasks, task, next);
    			break;
    		case USB_TASK_TYPE_EXPLORE:
    			TAILQ_INSERT_TAIL(&usb_explore_tasks, task, next);
    			break;
    		case USB_TASK_TYPE_GENERIC:
    			TAILQ_INSERT_TAIL(&usb_generic_tasks, task, next);
    			break;
    		}
    		task->state |= USB_TASK_STATE_ONQ;
    		task->dev = dev;
    	}
    	if (task->type == USB_TASK_TYPE_ABORT)
    		wakeup(&usb_run_abort_tasks);
    	else
    		wakeup(&usb_run_tasks);
    	splx(s);
    }
    
    void
    usb_rem_task(struct usbd_device *dev, struct usb_task *task)
    {
    	int s;
    
    	if (!(task->state & USB_TASK_STATE_ONQ))
    		return;
    
    	DPRINTFN(2,("%s: task=%p state=%d type=%d\n", __func__, task,
    	    task->state, task->type));
    
    	s = splusb();
    
    	switch (task->type) {
    	case USB_TASK_TYPE_ABORT:
    		TAILQ_REMOVE(&usb_abort_tasks, task, next);
    		break;
    	case USB_TASK_TYPE_EXPLORE:
    		TAILQ_REMOVE(&usb_explore_tasks, task, next);
    		break;
    	case USB_TASK_TYPE_GENERIC:
    		TAILQ_REMOVE(&usb_generic_tasks, task, next);
    		break;
    	}
    	task->state &= ~USB_TASK_STATE_ONQ;
    	if (task->state == USB_TASK_STATE_NONE)
    		wakeup(task);
    
    	splx(s);
    }
    
    void
    usb_wait_task(struct usbd_device *dev, struct usb_task *task)
    {
    	int s;
    
    	DPRINTFN(2,("%s: task=%p state=%d type=%d\n", __func__, task,
    	    task->state, task->type));
    
    	if (task->state == USB_TASK_STATE_NONE)
    		return;
    
    	s = splusb();
    	while (task->state != USB_TASK_STATE_NONE) {
    		DPRINTF(("%s: waiting for task to complete\n", __func__));
    		tsleep_nsec(task, PWAIT, "endtask", INFSLP);
    	}
    	splx(s);
    }
    
    void
    usb_rem_wait_task(struct usbd_device *dev, struct usb_task *task)
    {
    	usb_rem_task(dev, task);
    	usb_wait_task(dev, task);
    }
    
    void
    usb_task_thread(void *arg)
    {
    	struct usb_task *task;
    	int s;
    
    	DPRINTF(("usb_task_thread: start\n"));
    
    	s = splusb();
    	while (usb_run_tasks) {
    		if ((task = TAILQ_FIRST(&usb_explore_tasks)) != NULL)
    			TAILQ_REMOVE(&usb_explore_tasks, task, next);
    		else if ((task = TAILQ_FIRST(&usb_generic_tasks)) != NULL)
    			TAILQ_REMOVE(&usb_generic_tasks, task, next);
    		else {
    			tsleep_nsec(&usb_run_tasks, PWAIT, "usbtsk", INFSLP);
    			continue;
    		}
    		/*
    		 * Set the state run bit before clearing the onq bit.
    		 * This avoids state == none between dequeue and
    		 * execution, which could cause usb_wait_task() to do
    		 * the wrong thing.
    		 */
    		task->state |= USB_TASK_STATE_RUN;
    		task->state &= ~USB_TASK_STATE_ONQ;
    		/* Don't actually execute the task if dying. */
    		if (!usbd_is_dying(task->dev)) {
    			splx(s);
    			task->fun(task->arg);
    			s = splusb();
    		}
    		task->state &= ~USB_TASK_STATE_RUN;
    		if (task->state == USB_TASK_STATE_NONE)
    			wakeup(task);
    	}
    	splx(s);
    
    	kthread_exit(0);
    }
    
    /*
     * This thread is ONLY for the HCI drivers to be able to abort xfers.
     * Synchronous xfers sleep the task thread, so the aborts need to happen
     * in a different thread.
     */
    void
    usb_abort_task_thread(void *arg)
    {
    	struct usb_task *task;
    	int s;
    
    	DPRINTF(("usb_xfer_abort_thread: start\n"));
    
    	s = splusb();
    	while (usb_run_abort_tasks) {
    		if ((task = TAILQ_FIRST(&usb_abort_tasks)) != NULL)
    			TAILQ_REMOVE(&usb_abort_tasks, task, next);
    		else {
    			tsleep_nsec(&usb_run_abort_tasks, PWAIT, "usbatsk",
    			    INFSLP);
    			continue;
    		}
    		/*
    		 * Set the state run bit before clearing the onq bit.
    		 * This avoids state == none between dequeue and
    		 * execution, which could cause usb_wait_task() to do
    		 * the wrong thing.
    		 */
    		task->state |= USB_TASK_STATE_RUN;
    		task->state &= ~USB_TASK_STATE_ONQ;
    		splx(s);
    		task->fun(task->arg);
    		s = splusb();
    		task->state &= ~USB_TASK_STATE_RUN;
    		if (task->state == USB_TASK_STATE_NONE)
    			wakeup(task);
    	}
    	splx(s);
    
    	kthread_exit(0);
    }
    
    int
    usbctlprint(void *aux, const char *pnp)
    {
    	/* only "usb"es can attach to host controllers */
    	if (pnp)
    		printf("usb at %s", pnp);
    
    	return (UNCONF);
    }
    
    int
    usbopen(dev_t dev, int flag, int mode, struct proc *p)
    {
    	int unit = minor(dev);
    	struct usb_softc *sc;
    
    	if (unit >= usb_cd.cd_ndevs)
    		return (ENXIO);
    	sc = usb_cd.cd_devs[unit];
    	if (sc == NULL)
    		return (ENXIO);
    
    	if (sc->sc_bus->dying)
    		return (EIO);
    
    	return (0);
    }
    
    int
    usbclose(dev_t dev, int flag, int mode, struct proc *p)
    {
    	return (0);
    }
    
    void
    usb_fill_udc_task(void *arg)
    {
    	struct usb_device_cdesc *udc = (struct usb_device_cdesc *)arg;
    	struct usb_softc *sc;
    	struct usbd_device *dev;
    	int addr = udc->udc_addr, cdesc_len;
    	usb_config_descriptor_t *cdesc;
    
    	/* check that the bus and device are still present */
    	if (udc->udc_bus >= usb_cd.cd_ndevs)
    		return;
    	sc = usb_cd.cd_devs[udc->udc_bus];
    	if (sc == NULL)
    		return;
    	dev = sc->sc_bus->devices[udc->udc_addr];
    	if (dev == NULL)
    		return;
    
    	cdesc = usbd_get_cdesc(sc->sc_bus->devices[addr],
    	    udc->udc_config_index, &cdesc_len);
    	if (cdesc == NULL)
    		return;
    	udc->udc_desc = *cdesc;
    	free(cdesc, M_TEMP, cdesc_len);
    }
    
    void
    usb_fill_udf_task(void *arg)
    {
    	struct usb_device_fdesc *udf = (struct usb_device_fdesc *)arg;
    	struct usb_softc *sc;
    	struct usbd_device *dev;
    	int addr = udf->udf_addr;
    	usb_config_descriptor_t *cdesc;
    
    	/* check that the bus and device are still present */
    	if (udf->udf_bus >= usb_cd.cd_ndevs)
    		return;
    	sc = usb_cd.cd_devs[udf->udf_bus];
    	if (sc == NULL)
    		return;
    	dev = sc->sc_bus->devices[udf->udf_addr];
    	if (dev == NULL)
    		return;
    
    	cdesc = usbd_get_cdesc(sc->sc_bus->devices[addr],
    	    udf->udf_config_index, &udf->udf_size);
    	udf->udf_data = (char *)cdesc;
    }
    
    int
    usbioctl(dev_t devt, u_long cmd, caddr_t data, int flag, struct proc *p)
    {
    	struct usb_softc *sc;
    	int unit = minor(devt);
    	int error;
    
    	sc = usb_cd.cd_devs[unit];
    
    	if (sc->sc_bus->dying)
    		return (EIO);
    
    	error = 0;
    	switch (cmd) {
    #ifdef USB_DEBUG
    	case USB_SETDEBUG:
    		/* only root can access to these debug flags */
    		if ((error = suser(curproc)) != 0)
    			return (error);
    		if (!(flag & FWRITE))
    			return (EBADF);
    		usbdebug  = ((*(unsigned int *)data) & 0x000000ff);
    #if defined(UHCI_DEBUG) && NUHCI > 0
    		uhcidebug = ((*(unsigned int *)data) & 0x0000ff00) >> 8;
    #endif
    #if defined(OHCI_DEBUG) && NOHCI > 0
    		ohcidebug = ((*(unsigned int *)data) & 0x00ff0000) >> 16;
    #endif
    #if defined(EHCI_DEBUG) && NEHCI > 0
    		ehcidebug = ((*(unsigned int *)data) & 0xff000000) >> 24;
    #endif
    		break;
    #endif /* USB_DEBUG */
    	case USB_REQUEST:
    	{
    		struct usb_ctl_request *ur = (void *)data;
    		size_t len = UGETW(ur->ucr_request.wLength), mlen;
    		struct iovec iov;
    		struct uio uio;
    		void *ptr = NULL;
    		int addr = ur->ucr_addr;
    		usbd_status err;
    
    		if (!(flag & FWRITE))
    			return (EBADF);
    
    		DPRINTF(("%s: USB_REQUEST addr=%d len=%zu\n", __func__, addr, len));
    		/* 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 > 32767)
    			return (EINVAL);
    		if (addr < 0 || addr >= USB_MAX_DEVICES)
    			return (EINVAL);
    		if (sc->sc_bus->devices[addr] == NULL)
    			return (ENXIO);
    		if (len != 0) {
    			iov.iov_base = (caddr_t)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_segflg = UIO_USERSPACE;
    			uio.uio_rw =
    				ur->ucr_request.bmRequestType & UT_READ ?
    				UIO_READ : UIO_WRITE;
    			uio.uio_procp = p;
    			if ((ptr = malloc(len, M_TEMP, M_NOWAIT)) == NULL) {
    				error = ENOMEM;
    				goto ret;
    			}
    			if (uio.uio_rw == UIO_WRITE) {
    				error = uiomove(ptr, len, &uio);
    				if (error)
    					goto ret;
    			}
    		}
    		err = usbd_do_request_flags(sc->sc_bus->devices[addr],
    			  &ur->ucr_request, ptr, ur->ucr_flags,
    			  &ur->ucr_actlen, USBD_DEFAULT_TIMEOUT);
    		if (err) {
    			error = EIO;
    			goto ret;
    		}
    		/* Only if USBD_SHORT_XFER_OK is set. */
    		mlen = len;
    		if (mlen > ur->ucr_actlen)
    			mlen = ur->ucr_actlen;
    		if (mlen != 0) {
    			if (uio.uio_rw == UIO_READ) {
    				error = uiomove(ptr, mlen, &uio);
    				if (error)
    					goto ret;
    			}
    		}
    	ret:
    		free(ptr, M_TEMP, len);
    		return (error);
    	}
    
    	case USB_DEVICEINFO:
    	{
    		struct usb_device_info *di = (void *)data;
    		int addr = di->udi_addr;
    		struct usbd_device *dev;
    
    		if (addr < 1 || addr >= USB_MAX_DEVICES)
    			return (EINVAL);
    
    		dev = sc->sc_bus->devices[addr];
    		if (dev == NULL)
    			return (ENXIO);
    
    		usbd_fill_deviceinfo(dev, di);
    		break;
    	}
    
    	case USB_DEVICESTATS:
    		*(struct usb_device_stats *)data = sc->sc_bus->stats;
    		break;
    
    	case USB_DEVICE_GET_DDESC:
    	{
    		struct usb_device_ddesc *udd = (struct usb_device_ddesc *)data;
    		int addr = udd->udd_addr;
    		struct usbd_device *dev;
    
    		if (addr < 1 || addr >= USB_MAX_DEVICES)
    			return (EINVAL);
    
    		dev = sc->sc_bus->devices[addr];
    		if (dev == NULL)
    			return (ENXIO);
    
    		udd->udd_bus = unit;
    
    		udd->udd_desc = *usbd_get_device_descriptor(dev);
    		break;
    	}
    
    	case USB_DEVICE_GET_CDESC:
    	{
    		struct usb_device_cdesc *udc = (struct usb_device_cdesc *)data;
    		int addr = udc->udc_addr;
    		struct usb_task udc_task;
    
    		if (addr < 1 || addr >= USB_MAX_DEVICES)
    			return (EINVAL);
    		if (sc->sc_bus->devices[addr] == NULL)
    			return (ENXIO);
    
    		udc->udc_bus = unit;
    
    		udc->udc_desc.bLength = 0;
    		usb_init_task(&udc_task, usb_fill_udc_task, udc,
    		    USB_TASK_TYPE_GENERIC);
    		usb_add_task(sc->sc_bus->root_hub, &udc_task);
    		usb_wait_task(sc->sc_bus->root_hub, &udc_task);
    		if (udc->udc_desc.bLength == 0)
    			return (EINVAL);
    		break;
    	}
    
    	case USB_DEVICE_GET_FDESC:
    	{
    		struct usb_device_fdesc *udf = (struct usb_device_fdesc *)data;
    		int addr = udf->udf_addr;
    		struct usb_task udf_task;
    		struct usb_device_fdesc save_udf;
    		usb_config_descriptor_t *cdesc;
    		struct iovec iov;
    		struct uio uio;
    		size_t len, cdesc_len;
    
    		if (addr < 1 || addr >= USB_MAX_DEVICES)
    			return (EINVAL);
    		if (sc->sc_bus->devices[addr] == NULL)
    			return (ENXIO);
    
    		udf->udf_bus = unit;
    
    		save_udf = *udf;
    		udf->udf_data = NULL;
    		usb_init_task(&udf_task, usb_fill_udf_task, udf,
    		    USB_TASK_TYPE_GENERIC);
    		usb_add_task(sc->sc_bus->root_hub, &udf_task);
    		usb_wait_task(sc->sc_bus->root_hub, &udf_task);
    		len = cdesc_len = udf->udf_size;
    		cdesc = (usb_config_descriptor_t *)udf->udf_data;
    		*udf = save_udf;
    		if (cdesc == NULL)
    			return (EINVAL);
    		if (len > udf->udf_size)
    			len = udf->udf_size;
    		iov.iov_base = (caddr_t)udf->udf_data;
    		iov.iov_len = len;
    		uio.uio_iov = &iov;
    		uio.uio_iovcnt = 1;
    		uio.uio_resid = len;
    		uio.uio_offset = 0;
    		uio.uio_segflg = UIO_USERSPACE;
    		uio.uio_rw = UIO_READ;
    		uio.uio_procp = p;
    		error = uiomove((void *)cdesc, len, &uio);
    		free(cdesc, M_TEMP, cdesc_len);
    		return (error);
    	}
    
    	default:
    		return (EINVAL);
    	}
    	return (0);
    }
    
    /*
     * Explore device tree from the root.  We need mutual exclusion to this
     * hub while traversing the device tree, but this is guaranteed since this
     * function is only called from the task thread, with one exception:
     * usb_attach() calls this function, but there shouldn't be anything else
     * trying to explore this hub at that time.
     */
    void
    usb_explore(void *v)
    {
    	struct usb_softc *sc = v;
    	struct timeval now, waited;
    	int pwrdly, waited_ms;
    
    	DPRINTFN(2,("%s: %s\n", __func__, sc->sc_dev.dv_xname));
    #ifdef USB_DEBUG
    	if (usb_noexplore)
    		return;
    #endif
    
    	if (sc->sc_bus->dying)
    		return;
    
    	if (sc->sc_bus->flags & USB_BUS_CONFIG_PENDING) {
    		/*
    		 * If this is a low/full speed hub and there is a high
    		 * speed hub that hasn't explored yet, reschedule this
    		 * task, allowing the high speed explore task to run.
    		 */
    		if (sc->sc_bus->usbrev < USBREV_2_0 && explore_pending > 0) {
    			usb_add_task(sc->sc_bus->root_hub,
    			    &sc->sc_explore_task);
    			return;
    		}
    
    		/*
    		 * Wait for power to stabilize.
    		 */
    		getmicrouptime(&now);
    		timersub(&now, &sc->sc_ptime, &waited);
    		waited_ms = waited.tv_sec * 1000 + waited.tv_usec / 1000;
    
    		pwrdly = sc->sc_bus->root_hub->hub->powerdelay +
    		    USB_EXTRA_POWER_UP_TIME;
    		if (pwrdly > waited_ms)
    			usb_delay_ms(sc->sc_bus, pwrdly - waited_ms);
    	}
    
    	if (sc->sc_bus->flags & USB_BUS_DISCONNECTING) {
    		/* Prevent new tasks from being scheduled. */
    		sc->sc_bus->dying = 1;
    
    		/* Make all devices disconnect. */
    		if (sc->sc_port.device != NULL) {
    			usbd_detach(sc->sc_port.device, (struct device *)sc);
    			sc->sc_port.device = NULL;
    		}
    
    		sc->sc_bus->flags &= ~USB_BUS_DISCONNECTING;
    	} else {
    		sc->sc_bus->root_hub->hub->explore(sc->sc_bus->root_hub);
    	}
    
    	if (sc->sc_bus->flags & USB_BUS_CONFIG_PENDING) {
    		DPRINTF(("%s: %s: first explore done\n", __func__,
    		    sc->sc_dev.dv_xname));
    		if (sc->sc_bus->usbrev == USBREV_2_0 && explore_pending)
    			explore_pending--;
    		config_pending_decr();
    		sc->sc_bus->flags &= ~(USB_BUS_CONFIG_PENDING);
    	}
    }
    
    void
    usb_needs_explore(struct usbd_device *dev, int first_explore)
    {
    	struct usb_softc *usbctl = (struct usb_softc *)dev->bus->usbctl;
    
    	DPRINTFN(3,("%s: %s\n", usbctl->sc_dev.dv_xname, __func__));
    
    	if (!first_explore && (dev->bus->flags & USB_BUS_CONFIG_PENDING)) {
    		DPRINTF(("%s: %s: not exploring before first explore\n",
    		    __func__, usbctl->sc_dev.dv_xname));
    		return;
    	}
    
    	usb_add_task(dev, &usbctl->sc_explore_task);
    }
    
    void
    usb_needs_reattach(struct usbd_device *dev)
    {
    	DPRINTFN(2,("usb_needs_reattach\n"));
    	dev->powersrc->reattach = 1;
    	usb_needs_explore(dev, 0);
    }
    
    void
    usb_schedsoftintr(struct usbd_bus *bus)
    {
    	DPRINTFN(10,("%s: polling=%d\n", __func__, bus->use_polling));
    
    	/* In case usb(4) is disabled */
    	if (bus->soft == NULL)
    		return;
    
    	if (bus->use_polling) {
    		bus->methods->soft_intr(bus);
    	} else {
    		softintr_schedule(bus->soft);
    	}
    }
    
    int
    usb_activate(struct device *self, int act)
    {
    	struct usb_softc *sc = (struct usb_softc *)self;
    	int rv = 0;
    
    	switch (act) {
    	case DVACT_QUIESCE:
    		if (sc->sc_bus->root_hub != NULL)
    			usb_detach_roothub(sc);
    		break;
    	case DVACT_RESUME:
    		sc->sc_bus->dying = 0;
    		break;
    	case DVACT_WAKEUP:
    		sc->sc_bus->use_polling++;
    		if (!usb_attach_roothub(sc))
    			usb_needs_explore(sc->sc_bus->root_hub, 0);
    		sc->sc_bus->use_polling--;
    		break;
    	default:
    		rv = config_activate_children(self, act);
    		break;
    	}
    	return (rv);
    }
    
    int
    usb_detach(struct device *self, int flags)
    {
    	struct usb_softc *sc = (struct usb_softc *)self;
    
    	if (sc->sc_bus->root_hub != NULL) {
    		usb_detach_roothub(sc);
    
    		if (--usb_nbuses == 0) {
    			usb_run_tasks = usb_run_abort_tasks = 0;
    			wakeup(&usb_run_abort_tasks);
    			wakeup(&usb_run_tasks);
    		}
    	}
    
    	if (sc->sc_bus->soft != NULL) {
    		softintr_disestablish(sc->sc_bus->soft);
    		sc->sc_bus->soft = NULL;
    	}
    
    #if NBPFILTER > 0
    	bpfsdetach(sc->sc_bus->bpfif);
    #endif
    	return (0);
    }
    
    void
    usb_tap(struct usbd_bus *bus, struct usbd_xfer *xfer, uint8_t dir)
    {
    #if NBPFILTER > 0
    	struct usb_softc *sc = (struct usb_softc *)bus->usbctl;
    	usb_endpoint_descriptor_t *ed = xfer->pipe->endpoint->edesc;
    	union {
    		struct usbpcap_ctl_hdr		uch;
    		struct usbpcap_iso_hdr_full	uih;
    	} h;
    	struct usbpcap_pkt_hdr *uph = &h.uch.uch_hdr;
    	uint32_t nframes, psize;
    	unsigned int bpfdir;
    	void *data = NULL;
    	size_t flen;
    	caddr_t bpf;
    	int i;
    
    	bpf = bus->bpf;
    	if (bpf == NULL)
    		return;
    
    	switch (UE_GET_XFERTYPE(ed->bmAttributes)) {
    	case UE_CONTROL:
    		/* Control transfer headers include an extra byte */
    		uph->uph_hlen = htole16(sizeof(struct usbpcap_ctl_hdr));
    		uph->uph_xfertype = USBPCAP_TRANSFER_CONTROL;
    		break;
    	case UE_ISOCHRONOUS:
    		nframes = xfer->nframes;
    		/*
    		 * All our drivers use a fixed size (psize) for
    		 * ISOCHRONOUS packets. Calculate it to determine the
    		 * correct offset below.
    		 */
    		psize = xfer->length / nframes;
    #ifdef DIAGNOSTIC
    		if (nframes > _USBPCAP_MAX_ISOFRAMES) {
    			printf("%s: too many frames: %d > %d\n", __func__,
    			    xfer->nframes, _USBPCAP_MAX_ISOFRAMES);
    			nframes = _USBPCAP_MAX_ISOFRAMES;
    		}
    #endif
    		/* Isochronous transfer headers include space for one frame */
    		flen = (nframes - 1) * sizeof(struct usbpcap_iso_pkt);
    		uph->uph_hlen = htole16(sizeof(struct usbpcap_iso_hdr) + flen);
    		uph->uph_xfertype = USBPCAP_TRANSFER_ISOCHRONOUS;
    		h.uih.uih_startframe = 0; /* not yet used */
    		h.uih.uih_nframes = nframes;
    		h.uih.uih_errors = 0; /* we don't have per-frame error */
    		for (i = 0; i < nframes; i++) {
    			/*
    			 * We can't use length, because IN frame may
    			 * have shorter length of packet whan expected.
    			 */
    			h.uih.uih_frames[i].uip_offset = i * psize;
    			h.uih.uih_frames[i].uip_length = xfer->frlengths[i];
    			/* See above, we don't have per-frame error */
    			h.uih.uih_frames[i].uip_status = 0;
    		}
    		break;
    	case UE_BULK:
    		uph->uph_hlen = htole16(sizeof(*uph));
    		uph->uph_xfertype = USBPCAP_TRANSFER_BULK;
    		break;
    	case UE_INTERRUPT:
    		uph->uph_hlen = htole16(sizeof(*uph));
    		uph->uph_xfertype = USBPCAP_TRANSFER_INTERRUPT;
    		break;
    	default:
    		return;
    	}
    
    	uph->uph_id = 0; /* not yet used */
    	uph->uph_status = htole32(xfer->status);
    	uph->uph_function = 0; /* not yet used */
    	uph->uph_bus = htole32(sc->sc_dev.dv_unit);
    	uph->uph_devaddr = htole16(xfer->device->address);
    	uph->uph_epaddr = ed->bEndpointAddress;
    	uph->uph_info = 0;
    
    	/* Outgoing control requests start with a STAGE dump. */
    	if ((xfer->rqflags & URQ_REQUEST) && (dir == USBTAP_DIR_OUT)) {
    		h.uch.uch_stage = USBPCAP_CONTROL_STAGE_SETUP;
    		uph->uph_dlen = sizeof(usb_device_request_t);
    		bpf_tap_hdr(bpf, uph, uph->uph_hlen, &xfer->request,
    		    uph->uph_dlen, BPF_DIRECTION_OUT);
    	}
    
    	if (dir == USBTAP_DIR_OUT) {
    		bpfdir = BPF_DIRECTION_OUT;
    		if (!usbd_xfer_isread(xfer)) {
    			data = KERNADDR(&xfer->dmabuf, 0);
    			uph->uph_dlen = xfer->length;
    			if (xfer->rqflags & URQ_REQUEST)
    				h.uch.uch_stage = USBPCAP_CONTROL_STAGE_DATA;
    		} else {
    			data = NULL;
    			uph->uph_dlen = 0;
    			if (xfer->rqflags & URQ_REQUEST)
    				h.uch.uch_stage = USBPCAP_CONTROL_STAGE_STATUS;
    		}
    	} else { /* USBTAP_DIR_IN */
    		bpfdir = BPF_DIRECTION_IN;
    		uph->uph_info = USBPCAP_INFO_DIRECTION_IN;
    		if (usbd_xfer_isread(xfer)) {
    			data = KERNADDR(&xfer->dmabuf, 0);
    			uph->uph_dlen = xfer->actlen;
    			if (xfer->rqflags & URQ_REQUEST)
    				h.uch.uch_stage = USBPCAP_CONTROL_STAGE_DATA;
    		} else {
    			data = NULL;
    			uph->uph_dlen = 0;
    			if (xfer->rqflags & URQ_REQUEST)
    				h.uch.uch_stage = USBPCAP_CONTROL_STAGE_STATUS;
    		}
    	}
    
    	/* ISOCHRONOUS IN from device may have gaps, use full buffer */
    	if (bpfdir == BPF_DIRECTION_IN && uph->uph_dlen > 0 &&
    	    uph->uph_xfertype == USBPCAP_TRANSFER_ISOCHRONOUS) {
    		uph->uph_dlen = xfer->length;
    	}
    
    	/* Dump bulk/intr/iso data, ctrl DATA or STATUS stage. */
    	bpf_tap_hdr(bpf, uph, uph->uph_hlen, data, uph->uph_dlen, bpfdir);
    
    	/* Incoming control requests with DATA need a STATUS stage. */
    	if ((xfer->rqflags & URQ_REQUEST) && (dir == USBTAP_DIR_IN) &&
    	    (h.uch.uch_stage == USBPCAP_CONTROL_STAGE_DATA)) {
    		h.uch.uch_stage = USBPCAP_CONTROL_STAGE_STATUS;
    		uph->uph_dlen = 0;
    		bpf_tap_hdr(bpf, uph, uph->uph_hlen, NULL, 0, BPF_DIRECTION_IN);
    	}
    #endif
    }