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:
Родитель
a4204ff0bd
Коммит
d8521afe35
|
@ -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 @@ err_hcd_driver_start:
|
|||
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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 @@ exit:
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче