usb: core: lpm: add sysfs node for usb3 lpm permit
authorLu Baolu <baolu.lu@linux.intel.com>
Sat, 14 Nov 2015 08:26:33 +0000 (16:26 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 1 Dec 2015 22:58:18 +0000 (14:58 -0800)
USB3 LPM is default on in Linux kernel if both xHCI host controller
and the USB devices declare to be LPM-capable. Unfortunately, some
devices are known to work well with LPM disabled, but to be broken
if LPM is enabled, although it declares the LPM capability.  Users
won't be able to use this kind of devices, until someone puts them
in the kernel blacklist and gets the kernel upgraded.

This patch adds a sysfs node to permit or forbit USB3 LPM U1 or U2
entry for a port. The settings apply to both before and after device
enumeration. Supported values are "0" - neither u1 nor u2 permitted,
"u1" - only u1 is permitted, "u2" - only u2 is permitted, "u1_u2" -
both u1 and u2 are permitted. With this interface, users can use an
LPM-unfriendly USB device on a released Linux kernel.

Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Zhuang Jin Can <jin.can.zhuang@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-bus-usb
drivers/usb/core/hub.c
drivers/usb/core/hub.h
drivers/usb/core/port.c

index 136ba17d2da0558ad8ac129bc193e98486df0c0e..0bd731cbb50ccdb1a1aadaa06b1977eb2331900f 100644 (file)
@@ -189,6 +189,17 @@ Description:
                The file will read "hotplug", "wired" and "not used" if the
                information is available, and "unknown" otherwise.
 
+What:          /sys/bus/usb/devices/.../(hub interface)/portX/usb3_lpm_permit
+Date:          November 2015
+Contact:       Lu Baolu <baolu.lu@linux.intel.com>
+Description:
+               Some USB3.0 devices are not friendly to USB3 LPM.  usb3_lpm_permit
+               attribute allows enabling/disabling usb3 lpm of a port. It takes
+               effect both before and after a usb device is enumerated. Supported
+               values are "0" if both u1 and u2 are NOT permitted, "u1" if only u1
+               is permitted, "u2" if only u2 is permitted, "u1_u2" if both u1 and
+               u2 are permitted.
+
 What:          /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout
 Date:          May 2013
 Contact:       Mathias Nyman <mathias.nyman@linux.intel.com>
index 59f998e60030913be123370aa61592ed47f8f44e..db7683e2d34c6201dd79b0df7936a9438d0b22f1 100644 (file)
@@ -4020,6 +4020,8 @@ EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
 void usb_enable_lpm(struct usb_device *udev)
 {
        struct usb_hcd *hcd;
+       struct usb_hub *hub;
+       struct usb_port *port_dev;
 
        if (!udev || !udev->parent ||
                        udev->speed != USB_SPEED_SUPER ||
@@ -4039,8 +4041,17 @@ void usb_enable_lpm(struct usb_device *udev)
        if (udev->lpm_disable_count > 0)
                return;
 
-       usb_enable_link_state(hcd, udev, USB3_LPM_U1);
-       usb_enable_link_state(hcd, udev, USB3_LPM_U2);
+       hub = usb_hub_to_struct_hub(udev->parent);
+       if (!hub)
+               return;
+
+       port_dev = hub->ports[udev->portnum - 1];
+
+       if (port_dev->usb3_lpm_u1_permit)
+               usb_enable_link_state(hcd, udev, USB3_LPM_U1);
+
+       if (port_dev->usb3_lpm_u2_permit)
+               usb_enable_link_state(hcd, udev, USB3_LPM_U2);
 }
 EXPORT_SYMBOL_GPL(usb_enable_lpm);
 
index 688817fb3246cd6509242aac247b0f779273e86b..45d070dd1d0315cfca7b049e3e1d04e6e701645c 100644 (file)
@@ -92,6 +92,8 @@ struct usb_hub {
  * @status_lock: synchronize port_event() vs usb_port_{suspend|resume}
  * @portnum: port index num based one
  * @is_superspeed cache super-speed status
+ * @usb3_lpm_u1_permit: whether USB3 U1 LPM is permitted.
+ * @usb3_lpm_u2_permit: whether USB3 U2 LPM is permitted.
  */
 struct usb_port {
        struct usb_device *child;
@@ -104,6 +106,8 @@ struct usb_port {
        struct mutex status_lock;
        u8 portnum;
        unsigned int is_superspeed:1;
+       unsigned int usb3_lpm_u1_permit:1;
+       unsigned int usb3_lpm_u2_permit:1;
 };
 
 #define to_usb_port(_dev) \
@@ -155,4 +159,3 @@ static inline int hub_port_debounce_be_stable(struct usb_hub *hub,
 {
        return hub_port_debounce(hub, port1, false);
 }
-
index 210618319f10a4110bc53191fc8a06a860992c3a..cb18ce0d917752faa5dc210190950f87b6bd915f 100644 (file)
@@ -50,6 +50,72 @@ static ssize_t connect_type_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(connect_type);
 
+static ssize_t usb3_lpm_permit_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       struct usb_port *port_dev = to_usb_port(dev);
+       const char *p;
+
+       if (port_dev->usb3_lpm_u1_permit) {
+               if (port_dev->usb3_lpm_u2_permit)
+                       p = "u1_u2";
+               else
+                       p = "u1";
+       } else {
+               if (port_dev->usb3_lpm_u2_permit)
+                       p = "u2";
+               else
+                       p = "0";
+       }
+
+       return sprintf(buf, "%s\n", p);
+}
+
+static ssize_t usb3_lpm_permit_store(struct device *dev,
+                              struct device_attribute *attr,
+                              const char *buf, size_t count)
+{
+       struct usb_port *port_dev = to_usb_port(dev);
+       struct usb_device *udev = port_dev->child;
+       struct usb_hcd *hcd;
+
+       if (!strncmp(buf, "u1_u2", 5)) {
+               port_dev->usb3_lpm_u1_permit = 1;
+               port_dev->usb3_lpm_u2_permit = 1;
+
+       } else if (!strncmp(buf, "u1", 2)) {
+               port_dev->usb3_lpm_u1_permit = 1;
+               port_dev->usb3_lpm_u2_permit = 0;
+
+       } else if (!strncmp(buf, "u2", 2)) {
+               port_dev->usb3_lpm_u1_permit = 0;
+               port_dev->usb3_lpm_u2_permit = 1;
+
+       } else if (!strncmp(buf, "0", 1)) {
+               port_dev->usb3_lpm_u1_permit = 0;
+               port_dev->usb3_lpm_u2_permit = 0;
+       } else
+               return -EINVAL;
+
+       /* If device is connected to the port, disable or enable lpm
+        * to make new u1 u2 setting take effect immediately.
+        */
+       if (udev) {
+               hcd = bus_to_hcd(udev->bus);
+               if (!hcd)
+                       return -EINVAL;
+               usb_lock_device(udev);
+               mutex_lock(hcd->bandwidth_mutex);
+               if (!usb_disable_lpm(udev))
+                       usb_enable_lpm(udev);
+               mutex_unlock(hcd->bandwidth_mutex);
+               usb_unlock_device(udev);
+       }
+
+       return count;
+}
+static DEVICE_ATTR_RW(usb3_lpm_permit);
+
 static struct attribute *port_dev_attrs[] = {
        &dev_attr_connect_type.attr,
        NULL,
@@ -64,6 +130,21 @@ static const struct attribute_group *port_dev_group[] = {
        NULL,
 };
 
+static struct attribute *port_dev_usb3_attrs[] = {
+       &dev_attr_usb3_lpm_permit.attr,
+       NULL,
+};
+
+static struct attribute_group port_dev_usb3_attr_grp = {
+       .attrs = port_dev_usb3_attrs,
+};
+
+static const struct attribute_group *port_dev_usb3_group[] = {
+       &port_dev_attr_grp,
+       &port_dev_usb3_attr_grp,
+       NULL,
+};
+
 static void usb_port_device_release(struct device *dev)
 {
        struct usb_port *port_dev = to_usb_port(dev);
@@ -401,6 +482,7 @@ static void find_and_link_peer(struct usb_hub *hub, int port1)
 int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 {
        struct usb_port *port_dev;
+       struct usb_device *hdev = hub->hdev;
        int retval;
 
        port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
@@ -417,7 +499,12 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
        port_dev->portnum = port1;
        set_bit(port1, hub->power_bits);
        port_dev->dev.parent = hub->intfdev;
-       port_dev->dev.groups = port_dev_group;
+       if (hub_is_superspeed(hdev)) {
+               port_dev->usb3_lpm_u1_permit = 1;
+               port_dev->usb3_lpm_u2_permit = 1;
+               port_dev->dev.groups = port_dev_usb3_group;
+       } else
+               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))
This page took 0.037072 seconds and 5 git commands to generate.