On Fri, Feb 21, 2014 at 4:11 PM, Dan Williams <[email protected]> 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 [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html