On Thu, 13 Mar 2014, Dan Williams wrote:

> Sorry for the delay, I know it's distracting to lose context like this.

No problem.

> > In fact, do we need the pre/post_modify actions at all?  Wouldn't it be 
> > sufficient to do just a pm_runtime_get_sync on the SuperSpeed peer?
> 
> I think we miss a SuperSpeed suspend event that way:
> 
> CPU1                                            CPU2
> link_peers(usb3, usb2)
>   pm_runtime_get(usb3) /* wake */
>                                                 usb_port_runtime_suspend(usb2)
>   usb3->peer = usb2
>                                                   /* usb2->peer == NULL */
>                                                   if (usb2->peer)
>                                                     pm_runtime_put(usb3) /* 
> skip */
>   usb2->peer = usb3
>   pm_runtime_get(usb3) /* on behalf of usb2 */
>   pm_runtime_put(usb3) /* miss suspend */
> 
> 
> ...but we don't necessarily need to wake the usb3 port at link time,
> that will happen as a matter of course of adding the reference on behalf
> of the usb2 port.  See below.

You're right.  But maybe a simple solution would be for the
runtime-suspend and -resume routines to acquire the usb_port_peer_mutex
whenever they are called for a USB-2 port.  The mutex would protect
their accesses of port->peer, and I don't think it would result in any
deadlocks.

> Another revision in this patch is making sure the port resume sequence
> waits the hub-specified power-on delay before proceeding with reconnect
> timeouts.  This cleaned up cases where the usb2-port powered on and
> connected before usb3.

Okay, good.

> 8<-----------
> usb: block suspension of superspeed port while hispeed peer is active
> 
> From: Dan Williams <dan.j.willi...@intel.com>
> 
> ClearPortFeature(PORT_POWER) on a usb3 port places the port in either a
> DSPORT.Powered-off-detect / DSPORT.Powered-off-reset loop, or the
> DSPORT.Powered-off state.  There is no way to ensure that RX
> terminations will persist in this state, so it is possible a device will
> degrade to its usb2 connection.  Prevent this by blocking power-off of a
> usb3 port while its usb2 peer is active, and powering on a usb3 port
> before its usb2 peer.
> 
> By default the latency between peer power-on events is 0.  In order for
> the device to not see usb2 active while usb3 is still powering up inject
> the hub recommended power_on_good delay.
> 
> Signed-off-by: Dan Williams <dan.j.willi...@intel.com>

> @@ -810,14 +805,9 @@ int usb_hub_clear_tt_buffer(struct urb *urb)
>  }
>  EXPORT_SYMBOL_GPL(usb_hub_clear_tt_buffer);
>  
> -/* If do_delay is false, return the number of milliseconds the caller
> - * needs to delay.
> - */
> -static unsigned hub_power_on(struct usb_hub *hub, bool do_delay)
> +static void hub_power_on(struct usb_hub *hub)
>  {
>       int port1;
> -     unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2;
> -     unsigned delay;
>  
>       /* Enable power on each port.  Some hubs have reserved values
>        * of LPSM (> 2) in their descriptors, even though they are
> @@ -836,12 +826,6 @@ static unsigned hub_power_on(struct usb_hub *hub, bool 
> do_delay)
>               else
>                       usb_clear_port_feature(hub->hdev, port1,
>                                               USB_PORT_FEAT_POWER);
> -
> -     /* Wait at least 100 msec for power to become stable */
> -     delay = max(pgood_delay, (unsigned) 100);
> -     if (do_delay)
> -             msleep(delay);
> -     return delay;
>  }

I'm not sure about this.  Separating out hub_power_on_good_delay into
its own routine is fine, and so is changing the return type to void.  
But only one place calls hub_power_on without asking for the delay, so
we probably should keep the do_delay argument.  Otherwise you end up 
with msleep(hub_power_on_good_delay(hub)) sprinkled all over the place.

