Index: stable/8/sys/dev/usb/controller/usb_controller.c =================================================================== --- stable/8/sys/dev/usb/controller/usb_controller.c (revision 278294) +++ stable/8/sys/dev/usb/controller/usb_controller.c (revision 278295) @@ -1,963 +1,1002 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * * 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. */ #include "opt_ddb.h" #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_ctrl_debug #include #include #include #include #include #include +#include #include #include #include #include #include "usb_if.h" /* function prototypes */ static device_probe_t usb_probe; static device_attach_t usb_attach; static device_detach_t usb_detach; static device_suspend_t usb_suspend; static device_resume_t usb_resume; static device_shutdown_t usb_shutdown; static void usb_attach_sub(device_t, struct usb_bus *); /* static variables */ #ifdef USB_DEBUG static int usb_ctrl_debug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, ctrl, CTLFLAG_RW, 0, "USB controller"); SYSCTL_INT(_hw_usb_ctrl, OID_AUTO, debug, CTLFLAG_RW, &usb_ctrl_debug, 0, "Debug level"); #endif static int usb_no_boot_wait = 0; TUNABLE_INT("hw.usb.no_boot_wait", &usb_no_boot_wait); SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RD|CTLFLAG_TUN, &usb_no_boot_wait, 0, "No USB device enumerate waiting at boot."); static int usb_no_suspend_wait = 0; TUNABLE_INT("hw.usb.no_suspend_wait", &usb_no_suspend_wait); SYSCTL_INT(_hw_usb, OID_AUTO, no_suspend_wait, CTLFLAG_RW|CTLFLAG_TUN, &usb_no_suspend_wait, 0, "No USB device waiting at system suspend."); static int usb_no_shutdown_wait = 0; TUNABLE_INT("hw.usb.no_shutdown_wait", &usb_no_shutdown_wait); SYSCTL_INT(_hw_usb, OID_AUTO, no_shutdown_wait, CTLFLAG_RW|CTLFLAG_TUN, &usb_no_shutdown_wait, 0, "No USB device waiting at system shutdown."); static devclass_t usb_devclass; static device_method_t usb_methods[] = { DEVMETHOD(device_probe, usb_probe), DEVMETHOD(device_attach, usb_attach), DEVMETHOD(device_detach, usb_detach), DEVMETHOD(device_suspend, usb_suspend), DEVMETHOD(device_resume, usb_resume), DEVMETHOD(device_shutdown, usb_shutdown), {0, 0} }; static driver_t usb_driver = { .name = "usbus", .methods = usb_methods, .size = 0, }; /* Host Only Drivers */ DRIVER_MODULE(usbus, ohci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, uhci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, ehci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, xhci, usb_driver, usb_devclass, 0, 0); /* Device Only Drivers */ DRIVER_MODULE(usbus, at91_udp, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, musbotg, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usbus, uss820, usb_driver, usb_devclass, 0, 0); /*------------------------------------------------------------------------* * usb_probe * * This function is called from "{ehci,ohci,uhci}_pci_attach()". *------------------------------------------------------------------------*/ static int usb_probe(device_t dev) { DPRINTF("\n"); return (0); } static void usb_root_mount_rel(struct usb_bus *bus) { if (bus->bus_roothold != NULL) { DPRINTF("Releasing root mount hold %p\n", bus->bus_roothold); root_mount_rel(bus->bus_roothold); bus->bus_roothold = NULL; } } /*------------------------------------------------------------------------* * usb_attach *------------------------------------------------------------------------*/ static int usb_attach(device_t dev) { struct usb_bus *bus = device_get_ivars(dev); DPRINTF("\n"); if (bus == NULL) { device_printf(dev, "USB device has no ivars\n"); return (ENXIO); } if (usb_no_boot_wait == 0) { /* delay vfs_mountroot until the bus is explored */ bus->bus_roothold = root_mount_hold(device_get_nameunit(dev)); } usb_attach_sub(dev, bus); return (0); /* return success */ } /*------------------------------------------------------------------------* * usb_detach *------------------------------------------------------------------------*/ static int usb_detach(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } /* Stop power watchdog */ usb_callout_drain(&bus->power_wdog); /* Let the USB explore process detach all devices. */ usb_root_mount_rel(bus); USB_BUS_LOCK(bus); /* Queue detach job */ usb_proc_msignal(&bus->explore_proc, &bus->detach_msg[0], &bus->detach_msg[1]); /* Wait for detach to complete */ usb_proc_mwait(&bus->explore_proc, &bus->detach_msg[0], &bus->detach_msg[1]); +#if USB_HAVE_UGEN + /* Wait for cleanup to complete */ + usb_proc_mwait(&bus->explore_proc, + &bus->cleanup_msg[0], &bus->cleanup_msg[1]); +#endif USB_BUS_UNLOCK(bus); /* Get rid of USB callback processes */ usb_proc_free(&bus->giant_callback_proc); usb_proc_free(&bus->non_giant_callback_proc); /* Get rid of USB explore process */ usb_proc_free(&bus->explore_proc); /* Get rid of control transfer process */ usb_proc_free(&bus->control_xfer_proc); #if USB_HAVE_PF usbpf_detach(bus); #endif return (0); } /*------------------------------------------------------------------------* * usb_suspend *------------------------------------------------------------------------*/ static int usb_suspend(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } USB_BUS_LOCK(bus); usb_proc_msignal(&bus->explore_proc, &bus->suspend_msg[0], &bus->suspend_msg[1]); if (usb_no_suspend_wait == 0) { /* wait for suspend callback to be executed */ usb_proc_mwait(&bus->explore_proc, &bus->suspend_msg[0], &bus->suspend_msg[1]); } USB_BUS_UNLOCK(bus); return (0); } /*------------------------------------------------------------------------* * usb_resume *------------------------------------------------------------------------*/ static int usb_resume(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } USB_BUS_LOCK(bus); usb_proc_msignal(&bus->explore_proc, &bus->resume_msg[0], &bus->resume_msg[1]); USB_BUS_UNLOCK(bus); return (0); } /*------------------------------------------------------------------------* * usb_bus_reset_async_locked *------------------------------------------------------------------------*/ void usb_bus_reset_async_locked(struct usb_bus *bus) { USB_BUS_LOCK_ASSERT(bus, MA_OWNED); DPRINTF("\n"); if (bus->reset_msg[0].hdr.pm_qentry.tqe_prev != NULL || bus->reset_msg[1].hdr.pm_qentry.tqe_prev != NULL) { DPRINTF("Reset already pending\n"); return; } device_printf(bus->parent, "Resetting controller\n"); usb_proc_msignal(&bus->explore_proc, &bus->reset_msg[0], &bus->reset_msg[1]); } /*------------------------------------------------------------------------* * usb_shutdown *------------------------------------------------------------------------*/ static int usb_shutdown(device_t dev) { struct usb_bus *bus = device_get_softc(dev); DPRINTF("\n"); if (bus == NULL) { /* was never setup properly */ return (0); } device_printf(bus->bdev, "Controller shutdown\n"); USB_BUS_LOCK(bus); usb_proc_msignal(&bus->explore_proc, &bus->shutdown_msg[0], &bus->shutdown_msg[1]); if (usb_no_shutdown_wait == 0) { /* wait for shutdown callback to be executed */ usb_proc_mwait(&bus->explore_proc, &bus->shutdown_msg[0], &bus->shutdown_msg[1]); } USB_BUS_UNLOCK(bus); device_printf(bus->bdev, "Controller shutdown complete\n"); return (0); } /*------------------------------------------------------------------------* * usb_bus_explore * * This function is used to explore the device tree from the root. *------------------------------------------------------------------------*/ static void usb_bus_explore(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (bus->no_explore != 0) return; if (udev != NULL) { USB_BUS_UNLOCK(bus); uhub_explore_handle_re_enumerate(udev); USB_BUS_LOCK(bus); } if (udev != NULL && udev->hub != NULL) { if (bus->do_probe) { bus->do_probe = 0; bus->driver_added_refcount++; } if (bus->driver_added_refcount == 0) { /* avoid zero, hence that is memory default */ bus->driver_added_refcount = 1; } #ifdef DDB /* * The following three lines of code are only here to * recover from DDB: */ usb_proc_rewakeup(&bus->control_xfer_proc); usb_proc_rewakeup(&bus->giant_callback_proc); usb_proc_rewakeup(&bus->non_giant_callback_proc); #endif USB_BUS_UNLOCK(bus); #if USB_HAVE_POWERD /* * First update the USB power state! */ usb_bus_powerd(bus); #endif /* Explore the Root USB HUB. */ (udev->hub->explore) (udev); USB_BUS_LOCK(bus); } usb_root_mount_rel(bus); } /*------------------------------------------------------------------------* * usb_bus_detach * * This function is used to detach the device tree from the root. *------------------------------------------------------------------------*/ static void usb_bus_detach(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; device_t dev; bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; dev = bus->bdev; /* clear the softc */ device_set_softc(dev, NULL); USB_BUS_UNLOCK(bus); /* detach children first */ mtx_lock(&Giant); bus_generic_detach(dev); mtx_unlock(&Giant); /* * Free USB device and all subdevices, if any. */ usb_free_device(udev, 0); USB_BUS_LOCK(bus); /* clear bdev variable last */ bus->bdev = NULL; } /*------------------------------------------------------------------------* * usb_bus_suspend * * This function is used to suspend the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_suspend(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; usb_error_t err; uint8_t do_unlock; DPRINTF("\n"); bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev == NULL || bus->bdev == NULL) return; USB_BUS_UNLOCK(bus); /* * We use the shutdown event here because the suspend and * resume events are reserved for the USB port suspend and * resume. The USB system suspend is implemented like full * shutdown and all connected USB devices will be disconnected * subsequently. At resume all USB devices will be * re-connected again. */ bus_generic_shutdown(bus->bdev); do_unlock = usbd_enum_lock(udev); err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX); if (err) device_printf(bus->bdev, "Could not unconfigure root HUB\n"); USB_BUS_LOCK(bus); bus->hw_power_state = 0; bus->no_explore = 1; USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power != NULL) (bus->methods->set_hw_power) (bus); if (bus->methods->set_hw_power_sleep != NULL) (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SUSPEND); if (do_unlock) usbd_enum_unlock(udev); USB_BUS_LOCK(bus); } /*------------------------------------------------------------------------* * usb_bus_resume * * This function is used to resume the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_resume(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; usb_error_t err; uint8_t do_unlock; DPRINTF("\n"); bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev == NULL || bus->bdev == NULL) return; USB_BUS_UNLOCK(bus); do_unlock = usbd_enum_lock(udev); #if 0 DEVMETHOD(usb_take_controller, NULL); /* dummy */ #endif USB_TAKE_CONTROLLER(device_get_parent(bus->bdev)); USB_BUS_LOCK(bus); bus->hw_power_state = USB_HW_POWER_CONTROL | USB_HW_POWER_BULK | USB_HW_POWER_INTERRUPT | USB_HW_POWER_ISOC | USB_HW_POWER_NON_ROOT_HUB; bus->no_explore = 0; USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power_sleep != NULL) (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_RESUME); if (bus->methods->set_hw_power != NULL) (bus->methods->set_hw_power) (bus); /* restore USB configuration to index 0 */ err = usbd_set_config_index(udev, 0); if (err) device_printf(bus->bdev, "Could not configure root HUB\n"); /* probe and attach */ err = usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY); if (err) { device_printf(bus->bdev, "Could not probe and " "attach root HUB\n"); } if (do_unlock) usbd_enum_unlock(udev); USB_BUS_LOCK(bus); } /*------------------------------------------------------------------------* * usb_bus_reset * * This function is used to reset the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_reset(struct usb_proc_msg *pm) { struct usb_bus *bus; DPRINTF("\n"); bus = ((struct usb_bus_msg *)pm)->bus; if (bus->bdev == NULL || bus->no_explore != 0) return; /* a suspend and resume will reset the USB controller */ usb_bus_suspend(pm); usb_bus_resume(pm); } /*------------------------------------------------------------------------* * usb_bus_shutdown * * This function is used to shutdown the USB controller. *------------------------------------------------------------------------*/ static void usb_bus_shutdown(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *udev; usb_error_t err; uint8_t do_unlock; bus = ((struct usb_bus_msg *)pm)->bus; udev = bus->devices[USB_ROOT_HUB_ADDR]; if (udev == NULL || bus->bdev == NULL) return; USB_BUS_UNLOCK(bus); bus_generic_shutdown(bus->bdev); do_unlock = usbd_enum_lock(udev); err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX); if (err) device_printf(bus->bdev, "Could not unconfigure root HUB\n"); USB_BUS_LOCK(bus); bus->hw_power_state = 0; bus->no_explore = 1; USB_BUS_UNLOCK(bus); if (bus->methods->set_hw_power != NULL) (bus->methods->set_hw_power) (bus); if (bus->methods->set_hw_power_sleep != NULL) (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SHUTDOWN); if (do_unlock) usbd_enum_unlock(udev); USB_BUS_LOCK(bus); } +/*------------------------------------------------------------------------* + * usb_bus_cleanup + * + * This function is used to cleanup leftover USB character devices. + *------------------------------------------------------------------------*/ +#if USB_HAVE_UGEN static void +usb_bus_cleanup(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_fs_privdata *pd; + + bus = ((struct usb_bus_msg *)pm)->bus; + + while ((pd = LIST_FIRST(&bus->pd_cleanup_list)) != NULL) { + + LIST_REMOVE(pd, pd_next); + USB_BUS_UNLOCK(bus); + + usb_destroy_dev_sync(pd); + + USB_BUS_LOCK(bus); + } +} +#endif + +static void usb_power_wdog(void *arg) { struct usb_bus *bus = arg; USB_BUS_LOCK_ASSERT(bus, MA_OWNED); usb_callout_reset(&bus->power_wdog, 4 * hz, usb_power_wdog, arg); #ifdef DDB /* * The following line of code is only here to recover from * DDB: */ usb_proc_rewakeup(&bus->explore_proc); /* recover from DDB */ #endif #if USB_HAVE_POWERD USB_BUS_UNLOCK(bus); usb_bus_power_update(bus); USB_BUS_LOCK(bus); #endif } /*------------------------------------------------------------------------* * usb_bus_attach * * This function attaches USB in context of the explore thread. *------------------------------------------------------------------------*/ static void usb_bus_attach(struct usb_proc_msg *pm) { struct usb_bus *bus; struct usb_device *child; device_t dev; usb_error_t err; enum usb_dev_speed speed; bus = ((struct usb_bus_msg *)pm)->bus; dev = bus->bdev; DPRINTF("\n"); switch (bus->usbrev) { case USB_REV_1_0: speed = USB_SPEED_FULL; device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n"); break; case USB_REV_1_1: speed = USB_SPEED_FULL; device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n"); break; case USB_REV_2_0: speed = USB_SPEED_HIGH; device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n"); break; case USB_REV_2_5: speed = USB_SPEED_VARIABLE; device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n"); break; case USB_REV_3_0: speed = USB_SPEED_SUPER; device_printf(bus->bdev, "5.0Gbps Super Speed USB v3.0\n"); break; default: device_printf(bus->bdev, "Unsupported USB revision\n"); usb_root_mount_rel(bus); return; } /* default power_mask value */ bus->hw_power_state = USB_HW_POWER_CONTROL | USB_HW_POWER_BULK | USB_HW_POWER_INTERRUPT | USB_HW_POWER_ISOC | USB_HW_POWER_NON_ROOT_HUB; USB_BUS_UNLOCK(bus); /* make sure power is set at least once */ if (bus->methods->set_hw_power != NULL) { (bus->methods->set_hw_power) (bus); } /* allocate the Root USB device */ child = usb_alloc_device(bus->bdev, bus, NULL, 0, 0, 1, speed, USB_MODE_HOST); if (child) { err = usb_probe_and_attach(child, USB_IFACE_INDEX_ANY); if (!err) { if ((bus->devices[USB_ROOT_HUB_ADDR] == NULL) || (bus->devices[USB_ROOT_HUB_ADDR]->hub == NULL)) { err = USB_ERR_NO_ROOT_HUB; } } } else { err = USB_ERR_NOMEM; } USB_BUS_LOCK(bus); if (err) { device_printf(bus->bdev, "Root HUB problem, error=%s\n", usbd_errstr(err)); usb_root_mount_rel(bus); } /* set softc - we are ready */ device_set_softc(dev, bus); /* start watchdog */ usb_power_wdog(bus); } /*------------------------------------------------------------------------* * usb_attach_sub * * This function creates a thread which runs the USB attach code. *------------------------------------------------------------------------*/ static void usb_attach_sub(device_t dev, struct usb_bus *bus) { const char *pname = device_get_nameunit(dev); mtx_lock(&Giant); if (usb_devclass_ptr == NULL) usb_devclass_ptr = devclass_find("usbus"); mtx_unlock(&Giant); #if USB_HAVE_PF usbpf_attach(bus); #endif /* Initialise USB process messages */ bus->explore_msg[0].hdr.pm_callback = &usb_bus_explore; bus->explore_msg[0].bus = bus; bus->explore_msg[1].hdr.pm_callback = &usb_bus_explore; bus->explore_msg[1].bus = bus; bus->detach_msg[0].hdr.pm_callback = &usb_bus_detach; bus->detach_msg[0].bus = bus; bus->detach_msg[1].hdr.pm_callback = &usb_bus_detach; bus->detach_msg[1].bus = bus; bus->attach_msg[0].hdr.pm_callback = &usb_bus_attach; bus->attach_msg[0].bus = bus; bus->attach_msg[1].hdr.pm_callback = &usb_bus_attach; bus->attach_msg[1].bus = bus; bus->suspend_msg[0].hdr.pm_callback = &usb_bus_suspend; bus->suspend_msg[0].bus = bus; bus->suspend_msg[1].hdr.pm_callback = &usb_bus_suspend; bus->suspend_msg[1].bus = bus; bus->resume_msg[0].hdr.pm_callback = &usb_bus_resume; bus->resume_msg[0].bus = bus; bus->resume_msg[1].hdr.pm_callback = &usb_bus_resume; bus->resume_msg[1].bus = bus; bus->reset_msg[0].hdr.pm_callback = &usb_bus_reset; bus->reset_msg[0].bus = bus; bus->reset_msg[1].hdr.pm_callback = &usb_bus_reset; bus->reset_msg[1].bus = bus; bus->shutdown_msg[0].hdr.pm_callback = &usb_bus_shutdown; bus->shutdown_msg[0].bus = bus; bus->shutdown_msg[1].hdr.pm_callback = &usb_bus_shutdown; bus->shutdown_msg[1].bus = bus; +#if USB_HAVE_UGEN + LIST_INIT(&bus->pd_cleanup_list); + bus->cleanup_msg[0].hdr.pm_callback = &usb_bus_cleanup; + bus->cleanup_msg[0].bus = bus; + bus->cleanup_msg[1].hdr.pm_callback = &usb_bus_cleanup; + bus->cleanup_msg[1].bus = bus; +#endif /* Create USB explore and callback processes */ if (usb_proc_create(&bus->giant_callback_proc, &bus->bus_mtx, pname, USB_PRI_MED)) { device_printf(dev, "WARNING: Creation of USB Giant " "callback process failed.\n"); } else if (usb_proc_create(&bus->non_giant_callback_proc, &bus->bus_mtx, pname, USB_PRI_HIGH)) { device_printf(dev, "WARNING: Creation of USB non-Giant " "callback process failed.\n"); } else if (usb_proc_create(&bus->explore_proc, &bus->bus_mtx, pname, USB_PRI_MED)) { device_printf(dev, "WARNING: Creation of USB explore " "process failed.\n"); } else if (usb_proc_create(&bus->control_xfer_proc, &bus->bus_mtx, pname, USB_PRI_MED)) { device_printf(dev, "WARNING: Creation of USB control transfer " "process failed.\n"); } else { /* Get final attach going */ USB_BUS_LOCK(bus); usb_proc_msignal(&bus->explore_proc, &bus->attach_msg[0], &bus->attach_msg[1]); USB_BUS_UNLOCK(bus); /* Do initial explore */ usb_needs_explore(bus, 1); } } SYSUNINIT(usb_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb_bus_unload, NULL); /*------------------------------------------------------------------------* * usb_bus_mem_flush_all_cb *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA static void usb_bus_mem_flush_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align) { usb_pc_cpu_flush(pc); } #endif /*------------------------------------------------------------------------* * usb_bus_mem_flush_all - factored out code *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA void usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) { if (cb) { cb(bus, &usb_bus_mem_flush_all_cb); } } #endif /*------------------------------------------------------------------------* * usb_bus_mem_alloc_all_cb *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA static void usb_bus_mem_alloc_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align) { /* need to initialize the page cache */ pc->tag_parent = bus->dma_parent_tag; if (usb_pc_alloc_mem(pc, pg, size, align)) { bus->alloc_failed = 1; } } #endif /*------------------------------------------------------------------------* * usb_bus_mem_alloc_all - factored out code * * Returns: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ uint8_t usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, usb_bus_mem_cb_t *cb) { bus->alloc_failed = 0; mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent), NULL, MTX_DEF | MTX_RECURSE); usb_callout_init_mtx(&bus->power_wdog, &bus->bus_mtx, 0); TAILQ_INIT(&bus->intr_q.head); #if USB_HAVE_BUSDMA usb_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags, dmat, &bus->bus_mtx, NULL, 32, USB_BUS_DMA_TAG_MAX); #endif if ((bus->devices_max > USB_MAX_DEVICES) || (bus->devices_max < USB_MIN_DEVICES) || (bus->devices == NULL)) { DPRINTFN(0, "Devices field has not been " "initialised properly\n"); bus->alloc_failed = 1; /* failure */ } #if USB_HAVE_BUSDMA if (cb) { cb(bus, &usb_bus_mem_alloc_all_cb); } #endif if (bus->alloc_failed) { usb_bus_mem_free_all(bus, cb); } return (bus->alloc_failed); } /*------------------------------------------------------------------------* * usb_bus_mem_free_all_cb *------------------------------------------------------------------------*/ #if USB_HAVE_BUSDMA static void usb_bus_mem_free_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align) { usb_pc_free_mem(pc); } #endif /*------------------------------------------------------------------------* * usb_bus_mem_free_all - factored out code *------------------------------------------------------------------------*/ void usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) { #if USB_HAVE_BUSDMA if (cb) { cb(bus, &usb_bus_mem_free_all_cb); } usb_dma_tag_unsetup(bus->dma_parent_tag); #endif mtx_destroy(&bus->bus_mtx); } /* convenience wrappers */ void usb_proc_explore_mwait(struct usb_device *udev, void *pm1, void *pm2) { usb_proc_mwait(&udev->bus->explore_proc, pm1, pm2); } void * usb_proc_explore_msignal(struct usb_device *udev, void *pm1, void *pm2) { return (usb_proc_msignal(&udev->bus->explore_proc, pm1, pm2)); } void usb_proc_explore_lock(struct usb_device *udev) { USB_BUS_LOCK(udev->bus); } void usb_proc_explore_unlock(struct usb_device *udev) { USB_BUS_UNLOCK(udev->bus); } Index: stable/8/sys/dev/usb/usb_bus.h =================================================================== --- stable/8/sys/dev/usb/usb_bus.h (revision 278294) +++ stable/8/sys/dev/usb/usb_bus.h (revision 278295) @@ -1,109 +1,115 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * * 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_BUS_H_ #define _USB_BUS_H_ +struct usb_fs_privdata; + /* * The following structure defines the USB explore message sent to the USB * explore process. */ struct usb_bus_msg { struct usb_proc_msg hdr; struct usb_bus *bus; }; /* * The following structure defines the USB statistics structure. */ struct usb_bus_stat { uint32_t uds_requests[4]; }; /* * The following structure defines an USB BUS. There is one USB BUS * for every Host or Device controller. */ struct usb_bus { struct usb_bus_stat stats_err; struct usb_bus_stat stats_ok; struct root_hold_token *bus_roothold; /* * There are two callback processes. One for Giant locked * callbacks. One for non-Giant locked callbacks. This should * avoid congestion and reduce response time in most cases. */ struct usb_process giant_callback_proc; struct usb_process non_giant_callback_proc; /* Explore process */ struct usb_process explore_proc; /* Control request process */ struct usb_process control_xfer_proc; struct usb_bus_msg explore_msg[2]; struct usb_bus_msg detach_msg[2]; struct usb_bus_msg attach_msg[2]; struct usb_bus_msg suspend_msg[2]; struct usb_bus_msg resume_msg[2]; struct usb_bus_msg reset_msg[2]; struct usb_bus_msg shutdown_msg[2]; +#if USB_HAVE_UGEN + struct usb_bus_msg cleanup_msg[2]; + LIST_HEAD(,usb_fs_privdata) pd_cleanup_list; +#endif /* * This mutex protects the USB hardware: */ struct mtx bus_mtx; struct usb_xfer_queue intr_q; struct usb_callout power_wdog; /* power management */ device_t parent; device_t bdev; /* filled by HC driver */ #if USB_HAVE_BUSDMA struct usb_dma_parent_tag dma_parent_tag[1]; struct usb_dma_tag dma_tags[USB_BUS_DMA_TAG_MAX]; #endif struct usb_bus_methods *methods; /* filled by HC driver */ struct usb_device **devices; struct ifnet *ifp; /* only for USB Packet Filter */ usb_power_mask_t hw_power_state; /* see USB_HW_POWER_XXX */ usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; uint16_t isoc_time_last; /* in milliseconds */ uint8_t alloc_failed; /* Set if memory allocation failed. */ uint8_t driver_added_refcount; /* Current driver generation count */ enum usb_revision usbrev; /* USB revision. See "USB_REV_XXX". */ uint8_t devices_max; /* maximum number of USB devices */ uint8_t do_probe; /* set if USB should be re-probed */ uint8_t no_explore; /* don't explore USB ports */ }; #endif /* _USB_BUS_H_ */ Index: stable/8/sys/dev/usb/usb_dev.c =================================================================== --- stable/8/sys/dev/usb/usb_dev.c (revision 278294) +++ stable/8/sys/dev/usb/usb_dev.c (revision 278295) @@ -1,2303 +1,2303 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2006-2008 Hans Petter Selasky. All rights reserved. * * 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/... */ #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 #if USB_HAVE_UGEN #ifdef USB_DEBUG static int usb_fifo_debug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device"); SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_TUN, &usb_fifo_debug, 0, "Debug Level"); TUNABLE_INT("hw.usb.dev.debug", &usb_fifo_debug); #endif #if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \ ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000))) #define USB_UCRED struct ucred *ucred, #else #define USB_UCRED #endif /* 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(void); 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_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 }; 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 dissappear 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(cpd->udev); mtx_lock(&usb_ref_lock); /* * Set "is_uref" after grabbing the default SX lock */ crd->is_uref = 1; } /* 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) { - cpd->udev->refcount--; - cv_broadcast(&cpd->udev->ref_cv); + 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; - cpd->udev->refcount--; - cv_broadcast(&cpd->udev->ref_cv); + if (--(cpd->udev->refcount) == 0) + cv_broadcast(&cpd->udev->ref_cv); } mtx_unlock(&usb_ref_lock); } static struct usb_fifo * usb_fifo_alloc(void) { struct usb_fifo *f; f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); if (f) { cv_init(&f->cv_io, "FIFO-IO"); cv_init(&f->cv_drain, "FIFO-DRAIN"); f->refcount = 1; } 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(); 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_mtx = &udev->device_mtx; 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(); 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_mtx = &udev->device_mtx; 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--; - /* prevent any write flush */ - f->flag_iserror = 1; /* 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); 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 */ f->curr_cpd = NULL; /* 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); 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_wait_sig(&f->cv_io, f->priv_mtx); 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, ep; DPRINTFN(2, "%s fflags=0x%08x\n", dev->si_name, 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); ep = cpd->ep_addr = pd->ep_addr; 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); } /* 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 and 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 fflags; int resid; 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); fflags = cpd->fflags; f = refs.rxfifo; if (f == NULL) { /* should not happen */ usb_unref_device(cpd, &refs); return (EPERM); } resid = uio->uio_resid; 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 fflags; int resid; 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); fflags = cpd->fflags; f = refs.txfifo; if (f == NULL) { /* should not happen */ usb_unref_device(cpd, &refs); return (EPERM); } resid = uio->uio_resid; 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; 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; 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; 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); if (f->flag_isselect) { selwakeup(&f->selinfo); f->flag_isselect = 0; } if (f->async_p != NULL) { PROC_LOCK(f->async_p); 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(); f_rx = usb_fifo_alloc(); 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_mtx = priv_mtx; 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_mtx = priv_mtx; 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) { usb_fifo_free_buffer(f); /* allocate an endpoint */ f->free_q.ifq_maxlen = nbuf; f->used_q.ifq_maxlen = nbuf; f->queue_data = usb_alloc_mbufs( M_USBDEV, &f->free_q, bufsize, nbuf); if ((f->queue_data == NULL) && bufsize && nbuf) { return (ENOMEM); } 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) { if (f->queue_data) { /* free old buffer */ free(f->queue_data, M_USBDEV); f->queue_data = NULL; } /* reset queues */ memset(&f->free_q, 0, sizeof(f->free_q)); memset(&f->used_q, 0, sizeof(f->used_q)); } 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); if (ps == NULL) { return (ps); } /* 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 */ Index: stable/8/sys/dev/usb/usb_device.c =================================================================== --- stable/8/sys/dev/usb/usb_device.c (revision 278294) +++ stable/8/sys/dev/usb/usb_device.c (revision 278295) @@ -1,2896 +1,2878 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * * 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. */ #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 /* function prototypes */ 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); static void usbd_set_device_strings(struct usb_device *); #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: */ int usb_template = 0; TUNABLE_INT("hw.usb.usb_template", &usb_template); SYSCTL_INT(_hw_usb, OID_AUTO, template, CTLFLAG_RW | CTLFLAG_TUN, &usb_template, 0, "Selected USB device side template"); /* English is default language */ static int usb_lang_id = 0x0009; static int usb_lang_mask = 0x00FF; TUNABLE_INT("hw.usb.usb_lang_id", &usb_lang_id); SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RW | CTLFLAG_TUN, &usb_lang_id, 0, "Preferred USB language ID"); TUNABLE_INT("hw.usb.usb_lang_mask", &usb_lang_mask); SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RW | CTLFLAG_TUN, &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 accross 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) && ((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 accross 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) && ((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) { struct usb_bus_methods *methods; 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; TAILQ_INIT(&ep->endpoint_q.head); ep->endpoint_q.command = &usbd_pipe_start; /* the pipe is not supported by the hardware */ if (ep->methods == NULL) return; /* 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); } -#if USB_HAVE_UGEN -static uint16_t -usb_get_refcount(struct usb_device *udev) -{ - if (usb_proc_is_called_from(&udev->bus->explore_proc) || - usb_proc_is_called_from(&udev->bus->control_xfer_proc)) - return (1); - return (2); -} -#endif - /*------------------------------------------------------------------------* - * usb_wait_pending_ref_locked + * usb_wait_pending_refs * * This function will wait for any USB references to go away before - * returning and disable further USB device refcounting on the - * specified USB device. This function is used when detaching a USB - * device. + * returning. This function is used before freeing a USB device. *------------------------------------------------------------------------*/ static void -usb_wait_pending_ref_locked(struct usb_device *udev) +usb_wait_pending_refs(struct usb_device *udev) { #if USB_HAVE_UGEN - const uint16_t refcount = usb_get_refcount(udev); + DPRINTF("Refcount = %d\n", (int)udev->refcount); - DPRINTF("Refcount = %d\n", (int)refcount); - + mtx_lock(&usb_ref_lock); + udev->refcount--; while (1) { /* wait for any pending references to go away */ - mtx_lock(&usb_ref_lock); - if (udev->refcount == refcount) { - /* prevent further refs being taken */ + if (udev->refcount == 0) { + /* prevent further refs being taken, if any */ udev->refcount = USB_DEV_REF_MAX; - mtx_unlock(&usb_ref_lock); break; } - usbd_enum_unlock(udev); cv_wait(&udev->ref_cv, &usb_ref_lock); - mtx_unlock(&usb_ref_lock); - (void) usbd_enum_lock(udev); } -#endif -} - -/*------------------------------------------------------------------------* - * usb_ref_restore_locked - * - * This function will restore the reference count value after a call - * to "usb_wait_pending_ref_locked()". - *------------------------------------------------------------------------*/ -static void -usb_ref_restore_locked(struct usb_device *udev) -{ -#if USB_HAVE_UGEN - const uint16_t refcount = usb_get_refcount(udev); - - DPRINTF("Refcount = %d\n", (int)refcount); - - /* restore reference count and wakeup waiters, if any */ - mtx_lock(&usb_ref_lock); - udev->refcount = refcount; - cv_broadcast(&udev->ref_cv); 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) { usb_linux_free_device(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) free(udev->cdesc, M_USB); 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, M_USB, 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_FREE) goto cleanup; if (cmd == USB_CFG_INIT) { sx_assert(&udev->enum_sx, SA_LOCKED); /* check for in-use endpoints */ 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) { /* * This typically indicates a * more serious error. */ err = USB_ERR_IN_USE; } else { /* 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 (err) return (err); } memset(&ips, 0, sizeof(ips)); ep_curr = 0; ep_max = 0; while ((id = usb_idesc_foreach(udev->cdesc, &ips))) { /* check for interface overflow */ if (ips.iface_index == USB_IFACE_MAX) break; /* crazy */ 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; } } 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))) { if (temp == USB_EP_MAX) break; /* crazy */ 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; /* optimalisation */ id = (struct usb_interface_descriptor *)ed; } } /* NOTE: It is valid to have no interfaces and no endpoints! */ if (cmd == USB_CFG_ALLOC) { udev->ifaces_max = ips.iface_index; 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; } } 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; } USB_BUS_LOCK(udev->bus); udev->endpoints_max = ep_max; /* reset any ongoing clear-stall */ udev->ep_curr = NULL; USB_BUS_UNLOCK(udev->bus); } done: 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); /* cleanup */ if (udev->ifaces != NULL) free(udev->ifaces, M_USB); if (udev->endpoints != NULL) free(udev->endpoints, M_USB); udev->ifaces = NULL; udev->endpoints = NULL; 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; 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) { /* lookup the current USB transfer, if any */ xfer = ep->endpoint_q.curr; } else { xfer = NULL; } /* * If "xfer" is non-NULL the "set_stall" method will * complete the USB transfer like in case of a timeout * setting the error code "USB_ERR_STALLED". */ (udev->bus->methods->set_stall) (udev, xfer, 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 up the current or next transfer, if any */ usb_command_wrapper(&ep->endpoint_q, ep->endpoint_q.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; 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"); } } if (device_detach(dev)) { goto error; } } 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); - /* wait for pending refs to go away */ - usb_wait_pending_ref_locked(udev); - /* * 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_ref_restore_locked(udev); } /*------------------------------------------------------------------------* * 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) { /* 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) { 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); mtx_lock(&udev->device_mtx); /* Start clear stall callback */ usbd_transfer_start(udev->ctrl_xfer[1]); /* Change lock */ mtx_unlock(&udev->device_mtx); USB_BUS_LOCK(udev->bus); } /*------------------------------------------------------------------------* * 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; uint8_t *scratch_ptr; usb_error_t err; uint8_t device_index; uint8_t config_index; uint8_t config_quirk; uint8_t set_config_failed; uint8_t do_unlock; 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 (udev == NULL) { return (NULL); } /* 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); 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 maybe the USB " "template module has not been 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; } /* * 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_enum_lock(udev); scratch_ptr = udev->scratch.data; 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_enum_unlock(udev); /* assume 100mA bus powered for now. Changed when configured. */ udev->power = USB_MIN_POWER; /* fetch the vendor and product strings from the device */ usbd_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 (config_index == 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; } } } if (set_config_failed == 0 && config_index == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_SYNC_CACHE) == 0 && usb_test_quirk(&uaa, UQ_MSC_NO_GETMAXLUN) == 0) { /* * Try to figure out if there are any MSC quirks we * should apply automatically: */ err = usb_msc_auto_quirk(udev, 0); if (err != 0) { set_config_failed = 1; goto repeat_set_config; } } 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> at %s\n", udev->ugen_name, usb_get_manufacturer(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; 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); } pd->cdev = make_dev(&usb_devsw, 0, uid, gid, mode, "%s", devname); if (pd->cdev == NULL) { DPRINTFN(0, "Failed to create device %s\n", devname); free(pd, M_USBDEV); return (NULL); } /* XXX setting si_drv1 and creating the device is not atomic! */ pd->cdev->si_drv1 = pd; 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; - destroy_dev(pd->cdev); + mtx_lock(&usb_ref_lock); + bus = devclass_get_softc(usb_devclass_ptr, pd->bus_index); + mtx_unlock(&usb_ref_lock); - free(pd, M_USBDEV); + if (bus == NULL) { + usb_destroy_dev_sync(pd); + return; + } + + USB_BUS_LOCK(bus); + LIST_INSERT_HEAD(&bus->pd_cleanup_list, pd, pd_next); + /* get cleanup going */ + usb_proc_msignal(&bus->explore_proc, + &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 printf("%s: <%s> at %s (disconnected)\n", udev->ugen_name, usb_get_manufacturer(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(&udev->bus->non_giant_callback_proc, &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); 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 */ static void usbd_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_enum_lock(udev); temp_ptr = (char *)udev->scratch.data; temp_size = sizeof(udev->scratch.data); vendor_id = UGETW(udd->idVendor); product_id = UGETW(udd->idProduct); /* 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_enum_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 /*------------------------------------------------------------------------* * usb_notify_addq * * This function will generate events for dev. *------------------------------------------------------------------------*/ #ifndef BURN_BRIDGES static void usb_notify_addq_compat(const char *type, struct usb_device *udev) { char *data = NULL; const char *ntype; struct malloc_type *mt; const size_t buf_size = 512; /* Convert notify type */ if (strcmp(type, "ATTACH") == 0) ntype = "+"; else if (strcmp(type, "DETACH") == 0) ntype = "-"; else return; mtx_lock(&malloc_mtx); mt = malloc_desc2type("bus"); /* XXX M_BUS */ mtx_unlock(&malloc_mtx); if (mt == NULL) return; data = malloc(buf_size, mt, M_NOWAIT); if (data == NULL) return; /* String it all together. */ snprintf(data, buf_size, "%s" #if USB_HAVE_UGEN "%s " #endif "at port=%u " "vendor=0x%04x " "product=0x%04x " "devclass=0x%02x " "devsubclass=0x%02x " "sernum=\"%s\" " "release=0x%04x " #if USB_HAVE_UGEN "on %s\n" #endif "", ntype, #if USB_HAVE_UGEN udev->ugen_name, #endif udev->port_no, UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct), udev->ddesc.bDeviceClass, udev->ddesc.bDeviceSubClass, usb_get_serial(udev), UGETW(udev->ddesc.bcdDevice) #if USB_HAVE_UGEN , udev->parent_hub != NULL ? udev->parent_hub->ugen_name : device_get_nameunit(device_get_parent(udev->bus->bdev)) #endif ); devctl_queue_data(data); } #endif static void usb_notify_addq(const char *type, struct usb_device *udev) { struct usb_interface *iface; struct sbuf *sb; int i; #ifndef BURN_BRIDGES usb_notify_addq_compat(type, udev); #endif /* 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)) { /* 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)) { /* no need to free this FIFO */ continue; } } else { /* no need to free this FIFO */ continue; } - /* wait for pending refs to go away */ - usb_wait_pending_ref_locked(udev); - /* free this FIFO */ usb_fifo_free(f); - - /* restore refcount */ - usb_ref_restore_locked(udev); } } #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 * non-zero value 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. */ mtx_lock(&Giant); return (1); } /* The following function unlocks enumerating the given USB device. */ void usbd_enum_unlock(struct usb_device *udev) { mtx_unlock(&Giant); 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. */ mtx_lock(&Giant); } /* The following function unlocks suspend and resume. */ void usbd_sr_unlock(struct usb_device *udev) { mtx_unlock(&Giant); 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 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); } Index: stable/8/sys/dev/usb/usb_device.h =================================================================== --- stable/8/sys/dev/usb/usb_device.h (revision 278294) +++ stable/8/sys/dev/usb/usb_device.h (revision 278295) @@ -1,316 +1,317 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * * 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_DEVICE_H_ #define _USB_DEVICE_H_ #ifndef USB_GLOBAL_INCLUDE_FILE #include #include #include #endif struct usb_bus_methods; struct usb_config_descriptor; struct usb_device; /* linux compat */ struct usb_fs_privdata; struct usb_hw_ep_profile; struct usb_symlink; /* UGEN */ #define USB_CTRL_XFER_MAX 2 /* "usb_parse_config()" commands */ #define USB_CFG_ALLOC 0 #define USB_CFG_FREE 1 #define USB_CFG_INIT 2 /* "usb_unconfigure()" flags */ #define USB_UNCFG_FLAG_NONE 0x00 #define USB_UNCFG_FLAG_FREE_EP0 0x02 /* endpoint zero is freed */ struct usb_udev_msg { struct usb_proc_msg hdr; struct usb_device *udev; }; /* The following four structures makes up a tree, where we have the * leaf structure, "usb_host_endpoint", first, and the root structure, * "usb_device", last. The four structures below mirror the structure * of the USB descriptors belonging to an USB configuration. Please * refer to the USB specification for a definition of "endpoints" and * "interfaces". */ struct usb_host_endpoint { struct usb_endpoint_descriptor desc; TAILQ_HEAD(, urb) bsd_urb_list; struct usb_xfer *bsd_xfer[2]; uint8_t *extra; /* Extra descriptors */ usb_frlength_t fbsd_buf_size; uint16_t extralen; uint8_t bsd_iface_index; } __aligned(USB_HOST_ALIGN); struct usb_host_interface { struct usb_interface_descriptor desc; /* the following array has size "desc.bNumEndpoint" */ struct usb_host_endpoint *endpoint; const char *string; /* iInterface string, if present */ uint8_t *extra; /* Extra descriptors */ uint16_t extralen; uint8_t bsd_iface_index; } __aligned(USB_HOST_ALIGN); /* * The following structure defines the USB device flags. */ struct usb_device_flags { enum usb_hc_mode usb_mode; /* host or device mode */ uint8_t self_powered:1; /* set if USB device is self powered */ uint8_t no_strings:1; /* set if USB device does not support * strings */ uint8_t remote_wakeup:1; /* set if remote wakeup is enabled */ uint8_t uq_bus_powered:1; /* set if BUS powered quirk is present */ /* * NOTE: Although the flags below will reach the same value * over time, but the instant values may differ, and * consequently the flags cannot be merged into one! */ uint8_t peer_suspended:1; /* set if peer is suspended */ uint8_t self_suspended:1; /* set if self is suspended */ }; /* * The following structure is used for power-save purposes. The data * in this structure is protected by the USB BUS lock. */ struct usb_power_save { usb_ticks_t last_xfer_time; /* copy of "ticks" */ usb_size_t type_refs[4]; /* transfer reference count */ usb_size_t read_refs; /* data read references */ usb_size_t write_refs; /* data write references */ }; /* * The following structure is used when trying to allocate hardware * endpoints for an USB configuration in USB device side mode. */ struct usb_hw_ep_scratch_sub { const struct usb_hw_ep_profile *pf; uint16_t max_frame_size; uint8_t hw_endpoint_out; uint8_t hw_endpoint_in; uint8_t needs_ep_type; uint8_t needs_in:1; uint8_t needs_out:1; }; /* * The following structure is used when trying to allocate hardware * endpoints for an USB configuration in USB device side mode. */ struct usb_hw_ep_scratch { struct usb_hw_ep_scratch_sub ep[USB_EP_MAX]; struct usb_hw_ep_scratch_sub *ep_max; struct usb_config_descriptor *cd; struct usb_device *udev; struct usb_bus_methods *methods; uint8_t bmOutAlloc[(USB_EP_MAX + 15) / 16]; uint8_t bmInAlloc[(USB_EP_MAX + 15) / 16]; }; /* * The following structure is used when generating USB descriptors * from USB templates. */ struct usb_temp_setup { void *buf; usb_size_t size; enum usb_dev_speed usb_speed; uint8_t self_powered; uint8_t bNumEndpoints; uint8_t bInterfaceNumber; uint8_t bAlternateSetting; uint8_t bConfigurationValue; usb_error_t err; }; /* * The scratch area for USB devices. Access to this structure is * protected by the enumeration SX lock. */ union usb_device_scratch { struct usb_hw_ep_scratch hw_ep_scratch[1]; struct usb_temp_setup temp_setup[1]; struct { struct usb_xfer dummy; struct usb_setup_params parm; } xfer_setup[1]; uint8_t data[255]; }; /* * The following structure defines an USB device. There exists one of * these structures for every USB device. */ struct usb_device { /* generic clear stall message */ struct usb_udev_msg cs_msg[2]; struct sx enum_sx; struct sx sr_sx; struct mtx device_mtx; struct cv ctrlreq_cv; struct cv ref_cv; struct usb_interface *ifaces; struct usb_endpoint ctrl_ep; /* Control Endpoint 0 */ struct usb_endpoint *endpoints; struct usb_power_save pwr_save;/* power save data */ struct usb_bus *bus; /* our USB BUS */ device_t parent_dev; /* parent device */ struct usb_device *parent_hub; struct usb_device *parent_hs_hub; /* high-speed parent HUB */ struct usb_config_descriptor *cdesc; /* full config descr */ struct usb_hub *hub; /* only if this is a hub */ struct usb_xfer *ctrl_xfer[USB_CTRL_XFER_MAX]; struct usb_temp_data *usb_template_ptr; struct usb_endpoint *ep_curr; /* current clear stall endpoint */ #if USB_HAVE_UGEN struct usb_fifo *fifo[USB_FIFO_MAX]; struct usb_symlink *ugen_symlink; /* our generic symlink */ struct usb_fs_privdata *ctrl_dev; /* Control Endpoint 0 device node */ LIST_HEAD(,usb_fs_privdata) pd_list; char ugen_name[20]; /* name of ugenX.X device */ #endif usb_ticks_t plugtime; /* copy of "ticks" */ enum usb_dev_state state; enum usb_dev_speed speed; uint16_t refcount; #define USB_DEV_REF_MAX 0xffff uint16_t power; /* mA the device uses */ uint16_t langid; /* language for strings */ uint8_t address; /* device addess */ uint8_t device_index; /* device index in "bus->devices" */ uint8_t controller_slot_id; /* controller specific value */ uint8_t next_config_index; /* used by USB_RE_ENUM_SET_CONFIG */ uint8_t curr_config_index; /* current configuration index */ uint8_t curr_config_no; /* current configuration number */ uint8_t depth; /* distance from root HUB */ uint8_t port_index; /* parent HUB port index */ uint8_t port_no; /* parent HUB port number */ uint8_t hs_hub_addr; /* high-speed HUB address */ uint8_t hs_port_no; /* high-speed HUB port number */ uint8_t driver_added_refcount; /* our driver added generation count */ uint8_t power_mode; /* see USB_POWER_XXX */ uint8_t re_enumerate_wait; /* set if re-enum. is in progress */ #define USB_RE_ENUM_DONE 0 #define USB_RE_ENUM_START 1 #define USB_RE_ENUM_PWR_OFF 2 #define USB_RE_ENUM_SET_CONFIG 3 uint8_t ifaces_max; /* number of interfaces present */ uint8_t endpoints_max; /* number of endpoints present */ /* the "flags" field is write-protected by "bus->mtx" */ struct usb_device_flags flags; struct usb_endpoint_descriptor ctrl_ep_desc; /* for endpoint 0 */ struct usb_endpoint_ss_comp_descriptor ctrl_ep_comp_desc; /* for endpoint 0 */ struct usb_device_descriptor ddesc; /* device descriptor */ char *serial; /* serial number, can be NULL */ char *manufacturer; /* manufacturer string, can be NULL */ char *product; /* product string, can be NULL */ #if USB_HAVE_COMPAT_LINUX /* Linux compat */ struct usb_device_descriptor descriptor; struct usb_host_endpoint ep0; struct usb_interface *linux_iface_start; struct usb_interface *linux_iface_end; struct usb_host_endpoint *linux_endpoint_start; struct usb_host_endpoint *linux_endpoint_end; uint16_t devnum; #endif uint32_t clear_stall_errors; /* number of clear-stall failures */ uint16_t autoQuirk[USB_MAX_AUTO_QUIRK]; /* dynamic quirks */ union usb_device_scratch scratch; }; /* globals */ extern int usb_template; /* function prototypes */ const char *usb_statestr(enum usb_dev_state state); 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); #if USB_HAVE_UGEN struct usb_fs_privdata *usb_make_dev(struct usb_device *, const char *, int, int, int, uid_t, gid_t, int); void usb_destroy_dev(struct usb_fs_privdata *); +void usb_destroy_dev_sync(struct usb_fs_privdata *); #endif usb_error_t usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index); void usb_detach_device(struct usb_device *, uint8_t, uint8_t); usb_error_t usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index); usb_error_t usbd_set_config_index(struct usb_device *udev, uint8_t index); usb_error_t usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep, uint8_t do_stall); usb_error_t usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend); void usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len); void usb_free_device(struct usb_device *, uint8_t); void usb_linux_free_device(struct usb_device *dev); uint8_t usb_peer_can_wakeup(struct usb_device *udev); struct usb_endpoint *usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep); void usb_set_device_state(struct usb_device *, enum usb_dev_state); enum usb_dev_state usb_get_device_state(struct usb_device *); uint8_t usbd_enum_lock(struct usb_device *); void usbd_enum_unlock(struct usb_device *); void usbd_sr_lock(struct usb_device *); void usbd_sr_unlock(struct usb_device *); uint8_t usbd_enum_is_locked(struct usb_device *); #if USB_HAVE_TT_SUPPORT void uhub_tt_buffer_reset_async_locked(struct usb_device *, struct usb_endpoint *); #endif uint8_t uhub_count_active_host_ports(struct usb_device *, enum usb_dev_speed); #endif /* _USB_DEVICE_H_ */ Index: stable/8/sys/dev/usb =================================================================== --- stable/8/sys/dev/usb (revision 278294) +++ stable/8/sys/dev/usb (revision 278295) Property changes on: stable/8/sys/dev/usb ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys/dev/usb:r277136 Index: stable/8/sys/dev =================================================================== --- stable/8/sys/dev (revision 278294) +++ stable/8/sys/dev (revision 278295) Property changes on: stable/8/sys/dev ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys/dev:r277136 Index: stable/8/sys =================================================================== --- stable/8/sys (revision 278294) +++ stable/8/sys (revision 278295) Property changes on: stable/8/sys ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/sys:r277136