Re: [PATCH v6 part1 8/8] usb: block suspension of superspeed port while hispeed peer is active

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

 



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.williams@xxxxxxxxx>
> 
> 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.williams@xxxxxxxxx>

> @@ -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 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