/* $OpenBSD: usb.c,v 1.130 2022/07/02 08:50:42 visa 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 wont 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; /* * Make sure the root hub is present before interrupts * get enabled. As long as the bus is in polling mode * it is safe to call usbd_new_device() now since root * hub transfers do not need to sleep. */ 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, offset; 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: offset = 0; nframes = xfer->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++) { h.uih.uih_frames[i].uip_offset = offset; 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; offset += xfer->frlengths[i]; } 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; } } /* 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 }