To simplify and harden port power management the port needs to be a
proper parent of a connected child, and the hub needs to check whether a
port has power before performing actions on it.

However, we also want the capability to suspend hubs and connected
devices while port power is enabled.  So, before moving a port_dev into the
hdev->udev hierarchy implement the following port power policies:

  power policy 'on':
    port is forced on, port suspend is a nop allowing the hub to suspend

  power policy 'off':
    manage port power on idle, hub suspend will be blocked until all
    power policy off ports are suspended.

Otherwise changing the device model hierarchy will result in a
regression of not suspending hubs while port power is enabled.

Summary of the policy change (when combined with a device model change):

Events:               | Proposed pm lifetime: | Current:
                      | Power: State:  Count: | Power: State:  Count:
pm_qos_no_power_off=1 | on     suspend 0      | on     active  0
device connect        | on     active  child  | on     active  1 (explicit get)
pm_qos_no_power_off=0 | on     active  child  | on     active  1
device sleep          | off    suspend 0      | off    suspend 0 (explicit put)
device wake           | on     active  child  | on     active  1 (explicit get)
device disconnect     | off    suspend 0      | off    suspend 0 (explicit put)


Proposed hotplug policy (enable/disable hotplug in another patch)
pm_qos_no_power_off=1 | on     suspend 0
enable hotplug        | on     suspend 0
device connect        | on     active  child
pm_qos_no_power_off=0 | on     active  child
device sleep          | on     suspend 0
device wake           | on     active  child
device disconnect     | on     suspend 0
disable hotplug       | off    suspend 0

Signed-off-by: Dan Williams <dan.j.willi...@intel.com>
---
 drivers/usb/core/port.c |   73 ++++++++++++++++++++++++++++-------------------
 1 files changed, 43 insertions(+), 30 deletions(-)

diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 237b91bb2079..259ed86f56d2 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -71,7 +71,20 @@ static void usb_port_device_release(struct device *dev)
 }
 
 #ifdef CONFIG_PM_RUNTIME
-static int usb_port_runtime_resume(struct device *dev)
+
+static bool is_power_policy_on(struct usb_port *port_dev)
+{
+       int flag = PM_QOS_FLAG_NO_POWER_OFF;
+
+       if (port_dev->connect_type <= USB_PORT_CONNECT_TYPE_HOT_PLUG)
+               return true;
+       if (dev_pm_qos_flags(&port_dev->dev, flag) == PM_QOS_FLAGS_ALL)
+               return true;
+
+       return false;
+}
+
+static int usb_port_runtime_poweron(struct device *dev)
 {
        struct usb_port *port_dev = to_usb_port(dev);
        struct usb_device *hdev = to_usb_device(dev->parent->parent);
@@ -83,6 +96,14 @@ static int usb_port_runtime_resume(struct device *dev)
        if (!hub)
                return -EINVAL;
 
+       /* if the policy is 'on' then we did not disable port power on
+        * the suspend path. when turning the policy to 'off' we are
+        * guaranteed to be resumed under the 'on' policy before being
+        * requested to resume a powered down port
+        */
+       if (is_power_policy_on(port_dev))
+               return 0;
+
        usb_autopm_get_interface(intf);
        set_bit(port1, hub->busy_bits);
 
@@ -107,7 +128,7 @@ static int usb_port_runtime_resume(struct device *dev)
        return retval;
 }
 
-static int usb_port_runtime_suspend(struct device *dev)
+static int usb_port_runtime_poweroff(struct device *dev)
 {
        struct usb_port *port_dev = to_usb_port(dev);
        struct usb_device *hdev = to_usb_device(dev->parent->parent);
@@ -119,9 +140,8 @@ static int usb_port_runtime_suspend(struct device *dev)
        if (!hub)
                return -EINVAL;
 
-       if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF)
-                       == PM_QOS_FLAGS_ALL)
-               return -EAGAIN;
+       if (is_power_policy_on(port_dev))
+               return 0;
 
        usb_autopm_get_interface(intf);
        set_bit(port1, hub->busy_bits);
@@ -141,8 +161,8 @@ static bool usb_port_notify_flags(struct device *dev, s32 
mask, bool set)
 
 static const struct dev_pm_ops usb_port_pm_ops = {
 #ifdef CONFIG_PM_RUNTIME
-       .runtime_suspend =      usb_port_runtime_suspend,
-       .runtime_resume =       usb_port_runtime_resume,
+       .runtime_suspend =      usb_port_runtime_poweroff,
+       .runtime_resume =       usb_port_runtime_poweron,
        .notify_flags =         usb_port_notify_flags,
 #endif
 };
@@ -156,13 +176,11 @@ struct device_type usb_port_device_type = {
 int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 {
        struct usb_port *port_dev = NULL;
-       int retval;
+       int retval, flag = PM_QOS_FLAG_NO_POWER_OFF;
 
        port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
-       if (!port_dev) {
-               retval = -ENOMEM;
-               goto exit;
-       }
+       if (!port_dev)
+               return -ENOMEM;
 
        hub->ports[port1 - 1] = port_dev;
        port_dev->portnum = port1;
@@ -173,25 +191,20 @@ int usb_hub_create_port_device(struct usb_hub *hub, int 
port1)
        dev_set_name(&port_dev->dev, "port%d", port1);
 
        retval = device_register(&port_dev->dev);
-       if (retval)
-               goto error_register;
-
-       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.
-        */
-       if (!dev_pm_qos_expose_flags(&port_dev->dev,
-                       PM_QOS_FLAG_NO_POWER_OFF))
-               pm_runtime_enable(&port_dev->dev);
-
-       device_enable_async_suspend(&port_dev->dev);
-       return 0;
+       if (retval) {
+               put_device(&port_dev->dev);
+       } else {
+               device_enable_async_suspend(&port_dev->dev);
+
+               /* Force active if we can't expose the default power policy */
+               if (dev_pm_qos_expose_flags(&port_dev->dev, flag) == 0) {
+                       pm_runtime_set_suspended(&port_dev->dev);
+                       pm_runtime_enable(&port_dev->dev);
+               } else {
+                       pm_runtime_set_active(&port_dev->dev);
+               }
+       }
 
-error_register:
-       put_device(&port_dev->dev);
-exit:
        return retval;
 }
 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to