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: 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>
This commit is contained in:
Dan Williams 2014-05-20 18:08:28 -07:00 committed by Greg Kroah-Hartman
parent a4204ff0bd
commit d8521afe35
5 changed files with 134 additions and 27 deletions

View file

@ -2458,11 +2458,13 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
mutex_init(hcd->bandwidth_mutex);
dev_set_drvdata(dev, hcd);
} else {
mutex_lock(&usb_port_peer_mutex);
hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex;
hcd->primary_hcd = primary_hcd;
primary_hcd->primary_hcd = primary_hcd;
hcd->shared_hcd = primary_hcd;
primary_hcd->shared_hcd = hcd;
mutex_unlock(&usb_port_peer_mutex);
}
kref_init(&hcd->kref);
@ -2514,18 +2516,25 @@ EXPORT_SYMBOL_GPL(usb_create_hcd);
* deallocated.
*
* 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
* primary_hcd's shared_hcd pointer to null (since the non-primary HCD will be
* freed shortly).
* freed. When hcd_release() is called for either hcd in a peer set
* invalidate the peer's ->shared_hcd and ->primary_hcd pointers to
* 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);
mutex_lock(&usb_port_peer_mutex);
if (usb_hcd_is_primary_hcd(hcd))
kfree(hcd->bandwidth_mutex);
else
hcd->shared_hcd->shared_hcd = NULL;
if (hcd->shared_hcd) {
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);
}
@ -2593,6 +2602,21 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd,
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
* @hcd: the usb_hcd structure to initialize
@ -2653,7 +2677,9 @@ int usb_add_hcd(struct usb_hcd *hcd,
retval = -ENOMEM;
goto err_allocate_root_hub;
}
mutex_lock(&usb_port_peer_mutex);
hcd->self.root_hub = rhdev;
mutex_unlock(&usb_port_peer_mutex);
switch (hcd->speed) {
case HCD_USB11:
@ -2762,7 +2788,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
err_request_irq:
err_hcd_driver_setup:
err_set_rh_speed:
usb_put_dev(hcd->self.root_hub);
usb_put_invalidate_rhdev(hcd);
err_allocate_root_hub:
usb_deregister_bus(&hcd->self);
err_register_bus:
@ -2842,7 +2868,6 @@ void usb_remove_hcd(struct usb_hcd *hcd)
free_irq(hcd->irq, hcd);
}
usb_put_dev(hcd->self.root_hub);
usb_deregister_bus(&hcd->self);
hcd_buffer_destroy(hcd);
if (hcd->remove_phy && hcd->phy) {
@ -2850,6 +2875,8 @@ void usb_remove_hcd(struct usb_hcd *hcd)
usb_put_phy(hcd->phy);
hcd->phy = NULL;
}
usb_put_invalidate_rhdev(hcd);
}
EXPORT_SYMBOL_GPL(usb_remove_hcd);

View file

