...because struct dev_pm_ops is too clean and uniform.

USB port power management needs to manage ports that are colocated in a
given connector.  If we power down a connector we need to arrange for
the peer port in the connector to also be suspended.  If the peer
connector stays active it may cause the device to disconnect on the initial
(suspended) port and reconnect on the other.

Notification of when userspace disables pm_qos_no_power_off allows the
driver to arrange for the peer port to be suspended before the primary
port suspends.

Alternatives considered:
1/ force power down the peer port: denies userspace control over power
   policy

2/ try to synchronize port power management within the USB core:
   proved difficult to find a non-racy way to synchronize peer
   connections against power management changes.

3/ create infrastructure to expose the concept of peer devices in the
   pm_runtime system... maybe defer until other use cases outside of USB pop
   up.  For now, punting the notification to the driver is sufficient.

As stated this notification allows the kernel to keep power-policy
consistent across a connector.  If userspace wants to regain per-port
control (at the risk of unexpected disconnects) it can set the
pm_qos_no_notify flag to prevent ports from coordinating as a connector.
pm_qos_no_notify blocks flags synchronization, restoring the traditional
behavior of one pm_qos_no_power_off manipulation only affecting one
device.

Cc: Rafael J. Wysocki <r...@sisk.pl>
Cc: Greg Kroah-Hartman <gre...@linuxfoundation.org>
Cc: Alan Stern <st...@rowland.harvard.edu>
Cc: Sarah Sharp <sarah.a.sh...@linux.intel.com>
Signed-off-by: Dan Williams <dan.j.willi...@intel.com>
---
 Documentation/ABI/testing/sysfs-devices-power |   21 +++++++++++
 Documentation/power/pm_qos_interface.txt      |   15 ++++----
 drivers/base/power/qos.c                      |   48 ++++++++++++++++++++++---
 drivers/base/power/sysfs.c                    |   45 ++++++++++++++++-------
 drivers/usb/core/port.c                       |    7 ++++
 drivers/usb/core/usb-platform.c               |   38 ++++++++++++++++++++
 drivers/usb/core/usb-platform.h               |    1 +
 include/linux/pm.h                            |    5 +++
 include/linux/pm_qos.h                        |    5 +++
 9 files changed, 159 insertions(+), 26 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-devices-power 
b/Documentation/ABI/testing/sysfs-devices-power
index efe449bdf811..36d1603ab3bb 100644
--- a/Documentation/ABI/testing/sysfs-devices-power
+++ b/Documentation/ABI/testing/sysfs-devices-power
@@ -235,3 +235,24 @@ Description:
 
                This attribute has no effect on system-wide suspend/resume and
                hibernation.
+
+What:          /sys/devices/.../power/pm_qos_no_notify_flags
+Date:          October 2013
+Contact:       Dan Williams <dan.j.willi...@intel.com>
+Description:
+               The /sys/devices/.../power/pm_qos_no_notify_flags attribute is
+               used for determining whether the kernel is permitted to forward
+               changes to the the PM QoS flags (no_power_off, remote_wakeup) to
+               other devices it deems to be related in the system.  When this
+               flag is set userspace is indicating that it wants exclusive
+               control of the flags and takes responsibility for situations
+               where a driver would want the power control setting unified
+               amongst a set of devices.  In the case of USB a relationship is
+               indicating by a "peer:<hub -deviceB>-port<num>" link in the
+               <hub-deviceA>/port<num> directory.
+
+               Not all drivers support this attribute.  If it isn't supported,
+               it is not present.
+
+               This attribute has no effect on system-wide suspend/resume and
+               hibernation.
diff --git a/Documentation/power/pm_qos_interface.txt 
b/Documentation/power/pm_qos_interface.txt
index 483632087788..a63ad55dbf6f 100644
--- a/Documentation/power/pm_qos_interface.txt
+++ b/Documentation/power/pm_qos_interface.txt
@@ -94,8 +94,8 @@ flags. Values are updated in response to changes of the 
request list.
 
 Target latency value is simply the minimum of the request values held in the
 parameter list elements.  The PM QoS flags aggregate value is a gather (bitwise
-OR) of all list elements' values. Two device PM QoS flags are defined 
currently:
-PM_QOS_FLAG_NO_POWER_OFF and PM_QOS_FLAG_REMOTE_WAKEUP.
+OR) of all list elements' values. Three device PM QoS flags are defined 
currently:
+PM_QOS_FLAG_NO_POWER_OFF, PM_QOS_FLAG_REMOTE_WAKEUP, PM_QOS_FLAG_NO_NOTIFY.
 
 Note: the aggregated target value is implemented as an atomic variable so that
 reading the aggregated value does not require any locking mechanism.
