summaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/usb-acpi.c
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2014-05-20 18:08:40 -0700
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-05-27 16:38:52 -0700
commit3bfd659baec822f54e4acb0734669e671d853a35 (patch)
treec44ad6218e8b028e5595dd80e9c29213a8e96298 /drivers/usb/core/usb-acpi.c
parent8b1ba80c59fb3e77f9e1761480617d5ea9ee159c (diff)
downloadtalos-obmc-linux-3bfd659baec822f54e4acb0734669e671d853a35.tar.gz
talos-obmc-linux-3bfd659baec822f54e4acb0734669e671d853a35.zip
usb: find internal hub tier mismatch via acpi
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@rowland.harvard.edu> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Dan Williams <dan.j.williams@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core/usb-acpi.c')
-rw-r--r--drivers/usb/core/usb-acpi.c41
1 files changed, 23 insertions, 18 deletions
diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c
index d3e7e1b4125e..2776cfe64c09 100644
--- a/drivers/usb/core/usb-acpi.c
+++ b/drivers/usb/core/usb-acpi.c
@@ -85,19 +85,13 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
}
EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
-static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
- acpi_handle handle, int port1)
+static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
+ struct acpi_pld_info *pld)
{
enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
- struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
- struct acpi_pld_info *pld;
union acpi_object *upc;
acpi_status status;
- int ret = 0;
-
- if (!hub)
- return 0;
/*
* According to ACPI Spec 9.13. PLD indicates whether usb port is
@@ -107,15 +101,10 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
* a usb device is directly hard-wired to the port. If no visible and
* no connectable, the port would be not used.
*/
- status = acpi_get_physical_device_location(handle, &pld);
- if (ACPI_FAILURE(status))
- return -ENODEV;
-
status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
upc = buffer.pointer;
if (!upc || (upc->type != ACPI_TYPE_PACKAGE)
|| upc->package.count != 4) {
- ret = -EINVAL;
goto out;
}
@@ -126,14 +115,18 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
else if (!pld->user_visible)
connect_type = USB_PORT_NOT_USED;
- hub->ports[port1 - 1]->connect_type = connect_type;
-
out:
- ACPI_FREE(pld);
kfree(upc);
- return ret;
+ return connect_type;
}
+
+/*
+ * Private to usb-acpi, all the core needs to know is that
+ * port_dev->location is non-zero when it has been set by the firmware.
+ */
+#define USB_ACPI_LOCATION_VALID (1 << 31)
+
static struct acpi_device *usb_acpi_find_companion(struct device *dev)
{
struct usb_device *udev;
@@ -164,6 +157,9 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
} else if (is_usb_port(dev)) {
struct usb_port *port_dev = to_usb_port(dev);
int port1 = port_dev->portnum;
+ struct acpi_pld_info *pld;
+ acpi_handle *handle;
+ acpi_status status;
/* Get the struct usb_device point of port's hub */
udev = to_usb_device(dev->parent->parent);
@@ -194,7 +190,16 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
if (!adev)
return NULL;
}
- usb_acpi_check_port_connect_type(udev, adev->handle, port1);
+ handle = adev->handle;
+ status = acpi_get_physical_device_location(handle, &pld);
+ if (ACPI_FAILURE(status) || !pld)
+ return adev;
+
+ port_dev->location = USB_ACPI_LOCATION_VALID
+ | pld->group_token << 8 | pld->group_position;
+ port_dev->connect_type = usb_acpi_get_connect_type(handle, pld);
+ ACPI_FREE(pld);
+
return adev;
}
OpenPOWER on IntegriCloud