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 <[email protected]>
---
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 [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html