diff --git a/sys/dev/usb/usb_dev.c b/sys/dev/usb/usb_dev.c index 84446edc3ecd..5f06b1b9690e 100644 --- a/sys/dev/usb/usb_dev.c +++ b/sys/dev/usb/usb_dev.c @@ -1,2470 +1,2472 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2006-2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2006-2023 Hans Petter Selasky * * 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 AUTHOR 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 AUTHOR 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_dev.c - An abstraction layer for creating devices under /dev/... */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #ifdef COMPAT_FREEBSD32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_fifo_debug #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #if USB_HAVE_UGEN #ifdef USB_DEBUG static int usb_fifo_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB device"); SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_fifo_debug, 0, "Debug Level"); #endif #define USB_UCRED struct ucred *ucred, /* prototypes */ static int usb_fifo_open(struct usb_cdev_privdata *, struct usb_fifo *, int); static void usb_fifo_close(struct usb_fifo *, int); static void usb_dev_init(void *); static void usb_dev_init_post(void *); static void usb_dev_uninit(void *); static int usb_fifo_uiomove(struct usb_fifo *, void *, int, struct uio *); static void usb_fifo_check_methods(struct usb_fifo_methods *); static struct usb_fifo *usb_fifo_alloc(struct mtx *); static struct usb_endpoint *usb_dev_get_ep(struct usb_device *, uint8_t, uint8_t); static void usb_loc_fill(struct usb_fs_privdata *, struct usb_cdev_privdata *); static void usb_close(void *); static usb_error_t usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *, int); static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); static void usb_unref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); static d_open_t usb_open; static d_ioctl_t usb_ioctl; static d_read_t usb_read; static d_write_t usb_write; static d_poll_t usb_poll; static d_kqfilter_t usb_kqfilter; static d_ioctl_t usb_static_ioctl; static usb_fifo_open_t usb_fifo_dummy_open; static usb_fifo_close_t usb_fifo_dummy_close; static usb_fifo_ioctl_t usb_fifo_dummy_ioctl; static usb_fifo_cmd_t usb_fifo_dummy_cmd; /* character device structure used for devices (/dev/ugenX.Y and /dev/uXXX) */ struct cdevsw usb_devsw = { .d_version = D_VERSION, .d_open = usb_open, .d_ioctl = usb_ioctl, .d_name = "usbdev", .d_flags = D_TRACKCLOSE, .d_read = usb_read, .d_write = usb_write, .d_poll = usb_poll, .d_kqfilter = usb_kqfilter, }; static struct cdev* usb_dev = NULL; /* character device structure used for /dev/usb */ static struct cdevsw usb_static_devsw = { .d_version = D_VERSION, .d_ioctl = usb_static_ioctl, .d_name = "usb" }; static TAILQ_HEAD(, usb_symlink) usb_sym_head; static struct sx usb_sym_lock; struct mtx usb_ref_lock; /*------------------------------------------------------------------------* * usb_loc_fill * * This is used to fill out a usb_cdev_privdata structure based on the * device's address as contained in usb_fs_privdata. *------------------------------------------------------------------------*/ static void usb_loc_fill(struct usb_fs_privdata* pd, struct usb_cdev_privdata *cpd) { cpd->bus_index = pd->bus_index; cpd->dev_index = pd->dev_index; cpd->ep_addr = pd->ep_addr; cpd->fifo_index = pd->fifo_index; } /*------------------------------------------------------------------------* * usb_ref_device * * This function is used to atomically refer an USB device by its * device location. If this function returns success the USB device * will not disappear until the USB device is unreferenced. * * Return values: * 0: Success, refcount incremented on the given USB device. * Else: Failure. *------------------------------------------------------------------------*/ static usb_error_t usb_ref_device(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd, int need_uref) { struct usb_fifo **ppf; struct usb_fifo *f; DPRINTFN(2, "cpd=%p need uref=%d\n", cpd, need_uref); /* clear all refs */ memset(crd, 0, sizeof(*crd)); mtx_lock(&usb_ref_lock); cpd->bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index); if (cpd->bus == NULL) { DPRINTFN(2, "no bus at %u\n", cpd->bus_index); goto error; } cpd->udev = cpd->bus->devices[cpd->dev_index]; if (cpd->udev == NULL) { DPRINTFN(2, "no device at %u\n", cpd->dev_index); goto error; } if (cpd->udev->state == USB_STATE_DETACHED && (need_uref != 2)) { DPRINTFN(2, "device is detached\n"); goto error; } if (need_uref) { DPRINTFN(2, "ref udev - needed\n"); if (cpd->udev->refcount == USB_DEV_REF_MAX) { DPRINTFN(2, "no dev ref\n"); goto error; } cpd->udev->refcount++; mtx_unlock(&usb_ref_lock); /* * We need to grab the enumeration SX-lock before * grabbing the FIFO refs to avoid deadlock at detach! */ crd->do_unlock = usbd_enum_lock_sig(cpd->udev); mtx_lock(&usb_ref_lock); /* * Set "is_uref" after grabbing the default SX lock */ crd->is_uref = 1; /* check for signal */ if (crd->do_unlock > 1) { crd->do_unlock = 0; goto error; } } /* check if we are doing an open */ if (cpd->fflags == 0) { /* use zero defaults */ } else { /* check for write */ if (cpd->fflags & FWRITE) { ppf = cpd->udev->fifo; f = ppf[cpd->fifo_index + USB_FIFO_TX]; crd->txfifo = f; crd->is_write = 1; /* ref */ if (f == NULL || f->refcount == USB_FIFO_REF_MAX) goto error; if (f->curr_cpd != cpd) goto error; /* check if USB-FS is active */ if (f->fs_ep_max != 0) { crd->is_usbfs = 1; } } /* check for read */ if (cpd->fflags & FREAD) { ppf = cpd->udev->fifo; f = ppf[cpd->fifo_index + USB_FIFO_RX]; crd->rxfifo = f; crd->is_read = 1; /* ref */ if (f == NULL || f->refcount == USB_FIFO_REF_MAX) goto error; if (f->curr_cpd != cpd) goto error; /* check if USB-FS is active */ if (f->fs_ep_max != 0) { crd->is_usbfs = 1; } } } /* when everything is OK we increment the refcounts */ if (crd->is_write) { DPRINTFN(2, "ref write\n"); crd->txfifo->refcount++; } if (crd->is_read) { DPRINTFN(2, "ref read\n"); crd->rxfifo->refcount++; } mtx_unlock(&usb_ref_lock); return (0); error: if (crd->do_unlock) usbd_enum_unlock(cpd->udev); if (crd->is_uref) { if (--(cpd->udev->refcount) == 0) cv_broadcast(&cpd->udev->ref_cv); } mtx_unlock(&usb_ref_lock); DPRINTFN(2, "fail\n"); /* clear all refs */ memset(crd, 0, sizeof(*crd)); return (USB_ERR_INVAL); } /*------------------------------------------------------------------------* * usb_usb_ref_device * * This function is used to upgrade an USB reference to include the * USB device reference on a USB location. * * Return values: * 0: Success, refcount incremented on the given USB device. * Else: Failure. *------------------------------------------------------------------------*/ static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd) { /* * Check if we already got an USB reference on this location: */ if (crd->is_uref) return (0); /* success */ /* * To avoid deadlock at detach we need to drop the FIFO ref * and re-acquire a new ref! */ usb_unref_device(cpd, crd); return (usb_ref_device(cpd, crd, 1 /* need uref */)); } /*------------------------------------------------------------------------* * usb_unref_device * * This function will release the reference count by one unit for the * given USB device. *------------------------------------------------------------------------*/ static void usb_unref_device(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd) { DPRINTFN(2, "cpd=%p is_uref=%d\n", cpd, crd->is_uref); if (crd->do_unlock) usbd_enum_unlock(cpd->udev); mtx_lock(&usb_ref_lock); if (crd->is_read) { if (--(crd->rxfifo->refcount) == 0) { cv_signal(&crd->rxfifo->cv_drain); } crd->is_read = 0; } if (crd->is_write) { if (--(crd->txfifo->refcount) == 0) { cv_signal(&crd->txfifo->cv_drain); } crd->is_write = 0; } if (crd->is_uref) { crd->is_uref = 0; if (--(cpd->udev->refcount) == 0) cv_broadcast(&cpd->udev->ref_cv); } mtx_unlock(&usb_ref_lock); } static struct usb_fifo * usb_fifo_alloc(struct mtx *mtx) { struct usb_fifo *f; f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); cv_init(&f->cv_io, "FIFO-IO"); cv_init(&f->cv_drain, "FIFO-DRAIN"); + sx_init(&f->fs_fastpath_lock, "FIFO-FP"); f->priv_mtx = mtx; f->refcount = 1; knlist_init_mtx(&f->selinfo.si_note, mtx); return (f); } /*------------------------------------------------------------------------* * usb_fifo_create *------------------------------------------------------------------------*/ static int usb_fifo_create(struct usb_cdev_privdata *cpd, struct usb_cdev_refdata *crd) { struct usb_device *udev = cpd->udev; struct usb_fifo *f; struct usb_endpoint *ep; uint8_t n; uint8_t is_tx; uint8_t is_rx; uint8_t no_null; uint8_t is_busy; int e = cpd->ep_addr; is_tx = (cpd->fflags & FWRITE) ? 1 : 0; is_rx = (cpd->fflags & FREAD) ? 1 : 0; no_null = 1; is_busy = 0; /* Preallocated FIFO */ if (e < 0) { DPRINTFN(5, "Preallocated FIFO\n"); if (is_tx) { f = udev->fifo[cpd->fifo_index + USB_FIFO_TX]; if (f == NULL) return (EINVAL); crd->txfifo = f; } if (is_rx) { f = udev->fifo[cpd->fifo_index + USB_FIFO_RX]; if (f == NULL) return (EINVAL); crd->rxfifo = f; } return (0); } KASSERT(e >= 0 && e <= 15, ("endpoint %d out of range", e)); /* search for a free FIFO slot */ DPRINTFN(5, "Endpoint device, searching for 0x%02x\n", e); for (n = 0;; n += 2) { if (n == USB_FIFO_MAX) { if (no_null) { no_null = 0; n = 0; } else { /* end of FIFOs reached */ DPRINTFN(5, "out of FIFOs\n"); return (ENOMEM); } } /* Check for TX FIFO */ if (is_tx) { f = udev->fifo[n + USB_FIFO_TX]; if (f != NULL) { if (f->dev_ep_index != e) { /* wrong endpoint index */ continue; } if (f->curr_cpd != NULL) { /* FIFO is opened */ is_busy = 1; continue; } } else if (no_null) { continue; } } /* Check for RX FIFO */ if (is_rx) { f = udev->fifo[n + USB_FIFO_RX]; if (f != NULL) { if (f->dev_ep_index != e) { /* wrong endpoint index */ continue; } if (f->curr_cpd != NULL) { /* FIFO is opened */ is_busy = 1; continue; } } else if (no_null) { continue; } } break; } if (no_null == 0) { if (e >= (USB_EP_MAX / 2)) { /* we don't create any endpoints in this range */ DPRINTFN(5, "ep out of range\n"); return (is_busy ? EBUSY : EINVAL); } } if ((e != 0) && is_busy) { /* * Only the default control endpoint is allowed to be * opened multiple times! */ DPRINTFN(5, "busy\n"); return (EBUSY); } /* Check TX FIFO */ if (is_tx && (udev->fifo[n + USB_FIFO_TX] == NULL)) { ep = usb_dev_get_ep(udev, e, USB_FIFO_TX); DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_TX); if (ep == NULL) { DPRINTFN(5, "dev_get_endpoint returned NULL\n"); return (EINVAL); } f = usb_fifo_alloc(&udev->device_mtx); if (f == NULL) { DPRINTFN(5, "could not alloc tx fifo\n"); return (ENOMEM); } /* update some fields */ f->fifo_index = n + USB_FIFO_TX; f->dev_ep_index = e; f->priv_sc0 = ep; f->methods = &usb_ugen_methods; f->iface_index = ep->iface_index; f->udev = udev; mtx_lock(&usb_ref_lock); udev->fifo[n + USB_FIFO_TX] = f; mtx_unlock(&usb_ref_lock); } /* Check RX FIFO */ if (is_rx && (udev->fifo[n + USB_FIFO_RX] == NULL)) { ep = usb_dev_get_ep(udev, e, USB_FIFO_RX); DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_RX); if (ep == NULL) { DPRINTFN(5, "dev_get_endpoint returned NULL\n"); return (EINVAL); } f = usb_fifo_alloc(&udev->device_mtx); if (f == NULL) { DPRINTFN(5, "could not alloc rx fifo\n"); return (ENOMEM); } /* update some fields */ f->fifo_index = n + USB_FIFO_RX; f->dev_ep_index = e; f->priv_sc0 = ep; f->methods = &usb_ugen_methods; f->iface_index = ep->iface_index; f->udev = udev; mtx_lock(&usb_ref_lock); udev->fifo[n + USB_FIFO_RX] = f; mtx_unlock(&usb_ref_lock); } if (is_tx) { crd->txfifo = udev->fifo[n + USB_FIFO_TX]; } if (is_rx) { crd->rxfifo = udev->fifo[n + USB_FIFO_RX]; } /* fill out fifo index */ DPRINTFN(5, "fifo index = %d\n", n); cpd->fifo_index = n; /* complete */ return (0); } void usb_fifo_free(struct usb_fifo *f) { uint8_t n; if (f == NULL) { /* be NULL safe */ return; } /* destroy symlink devices, if any */ for (n = 0; n != 2; n++) { if (f->symlink[n]) { usb_free_symlink(f->symlink[n]); f->symlink[n] = NULL; } } mtx_lock(&usb_ref_lock); /* delink ourselves to stop calls from userland */ if ((f->fifo_index < USB_FIFO_MAX) && (f->udev != NULL) && (f->udev->fifo[f->fifo_index] == f)) { f->udev->fifo[f->fifo_index] = NULL; } else { DPRINTFN(0, "USB FIFO %p has not been linked\n", f); } /* decrease refcount */ f->refcount--; /* need to wait until all callers have exited */ while (f->refcount != 0) { mtx_unlock(&usb_ref_lock); /* avoid LOR */ mtx_lock(f->priv_mtx); /* prevent write flush, if any */ f->flag_iserror = 1; /* get I/O thread out of any sleep state */ if (f->flag_sleeping) { f->flag_sleeping = 0; cv_broadcast(&f->cv_io); } mtx_unlock(f->priv_mtx); mtx_lock(&usb_ref_lock); /* * Check if the "f->refcount" variable reached zero * during the unlocked time before entering wait: */ if (f->refcount == 0) break; /* wait for sync */ cv_wait(&f->cv_drain, &usb_ref_lock); } mtx_unlock(&usb_ref_lock); /* take care of closing the device here, if any */ usb_fifo_close(f, 0); cv_destroy(&f->cv_io); cv_destroy(&f->cv_drain); + sx_destroy(&f->fs_fastpath_lock); knlist_clear(&f->selinfo.si_note, 0); seldrain(&f->selinfo); knlist_destroy(&f->selinfo.si_note); free(f, M_USBDEV); } static struct usb_endpoint * usb_dev_get_ep(struct usb_device *udev, uint8_t ep_index, uint8_t dir) { struct usb_endpoint *ep; uint8_t ep_dir; if (ep_index == 0) { ep = &udev->ctrl_ep; } else { if (dir == USB_FIFO_RX) { if (udev->flags.usb_mode == USB_MODE_HOST) { ep_dir = UE_DIR_IN; } else { ep_dir = UE_DIR_OUT; } } else { if (udev->flags.usb_mode == USB_MODE_HOST) { ep_dir = UE_DIR_OUT; } else { ep_dir = UE_DIR_IN; } } ep = usbd_get_ep_by_addr(udev, ep_index | ep_dir); } if (ep == NULL) { /* if the endpoint does not exist then return */ return (NULL); } if (ep->edesc == NULL) { /* invalid endpoint */ return (NULL); } return (ep); /* success */ } /*------------------------------------------------------------------------* * usb_fifo_open * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int usb_fifo_open(struct usb_cdev_privdata *cpd, struct usb_fifo *f, int fflags) { int err; if (f == NULL) { /* no FIFO there */ DPRINTFN(2, "no FIFO\n"); return (ENXIO); } /* remove FWRITE and FREAD flags */ fflags &= ~(FWRITE | FREAD); /* set correct file flags */ if ((f->fifo_index & 1) == USB_FIFO_TX) { fflags |= FWRITE; } else { fflags |= FREAD; } /* check if we are already opened */ /* we don't need any locks when checking this variable */ if (f->curr_cpd != NULL) { err = EBUSY; goto done; } /* reset short flag before open */ f->flag_short = 0; /* call open method */ err = (f->methods->f_open) (f, fflags); if (err) { goto done; } mtx_lock(f->priv_mtx); /* reset sleep flag */ f->flag_sleeping = 0; /* reset error flag */ f->flag_iserror = 0; /* reset complete flag */ f->flag_iscomplete = 0; /* reset select flag */ f->flag_isselect = 0; /* reset flushing flag */ f->flag_flushing = 0; /* reset ASYNC proc flag */ f->async_p = NULL; mtx_lock(&usb_ref_lock); /* flag the fifo as opened to prevent others */ f->curr_cpd = cpd; mtx_unlock(&usb_ref_lock); /* reset queue */ usb_fifo_reset(f); mtx_unlock(f->priv_mtx); done: return (err); } /*------------------------------------------------------------------------* * usb_fifo_reset *------------------------------------------------------------------------*/ void usb_fifo_reset(struct usb_fifo *f) { struct usb_mbuf *m; if (f == NULL) { return; } while (1) { USB_IF_DEQUEUE(&f->used_q, m); if (m) { USB_IF_ENQUEUE(&f->free_q, m); } else { break; } } /* reset have fragment flag */ f->flag_have_fragment = 0; } /*------------------------------------------------------------------------* * usb_fifo_close *------------------------------------------------------------------------*/ static void usb_fifo_close(struct usb_fifo *f, int fflags) { int err; /* check if we are not opened */ if (f->curr_cpd == NULL) { /* nothing to do - already closed */ return; } mtx_lock(f->priv_mtx); /* clear current cdev private data pointer */ mtx_lock(&usb_ref_lock); f->curr_cpd = NULL; mtx_unlock(&usb_ref_lock); /* check if we are watched by kevent */ KNOTE_LOCKED(&f->selinfo.si_note, 0); /* check if we are selected */ if (f->flag_isselect) { selwakeup(&f->selinfo); f->flag_isselect = 0; } /* check if a thread wants SIGIO */ if (f->async_p != NULL) { PROC_LOCK(f->async_p); kern_psignal(f->async_p, SIGIO); PROC_UNLOCK(f->async_p); f->async_p = NULL; } /* remove FWRITE and FREAD flags */ fflags &= ~(FWRITE | FREAD); /* flush written data, if any */ if ((f->fifo_index & 1) == USB_FIFO_TX) { if (!f->flag_iserror) { /* set flushing flag */ f->flag_flushing = 1; /* get the last packet in */ if (f->flag_have_fragment) { struct usb_mbuf *m; f->flag_have_fragment = 0; USB_IF_DEQUEUE(&f->free_q, m); if (m) { USB_IF_ENQUEUE(&f->used_q, m); } } /* start write transfer, if not already started */ (f->methods->f_start_write) (f); /* check if flushed already */ while (f->flag_flushing && (!f->flag_iserror)) { /* wait until all data has been written */ f->flag_sleeping = 1; err = cv_timedwait_sig(&f->cv_io, f->priv_mtx, USB_MS_TO_TICKS(USB_DEFAULT_TIMEOUT)); if (err) { DPRINTF("signal received\n"); break; } } } fflags |= FWRITE; /* stop write transfer, if not already stopped */ (f->methods->f_stop_write) (f); } else { fflags |= FREAD; /* stop write transfer, if not already stopped */ (f->methods->f_stop_read) (f); } /* check if we are sleeping */ if (f->flag_sleeping) { DPRINTFN(2, "Sleeping at close!\n"); } mtx_unlock(f->priv_mtx); /* call close method */ (f->methods->f_close) (f, fflags); DPRINTF("closed\n"); } /*------------------------------------------------------------------------* * usb_open - cdev callback *------------------------------------------------------------------------*/ static int usb_open(struct cdev *dev, int fflags, int devtype, struct thread *td) { struct usb_fs_privdata* pd = (struct usb_fs_privdata*)dev->si_drv1; struct usb_cdev_refdata refs; struct usb_cdev_privdata *cpd; int err; DPRINTFN(2, "%s fflags=0x%08x\n", devtoname(dev), fflags); KASSERT(fflags & (FREAD|FWRITE), ("invalid open flags")); if (((fflags & FREAD) && !(pd->mode & FREAD)) || ((fflags & FWRITE) && !(pd->mode & FWRITE))) { DPRINTFN(2, "access mode not supported\n"); return (EPERM); } cpd = malloc(sizeof(*cpd), M_USBDEV, M_WAITOK | M_ZERO); usb_loc_fill(pd, cpd); err = usb_ref_device(cpd, &refs, 1); if (err) { DPRINTFN(2, "cannot ref device\n"); free(cpd, M_USBDEV); return (ENXIO); } cpd->fflags = fflags; /* access mode for open lifetime */ /* create FIFOs, if any */ err = usb_fifo_create(cpd, &refs); /* check for error */ if (err) { DPRINTFN(2, "cannot create fifo\n"); usb_unref_device(cpd, &refs); free(cpd, M_USBDEV); return (err); } if (fflags & FREAD) { err = usb_fifo_open(cpd, refs.rxfifo, fflags); if (err) { DPRINTFN(2, "read open failed\n"); usb_unref_device(cpd, &refs); free(cpd, M_USBDEV); return (err); } } if (fflags & FWRITE) { err = usb_fifo_open(cpd, refs.txfifo, fflags); if (err) { DPRINTFN(2, "write open failed\n"); if (fflags & FREAD) { usb_fifo_close(refs.rxfifo, fflags); } usb_unref_device(cpd, &refs); free(cpd, M_USBDEV); return (err); } } usb_unref_device(cpd, &refs); devfs_set_cdevpriv(cpd, usb_close); return (0); } /*------------------------------------------------------------------------* * usb_close - cdev callback *------------------------------------------------------------------------*/ static void usb_close(void *arg) { struct usb_cdev_refdata refs; struct usb_cdev_privdata *cpd = arg; int err; DPRINTFN(2, "cpd=%p\n", cpd); err = usb_ref_device(cpd, &refs, 2 /* uref and allow detached state */); if (err) { DPRINTFN(2, "Cannot grab USB reference when " "closing USB file handle\n"); goto done; } if (cpd->fflags & FREAD) { usb_fifo_close(refs.rxfifo, cpd->fflags); } if (cpd->fflags & FWRITE) { usb_fifo_close(refs.txfifo, cpd->fflags); } usb_unref_device(cpd, &refs); done: free(cpd, M_USBDEV); } static void usb_dev_init(void *arg) { mtx_init(&usb_ref_lock, "USB ref mutex", NULL, MTX_DEF); sx_init(&usb_sym_lock, "USB sym mutex"); TAILQ_INIT(&usb_sym_head); /* check the UGEN methods */ usb_fifo_check_methods(&usb_ugen_methods); } SYSINIT(usb_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb_dev_init, NULL); static void usb_dev_init_post(void *arg) { /* * Create /dev/usb - this is needed for usbconfig(8), which * needs a well-known device name to access. */ usb_dev = make_dev(&usb_static_devsw, 0, UID_ROOT, GID_OPERATOR, 0644, USB_DEVICE_NAME); if (usb_dev == NULL) { DPRINTFN(0, "Could not create usb bus device\n"); } } SYSINIT(usb_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb_dev_init_post, NULL); static void usb_dev_uninit(void *arg) { if (usb_dev != NULL) { destroy_dev(usb_dev); usb_dev = NULL; } mtx_destroy(&usb_ref_lock); sx_destroy(&usb_sym_lock); } SYSUNINIT(usb_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb_dev_uninit, NULL); static int usb_ioctl_f_sub(struct usb_fifo *f, u_long cmd, void *addr, struct thread *td) { int error = 0; switch (cmd) { case FIODTYPE: *(int *)addr = 0; /* character device */ break; case FIONBIO: /* handled by upper FS layer */ break; case FIOASYNC: if (*(int *)addr) { if (f->async_p != NULL) { error = EBUSY; break; } f->async_p = USB_TD_GET_PROC(td); } else { f->async_p = NULL; } break; /* XXX this is not the most general solution */ case TIOCSPGRP: if (f->async_p == NULL) { error = EINVAL; break; } if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) { error = EPERM; break; } break; default: return (ENOIOCTL); } DPRINTFN(3, "cmd 0x%lx = %d\n", cmd, error); return (error); } /*------------------------------------------------------------------------* * usb_ioctl - cdev callback *------------------------------------------------------------------------*/ static int usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread* td) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; int fflags; int err; DPRINTFN(2, "cmd=0x%lx\n", cmd); err = devfs_get_cdevpriv((void **)&cpd); if (err != 0) return (err); /* * Performance optimisation: We try to check for IOCTL's that * don't need the USB reference first. Then we grab the USB * reference if we need it! */ err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); if (err) return (ENXIO); fflags = cpd->fflags; f = NULL; /* set default value */ err = ENOIOCTL; /* set default value */ if (fflags & FWRITE) { f = refs.txfifo; err = usb_ioctl_f_sub(f, cmd, addr, td); } if (fflags & FREAD) { f = refs.rxfifo; err = usb_ioctl_f_sub(f, cmd, addr, td); } KASSERT(f != NULL, ("fifo not found")); if (err != ENOIOCTL) goto done; err = (f->methods->f_ioctl) (f, cmd, addr, fflags); DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err); if (err != ENOIOCTL) goto done; if (usb_usb_ref_device(cpd, &refs)) { /* we lost the reference */ return (ENXIO); } err = (f->methods->f_ioctl_post) (f, cmd, addr, fflags); DPRINTFN(2, "f_ioctl_post cmd 0x%lx = %d\n", cmd, err); if (err == ENOIOCTL) err = ENOTTY; if (err) goto done; /* Wait for re-enumeration, if any */ while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) { usb_unref_device(cpd, &refs); usb_pause_mtx(NULL, hz / 128); while (usb_ref_device(cpd, &refs, 1 /* need uref */)) { if (usb_ref_device(cpd, &refs, 0)) { /* device no longer exists */ return (ENXIO); } usb_unref_device(cpd, &refs); usb_pause_mtx(NULL, hz / 128); } } done: usb_unref_device(cpd, &refs); return (err); } static void usb_filter_detach(struct knote *kn) { struct usb_fifo *f = kn->kn_hook; knlist_remove(&f->selinfo.si_note, kn, 0); } static int usb_filter_write(struct knote *kn, long hint) { struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; DPRINTFN(2, "\n"); f = kn->kn_hook; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); cpd = f->curr_cpd; if (cpd == NULL) { m = (void *)1; } else if (f->fs_ep_max == 0) { if (f->flag_iserror) { /* we got an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start write transfer, if not * already started */ (f->methods->f_start_write) (f); } /* check if any packets are available */ USB_IF_POLL(&f->free_q, m); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } return (m ? 1 : 0); } static int usb_filter_read(struct knote *kn, long hint) { struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; DPRINTFN(2, "\n"); f = kn->kn_hook; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); cpd = f->curr_cpd; if (cpd == NULL) { m = (void *)1; } else if (f->fs_ep_max == 0) { if (f->flag_iserror) { /* we have an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start read transfer, if not * already started */ (f->methods->f_start_read) (f); } /* check if any packets are available */ USB_IF_POLL(&f->used_q, m); /* start reading data, if any */ if (m == NULL) (f->methods->f_start_read) (f); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } return (m ? 1 : 0); } static struct filterops usb_filtops_write = { .f_isfd = 1, .f_detach = usb_filter_detach, .f_event = usb_filter_write, }; static struct filterops usb_filtops_read = { .f_isfd = 1, .f_detach = usb_filter_detach, .f_event = usb_filter_read, }; /* ARGSUSED */ static int usb_kqfilter(struct cdev* dev, struct knote *kn) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; int fflags; int err = EINVAL; DPRINTFN(2, "\n"); if (devfs_get_cdevpriv((void **)&cpd) != 0 || usb_ref_device(cpd, &refs, 0) != 0) return (ENXIO); fflags = cpd->fflags; /* Figure out who needs service */ switch (kn->kn_filter) { case EVFILT_WRITE: if (fflags & FWRITE) { f = refs.txfifo; kn->kn_fop = &usb_filtops_write; err = 0; } break; case EVFILT_READ: if (fflags & FREAD) { f = refs.rxfifo; kn->kn_fop = &usb_filtops_read; err = 0; } break; default: err = EOPNOTSUPP; break; } if (err == 0) { kn->kn_hook = f; mtx_lock(f->priv_mtx); knlist_add(&f->selinfo.si_note, kn, 1); mtx_unlock(f->priv_mtx); } usb_unref_device(cpd, &refs); return (err); } /* ARGSUSED */ static int usb_poll(struct cdev* dev, int events, struct thread* td) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; int fflags, revents; if (devfs_get_cdevpriv((void **)&cpd) != 0 || usb_ref_device(cpd, &refs, 0) != 0) return (events & (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); fflags = cpd->fflags; /* Figure out who needs service */ revents = 0; if ((events & (POLLOUT | POLLWRNORM)) && (fflags & FWRITE)) { f = refs.txfifo; mtx_lock(f->priv_mtx); if (!refs.is_usbfs) { if (f->flag_iserror) { /* we got an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start write transfer, if not * already started */ (f->methods->f_start_write) (f); } /* check if any packets are available */ USB_IF_POLL(&f->free_q, m); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } if (m) { revents |= events & (POLLOUT | POLLWRNORM); } else { f->flag_isselect = 1; selrecord(td, &f->selinfo); } mtx_unlock(f->priv_mtx); } if ((events & (POLLIN | POLLRDNORM)) && (fflags & FREAD)) { f = refs.rxfifo; mtx_lock(f->priv_mtx); if (!refs.is_usbfs) { if (f->flag_iserror) { /* we have an error */ m = (void *)1; } else { if (f->queue_data == NULL) { /* * start read transfer, if not * already started */ (f->methods->f_start_read) (f); } /* check if any packets are available */ USB_IF_POLL(&f->used_q, m); } } else { if (f->flag_iscomplete) { m = (void *)1; } else { m = NULL; } } if (m) { revents |= events & (POLLIN | POLLRDNORM); } else { f->flag_isselect = 1; selrecord(td, &f->selinfo); if (!refs.is_usbfs) { /* start reading data */ (f->methods->f_start_read) (f); } } mtx_unlock(f->priv_mtx); } usb_unref_device(cpd, &refs); return (revents); } static int usb_read(struct cdev *dev, struct uio *uio, int ioflag) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; int io_len; int err; uint8_t tr_data = 0; err = devfs_get_cdevpriv((void **)&cpd); if (err != 0) return (err); err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); if (err) return (ENXIO); f = refs.rxfifo; if (f == NULL) { /* should not happen */ usb_unref_device(cpd, &refs); return (EPERM); } mtx_lock(f->priv_mtx); /* check for permanent read error */ if (f->flag_iserror) { err = EIO; goto done; } /* check if USB-FS interface is active */ if (refs.is_usbfs) { /* * The queue is used for events that should be * retrieved using the "USB_FS_COMPLETE" ioctl. */ err = EINVAL; goto done; } while (uio->uio_resid > 0) { USB_IF_DEQUEUE(&f->used_q, m); if (m == NULL) { /* start read transfer, if not already started */ (f->methods->f_start_read) (f); if (ioflag & IO_NDELAY) { if (tr_data) { /* return length before error */ break; } err = EWOULDBLOCK; break; } DPRINTF("sleeping\n"); err = usb_fifo_wait(f); if (err) { break; } continue; } if (f->methods->f_filter_read) { /* * Sometimes it is convenient to process data at the * expense of a userland process instead of a kernel * process. */ (f->methods->f_filter_read) (f, m); } tr_data = 1; io_len = MIN(m->cur_data_len, uio->uio_resid); DPRINTFN(2, "transfer %d bytes from %p\n", io_len, m->cur_data_ptr); err = usb_fifo_uiomove(f, m->cur_data_ptr, io_len, uio); m->cur_data_len -= io_len; m->cur_data_ptr += io_len; if (m->cur_data_len == 0) { uint8_t last_packet; last_packet = m->last_packet; USB_IF_ENQUEUE(&f->free_q, m); if (last_packet) { /* keep framing */ break; } } else { USB_IF_PREPEND(&f->used_q, m); } if (err) { break; } } done: mtx_unlock(f->priv_mtx); usb_unref_device(cpd, &refs); return (err); } static int usb_write(struct cdev *dev, struct uio *uio, int ioflag) { struct usb_cdev_refdata refs; struct usb_cdev_privdata* cpd; struct usb_fifo *f; struct usb_mbuf *m; uint8_t *pdata; int io_len; int err; uint8_t tr_data = 0; DPRINTFN(2, "\n"); err = devfs_get_cdevpriv((void **)&cpd); if (err != 0) return (err); err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); if (err) return (ENXIO); f = refs.txfifo; if (f == NULL) { /* should not happen */ usb_unref_device(cpd, &refs); return (EPERM); } mtx_lock(f->priv_mtx); /* check for permanent write error */ if (f->flag_iserror) { err = EIO; goto done; } /* check if USB-FS interface is active */ if (refs.is_usbfs) { /* * The queue is used for events that should be * retrieved using the "USB_FS_COMPLETE" ioctl. */ err = EINVAL; goto done; } if (f->queue_data == NULL) { /* start write transfer, if not already started */ (f->methods->f_start_write) (f); } /* we allow writing zero length data */ do { USB_IF_DEQUEUE(&f->free_q, m); if (m == NULL) { if (ioflag & IO_NDELAY) { if (tr_data) { /* return length before error */ break; } err = EWOULDBLOCK; break; } DPRINTF("sleeping\n"); err = usb_fifo_wait(f); if (err) { break; } continue; } tr_data = 1; if (f->flag_have_fragment == 0) { USB_MBUF_RESET(m); io_len = m->cur_data_len; pdata = m->cur_data_ptr; if (io_len > uio->uio_resid) io_len = uio->uio_resid; m->cur_data_len = io_len; } else { io_len = m->max_data_len - m->cur_data_len; pdata = m->cur_data_ptr + m->cur_data_len; if (io_len > uio->uio_resid) io_len = uio->uio_resid; m->cur_data_len += io_len; } DPRINTFN(2, "transfer %d bytes to %p\n", io_len, pdata); err = usb_fifo_uiomove(f, pdata, io_len, uio); if (err) { f->flag_have_fragment = 0; USB_IF_ENQUEUE(&f->free_q, m); break; } /* check if the buffer is ready to be transmitted */ if ((f->flag_write_defrag == 0) || (m->cur_data_len == m->max_data_len)) { f->flag_have_fragment = 0; /* * Check for write filter: * * Sometimes it is convenient to process data * at the expense of a userland process * instead of a kernel process. */ if (f->methods->f_filter_write) { (f->methods->f_filter_write) (f, m); } /* Put USB mbuf in the used queue */ USB_IF_ENQUEUE(&f->used_q, m); /* Start writing data, if not already started */ (f->methods->f_start_write) (f); } else { /* Wait for more data or close */ f->flag_have_fragment = 1; USB_IF_PREPEND(&f->free_q, m); } } while (uio->uio_resid > 0); done: mtx_unlock(f->priv_mtx); usb_unref_device(cpd, &refs); return (err); } int usb_static_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { union { struct usb_read_dir *urd; #ifdef COMPAT_FREEBSD32 struct usb_read_dir32 *urd32; #endif void* data; } u; int err; u.data = data; switch (cmd) { case USB_READ_DIR: err = usb_read_symlink(u.urd->urd_data, u.urd->urd_startentry, u.urd->urd_maxlen); break; #ifdef COMPAT_FREEBSD32 case USB_READ_DIR32: err = usb_read_symlink(PTRIN(u.urd32->urd_data), u.urd32->urd_startentry, u.urd32->urd_maxlen); break; #endif case USB_DEV_QUIRK_GET: case USB_QUIRK_NAME_GET: case USB_DEV_QUIRK_ADD: case USB_DEV_QUIRK_REMOVE: err = usb_quirk_ioctl_p(cmd, data, fflag, td); break; case USB_GET_TEMPLATE: *(int *)data = usb_template; err = 0; break; case USB_SET_TEMPLATE: err = priv_check(curthread, PRIV_DRIVER); if (err) break; usb_template = *(int *)data; break; default: err = ENOTTY; break; } return (err); } static int usb_fifo_uiomove(struct usb_fifo *f, void *cp, int n, struct uio *uio) { int error; mtx_unlock(f->priv_mtx); /* * "uiomove()" can sleep so one needs to make a wrapper, * exiting the mutex and checking things: */ error = uiomove(cp, n, uio); mtx_lock(f->priv_mtx); return (error); } int usb_fifo_wait(struct usb_fifo *f) { int err; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); if (f->flag_iserror) { /* we are gone */ return (EIO); } f->flag_sleeping = 1; err = cv_wait_sig(&f->cv_io, f->priv_mtx); if (f->flag_iserror) { /* we are gone */ err = EIO; } return (err); } void usb_fifo_signal(struct usb_fifo *f) { if (f->flag_sleeping) { f->flag_sleeping = 0; cv_broadcast(&f->cv_io); } } void usb_fifo_wakeup(struct usb_fifo *f) { usb_fifo_signal(f); KNOTE_LOCKED(&f->selinfo.si_note, 0); if (f->flag_isselect) { selwakeup(&f->selinfo); f->flag_isselect = 0; } if (f->async_p != NULL) { PROC_LOCK(f->async_p); kern_psignal(f->async_p, SIGIO); PROC_UNLOCK(f->async_p); } } static int usb_fifo_dummy_open(struct usb_fifo *fifo, int fflags) { return (0); } static void usb_fifo_dummy_close(struct usb_fifo *fifo, int fflags) { return; } static int usb_fifo_dummy_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { return (ENOIOCTL); } static void usb_fifo_dummy_cmd(struct usb_fifo *fifo) { fifo->flag_flushing = 0; /* not flushing */ } static void usb_fifo_check_methods(struct usb_fifo_methods *pm) { /* check that all callback functions are OK */ if (pm->f_open == NULL) pm->f_open = &usb_fifo_dummy_open; if (pm->f_close == NULL) pm->f_close = &usb_fifo_dummy_close; if (pm->f_ioctl == NULL) pm->f_ioctl = &usb_fifo_dummy_ioctl; if (pm->f_ioctl_post == NULL) pm->f_ioctl_post = &usb_fifo_dummy_ioctl; if (pm->f_start_read == NULL) pm->f_start_read = &usb_fifo_dummy_cmd; if (pm->f_stop_read == NULL) pm->f_stop_read = &usb_fifo_dummy_cmd; if (pm->f_start_write == NULL) pm->f_start_write = &usb_fifo_dummy_cmd; if (pm->f_stop_write == NULL) pm->f_stop_write = &usb_fifo_dummy_cmd; } /*------------------------------------------------------------------------* * usb_fifo_attach * * The following function will create a duplex FIFO. * * Return values: * 0: Success. * Else: Failure. *------------------------------------------------------------------------*/ int usb_fifo_attach(struct usb_device *udev, void *priv_sc, struct mtx *priv_mtx, struct usb_fifo_methods *pm, struct usb_fifo_sc *f_sc, uint16_t unit, int16_t subunit, uint8_t iface_index, uid_t uid, gid_t gid, int mode) { struct usb_fifo *f_tx; struct usb_fifo *f_rx; char devname[32]; uint8_t n; f_sc->fp[USB_FIFO_TX] = NULL; f_sc->fp[USB_FIFO_RX] = NULL; if (pm == NULL) return (EINVAL); /* check the methods */ usb_fifo_check_methods(pm); if (priv_mtx == NULL) priv_mtx = &Giant; /* search for a free FIFO slot */ for (n = 0;; n += 2) { if (n == USB_FIFO_MAX) { /* end of FIFOs reached */ return (ENOMEM); } /* Check for TX FIFO */ if (udev->fifo[n + USB_FIFO_TX] != NULL) { continue; } /* Check for RX FIFO */ if (udev->fifo[n + USB_FIFO_RX] != NULL) { continue; } break; } f_tx = usb_fifo_alloc(priv_mtx); f_rx = usb_fifo_alloc(priv_mtx); if ((f_tx == NULL) || (f_rx == NULL)) { usb_fifo_free(f_tx); usb_fifo_free(f_rx); return (ENOMEM); } /* initialise FIFO structures */ f_tx->fifo_index = n + USB_FIFO_TX; f_tx->dev_ep_index = -1; f_tx->priv_sc0 = priv_sc; f_tx->methods = pm; f_tx->iface_index = iface_index; f_tx->udev = udev; f_rx->fifo_index = n + USB_FIFO_RX; f_rx->dev_ep_index = -1; f_rx->priv_sc0 = priv_sc; f_rx->methods = pm; f_rx->iface_index = iface_index; f_rx->udev = udev; f_sc->fp[USB_FIFO_TX] = f_tx; f_sc->fp[USB_FIFO_RX] = f_rx; mtx_lock(&usb_ref_lock); udev->fifo[f_tx->fifo_index] = f_tx; udev->fifo[f_rx->fifo_index] = f_rx; mtx_unlock(&usb_ref_lock); for (n = 0; n != 4; n++) { if (pm->basename[n] == NULL) { continue; } if (subunit < 0) { if (snprintf(devname, sizeof(devname), "%s%u%s", pm->basename[n], unit, pm->postfix[n] ? pm->postfix[n] : "")) { /* ignore */ } } else { if (snprintf(devname, sizeof(devname), "%s%u.%d%s", pm->basename[n], unit, subunit, pm->postfix[n] ? pm->postfix[n] : "")) { /* ignore */ } } /* * Distribute the symbolic links into two FIFO structures: */ if (n & 1) { f_rx->symlink[n / 2] = usb_alloc_symlink(devname); } else { f_tx->symlink[n / 2] = usb_alloc_symlink(devname); } /* Create the device */ f_sc->dev = usb_make_dev(udev, devname, -1, f_tx->fifo_index & f_rx->fifo_index, FREAD|FWRITE, uid, gid, mode); } DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx); return (0); } /*------------------------------------------------------------------------* * usb_fifo_alloc_buffer * * Return values: * 0: Success * Else failure *------------------------------------------------------------------------*/ int usb_fifo_alloc_buffer(struct usb_fifo *f, usb_size_t bufsize, uint16_t nbuf) { struct usb_ifqueue temp_q = {}; void *queue_data; usb_fifo_free_buffer(f); temp_q.ifq_maxlen = nbuf; queue_data = usb_alloc_mbufs( M_USBDEV, &temp_q, bufsize, nbuf); if (queue_data == NULL && bufsize != 0 && nbuf != 0) return (ENOMEM); mtx_lock(f->priv_mtx); /* * Setup queues and sizes under lock to avoid early use by * concurrent FIFO access: */ f->free_q = temp_q; f->used_q.ifq_maxlen = nbuf; f->queue_data = queue_data; mtx_unlock(f->priv_mtx); return (0); /* success */ } /*------------------------------------------------------------------------* * usb_fifo_free_buffer * * This function will free the buffers associated with a FIFO. This * function can be called multiple times in a row. *------------------------------------------------------------------------*/ void usb_fifo_free_buffer(struct usb_fifo *f) { void *queue_data; mtx_lock(f->priv_mtx); /* Get and clear pointer to free, if any. */ queue_data = f->queue_data; f->queue_data = NULL; /* * Reset queues under lock to avoid use of freed buffers by * concurrent FIFO activity: */ memset(&f->free_q, 0, sizeof(f->free_q)); memset(&f->used_q, 0, sizeof(f->used_q)); mtx_unlock(f->priv_mtx); /* Free old buffer, if any. */ free(queue_data, M_USBDEV); } void usb_fifo_detach(struct usb_fifo_sc *f_sc) { if (f_sc == NULL) { return; } usb_fifo_free(f_sc->fp[USB_FIFO_TX]); usb_fifo_free(f_sc->fp[USB_FIFO_RX]); f_sc->fp[USB_FIFO_TX] = NULL; f_sc->fp[USB_FIFO_RX] = NULL; usb_destroy_dev(f_sc->dev); f_sc->dev = NULL; DPRINTFN(2, "detached %p\n", f_sc); } usb_size_t usb_fifo_put_bytes_max(struct usb_fifo *f) { struct usb_mbuf *m; usb_size_t len; USB_IF_POLL(&f->free_q, m); if (m) { len = m->max_data_len; } else { len = 0; } return (len); } /*------------------------------------------------------------------------* * usb_fifo_put_data * * what: * 0 - normal operation * 1 - set last packet flag to enforce framing *------------------------------------------------------------------------*/ void usb_fifo_put_data(struct usb_fifo *f, struct usb_page_cache *pc, usb_frlength_t offset, usb_frlength_t len, uint8_t what) { struct usb_mbuf *m; usb_frlength_t io_len; while (len || (what == 1)) { USB_IF_DEQUEUE(&f->free_q, m); if (m) { USB_MBUF_RESET(m); io_len = MIN(len, m->cur_data_len); usbd_copy_out(pc, offset, m->cur_data_ptr, io_len); m->cur_data_len = io_len; offset += io_len; len -= io_len; if ((len == 0) && (what == 1)) { m->last_packet = 1; } USB_IF_ENQUEUE(&f->used_q, m); usb_fifo_wakeup(f); if ((len == 0) || (what == 1)) { break; } } else { break; } } } void usb_fifo_put_data_linear(struct usb_fifo *f, void *ptr, usb_size_t len, uint8_t what) { struct usb_mbuf *m; usb_size_t io_len; while (len || (what == 1)) { USB_IF_DEQUEUE(&f->free_q, m); if (m) { USB_MBUF_RESET(m); io_len = MIN(len, m->cur_data_len); memcpy(m->cur_data_ptr, ptr, io_len); m->cur_data_len = io_len; ptr = USB_ADD_BYTES(ptr, io_len); len -= io_len; if ((len == 0) && (what == 1)) { m->last_packet = 1; } USB_IF_ENQUEUE(&f->used_q, m); usb_fifo_wakeup(f); if ((len == 0) || (what == 1)) { break; } } else { break; } } } uint8_t usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len) { struct usb_mbuf *m; USB_IF_DEQUEUE(&f->free_q, m); if (m) { m->cur_data_len = len; m->cur_data_ptr = ptr; USB_IF_ENQUEUE(&f->used_q, m); usb_fifo_wakeup(f); return (1); } return (0); } void usb_fifo_put_data_error(struct usb_fifo *f) { f->flag_iserror = 1; usb_fifo_wakeup(f); } /*------------------------------------------------------------------------* * usb_fifo_get_data * * what: * 0 - normal operation * 1 - only get one "usb_mbuf" * * returns: * 0 - no more data * 1 - data in buffer *------------------------------------------------------------------------*/ uint8_t usb_fifo_get_data(struct usb_fifo *f, struct usb_page_cache *pc, usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen, uint8_t what) { struct usb_mbuf *m; usb_frlength_t io_len; uint8_t tr_data = 0; actlen[0] = 0; while (1) { USB_IF_DEQUEUE(&f->used_q, m); if (m) { tr_data = 1; io_len = MIN(len, m->cur_data_len); usbd_copy_in(pc, offset, m->cur_data_ptr, io_len); len -= io_len; offset += io_len; actlen[0] += io_len; m->cur_data_ptr += io_len; m->cur_data_len -= io_len; if ((m->cur_data_len == 0) || (what == 1)) { USB_IF_ENQUEUE(&f->free_q, m); usb_fifo_wakeup(f); if (what == 1) { break; } } else { USB_IF_PREPEND(&f->used_q, m); } } else { if (tr_data) { /* wait for data to be written out */ break; } if (f->flag_flushing) { /* check if we should send a short packet */ if (f->flag_short != 0) { f->flag_short = 0; tr_data = 1; break; } /* flushing complete */ f->flag_flushing = 0; usb_fifo_wakeup(f); } break; } if (len == 0) { break; } } return (tr_data); } uint8_t usb_fifo_get_data_linear(struct usb_fifo *f, void *ptr, usb_size_t len, usb_size_t *actlen, uint8_t what) { struct usb_mbuf *m; usb_size_t io_len; uint8_t tr_data = 0; actlen[0] = 0; while (1) { USB_IF_DEQUEUE(&f->used_q, m); if (m) { tr_data = 1; io_len = MIN(len, m->cur_data_len); memcpy(ptr, m->cur_data_ptr, io_len); len -= io_len; ptr = USB_ADD_BYTES(ptr, io_len); actlen[0] += io_len; m->cur_data_ptr += io_len; m->cur_data_len -= io_len; if ((m->cur_data_len == 0) || (what == 1)) { USB_IF_ENQUEUE(&f->free_q, m); usb_fifo_wakeup(f); if (what == 1) { break; } } else { USB_IF_PREPEND(&f->used_q, m); } } else { if (tr_data) { /* wait for data to be written out */ break; } if (f->flag_flushing) { /* check if we should send a short packet */ if (f->flag_short != 0) { f->flag_short = 0; tr_data = 1; break; } /* flushing complete */ f->flag_flushing = 0; usb_fifo_wakeup(f); } break; } if (len == 0) { break; } } return (tr_data); } uint8_t usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, usb_size_t *plen) { struct usb_mbuf *m; USB_IF_POLL(&f->used_q, m); if (m) { *plen = m->cur_data_len; *pptr = m->cur_data_ptr; return (1); } return (0); } void usb_fifo_get_data_error(struct usb_fifo *f) { f->flag_iserror = 1; usb_fifo_wakeup(f); } /*------------------------------------------------------------------------* * usb_alloc_symlink * * Return values: * NULL: Failure * Else: Pointer to symlink entry *------------------------------------------------------------------------*/ struct usb_symlink * usb_alloc_symlink(const char *target) { struct usb_symlink *ps; ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK); /* XXX no longer needed */ strlcpy(ps->src_path, target, sizeof(ps->src_path)); ps->src_len = strlen(ps->src_path); strlcpy(ps->dst_path, target, sizeof(ps->dst_path)); ps->dst_len = strlen(ps->dst_path); sx_xlock(&usb_sym_lock); TAILQ_INSERT_TAIL(&usb_sym_head, ps, sym_entry); sx_unlock(&usb_sym_lock); return (ps); } /*------------------------------------------------------------------------* * usb_free_symlink *------------------------------------------------------------------------*/ void usb_free_symlink(struct usb_symlink *ps) { if (ps == NULL) { return; } sx_xlock(&usb_sym_lock); TAILQ_REMOVE(&usb_sym_head, ps, sym_entry); sx_unlock(&usb_sym_lock); free(ps, M_USBDEV); } /*------------------------------------------------------------------------* * usb_read_symlink * * Return value: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ int usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len) { struct usb_symlink *ps; uint32_t temp; uint32_t delta = 0; uint8_t len; int error = 0; sx_xlock(&usb_sym_lock); TAILQ_FOREACH(ps, &usb_sym_head, sym_entry) { /* * Compute total length of source and destination symlink * strings pluss one length byte and two NUL bytes: */ temp = ps->src_len + ps->dst_len + 3; if (temp > 255) { /* * Skip entry because this length cannot fit * into one byte: */ continue; } if (startentry != 0) { /* decrement read offset */ startentry--; continue; } if (temp > user_len) { /* out of buffer space */ break; } len = temp; /* copy out total length */ error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); if (error) { break; } delta += 1; /* copy out source string */ error = copyout(ps->src_path, USB_ADD_BYTES(user_ptr, delta), ps->src_len); if (error) { break; } len = 0; delta += ps->src_len; error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); if (error) { break; } delta += 1; /* copy out destination string */ error = copyout(ps->dst_path, USB_ADD_BYTES(user_ptr, delta), ps->dst_len); if (error) { break; } len = 0; delta += ps->dst_len; error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); if (error) { break; } delta += 1; user_len -= temp; } /* a zero length entry indicates the end */ if ((user_len != 0) && (error == 0)) { len = 0; error = copyout(&len, USB_ADD_BYTES(user_ptr, delta), 1); } sx_unlock(&usb_sym_lock); return (error); } void usb_fifo_set_close_zlp(struct usb_fifo *f, uint8_t onoff) { if (f == NULL) return; /* send a Zero Length Packet, ZLP, before close */ f->flag_short = onoff; } void usb_fifo_set_write_defrag(struct usb_fifo *f, uint8_t onoff) { if (f == NULL) return; /* defrag written data */ f->flag_write_defrag = onoff; /* reset defrag state */ f->flag_have_fragment = 0; } void * usb_fifo_softc(struct usb_fifo *f) { return (f->priv_sc0); } #endif /* USB_HAVE_UGEN */ diff --git a/sys/dev/usb/usb_dev.h b/sys/dev/usb/usb_dev.h index 1bdfa46d064f..8df3af9990cf 100644 --- a/sys/dev/usb/usb_dev.h +++ b/sys/dev/usb/usb_dev.h @@ -1,160 +1,162 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2008-2023 Hans Petter Selasky * * 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 AUTHOR 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 AUTHOR 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. */ #ifndef _USB_DEV_H_ #define _USB_DEV_H_ #ifndef USB_GLOBAL_INCLUDE_FILE #include #include #include #include #include #endif struct usb_fifo; struct usb_mbuf; struct usb_symlink { TAILQ_ENTRY(usb_symlink) sym_entry; char src_path[32]; /* Source path - including terminating * zero */ char dst_path[32]; /* Destination path - including * terminating zero */ uint8_t src_len; /* String length */ uint8_t dst_len; /* String length */ }; /* * Private per-device information. */ struct usb_cdev_privdata { struct usb_bus *bus; struct usb_device *udev; struct usb_interface *iface; int bus_index; /* bus index */ int dev_index; /* device index */ int ep_addr; /* endpoint address */ int fflags; uint8_t fifo_index; /* FIFO index */ }; /* * The following structure defines a minimum re-implementation of the * ifqueue structure in the kernel. */ struct usb_ifqueue { struct usb_mbuf *ifq_head; struct usb_mbuf *ifq_tail; usb_size_t ifq_len; usb_size_t ifq_maxlen; }; /* * Private per-device and per-thread reference information */ struct usb_cdev_refdata { struct usb_fifo *rxfifo; struct usb_fifo *txfifo; uint8_t is_read; /* location has read access */ uint8_t is_write; /* location has write access */ uint8_t is_uref; /* USB refcount decr. needed */ uint8_t is_usbfs; /* USB-FS is active */ uint8_t do_unlock; /* USB enum unlock needed */ }; struct usb_fs_privdata { int bus_index; int dev_index; int ep_addr; int mode; int fifo_index; struct cdev *cdev; LIST_ENTRY(usb_fs_privdata) pd_next; }; /* * Most of the fields in the "usb_fifo" structure are used by the * generic USB access layer. */ struct usb_fifo { struct usb_ifqueue free_q; struct usb_ifqueue used_q; struct selinfo selinfo; struct cv cv_io; struct cv cv_drain; + struct sx fs_fastpath_lock; struct usb_fifo_methods *methods; struct usb_symlink *symlink[2];/* our symlinks */ struct proc *async_p; /* process that wants SIGIO */ struct usb_fs_endpoint *fs_ep_ptr; struct usb_device *udev; +#define USB_FS_XFER_MAX 126 struct usb_xfer *xfer[2]; - struct usb_xfer **fs_xfer; + struct usb_xfer *fs_xfer[USB_FS_XFER_MAX]; struct mtx *priv_mtx; /* client data */ /* set if FIFO is opened by a FILE: */ struct usb_cdev_privdata *curr_cpd; void *priv_sc0; /* client data */ void *priv_sc1; /* client data */ void *queue_data; usb_size_t fs_ep_sz; usb_timeout_t timeout; /* timeout in milliseconds */ usb_frlength_t bufsize; /* BULK and INTERRUPT buffer size */ usb_frcount_t nframes; /* for isochronous mode */ uint16_t dev_ep_index; /* our device endpoint index */ uint8_t flag_sleeping; /* set if FIFO is sleeping */ uint8_t flag_iscomplete; /* set if a USB transfer is complete */ uint8_t flag_iserror; /* set if FIFO error happened */ uint8_t flag_isselect; /* set if FIFO is selected */ uint8_t flag_flushing; /* set if FIFO is flushing data */ uint8_t flag_short; /* set if short_ok or force_short * transfer flags should be set */ uint8_t flag_stall; /* set if clear stall should be run */ uint8_t flag_write_defrag; /* set to defrag written data */ uint8_t flag_have_fragment; /* set if defragging */ uint8_t iface_index; /* set to the interface we belong to */ uint8_t fifo_index; /* set to the FIFO index in "struct * usb_device" */ uint8_t fs_ep_max; uint8_t fifo_zlp; /* zero length packet count */ uint8_t refcount; #define USB_FIFO_REF_MAX 0xFF }; extern struct cdevsw usb_devsw; int usb_fifo_wait(struct usb_fifo *fifo); void usb_fifo_signal(struct usb_fifo *fifo); uint8_t usb_fifo_opened(struct usb_fifo *fifo); struct usb_symlink *usb_alloc_symlink(const char *target); void usb_free_symlink(struct usb_symlink *ps); int usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len); #endif /* _USB_DEV_H_ */ diff --git a/sys/dev/usb/usb_device.c b/sys/dev/usb/usb_device.c index 392d969587c0..820cb21c704d 100644 --- a/sys/dev/usb/usb_device.c +++ b/sys/dev/usb/usb_device.c @@ -1,3112 +1,3112 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2008-2020 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2008-2023 Hans Petter Selasky * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if USB_HAVE_UGEN #include #endif #include "usbdevs.h" #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #include #include #if USB_HAVE_UGEN #include #include #endif #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ /* function prototypes */ static int sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS); static void usb_init_endpoint(struct usb_device *, uint8_t, struct usb_endpoint_descriptor *, struct usb_endpoint_ss_comp_descriptor *, struct usb_endpoint *); static void usb_unconfigure(struct usb_device *, uint8_t); static void usb_detach_device_sub(struct usb_device *, device_t *, char **, uint8_t); static uint8_t usb_probe_and_attach_sub(struct usb_device *, struct usb_attach_arg *); static void usb_init_attach_arg(struct usb_device *, struct usb_attach_arg *); static void usb_suspend_resume_sub(struct usb_device *, device_t, uint8_t); static usb_proc_callback_t usbd_clear_stall_proc; static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t); #if USB_HAVE_DEVCTL static void usb_notify_addq(const char *type, struct usb_device *); #endif #if USB_HAVE_UGEN static void usb_fifo_free_wrap(struct usb_device *, uint8_t, uint8_t); static void usb_cdev_create(struct usb_device *); static void usb_cdev_free(struct usb_device *); #endif /* This variable is global to allow easy access to it: */ #ifdef USB_TEMPLATE int usb_template = USB_TEMPLATE; #else int usb_template = -1; #endif SYSCTL_PROC(_hw_usb, OID_AUTO, template, CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL, 0, sysctl_hw_usb_template, "I", "Selected USB device side template"); /*------------------------------------------------------------------------* * usb_trigger_reprobe_on_off * * This function sets the pull up resistors for all ports currently * operating in device mode either on (when on_not_off is 1), or off * (when it's 0). *------------------------------------------------------------------------*/ static void usb_trigger_reprobe_on_off(int on_not_off) { struct usb_port_status ps; struct usb_bus *bus; struct usb_device *udev; usb_error_t err; int do_unlock, max; max = devclass_get_maxunit(usb_devclass_ptr); while (max >= 0) { mtx_lock(&usb_ref_lock); bus = devclass_get_softc(usb_devclass_ptr, max); max--; if (bus == NULL || bus->devices == NULL || bus->devices[USB_ROOT_HUB_ADDR] == NULL) { mtx_unlock(&usb_ref_lock); continue; } udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev->refcount == USB_DEV_REF_MAX) { mtx_unlock(&usb_ref_lock); continue; } udev->refcount++; mtx_unlock(&usb_ref_lock); do_unlock = usbd_enum_lock(udev); if (do_unlock > 1) { do_unlock = 0; goto next; } err = usbd_req_get_port_status(udev, NULL, &ps, 1); if (err != 0) { DPRINTF("usbd_req_get_port_status() " "failed: %s\n", usbd_errstr(err)); goto next; } if ((UGETW(ps.wPortStatus) & UPS_PORT_MODE_DEVICE) == 0) goto next; if (on_not_off) { err = usbd_req_set_port_feature(udev, NULL, 1, UHF_PORT_POWER); if (err != 0) { DPRINTF("usbd_req_set_port_feature() " "failed: %s\n", usbd_errstr(err)); } } else { err = usbd_req_clear_port_feature(udev, NULL, 1, UHF_PORT_POWER); if (err != 0) { DPRINTF("usbd_req_clear_port_feature() " "failed: %s\n", usbd_errstr(err)); } } next: mtx_lock(&usb_ref_lock); if (do_unlock) usbd_enum_unlock(udev); if (--(udev->refcount) == 0) cv_broadcast(&udev->ref_cv); mtx_unlock(&usb_ref_lock); } } /*------------------------------------------------------------------------* * usb_trigger_reprobe_all * * This function toggles the pull up resistors for all ports currently * operating in device mode, causing the host machine to reenumerate them. *------------------------------------------------------------------------*/ static void usb_trigger_reprobe_all(void) { /* * Set the pull up resistors off for all ports in device mode. */ usb_trigger_reprobe_on_off(0); /* * According to the DWC OTG spec this must be at least 3ms. */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME)); /* * Set the pull up resistors back on. */ usb_trigger_reprobe_on_off(1); } static int sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS) { int error, val; val = usb_template; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL || usb_template == val) return (error); usb_template = val; if (usb_template < 0) { usb_trigger_reprobe_on_off(0); } else { usb_trigger_reprobe_all(); } return (0); } /* English is default language */ static int usb_lang_id = 0x0009; static int usb_lang_mask = 0x00FF; SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RWTUN, &usb_lang_id, 0, "Preferred USB language ID"); SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RWTUN, &usb_lang_mask, 0, "Preferred USB language mask"); static const char* statestr[USB_STATE_MAX] = { [USB_STATE_DETACHED] = "DETACHED", [USB_STATE_ATTACHED] = "ATTACHED", [USB_STATE_POWERED] = "POWERED", [USB_STATE_ADDRESSED] = "ADDRESSED", [USB_STATE_CONFIGURED] = "CONFIGURED", }; const char * usb_statestr(enum usb_dev_state state) { return ((state < USB_STATE_MAX) ? statestr[state] : "UNKNOWN"); } const char * usb_get_manufacturer(struct usb_device *udev) { return (udev->manufacturer ? udev->manufacturer : "Unknown"); } const char * usb_get_product(struct usb_device *udev) { return (udev->product ? udev->product : ""); } const char * usb_get_serial(struct usb_device *udev) { return (udev->serial ? udev->serial : ""); } /*------------------------------------------------------------------------* * usbd_get_ep_by_addr * * This function searches for an USB ep by endpoint address and * direction. * * Returns: * NULL: Failure * Else: Success *------------------------------------------------------------------------*/ struct usb_endpoint * usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val) { struct usb_endpoint *ep = udev->endpoints; struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; enum { EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR), }; /* * According to the USB specification not all bits are used * for the endpoint address. Keep defined bits only: */ ea_val &= EA_MASK; /* * Iterate across all the USB endpoints searching for a match * based on the endpoint address: */ for (; ep != ep_end; ep++) { if (ep->edesc == NULL) { continue; } /* do the mask and check the value */ if ((ep->edesc->bEndpointAddress & EA_MASK) == ea_val) { goto found; } } /* * The default endpoint is always present and is checked separately: */ if ((udev->ctrl_ep.edesc != NULL) && ((udev->ctrl_ep.edesc->bEndpointAddress & EA_MASK) == ea_val)) { ep = &udev->ctrl_ep; goto found; } return (NULL); found: return (ep); } /*------------------------------------------------------------------------* * usbd_get_endpoint * * This function searches for an USB endpoint based on the information * given by the passed "struct usb_config" pointer. * * Return values: * NULL: No match. * Else: Pointer to "struct usb_endpoint". *------------------------------------------------------------------------*/ struct usb_endpoint * usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index, const struct usb_config *setup) { struct usb_endpoint *ep = udev->endpoints; struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; uint8_t index = setup->ep_index; uint8_t ea_mask; uint8_t ea_val; uint8_t type_mask; uint8_t type_val; DPRINTFN(10, "udev=%p iface_index=%d address=0x%x " "type=0x%x dir=0x%x index=%d\n", udev, iface_index, setup->endpoint, setup->type, setup->direction, setup->ep_index); /* check USB mode */ if (setup->usb_mode != USB_MODE_DUAL && udev->flags.usb_mode != setup->usb_mode) { /* wrong mode - no endpoint */ return (NULL); } /* setup expected endpoint direction mask and value */ if (setup->direction == UE_DIR_RX) { ea_mask = (UE_DIR_IN | UE_DIR_OUT); ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? UE_DIR_OUT : UE_DIR_IN; } else if (setup->direction == UE_DIR_TX) { ea_mask = (UE_DIR_IN | UE_DIR_OUT); ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? UE_DIR_IN : UE_DIR_OUT; } else if (setup->direction == UE_DIR_ANY) { /* match any endpoint direction */ ea_mask = 0; ea_val = 0; } else { /* match the given endpoint direction */ ea_mask = (UE_DIR_IN | UE_DIR_OUT); ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT)); } /* setup expected endpoint address */ if (setup->endpoint == UE_ADDR_ANY) { /* match any endpoint address */ } else { /* match the given endpoint address */ ea_mask |= UE_ADDR; ea_val |= (setup->endpoint & UE_ADDR); } /* setup expected endpoint type */ if (setup->type == UE_BULK_INTR) { /* this will match BULK and INTERRUPT endpoints */ type_mask = 2; type_val = 2; } else if (setup->type == UE_TYPE_ANY) { /* match any endpoint type */ type_mask = 0; type_val = 0; } else { /* match the given endpoint type */ type_mask = UE_XFERTYPE; type_val = (setup->type & UE_XFERTYPE); } /* * Iterate across all the USB endpoints searching for a match * based on the endpoint address. Note that we are searching * the endpoints from the beginning of the "udev->endpoints" array. */ for (; ep != ep_end; ep++) { if ((ep->edesc == NULL) || (ep->iface_index != iface_index)) { continue; } /* do the masks and check the values */ if (((ep->edesc->bEndpointAddress & ea_mask) == ea_val) && ((ep->edesc->bmAttributes & type_mask) == type_val)) { if (!index--) { goto found; } } } /* * Match against default endpoint last, so that "any endpoint", "any * address" and "any direction" returns the first endpoint of the * interface. "iface_index" and "direction" is ignored: */ if ((udev->ctrl_ep.edesc != NULL) && ((udev->ctrl_ep.edesc->bEndpointAddress & ea_mask) == ea_val) && ((udev->ctrl_ep.edesc->bmAttributes & type_mask) == type_val) && (!index)) { ep = &udev->ctrl_ep; goto found; } return (NULL); found: return (ep); } /*------------------------------------------------------------------------* * usbd_interface_count * * This function stores the number of USB interfaces excluding * alternate settings, which the USB config descriptor reports into * the unsigned 8-bit integer pointed to by "count". * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_interface_count(struct usb_device *udev, uint8_t *count) { if (udev->cdesc == NULL) { *count = 0; return (USB_ERR_NOT_CONFIGURED); } *count = udev->ifaces_max; return (USB_ERR_NORMAL_COMPLETION); } /*------------------------------------------------------------------------* * usb_init_endpoint * * This function will initialise the USB endpoint structure pointed to by * the "endpoint" argument. The structure pointed to by "endpoint" must be * zeroed before calling this function. *------------------------------------------------------------------------*/ static void usb_init_endpoint(struct usb_device *udev, uint8_t iface_index, struct usb_endpoint_descriptor *edesc, struct usb_endpoint_ss_comp_descriptor *ecomp, struct usb_endpoint *ep) { const struct usb_bus_methods *methods; usb_stream_t x; methods = udev->bus->methods; (methods->endpoint_init) (udev, edesc, ep); /* initialise USB endpoint structure */ ep->edesc = edesc; ep->ecomp = ecomp; ep->iface_index = iface_index; /* setup USB stream queues */ for (x = 0; x != USB_MAX_EP_STREAMS; x++) { TAILQ_INIT(&ep->endpoint_q[x].head); ep->endpoint_q[x].command = &usbd_pipe_start; } /* the pipe is not supported by the hardware */ if (ep->methods == NULL) return; /* check for SUPER-speed streams mode endpoint */ if (udev->speed == USB_SPEED_SUPER && ecomp != NULL && (edesc->bmAttributes & UE_XFERTYPE) == UE_BULK && (UE_GET_BULK_STREAMS(ecomp->bmAttributes) != 0)) { usbd_set_endpoint_mode(udev, ep, USB_EP_MODE_STREAMS); } else { usbd_set_endpoint_mode(udev, ep, USB_EP_MODE_DEFAULT); } /* clear stall, if any */ if (methods->clear_stall != NULL) { USB_BUS_LOCK(udev->bus); (methods->clear_stall) (udev, ep); USB_BUS_UNLOCK(udev->bus); } } /*-----------------------------------------------------------------------* * usb_endpoint_foreach * * This function will iterate all the USB endpoints except the control * endpoint. This function is NULL safe. * * Return values: * NULL: End of USB endpoints * Else: Pointer to next USB endpoint *------------------------------------------------------------------------*/ struct usb_endpoint * usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep) { struct usb_endpoint *ep_end; /* be NULL safe */ if (udev == NULL) return (NULL); ep_end = udev->endpoints + udev->endpoints_max; /* get next endpoint */ if (ep == NULL) ep = udev->endpoints; else ep++; /* find next allocated ep */ while (ep != ep_end) { if (ep->edesc != NULL) return (ep); ep++; } return (NULL); } /*------------------------------------------------------------------------* * usb_wait_pending_refs * * This function will wait for any USB references to go away before * returning. This function is used before freeing a USB device. *------------------------------------------------------------------------*/ static void usb_wait_pending_refs(struct usb_device *udev) { #if USB_HAVE_UGEN DPRINTF("Refcount = %d\n", (int)udev->refcount); mtx_lock(&usb_ref_lock); udev->refcount--; while (1) { /* wait for any pending references to go away */ if (udev->refcount == 0) { /* prevent further refs being taken, if any */ udev->refcount = USB_DEV_REF_MAX; break; } cv_wait(&udev->ref_cv, &usb_ref_lock); } mtx_unlock(&usb_ref_lock); #endif } /*------------------------------------------------------------------------* * usb_unconfigure * * This function will free all USB interfaces and USB endpoints belonging * to an USB device. * * Flag values, see "USB_UNCFG_FLAG_XXX". *------------------------------------------------------------------------*/ static void usb_unconfigure(struct usb_device *udev, uint8_t flag) { uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); /* detach all interface drivers */ usb_detach_device(udev, USB_IFACE_INDEX_ANY, flag); #if USB_HAVE_UGEN /* free all FIFOs except control endpoint FIFOs */ usb_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, flag); /* * Free all cdev's, if any. */ usb_cdev_free(udev); #endif #if USB_HAVE_COMPAT_LINUX /* free Linux compat device, if any */ if (udev->linux_endpoint_start != NULL) { usb_linux_free_device_p(udev); udev->linux_endpoint_start = NULL; } #endif usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_FREE); /* free "cdesc" after "ifaces" and "endpoints", if any */ if (udev->cdesc != NULL) { if (udev->flags.usb_mode != USB_MODE_DEVICE) usbd_free_config_desc(udev, udev->cdesc); udev->cdesc = NULL; } /* set unconfigured state */ udev->curr_config_no = USB_UNCONFIG_NO; udev->curr_config_index = USB_UNCONFIG_INDEX; if (do_unlock) usbd_enum_unlock(udev); } /*------------------------------------------------------------------------* * usbd_set_config_index * * This function selects configuration by index, independent of the * actual configuration number. This function should not be used by * USB drivers. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_set_config_index(struct usb_device *udev, uint8_t index) { struct usb_status ds; struct usb_config_descriptor *cdp; uint16_t power; uint16_t max_power; uint8_t selfpowered; uint8_t do_unlock; usb_error_t err; DPRINTFN(6, "udev=%p index=%d\n", udev, index); /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); usb_unconfigure(udev, 0); if (index == USB_UNCONFIG_INDEX) { /* * Leave unallocated when unconfiguring the * device. "usb_unconfigure()" will also reset * the current config number and index. */ err = usbd_req_set_config(udev, NULL, USB_UNCONFIG_NO); if (udev->state == USB_STATE_CONFIGURED) usb_set_device_state(udev, USB_STATE_ADDRESSED); goto done; } /* get the full config descriptor */ if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* save some memory */ err = usbd_req_get_descriptor_ptr(udev, &cdp, (UDESC_CONFIG << 8) | index); } else { /* normal request */ err = usbd_req_get_config_desc_full(udev, NULL, &cdp, index); } if (err) { goto done; } /* set the new config descriptor */ udev->cdesc = cdp; /* Figure out if the device is self or bus powered. */ selfpowered = 0; if ((!udev->flags.uq_bus_powered) && (cdp->bmAttributes & UC_SELF_POWERED) && (udev->flags.usb_mode == USB_MODE_HOST)) { /* May be self powered. */ if (cdp->bmAttributes & UC_BUS_POWERED) { /* Must ask device. */ err = usbd_req_get_device_status(udev, NULL, &ds); if (err) { DPRINTFN(0, "could not read " "device status: %s\n", usbd_errstr(err)); } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) { selfpowered = 1; } DPRINTF("status=0x%04x \n", UGETW(ds.wStatus)); } else selfpowered = 1; } DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, " "selfpowered=%d, power=%d\n", udev, cdp, udev->address, cdp->bConfigurationValue, cdp->bmAttributes, selfpowered, cdp->bMaxPower * 2); /* Check if we have enough power. */ power = cdp->bMaxPower * 2; if (udev->parent_hub) { max_power = udev->parent_hub->hub->portpower; } else { max_power = USB_MAX_POWER; } if (power > max_power) { DPRINTFN(0, "power exceeded %d > %d\n", power, max_power); err = USB_ERR_NO_POWER; goto done; } /* Only update "self_powered" in USB Host Mode */ if (udev->flags.usb_mode == USB_MODE_HOST) { udev->flags.self_powered = selfpowered; } udev->power = power; udev->curr_config_no = cdp->bConfigurationValue; udev->curr_config_index = index; usb_set_device_state(udev, USB_STATE_CONFIGURED); /* Set the actual configuration value. */ err = usbd_req_set_config(udev, NULL, cdp->bConfigurationValue); if (err) { goto done; } err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_ALLOC); if (err) { goto done; } err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_INIT); if (err) { goto done; } #if USB_HAVE_UGEN /* create device nodes for each endpoint */ usb_cdev_create(udev); #endif done: DPRINTF("error=%s\n", usbd_errstr(err)); if (err) { usb_unconfigure(udev, 0); } if (do_unlock) usbd_enum_unlock(udev); return (err); } /*------------------------------------------------------------------------* * usb_config_parse * * This function will allocate and free USB interfaces and USB endpoints, * parse the USB configuration structure and initialise the USB endpoints * and interfaces. If "iface_index" is not equal to * "USB_IFACE_INDEX_ANY" then the "cmd" parameter is the * alternate_setting to be selected for the given interface. Else the * "cmd" parameter is defined by "USB_CFG_XXX". "iface_index" can be * "USB_IFACE_INDEX_ANY" or a valid USB interface index. This function * is typically called when setting the configuration or when setting * an alternate interface. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static usb_error_t usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd) { struct usb_idesc_parse_state ips; struct usb_interface_descriptor *id; struct usb_endpoint_descriptor *ed; struct usb_interface *iface; struct usb_endpoint *ep; usb_error_t err; uint8_t ep_curr; uint8_t ep_max; uint8_t temp; uint8_t do_init; uint8_t alt_index; if (iface_index != USB_IFACE_INDEX_ANY) { /* parameter overload */ alt_index = cmd; cmd = USB_CFG_INIT; } else { /* not used */ alt_index = 0; } err = 0; DPRINTFN(5, "iface_index=%d cmd=%d\n", iface_index, cmd); if (cmd == USB_CFG_INIT || cmd == USB_CFG_FREE) { sx_assert(&udev->enum_sx, SA_LOCKED); /* check for in-use endpoints */ if (cmd == USB_CFG_INIT) { ep = udev->endpoints; ep_max = udev->endpoints_max; while (ep_max--) { /* look for matching endpoints */ if (iface_index == USB_IFACE_INDEX_ANY || iface_index == ep->iface_index) { if (ep->refcount_alloc != 0) return (USB_ERR_IN_USE); } } } ep = udev->endpoints; ep_max = udev->endpoints_max; while (ep_max--) { /* look for matching endpoints */ if (iface_index == USB_IFACE_INDEX_ANY || iface_index == ep->iface_index) { /* * Check if hardware needs a callback * to unconfigure the endpoint. This * may happen multiple times, * because the requested alternate * setting may fail. The callback * implementation should be aware of * and handle that. */ if (ep->edesc != NULL && udev->bus->methods->endpoint_uninit != NULL) udev->bus->methods->endpoint_uninit(udev, ep); /* reset endpoint */ memset(ep, 0, sizeof(*ep)); /* make sure we don't zero the endpoint again */ ep->iface_index = USB_IFACE_INDEX_ANY; } ep++; } if (cmd == USB_CFG_FREE) goto cleanup; } memset(&ips, 0, sizeof(ips)); ep_curr = 0; ep_max = 0; while ((id = usb_idesc_foreach(udev->cdesc, &ips))) { iface = udev->ifaces + ips.iface_index; /* check for specific interface match */ if (cmd == USB_CFG_INIT) { if ((iface_index != USB_IFACE_INDEX_ANY) && (iface_index != ips.iface_index)) { /* wrong interface */ do_init = 0; } else if (alt_index != ips.iface_index_alt) { /* wrong alternate setting */ do_init = 0; } else { /* initialise interface */ do_init = 1; } /* update number of alternate settings, if any */ if (iface_index == USB_IFACE_INDEX_ANY) iface->num_altsetting = ips.iface_index_alt + 1; } else do_init = 0; /* check for new interface */ if (ips.iface_index_alt == 0) { /* update current number of endpoints */ ep_curr = ep_max; } /* check for init */ if (do_init) { /* setup the USB interface structure */ iface->idesc = id; /* set alternate index */ iface->alt_index = alt_index; /* set default interface parent */ if (iface_index == USB_IFACE_INDEX_ANY) { iface->parent_iface_index = USB_IFACE_INDEX_ANY; } } DPRINTFN(5, "found idesc nendpt=%d\n", id->bNumEndpoints); ed = (struct usb_endpoint_descriptor *)id; temp = ep_curr; /* iterate all the endpoint descriptors */ while ((ed = usb_edesc_foreach(udev->cdesc, ed))) { /* check if endpoint limit has been reached */ if (temp >= USB_MAX_EP_UNITS) { DPRINTF("Endpoint limit reached\n"); break; } ep = udev->endpoints + temp; if (do_init) { void *ecomp; ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed); if (ecomp != NULL) DPRINTFN(5, "Found endpoint companion descriptor\n"); usb_init_endpoint(udev, ips.iface_index, ed, ecomp, ep); } temp ++; /* find maximum number of endpoints */ if (ep_max < temp) ep_max = temp; } } /* NOTE: It is valid to have no interfaces and no endpoints! */ if (cmd == USB_CFG_ALLOC) { udev->ifaces_max = ips.iface_index; #if (USB_HAVE_FIXED_IFACE == 0) udev->ifaces = NULL; if (udev->ifaces_max != 0) { udev->ifaces = malloc(sizeof(*iface) * udev->ifaces_max, M_USB, M_WAITOK | M_ZERO); if (udev->ifaces == NULL) { err = USB_ERR_NOMEM; goto done; } } #endif #if (USB_HAVE_FIXED_ENDPOINT == 0) if (ep_max != 0) { udev->endpoints = malloc(sizeof(*ep) * ep_max, M_USB, M_WAITOK | M_ZERO); if (udev->endpoints == NULL) { err = USB_ERR_NOMEM; goto done; } } else { udev->endpoints = NULL; } #endif USB_BUS_LOCK(udev->bus); udev->endpoints_max = ep_max; /* reset any ongoing clear-stall */ udev->ep_curr = NULL; USB_BUS_UNLOCK(udev->bus); } #if (USB_HAVE_FIXED_IFACE == 0) || (USB_HAVE_FIXED_ENDPOINT == 0) done: #endif if (err) { if (cmd == USB_CFG_ALLOC) { cleanup: USB_BUS_LOCK(udev->bus); udev->endpoints_max = 0; /* reset any ongoing clear-stall */ udev->ep_curr = NULL; USB_BUS_UNLOCK(udev->bus); #if (USB_HAVE_FIXED_IFACE == 0) free(udev->ifaces, M_USB); udev->ifaces = NULL; #endif #if (USB_HAVE_FIXED_ENDPOINT == 0) free(udev->endpoints, M_USB); udev->endpoints = NULL; #endif udev->ifaces_max = 0; } } return (err); } /*------------------------------------------------------------------------* * usbd_set_alt_interface_index * * This function will select an alternate interface index for the * given interface index. The interface should not be in use when this * function is called. That means there should not be any open USB * transfers. Else an error is returned. If the alternate setting is * already set this function will simply return success. This function * is called in Host mode and Device mode! * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_set_alt_interface_index(struct usb_device *udev, uint8_t iface_index, uint8_t alt_index) { struct usb_interface *iface = usbd_get_iface(udev, iface_index); usb_error_t err; uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); if (iface == NULL) { err = USB_ERR_INVAL; goto done; } if (iface->alt_index == alt_index) { /* * Optimise away duplicate setting of * alternate setting in USB Host Mode! */ err = 0; goto done; } #if USB_HAVE_UGEN /* * Free all generic FIFOs for this interface, except control * endpoint FIFOs: */ usb_fifo_free_wrap(udev, iface_index, 0); #endif err = usb_config_parse(udev, iface_index, alt_index); if (err) { goto done; } if (iface->alt_index != alt_index) { /* the alternate setting does not exist */ err = USB_ERR_INVAL; goto done; } err = usbd_req_set_alt_interface_no(udev, NULL, iface_index, iface->idesc->bAlternateSetting); done: if (do_unlock) usbd_enum_unlock(udev); return (err); } /*------------------------------------------------------------------------* * usbd_set_endpoint_stall * * This function is used to make a BULK or INTERRUPT endpoint send * STALL tokens in USB device mode. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t do_stall) { struct usb_xfer *xfer; usb_stream_t x; uint8_t et; uint8_t was_stalled; if (ep == NULL) { /* nothing to do */ DPRINTF("Cannot find endpoint\n"); /* * Pretend that the clear or set stall request is * successful else some USB host stacks can do * strange things, especially when a control endpoint * stalls. */ return (0); } et = (ep->edesc->bmAttributes & UE_XFERTYPE); if ((et != UE_BULK) && (et != UE_INTERRUPT)) { /* * Should not stall control * nor isochronous endpoints. */ DPRINTF("Invalid endpoint\n"); return (0); } USB_BUS_LOCK(udev->bus); /* store current stall state */ was_stalled = ep->is_stalled; /* check for no change */ if (was_stalled && do_stall) { /* if the endpoint is already stalled do nothing */ USB_BUS_UNLOCK(udev->bus); DPRINTF("No change\n"); return (0); } /* set stalled state */ ep->is_stalled = 1; if (do_stall || (!was_stalled)) { if (!was_stalled) { for (x = 0; x != USB_MAX_EP_STREAMS; x++) { /* lookup the current USB transfer, if any */ xfer = ep->endpoint_q[x].curr; if (xfer != NULL) { /* * The "xfer_stall" method * will complete the USB * transfer like in case of a * timeout setting the error * code "USB_ERR_STALLED". */ (udev->bus->methods->xfer_stall) (xfer); } } } (udev->bus->methods->set_stall) (udev, ep, &do_stall); } if (!do_stall) { ep->toggle_next = 0; /* reset data toggle */ ep->is_stalled = 0; /* clear stalled state */ (udev->bus->methods->clear_stall) (udev, ep); /* start the current or next transfer, if any */ for (x = 0; x != USB_MAX_EP_STREAMS; x++) { usb_command_wrapper(&ep->endpoint_q[x], ep->endpoint_q[x].curr); } } USB_BUS_UNLOCK(udev->bus); return (0); } /*------------------------------------------------------------------------* * usb_reset_iface_endpoints - used in USB device side mode *------------------------------------------------------------------------*/ usb_error_t usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index) { struct usb_endpoint *ep; struct usb_endpoint *ep_end; ep = udev->endpoints; ep_end = udev->endpoints + udev->endpoints_max; for (; ep != ep_end; ep++) { if ((ep->edesc == NULL) || (ep->iface_index != iface_index)) { continue; } /* simulate a clear stall from the peer */ usbd_set_endpoint_stall(udev, ep, 0); } return (0); } /*------------------------------------------------------------------------* * usb_detach_device_sub * * This function will try to detach an USB device. If it fails a panic * will result. * * Flag values, see "USB_UNCFG_FLAG_XXX". *------------------------------------------------------------------------*/ static void usb_detach_device_sub(struct usb_device *udev, device_t *ppdev, char **ppnpinfo, uint8_t flag) { device_t dev; char *pnpinfo; int err; dev = *ppdev; if (dev) { /* * NOTE: It is important to clear "*ppdev" before deleting * the child due to some device methods being called late * during the delete process ! */ *ppdev = NULL; if (!rebooting) { device_printf(dev, "at %s, port %d, addr %d " "(disconnected)\n", device_get_nameunit(udev->parent_dev), udev->port_no, udev->address); } if (device_is_attached(dev)) { if (udev->flags.peer_suspended) { err = DEVICE_RESUME(dev); if (err) { device_printf(dev, "Resume failed\n"); } } } /* detach and delete child */ if (device_delete_child(udev->parent_dev, dev)) { goto error; } } pnpinfo = *ppnpinfo; if (pnpinfo != NULL) { *ppnpinfo = NULL; free(pnpinfo, M_USBDEV); } return; error: /* Detach is not allowed to fail in the USB world */ panic("usb_detach_device_sub: A USB driver would not detach\n"); } /*------------------------------------------------------------------------* * usb_detach_device * * The following function will detach the matching interfaces. * This function is NULL safe. * * Flag values, see "USB_UNCFG_FLAG_XXX". *------------------------------------------------------------------------*/ void usb_detach_device(struct usb_device *udev, uint8_t iface_index, uint8_t flag) { struct usb_interface *iface; uint8_t i; if (udev == NULL) { /* nothing to do */ return; } DPRINTFN(4, "udev=%p\n", udev); sx_assert(&udev->enum_sx, SA_LOCKED); /* * First detach the child to give the child's detach routine a * chance to detach the sub-devices in the correct order. * Then delete the child using "device_delete_child()" which * will detach all sub-devices from the bottom and upwards! */ if (iface_index != USB_IFACE_INDEX_ANY) { i = iface_index; iface_index = i + 1; } else { i = 0; iface_index = USB_IFACE_MAX; } /* do the detach */ for (; i != iface_index; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) { /* looks like the end of the USB interfaces */ break; } usb_detach_device_sub(udev, &iface->subdev, &iface->pnpinfo, flag); } } /*------------------------------------------------------------------------* * usb_probe_and_attach_sub * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t usb_probe_and_attach_sub(struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; device_t dev; int err; iface = uaa->iface; if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) { /* leave interface alone */ return (0); } dev = iface->subdev; if (dev) { /* clean up after module unload */ if (device_is_attached(dev)) { /* already a device there */ return (0); } /* clear "iface->subdev" as early as possible */ iface->subdev = NULL; if (device_delete_child(udev->parent_dev, dev)) { /* * Panic here, else one can get a double call * to device_detach(). USB devices should * never fail on detach! */ panic("device_delete_child() failed\n"); } } if (uaa->temp_dev == NULL) { /* create a new child */ uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1); if (uaa->temp_dev == NULL) { device_printf(udev->parent_dev, "Device creation failed\n"); return (1); /* failure */ } device_set_ivars(uaa->temp_dev, uaa); device_quiet(uaa->temp_dev); } /* * Set "subdev" before probe and attach so that "devd" gets * the information it needs. */ iface->subdev = uaa->temp_dev; if (device_probe_and_attach(iface->subdev) == 0) { /* * The USB attach arguments are only available during probe * and attach ! */ uaa->temp_dev = NULL; device_set_ivars(iface->subdev, NULL); if (udev->flags.peer_suspended) { err = DEVICE_SUSPEND(iface->subdev); if (err) device_printf(iface->subdev, "Suspend failed\n"); } return (0); /* success */ } else { /* No USB driver found */ iface->subdev = NULL; } return (1); /* failure */ } /*------------------------------------------------------------------------* * usbd_set_parent_iface * * Using this function will lock the alternate interface setting on an * interface. It is typically used for multi interface drivers. In USB * device side mode it is assumed that the alternate interfaces all * have the same endpoint descriptors. The default parent index value * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not * locked. *------------------------------------------------------------------------*/ void usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index, uint8_t parent_index) { struct usb_interface *iface; if (udev == NULL || iface_index == parent_index) { /* nothing to do */ return; } iface = usbd_get_iface(udev, iface_index); if (iface != NULL) iface->parent_iface_index = parent_index; } static void usb_init_attach_arg(struct usb_device *udev, struct usb_attach_arg *uaa) { memset(uaa, 0, sizeof(*uaa)); uaa->device = udev; uaa->usb_mode = udev->flags.usb_mode; uaa->port = udev->port_no; uaa->dev_state = UAA_DEV_READY; uaa->info.idVendor = UGETW(udev->ddesc.idVendor); uaa->info.idProduct = UGETW(udev->ddesc.idProduct); uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice); uaa->info.bDeviceClass = udev->ddesc.bDeviceClass; uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass; uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol; uaa->info.bConfigIndex = udev->curr_config_index; uaa->info.bConfigNum = udev->curr_config_no; } /*------------------------------------------------------------------------* * usb_probe_and_attach * * This function is called from "uhub_explore_sub()", * "usb_handle_set_config()" and "usb_handle_request()". * * Returns: * 0: Success * Else: A control transfer failed *------------------------------------------------------------------------*/ usb_error_t usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index) { struct usb_attach_arg uaa; struct usb_interface *iface; uint8_t i; uint8_t j; uint8_t do_unlock; if (udev == NULL) { DPRINTF("udev == NULL\n"); return (USB_ERR_INVAL); } /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); if (udev->curr_config_index == USB_UNCONFIG_INDEX) { /* do nothing - no configuration has been set */ goto done; } /* setup USB attach arguments */ usb_init_attach_arg(udev, &uaa); /* * If the whole USB device is targeted, invoke the USB event * handler(s): */ if (iface_index == USB_IFACE_INDEX_ANY) { if (usb_test_quirk(&uaa, UQ_MSC_DYMO_EJECT) != 0 && usb_dymo_eject(udev, 0) == 0) { /* success, mark the udev as disappearing */ uaa.dev_state = UAA_DEV_EJECTING; } EVENTHANDLER_INVOKE(usb_dev_configured, udev, &uaa); if (uaa.dev_state != UAA_DEV_READY) { /* leave device unconfigured */ usb_unconfigure(udev, 0); goto done; } } /* Check if only one interface should be probed: */ if (iface_index != USB_IFACE_INDEX_ANY) { i = iface_index; j = i + 1; } else { i = 0; j = USB_IFACE_MAX; } /* Do the probe and attach */ for (; i != j; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) { /* * Looks like the end of the USB * interfaces ! */ DPRINTFN(2, "end of interfaces " "at %u\n", i); break; } if (iface->idesc == NULL) { /* no interface descriptor */ continue; } uaa.iface = iface; uaa.info.bInterfaceClass = iface->idesc->bInterfaceClass; uaa.info.bInterfaceSubClass = iface->idesc->bInterfaceSubClass; uaa.info.bInterfaceProtocol = iface->idesc->bInterfaceProtocol; uaa.info.bIfaceIndex = i; uaa.info.bIfaceNum = iface->idesc->bInterfaceNumber; uaa.driver_info = 0; /* reset driver_info */ DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n", uaa.info.bInterfaceClass, uaa.info.bInterfaceSubClass, uaa.info.bInterfaceProtocol, uaa.info.bIfaceIndex, uaa.info.bIfaceNum); usb_probe_and_attach_sub(udev, &uaa); /* * Remove the leftover child, if any, to enforce that * a new nomatch devd event is generated for the next * interface if no driver is found: */ if (uaa.temp_dev == NULL) continue; if (device_delete_child(udev->parent_dev, uaa.temp_dev)) DPRINTFN(0, "device delete child failed\n"); uaa.temp_dev = NULL; } done: if (do_unlock) usbd_enum_unlock(udev); return (0); } /*------------------------------------------------------------------------* * usb_suspend_resume_sub * * This function is called when the suspend or resume methods should * be executed on an USB device. *------------------------------------------------------------------------*/ static void usb_suspend_resume_sub(struct usb_device *udev, device_t dev, uint8_t do_suspend) { int err; if (dev == NULL) { return; } if (!device_is_attached(dev)) { return; } if (do_suspend) { err = DEVICE_SUSPEND(dev); } else { err = DEVICE_RESUME(dev); } if (err) { device_printf(dev, "%s failed\n", do_suspend ? "Suspend" : "Resume"); } } /*------------------------------------------------------------------------* * usb_suspend_resume * * The following function will suspend or resume the USB device. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ usb_error_t usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend) { struct usb_interface *iface; uint8_t i; if (udev == NULL) { /* nothing to do */ return (0); } DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend); sx_assert(&udev->sr_sx, SA_LOCKED); USB_BUS_LOCK(udev->bus); /* filter the suspend events */ if (udev->flags.peer_suspended == do_suspend) { USB_BUS_UNLOCK(udev->bus); /* nothing to do */ return (0); } udev->flags.peer_suspended = do_suspend; USB_BUS_UNLOCK(udev->bus); /* do the suspend or resume */ for (i = 0; i != USB_IFACE_MAX; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) { /* looks like the end of the USB interfaces */ break; } usb_suspend_resume_sub(udev, iface->subdev, do_suspend); } return (0); } /*------------------------------------------------------------------------* * usbd_clear_stall_proc * * This function performs generic USB clear stall operations. *------------------------------------------------------------------------*/ static void usbd_clear_stall_proc(struct usb_proc_msg *_pm) { struct usb_udev_msg *pm = (void *)_pm; struct usb_device *udev = pm->udev; /* Change lock */ USB_BUS_UNLOCK(udev->bus); USB_MTX_LOCK(&udev->device_mtx); /* Start clear stall callback */ usbd_transfer_start(udev->ctrl_xfer[1]); /* Change lock */ USB_MTX_UNLOCK(&udev->device_mtx); USB_BUS_LOCK(udev->bus); } /*------------------------------------------------------------------------* * usb_get_langid * * This function tries to figure out the USB string language to use. *------------------------------------------------------------------------*/ void usb_get_langid(struct usb_device *udev) { uint8_t *scratch_ptr; uint8_t do_unlock; int err; /* * Workaround for buggy USB devices. * * It appears that some string-less USB chips will crash and * disappear if any attempts are made to read any string * descriptors. * * Try to detect such chips by checking the strings in the USB * device descriptor. If no strings are present there we * simply disable all USB strings. */ /* Protect scratch area */ do_unlock = usbd_ctrl_lock(udev); scratch_ptr = udev->scratch.data; if (udev->flags.no_strings) { err = USB_ERR_INVAL; } else if (udev->ddesc.iManufacturer || udev->ddesc.iProduct || udev->ddesc.iSerialNumber) { /* read out the language ID string */ err = usbd_req_get_string_desc(udev, NULL, (char *)scratch_ptr, 4, 0, USB_LANGUAGE_TABLE); } else { err = USB_ERR_INVAL; } if (err || (scratch_ptr[0] < 4)) { udev->flags.no_strings = 1; } else { uint16_t langid; uint16_t pref; uint16_t mask; uint8_t x; /* load preferred value and mask */ pref = usb_lang_id; mask = usb_lang_mask; /* align length correctly */ scratch_ptr[0] &= ~1U; /* fix compiler warning */ langid = 0; /* search for preferred language */ for (x = 2; x < scratch_ptr[0]; x += 2) { langid = UGETW(scratch_ptr + x); if ((langid & mask) == pref) break; } if (x >= scratch_ptr[0]) { /* pick the first language as the default */ DPRINTFN(1, "Using first language\n"); langid = UGETW(scratch_ptr + 2); } DPRINTFN(1, "Language selected: 0x%04x\n", langid); udev->langid = langid; } if (do_unlock) usbd_ctrl_unlock(udev); } /*------------------------------------------------------------------------* * usb_alloc_device * * This function allocates a new USB device. This function is called * when a new device has been put in the powered state, but not yet in * the addressed state. Get initial descriptor, set the address, get * full descriptor and get strings. * * Return values: * 0: Failure * Else: Success *------------------------------------------------------------------------*/ struct usb_device * usb_alloc_device(device_t parent_dev, struct usb_bus *bus, struct usb_device *parent_hub, uint8_t depth, uint8_t port_index, uint8_t port_no, enum usb_dev_speed speed, enum usb_hc_mode mode) { struct usb_attach_arg uaa; struct usb_device *udev; struct usb_device *adev; struct usb_device *hub; usb_error_t err; uint8_t device_index; uint8_t config_index; uint8_t config_quirk; uint8_t set_config_failed; DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, " "port_index=%u, port_no=%u, speed=%u, usb_mode=%u\n", parent_dev, bus, parent_hub, depth, port_index, port_no, speed, mode); /* * Find an unused device index. In USB Host mode this is the * same as the device address. * * Device index zero is not used and device index 1 should * always be the root hub. */ for (device_index = USB_ROOT_HUB_ADDR; (device_index != bus->devices_max) && (bus->devices[device_index] != NULL); device_index++) /* nop */; if (device_index == bus->devices_max) { device_printf(bus->bdev, "No free USB device index for new device\n"); return (NULL); } if (depth > 0x10) { device_printf(bus->bdev, "Invalid device depth\n"); return (NULL); } udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO); #if (USB_HAVE_MALLOC_WAITOK == 0) if (udev == NULL) { return (NULL); } #endif /* initialise our SX-lock */ sx_init_flags(&udev->enum_sx, "USB config SX lock", SX_DUPOK); sx_init_flags(&udev->sr_sx, "USB suspend and resume SX lock", SX_NOWITNESS); sx_init_flags(&udev->ctrl_sx, "USB control transfer SX lock", SX_DUPOK); cv_init(&udev->ctrlreq_cv, "WCTRL"); cv_init(&udev->ref_cv, "UGONE"); /* initialise our mutex */ mtx_init(&udev->device_mtx, "USB device mutex", NULL, MTX_DEF); /* initialise generic clear stall */ udev->cs_msg[0].hdr.pm_callback = &usbd_clear_stall_proc; udev->cs_msg[0].udev = udev; udev->cs_msg[1].hdr.pm_callback = &usbd_clear_stall_proc; udev->cs_msg[1].udev = udev; /* initialise some USB device fields */ udev->parent_hub = parent_hub; udev->parent_dev = parent_dev; udev->port_index = port_index; udev->port_no = port_no; udev->depth = depth; udev->bus = bus; udev->address = USB_START_ADDR; /* default value */ udev->plugtime = (usb_ticks_t)ticks; /* * We need to force the power mode to "on" because there are plenty * of USB devices out there that do not work very well with * automatic suspend and resume! */ udev->power_mode = usbd_filter_power_mode(udev, USB_POWER_MODE_ON); udev->pwr_save.last_xfer_time = ticks; /* we are not ready yet */ udev->refcount = 1; /* set up default endpoint descriptor */ udev->ctrl_ep_desc.bLength = sizeof(udev->ctrl_ep_desc); udev->ctrl_ep_desc.bDescriptorType = UDESC_ENDPOINT; udev->ctrl_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; udev->ctrl_ep_desc.bmAttributes = UE_CONTROL; udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET; udev->ctrl_ep_desc.wMaxPacketSize[1] = 0; udev->ctrl_ep_desc.bInterval = 0; /* set up default endpoint companion descriptor */ udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc); udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP; udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; udev->speed = speed; udev->flags.usb_mode = mode; /* search for our High Speed USB HUB, if any */ adev = udev; hub = udev->parent_hub; while (hub) { if (hub->speed == USB_SPEED_HIGH) { udev->hs_hub_addr = hub->address; udev->parent_hs_hub = hub; udev->hs_port_no = adev->port_no; break; } adev = hub; hub = hub->parent_hub; } /* init the default endpoint */ usb_init_endpoint(udev, 0, &udev->ctrl_ep_desc, &udev->ctrl_ep_comp_desc, &udev->ctrl_ep); /* set device index */ udev->device_index = device_index; #if USB_HAVE_UGEN /* Create ugen name */ snprintf(udev->ugen_name, sizeof(udev->ugen_name), USB_GENERIC_NAME "%u.%u", device_get_unit(bus->bdev), device_index); LIST_INIT(&udev->pd_list); /* Create the control endpoint device */ udev->ctrl_dev = usb_make_dev(udev, NULL, 0, 0, FREAD|FWRITE, UID_ROOT, GID_OPERATOR, 0600); /* Create a link from /dev/ugenX.X to the default endpoint */ if (udev->ctrl_dev != NULL) make_dev_alias(udev->ctrl_dev->cdev, "%s", udev->ugen_name); #endif /* Initialise device */ if (bus->methods->device_init != NULL) { err = (bus->methods->device_init) (udev); if (err != 0) { DPRINTFN(0, "device init %d failed " "(%s, ignored)\n", device_index, usbd_errstr(err)); goto done; } } /* set powered device state after device init is complete */ usb_set_device_state(udev, USB_STATE_POWERED); if (udev->flags.usb_mode == USB_MODE_HOST) { err = usbd_req_set_address(udev, NULL, device_index); /* * This is the new USB device address from now on, if * the set address request didn't set it already. */ if (udev->address == USB_START_ADDR) udev->address = device_index; /* * We ignore any set-address errors, hence there are * buggy USB devices out there that actually receive * the SETUP PID, but manage to set the address before * the STATUS stage is ACK'ed. If the device responds * to the subsequent get-descriptor at the new * address, then we know that the set-address command * was successful. */ if (err) { DPRINTFN(0, "set address %d failed " "(%s, ignored)\n", udev->address, usbd_errstr(err)); } } else { /* We are not self powered */ udev->flags.self_powered = 0; /* Set unconfigured state */ udev->curr_config_no = USB_UNCONFIG_NO; udev->curr_config_index = USB_UNCONFIG_INDEX; /* Setup USB descriptors */ err = (usb_temp_setup_by_index_p) (udev, usb_template); if (err) { DPRINTFN(0, "setting up USB template failed - " "usb_template(4) not loaded?\n"); goto done; } } usb_set_device_state(udev, USB_STATE_ADDRESSED); /* setup the device descriptor and the initial "wMaxPacketSize" */ err = usbd_setup_device_desc(udev, NULL); if (err != 0) { /* try to enumerate two more times */ err = usbd_req_re_enumerate(udev, NULL); if (err != 0) { err = usbd_req_re_enumerate(udev, NULL); if (err != 0) { goto done; } } } /* * Setup temporary USB attach args so that we can figure out some * basic quirks for this device. */ usb_init_attach_arg(udev, &uaa); if (usb_test_quirk(&uaa, UQ_BUS_POWERED)) { udev->flags.uq_bus_powered = 1; } if (usb_test_quirk(&uaa, UQ_NO_STRINGS)) { udev->flags.no_strings = 1; } usb_get_langid(udev); /* assume 100mA bus powered for now. Changed when configured. */ udev->power = USB_MIN_POWER; /* fetch the vendor and product strings from the device */ usb_set_device_strings(udev); if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* USB device mode setup is complete */ err = 0; goto config_done; } /* * Most USB devices should attach to config index 0 by * default */ if (usb_test_quirk(&uaa, UQ_CFG_INDEX_0)) { config_index = 0; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_1)) { config_index = 1; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_2)) { config_index = 2; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_3)) { config_index = 3; config_quirk = 1; } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_4)) { config_index = 4; config_quirk = 1; } else { config_index = 0; config_quirk = 0; } set_config_failed = 0; repeat_set_config: DPRINTF("setting config %u\n", config_index); /* get the USB device configured */ err = usbd_set_config_index(udev, config_index); if (err) { if (udev->ddesc.bNumConfigurations != 0) { if (!set_config_failed) { set_config_failed = 1; /* XXX try to re-enumerate the device */ err = usbd_req_re_enumerate(udev, NULL); if (err == 0) goto repeat_set_config; } DPRINTFN(0, "Failure selecting configuration index %u:" "%s, port %u, addr %u (ignored)\n", config_index, usbd_errstr(err), udev->port_no, udev->address); } /* * Some USB devices do not have any configurations. Ignore any * set config failures! */ err = 0; goto config_done; } if (!config_quirk && config_index + 1 < udev->ddesc.bNumConfigurations) { if ((udev->cdesc->bNumInterface < 2) && usbd_get_no_descriptors(udev->cdesc, UDESC_ENDPOINT) == 0) { DPRINTFN(0, "Found no endpoints, trying next config\n"); config_index++; goto repeat_set_config; } #if USB_HAVE_MSCTEST if (config_index == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_INQUIRY) == 0) { /* * Try to figure out if we have an * auto-install disk there: */ if (usb_iface_is_cdrom(udev, 0)) { DPRINTFN(0, "Found possible auto-install " "disk (trying next config)\n"); config_index++; goto repeat_set_config; } } #endif } #if USB_HAVE_MSCTEST if (set_config_failed == 0 && config_index == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_START_STOP) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_PREVENT_ALLOW) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_SYNC_CACHE) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_TEST_UNIT_READY) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_GETMAXLUN) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_INQUIRY) == 0) { /* * Try to figure out if there are any MSC quirks we * should apply automatically: */ err = usb_msc_auto_quirk(udev, 0, &uaa); if (err != 0) { set_config_failed = 1; goto repeat_set_config; } } #endif config_done: DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n", udev->address, udev, udev->parent_hub); /* register our device - we are ready */ usb_bus_port_set_device(bus, parent_hub ? parent_hub->hub->ports + port_index : NULL, udev, device_index); #if USB_HAVE_UGEN /* Symlink the ugen device name */ udev->ugen_symlink = usb_alloc_symlink(udev->ugen_name); /* Announce device */ printf("%s: <%s %s> at %s\n", udev->ugen_name, usb_get_manufacturer(udev), usb_get_product(udev), device_get_nameunit(udev->bus->bdev)); #endif #if USB_HAVE_DEVCTL usb_notify_addq("ATTACH", udev); #endif done: if (err) { /* * Free USB device and all subdevices, if any. */ usb_free_device(udev, 0); udev = NULL; } return (udev); } #if USB_HAVE_UGEN struct usb_fs_privdata * usb_make_dev(struct usb_device *udev, const char *devname, int ep, int fi, int rwmode, uid_t uid, gid_t gid, int mode) { struct usb_fs_privdata* pd; struct make_dev_args args; char buffer[32]; /* Store information to locate ourselves again later */ pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV, M_WAITOK | M_ZERO); pd->bus_index = device_get_unit(udev->bus->bdev); pd->dev_index = udev->device_index; pd->ep_addr = ep; pd->fifo_index = fi; pd->mode = rwmode; /* Now, create the device itself */ if (devname == NULL) { devname = buffer; snprintf(buffer, sizeof(buffer), USB_DEVICE_DIR "/%u.%u.%u", pd->bus_index, pd->dev_index, pd->ep_addr); } /* Setup arguments for make_dev_s() */ make_dev_args_init(&args); args.mda_devsw = &usb_devsw; args.mda_uid = uid; args.mda_gid = gid; args.mda_mode = mode; args.mda_si_drv1 = pd; if (make_dev_s(&args, &pd->cdev, "%s", devname) != 0) { DPRINTFN(0, "Failed to create device %s\n", devname); free(pd, M_USBDEV); return (NULL); } return (pd); } void usb_destroy_dev_sync(struct usb_fs_privdata *pd) { DPRINTFN(1, "Destroying device at ugen%d.%d\n", pd->bus_index, pd->dev_index); /* * Destroy character device synchronously. After this * all system calls are returned. Can block. */ destroy_dev(pd->cdev); free(pd, M_USBDEV); } void usb_destroy_dev(struct usb_fs_privdata *pd) { struct usb_bus *bus; if (pd == NULL) return; mtx_lock(&usb_ref_lock); bus = devclass_get_softc(usb_devclass_ptr, pd->bus_index); mtx_unlock(&usb_ref_lock); if (bus == NULL) { usb_destroy_dev_sync(pd); return; } /* make sure we can re-use the device name */ delist_dev(pd->cdev); USB_BUS_LOCK(bus); LIST_INSERT_HEAD(&bus->pd_cleanup_list, pd, pd_next); /* get cleanup going */ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus), &bus->cleanup_msg[0], &bus->cleanup_msg[1]); USB_BUS_UNLOCK(bus); } static void usb_cdev_create(struct usb_device *udev) { struct usb_config_descriptor *cd; struct usb_endpoint_descriptor *ed; struct usb_descriptor *desc; struct usb_fs_privdata* pd; int inmode, outmode, inmask, outmask, mode; uint8_t ep; KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("stale cdev entries")); DPRINTFN(2, "Creating device nodes\n"); if (usbd_get_mode(udev) == USB_MODE_DEVICE) { inmode = FWRITE; outmode = FREAD; } else { /* USB_MODE_HOST */ inmode = FREAD; outmode = FWRITE; } inmask = 0; outmask = 0; desc = NULL; /* * Collect all used endpoint numbers instead of just * generating 16 static endpoints. */ cd = usbd_get_config_descriptor(udev); while ((desc = usb_desc_foreach(cd, desc))) { /* filter out all endpoint descriptors */ if ((desc->bDescriptorType == UDESC_ENDPOINT) && (desc->bLength >= sizeof(*ed))) { ed = (struct usb_endpoint_descriptor *)desc; /* update masks */ ep = ed->bEndpointAddress; if (UE_GET_DIR(ep) == UE_DIR_OUT) outmask |= 1 << UE_GET_ADDR(ep); else inmask |= 1 << UE_GET_ADDR(ep); } } /* Create all available endpoints except EP0 */ for (ep = 1; ep < 16; ep++) { mode = (inmask & (1 << ep)) ? inmode : 0; mode |= (outmask & (1 << ep)) ? outmode : 0; if (mode == 0) continue; /* no IN or OUT endpoint */ pd = usb_make_dev(udev, NULL, ep, 0, mode, UID_ROOT, GID_OPERATOR, 0600); if (pd != NULL) LIST_INSERT_HEAD(&udev->pd_list, pd, pd_next); } } static void usb_cdev_free(struct usb_device *udev) { struct usb_fs_privdata* pd; DPRINTFN(2, "Freeing device nodes\n"); while ((pd = LIST_FIRST(&udev->pd_list)) != NULL) { KASSERT(pd->cdev->si_drv1 == pd, ("privdata corrupt")); LIST_REMOVE(pd, pd_next); usb_destroy_dev(pd); } } #endif /*------------------------------------------------------------------------* * usb_free_device * * This function is NULL safe and will free an USB device and its * children devices, if any. * * Flag values: Reserved, set to zero. *------------------------------------------------------------------------*/ void usb_free_device(struct usb_device *udev, uint8_t flag) { struct usb_bus *bus; if (udev == NULL) return; /* already freed */ DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no); bus = udev->bus; /* set DETACHED state to prevent any further references */ usb_set_device_state(udev, USB_STATE_DETACHED); #if USB_HAVE_DEVCTL usb_notify_addq("DETACH", udev); #endif #if USB_HAVE_UGEN if (!rebooting) { printf("%s: <%s %s> at %s (disconnected)\n", udev->ugen_name, usb_get_manufacturer(udev), usb_get_product(udev), device_get_nameunit(bus->bdev)); } /* Destroy UGEN symlink, if any */ if (udev->ugen_symlink) { usb_free_symlink(udev->ugen_symlink); udev->ugen_symlink = NULL; } usb_destroy_dev(udev->ctrl_dev); #endif if (udev->flags.usb_mode == USB_MODE_DEVICE) { /* stop receiving any control transfers (Device Side Mode) */ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); } /* the following will get the device unconfigured in software */ usb_unconfigure(udev, USB_UNCFG_FLAG_FREE_EP0); /* final device unregister after all character devices are closed */ usb_bus_port_set_device(bus, udev->parent_hub ? udev->parent_hub->hub->ports + udev->port_index : NULL, NULL, USB_ROOT_HUB_ADDR); /* unsetup any leftover default USB transfers */ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); /* template unsetup, if any */ (usb_temp_unsetup_p) (udev); /* * Make sure that our clear-stall messages are not queued * anywhere: */ USB_BUS_LOCK(udev->bus); usb_proc_mwait(USB_BUS_CS_PROC(udev->bus), &udev->cs_msg[0], &udev->cs_msg[1]); USB_BUS_UNLOCK(udev->bus); /* wait for all references to go away */ usb_wait_pending_refs(udev); sx_destroy(&udev->enum_sx); sx_destroy(&udev->sr_sx); sx_destroy(&udev->ctrl_sx); cv_destroy(&udev->ctrlreq_cv); cv_destroy(&udev->ref_cv); mtx_destroy(&udev->device_mtx); #if USB_HAVE_UGEN KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries")); #endif /* Uninitialise device */ if (bus->methods->device_uninit != NULL) (bus->methods->device_uninit) (udev); /* free device */ free(udev->serial, M_USB); free(udev->manufacturer, M_USB); free(udev->product, M_USB); free(udev, M_USB); } /*------------------------------------------------------------------------* * usbd_get_iface * * This function is the safe way to get the USB interface structure * pointer by interface index. * * Return values: * NULL: Interface not present. * Else: Pointer to USB interface structure. *------------------------------------------------------------------------*/ struct usb_interface * usbd_get_iface(struct usb_device *udev, uint8_t iface_index) { struct usb_interface *iface = udev->ifaces + iface_index; if (iface_index >= udev->ifaces_max) return (NULL); return (iface); } /*------------------------------------------------------------------------* * usbd_find_descriptor * * This function will lookup the first descriptor that matches the * criteria given by the arguments "type" and "subtype". Descriptors * will only be searched within the interface having the index * "iface_index". If the "id" argument points to an USB descriptor, * it will be skipped before the search is started. This allows * searching for multiple descriptors using the same criteria. Else * the search is started after the interface descriptor. * * Return values: * NULL: End of descriptors * Else: A descriptor matching the criteria *------------------------------------------------------------------------*/ void * usbd_find_descriptor(struct usb_device *udev, void *id, uint8_t iface_index, uint8_t type, uint8_t type_mask, uint8_t subtype, uint8_t subtype_mask) { struct usb_descriptor *desc; struct usb_config_descriptor *cd; struct usb_interface *iface; cd = usbd_get_config_descriptor(udev); if (cd == NULL) { return (NULL); } if (id == NULL) { iface = usbd_get_iface(udev, iface_index); if (iface == NULL) { return (NULL); } id = usbd_get_interface_descriptor(iface); if (id == NULL) { return (NULL); } } desc = (void *)id; while ((desc = usb_desc_foreach(cd, desc))) { if (desc->bDescriptorType == UDESC_INTERFACE) { break; } if (((desc->bDescriptorType & type_mask) == type) && ((desc->bDescriptorSubtype & subtype_mask) == subtype)) { return (desc); } } return (NULL); } /*------------------------------------------------------------------------* * usb_devinfo * * This function will dump information from the device descriptor * belonging to the USB device pointed to by "udev", to the string * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes * including the terminating zero. *------------------------------------------------------------------------*/ void usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len) { struct usb_device_descriptor *udd = &udev->ddesc; uint16_t bcdDevice; uint16_t bcdUSB; bcdUSB = UGETW(udd->bcdUSB); bcdDevice = UGETW(udd->bcdDevice); if (udd->bDeviceClass != 0xFF) { snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/" "%x.%02x, addr %d", usb_get_manufacturer(udev), usb_get_product(udev), udd->bDeviceClass, udd->bDeviceSubClass, (bcdUSB >> 8), bcdUSB & 0xFF, (bcdDevice >> 8), bcdDevice & 0xFF, udev->address); } else { snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/" "%x.%02x, addr %d", usb_get_manufacturer(udev), usb_get_product(udev), (bcdUSB >> 8), bcdUSB & 0xFF, (bcdDevice >> 8), bcdDevice & 0xFF, udev->address); } } #ifdef USB_VERBOSE /* * Descriptions of of known vendors and devices ("products"). */ struct usb_knowndev { uint16_t vendor; uint16_t product; uint32_t flags; const char *vendorname; const char *productname; }; #define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ #include "usbdevs.h" #include "usbdevs_data.h" #endif /* USB_VERBOSE */ void usb_set_device_strings(struct usb_device *udev) { struct usb_device_descriptor *udd = &udev->ddesc; #ifdef USB_VERBOSE const struct usb_knowndev *kdp; #endif char *temp_ptr; size_t temp_size; uint16_t vendor_id; uint16_t product_id; uint8_t do_unlock; /* Protect scratch area */ do_unlock = usbd_ctrl_lock(udev); temp_ptr = (char *)udev->scratch.data; temp_size = sizeof(udev->scratch.data); vendor_id = UGETW(udd->idVendor); product_id = UGETW(udd->idProduct); /* cleanup old strings, if any */ free(udev->serial, M_USB); free(udev->manufacturer, M_USB); free(udev->product, M_USB); /* zero the string pointers */ udev->serial = NULL; udev->manufacturer = NULL; udev->product = NULL; /* get serial number string */ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, udev->ddesc.iSerialNumber); udev->serial = strdup(temp_ptr, M_USB); /* get manufacturer string */ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, udev->ddesc.iManufacturer); usb_trim_spaces(temp_ptr); if (temp_ptr[0] != '\0') udev->manufacturer = strdup(temp_ptr, M_USB); /* get product string */ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, udev->ddesc.iProduct); usb_trim_spaces(temp_ptr); if (temp_ptr[0] != '\0') udev->product = strdup(temp_ptr, M_USB); #ifdef USB_VERBOSE if (udev->manufacturer == NULL || udev->product == NULL) { for (kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) { if (kdp->vendor == vendor_id && (kdp->product == product_id || (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) break; } if (kdp->vendorname != NULL) { /* XXX should use pointer to knowndevs string */ if (udev->manufacturer == NULL) { udev->manufacturer = strdup(kdp->vendorname, M_USB); } if (udev->product == NULL && (kdp->flags & USB_KNOWNDEV_NOPROD) == 0) { udev->product = strdup(kdp->productname, M_USB); } } } #endif /* Provide default strings if none were found */ if (udev->manufacturer == NULL) { snprintf(temp_ptr, temp_size, "vendor 0x%04x", vendor_id); udev->manufacturer = strdup(temp_ptr, M_USB); } if (udev->product == NULL) { snprintf(temp_ptr, temp_size, "product 0x%04x", product_id); udev->product = strdup(temp_ptr, M_USB); } if (do_unlock) usbd_ctrl_unlock(udev); } /* * Returns: * See: USB_MODE_XXX */ enum usb_hc_mode usbd_get_mode(struct usb_device *udev) { return (udev->flags.usb_mode); } /* * Returns: * See: USB_SPEED_XXX */ enum usb_dev_speed usbd_get_speed(struct usb_device *udev) { return (udev->speed); } uint32_t usbd_get_isoc_fps(struct usb_device *udev) { ; /* indent fix */ switch (udev->speed) { case USB_SPEED_LOW: case USB_SPEED_FULL: return (1000); default: return (8000); } } struct usb_device_descriptor * usbd_get_device_descriptor(struct usb_device *udev) { if (udev == NULL) return (NULL); /* be NULL safe */ return (&udev->ddesc); } struct usb_config_descriptor * usbd_get_config_descriptor(struct usb_device *udev) { if (udev == NULL) return (NULL); /* be NULL safe */ return (udev->cdesc); } /*------------------------------------------------------------------------* * usb_test_quirk - test a device for a given quirk * * Return values: * 0: The USB device does not have the given quirk. * Else: The USB device has the given quirk. *------------------------------------------------------------------------*/ uint8_t usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk) { uint8_t found; uint8_t x; if (quirk == UQ_NONE) return (0); /* search the automatic per device quirks first */ for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) { if (uaa->device->autoQuirk[x] == quirk) return (1); } /* search global quirk table, if any */ found = (usb_test_quirk_p) (&uaa->info, quirk); return (found); } struct usb_interface_descriptor * usbd_get_interface_descriptor(struct usb_interface *iface) { if (iface == NULL) return (NULL); /* be NULL safe */ return (iface->idesc); } uint8_t usbd_get_interface_altindex(struct usb_interface *iface) { return (iface->alt_index); } uint8_t usbd_get_bus_index(struct usb_device *udev) { return ((uint8_t)device_get_unit(udev->bus->bdev)); } uint8_t usbd_get_device_index(struct usb_device *udev) { return (udev->device_index); } #if USB_HAVE_DEVCTL static void usb_notify_addq(const char *type, struct usb_device *udev) { struct usb_interface *iface; struct sbuf *sb; int i; /* announce the device */ sb = sbuf_new_auto(); sbuf_printf(sb, #if USB_HAVE_UGEN "ugen=%s " "cdev=%s " #endif "vendor=0x%04x " "product=0x%04x " "devclass=0x%02x " "devsubclass=0x%02x " "sernum=\"%s\" " "release=0x%04x " "mode=%s " "port=%u " #if USB_HAVE_UGEN "parent=%s" #endif "", #if USB_HAVE_UGEN udev->ugen_name, udev->ugen_name, #endif UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct), udev->ddesc.bDeviceClass, udev->ddesc.bDeviceSubClass, usb_get_serial(udev), UGETW(udev->ddesc.bcdDevice), (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", udev->port_no #if USB_HAVE_UGEN , udev->parent_hub != NULL ? udev->parent_hub->ugen_name : device_get_nameunit(device_get_parent(udev->bus->bdev)) #endif ); sbuf_finish(sb); devctl_notify("USB", "DEVICE", type, sbuf_data(sb)); sbuf_delete(sb); /* announce each interface */ for (i = 0; i < USB_IFACE_MAX; i++) { iface = usbd_get_iface(udev, i); if (iface == NULL) break; /* end of interfaces */ if (iface->idesc == NULL) continue; /* no interface descriptor */ sb = sbuf_new_auto(); sbuf_printf(sb, #if USB_HAVE_UGEN "ugen=%s " "cdev=%s " #endif "vendor=0x%04x " "product=0x%04x " "devclass=0x%02x " "devsubclass=0x%02x " "sernum=\"%s\" " "release=0x%04x " "mode=%s " "interface=%d " "endpoints=%d " "intclass=0x%02x " "intsubclass=0x%02x " "intprotocol=0x%02x", #if USB_HAVE_UGEN udev->ugen_name, udev->ugen_name, #endif UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct), udev->ddesc.bDeviceClass, udev->ddesc.bDeviceSubClass, usb_get_serial(udev), UGETW(udev->ddesc.bcdDevice), (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", iface->idesc->bInterfaceNumber, iface->idesc->bNumEndpoints, iface->idesc->bInterfaceClass, iface->idesc->bInterfaceSubClass, iface->idesc->bInterfaceProtocol); sbuf_finish(sb); devctl_notify("USB", "INTERFACE", type, sbuf_data(sb)); sbuf_delete(sb); } } #endif #if USB_HAVE_UGEN /*------------------------------------------------------------------------* * usb_fifo_free_wrap * * This function will free the FIFOs. * * Description of "flag" argument: If the USB_UNCFG_FLAG_FREE_EP0 flag * is set and "iface_index" is set to "USB_IFACE_INDEX_ANY", we free * all FIFOs. If the USB_UNCFG_FLAG_FREE_EP0 flag is not set and * "iface_index" is set to "USB_IFACE_INDEX_ANY", we free all non * control endpoint FIFOs. If "iface_index" is not set to * "USB_IFACE_INDEX_ANY" the flag has no effect. *------------------------------------------------------------------------*/ static void usb_fifo_free_wrap(struct usb_device *udev, uint8_t iface_index, uint8_t flag) { struct usb_fifo *f; uint16_t i; /* * Free any USB FIFOs on the given interface: */ for (i = 0; i != USB_FIFO_MAX; i++) { f = udev->fifo[i]; if (f == NULL) { continue; } /* Check if the interface index matches */ if (iface_index == f->iface_index) { if (f->methods != &usb_ugen_methods) { /* * Don't free any non-generic FIFOs in * this case. */ continue; } if ((f->dev_ep_index == 0) && - (f->fs_xfer == NULL)) { + (f->fs_ep_max == 0)) { /* no need to free this FIFO */ continue; } } else if (iface_index == USB_IFACE_INDEX_ANY) { if ((f->methods == &usb_ugen_methods) && (f->dev_ep_index == 0) && (!(flag & USB_UNCFG_FLAG_FREE_EP0)) && - (f->fs_xfer == NULL)) { + (f->fs_ep_max == 0)) { /* no need to free this FIFO */ continue; } } else { /* no need to free this FIFO */ continue; } /* free this FIFO */ usb_fifo_free(f); } } #endif /*------------------------------------------------------------------------* * usb_peer_can_wakeup * * Return values: * 0: Peer cannot do resume signalling. * Else: Peer can do resume signalling. *------------------------------------------------------------------------*/ uint8_t usb_peer_can_wakeup(struct usb_device *udev) { const struct usb_config_descriptor *cdp; cdp = udev->cdesc; if ((cdp != NULL) && (udev->flags.usb_mode == USB_MODE_HOST)) { return (cdp->bmAttributes & UC_REMOTE_WAKEUP); } return (0); /* not supported */ } void usb_set_device_state(struct usb_device *udev, enum usb_dev_state state) { KASSERT(state < USB_STATE_MAX, ("invalid udev state")); DPRINTF("udev %p state %s -> %s\n", udev, usb_statestr(udev->state), usb_statestr(state)); #if USB_HAVE_UGEN mtx_lock(&usb_ref_lock); #endif udev->state = state; #if USB_HAVE_UGEN mtx_unlock(&usb_ref_lock); #endif if (udev->bus->methods->device_state_change != NULL) (udev->bus->methods->device_state_change) (udev); } enum usb_dev_state usb_get_device_state(struct usb_device *udev) { if (udev == NULL) return (USB_STATE_DETACHED); return (udev->state); } uint8_t usbd_device_attached(struct usb_device *udev) { return (udev->state > USB_STATE_DETACHED); } /* * The following function locks enumerating the given USB device. If * the lock is already grabbed this function returns zero. Else a * a value of one is returned. */ uint8_t usbd_enum_lock(struct usb_device *udev) { if (sx_xlocked(&udev->enum_sx)) return (0); sx_xlock(&udev->enum_sx); sx_xlock(&udev->sr_sx); /* * NEWBUS LOCK NOTE: We should check if any parent SX locks * are locked before locking Giant. Else the lock can be * locked multiple times. */ bus_topo_lock(); return (1); } #if USB_HAVE_UGEN /* * This function is the same like usbd_enum_lock() except a value of * 255 is returned when a signal is pending: */ uint8_t usbd_enum_lock_sig(struct usb_device *udev) { if (sx_xlocked(&udev->enum_sx)) return (0); if (sx_xlock_sig(&udev->enum_sx)) return (255); if (sx_xlock_sig(&udev->sr_sx)) { sx_xunlock(&udev->enum_sx); return (255); } bus_topo_lock(); return (1); } #endif /* The following function unlocks enumerating the given USB device. */ void usbd_enum_unlock(struct usb_device *udev) { bus_topo_unlock(); sx_xunlock(&udev->enum_sx); sx_xunlock(&udev->sr_sx); } /* The following function locks suspend and resume. */ void usbd_sr_lock(struct usb_device *udev) { sx_xlock(&udev->sr_sx); /* * NEWBUS LOCK NOTE: We should check if any parent SX locks * are locked before locking Giant. Else the lock can be * locked multiple times. */ bus_topo_lock(); } /* The following function unlocks suspend and resume. */ void usbd_sr_unlock(struct usb_device *udev) { bus_topo_unlock(); sx_xunlock(&udev->sr_sx); } /* * The following function checks the enumerating lock for the given * USB device. */ uint8_t usbd_enum_is_locked(struct usb_device *udev) { return (sx_xlocked(&udev->enum_sx)); } /* * The following function is used to serialize access to USB control * transfers and the USB scratch area. If the lock is already grabbed * this function returns zero. Else a value of one is returned. */ uint8_t usbd_ctrl_lock(struct usb_device *udev) { if (sx_xlocked(&udev->ctrl_sx)) return (0); sx_xlock(&udev->ctrl_sx); /* * We need to allow suspend and resume at this point, else the * control transfer will timeout if the device is suspended! */ if (usbd_enum_is_locked(udev)) usbd_sr_unlock(udev); return (1); } void usbd_ctrl_unlock(struct usb_device *udev) { sx_xunlock(&udev->ctrl_sx); /* * Restore the suspend and resume lock after we have unlocked * the USB control transfer lock to avoid LOR: */ if (usbd_enum_is_locked(udev)) usbd_sr_lock(udev); } /* * The following function is used to set the per-interface specific * plug and play information. The string referred to by the pnpinfo * argument can safely be freed after calling this function. The * pnpinfo of an interface will be reset at device detach or when * passing a NULL argument to this function. This function * returns zero on success, else a USB_ERR_XXX failure code. */ usb_error_t usbd_set_pnpinfo(struct usb_device *udev, uint8_t iface_index, const char *pnpinfo) { struct usb_interface *iface; iface = usbd_get_iface(udev, iface_index); if (iface == NULL) return (USB_ERR_INVAL); if (iface->pnpinfo != NULL) { free(iface->pnpinfo, M_USBDEV); iface->pnpinfo = NULL; } if (pnpinfo == NULL || pnpinfo[0] == 0) return (0); /* success */ iface->pnpinfo = strdup(pnpinfo, M_USBDEV); if (iface->pnpinfo == NULL) return (USB_ERR_NOMEM); return (0); /* success */ } usb_error_t usbd_add_dynamic_quirk(struct usb_device *udev, uint16_t quirk) { uint8_t x; for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) { if (udev->autoQuirk[x] == 0 || udev->autoQuirk[x] == quirk) { udev->autoQuirk[x] = quirk; return (0); /* success */ } } return (USB_ERR_NOMEM); } /* * The following function is used to select the endpoint mode. It * should not be called outside enumeration context. */ usb_error_t usbd_set_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep, uint8_t ep_mode) { usb_error_t error; uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); if (udev->bus->methods->set_endpoint_mode != NULL) { error = (udev->bus->methods->set_endpoint_mode) ( udev, ep, ep_mode); } else if (ep_mode != USB_EP_MODE_DEFAULT) { error = USB_ERR_INVAL; } else { error = 0; } /* only set new mode regardless of error */ ep->ep_mode = ep_mode; if (do_unlock) usbd_enum_unlock(udev); return (error); } uint8_t usbd_get_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep) { return (ep->ep_mode); } diff --git a/sys/dev/usb/usb_generic.c b/sys/dev/usb/usb_generic.c index ad2f23eb9bfc..640f9fdb7ad6 100644 --- a/sys/dev/usb/usb_generic.c +++ b/sys/dev/usb/usb_generic.c @@ -1,2538 +1,2573 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2008-2022 Hans Petter Selasky + * Copyright (c) 2008-2023 Hans Petter Selasky * * 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 AUTHOR 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 AUTHOR 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. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #ifdef COMPAT_FREEBSD32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ugen_debug #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #if USB_HAVE_UGEN /* defines */ #define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */ #define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */ #define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */ /* function prototypes */ static usb_callback_t ugen_read_clear_stall_callback; static usb_callback_t ugen_write_clear_stall_callback; static usb_callback_t ugen_ctrl_read_callback; static usb_callback_t ugen_ctrl_write_callback; static usb_callback_t ugen_isoc_read_callback; static usb_callback_t ugen_isoc_write_callback; static usb_callback_t ugen_ctrl_fs_callback; static usb_fifo_open_t ugen_open; static usb_fifo_close_t ugen_close; static usb_fifo_ioctl_t ugen_ioctl; static usb_fifo_ioctl_t ugen_ioctl_post; static usb_fifo_cmd_t ugen_start_read; static usb_fifo_cmd_t ugen_start_write; static usb_fifo_cmd_t ugen_stop_io; static int ugen_transfer_setup(struct usb_fifo *, const struct usb_config *, uint8_t); static int ugen_open_pipe_write(struct usb_fifo *); static int ugen_open_pipe_read(struct usb_fifo *); static int ugen_set_config(struct usb_fifo *, uint8_t); static int ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t); static int ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *); static int ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *); static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd); #ifdef COMPAT_FREEBSD32 static int ugen_get32(u_long cmd, struct usb_fifo *f, struct usb_gen_descriptor32 *ugd32); #endif static int ugen_re_enumerate(struct usb_fifo *); static int ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int); static uint8_t ugen_fs_get_complete(struct usb_fifo *, uint8_t *); static int ugen_fs_uninit(struct usb_fifo *f); static int ugen_fs_copyin(struct usb_fifo *, uint8_t, struct usb_fs_endpoint*); static uint8_t ugen_fifo_in_use(struct usb_fifo *, int fflags); /* structures */ struct usb_fifo_methods usb_ugen_methods = { .f_open = &ugen_open, .f_close = &ugen_close, .f_ioctl = &ugen_ioctl, .f_ioctl_post = &ugen_ioctl_post, .f_start_read = &ugen_start_read, .f_stop_read = &ugen_stop_io, .f_start_write = &ugen_start_write, .f_stop_write = &ugen_stop_io, }; #ifdef USB_DEBUG static int ugen_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB generic"); SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RWTUN, &ugen_debug, 0, "Debug level"); #endif /* prototypes */ static int ugen_transfer_setup(struct usb_fifo *f, const struct usb_config *setup, uint8_t n_setup) { struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_device *udev = f->udev; uint8_t iface_index = ep->iface_index; int error; mtx_unlock(f->priv_mtx); /* * "usbd_transfer_setup()" can sleep so one needs to make a wrapper, * exiting the mutex and checking things */ error = usbd_transfer_setup(udev, &iface_index, f->xfer, setup, n_setup, f, f->priv_mtx); if (error == 0) { if (f->xfer[0]->nframes == 1) { error = usb_fifo_alloc_buffer(f, f->xfer[0]->max_data_length, 2); } else { error = usb_fifo_alloc_buffer(f, f->xfer[0]->max_frame_size, 2 * f->xfer[0]->nframes); } if (error) { usbd_transfer_unsetup(f->xfer, n_setup); } } mtx_lock(f->priv_mtx); return (error); } static int ugen_open(struct usb_fifo *f, int fflags) { struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_endpoint_descriptor *ed = ep->edesc; uint8_t type; DPRINTFN(1, "flag=0x%x pid=%d name=%s\n", fflags, curthread->td_proc->p_pid, curthread->td_proc->p_comm); mtx_lock(f->priv_mtx); switch (usbd_get_speed(f->udev)) { case USB_SPEED_LOW: case USB_SPEED_FULL: f->nframes = UGEN_HW_FRAMES; f->bufsize = UGEN_BULK_FS_BUFFER_SIZE; break; default: f->nframes = UGEN_HW_FRAMES * 8; f->bufsize = UGEN_BULK_HS_BUFFER_SIZE; break; } type = ed->bmAttributes & UE_XFERTYPE; if (type == UE_INTERRUPT) { f->bufsize = 0; /* use "wMaxPacketSize" */ } f->timeout = USB_NO_TIMEOUT; f->flag_short = 0; f->fifo_zlp = 0; mtx_unlock(f->priv_mtx); return (0); } static void ugen_close(struct usb_fifo *f, int fflags) { DPRINTFN(1, "flag=0x%x pid=%d name=%s\n", fflags, curthread->td_proc->p_pid, curthread->td_proc->p_comm); /* cleanup */ mtx_lock(f->priv_mtx); usbd_transfer_stop(f->xfer[0]); usbd_transfer_stop(f->xfer[1]); mtx_unlock(f->priv_mtx); usbd_transfer_unsetup(f->xfer, 2); usb_fifo_free_buffer(f); if (ugen_fs_uninit(f)) { /* ignore any errors - we are closing */ DPRINTFN(6, "no FIFOs\n"); } } static int ugen_open_pipe_write(struct usb_fifo *f) { struct usb_config usb_config[2]; struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_endpoint_descriptor *ed = ep->edesc; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); if (f->xfer[0] || f->xfer[1]) { /* transfers are already opened */ return (0); } memset(usb_config, 0, sizeof(usb_config)); usb_config[1].type = UE_CONTROL; usb_config[1].endpoint = 0; usb_config[1].direction = UE_DIR_ANY; usb_config[1].timeout = 1000; /* 1 second */ usb_config[1].interval = 50;/* 50 milliseconds */ usb_config[1].bufsize = sizeof(struct usb_device_request); usb_config[1].callback = &ugen_write_clear_stall_callback; usb_config[1].usb_mode = USB_MODE_HOST; usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; usb_config[0].stream_id = 0; /* XXX support more stream ID's */ usb_config[0].direction = UE_DIR_TX; usb_config[0].interval = USB_DEFAULT_INTERVAL; usb_config[0].flags.proxy_buffer = 1; usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ switch (ed->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_BULK: if (f->flag_short) { usb_config[0].flags.force_short_xfer = 1; } usb_config[0].callback = &ugen_ctrl_write_callback; usb_config[0].timeout = f->timeout; usb_config[0].frames = 1; usb_config[0].bufsize = f->bufsize; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } /* first transfer does not clear stall */ f->flag_stall = 0; break; case UE_ISOCHRONOUS: usb_config[0].flags.short_xfer_ok = 1; usb_config[0].bufsize = 0; /* use default */ usb_config[0].frames = f->nframes; usb_config[0].callback = &ugen_isoc_write_callback; usb_config[0].timeout = 0; /* clone configuration */ usb_config[1] = usb_config[0]; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } break; default: return (EINVAL); } return (0); } static int ugen_open_pipe_read(struct usb_fifo *f) { struct usb_config usb_config[2]; struct usb_endpoint *ep = usb_fifo_softc(f); struct usb_endpoint_descriptor *ed = ep->edesc; USB_MTX_ASSERT(f->priv_mtx, MA_OWNED); if (f->xfer[0] || f->xfer[1]) { /* transfers are already opened */ return (0); } memset(usb_config, 0, sizeof(usb_config)); usb_config[1].type = UE_CONTROL; usb_config[1].endpoint = 0; usb_config[1].direction = UE_DIR_ANY; usb_config[1].timeout = 1000; /* 1 second */ usb_config[1].interval = 50;/* 50 milliseconds */ usb_config[1].bufsize = sizeof(struct usb_device_request); usb_config[1].callback = &ugen_read_clear_stall_callback; usb_config[1].usb_mode = USB_MODE_HOST; usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; usb_config[0].stream_id = 0; /* XXX support more stream ID's */ usb_config[0].direction = UE_DIR_RX; usb_config[0].interval = USB_DEFAULT_INTERVAL; usb_config[0].flags.proxy_buffer = 1; usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ switch (ed->bmAttributes & UE_XFERTYPE) { case UE_INTERRUPT: case UE_BULK: if (f->flag_short) { usb_config[0].flags.short_xfer_ok = 1; } usb_config[0].timeout = f->timeout; usb_config[0].frames = 1; usb_config[0].callback = &ugen_ctrl_read_callback; usb_config[0].bufsize = f->bufsize; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } /* first transfer does not clear stall */ f->flag_stall = 0; break; case UE_ISOCHRONOUS: usb_config[0].flags.short_xfer_ok = 1; usb_config[0].bufsize = 0; /* use default */ usb_config[0].frames = f->nframes; usb_config[0].callback = &ugen_isoc_read_callback; usb_config[0].timeout = 0; /* clone configuration */ usb_config[1] = usb_config[0]; if (ugen_transfer_setup(f, usb_config, 2)) { return (EIO); } break; default: return (EINVAL); } return (0); } static void ugen_start_read(struct usb_fifo *f) { /* check that pipes are open */ if (ugen_open_pipe_read(f)) { /* signal error */ usb_fifo_put_data_error(f); } /* start transfers */ usbd_transfer_start(f->xfer[0]); usbd_transfer_start(f->xfer[1]); } static void ugen_start_write(struct usb_fifo *f) { /* check that pipes are open */ if (ugen_open_pipe_write(f)) { /* signal error */ usb_fifo_get_data_error(f); } /* start transfers */ usbd_transfer_start(f->xfer[0]); usbd_transfer_start(f->xfer[1]); } static void ugen_stop_io(struct usb_fifo *f) { /* stop transfers */ usbd_transfer_stop(f->xfer[0]); usbd_transfer_stop(f->xfer[1]); } static void ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); struct usb_mbuf *m; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (xfer->actlen == 0) { if (f->fifo_zlp != 4) { f->fifo_zlp++; } else { /* * Throttle a little bit we have multiple ZLPs * in a row! */ xfer->interval = 64; /* ms */ } } else { /* clear throttle */ xfer->interval = 0; f->fifo_zlp = 0; } usb_fifo_put_data(f, xfer->frbuffers, 0, xfer->actlen, 1); case USB_ST_SETUP: if (f->flag_stall) { usbd_transfer_start(f->xfer[1]); break; } USB_IF_POLL(&f->free_q, m); if (m) { usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); } break; default: /* Error */ if (xfer->error != USB_ERR_CANCELLED) { /* send a zero length packet to userland */ usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1); f->flag_stall = 1; f->fifo_zlp = 0; usbd_transfer_start(f->xfer[1]); } break; } } static void ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); usb_frlength_t actlen; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: /* * If writing is in stall, just jump to clear stall * callback and solve the situation. */ if (f->flag_stall) { usbd_transfer_start(f->xfer[1]); break; } /* * Write data, setup and perform hardware transfer. */ if (usb_fifo_get_data(f, xfer->frbuffers, 0, xfer->max_data_length, &actlen, 0)) { usbd_xfer_set_frame_len(xfer, 0, actlen); usbd_transfer_submit(xfer); } break; default: /* Error */ if (xfer->error != USB_ERR_CANCELLED) { f->flag_stall = 1; usbd_transfer_start(f->xfer[1]); } break; } } static void ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = f->xfer[0]; if (f->flag_stall == 0) { /* nothing to do */ return; } if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTFN(5, "f=%p: stall cleared\n", f); f->flag_stall = 0; usbd_transfer_start(xfer_other); } } static void ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); struct usb_xfer *xfer_other = f->xfer[0]; if (f->flag_stall == 0) { /* nothing to do */ return; } if (usbd_clear_stall_callback(xfer, xfer_other)) { DPRINTFN(5, "f=%p: stall cleared\n", f); f->flag_stall = 0; usbd_transfer_start(xfer_other); } } static void ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); usb_frlength_t offset; usb_frcount_t n; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(6, "actlen=%d\n", xfer->actlen); offset = 0; for (n = 0; n != xfer->aframes; n++) { usb_fifo_put_data(f, xfer->frbuffers, offset, xfer->frlengths[n], 1); offset += xfer->max_frame_size; } case USB_ST_SETUP: tr_setup: for (n = 0; n != xfer->nframes; n++) { /* setup size for next transfer */ usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size); } usbd_transfer_submit(xfer); break; default: /* Error */ if (xfer->error == USB_ERR_CANCELLED) { break; } goto tr_setup; } } static void ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct usb_fifo *f = usbd_xfer_softc(xfer); usb_frlength_t actlen; usb_frlength_t offset; usb_frcount_t n; DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: offset = 0; for (n = 0; n != xfer->nframes; n++) { if (usb_fifo_get_data(f, xfer->frbuffers, offset, xfer->max_frame_size, &actlen, 1)) { usbd_xfer_set_frame_len(xfer, n, actlen); offset += actlen; } else { break; } } for (; n != xfer->nframes; n++) { /* fill in zero frames */ usbd_xfer_set_frame_len(xfer, n, 0); } usbd_transfer_submit(xfer); break; default: /* Error */ if (xfer->error == USB_ERR_CANCELLED) { break; } goto tr_setup; } } static int ugen_set_config(struct usb_fifo *f, uint8_t index) { DPRINTFN(2, "index %u\n", index); if (f->udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ return (ENOTTY); } /* make sure all FIFO's are gone */ /* else there can be a deadlock */ if (ugen_fs_uninit(f)) { /* ignore any errors */ DPRINTFN(6, "no FIFOs\n"); } if (usbd_start_set_config(f->udev, index) != 0) return (EIO); return (0); } static int ugen_set_interface(struct usb_fifo *f, uint8_t iface_index, uint8_t alt_index) { DPRINTFN(2, "%u, %u\n", iface_index, alt_index); if (f->udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ return (ENOTTY); } /* make sure all FIFO's are gone */ /* else there can be a deadlock */ if (ugen_fs_uninit(f)) { /* ignore any errors */ DPRINTFN(6, "no FIFOs\n"); } /* change setting - will free generic FIFOs, if any */ if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) { return (EIO); } /* probe and attach */ if (usb_probe_and_attach(f->udev, iface_index)) { return (EIO); } return (0); } /*------------------------------------------------------------------------* * ugen_get_cdesc * * This function will retrieve the complete configuration descriptor * at the given index. *------------------------------------------------------------------------*/ static int ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) { struct usb_config_descriptor *cdesc; struct usb_device *udev = f->udev; int error; uint16_t len; uint8_t free_data; DPRINTFN(6, "\n"); if (ugd->ugd_data == NULL) { /* userland pointer should not be zero */ return (EINVAL); } if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) || (ugd->ugd_config_index == udev->curr_config_index)) { cdesc = usbd_get_config_descriptor(udev); if (cdesc == NULL) return (ENXIO); free_data = 0; } else { #if (USB_HAVE_FIXED_CONFIG == 0) if (usbd_req_get_config_desc_full(udev, NULL, &cdesc, ugd->ugd_config_index)) { return (ENXIO); } free_data = 1; #else /* configuration descriptor data is shared */ return (EINVAL); #endif } len = UGETW(cdesc->wTotalLength); if (len > ugd->ugd_maxlen) { len = ugd->ugd_maxlen; } DPRINTFN(6, "len=%u\n", len); ugd->ugd_actlen = len; ugd->ugd_offset = 0; error = copyout(cdesc, ugd->ugd_data, len); if (free_data) usbd_free_config_desc(udev, cdesc); return (error); } static int ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) { void *ptr; uint16_t size; int error; uint8_t do_unlock; /* Protect scratch area */ do_unlock = usbd_ctrl_lock(f->udev); ptr = f->udev->scratch.data; size = sizeof(f->udev->scratch.data); if (usbd_req_get_string_desc(f->udev, NULL, ptr, size, ugd->ugd_lang_id, ugd->ugd_string_index)) { error = EINVAL; } else { if (size > ((uint8_t *)ptr)[0]) { size = ((uint8_t *)ptr)[0]; } if (size > ugd->ugd_maxlen) { size = ugd->ugd_maxlen; } ugd->ugd_actlen = size; ugd->ugd_offset = 0; error = copyout(ptr, ugd->ugd_data, size); } if (do_unlock) usbd_ctrl_unlock(f->udev); return (error); } /*------------------------------------------------------------------------* * ugen_get_iface_driver * * This function generates an USB interface description for userland. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd) { struct usb_device *udev = f->udev; struct usb_interface *iface; const char *ptr; const char *desc; unsigned int len; unsigned int maxlen; char buf[128]; int error; DPRINTFN(6, "\n"); if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) { /* userland pointer should not be zero */ return (EINVAL); } iface = usbd_get_iface(udev, ugd->ugd_iface_index); if ((iface == NULL) || (iface->idesc == NULL)) { /* invalid interface index */ return (EINVAL); } /* read out device nameunit string, if any */ if ((iface->subdev != NULL) && device_is_attached(iface->subdev) && (ptr = device_get_nameunit(iface->subdev)) && (desc = device_get_desc(iface->subdev))) { /* print description */ snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc); /* range checks */ maxlen = ugd->ugd_maxlen - 1; len = strlen(buf); if (len > maxlen) len = maxlen; /* update actual length, including terminating zero */ ugd->ugd_actlen = len + 1; /* copy out interface description */ error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen); } else { /* zero length string is default */ error = copyout("", ugd->ugd_data, 1); } return (error); } /*------------------------------------------------------------------------* * ugen_fill_deviceinfo * * This function dumps information about an USB device to the * structure pointed to by the "di" argument. * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ int ugen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di) { struct usb_device *udev; struct usb_device *hub; udev = f->udev; bzero(di, sizeof(di[0])); di->udi_bus = device_get_unit(udev->bus->bdev); di->udi_addr = udev->address; di->udi_index = udev->device_index; strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial)); strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor)); strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product)); usb_printbcd(di->udi_release, sizeof(di->udi_release), UGETW(udev->ddesc.bcdDevice)); di->udi_vendorNo = UGETW(udev->ddesc.idVendor); di->udi_productNo = UGETW(udev->ddesc.idProduct); di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice); di->udi_class = udev->ddesc.bDeviceClass; di->udi_subclass = udev->ddesc.bDeviceSubClass; di->udi_protocol = udev->ddesc.bDeviceProtocol; di->udi_config_no = udev->curr_config_no; di->udi_config_index = udev->curr_config_index; di->udi_power = udev->flags.self_powered ? 0 : udev->power; di->udi_speed = udev->speed; di->udi_mode = udev->flags.usb_mode; di->udi_power_mode = udev->power_mode; di->udi_suspended = udev->flags.peer_suspended; hub = udev->parent_hub; if (hub) { di->udi_hubaddr = hub->address; di->udi_hubindex = hub->device_index; di->udi_hubport = udev->port_no; } return (0); } int ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur) { int error; uint16_t len; uint16_t actlen; if (usb_check_request(f->udev, &ur->ucr_request)) { return (EPERM); } len = UGETW(ur->ucr_request.wLength); /* check if "ucr_data" is valid */ if (len != 0) { if (ur->ucr_data == NULL) { return (EFAULT); } } /* do the USB request */ error = usbd_do_request_flags (f->udev, NULL, &ur->ucr_request, ur->ucr_data, (ur->ucr_flags & USB_SHORT_XFER_OK) | USB_USER_DATA_PTR, &actlen, USB_DEFAULT_TIMEOUT); ur->ucr_actlen = actlen; if (error) { error = EIO; } return (error); } #ifdef COMPAT_FREEBSD32 static int ugen_do_request32(struct usb_fifo *f, struct usb_ctl_request32 *ur32) { struct usb_ctl_request ur; int error; PTRIN_CP(*ur32, ur, ucr_data); CP(*ur32, ur, ucr_flags); CP(*ur32, ur, ucr_actlen); CP(*ur32, ur, ucr_addr); CP(*ur32, ur, ucr_request); error = ugen_do_request(f, &ur); /* Don't update ucr_data pointer */ CP(ur, *ur32, ucr_flags); CP(ur, *ur32, ucr_actlen); CP(ur, *ur32, ucr_addr); CP(ur, *ur32, ucr_request); return (error); } #endif /*------------------------------------------------------------------------ * ugen_re_enumerate *------------------------------------------------------------------------*/ static int ugen_re_enumerate(struct usb_fifo *f) { struct usb_device *udev = f->udev; int error; /* * This request can be useful for testing USB drivers: */ error = priv_check(curthread, PRIV_DRIVER); if (error) { return (error); } if (udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ DPRINTFN(6, "device mode\n"); return (ENOTTY); } /* make sure all FIFO's are gone */ /* else there can be a deadlock */ if (ugen_fs_uninit(f)) { /* ignore any errors */ DPRINTFN(6, "no FIFOs\n"); } /* start re-enumeration of device */ usbd_start_re_enumerate(udev); return (0); } static int ugen_fs_init(struct usb_fifo *f, struct usb_fs_endpoint *fs_ep_ptr, usb_size_t fs_ep_sz, int fflags, uint8_t ep_index_max) { int error; /* verify input parameters */ - if (fs_ep_ptr == NULL || ep_index_max > 127) + if (fs_ep_ptr == NULL || ep_index_max > USB_FS_XFER_MAX) return (EINVAL); - if (f->fs_xfer != NULL) + if (f->fs_ep_max != 0) return (EBUSY); if (f->dev_ep_index != 0 || ep_index_max == 0) return (EINVAL); if (ugen_fifo_in_use(f, fflags)) return (EBUSY); error = usb_fifo_alloc_buffer(f, 1, ep_index_max); if (error == 0) { - f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) * - ep_index_max, M_USB, M_WAITOK | M_ZERO); + mtx_lock(f->priv_mtx); f->fs_ep_max = ep_index_max; f->fs_ep_ptr = fs_ep_ptr; f->fs_ep_sz = fs_ep_sz; + mtx_unlock(f->priv_mtx); } return (error); } int ugen_fs_uninit(struct usb_fifo *f) { - if (f->fs_xfer == NULL) { + if (f->fs_ep_max == 0) return (EINVAL); - } - usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max); - free(f->fs_xfer, M_USB); - f->fs_xfer = NULL; + + /* + * Prevent calls into the fast-path code, by setting fs_ep_max + * to zero: + */ + sx_xlock(&f->fs_fastpath_lock); + mtx_lock(f->priv_mtx); f->fs_ep_max = 0; + mtx_unlock(f->priv_mtx); + sx_xunlock(&f->fs_fastpath_lock); + + usbd_transfer_unsetup(f->fs_xfer, USB_FS_XFER_MAX); + + mtx_lock(f->priv_mtx); f->fs_ep_ptr = NULL; f->flag_iscomplete = 0; + mtx_unlock(f->priv_mtx); + usb_fifo_free_buffer(f); return (0); } static int usb_fs_open(struct usb_fifo *f, struct usb_fs_open *popen, int fflags, usb_stream_t stream_id) { struct usb_config usb_config[1] = {}; struct usb_endpoint *ep; struct usb_endpoint_descriptor *ed; uint8_t iface_index; uint8_t pre_scale; uint8_t isread; int error; if (popen->ep_index >= f->fs_ep_max) return (EINVAL); if (f->fs_xfer[popen->ep_index] != NULL) return (EBUSY); if (popen->max_bufsize > USB_FS_MAX_BUFSIZE) popen->max_bufsize = USB_FS_MAX_BUFSIZE; if (popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) { pre_scale = 1; popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE; } else { pre_scale = 0; } if (popen->max_frames > USB_FS_MAX_FRAMES) popen->max_frames = USB_FS_MAX_FRAMES; if (popen->max_frames == 0) return (EINVAL); ep = usbd_get_ep_by_addr(f->udev, popen->ep_no); if (ep == NULL) return (EINVAL); ed = ep->edesc; if (ed == NULL) return (ENXIO); iface_index = ep->iface_index; usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); usb_config[0].interval = USB_DEFAULT_INTERVAL; usb_config[0].flags.proxy_buffer = 1; if (pre_scale != 0) usb_config[0].flags.pre_scale_frames = 1; usb_config[0].callback = &ugen_ctrl_fs_callback; usb_config[0].timeout = 0; /* no timeout */ usb_config[0].frames = popen->max_frames; usb_config[0].bufsize = popen->max_bufsize; usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ usb_config[0].stream_id = stream_id; if (usb_config[0].type == UE_CONTROL) { if (f->udev->flags.usb_mode != USB_MODE_HOST) return (EINVAL); } else { isread = ((usb_config[0].endpoint & (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN); if (f->udev->flags.usb_mode != USB_MODE_HOST) isread = !isread; /* check permissions */ if (isread) { if (!(fflags & FREAD)) return (EPERM); } else { if (!(fflags & FWRITE)) return (EPERM); } } error = usbd_transfer_setup(f->udev, &iface_index, f->fs_xfer + popen->ep_index, usb_config, 1, f, f->priv_mtx); if (error == 0) { /* update maximums */ popen->max_packet_length = f->fs_xfer[popen->ep_index]->max_frame_size; popen->max_bufsize = f->fs_xfer[popen->ep_index]->max_data_length; /* update number of frames */ popen->max_frames = f->fs_xfer[popen->ep_index]->nframes; /* store index of endpoint */ f->fs_xfer[popen->ep_index]->priv_fifo = ((uint8_t *)0) + popen->ep_index; } else { error = ENOMEM; } return (error); } static int usb_fs_close(struct usb_fifo *f, struct usb_fs_close *pclose) { + struct usb_xfer *xfer; + if (pclose->ep_index >= f->fs_ep_max) return (EINVAL); - usbd_transfer_unsetup(f->fs_xfer + pclose->ep_index, 1); + /* + * Prevent calls into the fast-path code, by setting the + * fs_xfer[] in question to NULL: + */ + sx_xlock(&f->fs_fastpath_lock); + mtx_lock(f->priv_mtx); + xfer = f->fs_xfer[pclose->ep_index]; + f->fs_xfer[pclose->ep_index] = NULL; + mtx_unlock(f->priv_mtx); + sx_xunlock(&f->fs_fastpath_lock); + + if (xfer == NULL) + return (EINVAL); + + usbd_transfer_unsetup(&xfer, 1); return (0); } static int usb_fs_clear_stall_sync(struct usb_fifo *f, struct usb_fs_clear_stall_sync *pstall) { struct usb_device_request req; struct usb_endpoint *ep; int error; if (pstall->ep_index >= f->fs_ep_max) return (EINVAL); if (f->fs_xfer[pstall->ep_index] == NULL) return (EINVAL); if (f->udev->flags.usb_mode != USB_MODE_HOST) return (EINVAL); mtx_lock(f->priv_mtx); error = usbd_transfer_pending(f->fs_xfer[pstall->ep_index]); mtx_unlock(f->priv_mtx); if (error) return (EBUSY); ep = f->fs_xfer[pstall->ep_index]->endpoint; /* setup a clear-stall packet */ req.bmRequestType = UT_WRITE_ENDPOINT; req.bRequest = UR_CLEAR_FEATURE; USETW(req.wValue, UF_ENDPOINT_HALT); req.wIndex[0] = ep->edesc->bEndpointAddress; req.wIndex[1] = 0; USETW(req.wLength, 0); error = usbd_do_request(f->udev, NULL, &req, NULL); if (error == 0) { usbd_clear_data_toggle(f->udev, ep); } else { error = ENXIO; } return (error); } static uint8_t ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex) { struct usb_mbuf *m; USB_IF_DEQUEUE(&f->used_q, m); if (m) { *pindex = *((uint8_t *)(m->cur_data_ptr)); USB_IF_ENQUEUE(&f->free_q, m); return (0); /* success */ } else { *pindex = 0; /* fix compiler warning */ f->flag_iscomplete = 0; } return (1); /* failure */ } static void ugen_fs_set_complete(struct usb_fifo *f, uint8_t index) { struct usb_mbuf *m; USB_IF_DEQUEUE(&f->free_q, m); if (m == NULL) { /* can happen during close */ DPRINTF("out of buffers\n"); return; } USB_MBUF_RESET(m); *((uint8_t *)(m->cur_data_ptr)) = index; USB_IF_ENQUEUE(&f->used_q, m); f->flag_iscomplete = 1; usb_fifo_wakeup(f); } static int ugen_fs_getbuffer(void **uptrp, struct usb_fifo *f, void *buffer, usb_frcount_t n) { union { void **ppBuffer; #ifdef COMPAT_FREEBSD32 uint32_t *ppBuffer32; #endif } u; #ifdef COMPAT_FREEBSD32 uint32_t uptr32; #endif u.ppBuffer = buffer; switch (f->fs_ep_sz) { case sizeof(struct usb_fs_endpoint): if (fueword(u.ppBuffer + n, (long *)uptrp) != 0) return (EFAULT); return (0); #ifdef COMPAT_FREEBSD32 case sizeof(struct usb_fs_endpoint32): if (fueword32(u.ppBuffer32 + n, &uptr32) != 0) return (EFAULT); *uptrp = PTRIN(uptr32); return (0); #endif default: panic("%s: unhandled fs_ep_sz %#x", __func__, f->fs_ep_sz); } } static int ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index) { struct usb_device_request *req; struct usb_xfer *xfer; struct usb_fs_endpoint fs_ep; void *uaddr; /* userland pointer */ void *kaddr; usb_frlength_t offset; usb_frlength_t rem; usb_frcount_t n; uint32_t length; int error; uint8_t isread; + mtx_lock(f->priv_mtx); if (ep_index >= f->fs_ep_max) { + mtx_unlock(f->priv_mtx); return (EINVAL); } xfer = f->fs_xfer[ep_index]; if (xfer == NULL) { + mtx_unlock(f->priv_mtx); return (EINVAL); } - mtx_lock(f->priv_mtx); if (usbd_transfer_pending(xfer)) { mtx_unlock(f->priv_mtx); return (EBUSY); /* should not happen */ } mtx_unlock(f->priv_mtx); error = ugen_fs_copyin(f, ep_index, &fs_ep); if (error) { return (error); } /* security checks */ if (fs_ep.nFrames > xfer->max_frame_count) { xfer->error = USB_ERR_INVAL; goto complete; } if (fs_ep.nFrames == 0) { xfer->error = USB_ERR_INVAL; goto complete; } error = ugen_fs_getbuffer(&uaddr, f, fs_ep.ppBuffer, 0); if (error) { return (error); } /* reset first frame */ usbd_xfer_set_frame_offset(xfer, 0, 0); if (xfer->flags_int.control_xfr) { req = xfer->frbuffers[0].buffer; if (fueword32(fs_ep.pLength, &length) != 0) { return (EFAULT); } if (length != sizeof(*req)) { xfer->error = USB_ERR_INVAL; goto complete; } if (length != 0) { error = copyin(uaddr, req, length); if (error) { return (error); } } if (usb_check_request(f->udev, req)) { xfer->error = USB_ERR_INVAL; goto complete; } usbd_xfer_set_frame_len(xfer, 0, length); /* Host mode only ! */ if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { isread = 1; } else { isread = 0; } n = 1; offset = sizeof(*req); } else { /* Device and Host mode */ if (USB_GET_DATA_ISREAD(xfer)) { isread = 1; } else { isread = 0; } n = 0; offset = 0; } rem = usbd_xfer_max_len(xfer); xfer->nframes = fs_ep.nFrames; xfer->timeout = fs_ep.timeout; if (xfer->timeout > 65535) { xfer->timeout = 65535; } if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK) xfer->flags.short_xfer_ok = 1; else xfer->flags.short_xfer_ok = 0; if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK) xfer->flags.short_frames_ok = 1; else xfer->flags.short_frames_ok = 0; if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT) xfer->flags.force_short_xfer = 1; else xfer->flags.force_short_xfer = 0; if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL) usbd_xfer_set_stall(xfer); else xfer->flags.stall_pipe = 0; for (; n != xfer->nframes; n++) { if (fueword32(fs_ep.pLength + n, &length) != 0) { break; } usbd_xfer_set_frame_len(xfer, n, length); if (length > rem) { xfer->error = USB_ERR_INVAL; goto complete; } rem -= length; if (!isread) { /* we need to know the source buffer */ error = ugen_fs_getbuffer(&uaddr, f, fs_ep.ppBuffer, n); if (error) { break; } if (xfer->flags_int.isochronous_xfr) { /* get kernel buffer address */ kaddr = xfer->frbuffers[0].buffer; kaddr = USB_ADD_BYTES(kaddr, offset); } else { /* set current frame offset */ usbd_xfer_set_frame_offset(xfer, offset, n); /* get kernel buffer address */ kaddr = xfer->frbuffers[n].buffer; } /* move data */ error = copyin(uaddr, kaddr, length); if (error) { break; } } offset += length; } return (error); complete: mtx_lock(f->priv_mtx); ugen_fs_set_complete(f, ep_index); mtx_unlock(f->priv_mtx); return (0); } static struct usb_fs_endpoint * ugen_fs_ep_uptr(struct usb_fifo *f, uint8_t ep_index) { return ((struct usb_fs_endpoint *) ((char *)f->fs_ep_ptr + (ep_index * f->fs_ep_sz))); } static int ugen_fs_copyin(struct usb_fifo *f, uint8_t ep_index, struct usb_fs_endpoint* fs_ep) { #ifdef COMPAT_FREEBSD32 struct usb_fs_endpoint32 fs_ep32; #endif int error; switch (f->fs_ep_sz) { case sizeof(struct usb_fs_endpoint): error = copyin(ugen_fs_ep_uptr(f, ep_index), fs_ep, f->fs_ep_sz); if (error != 0) return (error); break; #ifdef COMPAT_FREEBSD32 case sizeof(struct usb_fs_endpoint32): error = copyin(ugen_fs_ep_uptr(f, ep_index), &fs_ep32, f->fs_ep_sz); if (error != 0) return (error); PTRIN_CP(fs_ep32, *fs_ep, ppBuffer); PTRIN_CP(fs_ep32, *fs_ep, pLength); CP(fs_ep32, *fs_ep, nFrames); CP(fs_ep32, *fs_ep, aFrames); CP(fs_ep32, *fs_ep, flags); CP(fs_ep32, *fs_ep, timeout); CP(fs_ep32, *fs_ep, isoc_time_complete); CP(fs_ep32, *fs_ep, status); break; #endif default: panic("%s: unhandled fs_ep_sz %#x", __func__, f->fs_ep_sz); } return (0); } static int ugen_fs_update(const struct usb_fs_endpoint *fs_ep, struct usb_fifo *f, uint8_t ep_index) { union { struct usb_fs_endpoint *fs_ep_uptr; #ifdef COMPAT_FREEBSD32 struct usb_fs_endpoint32 *fs_ep_uptr32; #endif } u; uint32_t *aFrames_uptr; uint16_t *isoc_time_complete_uptr; int *status_uptr; switch (f->fs_ep_sz) { case sizeof(struct usb_fs_endpoint): u.fs_ep_uptr = ugen_fs_ep_uptr(f, ep_index); aFrames_uptr = &u.fs_ep_uptr->aFrames; isoc_time_complete_uptr = &u.fs_ep_uptr->isoc_time_complete; status_uptr = &u.fs_ep_uptr->status; break; #ifdef COMPAT_FREEBSD32 case sizeof(struct usb_fs_endpoint32): u.fs_ep_uptr32 = (struct usb_fs_endpoint32 *) ugen_fs_ep_uptr(f, ep_index); aFrames_uptr = &u.fs_ep_uptr32->aFrames; isoc_time_complete_uptr = &u.fs_ep_uptr32->isoc_time_complete; status_uptr = &u.fs_ep_uptr32->status; break; #endif default: panic("%s: unhandled fs_ep_sz %#x", __func__, f->fs_ep_sz); } /* update "aFrames" */ if (suword32(aFrames_uptr, fs_ep->aFrames) != 0) return (EFAULT); /* update "isoc_time_complete" */ if (suword16(isoc_time_complete_uptr, fs_ep->isoc_time_complete) != 0) return (EFAULT); /* update "status" */ if (suword32(status_uptr, fs_ep->status) != 0) return (EFAULT); return (0); } static int ugen_fs_copy_out_cancelled(struct usb_fifo *f, uint8_t ep_index) { struct usb_fs_endpoint fs_ep; int error; error = ugen_fs_copyin(f, ep_index, &fs_ep); if (error) return (error); fs_ep.status = USB_ERR_CANCELLED; fs_ep.aFrames = 0; fs_ep.isoc_time_complete = 0; return (ugen_fs_update(&fs_ep, f, ep_index)); } static int ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index) { struct usb_device_request *req; struct usb_xfer *xfer; struct usb_fs_endpoint fs_ep; void *uaddr; /* userland ptr */ void *kaddr; usb_frlength_t offset; usb_frlength_t rem; usb_frcount_t n; uint32_t length; uint32_t temp; int error; uint8_t isread; - if (ep_index >= f->fs_ep_max) + mtx_lock(f->priv_mtx); + if (ep_index >= f->fs_ep_max) { + mtx_unlock(f->priv_mtx); return (EINVAL); - + } xfer = f->fs_xfer[ep_index]; - if (xfer == NULL) + if (xfer == NULL) { + mtx_unlock(f->priv_mtx); return (EINVAL); - - mtx_lock(f->priv_mtx); + } if (!xfer->flags_int.transferring && !xfer->flags_int.started) { mtx_unlock(f->priv_mtx); DPRINTF("Returning fake cancel event\n"); return (ugen_fs_copy_out_cancelled(f, ep_index)); } else if (usbd_transfer_pending(xfer)) { mtx_unlock(f->priv_mtx); return (EBUSY); /* should not happen */ } mtx_unlock(f->priv_mtx); error = ugen_fs_copyin(f, ep_index, &fs_ep); if (error) { return (error); } fs_ep.status = xfer->error; fs_ep.aFrames = xfer->aframes; fs_ep.isoc_time_complete = xfer->isoc_time_complete; if (xfer->error) { goto complete; } if (xfer->flags_int.control_xfr) { req = xfer->frbuffers[0].buffer; /* Host mode only ! */ if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { isread = 1; } else { isread = 0; } if (xfer->nframes == 0) n = 0; /* should never happen */ else n = 1; } else { /* Device and Host mode */ if (USB_GET_DATA_ISREAD(xfer)) { isread = 1; } else { isread = 0; } n = 0; } /* Update lengths and copy out data */ rem = usbd_xfer_max_len(xfer); offset = 0; for (; n != xfer->nframes; n++) { /* get initial length into "temp" */ if (fueword32(fs_ep.pLength + n, &temp) != 0) { return (EFAULT); } if (temp > rem) { /* the userland length has been corrupted */ DPRINTF("corrupt userland length " "%u > %u\n", temp, rem); fs_ep.status = USB_ERR_INVAL; goto complete; } rem -= temp; /* get actual transfer length */ length = xfer->frlengths[n]; if (length > temp) { /* data overflow */ fs_ep.status = USB_ERR_INVAL; DPRINTF("data overflow %u > %u\n", length, temp); goto complete; } if (isread) { /* we need to know the destination buffer */ error = ugen_fs_getbuffer(&uaddr, f, fs_ep.ppBuffer, n); if (error) { return (error); } if (xfer->flags_int.isochronous_xfr) { /* only one frame buffer */ kaddr = USB_ADD_BYTES( xfer->frbuffers[0].buffer, offset); } else { /* multiple frame buffers */ kaddr = xfer->frbuffers[n].buffer; } /* move data */ error = copyout(kaddr, uaddr, length); if (error) { goto complete; } } /* * Update offset according to initial length, which is * needed by isochronous transfers! */ offset += temp; /* update length */ if (suword32(fs_ep.pLength + n, length) != 0) goto complete; } complete: if (error == 0) error = ugen_fs_update(&fs_ep, f, ep_index); return (error); } static uint8_t ugen_fifo_in_use(struct usb_fifo *f, int fflags) { struct usb_fifo *f_rx; struct usb_fifo *f_tx; f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; if ((fflags & FREAD) && f_rx && (f_rx->xfer[0] || f_rx->xfer[1])) { return (1); /* RX FIFO in use */ } if ((fflags & FWRITE) && f_tx && (f_tx->xfer[0] || f_tx->xfer[1])) { return (1); /* TX FIFO in use */ } return (0); /* not in use */ } static int ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) { union { struct usb_fs_complete *pcomp; struct usb_fs_start *pstart; struct usb_fs_stop *pstop; - struct usb_fs_open *popen; - struct usb_fs_open_stream *popen_stream; - struct usb_fs_close *pclose; - struct usb_fs_clear_stall_sync *pstall; void *addr; } u; struct usb_xfer *xfer; int error; uint8_t ep_index; u.addr = addr; DPRINTFN(6, "cmd=0x%08lx\n", cmd); switch (cmd) { case USB_FS_COMPLETE: + sx_slock(&f->fs_fastpath_lock); mtx_lock(f->priv_mtx); error = ugen_fs_get_complete(f, &ep_index); mtx_unlock(f->priv_mtx); if (error != 0) { error = EBUSY; } else { u.pcomp->ep_index = ep_index; error = ugen_fs_copy_out(f, u.pcomp->ep_index); } + sx_sunlock(&f->fs_fastpath_lock); break; case USB_FS_START: + sx_slock(&f->fs_fastpath_lock); error = ugen_fs_copy_in(f, u.pstart->ep_index); if (error == 0) { mtx_lock(f->priv_mtx); xfer = f->fs_xfer[u.pstart->ep_index]; usbd_transfer_start(xfer); mtx_unlock(f->priv_mtx); } + sx_sunlock(&f->fs_fastpath_lock); break; case USB_FS_STOP: mtx_lock(f->priv_mtx); if (u.pstop->ep_index >= f->fs_ep_max) { error = EINVAL; } else { error = 0; xfer = f->fs_xfer[u.pstart->ep_index]; if (usbd_transfer_pending(xfer)) { usbd_transfer_stop(xfer); /* * Check if the USB transfer was * stopped before it was even started * and fake a cancel event. */ if (!xfer->flags_int.transferring && !xfer->flags_int.started) { DPRINTF("Issuing fake completion event\n"); ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); } } } mtx_unlock(f->priv_mtx); break; - case USB_FS_OPEN: - case USB_FS_OPEN_STREAM: - error = usb_fs_open(f, u.popen, fflags, - (cmd == USB_FS_OPEN_STREAM) ? u.popen_stream->stream_id : 0); - break; - - case USB_FS_CLOSE: - error = usb_fs_close(f, u.pclose); - break; - - case USB_FS_CLEAR_STALL_SYNC: - error = usb_fs_clear_stall_sync(f, u.pstall); - break; - default: error = ENOIOCTL; break; } DPRINTFN(6, "error=%d\n", error); return (error); } static int ugen_set_short_xfer(struct usb_fifo *f, void *addr) { uint8_t t; if (*(int *)addr) t = 1; else t = 0; if (f->flag_short == t) { /* same value like before - accept */ return (0); } if (f->xfer[0] || f->xfer[1]) { /* cannot change this during transfer */ return (EBUSY); } f->flag_short = t; return (0); } static int ugen_set_timeout(struct usb_fifo *f, void *addr) { f->timeout = *(int *)addr; if (f->timeout > 65535) { /* limit user input */ f->timeout = 65535; } return (0); } static int ugen_get_frame_size(struct usb_fifo *f, void *addr) { if (f->xfer[0]) { *(int *)addr = f->xfer[0]->max_frame_size; } else { return (EINVAL); } return (0); } static int ugen_set_buffer_size(struct usb_fifo *f, void *addr) { usb_frlength_t t; if (*(int *)addr < 0) t = 0; /* use "wMaxPacketSize" */ else if (*(int *)addr < (256 * 1024)) t = *(int *)addr; else t = 256 * 1024; if (f->bufsize == t) { /* same value like before - accept */ return (0); } if (f->xfer[0] || f->xfer[1]) { /* cannot change this during transfer */ return (EBUSY); } f->bufsize = t; return (0); } static int ugen_get_buffer_size(struct usb_fifo *f, void *addr) { *(int *)addr = f->bufsize; return (0); } static int ugen_get_iface_desc(struct usb_fifo *f, struct usb_interface_descriptor *idesc) { struct usb_interface *iface; iface = usbd_get_iface(f->udev, f->iface_index); if (iface && iface->idesc) { *idesc = *(iface->idesc); } else { return (EIO); } return (0); } static int ugen_get_endpoint_desc(struct usb_fifo *f, struct usb_endpoint_descriptor *ed) { struct usb_endpoint *ep; ep = usb_fifo_softc(f); if (ep && ep->edesc) { *ed = *ep->edesc; } else { return (EINVAL); } return (0); } static int ugen_set_power_mode(struct usb_fifo *f, int mode) { struct usb_device *udev = f->udev; int err; uint8_t old_mode; if ((udev == NULL) || (udev->parent_hub == NULL)) { return (EINVAL); } err = priv_check(curthread, PRIV_DRIVER); if (err) return (err); /* get old power mode */ old_mode = udev->power_mode; /* if no change, then just return */ if (old_mode == mode) return (0); switch (mode) { case USB_POWER_MODE_OFF: if (udev->flags.usb_mode == USB_MODE_HOST && udev->re_enumerate_wait == USB_RE_ENUM_DONE) { udev->re_enumerate_wait = USB_RE_ENUM_PWR_OFF; } /* set power mode will wake up the explore thread */ break; case USB_POWER_MODE_ON: case USB_POWER_MODE_SAVE: break; case USB_POWER_MODE_RESUME: #if USB_HAVE_POWERD /* let USB-powerd handle resume */ USB_BUS_LOCK(udev->bus); udev->pwr_save.write_refs++; udev->pwr_save.last_xfer_time = ticks; USB_BUS_UNLOCK(udev->bus); /* set new power mode */ usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); /* wait for resume to complete */ usb_pause_mtx(NULL, hz / 4); /* clear write reference */ USB_BUS_LOCK(udev->bus); udev->pwr_save.write_refs--; USB_BUS_UNLOCK(udev->bus); #endif mode = USB_POWER_MODE_SAVE; break; case USB_POWER_MODE_SUSPEND: #if USB_HAVE_POWERD /* let USB-powerd handle suspend */ USB_BUS_LOCK(udev->bus); udev->pwr_save.last_xfer_time = ticks - (256 * hz); USB_BUS_UNLOCK(udev->bus); #endif mode = USB_POWER_MODE_SAVE; break; default: return (EINVAL); } if (err) return (ENXIO); /* I/O failure */ /* if we are powered off we need to re-enumerate first */ if (old_mode == USB_POWER_MODE_OFF) { if (udev->flags.usb_mode == USB_MODE_HOST && udev->re_enumerate_wait == USB_RE_ENUM_DONE) { udev->re_enumerate_wait = USB_RE_ENUM_START; } /* set power mode will wake up the explore thread */ } /* set new power mode */ usbd_set_power_mode(udev, mode); return (0); /* success */ } static int ugen_get_power_mode(struct usb_fifo *f) { struct usb_device *udev = f->udev; if (udev == NULL) return (USB_POWER_MODE_ON); return (udev->power_mode); } static int ugen_get_port_path(struct usb_fifo *f, struct usb_device_port_path *dpp) { struct usb_device *udev = f->udev; struct usb_device *next; unsigned int nlevel = 0; if (udev == NULL) goto error; dpp->udp_bus = device_get_unit(udev->bus->bdev); dpp->udp_index = udev->device_index; /* count port levels */ next = udev; while (next->parent_hub != NULL) { nlevel++; next = next->parent_hub; } /* check if too many levels */ if (nlevel > USB_DEVICE_PORT_PATH_MAX) goto error; /* store total level of ports */ dpp->udp_port_level = nlevel; /* store port index array */ next = udev; while (next->parent_hub != NULL) { dpp->udp_port_no[--nlevel] = next->port_no; next = next->parent_hub; } return (0); /* success */ error: return (EINVAL); /* failure */ } static int ugen_get_power_usage(struct usb_fifo *f) { struct usb_device *udev = f->udev; if (udev == NULL) return (0); return (udev->power); } static int ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no, uint8_t set, uint16_t feature) { struct usb_device *udev = f->udev; struct usb_hub *hub; int err; err = priv_check(curthread, PRIV_DRIVER); if (err) { return (err); } if (port_no == 0) { return (EINVAL); } if ((udev == NULL) || (udev->hub == NULL)) { return (EINVAL); } hub = udev->hub; if (port_no > hub->nports) { return (EINVAL); } if (set) err = usbd_req_set_port_feature(udev, NULL, port_no, feature); else err = usbd_req_clear_port_feature(udev, NULL, port_no, feature); if (err) return (ENXIO); /* failure */ return (0); /* success */ } static int ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) { struct usb_fifo *f_rx; struct usb_fifo *f_tx; int error = 0; f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; switch (cmd) { case USB_SET_RX_SHORT_XFER: if (fflags & FREAD) { error = ugen_set_short_xfer(f_rx, addr); } else { error = EINVAL; } break; case USB_SET_TX_FORCE_SHORT: if (fflags & FWRITE) { error = ugen_set_short_xfer(f_tx, addr); } else { error = EINVAL; } break; case USB_SET_RX_TIMEOUT: if (fflags & FREAD) { error = ugen_set_timeout(f_rx, addr); } else { error = EINVAL; } break; case USB_SET_TX_TIMEOUT: if (fflags & FWRITE) { error = ugen_set_timeout(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_FRAME_SIZE: if (fflags & FREAD) { error = ugen_get_frame_size(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_FRAME_SIZE: if (fflags & FWRITE) { error = ugen_get_frame_size(f_tx, addr); } else { error = EINVAL; } break; case USB_SET_RX_BUFFER_SIZE: if (fflags & FREAD) { error = ugen_set_buffer_size(f_rx, addr); } else { error = EINVAL; } break; case USB_SET_TX_BUFFER_SIZE: if (fflags & FWRITE) { error = ugen_set_buffer_size(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_BUFFER_SIZE: if (fflags & FREAD) { error = ugen_get_buffer_size(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_BUFFER_SIZE: if (fflags & FWRITE) { error = ugen_get_buffer_size(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_INTERFACE_DESC: if (fflags & FREAD) { error = ugen_get_iface_desc(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_INTERFACE_DESC: if (fflags & FWRITE) { error = ugen_get_iface_desc(f_tx, addr); } else { error = EINVAL; } break; case USB_GET_RX_ENDPOINT_DESC: if (fflags & FREAD) { error = ugen_get_endpoint_desc(f_rx, addr); } else { error = EINVAL; } break; case USB_GET_TX_ENDPOINT_DESC: if (fflags & FWRITE) { error = ugen_get_endpoint_desc(f_tx, addr); } else { error = EINVAL; } break; case USB_SET_RX_STALL_FLAG: if ((fflags & FREAD) && (*(int *)addr)) { f_rx->flag_stall = 1; } break; case USB_SET_TX_STALL_FLAG: if ((fflags & FWRITE) && (*(int *)addr)) { f_tx->flag_stall = 1; } break; default: error = ENOIOCTL; break; } return (error); } static int ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags) { union { struct usb_interface_descriptor *idesc; struct usb_alt_interface *ai; struct usb_device_descriptor *ddesc; struct usb_config_descriptor *cdesc; struct usb_device_stats *stat; struct usb_fs_init *pinit; #ifdef COMPAT_FREEBSD32 struct usb_fs_init32 *pinit32; #endif struct usb_fs_uninit *puninit; + struct usb_fs_open *popen; + struct usb_fs_open_stream *popen_stream; + struct usb_fs_close *pclose; + struct usb_fs_clear_stall_sync *pstall; struct usb_device_port_path *dpp; uint32_t *ptime; void *addr; int *pint; } u; struct usb_device_descriptor *dtemp; struct usb_config_descriptor *ctemp; struct usb_interface *iface; int error = 0; uint8_t n; u.addr = addr; DPRINTFN(6, "cmd=0x%08lx\n", cmd); switch (cmd) { case USB_DISCOVER: usb_needs_explore_all(); break; case USB_SETDEBUG: if (!(fflags & FWRITE)) { error = EPERM; break; } usb_debug = *(int *)addr; break; case USB_GET_CONFIG: *(int *)addr = f->udev->curr_config_index; break; case USB_SET_CONFIG: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_set_config(f, *(int *)addr); break; case USB_GET_ALTINTERFACE: iface = usbd_get_iface(f->udev, u.ai->uai_interface_index); if (iface && iface->idesc) { u.ai->uai_alt_index = iface->alt_index; } else { error = EINVAL; } break; case USB_SET_ALTINTERFACE: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_set_interface(f, u.ai->uai_interface_index, u.ai->uai_alt_index); break; case USB_GET_DEVICE_DESC: dtemp = usbd_get_device_descriptor(f->udev); if (!dtemp) { error = EIO; break; } *u.ddesc = *dtemp; break; case USB_GET_CONFIG_DESC: ctemp = usbd_get_config_descriptor(f->udev); if (!ctemp) { error = EIO; break; } *u.cdesc = *ctemp; break; case USB_GET_FULL_DESC: error = ugen_get_cdesc(f, addr); break; case USB_GET_STRING_DESC: error = ugen_get_sdesc(f, addr); break; case USB_GET_IFACE_DRIVER: error = ugen_get_iface_driver(f, addr); break; #ifdef COMPAT_FREEBSD32 case USB_GET_FULL_DESC32: case USB_GET_STRING_DESC32: case USB_GET_IFACE_DRIVER32: error = ugen_get32(cmd, f, addr); break; #endif case USB_REQUEST: case USB_DO_REQUEST: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_do_request(f, addr); break; #ifdef COMPAT_FREEBSD32 case USB_REQUEST32: case USB_DO_REQUEST32: if (!(fflags & FWRITE)) { error = EPERM; break; } error = ugen_do_request32(f, addr); break; #endif case USB_DEVICEINFO: case USB_GET_DEVICEINFO: error = ugen_fill_deviceinfo(f, addr); break; case USB_DEVICESTATS: for (n = 0; n != 4; n++) { u.stat->uds_requests_fail[n] = f->udev->stats_err.uds_requests[n]; u.stat->uds_requests_ok[n] = f->udev->stats_ok.uds_requests[n]; } break; case USB_DEVICEENUMERATE: error = ugen_re_enumerate(f); break; case USB_GET_PLUGTIME: *u.ptime = f->udev->plugtime; break; case USB_CLAIM_INTERFACE: case USB_RELEASE_INTERFACE: /* TODO */ break; case USB_IFACE_DRIVER_ACTIVE: n = *u.pint & 0xFF; iface = usbd_get_iface(f->udev, n); if (iface && iface->subdev) error = 0; else error = ENXIO; break; case USB_IFACE_DRIVER_DETACH: error = priv_check(curthread, PRIV_DRIVER); if (error) break; n = *u.pint & 0xFF; if (n == USB_IFACE_INDEX_ANY) { error = EINVAL; break; } /* * Detach the currently attached driver. */ usb_detach_device(f->udev, n, 0); /* * Set parent to self, this should keep attach away * until the next set configuration event. */ usbd_set_parent_iface(f->udev, n, n); break; case USB_SET_POWER_MODE: error = ugen_set_power_mode(f, *u.pint); break; case USB_GET_POWER_MODE: *u.pint = ugen_get_power_mode(f); break; case USB_GET_DEV_PORT_PATH: error = ugen_get_port_path(f, u.dpp); break; case USB_GET_POWER_USAGE: *u.pint = ugen_get_power_usage(f); break; case USB_SET_PORT_ENABLE: error = ugen_do_port_feature(f, *u.pint, 1, UHF_PORT_ENABLE); break; case USB_SET_PORT_DISABLE: error = ugen_do_port_feature(f, *u.pint, 0, UHF_PORT_ENABLE); break; case USB_FS_INIT: error = ugen_fs_init(f, u.pinit->pEndpoints, sizeof(struct usb_fs_endpoint), fflags, u.pinit->ep_index_max); break; #ifdef COMPAT_FREEBSD32 case USB_FS_INIT32: error = ugen_fs_init(f, PTRIN(u.pinit32->pEndpoints), sizeof(struct usb_fs_endpoint32), fflags, u.pinit32->ep_index_max); break; #endif case USB_FS_UNINIT: if (u.puninit->dummy != 0) { error = EINVAL; break; } error = ugen_fs_uninit(f); break; + case USB_FS_OPEN: + case USB_FS_OPEN_STREAM: + error = usb_fs_open(f, u.popen, fflags, + (cmd == USB_FS_OPEN_STREAM) ? u.popen_stream->stream_id : 0); + break; + + case USB_FS_CLOSE: + error = usb_fs_close(f, u.pclose); + break; + + case USB_FS_CLEAR_STALL_SYNC: + error = usb_fs_clear_stall_sync(f, u.pstall); + break; + default: mtx_lock(f->priv_mtx); error = ugen_iface_ioctl(f, cmd, addr, fflags); mtx_unlock(f->priv_mtx); break; } DPRINTFN(6, "error=%d\n", error); return (error); } static void ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error) { ; /* workaround for a bug in "indent" */ DPRINTF("st=%u alen=%u aframes=%u\n", USB_GET_STATE(xfer), xfer->actlen, xfer->aframes); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: usbd_transfer_submit(xfer); break; default: ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); break; } } #ifdef COMPAT_FREEBSD32 void usb_gen_descriptor_from32(struct usb_gen_descriptor *ugd, const struct usb_gen_descriptor32 *ugd32) { PTRIN_CP(*ugd32, *ugd, ugd_data); CP(*ugd32, *ugd, ugd_lang_id); CP(*ugd32, *ugd, ugd_maxlen); CP(*ugd32, *ugd, ugd_actlen); CP(*ugd32, *ugd, ugd_offset); CP(*ugd32, *ugd, ugd_config_index); CP(*ugd32, *ugd, ugd_string_index); CP(*ugd32, *ugd, ugd_iface_index); CP(*ugd32, *ugd, ugd_altif_index); CP(*ugd32, *ugd, ugd_endpt_index); CP(*ugd32, *ugd, ugd_report_type); /* Don't copy reserved */ } void update_usb_gen_descriptor32(struct usb_gen_descriptor32 *ugd32, struct usb_gen_descriptor *ugd) { /* Don't update ugd_data pointer */ CP(*ugd32, *ugd, ugd_lang_id); CP(*ugd32, *ugd, ugd_maxlen); CP(*ugd32, *ugd, ugd_actlen); CP(*ugd32, *ugd, ugd_offset); CP(*ugd32, *ugd, ugd_config_index); CP(*ugd32, *ugd, ugd_string_index); CP(*ugd32, *ugd, ugd_iface_index); CP(*ugd32, *ugd, ugd_altif_index); CP(*ugd32, *ugd, ugd_endpt_index); CP(*ugd32, *ugd, ugd_report_type); /* Don't update reserved */ } static int ugen_get32(u_long cmd, struct usb_fifo *f, struct usb_gen_descriptor32 *ugd32) { struct usb_gen_descriptor ugd; int error; usb_gen_descriptor_from32(&ugd, ugd32); switch (cmd) { case USB_GET_FULL_DESC32: error = ugen_get_cdesc(f, &ugd); break; case USB_GET_STRING_DESC32: error = ugen_get_sdesc(f, &ugd); break; case USB_GET_IFACE_DRIVER32: error = ugen_get_iface_driver(f, &ugd); break; default: /* Can't happen except by programmer error */ panic("%s: called with invalid cmd %lx", __func__, cmd); } update_usb_gen_descriptor32(ugd32, &ugd); return (error); } #endif /* COMPAT_FREEBSD32 */ #endif /* USB_HAVE_UGEN */