@ -55,6 +55,9 @@ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
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 */
static bool blinkenlights = 0;
module_param (blinkenlights, bool, S_IRUGO);
@ -1323,6 +1326,7 @@ static int hub_configure(struct usb_hub *hub,
char *message = "out of memory";
unsigned unit_load;
unsigned full_load;
unsigned maxchild;
hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);
if (!hub->buffer) {
@ -1361,12 +1365,11 @@ static int hub_configure(struct usb_hub *hub,
goto fail;
}
hdev->maxchild = hub->descriptor->bNbrPorts;
dev_info (hub_dev, "%d port%s detected\n", hdev->maxchild,
(hdev->maxchild == 1) ? "" : "s");
maxchild = hub->descriptor->bNbrPorts;
dev_info(hub_dev, "%d port%s detected\n", maxchild,
(maxchild == 1) ? "" : "s");
hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *),
GFP_KERNEL);
hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);
if (!hub->ports) {
ret = -ENOMEM;
goto fail;
@ -1387,11 +1390,11 @@ static int hub_configure(struct usb_hub *hub,
int i;
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
[((i + 1) / 8)] & (1 << ((i + 1) % 8))
? 'F' : 'R';
portstr[hdev->maxchild] = 0;
portstr[maxchild] = 0;
dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr);
} else
dev_dbg(hub_dev, "standalone hub\n");
@ -1503,7 +1506,7 @@ static int hub_configure(struct usb_hub *hub,
if (hcd->power_budget > 0)
hdev->bus_mA = hcd->power_budget;
else
hdev->bus_mA = full_load * hdev->maxchild;
hdev->bus_mA = full_load * maxchild;
if (hdev->bus_mA >= full_load)
hub->mA_per_port = full_load;
else {
@ -1518,7 +1521,7 @@ static int hub_configure(struct usb_hub *hub,
hub->descriptor->bHubContrCurrent);
hub->limited_power = 1;
if (remaining < hdev->maxchild * unit_load)
if (remaining < maxchild * unit_load)
dev_warn(hub_dev,
"insufficient power available "
"to use all downstream ports\n");
@ -1586,15 +1589,19 @@ static int hub_configure(struct usb_hub *hub,
if (hub->has_indicators && blinkenlights)
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);
if (ret < 0) {
dev_err(hub->intfdev,
"couldn't create port%d device.\n", i + 1);
hdev->maxchild = i;
goto fail_keep_maxchild;
break;
}
}
hdev->maxchild = i;
mutex_unlock(&usb_port_peer_mutex);
if (ret < 0)
goto fail;
usb_hub_adjust_deviceremovable(hdev, hub->descriptor);
@ -1602,8 +1609,6 @@ static int hub_configure(struct usb_hub *hub,
return 0;
fail:
hdev->maxchild = 0;
fail_keep_maxchild:
dev_err (hub_dev, "config failed, %s (err %d)\n",
message, ret);
/* hub_disconnect() frees urb and descriptor */
@ -1639,6 +1644,8 @@ static void hub_disconnect(struct usb_interface *intf)
hub->error = 0;
hub_quiesce(hub, HUB_DISCONNECT);
mutex_lock(&usb_port_peer_mutex);
/* Avoid races with recursively_mark_NOTATTACHED() */
spin_lock_irq(&device_state_lock);
port1 = hdev->maxchild;
@ -1649,6 +1656,8 @@ static void hub_disconnect(struct usb_interface *intf)
for (; port1 > 0; --port1)
usb_hub_remove_port_device(hub, port1);
mutex_unlock(&usb_port_peer_mutex);
if (hub->hdev->speed == USB_SPEED_HIGH)
highspeed_hubs--;
@ -4608,6 +4617,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
*/
status = 0;
mutex_lock(&usb_port_peer_mutex);
/* We mustn't add new devices if the parent hub has
* been disconnected; we would race with the
* recursively_mark_NOTATTACHED() routine.
@ -4618,14 +4629,17 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
else
port_dev->child = udev;
spin_unlock_irq(&device_state_lock);
mutex_unlock(&usb_port_peer_mutex);
/* Run it through the hoops (find a driver, etc) */
if (!status) {
status = usb_new_device(udev);
if (status) {
mutex_lock(&usb_port_peer_mutex);
spin_lock_irq(&device_state_lock);
port_dev->child = NULL;
spin_unlock_irq(&device_state_lock);
mutex_unlock(&usb_port_peer_mutex);
}
}

View file

@ -82,6 +82,7 @@ struct usb_hub {
* @child: usb device attached to the port
* @dev: generic device interface
* @port_owner: port's owner
* @peer: related usb2 and usb3 ports (share the same connector)
* @connect_type: port's connect type
* @portnum: port index num based one
* @power_is_on: port's power state
@ -91,6 +92,7 @@ struct usb_port {
struct usb_device *child;
struct device dev;
struct usb_dev_state *port_owner;
struct usb_port *peer;
enum usb_port_connect_type connect_type;
u8 portnum;
unsigned power_is_on:1;

View file

@ -157,9 +157,66 @@ static struct device_driver usb_port_driver = {
.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)
{
struct usb_port *port_dev = NULL;
struct usb_port *port_dev;
int retval;
port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
@ -181,6 +238,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
if (retval)
goto error_register;
find_and_link_peer(hub, port1);
pm_runtime_set_active(&port_dev->dev);
/*
@ -203,9 +262,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
return retval;
}
void usb_hub_remove_port_device(struct usb_hub *hub,
int port1)
void usb_hub_remove_port_device(struct usb_hub *hub, 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);
}

View file

@ -119,6 +119,7 @@ static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable)
#endif
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_if_device_type;
extern struct device_type usb_ep_device_type;