summaryrefslogtreecommitdiff
path: root/drivers/usb/core/port.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/port.c')
-rw-r--r--drivers/usb/core/port.c130
1 files changed, 105 insertions, 25 deletions
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 06a8f1f84f6f..f54198171b6a 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -9,8 +9,11 @@
#include <linux/kstrtox.h>
#include <linux/slab.h>
+#include <linux/string_choices.h>
+#include <linux/sysfs.h>
#include <linux/pm_qos.h>
#include <linux/component.h>
+#include <linux/usb/of.h>
#include "hub.h"
@@ -23,7 +26,7 @@ static ssize_t early_stop_show(struct device *dev,
{
struct usb_port *port_dev = to_usb_port(dev);
- return sysfs_emit(buf, "%s\n", port_dev->early_stop ? "yes" : "no");
+ return sysfs_emit(buf, "%s\n", str_yes_no(port_dev->early_stop));
}
static ssize_t early_stop_store(struct device *dev, struct device_attribute *attr,
@@ -50,16 +53,29 @@ static ssize_t disable_show(struct device *dev,
struct usb_port *port_dev = to_usb_port(dev);
struct usb_device *hdev = to_usb_device(dev->parent->parent);
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
- struct usb_interface *intf = to_usb_interface(hub->intfdev);
+ struct usb_interface *intf = to_usb_interface(dev->parent);
int port1 = port_dev->portnum;
u16 portstatus, unused;
bool disabled;
int rc;
+ struct kernfs_node *kn;
+ if (!hub)
+ return -ENODEV;
+ hub_get(hub);
rc = usb_autopm_get_interface(intf);
if (rc < 0)
- return rc;
+ goto out_hub_get;
+ /*
+ * Prevent deadlock if another process is concurrently
+ * trying to unregister hdev.
+ */
+ kn = sysfs_break_active_protection(&dev->kobj, &attr->attr);
+ if (!kn) {
+ rc = -ENODEV;
+ goto out_autopm;
+ }
usb_lock_device(hdev);
if (hub->disconnected) {
rc = -ENODEV;
@@ -69,9 +85,13 @@ static ssize_t disable_show(struct device *dev,
usb_hub_port_status(hub, port1, &portstatus, &unused);
disabled = !usb_port_is_power_on(hub, portstatus);
-out_hdev_lock:
+ out_hdev_lock:
usb_unlock_device(hdev);
+ sysfs_unbreak_active_protection(kn);
+ out_autopm:
usb_autopm_put_interface(intf);
+ out_hub_get:
+ hub_put(hub);
if (rc)
return rc;
@@ -85,19 +105,32 @@ static ssize_t disable_store(struct device *dev, struct device_attribute *attr,
struct usb_port *port_dev = to_usb_port(dev);
struct usb_device *hdev = to_usb_device(dev->parent->parent);
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
- struct usb_interface *intf = to_usb_interface(hub->intfdev);
+ struct usb_interface *intf = to_usb_interface(dev->parent);
int port1 = port_dev->portnum;
bool disabled;
int rc;
+ struct kernfs_node *kn;
+ if (!hub)
+ return -ENODEV;
rc = kstrtobool(buf, &disabled);
if (rc)
return rc;
+ hub_get(hub);
rc = usb_autopm_get_interface(intf);
if (rc < 0)
- return rc;
+ goto out_hub_get;
+ /*
+ * Prevent deadlock if another process is concurrently
+ * trying to unregister hdev.
+ */
+ kn = sysfs_break_active_protection(&dev->kobj, &attr->attr);
+ if (!kn) {
+ rc = -ENODEV;
+ goto out_autopm;
+ }
usb_lock_device(hdev);
if (hub->disconnected) {
rc = -ENODEV;
@@ -118,9 +151,13 @@ static ssize_t disable_store(struct device *dev, struct device_attribute *attr,
if (!rc)
rc = count;
-out_hdev_lock:
+ out_hdev_lock:
usb_unlock_device(hdev);
+ sysfs_unbreak_active_protection(kn);
+ out_autopm:
usb_autopm_put_interface(intf);
+ out_hub_get:
+ hub_put(hub);
return rc;
}
@@ -131,7 +168,7 @@ static ssize_t location_show(struct device *dev,
{
struct usb_port *port_dev = to_usb_port(dev);
- return sprintf(buf, "0x%08x\n", port_dev->location);
+ return sysfs_emit(buf, "0x%08x\n", port_dev->location);
}
static DEVICE_ATTR_RO(location);
@@ -156,16 +193,26 @@ static ssize_t connect_type_show(struct device *dev,
break;
}
- return sprintf(buf, "%s\n", result);
+ return sysfs_emit(buf, "%s\n", result);
}
static DEVICE_ATTR_RO(connect_type);
+static ssize_t state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_port *port_dev = to_usb_port(dev);
+ enum usb_device_state state = READ_ONCE(port_dev->state);
+
+ return sysfs_emit(buf, "%s\n", usb_state_string(state));
+}
+static DEVICE_ATTR_RO(state);
+
static ssize_t over_current_count_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct usb_port *port_dev = to_usb_port(dev);
- return sprintf(buf, "%u\n", port_dev->over_current_count);
+ return sysfs_emit(buf, "%u\n", port_dev->over_current_count);
}
static DEVICE_ATTR_RO(over_current_count);
@@ -174,7 +221,7 @@ static ssize_t quirks_show(struct device *dev,
{
struct usb_port *port_dev = to_usb_port(dev);
- return sprintf(buf, "%08x\n", port_dev->quirks);
+ return sysfs_emit(buf, "%08x\n", port_dev->quirks);
}
static ssize_t quirks_store(struct device *dev, struct device_attribute *attr,
@@ -209,7 +256,7 @@ static ssize_t usb3_lpm_permit_show(struct device *dev,
p = "0";
}
- return sprintf(buf, "%s\n", p);
+ return sysfs_emit(buf, "%s\n", p);
}
static ssize_t usb3_lpm_permit_store(struct device *dev,
@@ -259,6 +306,7 @@ static DEVICE_ATTR_RW(usb3_lpm_permit);
static struct attribute *port_dev_attrs[] = {
&dev_attr_connect_type.attr,
+ &dev_attr_state.attr,
&dev_attr_location.attr,
&dev_attr_quirks.attr,
&dev_attr_over_current_count.attr,
@@ -406,9 +454,12 @@ static int usb_port_runtime_suspend(struct device *dev)
static void usb_port_shutdown(struct device *dev)
{
struct usb_port *port_dev = to_usb_port(dev);
+ struct usb_device *udev = port_dev->child;
- if (port_dev->child)
- usb_disable_usb2_hardware_lpm(port_dev->child);
+ if (udev && !udev->port_is_suspended) {
+ usb_disable_usb2_hardware_lpm(udev);
+ usb_unlocked_disable_lpm(udev);
+ }
}
static const struct dev_pm_ops usb_port_pm_ops = {
@@ -418,7 +469,7 @@ static const struct dev_pm_ops usb_port_pm_ops = {
#endif
};
-struct device_type usb_port_device_type = {
+const struct device_type usb_port_device_type = {
.name = "usb_port",
.release = usb_port_device_release,
.pm = &usb_port_pm_ops,
@@ -562,7 +613,7 @@ static int match_location(struct usb_device *peer_hdev, void *p)
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)
+ if (!peer_hub || port_dev->connect_type == USB_PORT_NOT_USED)
return 0;
hcd = bus_to_hcd(hdev->bus);
@@ -573,7 +624,8 @@ static int match_location(struct usb_device *peer_hdev, void *p)
for (port1 = 1; port1 <= peer_hdev->maxchild; port1++) {
peer = peer_hub->ports[port1 - 1];
- if (peer && peer->location == port_dev->location) {
+ if (peer && peer->connect_type != USB_PORT_NOT_USED &&
+ peer->location == port_dev->location) {
link_peers_report(port_dev, peer);
return 1; /* done */
}
@@ -642,6 +694,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
static int connector_bind(struct device *dev, struct device *connector, void *data)
{
+ struct usb_port *port_dev = to_usb_port(dev);
int ret;
ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector");
@@ -649,16 +702,30 @@ static int connector_bind(struct device *dev, struct device *connector, void *da
return ret;
ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev));
- if (ret)
+ if (ret) {
sysfs_remove_link(&dev->kobj, "connector");
+ return ret;
+ }
+
+ port_dev->connector = data;
- return ret;
+ /*
+ * If there is already USB device connected to the port, letting the
+ * Type-C connector know about it immediately.
+ */
+ if (port_dev->child)
+ typec_attach(port_dev->connector, &port_dev->child->dev);
+
+ return 0;
}
static void connector_unbind(struct device *dev, struct device *connector, void *data)
{
+ struct usb_port *port_dev = to_usb_port(dev);
+
sysfs_remove_link(&connector->kobj, dev_name(dev));
sysfs_remove_link(&dev->kobj, "connector");
+ port_dev->connector = NULL;
}
static const struct component_ops connector_ops = {
@@ -682,11 +749,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
return -ENOMEM;
}
+ port_dev->connect_type = usb_of_get_connect_type(hdev, port1);
hub->ports[port1 - 1] = port_dev;
port_dev->portnum = port1;
set_bit(port1, hub->power_bits);
port_dev->dev.parent = hub->intfdev;
if (hub_is_superspeed(hdev)) {
+ port_dev->is_superspeed = 1;
port_dev->usb3_lpm_u1_permit = 1;
port_dev->usb3_lpm_u2_permit = 1;
port_dev->dev.groups = port_dev_usb3_group;
@@ -694,8 +763,6 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
port_dev->dev.groups = port_dev_group;
port_dev->dev.type = &usb_port_device_type;
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);
@@ -705,19 +772,24 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
return retval;
}
+ port_dev->state_kn = sysfs_get_dirent(port_dev->dev.kobj.sd, "state");
+ if (!port_dev->state_kn) {
+ dev_err(&port_dev->dev, "failed to sysfs_get_dirent 'state'\n");
+ retval = -ENODEV;
+ goto err_unregister;
+ }
+
/* Set default policy of port-poweroff disabled. */
retval = dev_pm_qos_add_request(&port_dev->dev, port_dev->req,
DEV_PM_QOS_FLAGS, PM_QOS_FLAG_NO_POWER_OFF);
if (retval < 0) {
- device_unregister(&port_dev->dev);
- return retval;
+ goto err_put_kn;
}
retval = component_add(&port_dev->dev, &connector_ops);
if (retval) {
dev_warn(&port_dev->dev, "failed to add component\n");
- device_unregister(&port_dev->dev);
- return retval;
+ goto err_put_kn;
}
find_and_link_peer(hub, port1);
@@ -754,6 +826,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
port_dev->req = NULL;
}
return 0;
+
+err_put_kn:
+ sysfs_put(port_dev->state_kn);
+err_unregister:
+ device_unregister(&port_dev->dev);
+
+ return retval;
}
void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
@@ -765,5 +844,6 @@ void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
if (peer)
unlink_peers(port_dev, peer);
component_del(&port_dev->dev, &connector_ops);
+ sysfs_put(port_dev->state_kn);
device_unregister(&port_dev->dev);
}