On Fri, Feb 21, 2014 at 4:11 PM, Dan Williams <dan.j.williams@xxxxxxxxx> wrote: > Until all root hubs have been discovered and tier mismatch identified, > port power control is unreliable. When a USB3 port is paired with an > incorrect peer port there is chance a connected device will downgrade > its connection to its USB2 pins. The downgrade occurs when the USB3 > port is powered off while the USB2 peer port is powered. Once the peer > ports are correctly assigned the kernel will prevent the disconnect > scenario. So, wait for the peer ports to be correctly assigned before > allowing a USB3 port to power off. > > Now that khubd is a workqueue, and provided that khubd arranges to > re-queue enumeration work until all hubs (connected at driver load) are > enumerated, a drain_workqueue() operation will wait for all initial > discovery to complete. This requires the delayed hub->init_work to move > to khubd context. Care is taken to maintain parallel waiting for all > root hubs power on delays. However, since hub_quiesce() is called with > the device lock held it can no longer synchronously wait for init_work > to flush. > > The workqueue subsystem enforces that no un-chained work (work queued > outside the workqueue context, e.g. hub_irq) may be queued for the > duration of the drain. Add infrastructure to defer and replay > kick_khubd() requests. > > Not Signed-off, pending resolution of the locking horror in hub_quiesce() > --- > drivers/usb/core/hcd.c | 1 > drivers/usb/core/hub.c | 88 ++++++++++++++++++++++++++++++++++-------- > drivers/usb/core/hub.h | 4 +- > drivers/usb/core/port.c | 4 ++ > drivers/usb/host/xhci-pci.c | 5 ++ > drivers/usb/host/xhci-plat.c | 4 ++ > drivers/usb/host/xhci.c | 10 +++++ > drivers/usb/host/xhci.h | 5 ++ > include/linux/device.h | 5 ++ > include/linux/usb.h | 1 > include/linux/usb/hcd.h | 3 + > 11 files changed, 112 insertions(+), 18 deletions(-) > > diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c > index b3ecaf32f2aa..f79bd06edc5f 100644 > --- a/drivers/usb/core/hcd.c > +++ b/drivers/usb/core/hcd.c > @@ -2424,6 +2424,7 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, > dev_dbg (dev, "hcd alloc failed\n"); > return NULL; > } > + INIT_LIST_HEAD(&hcd->khubd_defer); > if (primary_hcd == NULL) { > hcd->bandwidth_mutex = kmalloc(sizeof(*hcd->bandwidth_mutex), > GFP_KERNEL); > diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c > index e0518af66af9..894d0e47b563 100644 > --- a/drivers/usb/core/hub.c > +++ b/drivers/usb/core/hub.c > @@ -562,14 +562,20 @@ static void hub_release(struct kref *kref) > kfree(hub); > } > > -static void kick_khubd(struct usb_hub *hub) > +static int khubd_draining; > + > +static void kick_khubd_unlocked(struct usb_hub *hub) > { > struct usb_interface *intf = to_usb_interface(hub->intfdev); > - unsigned long flags; > + struct usb_hcd *hcd = bus_to_hcd(hub->hdev->bus); > > - /* Suppress autosuspend until khubd runs */ > - spin_lock_irqsave(&hub_event_lock, flags); > - if (!hub->disconnected) { > + if (hub->disconnected) > + return; > + > + if (khubd_draining) { > + list_move_tail(&hub->defer, &hcd->khubd_defer); The deferq needs to be global, not per hcd... > + } else { > + /* Suppress autosuspend until khubd runs */ > usb_autopm_get_interface_no_resume(intf); > kref_get(&hub->kref); > if (!queue_work(khubd_wq, &hub->event_work)) { > @@ -577,6 +583,14 @@ static void kick_khubd(struct usb_hub *hub) > kref_put(&hub->kref, hub_release); > } > } > +} > + > +static void kick_khubd(struct usb_hub *hub) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&hub_event_lock, flags); > + kick_khubd_unlocked(hub); > spin_unlock_irqrestore(&hub_event_lock, flags); > } > > @@ -588,6 +602,33 @@ void usb_kick_khubd(struct usb_device *hdev) > kick_khubd(hub); > } > > +void usb_drain_khubd(struct usb_hcd *hcd) > +{ > + static DEFINE_MUTEX(drain_mutex); > + struct usb_hub *hub, *_h; > + > + /* prevent concurrent drainers */ > + mutex_lock(&drain_mutex); > + > + /* flag khubd to start deferring work */ > + spin_lock_irq(&hub_event_lock); > + khubd_draining = 1; > + spin_unlock_irq(&hub_event_lock); > + > + drain_workqueue(khubd_wq); > + > + spin_lock_irq(&hub_event_lock); > + khubd_draining = 0; > + list_for_each_entry_safe(hub, _h, &hcd->khubd_defer, defer) { > + list_del_init(&hub->defer); > + kick_khubd_unlocked(hub); > + } ...otherwise deferred works from other hcds get dropped. -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html