Commit d8521afe authored by Dan Williams's avatar Dan Williams Committed by Greg Kroah-Hartman

usb: assign default peer ports for root hubs

Assume that the peer of a superspeed port is the port with the same id
on the shared_hcd root hub.  This identification scheme is required of
external hubs by the USB3 spec [1].  However, for root hubs, tier mismatch
may be in effect [2].  Tier mismatch can only be enumerated via platform
firmware.  For now, simply perform the nominal association.

A new lock 'usb_port_peer_mutex' is introduced to synchronize port
device add/remove with peer lookups.  It protects peering against
changes to hcd->shared_hcd, hcd->self.root_hub, hdev->maxchild, and
port_dev->child pointers.

[1]: usb 3.1 section 10.3.3
[2]: xhci 1.1 appendix D

Cc: Alan Stern <stern@rowland.harvard.edu>
[alan: usb_port_peer_mutex locking scheme]
Acked-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent a4204ff0
...@@ -2458,11 +2458,13 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, ...@@ -2458,11 +2458,13 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
mutex_init(hcd->bandwidth_mutex); mutex_init(hcd->bandwidth_mutex);
dev_set_drvdata(dev, hcd); dev_set_drvdata(dev, hcd);
} else { } else {
mutex_lock(&usb_port_peer_mutex);
hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex; hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex;
hcd->primary_hcd = primary_hcd; hcd->primary_hcd = primary_hcd;
primary_hcd->primary_hcd = primary_hcd; primary_hcd->primary_hcd = primary_hcd;
hcd->shared_hcd = primary_hcd; hcd->shared_hcd = primary_hcd;
primary_hcd->shared_hcd = hcd; primary_hcd->shared_hcd = hcd;
mutex_unlock(&usb_port_peer_mutex);
} }
kref_init(&hcd->kref); kref_init(&hcd->kref);
...@@ -2514,18 +2516,25 @@ EXPORT_SYMBOL_GPL(usb_create_hcd); ...@@ -2514,18 +2516,25 @@ EXPORT_SYMBOL_GPL(usb_create_hcd);
* deallocated. * deallocated.
* *
* Make sure to only deallocate the bandwidth_mutex when the primary HCD is * Make sure to only deallocate the bandwidth_mutex when the primary HCD is
* freed. When hcd_release() is called for the non-primary HCD, set the * freed. When hcd_release() is called for either hcd in a peer set
* primary_hcd's shared_hcd pointer to null (since the non-primary HCD will be * invalidate the peer's ->shared_hcd and ->primary_hcd pointers to
* freed shortly). * block new peering attempts
*/ */
static void hcd_release (struct kref *kref) static void hcd_release(struct kref *kref)
{ {
struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref); struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref);
mutex_lock(&usb_port_peer_mutex);
if (usb_hcd_is_primary_hcd(hcd)) if (usb_hcd_is_primary_hcd(hcd))
kfree(hcd->bandwidth_mutex); kfree(hcd->bandwidth_mutex);
else if (hcd->shared_hcd) {
hcd->shared_hcd->shared_hcd = NULL; struct usb_hcd *peer = hcd->shared_hcd;
peer->shared_hcd = NULL;
if (peer->primary_hcd == hcd)
peer->primary_hcd = NULL;
}
mutex_unlock(&usb_port_peer_mutex);
kfree(hcd); kfree(hcd);
} }
...@@ -2593,6 +2602,21 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd, ...@@ -2593,6 +2602,21 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd,
return 0; return 0;
} }
/*
* Before we free this root hub, flush in-flight peering attempts
* and disable peer lookups
*/
static void usb_put_invalidate_rhdev(struct usb_hcd *hcd)
{
struct usb_device *rhdev;
mutex_lock(&usb_port_peer_mutex);
rhdev = hcd->self.root_hub;
hcd->self.root_hub = NULL;
mutex_unlock(&usb_port_peer_mutex);
usb_put_dev(rhdev);
}
/** /**
* usb_add_hcd - finish generic HCD structure initialization and register * usb_add_hcd - finish generic HCD structure initialization and register
* @hcd: the usb_hcd structure to initialize * @hcd: the usb_hcd structure to initialize
...@@ -2653,7 +2677,9 @@ int usb_add_hcd(struct usb_hcd *hcd, ...@@ -2653,7 +2677,9 @@ int usb_add_hcd(struct usb_hcd *hcd,
retval = -ENOMEM; retval = -ENOMEM;
goto err_allocate_root_hub; goto err_allocate_root_hub;
} }
mutex_lock(&usb_port_peer_mutex);
hcd->self.root_hub = rhdev; hcd->self.root_hub = rhdev;
mutex_unlock(&usb_port_peer_mutex);
switch (hcd->speed) { switch (hcd->speed) {
case HCD_USB11: case HCD_USB11:
...@@ -2762,7 +2788,7 @@ int usb_add_hcd(struct usb_hcd *hcd, ...@@ -2762,7 +2788,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
err_request_irq: err_request_irq:
err_hcd_driver_setup: err_hcd_driver_setup:
err_set_rh_speed: err_set_rh_speed:
usb_put_dev(hcd->self.root_hub); usb_put_invalidate_rhdev(hcd);
err_allocate_root_hub: err_allocate_root_hub:
usb_deregister_bus(&hcd->self); usb_deregister_bus(&hcd->self);
err_register_bus: err_register_bus:
...@@ -2842,7 +2868,6 @@ void usb_remove_hcd(struct usb_hcd *hcd) ...@@ -2842,7 +2868,6 @@ void usb_remove_hcd(struct usb_hcd *hcd)
free_irq(hcd->irq, hcd); free_irq(hcd->irq, hcd);
} }
usb_put_dev(hcd->self.root_hub);
usb_deregister_bus(&hcd->self); usb_deregister_bus(&hcd->self);
hcd_buffer_destroy(hcd); hcd_buffer_destroy(hcd);
if (hcd->remove_phy && hcd->phy) { if (hcd->remove_phy && hcd->phy) {
...@@ -2850,6 +2875,8 @@ void usb_remove_hcd(struct usb_hcd *hcd) ...@@ -2850,6 +2875,8 @@ void usb_remove_hcd(struct usb_hcd *hcd)
usb_put_phy(hcd->phy); usb_put_phy(hcd->phy);
hcd->phy = NULL; hcd->phy = NULL;
} }
usb_put_invalidate_rhdev(hcd);
} }
EXPORT_SYMBOL_GPL(usb_remove_hcd); EXPORT_SYMBOL_GPL(usb_remove_hcd);
......
...@@ -55,6 +55,9 @@ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); ...@@ -55,6 +55,9 @@ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
static struct task_struct *khubd_task; static struct task_struct *khubd_task;
/* synchronize hub-port add/remove and peering operations */
DEFINE_MUTEX(usb_port_peer_mutex);
/* cycle leds on hubs that aren't blinking for attention */ /* cycle leds on hubs that aren't blinking for attention */
static bool blinkenlights = 0; static bool blinkenlights = 0;
module_param (blinkenlights, bool, S_IRUGO); module_param (blinkenlights, bool, S_IRUGO);
...@@ -1323,6 +1326,7 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1323,6 +1326,7 @@ static int hub_configure(struct usb_hub *hub,
char *message = "out of memory"; char *message = "out of memory";
unsigned unit_load; unsigned unit_load;
unsigned full_load; unsigned full_load;
unsigned maxchild;
hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL); hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);
if (!hub->buffer) { if (!hub->buffer) {
...@@ -1361,12 +1365,11 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1361,12 +1365,11 @@ static int hub_configure(struct usb_hub *hub,
goto fail; goto fail;
} }
hdev->maxchild = hub->descriptor->bNbrPorts; maxchild = hub->descriptor->bNbrPorts;
dev_info (hub_dev, "%d port%s detected\n", hdev->maxchild, dev_info(hub_dev, "%d port%s detected\n", maxchild,
(hdev->maxchild == 1) ? "" : "s"); (maxchild == 1) ? "" : "s");
hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *), hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);
GFP_KERNEL);
if (!hub->ports) { if (!hub->ports) {
ret = -ENOMEM; ret = -ENOMEM;
goto fail; goto fail;
...@@ -1387,11 +1390,11 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1387,11 +1390,11 @@ static int hub_configure(struct usb_hub *hub,
int i; int i;
char portstr[USB_MAXCHILDREN + 1]; char portstr[USB_MAXCHILDREN + 1];
for (i = 0; i < hdev->maxchild; i++) for (i = 0; i < maxchild; i++)
portstr[i] = hub->descriptor->u.hs.DeviceRemovable portstr[i] = hub->descriptor->u.hs.DeviceRemovable
[((i + 1) / 8)] & (1 << ((i + 1) % 8)) [((i + 1) / 8)] & (1 << ((i + 1) % 8))
? 'F' : 'R'; ? 'F' : 'R';
portstr[hdev->maxchild] = 0; portstr[maxchild] = 0;
dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr); dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr);
} else } else
dev_dbg(hub_dev, "standalone hub\n"); dev_dbg(hub_dev, "standalone hub\n");
...@@ -1503,7 +1506,7 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1503,7 +1506,7 @@ static int hub_configure(struct usb_hub *hub,
if (hcd->power_budget > 0) if (hcd->power_budget > 0)
hdev->bus_mA = hcd->power_budget; hdev->bus_mA = hcd->power_budget;
else else
hdev->bus_mA = full_load * hdev->maxchild; hdev->bus_mA = full_load * maxchild;
if (hdev->bus_mA >= full_load) if (hdev->bus_mA >= full_load)
hub->mA_per_port = full_load; hub->mA_per_port = full_load;
else { else {
...@@ -1518,7 +1521,7 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1518,7 +1521,7 @@ static int hub_configure(struct usb_hub *hub,
hub->descriptor->bHubContrCurrent); hub->descriptor->bHubContrCurrent);
hub->limited_power = 1; hub->limited_power = 1;
if (remaining < hdev->maxchild * unit_load) if (remaining < maxchild * unit_load)
dev_warn(hub_dev, dev_warn(hub_dev,
"insufficient power available " "insufficient power available "
"to use all downstream ports\n"); "to use all downstream ports\n");
...@@ -1586,15 +1589,19 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1586,15 +1589,19 @@ static int hub_configure(struct usb_hub *hub,
if (hub->has_indicators && blinkenlights) if (hub->has_indicators && blinkenlights)
hub->indicator[0] = INDICATOR_CYCLE; hub->indicator[0] = INDICATOR_CYCLE;
for (i = 0; i < hdev->maxchild; i++) { mutex_lock(&usb_port_peer_mutex);
for (i = 0; i < maxchild; i++) {
ret = usb_hub_create_port_device(hub, i + 1); ret = usb_hub_create_port_device(hub, i + 1);
if (ret < 0) { if (ret < 0) {
dev_err(hub->intfdev, dev_err(hub->intfdev,
"couldn't create port%d device.\n", i + 1); "couldn't create port%d device.\n", i + 1);
hdev->maxchild = i; break;
goto fail_keep_maxchild;
} }
} }
hdev->maxchild = i;
mutex_unlock(&usb_port_peer_mutex);
if (ret < 0)
goto fail;
usb_hub_adjust_deviceremovable(hdev, hub->descriptor); usb_hub_adjust_deviceremovable(hdev, hub->descriptor);
...@@ -1602,8 +1609,6 @@ static int hub_configure(struct usb_hub *hub, ...@@ -1602,8 +1609,6 @@ static int hub_configure(struct usb_hub *hub,
return 0; return 0;
fail: fail:
hdev->maxchild = 0;
fail_keep_maxchild:
dev_err (hub_dev, "config failed, %s (err %d)\n", dev_err (hub_dev, "config failed, %s (err %d)\n",
message, ret); message, ret);
/* hub_disconnect() frees urb and descriptor */ /* hub_disconnect() frees urb and descriptor */
...@@ -1639,6 +1644,8 @@ static void hub_disconnect(struct usb_interface *intf) ...@@ -1639,6 +1644,8 @@ static void hub_disconnect(struct usb_interface *intf)
hub->error = 0; hub->error = 0;
hub_quiesce(hub, HUB_DISCONNECT); hub_quiesce(hub, HUB_DISCONNECT);
mutex_lock(&usb_port_peer_mutex);
/* Avoid races with recursively_mark_NOTATTACHED() */ /* Avoid races with recursively_mark_NOTATTACHED() */
spin_lock_irq(&device_state_lock); spin_lock_irq(&device_state_lock);
port1 = hdev->maxchild; port1 = hdev->maxchild;
...@@ -1649,6 +1656,8 @@ static void hub_disconnect(struct usb_interface *intf) ...@@ -1649,6 +1656,8 @@ static void hub_disconnect(struct usb_interface *intf)
for (; port1 > 0; --port1) for (; port1 > 0; --port1)
usb_hub_remove_port_device(hub, port1); usb_hub_remove_port_device(hub, port1);
mutex_unlock(&usb_port_peer_mutex);
if (hub->hdev->speed == USB_SPEED_HIGH) if (hub->hdev->speed == USB_SPEED_HIGH)
highspeed_hubs--; highspeed_hubs--;
...@@ -4608,6 +4617,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, ...@@ -4608,6 +4617,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
*/ */
status = 0; status = 0;
mutex_lock(&usb_port_peer_mutex);
/* We mustn't add new devices if the parent hub has /* We mustn't add new devices if the parent hub has
* been disconnected; we would race with the * been disconnected; we would race with the
* recursively_mark_NOTATTACHED() routine. * recursively_mark_NOTATTACHED() routine.
...@@ -4618,14 +4629,17 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, ...@@ -4618,14 +4629,17 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
else else
port_dev->child = udev; port_dev->child = udev;
spin_unlock_irq(&device_state_lock); spin_unlock_irq(&device_state_lock);
mutex_unlock(&usb_port_peer_mutex);
/* Run it through the hoops (find a driver, etc) */ /* Run it through the hoops (find a driver, etc) */
if (!status) { if (!status) {
status = usb_new_device(udev); status = usb_new_device(udev);
if (status) { if (status) {
mutex_lock(&usb_port_peer_mutex);
spin_lock_irq(&device_state_lock); spin_lock_irq(&device_state_lock);
port_dev->child = NULL; port_dev->child = NULL;
spin_unlock_irq(&device_state_lock); spin_unlock_irq(&device_state_lock);
mutex_unlock(&usb_port_peer_mutex);
} }
} }
......
...@@ -82,6 +82,7 @@ struct usb_hub { ...@@ -82,6 +82,7 @@ struct usb_hub {
* @child: usb device attached to the port * @child: usb device attached to the port
* @dev: generic device interface * @dev: generic device interface
* @port_owner: port's owner * @port_owner: port's owner
* @peer: related usb2 and usb3 ports (share the same connector)
* @connect_type: port's connect type * @connect_type: port's connect type
* @portnum: port index num based one * @portnum: port index num based one
* @power_is_on: port's power state * @power_is_on: port's power state
...@@ -91,6 +92,7 @@ struct usb_port { ...@@ -91,6 +92,7 @@ struct usb_port {
struct usb_device *child; struct usb_device *child;
struct device dev; struct device dev;
struct usb_dev_state *port_owner; struct usb_dev_state *port_owner;
struct usb_port *peer;
enum usb_port_connect_type connect_type; enum usb_port_connect_type connect_type;
u8 portnum; u8 portnum;
unsigned power_is_on:1; unsigned power_is_on:1;
......
...@@ -157,9 +157,66 @@ static struct device_driver usb_port_driver = { ...@@ -157,9 +157,66 @@ static struct device_driver usb_port_driver = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
}; };
static void link_peers(struct usb_port *left, struct usb_port *right)
{
if (left->peer == right && right->peer == left)
return;
if (left->peer || right->peer) {
struct usb_port *lpeer = left->peer;
struct usb_port *rpeer = right->peer;
WARN(1, "failed to peer %s and %s (%s -> %p) (%s -> %p)\n",
dev_name(&left->dev), dev_name(&right->dev),
dev_name(&left->dev), lpeer,
dev_name(&right->dev), rpeer);
return;
}
left->peer = right;
right->peer = left;
}
static void unlink_peers(struct usb_port *left, struct usb_port *right)
{
WARN(right->peer != left || left->peer != right,
"%s and %s are not peers?\n",
dev_name(&left->dev), dev_name(&right->dev));
right->peer = NULL;
left->peer = NULL;
}
/* set the default peer port for root hubs */
static void find_and_link_peer(struct usb_hub *hub, int port1)
{
struct usb_port *port_dev = hub->ports[port1 - 1], *peer;
struct usb_device *hdev = hub->hdev;
if (!hdev->parent) {
struct usb_hub *peer_hub;
struct usb_device *peer_hdev;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
struct usb_hcd *peer_hcd = hcd->shared_hcd;
if (!peer_hcd)
return;
peer_hdev = peer_hcd->self.root_hub;
peer_hub = usb_hub_to_struct_hub(peer_hdev);
if (!peer_hub || port1 > peer_hdev->maxchild)
return;
peer = peer_hub->ports[port1 - 1];
if (peer)
link_peers(port_dev, peer);
}
}
int usb_hub_create_port_device(struct usb_hub *hub, int port1) int usb_hub_create_port_device(struct usb_hub *hub, int port1)
{ {
struct usb_port *port_dev = NULL; struct usb_port *port_dev;
int retval; int retval;
port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
...@@ -181,6 +238,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) ...@@ -181,6 +238,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
if (retval) if (retval)
goto error_register; goto error_register;
find_and_link_peer(hub, port1);
pm_runtime_set_active(&port_dev->dev); pm_runtime_set_active(&port_dev->dev);
/* /*
...@@ -203,9 +262,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) ...@@ -203,9 +262,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
return retval; return retval;
} }
void usb_hub_remove_port_device(struct usb_hub *hub, void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
int port1)
{ {
device_unregister(&hub->ports[port1 - 1]->dev); struct usb_port *port_dev = hub->ports[port1 - 1];
} struct usb_port *peer;
peer = port_dev->peer;
if (peer)
unlink_peers(port_dev, peer);
device_unregister(&port_dev->dev);
}
...@@ -119,6 +119,7 @@ static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable) ...@@ -119,6 +119,7 @@ static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable)
#endif #endif
extern struct bus_type usb_bus_type; extern struct bus_type usb_bus_type;
extern struct mutex usb_port_peer_mutex;
extern struct device_type usb_device_type; extern struct device_type usb_device_type;
extern struct device_type usb_if_device_type; extern struct device_type usb_if_device_type;
extern struct device_type usb_ep_device_type; extern struct device_type usb_ep_device_type;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment