Re: [PATCH v6 part1 6/8] usb: find internal hub tier mismatch via acpi

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

 



On Mon, 3 Mar 2014, Dan Williams wrote:

> Subject: usb: find internal hub tier mismatch via acpi
> 
> From: Dan Williams <dan.j.williams@xxxxxxxxx>
> 
> ACPI identifies peer ports by setting their 'group_token' and
> 'group_position' _PLD data to the same value.  If a platform has tier
> mismatch [1] , ACPI can override the default (USB3 defined) peer port
> association for internal hubs.  External hubs follow the default peer
> association scheme.
> 
> Location data is cached as an opaque cookie in usb_port_location data.
> 
> Note that we only consider the group_token and group_position attributes
> from the _PLD data as ACPI specifies that group_token is a unique
> identifier.
> 
> When we find port location data for a port then we assume that the
> firmware will also describe its peer port.  This allows the
> implementation to only ever set the peer once.  This leads to a question
> about what happens when a pm runtime event occurs while the peer
> associations are still resolving.  Since we only ever set the peer
> information once, a USB3 port needs to be prevented from suspending
> while its ->peer pointer is NULL (implemented in a subsequent patch).
> 
> There is always the possibility that firmware mis-identifies the ports,
> but there is not much the kernel can do in that case.
> 
> [1]: xhci 1.1 appendix D figure 131
> [2]: acpi 5 section 6.1.8
> 
> [alan]: don't do default peering when acpi data present
> Suggested-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
> Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>

>  static struct usb_port *find_default_peer(struct usb_hub *hub, int port1)
>  {
> +	struct usb_port *port_dev = hub->ports[port1 - 1];
>  	struct usb_device *hdev = hub->hdev;
>  	struct usb_device *peer_hdev = NULL;
>  	struct usb_hub *peer_hub;
>  
> +	/*
> +	 * If location data is available then we can only peer this port
> +	 * by a location match, not the default peer (lest we create a
> +	 * situation where we need to go back and undo a default peering
> +	 * when the port is later peered by location data)
> +	 */
> +	if (port_dev->location)
> +		return NULL;

I think you probably also want to reject matches where
port_dev->location is 0 but the peer port does have location data.

> @@ -222,9 +233,92 @@ static void unlink_peers(struct usb_port *left, struct usb_port *right)
>  	left->peer = NULL;
>  }
>  
> +/**
> + * for_each_child_port() - invoke 'fn' on all usb_port instances beneath 'hdev'
> + * @hdev: potential hub usb_device (validated by usb_hub_to_struct_hub)
> + * @level: track recursion level to stop after reaching USB spec max depth
> + * @p: parameter to pass to 'fn'
> + * @fn: routine to invoke on each port
> + *
> + * Recursively iterate all ports (depth-first) beneath 'hdev' until 'fn'
> + * returns a non-NULL usb_port or all ports have been visited.
> + */
> +static struct usb_port *for_each_child_port(struct usb_device *hdev, int level,
> +		void *p, struct usb_port * (*fn)(struct usb_port *, void *))
> +{
> +	struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
> +	int port1;
> +
> +#define MAX_HUB_DEPTH 5
> +	if (!hub || level > MAX_HUB_DEPTH || hub->disconnected
> +			|| hdev->state == USB_STATE_NOTATTACHED)
> +		return NULL;
> +
> +	level++;
> +	for (port1 = 1; port1 <= hdev->maxchild; port1++) {
> +		struct usb_port *ret, *port_dev = hub->ports[port1 - 1];
> +
> +		if (!port_dev)
> +			continue;
> +		ret = fn(port_dev, p);
> +		if (ret)
> +			return ret;
> +		ret = for_each_child_port(port_dev->child, level, p, fn);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return NULL;
> +}
> +
> +static struct usb_port *do_match_location(struct usb_port *port_dev, void *_loc)
> +{
> +	usb_port_location_t *loc = _loc;
> +
> +	if (port_dev->location == *loc)
> +		return port_dev;
> +	return NULL;
> +}
> +
> +static struct usb_port *find_port_by_location(struct usb_device *hdev,
> +		usb_port_location_t *loc)
> +{
> +	return for_each_child_port(hdev, 1, loc, do_match_location);
> +}
> +
> +void usb_set_hub_port_location(struct usb_device *hdev, int port1,
> +		usb_port_location_t location)
> +{
> +	struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
> +	struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
> +	struct usb_hcd *peer_hcd = hcd->shared_hcd;
> +	struct usb_port *peer, *port_dev;
> +	struct usb_device *peer_hdev;
> +
> +	if (!hub)
> +		return;
> +
> +	port_dev = hub->ports[port1 - 1];
> +	port_dev->location = location;
> +	if (port_dev->peer) {
> +		WARN_ON(1);
> +		return;
> +	}
> +
> +	if (!peer_hcd || !peer_hcd->rh_registered)
> +		return;
> +
> +	peer_hdev = peer_hcd->self.root_hub;
> +	peer = find_port_by_location(peer_hdev, &port_dev->location);
> +	if (!peer)
> +		return;
> +
> +	link_peers(port_dev, peer);
> +}

This all looks more complicated than necessary.  Why not define a 
usb_for_each_port function, like the usb_for_each_dev routine in usb.c?

And instead of calling it in a new usb_set_hub_port_location routine, 
why not call it from find_default_peer?

> @@ -247,9 +341,12 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
>  	if (retval)
>  		goto error_register;
>  
> -	peer = find_default_peer(hub, port1);
> -	if (peer)
> -		link_peers(port_dev, peer);
> +	if (!port_dev->peer) {

port_dev->peer should never be set at this point, if you don't try to 
do the location matching during the ACPI callback.

> +		struct usb_port *peer = find_default_peer(hub, port1);
> +
> +		if (peer)
> +			link_peers(port_dev, peer);
> +	}
>  
>  	pm_runtime_set_active(&port_dev->dev);
>  
> diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c
> index ce0f4e8d81cb..e1e1f40f6950 100644
> --- a/drivers/usb/core/usb-acpi.c
> +++ b/drivers/usb/core/usb-acpi.c
> @@ -41,6 +41,17 @@ bool usb_acpi_power_manageable(struct usb_device *hdev, int index)
>  }
>  EXPORT_SYMBOL_GPL(usb_acpi_power_manageable);
>  
> +static void usb_acpi_check_port_peer(struct usb_device *hdev,
> +	acpi_handle *handle, int port1, struct acpi_pld_info *pld)
> +{
> +	if (!pld)
> +		return;
> +
> +	#define USB_ACPI_LOCATION_VALID (1 << 31)
> +	usb_set_hub_port_location(hdev, port1, USB_ACPI_LOCATION_VALID
> +		| pld->group_token << 8 | pld->group_position);
> +}

Why call an external function?  Just store the value in the appropriate 
port_dev->location.

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