@@ -148,13 +148,14 @@ from the device's power directory.
 
 int dev_pm_qos_expose_flags(device, value)
 Add a request to the device's PM QoS list of flags and create sysfs attributes
-pm_qos_no_power_off and pm_qos_remote_wakeup under the device's power directory
-allowing user space to change these flags' value.
+pm_qos_no_power_off, pm_qos_remote_wakeup, and pm_qos_no_notify_flags under the
+device's power directory allowing user space to change these flags' value.
 
 void dev_pm_qos_hide_flags(device)
-Drop the request added by dev_pm_qos_expose_flags() from the device's PM QoS 
list
-of flags and remove sysfs attributes pm_qos_no_power_off and 
pm_qos_remote_wakeup
-under the device's power directory.
+Drop the request added by dev_pm_qos_expose_flags() from the device's PM QoS
+list of flags and remove sysfs attributes pm_qos_no_power_off,
+pm_qos_remote_wakeup, and pm_qos_no_notify_flags under the device's power
+directory.
 
 Notification mechanisms:
 The per-device PM QoS framework has 2 different and distinct notification 
trees:
diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c
index 5c1361a9e5dd..b8ec64e8ee66 100644
--- a/drivers/base/power/qos.c
+++ b/drivers/base/power/qos.c
@@ -737,12 +737,12 @@ void dev_pm_qos_hide_flags(struct device *dev)
 EXPORT_SYMBOL_GPL(dev_pm_qos_hide_flags);
 
 /**
- * dev_pm_qos_update_flags - Update PM QoS flags request owned by user space.
- * @dev: Device to update the PM QoS flags request for.
- * @mask: Flags to set/clear.
- * @set: Whether to set or clear the flags (true means set).
+ * dev_pm_qos_update_flags_nonotify - update flags without notifying a driver
+ *
+ * This routine is only meant to be used by drivers that are forwarding
+ * userspace flag change requests between known peers
  */
-int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set)
+int dev_pm_qos_update_flags_nonotify(struct device *dev, s32 mask, bool set)
 {
        s32 value;
        int ret;
@@ -768,6 +768,44 @@ int dev_pm_qos_update_flags(struct device *dev, s32 mask, 
bool set)
        pm_runtime_put(dev);
        return ret;
 }
+EXPORT_SYMBOL_GPL(dev_pm_qos_update_flags_nonotify);
+
+/**
+ * dev_pm_qos_update_flags - Update PM QoS flags request owned by user space.
+ * @dev: Device to update the PM QoS flags request for.
+ * @mask: Flags to set/clear.
+ * @set: Whether to set or clear the flags (true means set).
+ */
+int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set)
+{
+       bool (*callback)(struct device *, s32, bool);
+       enum pm_qos_flags_status stat;
+       bool done = false;
+
+       if (dev->pm_domain)
+               callback = dev->pm_domain->ops.notify_flags;
+       else if (dev->type && dev->type->pm)
+               callback = dev->type->pm->notify_flags;
+       else if (dev->class && dev->class->pm)
+               callback = dev->class->pm->notify_flags;
+       else if (dev->bus && dev->bus->pm)
+               callback = dev->bus->pm->notify_flags;
+       else
+               callback = NULL;
+
+       if (!callback && dev->driver && dev->driver->pm)
+               callback = dev->driver->pm->notify_flags;
+
+       stat = dev_pm_qos_flags(dev, PM_QOS_FLAG_NO_NOTIFY);
+       if (callback && stat == PM_QOS_FLAGS_NONE)
+               done = callback(dev, mask, set);
+
+       /* callback consumed the event */
+       if (done)
+               return 0;
+
+       return dev_pm_qos_update_flags_nonotify(dev, mask, set);
+}
 #else /* !CONFIG_PM_RUNTIME */
 static void __dev_pm_qos_hide_latency_limit(struct device *dev) {}
 static void __dev_pm_qos_hide_flags(struct device *dev) {}
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c
index 03e089ade5ce..a1e4be468b7f 100644
--- a/drivers/base/power/sysfs.c
+++ b/drivers/base/power/sysfs.c
@@ -252,9 +252,7 @@ static ssize_t pm_qos_no_power_off_show(struct device *dev,
                                        & PM_QOS_FLAG_NO_POWER_OFF));
 }
 
