summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/dev/usb/usb_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/dev/usb/usb_device.c')
-rw-r--r--freebsd/sys/dev/usb/usb_device.c116
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);
}