summaryrefslogtreecommitdiffstats
path: root/sys/dev/usb
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2010-10-04 22:21:30 +0000
committerhselasky <hselasky@FreeBSD.org>2010-10-04 22:21:30 +0000
commit97650b7539017733bdc3cd4318ccd7ab926a730b (patch)
tree7df636a0fa2dd65329e74c93f332faf12841b517 /sys/dev/usb
parentc4bf79bca3ee37849267066f0dd5c166ffdac8e2 (diff)
downloadFreeBSD-src-97650b7539017733bdc3cd4318ccd7ab926a730b.zip
FreeBSD-src-97650b7539017733bdc3cd4318ccd7ab926a730b.tar.gz
Serialise USB re-enumeration with the USB explore thread.
This patch can solve problems when multiple USB devices are re-enumerated at the same time on the same bus. Approved by: thompsa (mentor)
Diffstat (limited to 'sys/dev/usb')
-rw-r--r--sys/dev/usb/usb_dev.c49
-rw-r--r--sys/dev/usb/usb_device.h1
-rw-r--r--sys/dev/usb/usb_generic.c36
-rw-r--r--sys/dev/usb/usb_hub.c22
4 files changed, 76 insertions, 32 deletions
diff --git a/sys/dev/usb/usb_dev.c b/sys/dev/usb/usb_dev.c
index cb23fcd..5ccbae6 100644
--- a/sys/dev/usb/usb_dev.c
+++ b/sys/dev/usb/usb_dev.c
@@ -964,7 +964,6 @@ 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);
@@ -1058,21 +1057,45 @@ usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread*
err = usb_ioctl_f_sub(f, cmd, addr, td);
}
KASSERT(f != NULL, ("fifo not found"));
- if (err == ENOIOCTL) {
- err = (f->methods->f_ioctl) (f, cmd, addr, fflags);
- DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err);
- if (err == ENOIOCTL) {
- if (usb_usb_ref_device(cpd, &refs)) {
- err = ENXIO;
- goto done;
- }
- 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)
+ 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)) {
+ err = ENXIO;
+ goto done;
}
- if (err == ENOIOCTL) {
+
+ 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 != 0) {
+
+ usb_unref_device(cpd, &refs);
+
+ usb_pause_mtx(NULL, hz / 128);
+
+ if (usb_ref_device(cpd, &refs, 1 /* need uref */)) {
+ err = ENXIO;
+ goto done;
+ }
}
+
done:
usb_unref_device(cpd, &refs);
return (err);
diff --git a/sys/dev/usb/usb_device.h b/sys/dev/usb/usb_device.h
index 6a02f50..e53afa2 100644
--- a/sys/dev/usb/usb_device.h
+++ b/sys/dev/usb/usb_device.h
@@ -160,6 +160,7 @@ struct usb_device {
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 */
uint8_t ifaces_max; /* number of interfaces present */
uint8_t endpoints_max; /* number of endpoints present */
diff --git a/sys/dev/usb/usb_generic.c b/sys/dev/usb/usb_generic.c
index 0cac19b..872979b 100644
--- a/sys/dev/usb/usb_generic.c
+++ b/sys/dev/usb/usb_generic.c
@@ -109,7 +109,7 @@ static int usb_gen_fill_deviceinfo(struct usb_fifo *,
static int ugen_re_enumerate(struct usb_fifo *);
static int ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int);
static uint8_t ugen_fs_get_complete(struct usb_fifo *, uint8_t *);
-static int ugen_fs_uninit(struct usb_fifo *f);
+static int ugen_fs_uninit(struct usb_fifo *f);
/* structures */
@@ -951,23 +951,19 @@ ugen_re_enumerate(struct usb_fifo *f)
if (error) {
return (error);
}
- /* get the device unconfigured */
- error = ugen_set_config(f, USB_UNCONFIG_INDEX);
- if (error) {
- return (error);
+ if (udev->flags.usb_mode != USB_MODE_HOST) {
+ /* not possible in device side mode */
+ return (ENOTTY);
}
- /* do a bus-reset */
- mtx_lock(f->priv_mtx);
- error = usbd_req_re_enumerate(udev, f->priv_mtx);
- mtx_unlock(f->priv_mtx);
-
- if (error) {
- return (ENXIO);
+ /* make sure all FIFO's are gone */
+ /* else there can be a deadlock */
+ if (ugen_fs_uninit(f)) {
+ /* ignore any errors */
+ DPRINTFN(6, "no FIFOs\n");
}
- /* restore configuration to index 0 */
- error = ugen_set_config(f, 0);
- if (error) {
- return (error);
+ if (udev->re_enumerate_wait == 0) {
+ udev->re_enumerate_wait = 1;
+ usb_needs_explore(udev->bus, 0);
}
return (0);
}
@@ -1775,9 +1771,11 @@ ugen_set_power_mode(struct usb_fifo *f, int mode)
/* if we are powered off we need to re-enumerate first */
if (old_mode == USB_POWER_MODE_OFF) {
- err = ugen_re_enumerate(f);
- if (err)
- return (err);
+ if (udev->flags.usb_mode == USB_MODE_HOST) {
+ if (udev->re_enumerate_wait == 0)
+ udev->re_enumerate_wait = 1;
+ }
+ /* set power mode will wake up the explore thread */
}
/* set new power mode */
diff --git a/sys/dev/usb/usb_hub.c b/sys/dev/usb/usb_hub.c
index 87c86d2..b8c4d0c 100644
--- a/sys/dev/usb/usb_hub.c
+++ b/sys/dev/usb/usb_hub.c
@@ -236,6 +236,26 @@ uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
/* nothing to do */
goto done;
}
+ /* check if device should be re-enumerated */
+
+ if (child->flags.usb_mode == USB_MODE_HOST) {
+ usbd_enum_lock(child);
+ if (child->re_enumerate_wait) {
+ err = usbd_set_config_index(child, USB_UNCONFIG_INDEX);
+ if (err == 0)
+ err = usbd_req_re_enumerate(child, NULL);
+ if (err == 0)
+ err = usbd_set_config_index(child, 0);
+ if (err == 0) {
+ err = usb_probe_and_attach(child,
+ USB_IFACE_INDEX_ANY);
+ }
+ child->re_enumerate_wait = 0;
+ err = 0;
+ }
+ usbd_enum_unlock(child);
+ }
+
/* check if probe and attach should be done */
if (child->driver_added_refcount != refcount) {
@@ -1763,6 +1783,8 @@ static uint8_t
usb_peer_should_wakeup(struct usb_device *udev)
{
return ((udev->power_mode == USB_POWER_MODE_ON) ||
+ (udev->driver_added_refcount != udev->bus->driver_added_refcount) ||
+ (udev->re_enumerate_wait != 0) ||
(udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) ||
(udev->pwr_save.write_refs != 0) ||
((udev->pwr_save.read_refs != 0) &&
OpenPOWER on IntegriCloud