-static ssize_t pm_qos_no_power_off_store(struct device *dev,
-                                        struct device_attribute *attr,
-                                        const char *buf, size_t n)
+static ssize_t flags_store(struct device *dev, s32 mask, const char *buf, 
size_t n)
 {
        int ret;
 
@@ -264,13 +262,40 @@ static ssize_t pm_qos_no_power_off_store(struct device 
*dev,
        if (ret != 0 && ret != 1)
                return -EINVAL;
 
-       ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_NO_POWER_OFF, ret);
+       ret = dev_pm_qos_update_flags(dev, mask, ret);
        return ret < 0 ? ret : n;
 }
 
+static ssize_t pm_qos_no_power_off_store(struct device *dev,
+                                        struct device_attribute *attr,
+                                        const char *buf, size_t n)
+{
+       return flags_store(dev, PM_QOS_FLAG_NO_POWER_OFF, buf, n);
+}
+
 static DEVICE_ATTR(pm_qos_no_power_off, 0644,
                   pm_qos_no_power_off_show, pm_qos_no_power_off_store);
 
+static ssize_t pm_qos_no_notify_flags_show(struct device *dev,
+                                          struct device_attribute *attr,
+                                          char *buf)
+{
+       return sprintf(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev)
+                                       & PM_QOS_FLAG_NO_POWER_OFF));
+}
+
+
+static ssize_t pm_qos_no_notify_flags_store(struct device *dev,
+                                           struct device_attribute *attr,
+                                           const char *buf, size_t n)
+{
+       return flags_store(dev, PM_QOS_FLAG_NO_NOTIFY, buf, n);
+}
+
+static DEVICE_ATTR(pm_qos_no_notify_flags, 0644,
+                  pm_qos_no_notify_flags_show, pm_qos_no_notify_flags_store);
+
+
 static ssize_t pm_qos_remote_wakeup_show(struct device *dev,
                                         struct device_attribute *attr,
                                         char *buf)
@@ -283,16 +308,7 @@ static ssize_t pm_qos_remote_wakeup_store(struct device 
*dev,
                                          struct device_attribute *attr,
                                          const char *buf, size_t n)
 {
-       int ret;
-
-       if (kstrtoint(buf, 0, &ret))
-               return -EINVAL;
-
-       if (ret != 0 && ret != 1)
-               return -EINVAL;
-
-       ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP, ret);
-       return ret < 0 ? ret : n;
+       return flags_store(dev, PM_QOS_FLAG_REMOTE_WAKEUP, buf, n);
 }
 
 static DEVICE_ATTR(pm_qos_remote_wakeup, 0644,
@@ -633,6 +649,7 @@ static struct attribute *pm_qos_flags_attrs[] = {
 #ifdef CONFIG_PM_RUNTIME
        &dev_attr_pm_qos_no_power_off.attr,
        &dev_attr_pm_qos_remote_wakeup.attr,
+       &dev_attr_pm_qos_no_notify_flags.attr,
 #endif /* CONFIG_PM_RUNTIME */
        NULL,
 };
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 51542f852393..237b91bb2079 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -20,6 +20,7 @@
 #include <linux/pm_qos.h>
 
 #include "hub.h"
+#include "usb-platform.h"
 
 static const struct attribute_group *port_dev_group[];
 
@@ -131,12 +132,18 @@ static int usb_port_runtime_suspend(struct device *dev)
        usb_autopm_put_interface(intf);
        return retval;
 }
+
+static bool usb_port_notify_flags(struct device *dev, s32 mask, bool set)
+{
+       return usb_connector_notify_flags(to_usb_port(dev), mask, set);
+}
 #endif
 
 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,
+       .notify_flags =         usb_port_notify_flags,
 #endif
 };
 
diff --git a/drivers/usb/core/usb-platform.c b/drivers/usb/core/usb-platform.c
index 6b39d1fde8e4..8bec79131bb1 100644
--- a/drivers/usb/core/usb-platform.c
+++ b/drivers/usb/core/usb-platform.c
@@ -8,6 +8,7 @@
  * Software Foundation, version 2.
  *
  */
+#include <linux/pm_qos.h>
 #include <linux/device.h>
 #include "usb-platform.h"
 
@@ -53,6 +54,43 @@ struct usb_domain *usb_create_domain(void)
 }
 EXPORT_SYMBOL_GPL(usb_create_domain);
 