> @@ -1073,10 +1059,11 @@ static void hub_activate(struct usb_hub *hub, enum 
> hub_activation_type type)
>                                                       "under this hub\n.");
>                               }
>                       }
> -                     hub_power_on(hub, true);
> +                     hub_power_on(hub);
>               } else {
> -                     hub_power_on(hub, true);
> +                     hub_power_on(hub);
>               }
> +             msleep(hub_power_on_good_delay(hub));

Like here.

> @@ -178,9 +200,40 @@ static int link_peers(struct usb_port *left, struct 
> usb_port *right)
>               return rc;
>       }
>  
> +     /*
> +      * By default the SuperSpeed port will not suspend while its
> +      * ->peer link is NULL,

Hmmm.  Is this really true?  If so, what is to prevent it?

And what about non-visible SS ports attached to built-in devices?  
They might not have any peers, but they are prime candidates for
powering off.

> but we need to wake the HiSpeed port to
> +      * make sure we don't race setting ->peer with
> +      * usb_port_runtime_suspend().  Otherwise we may miss a suspend
> +      * event for the SuperSpeed port.
> +      */
> +     l_hdev = to_usb_device(left->dev.parent->parent);
> +     r_hdev = to_usb_device(right->dev.parent->parent);
> +     if (hub_is_superspeed(l_hdev)) {

Would it be worthwhile adding an "is_superspeed" field to the port 
structure?

> +             ss_port = left;
> +             WARN_ON(hub_is_superspeed(r_hdev));
> +             hs_port = right;
> +     } else {
> +             ss_port = right;
> +             WARN_ON(!hub_is_superspeed(r_hdev));
> +             hs_port = left;
> +     }
> +     pm_runtime_get_sync(&hs_port->dev);
> +
>       left->peer = right;
>       right->peer = left;
>  
> +     /*
> +      * The SuperSpeed reference is dropped when the HiSpeed port in
> +      * this relationship suspends, i.e. when it is safe to allow a
> +      * SuperSpeed connection to drop since there is no risk of a
> +      * device degrading to its suspended HiSpeed connection
> +      *
> +      * Also drop the HiSpeed ref taken above
> +      */
> +     pm_runtime_get(&ss_port->dev);
> +     pm_runtime_put(&hs_port->dev);
> +
>       return 0;
>  }
>  
> @@ -200,14 +253,39 @@ static void link_peers_report(struct usb_port *left, 
> struct usb_port *right)
>  
>  static void unlink_peers(struct usb_port *left, struct usb_port *right)
>  {
> +     struct usb_device *l_hdev;
> +     struct usb_port *ss_port;
> +
>       WARN(right->peer != left || left->peer != right,
>                       "%s and %s are not peers?\n",
>                       dev_name(&left->dev), dev_name(&right->dev));
>  
> +     /*
> +      * We wake the SuperSpeed port when unlinking since it must be
> +      * active while ->peer is NULL.  We wake the HiSpeed port to
> +      * make sure we don't race it's usb_port_runtime_resume() event
> +      * which takes a SuperSpeed ref when ->peer is !NULL
> +      */
> +     pm_runtime_get_sync(&left->dev);
> +     pm_runtime_get_sync(&right->dev);
> +
>       sysfs_remove_link(&left->dev.kobj, "peer");
>       right->peer = NULL;
>       sysfs_remove_link(&right->dev.kobj, "peer");
>       left->peer = NULL;
> +
> +     l_hdev = to_usb_device(left->dev.parent->parent);
> +     if (hub_is_superspeed(l_hdev))
> +             ss_port = left;
> +     else
> +             ss_port = right;
> +
> +     /* drop the SuperSpeed ref held on behalf of the active HiSpeed port */
> +     pm_runtime_put(&ss_port->dev);
> +
> +     /* drop the refs taken above */
> +     pm_runtime_put(&left->dev);
> +     pm_runtime_put(&right->dev);

If we adopt the change mentioned near the top of this email then
link_peers merely has to do pm_runtime_get_sync on the USB-3 port if
the USB-2 port is active.  Similarly, unlink_peers merely has to do a
pm_runtime_put on the USB-3 port if the USB-2 port is active.

Alan Stern

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to