aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-06-08 11:31:16 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2014-06-08 11:31:16 -0700
commit3f17ea6dea8ba5668873afa54628a91aaa3fb1c0 (patch)
treeafbeb2accd4c2199ddd705ae943995b143a0af02 /drivers/usb/core
parent1860e379875dfe7271c649058aeddffe5afd9d0d (diff)
parent1a5700bc2d10cd379a795fd2bb377a190af5acd4 (diff)
downloadlinux-stericsson-3f17ea6dea8ba5668873afa54628a91aaa3fb1c0.tar.gz
Merge branch 'next' (accumulated 3.16 merge window patches) into master
Now that 3.15 is released, this merges the 'next' branch into 'master', bringing us to the normal situation where my 'master' branch is the merge window. * accumulated work in next: (6809 commits) ufs: sb mutex merge + mutex_destroy powerpc: update comments for generic idle conversion cris: update comments for generic idle conversion idle: remove cpu_idle() forward declarations nbd: zero from and len fields in NBD_CMD_DISCONNECT. mm: convert some level-less printks to pr_* MAINTAINERS: adi-buildroot-devel is moderated MAINTAINERS: add linux-api for review of API/ABI changes mm/kmemleak-test.c: use pr_fmt for logging fs/dlm/debug_fs.c: replace seq_printf by seq_puts fs/dlm/lockspace.c: convert simple_str to kstr fs/dlm/config.c: convert simple_str to kstr mm: mark remap_file_pages() syscall as deprecated mm: memcontrol: remove unnecessary memcg argument from soft limit functions mm: memcontrol: clean up memcg zoneinfo lookup mm/memblock.c: call kmemleak directly from memblock_(alloc|free) mm/mempool.c: update the kmemleak stack trace for mempool allocations lib/radix-tree.c: update the kmemleak stack trace for radix tree allocations mm: introduce kmemleak_update_trace() mm/kmemleak.c: use %u to print ->checksum ...
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/Kconfig16
-rw-r--r--drivers/usb/core/hcd-pci.c1
-rw-r--r--drivers/usb/core/hcd.c49
-rw-r--r--drivers/usb/core/hub.c956
-rw-r--r--drivers/usb/core/hub.h41
-rw-r--r--drivers/usb/core/port.c289
-rw-r--r--drivers/usb/core/usb-acpi.c107
-rw-r--r--drivers/usb/core/usb.h16
8 files changed, 918 insertions, 557 deletions
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index cb8e99156f5a..1060657ca1b0 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -1,13 +1,6 @@
#
# USB Core configuration
#
-config USB_DEBUG
- bool "USB verbose debug messages"
- help
- Say Y here if you want the USB core & hub drivers to produce a bunch
- of debug messages to the system log. Select this if you are having a
- problem with USB support and want to see more of what is going on.
-
config USB_ANNOUNCE_NEW_DEVICES
bool "USB announce new devices"
help
@@ -88,3 +81,12 @@ config USB_OTG_BLACKLIST_HUB
and software costs by not supporting external hubs. So
are "Embedded Hosts" that don't offer OTG support.
+config USB_OTG_FSM
+ tristate "USB 2.0 OTG FSM implementation"
+ depends on USB
+ select USB_OTG
+ select USB_PHY
+ help
+ Implements OTG Finite State Machine as specified in On-The-Go
+ and Embedded Host Supplement to the USB Revision 2.0 Specification.
+
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index 1f02e65fe305..82044b5d6113 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -192,7 +192,6 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
if (pci_enable_device(dev) < 0)
return -ENODEV;
- dev->current_state = PCI_D0;
/*
* The xHCI driver has its own irq management
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 9c4e2922b04d..bec31e2efb88 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -918,6 +918,7 @@ static void usb_bus_init (struct usb_bus *bus)
bus->bandwidth_allocated = 0;
bus->bandwidth_int_reqs = 0;
bus->bandwidth_isoc_reqs = 0;
+ mutex_init(&bus->usb_address0_mutex);
INIT_LIST_HEAD (&bus->bus_list);
}
@@ -1502,6 +1503,9 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
ret = -EAGAIN;
else
urb->transfer_flags |= URB_DMA_MAP_PAGE;
+ } else if (is_vmalloc_addr(urb->transfer_buffer)) {
+ WARN_ONCE(1, "transfer buffer not dma capable\n");
+ ret = -EAGAIN;
} else {
urb->transfer_dma = dma_map_single(
hcd->self.controller,
@@ -2263,9 +2267,7 @@ static void hcd_resume_work(struct work_struct *work)
struct usb_hcd *hcd = container_of(work, struct usb_hcd, wakeup_work);
struct usb_device *udev = hcd->self.root_hub;
- usb_lock_device(udev);
usb_remote_wakeup(udev);
- usb_unlock_device(udev);
}
/**
@@ -2454,11 +2456,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);
@@ -2510,18 +2514,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);
}
@@ -2589,6 +2600,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
@@ -2649,7 +2675,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:
@@ -2758,7 +2786,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:
@@ -2838,7 +2866,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) {
@@ -2846,6 +2873,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);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 229a73f64304..879b66e13370 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -36,11 +36,6 @@
#define USB_VENDOR_GENESYS_LOGIC 0x05e3
#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01
-static inline int hub_is_superspeed(struct usb_device *hdev)
-{
- return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS);
-}
-
/* Protect struct usb_device->state and ->children members
* Note: Both are also protected by ->dev.sem, except that ->state can
* change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
@@ -55,6 +50,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);
@@ -412,30 +410,35 @@ static int set_port_feature(struct usb_device *hdev, int port1, int feature)
NULL, 0, 1000);
}
+static char *to_led_name(int selector)
+{
+ switch (selector) {
+ case HUB_LED_AMBER:
+ return "amber";
+ case HUB_LED_GREEN:
+ return "green";
+ case HUB_LED_OFF:
+ return "off";
+ case HUB_LED_AUTO:
+ return "auto";
+ default:
+ return "??";
+ }
+}
+
/*
* USB 2.0 spec Section 11.24.2.7.1.10 and table 11-7
* for info about using port indicators
*/
-static void set_port_led(
- struct usb_hub *hub,
- int port1,
- int selector
-)
+static void set_port_led(struct usb_hub *hub, int port1, int selector)
{
- int status = set_port_feature(hub->hdev, (selector << 8) | port1,
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ int status;
+
+ status = set_port_feature(hub->hdev, (selector << 8) | port1,
USB_PORT_FEAT_INDICATOR);
- if (status < 0)
- dev_dbg (hub->intfdev,
- "port %d indicator %s status %d\n",
- port1,
- ({ char *s; switch (selector) {
- case HUB_LED_AMBER: s = "amber"; break;
- case HUB_LED_GREEN: s = "green"; break;
- case HUB_LED_OFF: s = "off"; break;
- case HUB_LED_AUTO: s = "auto"; break;
- default: s = "??"; break;
- } s; }),
- status);
+ dev_dbg(&port_dev->dev, "indicator %s status %d\n",
+ to_led_name(selector), status);
}
#define LED_CYCLE_PERIOD ((2*HZ)/3)
@@ -743,16 +746,20 @@ int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub,
int port1, bool set)
{
int ret;
- struct usb_port *port_dev = hub->ports[port1 - 1];
if (set)
ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
else
ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
- if (!ret)
- port_dev->power_is_on = set;
- return ret;
+ if (ret)
+ return ret;
+
+ if (set)
+ set_bit(port1, hub->power_bits);
+ else
+ clear_bit(port1, hub->power_bits);
+ return 0;
}
/**
@@ -810,16 +817,9 @@ int usb_hub_clear_tt_buffer(struct urb *urb)
}
EXPORT_SYMBOL_GPL(usb_hub_clear_tt_buffer);
-/* If do_delay is false, return the number of milliseconds the caller
- * needs to delay.
- */
-static unsigned hub_power_on(struct usb_hub *hub, bool do_delay)
+static void hub_power_on(struct usb_hub *hub, bool do_delay)
{
int port1;
- unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2;
- unsigned delay;
- u16 wHubCharacteristics =
- le16_to_cpu(hub->descriptor->wHubCharacteristics);
/* Enable power on each port. Some hubs have reserved values
* of LPSM (> 2) in their descriptors, even though they are
@@ -827,23 +827,19 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay)
* but only emulate it. In all cases, the ports won't work
* unless we send these messages to the hub.
*/
- if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2)
+ if (hub_is_port_power_switchable(hub))
dev_dbg(hub->intfdev, "enabling power on all ports\n");
else
dev_dbg(hub->intfdev, "trying to enable port power on "
"non-switchable hub\n");
for (port1 = 1; port1 <= hub->hdev->maxchild; port1++)
- if (hub->ports[port1 - 1]->power_is_on)
+ if (test_bit(port1, hub->power_bits))
set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
else
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_POWER);
-
- /* Wait at least 100 msec for power to become stable */
- delay = max(pgood_delay, (unsigned) 100);
if (do_delay)
- msleep(delay);
- return delay;
+ msleep(hub_power_on_good_delay(hub));
}
static int hub_hub_status(struct usb_hub *hub,
@@ -911,20 +907,20 @@ static int hub_usb3_port_disable(struct usb_hub *hub, int port1)
msleep(HUB_DEBOUNCE_STEP);
}
if (total_time >= HUB_DEBOUNCE_TIMEOUT)
- dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n",
- port1, total_time);
+ dev_warn(&hub->ports[port1 - 1]->dev,
+ "Could not disable after %d ms\n", total_time);
return hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_RX_DETECT);
}
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
{
+ struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *hdev = hub->hdev;
int ret = 0;
- if (hub->ports[port1 - 1]->child && set_state)
- usb_set_device_state(hub->ports[port1 - 1]->child,
- USB_STATE_NOTATTACHED);
+ if (port_dev->child && set_state)
+ usb_set_device_state(port_dev->child, USB_STATE_NOTATTACHED);
if (!hub->error) {
if (hub_is_superspeed(hub->hdev))
ret = hub_usb3_port_disable(hub, port1);
@@ -933,8 +929,7 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
USB_PORT_FEAT_ENABLE);
}
if (ret && ret != -ENODEV)
- dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
- port1, ret);
+ dev_err(&port_dev->dev, "cannot disable (err = %d)\n", ret);
return ret;
}
@@ -945,7 +940,7 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
*/
static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
{
- dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1);
+ dev_dbg(&hub->ports[port1 - 1]->dev, "logical disconnect\n");
hub_port_disable(hub, port1, 1);
/* FIXME let caller ask to power down the port:
@@ -1048,7 +1043,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
* for HUB_POST_RESET, but it's easier not to.
*/
if (type == HUB_INIT) {
- delay = hub_power_on(hub, false);
+ unsigned delay = hub_power_on_good_delay(hub);
+
+ hub_power_on(hub, false);
INIT_DELAYED_WORK(&hub->init_work, hub_init_func2);
queue_delayed_work(system_power_efficient_wq,
&hub->init_work,
@@ -1083,21 +1080,23 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
}
init2:
- /* Check each port and set hub->change_bits to let khubd know
+ /*
+ * Check each port and set hub->change_bits to let khubd know
* which ports need attention.
*/
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
- struct usb_device *udev = hub->ports[port1 - 1]->child;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ struct usb_device *udev = port_dev->child;
u16 portstatus, portchange;
portstatus = portchange = 0;
status = hub_port_status(hub, port1, &portstatus, &portchange);
if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
- dev_dbg(hub->intfdev,
- "port %d: status %04x change %04x\n",
- port1, portstatus, portchange);
+ dev_dbg(&port_dev->dev, "status %04x change %04x\n",
+ portstatus, portchange);
- /* After anything other than HUB_RESUME (i.e., initialization
+ /*
+ * After anything other than HUB_RESUME (i.e., initialization
* or any sort of reset), every port should be disabled.
* Unconnected ports should likewise be disabled (paranoia),
* and so should ports for which we have no usb_device.
@@ -1173,15 +1172,13 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
set_bit(port1, hub->change_bits);
} else if (udev->persist_enabled) {
- struct usb_port *port_dev = hub->ports[port1 - 1];
-
#ifdef CONFIG_PM
udev->reset_resume = 1;
#endif
/* Don't set the change_bits when the device
* was powered off.
*/
- if (port_dev->power_is_on)
+ if (test_bit(port1, hub->power_bits))
set_bit(port1, hub->change_bits);
} else {
@@ -1276,12 +1273,22 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
flush_work(&hub->tt.clear_work);
}
+static void hub_pm_barrier_for_all_ports(struct usb_hub *hub)
+{
+ int i;
+
+ for (i = 0; i < hub->hdev->maxchild; ++i)
+ pm_runtime_barrier(&hub->ports[i]->dev);
+}
+
/* caller has locked the hub device */
static int hub_pre_reset(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata(intf);
hub_quiesce(hub, HUB_PRE_RESET);
+ hub->in_reset = 1;
+ hub_pm_barrier_for_all_ports(hub);
return 0;
}
@@ -1290,6 +1297,8 @@ static int hub_post_reset(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata(intf);
+ hub->in_reset = 0;
+ hub_pm_barrier_for_all_ports(hub);
hub_activate(hub, HUB_POST_RESET);
return 0;
}
@@ -1307,6 +1316,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) {
@@ -1345,12 +1355,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;
@@ -1371,11 +1380,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");
@@ -1487,7 +1496,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 {
@@ -1502,7 +1511,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");
@@ -1570,15 +1579,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);
@@ -1586,8 +1599,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 */
@@ -1623,6 +1634,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;
@@ -1633,6 +1646,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--;
@@ -2035,6 +2050,18 @@ static void hub_free_dev(struct usb_device *udev)
hcd->driver->free_dev(hcd, udev);
}
+static void hub_disconnect_children(struct usb_device *udev)
+{
+ struct usb_hub *hub = usb_hub_to_struct_hub(udev);
+ int i;
+
+ /* Free up all the children before we remove this device */
+ for (i = 0; i < udev->maxchild; i++) {
+ if (hub->ports[i]->child)
+ usb_disconnect(&hub->ports[i]->child);
+ }
+}
+
/**
* usb_disconnect - disconnect a device (usbcore-internal)
* @pdev: pointer to device being disconnected
@@ -2053,9 +2080,10 @@ static void hub_free_dev(struct usb_device *udev)
*/
void usb_disconnect(struct usb_device **pdev)
{
- struct usb_device *udev = *pdev;
- struct usb_hub *hub = usb_hub_to_struct_hub(udev);
- int i;
+ struct usb_port *port_dev = NULL;
+ struct usb_device *udev = *pdev;
+ struct usb_hub *hub;
+ int port1;
/* mark the device as inactive, so any further urb submissions for
* this device (and any of its children) will fail immediately.
@@ -2067,11 +2095,7 @@ void usb_disconnect(struct usb_device **pdev)
usb_lock_device(udev);
- /* Free up all the children before we remove this device */
- for (i = 0; i < udev->maxchild; i++) {
- if (hub->ports[i]->child)
- usb_disconnect(&hub->ports[i]->child);
- }
+ hub_disconnect_children(udev);
/* deallocate hcd/hardware state ... nuking all pending urbs and
* cleaning up all state associated with the current configuration
@@ -2082,16 +2106,19 @@ void usb_disconnect(struct usb_device **pdev)
usb_hcd_synchronize_unlinks(udev);
if (udev->parent) {
- struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
- struct usb_port *port_dev = hub->ports[udev->portnum - 1];
+ port1 = udev->portnum;
+ hub = usb_hub_to_struct_hub(udev->parent);
+ port_dev = hub->ports[port1 - 1];
sysfs_remove_link(&udev->dev.kobj, "port");
sysfs_remove_link(&port_dev->dev.kobj, "device");
- if (!port_dev->did_runtime_put)
- pm_runtime_put(&port_dev->dev);
- else
- port_dev->did_runtime_put = false;
+ /*
+ * As usb_port_runtime_resume() de-references udev, make
+ * sure no resumes occur during removal
+ */
+ if (!test_and_set_bit(port1, hub->child_usage_bits))
+ pm_runtime_get_sync(&port_dev->dev);
}
usb_remove_ep_devs(&udev->ep0);
@@ -2113,6 +2140,9 @@ void usb_disconnect(struct usb_device **pdev)
*pdev = NULL;
spin_unlock_irq(&device_state_lock);
+ if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
+ pm_runtime_put(&port_dev->dev);
+
hub_free_dev(udev);
put_device(&udev->dev);
@@ -2300,6 +2330,22 @@ static void set_usb_port_removable(struct usb_device *udev)
udev->removable = USB_DEVICE_REMOVABLE;
else
udev->removable = USB_DEVICE_FIXED;
+
+ /*
+ * Platform firmware may have populated an alternative value for
+ * removable. If the parent port has a known connect_type use
+ * that instead.
+ */
+ switch (hub->ports[udev->portnum - 1]->connect_type) {
+ case USB_PORT_CONNECT_TYPE_HOT_PLUG:
+ udev->removable = USB_DEVICE_REMOVABLE;
+ break;
+ case USB_PORT_CONNECT_TYPE_HARD_WIRED:
+ udev->removable = USB_DEVICE_FIXED;
+ break;
+ default: /* use what was set above */
+ break;
+ }
}
/**
@@ -2369,11 +2415,7 @@ int usb_new_device(struct usb_device *udev)
device_enable_async_suspend(&udev->dev);
- /*
- * check whether the hub marks this port as non-removable. Do it
- * now so that platform-specific data can override it in
- * device_add()
- */
+ /* check whether the hub or firmware marks this port as non-removable */
if (udev->parent)
set_usb_port_removable(udev);
@@ -2390,7 +2432,8 @@ int usb_new_device(struct usb_device *udev)
/* Create link files between child device and usb port device. */
if (udev->parent) {
struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
- struct usb_port *port_dev = hub->ports[udev->portnum - 1];
+ int port1 = udev->portnum;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
err = sysfs_create_link(&udev->dev.kobj,
&port_dev->dev.kobj, "port");
@@ -2404,7 +2447,8 @@ int usb_new_device(struct usb_device *udev)
goto fail;
}
- pm_runtime_get_sync(&port_dev->dev);
+ if (!test_and_set_bit(port1, hub->child_usage_bits))
+ pm_runtime_get_sync(&port_dev->dev);
}
(void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
@@ -2572,9 +2616,9 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
delay = HUB_LONG_RESET_TIME;
- dev_dbg (hub->intfdev,
- "port %d not %sreset yet, waiting %dms\n",
- port1, warm ? "warm " : "", delay);
+ dev_dbg(&hub->ports[port1 - 1]->dev,
+ "not %sreset yet, waiting %dms\n",
+ warm ? "warm " : "", delay);
}
if ((portstatus & USB_PORT_STAT_RESET))
@@ -2658,6 +2702,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
{
int i, status;
u16 portchange, portstatus;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
if (!hub_is_superspeed(hub->hdev)) {
if (warm) {
@@ -2691,9 +2736,9 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
if (status == -ENODEV) {
; /* The hub is gone */
} else if (status) {
- dev_err(hub->intfdev,
- "cannot %sreset port %d (err = %d)\n",
- warm ? "warm " : "", port1, status);
+ dev_err(&port_dev->dev,
+ "cannot %sreset (err = %d)\n",
+ warm ? "warm " : "", status);
} else {
status = hub_port_wait_reset(hub, port1, udev, delay,
warm);
@@ -2726,21 +2771,19 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
* hot or warm reset failed. Try another warm reset.
*/
if (!warm) {
- dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
- port1);
+ dev_dbg(&port_dev->dev,
+ "hot reset failed, warm reset\n");
warm = true;
}
}
- dev_dbg (hub->intfdev,
- "port %d not enabled, trying %sreset again...\n",
- port1, warm ? "warm " : "");
+ dev_dbg(&port_dev->dev,
+ "not enabled, trying %sreset again...\n",
+ warm ? "warm " : "");
delay = HUB_LONG_RESET_TIME;
}
- dev_err (hub->intfdev,
- "Cannot enable port %i. Maybe the USB cable is bad?\n",
- port1);
+ dev_err(&port_dev->dev, "Cannot enable. Maybe the USB cable is bad?\n");
done:
if (!hub_is_superspeed(hub->hdev))
@@ -2765,6 +2808,20 @@ static int port_is_power_on(struct usb_hub *hub, unsigned portstatus)
return ret;
}
+static void usb_lock_port(struct usb_port *port_dev)
+ __acquires(&port_dev->status_lock)
+{
+ mutex_lock(&port_dev->status_lock);
+ __acquire(&port_dev->status_lock);
+}
+
+static void usb_unlock_port(struct usb_port *port_dev)
+ __releases(&port_dev->status_lock)
+{
+ mutex_unlock(&port_dev->status_lock);
+ __release(&port_dev->status_lock);
+}
+
#ifdef CONFIG_PM
/* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */
@@ -2791,6 +2848,8 @@ static int check_port_resume_type(struct usb_device *udev,
struct usb_hub *hub, int port1,
int status, unsigned portchange, unsigned portstatus)
{
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+
/* Is the device still present? */
if (status || port_is_suspended(hub, portstatus) ||
!port_is_power_on(hub, portstatus) ||
@@ -2810,9 +2869,8 @@ static int check_port_resume_type(struct usb_device *udev,
}
if (status) {
- dev_dbg(hub->intfdev,
- "port %d status %04x.%04x after resume, %d\n",
- port1, portchange, portstatus, status);
+ dev_dbg(&port_dev->dev, "status %04x.%04x after resume, %d\n",
+ portchange, portstatus, status);
} else if (udev->reset_resume) {
/* Late port handoff can set status-change bits */
@@ -2986,6 +3044,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
int status;
bool really_suspend = true;
+ usb_lock_port(port_dev);
+
/* enable remote wakeup when appropriate; this lets the device
* wake up the upstream hub (including maybe the root hub).
*
@@ -3043,8 +3103,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
status = 0;
}
if (status) {
- dev_dbg(hub->intfdev, "can't suspend port %d, status %d\n",
- port1, status);
+ dev_dbg(&port_dev->dev, "can't suspend, status %d\n", status);
/* Try to enable USB3 LPM and LTM again */
usb_unlocked_enable_lpm(udev);
@@ -3075,12 +3134,13 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
usb_set_device_state(udev, USB_STATE_SUSPENDED);
}
- if (status == 0 && !udev->do_remote_wakeup && udev->persist_enabled) {
+ if (status == 0 && !udev->do_remote_wakeup && udev->persist_enabled
+ && test_and_clear_bit(port1, hub->child_usage_bits))
pm_runtime_put_sync(&port_dev->dev);
- port_dev->did_runtime_put = true;
- }
usb_mark_last_busy(hub->hdev);
+
+ usb_unlock_port(port_dev);
return status;
}
@@ -3220,9 +3280,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
int status;
u16 portchange, portstatus;
- if (port_dev->did_runtime_put) {
+ if (!test_and_set_bit(port1, hub->child_usage_bits)) {
status = pm_runtime_get_sync(&port_dev->dev);
- port_dev->did_runtime_put = false;
if (status < 0) {
dev_dbg(&udev->dev, "can't resume usb port, status %d\n",
status);
@@ -3230,15 +3289,13 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
}
}
+ usb_lock_port(port_dev);
+
/* Skip the initial Clear-Suspend step for a remote wakeup */
status = hub_port_status(hub, port1, &portstatus, &portchange);
if (status == 0 && !port_is_suspended(hub, portstatus))
goto SuspendCleared;
- /* dev_dbg(hub->intfdev, "resume port %d\n", port1); */
-
- set_bit(port1, hub->busy_bits);
-
/* see 7.1.7.7; affects power usage, but not budgeting */
if (hub_is_superspeed(hub->hdev))
status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0);
@@ -3246,8 +3303,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
status = usb_clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_SUSPEND);
if (status) {
- dev_dbg(hub->intfdev, "can't resume port %d, status %d\n",
- port1, status);
+ dev_dbg(&port_dev->dev, "can't resume, status %d\n", status);
} else {
/* drive resume for at least 20 msec */
dev_dbg(&udev->dev, "usb %sresume\n",
@@ -3278,8 +3334,6 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
}
}
- clear_bit(port1, hub->busy_bits);
-
status = check_port_resume_type(udev,
hub, port1, status, portchange, portstatus);
if (status == 0)
@@ -3297,16 +3351,18 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
usb_unlocked_enable_lpm(udev);
}
+ usb_unlock_port(port_dev);
+
return status;
}
#ifdef CONFIG_PM_RUNTIME
-/* caller has locked udev */
int usb_remote_wakeup(struct usb_device *udev)
{
int status = 0;
+ usb_lock_device(udev);
if (udev->state == USB_STATE_SUSPENDED) {
dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-");
status = usb_autoresume_device(udev);
@@ -3315,9 +3371,59 @@ int usb_remote_wakeup(struct usb_device *udev)
usb_autosuspend_device(udev);
}
}
+ usb_unlock_device(udev);
return status;
}
+/* Returns 1 if there was a remote wakeup and a connect status change. */
+static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
+ u16 portstatus, u16 portchange)
+ __must_hold(&port_dev->status_lock)
+{
+ struct usb_port *port_dev = hub->ports[port - 1];
+ struct usb_device *hdev;
+ struct usb_device *udev;
+ int connect_change = 0;
+ int ret;
+
+ hdev = hub->hdev;
+ udev = port_dev->child;
+ if (!hub_is_superspeed(hdev)) {
+ if (!(portchange & USB_PORT_STAT_C_SUSPEND))
+ return 0;
+ usb_clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
+ } else {
+ if (!udev || udev->state != USB_STATE_SUSPENDED ||
+ (portstatus & USB_PORT_STAT_LINK_STATE) !=
+ USB_SS_PORT_LS_U0)
+ return 0;
+ }
+
+ if (udev) {
+ /* TRSMRCY = 10 msec */
+ msleep(10);
+
+ usb_unlock_port(port_dev);
+ ret = usb_remote_wakeup(udev);
+ usb_lock_port(port_dev);
+ if (ret < 0)
+ connect_change = 1;
+ } else {
+ ret = -ENODEV;
+ hub_port_disable(hub, port, 1);
+ }
+ dev_dbg(&port_dev->dev, "resume, status %d\n", ret);
+ return connect_change;
+}
+
+#else
+
+static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
+ u16 portstatus, u16 portchange)
+{
+ return 0;
+}
+
#endif
static int check_ports_changed(struct usb_hub *hub)
@@ -3348,12 +3454,11 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
*/
hub->wakeup_enabled_descendants = 0;
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
- struct usb_device *udev;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ struct usb_device *udev = port_dev->child;
- udev = hub->ports[port1 - 1]->child;
if (udev && udev->can_submit) {
- dev_warn(&intf->dev, "port %d not suspended yet\n",
- port1);
+ dev_warn(&port_dev->dev, "not suspended yet\n");
if (PMSG_IS_AUTO(msg))
return -EBUSY;
}
@@ -3872,6 +3977,12 @@ EXPORT_SYMBOL_GPL(usb_disable_ltm);
void usb_enable_ltm(struct usb_device *udev) { }
EXPORT_SYMBOL_GPL(usb_enable_ltm);
+static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
+ u16 portstatus, u16 portchange)
+{
+ return 0;
+}
+
#endif /* CONFIG_PM */
@@ -3893,9 +4004,10 @@ EXPORT_SYMBOL_GPL(usb_enable_ltm);
int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)
{
int ret;
- int total_time, stable_time = 0;
u16 portchange, portstatus;
unsigned connection = 0xffff;
+ int total_time, stable_time = 0;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
ret = hub_port_status(hub, port1, &portstatus, &portchange);
@@ -3924,9 +4036,8 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)
msleep(HUB_DEBOUNCE_STEP);
}
- dev_dbg (hub->intfdev,
- "debounce: port %d: total %dms stable %dms status 0x%x\n",
- port1, total_time, stable_time, portstatus);
+ dev_dbg(&port_dev->dev, "debounce total %dms stable %dms status 0x%x\n",
+ total_time, stable_time, portstatus);
if (stable_time < HUB_DEBOUNCE_STABLE)
return -ETIMEDOUT;
@@ -3985,13 +4096,14 @@ static int hub_set_address(struct usb_device *udev, int devnum)
*/
static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev)
{
- int connect_type;
+ struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
+ int connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
if (!udev->usb2_hw_lpm_capable)
return;
- connect_type = usb_get_hub_port_connect_type(udev->parent,
- udev->portnum);
+ if (hub)
+ connect_type = hub->ports[udev->portnum - 1]->connect_type;
if ((udev->bos->ext_cap->bmAttributes & cpu_to_le32(USB_BESL_SUPPORT)) ||
connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
@@ -4019,16 +4131,15 @@ static int hub_enable_device(struct usb_device *udev)
* Returns device in USB_STATE_ADDRESS, except on error.
*
* If this is called for an already-existing device (as part of
- * usb_reset_and_verify_device), the caller must own the device lock. For a
- * newly detected device that is not accessible through any global
- * pointers, it's not necessary to lock the device.
+ * usb_reset_and_verify_device), the caller must own the device lock and
+ * the port lock. For a newly detected device that is not accessible
+ * through any global pointers, it's not necessary to lock the device,
+ * but it is still necessary to lock the port.
*/
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
- static DEFINE_MUTEX(usb_address0_mutex);
-
struct usb_device *hdev = hub->hdev;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
int i, j, retval;
@@ -4051,7 +4162,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
if (oldspeed == USB_SPEED_LOW)
delay = HUB_LONG_RESET_TIME;
- mutex_lock(&usb_address0_mutex);
+ mutex_lock(&hdev->bus->usb_address0_mutex);
/* Reset the device; full speed may morph to high speed */
/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */
@@ -4328,7 +4439,7 @@ fail:
hub_port_disable(hub, port1, 0);
update_devnum(udev, devnum); /* for disconnect processing */
}
- mutex_unlock(&usb_address0_mutex);
+ mutex_unlock(&hdev->bus->usb_address0_mutex);
return retval;
}
@@ -4369,9 +4480,10 @@ hub_power_remaining (struct usb_hub *hub)
remaining = hdev->bus_mA - hub->descriptor->bHubContrCurrent;
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
- struct usb_device *udev = hub->ports[port1 - 1]->child;
- int delta;
- unsigned unit_load;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ struct usb_device *udev = port_dev->child;
+ unsigned unit_load;
+ int delta;
if (!udev)
continue;
@@ -4391,9 +4503,8 @@ hub_power_remaining (struct usb_hub *hub)
else
delta = 8;
if (delta > hub->mA_per_port)
- dev_warn(&udev->dev,
- "%dmA is over %umA budget for port %d!\n",
- delta, hub->mA_per_port, port1);
+ dev_warn(&port_dev->dev, "%dmA is over %umA budget!\n",
+ delta, hub->mA_per_port);
remaining -= delta;
}
if (remaining < 0) {
@@ -4404,78 +4515,23 @@ hub_power_remaining (struct usb_hub *hub)
return remaining;
}
-/* Handle physical or logical connection change events.
- * This routine is called when:
- * a port connection-change occurs;
- * a port enable-change occurs (often caused by EMI);
- * usb_reset_and_verify_device() encounters changed descriptors (as from
- * a firmware download)
- * caller already locked the hub
- */
-static void hub_port_connect_change(struct usb_hub *hub, int port1,
- u16 portstatus, u16 portchange)
+static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
+ u16 portchange)
{
- struct usb_device *hdev = hub->hdev;
- struct device *hub_dev = hub->intfdev;
- struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
- unsigned wHubCharacteristics =
- le16_to_cpu(hub->descriptor->wHubCharacteristics);
- struct usb_device *udev;
int status, i;
unsigned unit_load;
-
- dev_dbg (hub_dev,
- "port %d, status %04x, change %04x, %s\n",
- port1, portstatus, portchange, portspeed(hub, portstatus));
-
- if (hub->has_indicators) {
- set_port_led(hub, port1, HUB_LED_AUTO);
- hub->indicator[port1-1] = INDICATOR_AUTO;
- }
-
-#ifdef CONFIG_USB_OTG
- /* during HNP, don't repeat the debounce */
- if (hdev->bus->is_b_host)
- portchange &= ~(USB_PORT_STAT_C_CONNECTION |
- USB_PORT_STAT_C_ENABLE);
-#endif
-
- /* Try to resuscitate an existing device */
- udev = hub->ports[port1 - 1]->child;
- if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
- udev->state != USB_STATE_NOTATTACHED) {
- usb_lock_device(udev);
- if (portstatus & USB_PORT_STAT_ENABLE) {
- status = 0; /* Nothing to do */
-
-#ifdef CONFIG_PM_RUNTIME
- } else if (udev->state == USB_STATE_SUSPENDED &&
- udev->persist_enabled) {
- /* For a suspended device, treat this as a
- * remote wakeup event.
- */
- status = usb_remote_wakeup(udev);
-#endif
-
- } else {
- status = -ENODEV; /* Don't resuscitate */
- }
- usb_unlock_device(udev);
-
- if (status == 0) {
- clear_bit(port1, hub->change_bits);
- return;
- }
- }
+ struct usb_device *hdev = hub->hdev;
+ struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ struct usb_device *udev = port_dev->child;
/* Disconnect any existing devices under this port */
if (udev) {
if (hcd->phy && !hdev->parent &&
!(portstatus & USB_PORT_STAT_CONNECTION))
usb_phy_notify_disconnect(hcd->phy, udev->speed);
- usb_disconnect(&hub->ports[port1 - 1]->child);
+ usb_disconnect(&port_dev->child);
}
- clear_bit(port1, hub->change_bits);
/* We can forget about a "removed" device when there's a physical
* disconnect or the connect status changes.
@@ -4489,8 +4545,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
status = hub_port_debounce_be_stable(hub, port1);
if (status < 0) {
if (status != -ENODEV && printk_ratelimit())
- dev_err(hub_dev, "connect-debounce failed, "
- "port %d disabled\n", port1);
+ dev_err(&port_dev->dev,
+ "connect-debounce failed\n");
portstatus &= ~USB_PORT_STAT_CONNECTION;
} else {
portstatus = status;
@@ -4504,7 +4560,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
test_bit(port1, hub->removed_bits)) {
/* maybe switch power back on (e.g. root hub was reset) */
- if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
+ if (hub_is_port_power_switchable(hub)
&& !port_is_power_on(hub, portstatus))
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
@@ -4525,9 +4581,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
*/
udev = usb_alloc_dev(hdev, hdev->bus, port1);
if (!udev) {
- dev_err (hub_dev,
- "couldn't allocate port %d usb_device\n",
- port1);
+ dev_err(&port_dev->dev,
+ "couldn't allocate usb_device\n");
goto done;
}
@@ -4549,7 +4604,9 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
}
/* reset (non-USB 3.0 devices) and get descriptor */
+ usb_lock_port(port_dev);
status = hub_port_init(hub, udev, port1, i);
+ usb_unlock_port(port_dev);
if (status < 0)
goto loop;
@@ -4601,6 +4658,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.
@@ -4609,16 +4668,19 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
if (hdev->state == USB_STATE_NOTATTACHED)
status = -ENOTCONN;
else
- hub->ports[port1 - 1]->child = udev;
+ 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);
- hub->ports[port1 - 1]->child = NULL;
+ port_dev->child = NULL;
spin_unlock_irq(&device_state_lock);
+ mutex_unlock(&usb_port_peer_mutex);
}
}
@@ -4627,7 +4689,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
status = hub_power_remaining(hub);
if (status)
- dev_dbg(hub_dev, "%dmA power budget left\n", status);
+ dev_dbg(hub->intfdev, "%dmA power budget left\n", status);
return;
@@ -4645,56 +4707,200 @@ loop:
!hcd->driver->port_handed_over ||
!(hcd->driver->port_handed_over)(hcd, port1)) {
if (status != -ENOTCONN && status != -ENODEV)
- dev_err(hub_dev, "unable to enumerate USB device on port %d\n",
- port1);
+ dev_err(&port_dev->dev,
+ "unable to enumerate USB device\n");
}
done:
hub_port_disable(hub, port1, 1);
if (hcd->driver->relinquish_port && !hub->hdev->parent)
hcd->driver->relinquish_port(hcd, port1);
+
}
-/* Returns 1 if there was a remote wakeup and a connect status change. */
-static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
- u16 portstatus, u16 portchange)
+/* Handle physical or logical connection change events.
+ * This routine is called when:
+ * a port connection-change occurs;
+ * a port enable-change occurs (often caused by EMI);
+ * usb_reset_and_verify_device() encounters changed descriptors (as from
+ * a firmware download)
+ * caller already locked the hub
+ */
+static void hub_port_connect_change(struct usb_hub *hub, int port1,
+ u16 portstatus, u16 portchange)
+ __must_hold(&port_dev->status_lock)
{
- struct usb_device *hdev;
- struct usb_device *udev;
- int connect_change = 0;
- int ret;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ struct usb_device *udev = port_dev->child;
+ int status = -ENODEV;
- hdev = hub->hdev;
- udev = hub->ports[port - 1]->child;
- if (!hub_is_superspeed(hdev)) {
- if (!(portchange & USB_PORT_STAT_C_SUSPEND))
- return 0;
- usb_clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
- } else {
- if (!udev || udev->state != USB_STATE_SUSPENDED ||
- (portstatus & USB_PORT_STAT_LINK_STATE) !=
- USB_SS_PORT_LS_U0)
- return 0;
+ dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus,
+ portchange, portspeed(hub, portstatus));
+
+ if (hub->has_indicators) {
+ set_port_led(hub, port1, HUB_LED_AUTO);
+ hub->indicator[port1-1] = INDICATOR_AUTO;
}
- if (udev) {
- /* TRSMRCY = 10 msec */
- msleep(10);
+#ifdef CONFIG_USB_OTG
+ /* during HNP, don't repeat the debounce */
+ if (hub->hdev->bus->is_b_host)
+ portchange &= ~(USB_PORT_STAT_C_CONNECTION |
+ USB_PORT_STAT_C_ENABLE);
+#endif
+
+ /* Try to resuscitate an existing device */
+ if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
+ udev->state != USB_STATE_NOTATTACHED) {
+ if (portstatus & USB_PORT_STAT_ENABLE) {
+ status = 0; /* Nothing to do */
+#ifdef CONFIG_PM_RUNTIME
+ } else if (udev->state == USB_STATE_SUSPENDED &&
+ udev->persist_enabled) {
+ /* For a suspended device, treat this as a
+ * remote wakeup event.
+ */
+ usb_unlock_port(port_dev);
+ status = usb_remote_wakeup(udev);
+ usb_lock_port(port_dev);
+#endif
+ } else {
+ /* Don't resuscitate */;
+ }
+ }
+ clear_bit(port1, hub->change_bits);
+
+ /* successfully revalidated the connection */
+ if (status == 0)
+ return;
+
+ usb_unlock_port(port_dev);
+ hub_port_connect(hub, port1, portstatus, portchange);
+ usb_lock_port(port_dev);
+}
+
+static void port_event(struct usb_hub *hub, int port1)
+ __must_hold(&port_dev->status_lock)
+{
+ int connect_change, reset_device = 0;
+ struct usb_port *port_dev = hub->ports[port1 - 1];
+ struct usb_device *udev = port_dev->child;
+ struct usb_device *hdev = hub->hdev;
+ u16 portstatus, portchange;
+
+ connect_change = test_bit(port1, hub->change_bits);
+ clear_bit(port1, hub->event_bits);
+ clear_bit(port1, hub->wakeup_bits);
+
+ if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
+ return;
+
+ if (portchange & USB_PORT_STAT_C_CONNECTION) {
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
+ connect_change = 1;
+ }
+
+ if (portchange & USB_PORT_STAT_C_ENABLE) {
+ if (!connect_change)
+ dev_dbg(&port_dev->dev, "enable change, status %08x\n",
+ portstatus);
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
+
+ /*
+ * EM interference sometimes causes badly shielded USB devices
+ * to be shutdown by the hub, this hack enables them again.
+ * Works at least with mouse driver.
+ */
+ if (!(portstatus & USB_PORT_STAT_ENABLE)
+ && !connect_change && udev) {
+ dev_err(&port_dev->dev, "disabled by hub (EMI?), re-enabling...\n");
+ connect_change = 1;
+ }
+ }
+
+ if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
+ u16 status = 0, unused;
+
+ dev_dbg(&port_dev->dev, "over-current change\n");
+ usb_clear_port_feature(hdev, port1,
+ USB_PORT_FEAT_C_OVER_CURRENT);
+ msleep(100); /* Cool down */
+ hub_power_on(hub, true);
+ hub_port_status(hub, port1, &status, &unused);
+ if (status & USB_PORT_STAT_OVERCURRENT)
+ dev_err(&port_dev->dev, "over-current condition\n");
+ }
+
+ if (portchange & USB_PORT_STAT_C_RESET) {
+ dev_dbg(&port_dev->dev, "reset change\n");
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET);
+ }
+ if ((portchange & USB_PORT_STAT_C_BH_RESET)
+ && hub_is_superspeed(hdev)) {
+ dev_dbg(&port_dev->dev, "warm reset change\n");
+ usb_clear_port_feature(hdev, port1,
+ USB_PORT_FEAT_C_BH_PORT_RESET);
+ }
+ if (portchange & USB_PORT_STAT_C_LINK_STATE) {
+ dev_dbg(&port_dev->dev, "link state change\n");
+ usb_clear_port_feature(hdev, port1,
+ USB_PORT_FEAT_C_PORT_LINK_STATE);
+ }
+ if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
+ dev_warn(&port_dev->dev, "config error\n");
+ usb_clear_port_feature(hdev, port1,
+ USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
+ }
+
+ /* skip port actions that require the port to be powered on */
+ if (!pm_runtime_active(&port_dev->dev))
+ return;
+ if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange))
+ connect_change = 1;
+
+ /*
+ * Warm reset a USB3 protocol port if it's in
+ * SS.Inactive state.
+ */
+ if (hub_port_warm_reset_required(hub, portstatus)) {
+ dev_dbg(&port_dev->dev, "do warm reset\n");
+ if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)
+ || udev->state == USB_STATE_NOTATTACHED) {
+ if (hub_port_reset(hub, port1, NULL,
+ HUB_BH_RESET_TIME, true) < 0)
+ hub_port_disable(hub, port1, 1);
+ } else
+ reset_device = 1;
+ }
+
+ /*
+ * On disconnect USB3 protocol ports transit from U0 to
+ * SS.Inactive to Rx.Detect. If this happens a warm-
+ * reset is not needed, but a (re)connect may happen
+ * before khubd runs and sees the disconnect, and the
+ * device may be an unknown state.
+ *
+ * If the port went through SS.Inactive without khubd
+ * seeing it the C_LINK_STATE change flag will be set,
+ * and we reset the dev to put it in a known state.
+ */
+ if (reset_device || (udev && hub_is_superspeed(hub->hdev)
+ && (portchange & USB_PORT_STAT_C_LINK_STATE)
+ && (portstatus & USB_PORT_STAT_CONNECTION))) {
+ usb_unlock_port(port_dev);
usb_lock_device(udev);
- ret = usb_remote_wakeup(udev);
+ usb_reset_device(udev);
usb_unlock_device(udev);
- if (ret < 0)
- connect_change = 1;
- } else {
- ret = -ENODEV;
- hub_port_disable(hub, port, 1);
+ usb_lock_port(port_dev);
+ connect_change = 0;
}
- dev_dbg(hub->intfdev, "resume on port %d, status %d\n",
- port, ret);
- return connect_change;
+
+ if (connect_change)
+ hub_port_connect_change(hub, port1, portstatus, portchange);
}
+
static void hub_events(void)
{
struct list_head *tmp;
@@ -4704,10 +4910,7 @@ static void hub_events(void)
struct device *hub_dev;
u16 hubstatus;
u16 hubchange;
- u16 portstatus;
- u16 portchange;
int i, ret;
- int connect_change, wakeup_change;
/*
* We restart the list every time to avoid a deadlock with
@@ -4781,146 +4984,28 @@ static void hub_events(void)
/* deal with port status changes */
for (i = 1; i <= hdev->maxchild; i++) {
- struct usb_device *udev = hub->ports[i - 1]->child;
-
- if (test_bit(i, hub->busy_bits))
- continue;
- connect_change = test_bit(i, hub->change_bits);
- wakeup_change = test_and_clear_bit(i, hub->wakeup_bits);
- if (!test_and_clear_bit(i, hub->event_bits) &&
- !connect_change && !wakeup_change)
- continue;
-
- ret = hub_port_status(hub, i,
- &portstatus, &portchange);
- if (ret < 0)
- continue;
-
- if (portchange & USB_PORT_STAT_C_CONNECTION) {
- usb_clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_CONNECTION);
- connect_change = 1;
- }
-
- if (portchange & USB_PORT_STAT_C_ENABLE) {
- if (!connect_change)
- dev_dbg (hub_dev,
- "port %d enable change, "
- "status %08x\n",
- i, portstatus);
- usb_clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_ENABLE);
+ struct usb_port *port_dev = hub->ports[i - 1];
+ if (test_bit(i, hub->event_bits)
+ || test_bit(i, hub->change_bits)
+ || test_bit(i, hub->wakeup_bits)) {
/*
- * EM interference sometimes causes badly
- * shielded USB devices to be shutdown by
- * the hub, this hack enables them again.
- * Works at least with mouse driver.
+ * The get_noresume and barrier ensure that if
+ * the port was in the process of resuming, we
+ * flush that work and keep the port active for
+ * the duration of the port_event(). However,
+ * if the port is runtime pm suspended
+ * (powered-off), we leave it in that state, run
+ * an abbreviated port_event(), and move on.
*/
- if (!(portstatus & USB_PORT_STAT_ENABLE)
- && !connect_change
- && hub->ports[i - 1]->child) {
- dev_err (hub_dev,
- "port %i "
- "disabled by hub (EMI?), "
- "re-enabling...\n",
- i);
- connect_change = 1;
- }
- }
-
- if (hub_handle_remote_wakeup(hub, i,
- portstatus, portchange))
- connect_change = 1;
-
- if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
- u16 status = 0;
- u16 unused;
-
- dev_dbg(hub_dev, "over-current change on port "
- "%d\n", i);
- usb_clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_OVER_CURRENT);
- msleep(100); /* Cool down */
- hub_power_on(hub, true);
- hub_port_status(hub, i, &status, &unused);
- if (status & USB_PORT_STAT_OVERCURRENT)
- dev_err(hub_dev, "over-current "
- "condition on port %d\n", i);
- }
-
- if (portchange & USB_PORT_STAT_C_RESET) {
- dev_dbg (hub_dev,
- "reset change on port %d\n",
- i);
- usb_clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_RESET);
- }
- if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
- hub_is_superspeed(hub->hdev)) {
- dev_dbg(hub_dev,
- "warm reset change on port %d\n",
- i);
- usb_clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_BH_PORT_RESET);
- }
- if (portchange & USB_PORT_STAT_C_LINK_STATE) {
- usb_clear_port_feature(hub->hdev, i,
- USB_PORT_FEAT_C_PORT_LINK_STATE);
- }
- if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
- dev_warn(hub_dev,
- "config error on port %d\n",
- i);
- usb_clear_port_feature(hub->hdev, i,
- USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
+ pm_runtime_get_noresume(&port_dev->dev);
+ pm_runtime_barrier(&port_dev->dev);
+ usb_lock_port(port_dev);
+ port_event(hub, i);
+ usb_unlock_port(port_dev);
+ pm_runtime_put_sync(&port_dev->dev);
}
-
- /* Warm reset a USB3 protocol port if it's in
- * SS.Inactive state.
- */
- if (hub_port_warm_reset_required(hub, portstatus)) {
- int status;
-
- dev_dbg(hub_dev, "warm reset port %d\n", i);
- if (!udev ||
- !(portstatus & USB_PORT_STAT_CONNECTION) ||
- udev->state == USB_STATE_NOTATTACHED) {
- status = hub_port_reset(hub, i,
- NULL, HUB_BH_RESET_TIME,
- true);
- if (status < 0)
- hub_port_disable(hub, i, 1);
- } else {
- usb_lock_device(udev);
- status = usb_reset_device(udev);
- usb_unlock_device(udev);
- connect_change = 0;
- }
- /*
- * On disconnect USB3 protocol ports transit from U0 to
- * SS.Inactive to Rx.Detect. If this happens a warm-
- * reset is not needed, but a (re)connect may happen
- * before khubd runs and sees the disconnect, and the
- * device may be an unknown state.
- *
- * If the port went through SS.Inactive without khubd
- * seeing it the C_LINK_STATE change flag will be set,
- * and we reset the dev to put it in a known state.
- */
- } else if (udev && hub_is_superspeed(hub->hdev) &&
- (portchange & USB_PORT_STAT_C_LINK_STATE) &&
- (portstatus & USB_PORT_STAT_CONNECTION)) {
- usb_lock_device(udev);
- usb_reset_device(udev);
- usb_unlock_device(udev);
- connect_change = 0;
- }
-
- if (connect_change)
- hub_port_connect_change(hub, i,
- portstatus, portchange);
- } /* end for i */
+ }
/* deal with hub status changes */
if (test_and_clear_bit(0, hub->event_bits) == 0)
@@ -5155,15 +5240,18 @@ static int descriptors_changed(struct usb_device *udev,
* if the reset wasn't even attempted.
*
* Note:
- * The caller must own the device lock. For example, it's safe to use
- * this from a driver probe() routine after downloading new firmware.
- * For calls that might not occur during probe(), drivers should lock
- * the device using usb_lock_device_for_reset().
+ * The caller must own the device lock and the port lock, the latter is
+ * taken by usb_reset_device(). For example, it's safe to use
+ * usb_reset_device() from a driver probe() routine after downloading
+ * new firmware. For calls that might not occur during probe(), drivers
+ * should lock the device using usb_lock_device_for_reset().
*
* Locking exception: This routine may also be called from within an
* autoresume handler. Such usage won't conflict with other tasks
* holding the device lock because these tasks should always call
- * usb_autopm_resume_device(), thereby preventing any unwanted autoresume.
+ * usb_autopm_resume_device(), thereby preventing any unwanted
+ * autoresume. The autoresume handler is expected to have already
+ * acquired the port lock before calling this routine.
*/
static int usb_reset_and_verify_device(struct usb_device *udev)
{
@@ -5182,11 +5270,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
return -EINVAL;
}
- if (!parent_hdev) {
- /* this requires hcd-specific logic; see ohci_restart() */
- dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
+ if (!parent_hdev)
return -EISDIR;
- }
+
parent_hub = usb_hub_to_struct_hub(parent_hdev);
/* Disable USB2 hardware LPM.
@@ -5215,7 +5301,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
goto re_enumerate;
}
- set_bit(port1, parent_hub->busy_bits);
for (i = 0; i < SET_CONFIG_TRIES; ++i) {
/* ep0 maxpacket size may change; let the HCD know about it.
@@ -5225,7 +5310,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
break;
}
- clear_bit(port1, parent_hub->busy_bits);
if (ret < 0)
goto re_enumerate;
@@ -5346,7 +5430,9 @@ int usb_reset_device(struct usb_device *udev)
int ret;
int i;
unsigned int noio_flag;
+ struct usb_port *port_dev;
struct usb_host_config *config = udev->actconfig;
+ struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
if (udev->state == USB_STATE_NOTATTACHED ||
udev->state == USB_STATE_SUSPENDED) {
@@ -5355,6 +5441,14 @@ int usb_reset_device(struct usb_device *udev)
return -EINVAL;
}
+ if (!udev->parent) {
+ /* this requires hcd-specific logic; see ohci_restart() */
+ dev_dbg(&udev->dev, "%s for root hub!\n", __func__);
+ return -EISDIR;
+ }
+
+ port_dev = hub->ports[udev->portnum - 1];
+
/*
* Don't allocate memory with GFP_KERNEL in current
* context to avoid possible deadlock if usb mass
@@ -5388,7 +5482,9 @@ int usb_reset_device(struct usb_device *udev)
}
}
+ usb_lock_port(port_dev);
ret = usb_reset_and_verify_device(udev);
+ usb_unlock_port(port_dev);
if (config) {
for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) {
@@ -5483,56 +5579,26 @@ struct usb_device *usb_hub_find_child(struct usb_device *hdev,
}
EXPORT_SYMBOL_GPL(usb_hub_find_child);
-/**
- * usb_set_hub_port_connect_type - set hub port connect type.
- * @hdev: USB device belonging to the usb hub
- * @port1: port num of the port
- * @type: connect type of the port
- */
-void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
- enum usb_port_connect_type type)
-{
- struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
-
- if (hub)
- hub->ports[port1 - 1]->connect_type = type;
-}
-
-/**
- * usb_get_hub_port_connect_type - Get the port's connect type
- * @hdev: USB device belonging to the usb hub
- * @port1: port num of the port
- *
- * Return: The connect type of the port if successful. Or
- * USB_PORT_CONNECT_TYPE_UNKNOWN if input params are invalid.
- */
-enum usb_port_connect_type
-usb_get_hub_port_connect_type(struct usb_device *hdev, int port1)
-{
- struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
-
- if (!hub)
- return USB_PORT_CONNECT_TYPE_UNKNOWN;
-
- return hub->ports[port1 - 1]->connect_type;
-}
-
void usb_hub_adjust_deviceremovable(struct usb_device *hdev,
struct usb_hub_descriptor *desc)
{
+ struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
enum usb_port_connect_type connect_type;
int i;
+ if (!hub)
+ return;
+
if (!hub_is_superspeed(hdev)) {
for (i = 1; i <= hdev->maxchild; i++) {
- connect_type = usb_get_hub_port_connect_type(hdev, i);
+ struct usb_port *port_dev = hub->ports[i - 1];
+ connect_type = port_dev->connect_type;
if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
u8 mask = 1 << (i%8);
if (!(desc->u.hs.DeviceRemovable[i/8] & mask)) {
- dev_dbg(&hdev->dev, "usb port%d's DeviceRemovable is changed to 1 according to platform information.\n",
- i);
+ dev_dbg(&port_dev->dev, "DeviceRemovable is changed to 1 according to platform information.\n");
desc->u.hs.DeviceRemovable[i/8] |= mask;
}
}
@@ -5541,14 +5607,14 @@ void usb_hub_adjust_deviceremovable(struct usb_device *hdev,
u16 port_removable = le16_to_cpu(desc->u.ss.DeviceRemovable);
for (i = 1; i <= hdev->maxchild; i++) {
- connect_type = usb_get_hub_port_connect_type(hdev, i);
+ struct usb_port *port_dev = hub->ports[i - 1];
+ connect_type = port_dev->connect_type;
if (connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
u16 mask = 1 << i;
if (!(port_removable & mask)) {
- dev_dbg(&hdev->dev, "usb port%d's DeviceRemovable is changed to 1 according to platform information.\n",
- i);
+ dev_dbg(&port_dev->dev, "DeviceRemovable is changed to 1 according to platform information.\n");
port_removable |= mask;
}
}
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 33bcb2c6f90a..0a7cdc0ef0a9 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -45,12 +45,13 @@ struct usb_hub {
unsigned long event_bits[1]; /* status change bitmask */
unsigned long change_bits[1]; /* ports with logical connect
status change */
- unsigned long busy_bits[1]; /* ports being reset or
- resumed */
unsigned long removed_bits[1]; /* ports with a "removed"
device present */
unsigned long wakeup_bits[1]; /* ports that have signaled
remote wakeup */
+ unsigned long power_bits[1]; /* ports that are powered */
+ unsigned long child_usage_bits[1]; /* ports powered on for
+ children */
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
#error event_bits[] is too short!
#endif
@@ -66,6 +67,7 @@ struct usb_hub {
unsigned limited_power:1;
unsigned quiescing:1;
unsigned disconnected:1;
+ unsigned in_reset:1;
unsigned quirk_check_port_auto_suspend:1;
@@ -81,19 +83,23 @@ 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
+ * @location: opaque representation of platform connector location
+ * @status_lock: synchronize port_event() vs usb_port_{suspend|resume}
* @portnum: port index num based one
- * @power_is_on: port's power state
- * @did_runtime_put: port has done pm_runtime_put().
+ * @is_superspeed cache super-speed status
*/
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;
+ usb_port_location_t location;
+ struct mutex status_lock;
u8 portnum;
- unsigned power_is_on:1;
- unsigned did_runtime_put:1;
+ unsigned int is_superspeed:1;
};
#define to_usb_port(_dev) \
@@ -111,6 +117,29 @@ extern int hub_port_debounce(struct usb_hub *hub, int port1,
extern int usb_clear_port_feature(struct usb_device *hdev,
int port1, int feature);
+static inline bool hub_is_port_power_switchable(struct usb_hub *hub)
+{
+ __le16 hcs;
+
+ if (!hub)
+ return false;
+ hcs = hub->descriptor->wHubCharacteristics;
+ return (le16_to_cpu(hcs) & HUB_CHAR_LPSM) < HUB_CHAR_NO_LPSM;
+}
+
+static inline int hub_is_superspeed(struct usb_device *hdev)
+{
+ return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS;
+}
+
+static inline unsigned hub_power_on_good_delay(struct usb_hub *hub)
+{
+ unsigned delay = hub->descriptor->bPwrOn2PwrGood * 2;
+
+ /* Wait at least 100 msec for power to become stable */
+ return max(delay, 100U);
+}
+
static inline int hub_port_debounce_be_connected(struct usb_hub *hub,
int port1)
{
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 51542f852393..62036faf56c0 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -76,17 +76,29 @@ static int usb_port_runtime_resume(struct device *dev)
struct usb_device *hdev = to_usb_device(dev->parent->parent);
struct usb_interface *intf = to_usb_interface(dev->parent);
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+ struct usb_device *udev = port_dev->child;
+ struct usb_port *peer = port_dev->peer;
int port1 = port_dev->portnum;
int retval;
if (!hub)
return -EINVAL;
+ if (hub->in_reset) {
+ set_bit(port1, hub->power_bits);
+ return 0;
+ }
- usb_autopm_get_interface(intf);
- set_bit(port1, hub->busy_bits);
+ /*
+ * Power on our usb3 peer before this usb2 port to prevent a usb3
+ * device from degrading to its usb2 connection
+ */
+ if (!port_dev->is_superspeed && peer)
+ pm_runtime_get_sync(&peer->dev);
+ usb_autopm_get_interface(intf);
retval = usb_hub_set_port_power(hdev, hub, port1, true);
- if (port_dev->child && !retval) {
+ msleep(hub_power_on_good_delay(hub));
+ if (udev && !retval) {
/*
* Attempt to wait for usb hub port to be reconnected in order
* to make the resume procedure successful. The device may have
@@ -97,12 +109,17 @@ static int usb_port_runtime_resume(struct device *dev)
if (retval < 0)
dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n",
retval);
- usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
retval = 0;
+
+ /* Force the child awake to revalidate after the power loss. */
+ if (!test_and_set_bit(port1, hub->child_usage_bits)) {
+ pm_runtime_get_noresume(&port_dev->dev);
+ pm_request_resume(&udev->dev);
+ }
}
- clear_bit(port1, hub->busy_bits);
usb_autopm_put_interface(intf);
+
return retval;
}
@@ -112,23 +129,34 @@ static int usb_port_runtime_suspend(struct device *dev)
struct usb_device *hdev = to_usb_device(dev->parent->parent);
struct usb_interface *intf = to_usb_interface(dev->parent);
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+ struct usb_port *peer = port_dev->peer;
int port1 = port_dev->portnum;
int retval;
if (!hub)
return -EINVAL;
+ if (hub->in_reset)
+ return -EBUSY;
if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF)
== PM_QOS_FLAGS_ALL)
return -EAGAIN;
usb_autopm_get_interface(intf);
- set_bit(port1, hub->busy_bits);
retval = usb_hub_set_port_power(hdev, hub, port1, false);
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
- usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
- clear_bit(port1, hub->busy_bits);
+ if (!port_dev->is_superspeed)
+ usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
usb_autopm_put_interface(intf);
+
+ /*
+ * Our peer usb3 port may now be able to suspend, so
+ * asynchronously queue a suspend request to observe that this
+ * usb2 port is now off.
+ */
+ if (!port_dev->is_superspeed && peer)
+ pm_runtime_put(&peer->dev);
+
return retval;
}
#endif
@@ -146,9 +174,215 @@ struct device_type usb_port_device_type = {
.pm = &usb_port_pm_ops,
};
+static struct device_driver usb_port_driver = {
+ .name = "usb",
+ .owner = THIS_MODULE,
+};
+
+static int link_peers(struct usb_port *left, struct usb_port *right)
+{
+ struct usb_port *ss_port, *hs_port;
+ int rc;
+
+ if (left->peer == right && right->peer == left)
+ return 0;
+
+ 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 -EBUSY;
+ }
+
+ rc = sysfs_create_link(&left->dev.kobj, &right->dev.kobj, "peer");
+ if (rc)
+ return rc;
+ rc = sysfs_create_link(&right->dev.kobj, &left->dev.kobj, "peer");
+ if (rc) {
+ sysfs_remove_link(&left->dev.kobj, "peer");
+ return rc;
+ }
+
+ /*
+ * We need to wake the HiSpeed port to make sure we don't race
+ * setting ->peer with usb_port_runtime_suspend(). Otherwise we
+ * may miss a suspend event for the SuperSpeed port.
+ */
+ if (left->is_superspeed) {
+ ss_port = left;
+ WARN_ON(right->is_superspeed);
+ hs_port = right;
+ } else {
+ ss_port = right;
+ WARN_ON(!right->is_superspeed);
+ hs_port = left;
+ }
+ pm_runtime_get_sync(&hs_port->dev);
+
+ left->peer = right;
+ right->peer = left;
+
+ /*
+ * The SuperSpeed reference is dropped when the HiSpeed port in
+ * this relationship suspends, i.e. when it is safe to allow a
+ * SuperSpeed connection to drop since there is no risk of a
+ * device degrading to its powered-off HiSpeed connection.
+ *
+ * Also, drop the HiSpeed ref taken above.
+ */
+ pm_runtime_get_sync(&ss_port->dev);
+ pm_runtime_put(&hs_port->dev);
+
+ return 0;
+}
+
+static void link_peers_report(struct usb_port *left, struct usb_port *right)
+{
+ int rc;
+
+ rc = link_peers(left, right);
+ if (rc == 0) {
+ dev_dbg(&left->dev, "peered to %s\n", dev_name(&right->dev));
+ } else {
+ dev_warn(&left->dev, "failed to peer to %s (%d)\n",
+ dev_name(&right->dev), rc);
+ pr_warn_once("usb: port power management may be unreliable\n");
+ }
+}
+
+static void unlink_peers(struct usb_port *left, struct usb_port *right)
+{
+ struct usb_port *ss_port, *hs_port;
+
+ WARN(right->peer != left || left->peer != right,
+ "%s and %s are not peers?\n",
+ dev_name(&left->dev), dev_name(&right->dev));
+
+ /*
+ * We wake the HiSpeed port to make sure we don't race its
+ * usb_port_runtime_resume() event which takes a SuperSpeed ref
+ * when ->peer is !NULL.
+ */
+ if (left->is_superspeed) {
+ ss_port = left;
+ hs_port = right;
+ } else {
+ ss_port = right;
+ hs_port = left;
+ }
+
+ pm_runtime_get_sync(&hs_port->dev);
+
+ sysfs_remove_link(&left->dev.kobj, "peer");
+ right->peer = NULL;
+ sysfs_remove_link(&right->dev.kobj, "peer");
+ left->peer = NULL;
+
+ /* Drop the SuperSpeed ref held on behalf of the active HiSpeed port */
+ pm_runtime_put(&ss_port->dev);
+
+ /* Drop the ref taken above */
+ pm_runtime_put(&hs_port->dev);
+}
+
+/*
+ * For each usb hub device in the system check to see if it is in the
+ * peer domain of the given port_dev, and if it is check to see if it
+ * has a port that matches the given port by location
+ */
+static int match_location(struct usb_device *peer_hdev, void *p)
+{
+ int port1;
+ struct usb_hcd *hcd, *peer_hcd;
+ struct usb_port *port_dev = p, *peer;
+ struct usb_hub *peer_hub = usb_hub_to_struct_hub(peer_hdev);
+ struct usb_device *hdev = to_usb_device(port_dev->dev.parent->parent);
+
+ if (!peer_hub)
+ return 0;
+
+ hcd = bus_to_hcd(hdev->bus);
+ peer_hcd = bus_to_hcd(peer_hdev->bus);
+ /* peer_hcd is provisional until we verify it against the known peer */
+ if (peer_hcd != hcd->shared_hcd)
+ return 0;
+
+ for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) {
+ peer = peer_hub->ports[port1 - 1];
+ if (peer && peer->location == port_dev->location) {
+ link_peers_report(port_dev, peer);
+ return 1; /* done */
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Find the peer port either via explicit platform firmware "location"
+ * data, the peer hcd for root hubs, or the upstream peer relationship
+ * for all other 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;
+ struct usb_device *peer_hdev;
+ struct usb_hub *peer_hub;
+
+ /*
+ * If location data is available then we can only peer this port
+ * by a location match, not the default peer (lest we create a
+ * situation where we need to go back and undo a default peering
+ * when the port is later peered by location data)
+ */
+ if (port_dev->location) {
+ /* we link the peer in match_location() if found */
+ usb_for_each_dev(port_dev, match_location);
+ return;
+ } else if (!hdev->parent) {
+ 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;
+ } else {
+ struct usb_port *upstream;
+ struct usb_device *parent = hdev->parent;
+ struct usb_hub *parent_hub = usb_hub_to_struct_hub(parent);
+
+ if (!parent_hub)
+ return;
+
+ upstream = parent_hub->ports[hdev->portnum - 1];
+ if (!upstream || !upstream->peer)
+ return;
+
+ peer_hdev = upstream->peer->child;
+ }
+
+ peer_hub = usb_hub_to_struct_hub(peer_hdev);
+ if (!peer_hub || port1 > peer_hdev->maxchild)
+ return;
+
+ /*
+ * we found a valid default peer, last check is to make sure it
+ * does not have location data
+ */
+ peer = peer_hub->ports[port1 - 1];
+ if (peer && peer->location == 0)
+ link_peers_report(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);
@@ -159,24 +393,33 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
hub->ports[port1 - 1] = port_dev;
port_dev->portnum = port1;
- port_dev->power_is_on = true;
+ set_bit(port1, hub->power_bits);
port_dev->dev.parent = hub->intfdev;
port_dev->dev.groups = port_dev_group;
port_dev->dev.type = &usb_port_device_type;
- dev_set_name(&port_dev->dev, "port%d", port1);
-
+ port_dev->dev.driver = &usb_port_driver;
+ if (hub_is_superspeed(hub->hdev))
+ port_dev->is_superspeed = 1;
+ dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev),
+ port1);
+ mutex_init(&port_dev->status_lock);
retval = device_register(&port_dev->dev);
if (retval)
goto error_register;
+ find_and_link_peer(hub, port1);
+
pm_runtime_set_active(&port_dev->dev);
- /* It would be dangerous if user space couldn't
- * prevent usb device from being powered off. So don't
- * enable port runtime pm if failed to expose port's pm qos.
+ /*
+ * Do not enable port runtime pm if the hub does not support
+ * power switching. Also, userspace must have final say of
+ * whether a port is permitted to power-off. Do not enable
+ * runtime pm if we fail to expose pm_qos_no_power_off.
*/
- if (!dev_pm_qos_expose_flags(&port_dev->dev,
- PM_QOS_FLAG_NO_POWER_OFF))
+ if (hub_is_port_power_switchable(hub)
+ && dev_pm_qos_expose_flags(&port_dev->dev,
+ PM_QOS_FLAG_NO_POWER_OFF) == 0)
pm_runtime_enable(&port_dev->dev);
device_enable_async_suspend(&port_dev->dev);
@@ -188,9 +431,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);
+}
diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c
index 5ca4070b1f38..2776cfe64c09 100644
--- a/drivers/usb/core/usb-acpi.c
+++ b/drivers/usb/core/usb-acpi.c
@@ -17,7 +17,7 @@
#include <linux/pci.h>
#include <linux/usb/hcd.h>
-#include "usb.h"
+#include "hub.h"
/**
* usb_acpi_power_manageable - check whether usb port has
@@ -55,13 +55,18 @@ EXPORT_SYMBOL_GPL(usb_acpi_power_manageable);
*/
int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
{
+ struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+ struct usb_port *port_dev;
acpi_handle port_handle;
unsigned char state;
int port1 = index + 1;
int error = -EINVAL;
- port_handle = (acpi_handle)usb_get_hub_port_acpi_handle(hdev,
- port1);
+ if (!hub)
+ return -ENODEV;
+ port_dev = hub->ports[port1 - 1];
+
+ port_handle = (acpi_handle) usb_get_hub_port_acpi_handle(hdev, port1);
if (!port_handle)
return error;
@@ -72,23 +77,21 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
error = acpi_bus_set_power(port_handle, state);
if (!error)
- dev_dbg(&hdev->dev, "The power of hub port %d was set to %d\n",
- port1, enable);
+ dev_dbg(&port_dev->dev, "acpi: power was set to %d\n", enable);
else
- dev_dbg(&hdev->dev, "The power of hub port failed to be set\n");
+ dev_dbg(&port_dev->dev, "acpi: power failed to be set\n");
return error;
}
EXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
-static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
- acpi_handle handle, int port1)
+static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
+ struct acpi_pld_info *pld)
{
- acpi_status status;
+ enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *upc;
- struct acpi_pld_info *pld;
- int ret = 0;
+ acpi_status status;
/*
* According to ACPI Spec 9.13. PLD indicates whether usb port is
@@ -98,39 +101,37 @@ static int usb_acpi_check_port_connect_type(struct usb_device *hdev,
* a usb device is directly hard-wired to the port. If no visible and
* no connectable, the port would be not used.
*/
- status = acpi_get_physical_device_location(handle, &pld);
- if (ACPI_FAILURE(status))
- return -ENODEV;
-
status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
upc = buffer.pointer;
if (!upc || (upc->type != ACPI_TYPE_PACKAGE)
|| upc->package.count != 4) {
- ret = -EINVAL;
goto out;
}
if (upc->package.elements[0].integer.value)
if (pld->user_visible)
- usb_set_hub_port_connect_type(hdev, port1,
- USB_PORT_CONNECT_TYPE_HOT_PLUG);
+ connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG;
else
- usb_set_hub_port_connect_type(hdev, port1,
- USB_PORT_CONNECT_TYPE_HARD_WIRED);
+ connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
else if (!pld->user_visible)
- usb_set_hub_port_connect_type(hdev, port1, USB_PORT_NOT_USED);
-
+ connect_type = USB_PORT_NOT_USED;
out:
- ACPI_FREE(pld);
kfree(upc);
- return ret;
+ return connect_type;
}
+
+/*
+ * Private to usb-acpi, all the core needs to know is that
+ * port_dev->location is non-zero when it has been set by the firmware.
+ */
+#define USB_ACPI_LOCATION_VALID (1 << 31)
+
static struct acpi_device *usb_acpi_find_companion(struct device *dev)
{
struct usb_device *udev;
+ struct acpi_device *adev;
acpi_handle *parent_handle;
- int port_num;
/*
* In the ACPI DSDT table, only usb root hub and usb ports are
@@ -147,37 +148,19 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
*/
if (is_usb_device(dev)) {
udev = to_usb_device(dev);
- if (udev->parent) {
- enum usb_port_connect_type type;
-
- /*
- * According usb port's connect type to set usb device's
- * removability.
- */
- type = usb_get_hub_port_connect_type(udev->parent,
- udev->portnum);
- switch (type) {
- case USB_PORT_CONNECT_TYPE_HOT_PLUG:
- udev->removable = USB_DEVICE_REMOVABLE;
- break;
- case USB_PORT_CONNECT_TYPE_HARD_WIRED:
- udev->removable = USB_DEVICE_FIXED;
- break;
- default:
- udev->removable = USB_DEVICE_REMOVABLE_UNKNOWN;
- break;
- }
-
+ if (udev->parent)
return NULL;
- }
- /* root hub's parent is the usb hcd. */
- return acpi_find_child_device(ACPI_COMPANION(dev->parent),
- udev->portnum, false);
+ /* root hub is only child (_ADR=0) under its parent, the HC */
+ adev = ACPI_COMPANION(dev->parent);
+ return acpi_find_child_device(adev, 0, false);
} else if (is_usb_port(dev)) {
- struct acpi_device *adev = NULL;
+ struct usb_port *port_dev = to_usb_port(dev);
+ int port1 = port_dev->portnum;
+ struct acpi_pld_info *pld;
+ acpi_handle *handle;
+ acpi_status status;
- sscanf(dev_name(dev), "port%d", &port_num);
/* Get the struct usb_device point of port's hub */
udev = to_usb_device(dev->parent->parent);
@@ -188,12 +171,11 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
*/
if (!udev->parent) {
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
- int raw_port_num;
+ int raw;
- raw_port_num = usb_hcd_find_raw_port_number(hcd,
- port_num);
+ raw = usb_hcd_find_raw_port_number(hcd, port1);
adev = acpi_find_child_device(ACPI_COMPANION(&udev->dev),
- raw_port_num, false);
+ raw, false);
if (!adev)
return NULL;
} else {
@@ -204,11 +186,20 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev)
return NULL;
acpi_bus_get_device(parent_handle, &adev);
- adev = acpi_find_child_device(adev, port_num, false);
+ adev = acpi_find_child_device(adev, port1, false);
if (!adev)
return NULL;
}
- usb_acpi_check_port_connect_type(udev, adev->handle, port_num);
+ handle = adev->handle;
+ status = acpi_get_physical_device_location(handle, &pld);
+ if (ACPI_FAILURE(status) || !pld)
+ return adev;
+
+ port_dev->location = USB_ACPI_LOCATION_VALID
+ | pld->group_token << 8 | pld->group_position;
+ port_dev->connect_type = usb_acpi_get_connect_type(handle, pld);
+ ACPI_FREE(pld);
+
return adev;
}
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 75bf649da82d..d9d08720c386 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -107,11 +107,6 @@ static inline int usb_autoresume_device(struct usb_device *udev)
return 0;
}
-static inline int usb_remote_wakeup(struct usb_device *udev)
-{
- return 0;
-}
-
static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable)
{
return 0;
@@ -119,6 +114,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;
@@ -170,15 +166,17 @@ extern void usbfs_conn_disc_event(void);
extern int usb_devio_init(void);
extern void usb_devio_cleanup(void);
+/*
+ * Firmware specific cookie identifying a port's location. '0' == no location
+ * data available
+ */
+typedef u32 usb_port_location_t;
+
/* internal notify stuff */
extern void usb_notify_add_device(struct usb_device *udev);
extern void usb_notify_remove_device(struct usb_device *udev);
extern void usb_notify_add_bus(struct usb_bus *ubus);
extern void usb_notify_remove_bus(struct usb_bus *ubus);
-extern enum usb_port_connect_type
- usb_get_hub_port_connect_type(struct usb_device *hdev, int port1);
-extern void usb_set_hub_port_connect_type(struct usb_device *hdev, int port1,
- enum usb_port_connect_type type);
extern void usb_hub_adjust_deviceremovable(struct usb_device *hdev,
struct usb_hub_descriptor *desc);