+bool usb_connector_notify_flags(struct usb_port *port_dev, s32 mask, bool set)
+{
+       LIST_HEAD(tmp);
+       struct usb_port *p;
+       struct usb_domain *udom;
+       struct usb_connector *uconn = port_dev->connector;
+
+       if (!uconn || mask != PM_QOS_FLAG_NO_POWER_OFF)
+               return false;
+
+       udom = uconn->domain;
+       mutex_lock(&udom->lock);
+
+       /* resume the connector to set the flags */
+       list_for_each_entry(port_dev, &uconn->ports, node) {
+               pm_runtime_get_sync(&port_dev->dev);
+               dev_pm_qos_update_flags_nonotify(&port_dev->dev, mask, set);
+       }
+
+       /* order the resulting suspensions disconnected ports first */
+       list_for_each_entry_safe(port_dev, p, &uconn->ports, node) {
+               if (port_dev->child)
+                       list_move(&port_dev->node, &tmp);
+               else
+                       pm_runtime_put(&port_dev->dev);
+       }
+
+       list_for_each_entry_safe(port_dev, p, &tmp, node) {
+               list_move(&port_dev->node, &uconn->ports);
+               pm_runtime_put(&port_dev->dev);
+       }
+       mutex_unlock(&udom->lock);
+
+       /* notify pm core that we completed the request ourself */
+       return true;
+}
+
 static struct usb_connector *create_connector(struct usb_domain *udom,
                                              struct usb_port *port_dev,
                                              size_t pair_data)
diff --git a/drivers/usb/core/usb-platform.h b/drivers/usb/core/usb-platform.h
index 768bc1d4c831..1938479dc7ad 100644
--- a/drivers/usb/core/usb-platform.h
+++ b/drivers/usb/core/usb-platform.h
@@ -42,3 +42,4 @@ struct usb_connector {
 int usb_domain_pair_port(struct usb_domain *udom, struct usb_port *port_dev,
                         void *data, size_t size);
 void usb_domain_remove_port(struct usb_domain *udom, struct usb_port 
*port_dev);
+bool usb_connector_notify_flags(struct usb_port *port_dev, s32 mask, bool set);
diff --git a/include/linux/pm.h b/include/linux/pm.h
index a224c7f5c377..12e2d5b549fd 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -268,6 +268,10 @@ typedef struct pm_message {
  *     these conditions and handle the device as appropriate, possibly queueing
  *     a suspend request for it.  The return value is ignored by the PM core.
  *
+ * @notify_flags: Device wants to know when the flags are being changed
+ *     as it may have implications on a peer device.  For examle USB
+ *     peer port power management.
+ *
  * Refer to Documentation/power/runtime_pm.txt for more information about the
  * role of the above callbacks in device runtime power management.
  *
@@ -297,6 +301,7 @@ struct dev_pm_ops {
        int (*runtime_suspend)(struct device *dev);
        int (*runtime_resume)(struct device *dev);
        int (*runtime_idle)(struct device *dev);
+       bool (*notify_flags)(struct device *dev, s32 mask, bool set);
 };
 
 #ifdef CONFIG_PM_SLEEP
diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h
index 5a95013905c8..c4bdf5d21a6b 100644
--- a/include/linux/pm_qos.h
+++ b/include/linux/pm_qos.h
@@ -36,6 +36,7 @@ enum pm_qos_flags_status {
 
 #define PM_QOS_FLAG_NO_POWER_OFF       (1 << 0)
 #define PM_QOS_FLAG_REMOTE_WAKEUP      (1 << 1)
+#define PM_QOS_FLAG_NO_NOTIFY          (1 << 2)
 
 struct pm_qos_request {
        struct plist_node node;
@@ -205,6 +206,7 @@ static inline s32 dev_pm_qos_requested_flags(struct device 
*dev)
 {
        return dev->power.qos->flags_req->data.flr.flags;
 }
+int dev_pm_qos_update_flags_nonotify(struct device *dev, s32 mask, bool set);
 #else
 static inline int dev_pm_qos_expose_latency_limit(struct device *dev, s32 
value)
                        { return 0; }
@@ -217,6 +219,9 @@ static inline int dev_pm_qos_update_flags(struct device 
*dev, s32 m, bool set)
 
 static inline s32 dev_pm_qos_requested_latency(struct device *dev) { return 0; 
}
 static inline s32 dev_pm_qos_requested_flags(struct device *dev) { return 0; }
+static inline int dev_pm_qos_update_flags_nonotify(struct device *dev, s32 
mask,
+                                                  bool set)
+                       { return 0; }
 #endif
 
 #endif

--
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