2013-01-11 12:10:38 +00:00
|
|
|
/*
|
|
|
|
* usb port device code
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Intel Corp
|
|
|
|
*
|
|
|
|
* Author: Lan Tianyu <tianyu.lan@intel.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* for more details.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2013-01-19 14:30:19 +00:00
|
|
|
#include <linux/slab.h>
|
2013-01-22 20:26:29 +00:00
|
|
|
#include <linux/pm_qos.h>
|
2013-01-19 14:30:19 +00:00
|
|
|
|
2013-01-11 12:10:38 +00:00
|
|
|
#include "hub.h"
|
|
|
|
|
2013-01-19 17:53:32 +00:00
|
|
|
static const struct attribute_group *port_dev_group[];
|
|
|
|
|
2013-08-23 23:05:26 +00:00
|
|
|
static ssize_t connect_type_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2013-01-19 17:53:32 +00:00
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
char *result;
|
|
|
|
|
|
|
|
switch (port_dev->connect_type) {
|
|
|
|
case USB_PORT_CONNECT_TYPE_HOT_PLUG:
|
|
|
|
result = "hotplug";
|
|
|
|
break;
|
|
|
|
case USB_PORT_CONNECT_TYPE_HARD_WIRED:
|
|
|
|
result = "hardwired";
|
|
|
|
break;
|
|
|
|
case USB_PORT_NOT_USED:
|
|
|
|
result = "not used";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result = "unknown";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", result);
|
|
|
|
}
|
2013-08-23 23:05:26 +00:00
|
|
|
static DEVICE_ATTR_RO(connect_type);
|
2013-01-19 17:53:32 +00:00
|
|
|
|
|
|
|
static struct attribute *port_dev_attrs[] = {
|
|
|
|
&dev_attr_connect_type.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute_group port_dev_attr_grp = {
|
|
|
|
.attrs = port_dev_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group *port_dev_group[] = {
|
|
|
|
&port_dev_attr_grp,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2013-01-11 12:10:38 +00:00
|
|
|
static void usb_port_device_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
|
|
|
|
kfree(port_dev);
|
|
|
|
}
|
|
|
|
|
2013-03-27 20:14:46 +00:00
|
|
|
#ifdef CONFIG_PM_RUNTIME
|
2013-01-22 20:26:29 +00:00
|
|
|
static int usb_port_runtime_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
struct usb_device *hdev = to_usb_device(dev->parent->parent);
|
|
|
|
struct usb_interface *intf = to_usb_interface(dev->parent);
|
2013-01-22 20:26:30 +00:00
|
|
|
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
2014-05-21 01:08:57 +00:00
|
|
|
struct usb_port *peer = port_dev->peer;
|
2013-01-22 20:26:30 +00:00
|
|
|
int port1 = port_dev->portnum;
|
2013-01-22 20:26:29 +00:00
|
|
|
int retval;
|
|
|
|
|
2013-01-22 20:26:30 +00:00
|
|
|
if (!hub)
|
|
|
|
return -EINVAL;
|
2014-05-21 01:08:07 +00:00
|
|
|
if (hub->in_reset) {
|
2014-05-21 01:08:52 +00:00
|
|
|
set_bit(port1, hub->power_bits);
|
2014-05-21 01:08:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2013-01-22 20:26:30 +00:00
|
|
|
|
2014-05-21 01:08:57 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2013-01-22 20:26:29 +00:00
|
|
|
usb_autopm_get_interface(intf);
|
2013-01-22 20:26:30 +00:00
|
|
|
set_bit(port1, hub->busy_bits);
|
|
|
|
|
2013-06-18 14:28:48 +00:00
|
|
|
retval = usb_hub_set_port_power(hdev, hub, port1, true);
|
2014-05-21 01:08:57 +00:00
|
|
|
msleep(hub_power_on_good_delay(hub));
|
2013-01-22 20:26:30 +00:00
|
|
|
if (port_dev->child && !retval) {
|
|
|
|
/*
|
usb: Don't fail port power resume on device disconnect.
Userspace can tell the kernel to power off any USB port, including ones
that are visible and connectible to users. When an attached USB device
goes into suspend, the port will be powered off if the
pm_qos_no_port_poweroff file for its port is set to 0, the device does
not have remote wakeup enabled, and the device is marked as persistent.
If the user disconnects the USB device while the port is powered off,
the current code does not handle that properly. If you disconnect a
device, and then run `lsusb -v -s` for the device, the device disconnect
does not get handled by the USB core. The runtime resume of the port
fails, because hub_port_debounce_be_connected() returns -ETIMEDOUT.
This means the port resume fails and khubd doesn't handle the USB device
disconnect. This leaves the device listed in lsusb, and the port's
runtime_status will be permanently marked as "error".
Fix this by ignoring the return value of hub_port_debounce_be_connected.
Users can disconnect USB devices while the ports are powered off, and we
must be able to handle that.
This patch should be backported to kernels as old as 3.9, that
contain the commit ad493e5e580546e6c3024b76a41535476da1546a "usb: add
usb port auto power off mechanism"
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Cc: Lan Tianyu <tianyu.lan@intel.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Cc: stable@vger.kernel.org
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
2013-08-06 01:58:15 +00:00
|
|
|
* Attempt to wait for usb hub port to be reconnected in order
|
|
|
|
* to make the resume procedure successful. The device may have
|
|
|
|
* disconnected while the port was powered off, so ignore the
|
|
|
|
* return status.
|
2013-01-22 20:26:30 +00:00
|
|
|
*/
|
|
|
|
retval = hub_port_debounce_be_connected(hub, port1);
|
usb: Don't fail port power resume on device disconnect.
Userspace can tell the kernel to power off any USB port, including ones
that are visible and connectible to users. When an attached USB device
goes into suspend, the port will be powered off if the
pm_qos_no_port_poweroff file for its port is set to 0, the device does
not have remote wakeup enabled, and the device is marked as persistent.
If the user disconnects the USB device while the port is powered off,
the current code does not handle that properly. If you disconnect a
device, and then run `lsusb -v -s` for the device, the device disconnect
does not get handled by the USB core. The runtime resume of the port
fails, because hub_port_debounce_be_connected() returns -ETIMEDOUT.
This means the port resume fails and khubd doesn't handle the USB device
disconnect. This leaves the device listed in lsusb, and the port's
runtime_status will be permanently marked as "error".
Fix this by ignoring the return value of hub_port_debounce_be_connected.
Users can disconnect USB devices while the ports are powered off, and we
must be able to handle that.
This patch should be backported to kernels as old as 3.9, that
contain the commit ad493e5e580546e6c3024b76a41535476da1546a "usb: add
usb port auto power off mechanism"
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Cc: Lan Tianyu <tianyu.lan@intel.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Cc: stable@vger.kernel.org
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
2013-08-06 01:58:15 +00:00
|
|
|
if (retval < 0)
|
2013-01-22 20:26:30 +00:00
|
|
|
dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n",
|
|
|
|
retval);
|
|
|
|
retval = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
clear_bit(port1, hub->busy_bits);
|
2013-01-22 20:26:29 +00:00
|
|
|
usb_autopm_put_interface(intf);
|
2014-05-21 01:08:57 +00:00
|
|
|
|
2013-01-22 20:26:29 +00:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int usb_port_runtime_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
struct usb_device *hdev = to_usb_device(dev->parent->parent);
|
|
|
|
struct usb_interface *intf = to_usb_interface(dev->parent);
|
2013-01-22 20:26:30 +00:00
|
|
|
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
2014-05-21 01:08:57 +00:00
|
|
|
struct usb_port *peer = port_dev->peer;
|
2013-01-22 20:26:30 +00:00
|
|
|
int port1 = port_dev->portnum;
|
2013-01-22 20:26:29 +00:00
|
|
|
int retval;
|
|
|
|
|
2013-01-22 20:26:30 +00:00
|
|
|
if (!hub)
|
|
|
|
return -EINVAL;
|
2014-05-21 01:08:07 +00:00
|
|
|
if (hub->in_reset)
|
|
|
|
return -EBUSY;
|
2013-01-22 20:26:30 +00:00
|
|
|
|
2013-01-22 20:26:29 +00:00
|
|
|
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);
|
2013-01-22 20:26:30 +00:00
|
|
|
set_bit(port1, hub->busy_bits);
|
2013-06-18 14:28:48 +00:00
|
|
|
retval = usb_hub_set_port_power(hdev, hub, port1, false);
|
2013-01-22 20:26:30 +00:00
|
|
|
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);
|
2013-01-22 20:26:29 +00:00
|
|
|
usb_autopm_put_interface(intf);
|
2014-05-21 01:08:57 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2013-01-22 20:26:29 +00:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct dev_pm_ops usb_port_pm_ops = {
|
2013-03-27 20:14:46 +00:00
|
|
|
#ifdef CONFIG_PM_RUNTIME
|
2013-01-22 20:26:29 +00:00
|
|
|
.runtime_suspend = usb_port_runtime_suspend,
|
|
|
|
.runtime_resume = usb_port_runtime_resume,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2013-01-11 12:10:38 +00:00
|
|
|
struct device_type usb_port_device_type = {
|
|
|
|
.name = "usb_port",
|
|
|
|
.release = usb_port_device_release,
|
2013-01-22 20:26:29 +00:00
|
|
|
.pm = &usb_port_pm_ops,
|
2013-01-11 12:10:38 +00:00
|
|
|
};
|
|
|
|
|
2014-05-21 01:08:17 +00:00
|
|
|
static struct device_driver usb_port_driver = {
|
|
|
|
.name = "usb",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
2014-05-21 01:08:45 +00:00
|
|
|
static int link_peers(struct usb_port *left, struct usb_port *right)
|
2014-05-21 01:08:28 +00:00
|
|
|
{
|
2014-05-21 01:08:57 +00:00
|
|
|
struct usb_port *ss_port, *hs_port;
|
2014-05-21 01:08:45 +00:00
|
|
|
int rc;
|
|
|
|
|
2014-05-21 01:08:28 +00:00
|
|
|
if (left->peer == right && right->peer == left)
|
2014-05-21 01:08:45 +00:00
|
|
|
return 0;
|
2014-05-21 01:08:28 +00:00
|
|
|
|
|
|
|
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);
|
2014-05-21 01:08:45 +00:00
|
|
|
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;
|
2014-05-21 01:08:28 +00:00
|
|
|
}
|
|
|
|
|
2014-05-21 01:08:57 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2014-05-21 01:08:28 +00:00
|
|
|
left->peer = right;
|
|
|
|
right->peer = left;
|
2014-05-21 01:08:45 +00:00
|
|
|
|
2014-05-21 01:08:57 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2014-05-21 01:08:45 +00:00
|
|
|
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");
|
|
|
|
}
|
2014-05-21 01:08:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void unlink_peers(struct usb_port *left, struct usb_port *right)
|
|
|
|
{
|
2014-05-21 01:08:57 +00:00
|
|
|
struct usb_port *ss_port, *hs_port;
|
|
|
|
|
2014-05-21 01:08:28 +00:00
|
|
|
WARN(right->peer != left || left->peer != right,
|
|
|
|
"%s and %s are not peers?\n",
|
|
|
|
dev_name(&left->dev), dev_name(&right->dev));
|
|
|
|
|
2014-05-21 01:08:57 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
|
2014-05-21 01:08:45 +00:00
|
|
|
sysfs_remove_link(&left->dev.kobj, "peer");
|
2014-05-21 01:08:28 +00:00
|
|
|
right->peer = NULL;
|
2014-05-21 01:08:45 +00:00
|
|
|
sysfs_remove_link(&right->dev.kobj, "peer");
|
2014-05-21 01:08:28 +00:00
|
|
|
left->peer = NULL;
|
2014-05-21 01:08:57 +00:00
|
|
|
|
|
|
|
/* 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);
|
2014-05-21 01:08:28 +00:00
|
|
|
}
|
|
|
|
|
2014-05-21 01:08:33 +00:00
|
|
|
/*
|
2014-05-21 01:08:40 +00:00
|
|
|
* 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) {
|
2014-05-21 01:08:45 +00:00
|
|
|
link_peers_report(port_dev, peer);
|
2014-05-21 01:08:40 +00:00
|
|
|
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.
|
2014-05-21 01:08:33 +00:00
|
|
|
*/
|
2014-05-21 01:08:28 +00:00
|
|
|
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;
|
2014-05-21 01:08:33 +00:00
|
|
|
struct usb_device *peer_hdev;
|
|
|
|
struct usb_hub *peer_hub;
|
2014-05-21 01:08:28 +00:00
|
|
|
|
2014-05-21 01:08:40 +00:00
|
|
|
/*
|
|
|
|
* 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) {
|
2014-05-21 01:08:28 +00:00
|
|
|
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;
|
2014-05-21 01:08:33 +00:00
|
|
|
} 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)
|
2014-05-21 01:08:28 +00:00
|
|
|
return;
|
|
|
|
|
2014-05-21 01:08:33 +00:00
|
|
|
upstream = parent_hub->ports[hdev->portnum - 1];
|
|
|
|
if (!upstream || !upstream->peer)
|
|
|
|
return;
|
2014-05-21 01:08:28 +00:00
|
|
|
|
2014-05-21 01:08:33 +00:00
|
|
|
peer_hdev = upstream->peer->child;
|
2014-05-21 01:08:28 +00:00
|
|
|
}
|
2014-05-21 01:08:33 +00:00
|
|
|
|
|
|
|
peer_hub = usb_hub_to_struct_hub(peer_hdev);
|
|
|
|
if (!peer_hub || port1 > peer_hdev->maxchild)
|
|
|
|
return;
|
|
|
|
|
2014-05-21 01:08:40 +00:00
|
|
|
/*
|
|
|
|
* we found a valid default peer, last check is to make sure it
|
|
|
|
* does not have location data
|
|
|
|
*/
|
2014-05-21 01:08:33 +00:00
|
|
|
peer = peer_hub->ports[port1 - 1];
|
2014-05-21 01:08:40 +00:00
|
|
|
if (peer && peer->location == 0)
|
2014-05-21 01:08:45 +00:00
|
|
|
link_peers_report(port_dev, peer);
|
2014-05-21 01:08:28 +00:00
|
|
|
}
|
|
|
|
|
2013-01-11 12:10:38 +00:00
|
|
|
int usb_hub_create_port_device(struct usb_hub *hub, int port1)
|
|
|
|
{
|
2014-05-21 01:08:28 +00:00
|
|
|
struct usb_port *port_dev;
|
2013-01-11 12:10:38 +00:00
|
|
|
int retval;
|
|
|
|
|
|
|
|
port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
|
|
|
|
if (!port_dev) {
|
|
|
|
retval = -ENOMEM;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
hub->ports[port1 - 1] = port_dev;
|
2013-01-22 20:26:29 +00:00
|
|
|
port_dev->portnum = port1;
|
2014-05-21 01:08:52 +00:00
|
|
|
set_bit(port1, hub->power_bits);
|
2013-01-11 12:10:38 +00:00
|
|
|
port_dev->dev.parent = hub->intfdev;
|
2013-01-19 17:53:32 +00:00
|
|
|
port_dev->dev.groups = port_dev_group;
|
2013-01-11 12:10:38 +00:00
|
|
|
port_dev->dev.type = &usb_port_device_type;
|
2014-05-21 01:08:17 +00:00
|
|
|
port_dev->dev.driver = &usb_port_driver;
|
2014-05-21 01:08:57 +00:00
|
|
|
if (hub_is_superspeed(hub->hdev))
|
|
|
|
port_dev->is_superspeed = 1;
|
2014-05-21 01:08:17 +00:00
|
|
|
dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev),
|
|
|
|
port1);
|
2013-01-11 12:10:38 +00:00
|
|
|
retval = device_register(&port_dev->dev);
|
|
|
|
if (retval)
|
|
|
|
goto error_register;
|
|
|
|
|
2014-05-21 01:08:28 +00:00
|
|
|
find_and_link_peer(hub, port1);
|
|
|
|
|
2013-01-22 20:26:29 +00:00
|
|
|
pm_runtime_set_active(&port_dev->dev);
|
2013-01-22 20:26:31 +00:00
|
|
|
|
2014-05-21 01:08:12 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
2013-01-22 20:26:31 +00:00
|
|
|
*/
|
2014-05-21 01:08:12 +00:00
|
|
|
if (hub_is_port_power_switchable(hub)
|
|
|
|
&& dev_pm_qos_expose_flags(&port_dev->dev,
|
|
|
|
PM_QOS_FLAG_NO_POWER_OFF) == 0)
|
2013-01-22 20:26:31 +00:00
|
|
|
pm_runtime_enable(&port_dev->dev);
|
|
|
|
|
2013-01-22 20:26:32 +00:00
|
|
|
device_enable_async_suspend(&port_dev->dev);
|
2013-01-11 12:10:38 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_register:
|
|
|
|
put_device(&port_dev->dev);
|
|
|
|
exit:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2014-05-21 01:08:28 +00:00
|
|
|
void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
|
2013-01-11 12:10:38 +00:00
|
|
|
{
|
2014-05-21 01:08:28 +00:00
|
|
|
struct usb_port *port_dev = hub->ports[port1 - 1];
|
|
|
|
struct usb_port *peer;
|
2013-01-11 12:10:38 +00:00
|
|
|
|
2014-05-21 01:08:28 +00:00
|
|
|
peer = port_dev->peer;
|
|
|
|
if (peer)
|
|
|
|
unlink_peers(port_dev, peer);
|
|
|
|
device_unregister(&port_dev->dev);
|
|
|
|
}
|