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