[RFC PATCH 05/15] usbcore: port power policy / prep for usb_port as usb_device parent

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.williams@xxxxxxxxx>
---
 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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux