aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2014-05-20 21:08:40 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-05-27 19:38:52 -0400
commit3bfd659baec822f54e4acb0734669e671d853a35 (patch)
treec44ad6218e8b028e5595dd80e9c29213a8e96298 /drivers/usb
parent8b1ba80c59fb3e77f9e1761480617d5ea9ee159c (diff)
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')
-rw-r--r--drivers/usb/core/hub.h2
-rw-r--r--drivers/usb/core/port.c56
-rw-r--r--drivers/usb/core/usb-acpi.c41
-rw-r--r--drivers/usb/core/usb.h6
4 files changed, 83 insertions, 22 deletions
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index fcad5f9d12f0..048c797f394c 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -84,6 +84,7 @@ struct usb_hub {
84 * @port_owner: port's owner 84 * @port_owner: port's owner
85 * @peer: related usb2 and usb3 ports (share the same connector) 85 * @peer: related usb2 and usb3 ports (share the same connector)
86 * @connect_type: port's connect type 86 * @connect_type: port's connect type
87 * @location: opaque representation of platform connector location
87 * @portnum: port index num based one 88 * @portnum: port index num based one
88 * @power_is_on: port's power state 89 * @power_is_on: port's power state
89 * @did_runtime_put: port has done pm_runtime_put(). 90 * @did_runtime_put: port has done pm_runtime_put().
@@ -94,6 +95,7 @@ struct usb_port {
94 struct usb_dev_state *port_owner; 95 struct usb_dev_state *port_owner;
95 struct usb_port *peer; 96 struct usb_port *peer;
96 enum usb_port_connect_type connect_type; 97 enum usb_port_connect_type connect_type;
98 usb_port_location_t location;
97 u8 portnum; 99 u8 portnum;
98 unsigned power_is_on:1; 100 unsigned power_is_on:1;
99 unsigned did_runtime_put:1; 101 unsigned did_runtime_put:1;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 9b7496b52f2a..aea54e8dfe47 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -188,8 +188,42 @@ static void unlink_peers(struct usb_port *left, struct usb_port *right)
188} 188}
189 189
190/* 190/*
191 * Set the default peer port for root hubs, or via the upstream peer 191 * For each usb hub device in the system check to see if it is in the
192 * relationship for all other hubs 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 */
195static 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 */
194static void find_and_link_peer(struct usb_hub *hub, int port1) 228static void find_and_link_peer(struct usb_hub *hub, int port1)
195{ 229{
@@ -198,7 +232,17 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
198 struct usb_device *peer_hdev; 232 struct usb_device *peer_hdev;
199 struct usb_hub *peer_hub; 233 struct usb_hub *peer_hub;
200 234
201 if (!hdev->parent) { 235 /*
236 * If location data is available then we can only peer this port
237 * by a location match, not the default peer (lest we create a
238 * situation where we need to go back and undo a default peering
239 * when the port is later peered by location data)
240 */
241 if (port_dev->location) {
242 /* we link the peer in match_location() if found */
243 usb_for_each_dev(port_dev, match_location);
244 return;
245 } else if (!hdev->parent) {
202 struct usb_hcd *hcd = bus_to_hcd(hdev->bus); 246 struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
203 struct usb_hcd *peer_hcd = hcd->shared_hcd; 247 struct usb_hcd *peer_hcd = hcd->shared_hcd;
204 248
@@ -225,8 +269,12 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
225 if (!peer_hub || port1 > peer_hdev->maxchild) 269 if (!peer_hub || port1 > peer_hdev->maxchild)
226 return; 270 return;
227 271
272 /*
273 * we found a valid default peer, last check is to make sure it
274 * does not have location data
275 */
228 peer = peer_hub->ports[port1 - 1]; 276 peer = peer_hub->ports[port1 - 1];
229 if (peer) 277 if (peer && peer->location == 0)
230 link_peers(port_dev, peer); 278 link_peers(port_dev, peer);
231} 279}
232 280
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)
85} 85}
86EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); 86EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
87 87
88static int usb_acpi_check_port_connect_type(struct usb_device *hdev, 88static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
89 acpi_handle handle, int port1) 89 struct acpi_pld_info *pld)
90{ 90{
91 enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; 91 enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
92 struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 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 union acpi_object *upc; 93 union acpi_object *upc;
96 acpi_status status; 94 acpi_status status;
97 int ret = 0;
98
99 if (!hub)
100 return 0;
101 95
102 /* 96 /*
103 * According to ACPI Spec 9.13. PLD indicates whether usb port is 97 * 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,
107 * a usb device is directly hard-wired to the port. If no visible and 101 * a usb device is directly hard-wired to the port. If no visible and
108 * no connectable, the port would be not used. 102 * no connectable, the port would be not used.
109 */ 103 */
110 status = acpi_get_physical_device_location(handle, &pld);
111 if (ACPI_FAILURE(status))
112 return -ENODEV;
113
114 status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); 104 status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
115 upc = buffer.pointer; 105 upc = buffer.pointer;
116 if (!upc || (upc->type != ACPI_TYPE_PACKAGE) 106 if (!upc || (upc->type != ACPI_TYPE_PACKAGE)
117 || upc->package.count != 4) { 107 || upc->package.count != 4) {
118 ret = -EINVAL;
119 goto out; 108 goto out;
120 } 109 }
121 110
@@ -126,14 +115,18 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
126 connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED; 115 connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
127 else if (!pld->user_visible) 116 else if (!pld->user_visible)
128 connect_type = USB_PORT_NOT_USED; 117 connect_type = USB_PORT_NOT_USED;
129 hub->ports[port1 - 1]->connect_type = connect_type;
130
131out: 118out:
132 ACPI_FREE(pld);
133 kfree(upc); 119 kfree(upc);
134 return ret; 120 return connect_type;
135} 121}
136 122
123
124/*
125 * Private to usb-acpi, all the core needs to know is that
126 * port_dev->location is non-zero when it has been set by the firmware.
127 */
128#define USB_ACPI_LOCATION_VALID (1 << 31)
129
137static struct acpi_device *usb_acpi_find_companion(struct device *dev) 130static struct acpi_device *usb_acpi_find_companion(struct device *dev)
138{ 131{
139 struct usb_device *udev; 132 struct usb_device *udev;
@@ -164,6 +157,9 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
164 } else if (is_usb_port(dev)) { 157 } else if (is_usb_port(dev)) {
165 struct usb_port *port_dev = to_usb_port(dev); 158 struct usb_port *port_dev = to_usb_port(dev);
166 int port1 = port_dev->portnum; 159 int port1 = port_dev->portnum;
160 struct acpi_pld_info *pld;
161 acpi_handle *handle;
162 acpi_status status;
167 163
168 /* Get the struct usb_device point of port's hub */ 164 /* Get the struct usb_device point of port's hub */
169 udev = to_usb_device(dev->parent->parent); 165 udev = to_usb_device(dev->parent->parent);
@@ -194,7 +190,16 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
194 if (!adev) 190 if (!adev)
195 return NULL; 191 return NULL;
196 } 192 }
197 usb_acpi_check_port_connect_type(udev, adev->handle, port1); 193 handle = adev->handle;
194 status = acpi_get_physical_device_location(handle, &pld);
195 if (ACPI_FAILURE(status) || !pld)
196 return adev;
197
198 port_dev->location = USB_ACPI_LOCATION_VALID
199 | pld->group_token << 8 | pld->group_position;
200 port_dev->connect_type = usb_acpi_get_connect_type(handle, pld);
201 ACPI_FREE(pld);
202
198 return adev; 203 return adev;
199 } 204 }
200 205
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 6afa738b5ba4..98dc08e13448 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -171,6 +171,12 @@ extern void usbfs_conn_disc_event(void);
171extern int usb_devio_init(void); 171extern int usb_devio_init(void);
172extern void usb_devio_cleanup(void); 172extern void usb_devio_cleanup(void);
173 173
174/*
175 * Firmware specific cookie identifying a port's location. '0' == no location
176 * data available
177 */
178typedef u32 usb_port_location_t;
179
174/* internal notify stuff */ 180/* internal notify stuff */
175extern void usb_notify_add_device(struct usb_device *udev); 181extern void usb_notify_add_device(struct usb_device *udev);
176extern void usb_notify_remove_device(struct usb_device *udev); 182extern void usb_notify_remove_device(struct usb_device *udev);