On Mon, 3 Mar 2014, Dan Williams wrote:

> Subject: usb: find internal hub tier mismatch via acpi
> 
> From: Dan Williams <dan.j.willi...@intel.com>
> 
> 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 <st...@rowland.harvard.edu>
> Signed-off-by: Dan Williams <dan.j.willi...@intel.com>

>  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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to