Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Dan Williams and committed by
Greg Kroah-Hartman
3bfd659b 8b1ba80c

+83 -22
+2
drivers/usb/core/hub.h
··· 84 84 * @port_owner: port's owner 85 85 * @peer: related usb2 and usb3 ports (share the same connector) 86 86 * @connect_type: port's connect type 87 + * @location: opaque representation of platform connector location 87 88 * @portnum: port index num based one 88 89 * @power_is_on: port's power state 89 90 * @did_runtime_put: port has done pm_runtime_put(). ··· 95 94 struct usb_dev_state *port_owner; 96 95 struct usb_port *peer; 97 96 enum usb_port_connect_type connect_type; 97 + usb_port_location_t location; 98 98 u8 portnum; 99 99 unsigned power_is_on:1; 100 100 unsigned did_runtime_put:1;
+52 -4
drivers/usb/core/port.c
··· 188 188 } 189 189 190 190 /* 191 - * Set the default peer port for root hubs, or via the upstream peer 192 - * relationship for all other hubs 191 + * For each usb hub device in the system check to see if it is in the 192 + * peer domain of the given port_dev, and if it is check to see if it 193 + * has a port that matches the given port by location 194 + */ 195 + static int match_location(struct usb_device *peer_hdev, void *p) 196 + { 197 + int port1; 198 + struct usb_hcd *hcd, *peer_hcd; 199 + struct usb_port *port_dev = p, *peer; 200 + struct usb_hub *peer_hub = usb_hub_to_struct_hub(peer_hdev); 201 + struct usb_device *hdev = to_usb_device(port_dev->dev.parent->parent); 202 + 203 + if (!peer_hub) 204 + return 0; 205 + 206 + hcd = bus_to_hcd(hdev->bus); 207 + peer_hcd = bus_to_hcd(peer_hdev->bus); 208 + /* peer_hcd is provisional until we verify it against the known peer */ 209 + if (peer_hcd != hcd->shared_hcd) 210 + return 0; 211 + 212 + for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) { 213 + peer = peer_hub->ports[port1 - 1]; 214 + if (peer && peer->location == port_dev->location) { 215 + link_peers(port_dev, peer); 216 + return 1; /* done */ 217 + } 218 + } 219 + 220 + return 0; 221 + } 222 + 223 + /* 224 + * Find the peer port either via explicit platform firmware "location" 225 + * data, the peer hcd for root hubs, or the upstream peer relationship 226 + * for all other hubs. 193 227 */ 194 228 static void find_and_link_peer(struct usb_hub *hub, int port1) 195 229 { ··· 232 198 struct usb_device *peer_hdev; 233 199 struct usb_hub *peer_hub; 234 200 235 - if (!hdev->parent) { 201 + /* 202 + * If location data is available then we can only peer this port 203 + * by a location match, not the default peer (lest we create a 204 + * situation where we need to go back and undo a default peering 205 + * when the port is later peered by location data) 206 + */ 207 + if (port_dev->location) { 208 + /* we link the peer in match_location() if found */ 209 + usb_for_each_dev(port_dev, match_location); 210 + return; 211 + } else if (!hdev->parent) { 236 212 struct usb_hcd *hcd = bus_to_hcd(hdev->bus); 237 213 struct usb_hcd *peer_hcd = hcd->shared_hcd; 238 214 ··· 269 225 if (!peer_hub || port1 > peer_hdev->maxchild) 270 226 return; 271 227 228 + /* 229 + * we found a valid default peer, last check is to make sure it 230 + * does not have location data 231 + */ 272 232 peer = peer_hub->ports[port1 - 1]; 273 - if (peer) 233 + if (peer && peer->location == 0) 274 234 link_peers(port_dev, peer); 275 235 } 276 236
+23 -18
drivers/usb/core/usb-acpi.c
··· 85 85 } 86 86 EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); 87 87 88 - static int usb_acpi_check_port_connect_type(struct usb_device *hdev, 89 - acpi_handle handle, int port1) 88 + static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, 89 + struct acpi_pld_info *pld) 90 90 { 91 91 enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; 92 92 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 93 - struct usb_hub *hub = usb_hub_to_struct_hub(hdev); 94 - struct acpi_pld_info *pld; 95 93 union acpi_object *upc; 96 94 acpi_status status; 97 - int ret = 0; 98 - 99 - if (!hub) 100 - return 0; 101 95 102 96 /* 103 97 * According to ACPI Spec 9.13. PLD indicates whether usb port is ··· 101 107 * a usb device is directly hard-wired to the port. If no visible and 102 108 * no connectable, the port would be not used. 103 109 */ 104 - status = acpi_get_physical_device_location(handle, &pld); 105 - if (ACPI_FAILURE(status)) 106 - return -ENODEV; 107 - 108 110 status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); 109 111 upc = buffer.pointer; 110 112 if (!upc || (upc->type != ACPI_TYPE_PACKAGE) 111 113 || upc->package.count != 4) { 112 - ret = -EINVAL; 113 114 goto out; 114 115 } 115 116 ··· 115 126 connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED; 116 127 else if (!pld->user_visible) 117 128 connect_type = USB_PORT_NOT_USED; 118 - hub->ports[port1 - 1]->connect_type = connect_type; 119 - 120 129 out: 121 - ACPI_FREE(pld); 122 130 kfree(upc); 123 - return ret; 131 + return connect_type; 124 132 } 133 + 134 + 135 + /* 136 + * Private to usb-acpi, all the core needs to know is that 137 + * port_dev->location is non-zero when it has been set by the firmware. 138 + */ 139 + #define USB_ACPI_LOCATION_VALID (1 << 31) 125 140 126 141 static struct acpi_device *usb_acpi_find_companion(struct device *dev) 127 142 { ··· 157 164 } else if (is_usb_port(dev)) { 158 165 struct usb_port *port_dev = to_usb_port(dev); 159 166 int port1 = port_dev->portnum; 167 + struct acpi_pld_info *pld; 168 + acpi_handle *handle; 169 + acpi_status status; 160 170 161 171 /* Get the struct usb_device point of port's hub */ 162 172 udev = to_usb_device(dev->parent->parent); ··· 190 194 if (!adev) 191 195 return NULL; 192 196 } 193 - usb_acpi_check_port_connect_type(udev, adev->handle, port1); 197 + handle = adev->handle; 198 + status = acpi_get_physical_device_location(handle, &pld); 199 + if (ACPI_FAILURE(status) || !pld) 200 + return adev; 201 + 202 + port_dev->location = USB_ACPI_LOCATION_VALID 203 + | pld->group_token << 8 | pld->group_position; 204 + port_dev->connect_type = usb_acpi_get_connect_type(handle, pld); 205 + ACPI_FREE(pld); 206 + 194 207 return adev; 195 208 } 196 209
+6
drivers/usb/core/usb.h
··· 171 171 extern int usb_devio_init(void); 172 172 extern void usb_devio_cleanup(void); 173 173 174 + /* 175 + * Firmware specific cookie identifying a port's location. '0' == no location 176 + * data available 177 + */ 178 + typedef u32 usb_port_location_t; 179 + 174 180 /* internal notify stuff */ 175 181 extern void usb_notify_add_device(struct usb_device *udev); 176 182 extern void usb_notify_remove_device(struct usb_device *udev);