Re: [RFC PATCH v5 16/16] usb, xhci: flush initial hub discovery to gate port power control

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux