diff options
Diffstat (limited to 'freebsd/sys/dev/usb/usb_device.c')
-rw-r--r-- | freebsd/sys/dev/usb/usb_device.c | 116 |
1 files changed, 93 insertions, 23 deletions
diff --git a/freebsd/sys/dev/usb/usb_device.c b/freebsd/sys/dev/usb/usb_device.c index 78ae3d8c..e18c32df 100644 --- a/freebsd/sys/dev/usb/usb_device.c +++ b/freebsd/sys/dev/usb/usb_device.c @@ -96,7 +96,7 @@ 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 void usbd_clear_stall_proc(struct usb_proc_msg *_pm); +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 @@ -437,6 +437,65 @@ usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep) } /*------------------------------------------------------------------------* + * usb_wait_pending_ref_locked + * + * 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. + *------------------------------------------------------------------------*/ +static void +usb_wait_pending_ref_locked(struct usb_device *udev) +{ +#if USB_HAVE_UGEN + const uint16_t refcount = + usb_proc_is_called_from( + &udev->bus->explore_proc) ? 1 : 2; + + DPRINTF("Refcount = %d\n", (int)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 */ + 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_proc_is_called_from( + &udev->bus->explore_proc) ? 1 : 2; + + 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 @@ -1097,6 +1156,9 @@ usb_detach_device(struct usb_device *udev, uint8_t iface_index, 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. @@ -1123,6 +1185,8 @@ usb_detach_device(struct usb_device *udev, uint8_t iface_index, usb_detach_device_sub(udev, &iface->subdev, &iface->pnpinfo, flag); } + + usb_ref_restore_locked(udev); } /*------------------------------------------------------------------------* @@ -1450,7 +1514,7 @@ usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend) static void usbd_clear_stall_proc(struct usb_proc_msg *_pm) { - struct usb_clear_stall_msg *pm = (void *)_pm; + struct usb_udev_msg *pm = (void *)_pm; struct usb_device *udev = pm->udev; /* Change lock */ @@ -1679,10 +1743,14 @@ usb_alloc_device(device_t parent_dev, struct usb_bus *bus, err = usbd_setup_device_desc(udev, NULL); if (err != 0) { - /* XXX try to re-enumerate the device */ + /* try to enumerate two more times */ err = usbd_req_re_enumerate(udev, NULL); - if (err) - goto done; + if (err != 0) { + err = usbd_req_re_enumerate(udev, NULL); + if (err != 0) { + goto done; + } + } } /* @@ -2038,6 +2106,8 @@ usb_free_device(struct usb_device *udev, uint8_t flag) 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 @@ -2053,23 +2123,6 @@ usb_free_device(struct usb_device *udev, uint8_t flag) usb_free_symlink(udev->ugen_symlink); udev->ugen_symlink = NULL; } -#endif - /* - * Unregister our device first which will prevent any further - * references: - */ - usb_bus_port_set_device(bus, udev->parent_hub ? - udev->parent_hub->hub->ports + udev->port_index : NULL, - NULL, USB_ROOT_HUB_ADDR); - -#if USB_HAVE_UGEN - /* wait for all pending references to go away: */ - mtx_lock(&usb_ref_lock); - udev->refcount--; - while (udev->refcount != 0) { - cv_wait(&udev->ref_cv, &usb_ref_lock); - } - mtx_unlock(&usb_ref_lock); usb_destroy_dev(udev->ctrl_dev); #endif @@ -2082,6 +2135,11 @@ usb_free_device(struct usb_device *udev, uint8_t flag) /* 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); @@ -2582,8 +2640,14 @@ usb_fifo_free_wrap(struct usb_device *udev, /* 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 @@ -2615,8 +2679,14 @@ usb_set_device_state(struct usb_device *udev, enum usb_dev_state state) DPRINTF("udev %p state %s -> %s\n", udev, usb_statestr(udev->state), usb_statestr(state)); - udev->state = 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); } |