From 53823639173cc9e9a261f68f4abefe62364b86c6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:02:51 +0100 Subject: PM / Runtime: Add sysfs switch for disabling device run-time PM Add new device sysfs attribute, power/control, allowing the user space to block the run-time power management of the devices. If this attribute is set to "on", the driver of the device won't be able to power manage it at run time (without breaking the rules) and the device will always be in the full power state (except when the entire system goes into a sleep state). Signed-off-by: Rafael J. Wysocki Acked-by: Alan Stern --- drivers/base/power/runtime.c | 45 ++++++++++++++++++++++++++++++++++++++ drivers/base/power/sysfs.c | 51 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm.h | 1 + include/linux/pm_runtime.h | 4 ++++ 4 files changed, 101 insertions(+) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index f8b044e8aef7..626dd147b75f 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1010,6 +1010,50 @@ void pm_runtime_enable(struct device *dev) } EXPORT_SYMBOL_GPL(pm_runtime_enable); +/** + * pm_runtime_forbid - Block run-time PM of a device. + * @dev: Device to handle. + * + * Increase the device's usage count and clear its power.runtime_auto flag, + * so that it cannot be suspended at run time until pm_runtime_allow() is called + * for it. + */ +void pm_runtime_forbid(struct device *dev) +{ + spin_lock_irq(&dev->power.lock); + if (!dev->power.runtime_auto) + goto out; + + dev->power.runtime_auto = false; + atomic_inc(&dev->power.usage_count); + __pm_runtime_resume(dev, false); + + out: + spin_unlock_irq(&dev->power.lock); +} +EXPORT_SYMBOL_GPL(pm_runtime_forbid); + +/** + * pm_runtime_allow - Unblock run-time PM of a device. + * @dev: Device to handle. + * + * Decrease the device's usage count and set its power.runtime_auto flag. + */ +void pm_runtime_allow(struct device *dev) +{ + spin_lock_irq(&dev->power.lock); + if (dev->power.runtime_auto) + goto out; + + dev->power.runtime_auto = true; + if (atomic_dec_and_test(&dev->power.usage_count)) + __pm_runtime_idle(dev); + + out: + spin_unlock_irq(&dev->power.lock); +} +EXPORT_SYMBOL_GPL(pm_runtime_allow); + /** * pm_runtime_init - Initialize run-time PM fields in given device object. * @dev: Device object to initialize. @@ -1028,6 +1072,7 @@ void pm_runtime_init(struct device *dev) atomic_set(&dev->power.child_count, 0); pm_suspend_ignore_children(dev, false); + dev->power.runtime_auto = true; dev->power.request_pending = false; dev->power.request = RPM_REQ_NONE; diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 596aeecfdffe..c011ff15632c 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -4,9 +4,25 @@ #include #include +#include #include "power.h" /* + * control - Report/change current runtime PM setting of the device + * + * Runtime power management of a device can be blocked with the help of + * this attribute. All devices have one of the following two values for + * the power/control file: + * + * + "auto\n" to allow the device to be power managed at run time; + * + "on\n" to prevent the device from being power managed at run time; + * + * The default for all devices is "auto", which means that devices may be + * subject to automatic power management, depending on their drivers. + * Changing this attribute to "on" prevents the driver from power managing + * the device at run time. Doing that while the device is suspended causes + * it to be woken up. + * * wakeup - Report/change current wakeup option for device * * Some devices support "wakeup" events, which are hardware signals @@ -43,6 +59,38 @@ static const char enabled[] = "enabled"; static const char disabled[] = "disabled"; +#ifdef CONFIG_PM_RUNTIME +static const char ctrl_auto[] = "auto"; +static const char ctrl_on[] = "on"; + +static ssize_t control_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", + dev->power.runtime_auto ? ctrl_auto : ctrl_on); +} + +static ssize_t control_store(struct device * dev, struct device_attribute *attr, + const char * buf, size_t n) +{ + char *cp; + int len = n; + + cp = memchr(buf, '\n', n); + if (cp) + len = cp - buf; + if (len == sizeof ctrl_auto - 1 && strncmp(buf, ctrl_auto, len) == 0) + pm_runtime_allow(dev); + else if (len == sizeof ctrl_on - 1 && strncmp(buf, ctrl_on, len) == 0) + pm_runtime_forbid(dev); + else + return -EINVAL; + return n; +} + +static DEVICE_ATTR(control, 0644, control_show, control_store); +#endif + static ssize_t wake_show(struct device * dev, struct device_attribute *attr, char * buf) { @@ -79,6 +127,9 @@ static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); static struct attribute * power_attrs[] = { +#ifdef CONFIG_PM_RUNTIME + &dev_attr_control.attr, +#endif &dev_attr_wakeup.attr, NULL, }; diff --git a/include/linux/pm.h b/include/linux/pm.h index 198b8f9fe05e..25b1eca8049d 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -430,6 +430,7 @@ struct dev_pm_info { unsigned int request_pending:1; unsigned int deferred_resume:1; unsigned int run_wake:1; + unsigned int runtime_auto:1; enum rpm_request request; enum rpm_status runtime_status; int runtime_error; diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 370ce0a6fe4a..7d773aac5314 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -28,6 +28,8 @@ extern int __pm_runtime_set_status(struct device *dev, unsigned int status); extern int pm_runtime_barrier(struct device *dev); extern void pm_runtime_enable(struct device *dev); extern void __pm_runtime_disable(struct device *dev, bool check_resume); +extern void pm_runtime_allow(struct device *dev); +extern void pm_runtime_forbid(struct device *dev); static inline bool pm_children_suspended(struct device *dev) { @@ -78,6 +80,8 @@ static inline int __pm_runtime_set_status(struct device *dev, static inline int pm_runtime_barrier(struct device *dev) { return 0; } static inline void pm_runtime_enable(struct device *dev) {} static inline void __pm_runtime_disable(struct device *dev, bool c) {} +static inline void pm_runtime_allow(struct device *dev) {} +static inline void pm_runtime_forbid(struct device *dev) {} static inline bool pm_children_suspended(struct device *dev) { return false; } static inline void pm_suspend_ignore_children(struct device *dev, bool en) {} -- cgit v1.2.3 From 971cb7fba0c84dd74c39020f4596b08398f58199 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:03:22 +0100 Subject: PM: Document device power attributes in sysfs There are sysfs attributes in /sys/devices/.../power/ that haven't been documented yet in Documentation/ABI/. Document them as appropriate. Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-devices-power | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-devices-power diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power new file mode 100644 index 000000000000..431bfd7e65c7 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -0,0 +1,53 @@ +What: /sys/devices/.../power/ +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../power directory contains attributes + allowing the user space to check and modify some power + management related properties of given device. + +What: /sys/devices/.../power/wakeup +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../power/wakeup attribute allows the user + space to check if the device is enabled to wake up the system + from sleep states, such as the memory sleep state (suspend to + RAM) and hibernation (suspend to disk), and to enable or disable + it to do that as desired. + + Some devices support "wakeup" events, which are hardware signals + used to activate the system from a sleep state. Such devices + have one of the following two values for the sysfs power/wakeup + file: + + + "enabled\n" to issue the events; + + "disabled\n" not to do so; + + In that cases the user space can change the setting represented + by the contents of this file by writing either "enabled", or + "disabled" to it. + + For the devices that are not capable of generating system wakeup + events this file contains "\n". In that cases the user space + cannot modify the contents of this file and the device cannot be + enabled to wake up the system. + +What: /sys/devices/.../power/control +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../power/control attribute allows the user + space to control the run-time power management of the device. + + All devices have one of the following two values for the + power/control file: + + + "auto\n" to allow the device to be power managed at run time; + + "on\n" to prevent the device from being power managed; + + The default for all devices is "auto", which means that they may + be subject to automatic power management, depending on their + drivers. Changing this attribute to "on" prevents the driver + from power managing the device at run time. Doing that while + the device is suspended causes it to be woken up. -- cgit v1.2.3 From 8cc6b39ff36b4bbce2d7471da088df122b0e9033 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:03:29 +0100 Subject: PM: Add parent information to timing messages Add parent information to the messages printed by the suspend/resume core when initcall_debug is set. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index a5142bddef41..6ca5cdf63849 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -271,8 +271,9 @@ static int pm_noirq_op(struct device *dev, ktime_t calltime, delta, rettime; if (initcall_debug) { - pr_info("calling %s_i+ @ %i\n", - dev_name(dev), task_pid_nr(current)); + pr_info("calling %s+ @ %i, parent: %s\n", + dev_name(dev), task_pid_nr(current), + dev->parent ? dev_name(dev->parent) : "none"); calltime = ktime_get(); } -- cgit v1.2.3 From 5af84b82701a96be4b033aaa51d86c72e2ded061 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:23:32 +0100 Subject: PM: Asynchronous suspend and resume of devices Theoretically, the total time of system sleep transitions (suspend to RAM, hibernation) can be reduced by running suspend and resume callbacks of device drivers in parallel with each other. However, there are dependencies between devices such that we're not allowed to suspend the parent of a device before suspending the device itself. Analogously, we're not allowed to resume a device before resuming its parent. The most straightforward way to take these dependencies into accout is to start the async threads used for suspending and resuming devices at the core level, so that async_schedule() is called for each suspend and resume callback supposed to be executed asynchronously. For this purpose, introduce a new device flag, power.async_suspend, used to mark the devices whose suspend and resume callbacks are to be executed asynchronously (ie. in parallel with the main suspend/resume thread and possibly in parallel with each other) and helper function device_enable_async_suspend() allowing one to set power.async_suspend for given device (power.async_suspend is unset by default for all devices). For each device with the power.async_suspend flag set the PM core will use async_schedule() to execute its suspend and resume callbacks. The async threads started for different devices as a result of calling async_schedule() are synchronized with each other and with the main suspend/resume thread with the help of completions, in the following way: (1) There is a completion, power.completion, for each device object. (2) Each device's completion is reset before calling async_schedule() for the device or, in the case of devices with the power.async_suspend flags unset, before executing the device's suspend and resume callbacks. (3) During suspend, right before running the bus type, device type and device class suspend callbacks for the device, the PM core waits for the completions of all the device's children to be completed. (4) During resume, right before running the bus type, device type and device class resume callbacks for the device, the PM core waits for the completion of the device's parent to be completed. (5) The PM core completes power.completion for each device right after the bus type, device type and device class suspend (or resume) callbacks executed for the device have returned. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 115 ++++++++++++++++++++++++++++++++++++++++--- include/linux/device.h | 6 +++ include/linux/pm.h | 3 ++ include/linux/resume-trace.h | 7 +++ 4 files changed, 125 insertions(+), 6 deletions(-) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 6ca5cdf63849..3b44c201ddad 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "../base.h" #include "power.h" @@ -42,6 +43,7 @@ LIST_HEAD(dpm_list); static DEFINE_MUTEX(dpm_list_mtx); +static pm_message_t pm_transition; /* * Set once the preparation of devices for a PM transition has started, reset @@ -56,6 +58,7 @@ static bool transition_started; void device_pm_init(struct device *dev) { dev->power.status = DPM_ON; + init_completion(&dev->power.completion); pm_runtime_init(dev); } @@ -111,6 +114,7 @@ void device_pm_remove(struct device *dev) pr_debug("PM: Removing info for %s:%s\n", dev->bus ? dev->bus->name : "No Bus", kobject_name(&dev->kobj)); + complete_all(&dev->power.completion); mutex_lock(&dpm_list_mtx); list_del_init(&dev->power.entry); mutex_unlock(&dpm_list_mtx); @@ -187,6 +191,31 @@ static void initcall_debug_report(struct device *dev, ktime_t calltime, } } +/** + * dpm_wait - Wait for a PM operation to complete. + * @dev: Device to wait for. + * @async: If unset, wait only if the device's power.async_suspend flag is set. + */ +static void dpm_wait(struct device *dev, bool async) +{ + if (!dev) + return; + + if (async || dev->power.async_suspend) + wait_for_completion(&dev->power.completion); +} + +static int dpm_wait_fn(struct device *dev, void *async_ptr) +{ + dpm_wait(dev, *((bool *)async_ptr)); + return 0; +} + +static void dpm_wait_for_children(struct device *dev, bool async) +{ + device_for_each_child(dev, &async, dpm_wait_fn); +} + /** * pm_op - Execute the PM operation appropriate for given PM event. * @dev: Device to handle. @@ -466,17 +495,19 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev)) } /** - * device_resume - Execute "resume" callbacks for given device. + * __device_resume - Execute "resume" callbacks for given device. * @dev: Device to handle. * @state: PM transition of the system being carried out. + * @async: If true, the device is being resumed asynchronously. */ -static int device_resume(struct device *dev, pm_message_t state) +static int __device_resume(struct device *dev, pm_message_t state, bool async) { int error = 0; TRACE_DEVICE(dev); TRACE_RESUME(0); + dpm_wait(dev->parent, async); down(&dev->sem); if (dev->bus) { @@ -511,11 +542,36 @@ static int device_resume(struct device *dev, pm_message_t state) } End: up(&dev->sem); + complete_all(&dev->power.completion); TRACE_RESUME(error); return error; } +static void async_resume(void *data, async_cookie_t cookie) +{ + struct device *dev = (struct device *)data; + int error; + + error = __device_resume(dev, pm_transition, true); + if (error) + pm_dev_err(dev, pm_transition, " async", error); + put_device(dev); +} + +static int device_resume(struct device *dev) +{ + INIT_COMPLETION(dev->power.completion); + + if (dev->power.async_suspend && !pm_trace_is_enabled()) { + get_device(dev); + async_schedule(async_resume, dev); + return 0; + } + + return __device_resume(dev, pm_transition, false); +} + /** * dpm_resume - Execute "resume" callbacks for non-sysdev devices. * @state: PM transition of the system being carried out. @@ -530,6 +586,7 @@ static void dpm_resume(pm_message_t state) INIT_LIST_HEAD(&list); mutex_lock(&dpm_list_mtx); + pm_transition = state; while (!list_empty(&dpm_list)) { struct device *dev = to_device(dpm_list.next); @@ -540,7 +597,7 @@ static void dpm_resume(pm_message_t state) dev->power.status = DPM_RESUMING; mutex_unlock(&dpm_list_mtx); - error = device_resume(dev, state); + error = device_resume(dev); mutex_lock(&dpm_list_mtx); if (error) @@ -555,6 +612,7 @@ static void dpm_resume(pm_message_t state) } list_splice(&list, &dpm_list); mutex_unlock(&dpm_list_mtx); + async_synchronize_full(); dpm_show_time(starttime, state, NULL); } @@ -732,17 +790,24 @@ static int legacy_suspend(struct device *dev, pm_message_t state, return error; } +static int async_error; + /** * device_suspend - Execute "suspend" callbacks for given device. * @dev: Device to handle. * @state: PM transition of the system being carried out. + * @async: If true, the device is being suspended asynchronously. */ -static int device_suspend(struct device *dev, pm_message_t state) +static int __device_suspend(struct device *dev, pm_message_t state, bool async) { int error = 0; + dpm_wait_for_children(dev, async); down(&dev->sem); + if (async_error) + goto End; + if (dev->class) { if (dev->class->pm) { pm_dev_dbg(dev, state, "class "); @@ -773,12 +838,44 @@ static int device_suspend(struct device *dev, pm_message_t state) error = legacy_suspend(dev, state, dev->bus->suspend); } } + + if (!error) + dev->power.status = DPM_OFF; + End: up(&dev->sem); + complete_all(&dev->power.completion); return error; } +static void async_suspend(void *data, async_cookie_t cookie) +{ + struct device *dev = (struct device *)data; + int error; + + error = __device_suspend(dev, pm_transition, true); + if (error) { + pm_dev_err(dev, pm_transition, " async", error); + async_error = error; + } + + put_device(dev); +} + +static int device_suspend(struct device *dev) +{ + INIT_COMPLETION(dev->power.completion); + + if (dev->power.async_suspend) { + get_device(dev); + async_schedule(async_suspend, dev); + return 0; + } + + return __device_suspend(dev, pm_transition, false); +} + /** * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. * @state: PM transition of the system being carried out. @@ -791,13 +888,15 @@ static int dpm_suspend(pm_message_t state) INIT_LIST_HEAD(&list); mutex_lock(&dpm_list_mtx); + pm_transition = state; + async_error = 0; while (!list_empty(&dpm_list)) { struct device *dev = to_device(dpm_list.prev); get_device(dev); mutex_unlock(&dpm_list_mtx); - error = device_suspend(dev, state); + error = device_suspend(dev); mutex_lock(&dpm_list_mtx); if (error) { @@ -805,13 +904,17 @@ static int dpm_suspend(pm_message_t state) put_device(dev); break; } - dev->power.status = DPM_OFF; if (!list_empty(&dev->power.entry)) list_move(&dev->power.entry, &list); put_device(dev); + if (async_error) + break; } list_splice(&list, dpm_list.prev); mutex_unlock(&dpm_list_mtx); + async_synchronize_full(); + if (!error) + error = async_error; if (!error) dpm_show_time(starttime, state, NULL); return error; diff --git a/include/linux/device.h b/include/linux/device.h index a62799f2ab00..70adc5f3f50a 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -472,6 +472,12 @@ static inline int device_is_registered(struct device *dev) return dev->kobj.state_in_sysfs; } +static inline void device_enable_async_suspend(struct device *dev) +{ + if (dev->power.status == DPM_ON) + dev->power.async_suspend = true; +} + void driver_init(void); /* diff --git a/include/linux/pm.h b/include/linux/pm.h index 25b1eca8049d..9c16cd20fc96 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -26,6 +26,7 @@ #include #include #include +#include /* * Callbacks for platform drivers to implement. @@ -412,9 +413,11 @@ struct dev_pm_info { pm_message_t power_state; unsigned int can_wakeup:1; unsigned int should_wakeup:1; + unsigned async_suspend:1; enum dpm_state status; /* Owned by the PM core */ #ifdef CONFIG_PM_SLEEP struct list_head entry; + struct completion completion; #endif #ifdef CONFIG_PM_RUNTIME struct timer_list suspend_timer; diff --git a/include/linux/resume-trace.h b/include/linux/resume-trace.h index c9ba2fdf807d..bc8c3881c729 100644 --- a/include/linux/resume-trace.h +++ b/include/linux/resume-trace.h @@ -6,6 +6,11 @@ extern int pm_trace_enabled; +static inline int pm_trace_is_enabled(void) +{ + return pm_trace_enabled; +} + struct device; extern void set_trace_device(struct device *); extern void generate_resume_trace(const void *tracedata, unsigned int user); @@ -17,6 +22,8 @@ extern void generate_resume_trace(const void *tracedata, unsigned int user); #else +static inline int pm_trace_is_enabled(void) { return 0; } + #define TRACE_DEVICE(dev) do { } while (0) #define TRACE_RESUME(dev) do { } while (0) -- cgit v1.2.3 From 0e06b4a891c6a108412fe24b4500f499da2cf8a1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:25:15 +0100 Subject: PM: Add a switch for disabling/enabling asynchronous suspend/resume Add sysfs attribute /sys/power/pm_async allowing the user space to disable/enable asynchronous suspend/resume of devices. Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-power | 13 +++++++++++++ drivers/base/power/main.c | 7 ++++--- drivers/base/power/power.h | 6 +++--- kernel/power/main.c | 31 ++++++++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index dcff4d0623ad..d6a801f45b48 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -101,3 +101,16 @@ Description: CAUTION: Using it will cause your machine's real-time (CMOS) clock to be set to a random invalid time after a resume. + +What: /sys/power/pm_async +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/power/pm_async file controls the switch allowing the + user space to enable or disable asynchronous suspend and resume + of devices. If enabled, this feature will cause some device + drivers' suspend and resume callbacks to be executed in parallel + with each other and with the main suspend thread. It is enabled + if this file contains "1", which is the default. It may be + disabled by writing "0" to this file, in which case all devices + will be suspended and resumed synchronously. diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 3b44c201ddad..7e79201b09bb 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -201,7 +201,7 @@ static void dpm_wait(struct device *dev, bool async) if (!dev) return; - if (async || dev->power.async_suspend) + if (async || (pm_async_enabled && dev->power.async_suspend)) wait_for_completion(&dev->power.completion); } @@ -563,7 +563,8 @@ static int device_resume(struct device *dev) { INIT_COMPLETION(dev->power.completion); - if (dev->power.async_suspend && !pm_trace_is_enabled()) { + if (pm_async_enabled && dev->power.async_suspend + && !pm_trace_is_enabled()) { get_device(dev); async_schedule(async_resume, dev); return 0; @@ -867,7 +868,7 @@ static int device_suspend(struct device *dev) { INIT_COMPLETION(dev->power.completion); - if (dev->power.async_suspend) { + if (pm_async_enabled && dev->power.async_suspend) { get_device(dev); async_schedule(async_suspend, dev); return 0; diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index b8fa1aa5225a..c0bd03c83b9c 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -12,10 +12,10 @@ static inline void pm_runtime_remove(struct device *dev) {} #ifdef CONFIG_PM_SLEEP -/* - * main.c - */ +/* kernel/power/main.c */ +extern int pm_async_enabled; +/* drivers/base/power/main.c */ extern struct list_head dpm_list; /* The active device list */ static inline struct device *to_device(struct list_head *entry) diff --git a/kernel/power/main.c b/kernel/power/main.c index 0998c7139053..b58800b21fc0 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -44,6 +44,32 @@ int pm_notifier_call_chain(unsigned long val) == NOTIFY_BAD) ? -EINVAL : 0; } +/* If set, devices may be suspended and resumed asynchronously. */ +int pm_async_enabled = 1; + +static ssize_t pm_async_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", pm_async_enabled); +} + +static ssize_t pm_async_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val > 1) + return -EINVAL; + + pm_async_enabled = val; + return n; +} + +power_attr(pm_async); + #ifdef CONFIG_PM_DEBUG int pm_test_level = TEST_NONE; @@ -208,8 +234,11 @@ static struct attribute * g[] = { #ifdef CONFIG_PM_TRACE &pm_trace_attr.attr, #endif -#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG) +#ifdef CONFIG_PM_SLEEP + &pm_async_attr.attr, +#ifdef CONFIG_PM_DEBUG &pm_test_attr.attr, +#endif #endif NULL, }; -- cgit v1.2.3 From 5a2eb8585f3b38e01e30aacaa8b985a1520a993d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:25:23 +0100 Subject: PM: Add facility for advanced testing of async suspend/resume Add configuration switch CONFIG_PM_ADVANCED_DEBUG for compiling in extra PM debugging/testing code allowing one to access some PM-related attributes of devices from the user space via sysfs. If CONFIG_PM_ADVANCED_DEBUG is set, add sysfs attribute power/async for every device allowing the user space to access the device's power.async_suspend flag and modify it, if desired. Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-devices-power | 26 ++++++++++++++ drivers/base/power/sysfs.c | 49 +++++++++++++++++++++++++++ include/linux/device.h | 11 ++++++ kernel/power/Kconfig | 14 ++++++++ 4 files changed, 100 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power index 431bfd7e65c7..6123c523bfd7 100644 --- a/Documentation/ABI/testing/sysfs-devices-power +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -51,3 +51,29 @@ Description: drivers. Changing this attribute to "on" prevents the driver from power managing the device at run time. Doing that while the device is suspended causes it to be woken up. + +What: /sys/devices/.../power/async +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../async attribute allows the user space to + enable or diasble the device's suspend and resume callbacks to + be executed asynchronously (ie. in separate threads, in parallel + with the main suspend/resume thread) during system-wide power + transitions (eg. suspend to RAM, hibernation). + + All devices have one of the following two values for the + power/async file: + + + "enabled\n" to permit the asynchronous suspend/resume; + + "disabled\n" to forbid it; + + The value of this attribute may be changed by writing either + "enabled", or "disabled" to it. + + It generally is unsafe to permit the asynchronous suspend/resume + of a device unless it is certain that all of the PM dependencies + of the device are known to the PM core. However, for some + devices this attribute is set to "enabled" by bus type code or + device drivers and in that cases it should be safe to leave the + default value. diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index c011ff15632c..86fd9373447e 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -54,6 +54,24 @@ * wakeup events internally (unless they are disabled), keeping * their hardware in low power modes whenever they're unused. This * saves runtime power, without requiring system-wide sleep states. + * + * async - Report/change current async suspend setting for the device + * + * Asynchronous suspend and resume of the device during system-wide power + * state transitions can be enabled by writing "enabled" to this file. + * Analogously, if "disabled" is written to this file, the device will be + * suspended and resumed synchronously. + * + * All devices have one of the following two values for power/async: + * + * + "enabled\n" to permit the asynchronous suspend/resume of the device; + * + "disabled\n" to forbid it; + * + * NOTE: It generally is unsafe to permit the asynchronous suspend/resume + * of a device unless it is certain that all of the PM dependencies of the + * device are known to the PM core. However, for some devices this + * attribute is set to "enabled" by bus type code or device drivers and in + * that cases it should be safe to leave the default value. */ static const char enabled[] = "enabled"; @@ -125,12 +143,43 @@ wake_store(struct device * dev, struct device_attribute *attr, static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); +#ifdef CONFIG_PM_SLEEP_ADVANCED_DEBUG +static ssize_t async_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", + device_async_suspend_enabled(dev) ? enabled : disabled); +} + +static ssize_t async_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + char *cp; + int len = n; + + cp = memchr(buf, '\n', n); + if (cp) + len = cp - buf; + if (len == sizeof enabled - 1 && strncmp(buf, enabled, len) == 0) + device_enable_async_suspend(dev); + else if (len == sizeof disabled - 1 && strncmp(buf, disabled, len) == 0) + device_disable_async_suspend(dev); + else + return -EINVAL; + return n; +} + +static DEVICE_ATTR(async, 0644, async_show, async_store); +#endif /* CONFIG_PM_SLEEP_ADVANCED_DEBUG */ static struct attribute * power_attrs[] = { #ifdef CONFIG_PM_RUNTIME &dev_attr_control.attr, #endif &dev_attr_wakeup.attr, +#ifdef CONFIG_PM_SLEEP_ADVANCED_DEBUG + &dev_attr_async.attr, +#endif NULL, }; static struct attribute_group pm_attr_group = { diff --git a/include/linux/device.h b/include/linux/device.h index 70adc5f3f50a..b30527db3ac0 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -478,6 +478,17 @@ static inline void device_enable_async_suspend(struct device *dev) dev->power.async_suspend = true; } +static inline void device_disable_async_suspend(struct device *dev) +{ + if (dev->power.status == DPM_ON) + dev->power.async_suspend = false; +} + +static inline bool device_async_suspend_enabled(struct device *dev) +{ + return !!dev->power.async_suspend; +} + void driver_init(void); /* diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 4c9cffcf69c7..5c36ea9d55d2 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -27,6 +27,15 @@ config PM_DEBUG code. This is helpful when debugging and reporting PM bugs, like suspend support. +config PM_ADVANCED_DEBUG + bool "Extra PM attributes in sysfs for low-level debugging/testing" + depends on PM_DEBUG + default n + ---help--- + Add extra sysfs attributes allowing one to access some Power Management + fields of device objects from user space. If you are not a kernel + developer interested in debugging/testing Power Management, say "no". + config PM_VERBOSE bool "Verbose Power Management debugging" depends on PM_DEBUG @@ -85,6 +94,11 @@ config PM_SLEEP depends on SUSPEND || HIBERNATION || XEN_SAVE_RESTORE default y +config PM_SLEEP_ADVANCED_DEBUG + bool + depends on PM_ADVANCED_DEBUG + default n + config SUSPEND bool "Suspend to RAM and standby" depends on PM && ARCH_SUSPEND_POSSIBLE -- cgit v1.2.3 From 97df8c12995c5bac73e3bfeea4c5be155c1f4401 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:25:31 +0100 Subject: PM: Start asynchronous resume threads upfront It has been shown by testing that total device resume time can be reduced significantly (by as much as 50% or more) if the async threads executing some devices' resume routines are all started before the main resume thread starts to handle the "synchronous" devices. This is a consequence of the fact that the slowest devices tend to be located at the end of dpm_list, so their resume routines are started very late. Consequently, they have to wait for all the preceding "synchronous" devices before their resume routines can be started by the main resume thread, even if they are "asynchronous". By starting their async threads upfront we effectively move those devices towards the beginning of dpm_list, without breaking their ordering with respect to their parents and children. As a result, their resume routines are started much earlier and we are able to save much more device resume time this way. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 7e79201b09bb..6efef9fb23a1 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -495,12 +495,12 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev)) } /** - * __device_resume - Execute "resume" callbacks for given device. + * device_resume - Execute "resume" callbacks for given device. * @dev: Device to handle. * @state: PM transition of the system being carried out. * @async: If true, the device is being resumed asynchronously. */ -static int __device_resume(struct device *dev, pm_message_t state, bool async) +static int device_resume(struct device *dev, pm_message_t state, bool async) { int error = 0; @@ -510,6 +510,8 @@ static int __device_resume(struct device *dev, pm_message_t state, bool async) dpm_wait(dev->parent, async); down(&dev->sem); + dev->power.status = DPM_RESUMING; + if (dev->bus) { if (dev->bus->pm) { pm_dev_dbg(dev, state, ""); @@ -553,24 +555,16 @@ static void async_resume(void *data, async_cookie_t cookie) struct device *dev = (struct device *)data; int error; - error = __device_resume(dev, pm_transition, true); + error = device_resume(dev, pm_transition, true); if (error) pm_dev_err(dev, pm_transition, " async", error); put_device(dev); } -static int device_resume(struct device *dev) +static bool is_async(struct device *dev) { - INIT_COMPLETION(dev->power.completion); - - if (pm_async_enabled && dev->power.async_suspend - && !pm_trace_is_enabled()) { - get_device(dev); - async_schedule(async_resume, dev); - return 0; - } - - return __device_resume(dev, pm_transition, false); + return dev->power.async_suspend && pm_async_enabled + && !pm_trace_is_enabled(); } /** @@ -583,22 +577,33 @@ static int device_resume(struct device *dev) static void dpm_resume(pm_message_t state) { struct list_head list; + struct device *dev; ktime_t starttime = ktime_get(); INIT_LIST_HEAD(&list); mutex_lock(&dpm_list_mtx); pm_transition = state; - while (!list_empty(&dpm_list)) { - struct device *dev = to_device(dpm_list.next); + list_for_each_entry(dev, &dpm_list, power.entry) { + if (dev->power.status < DPM_OFF) + continue; + + INIT_COMPLETION(dev->power.completion); + if (is_async(dev)) { + get_device(dev); + async_schedule(async_resume, dev); + } + } + + while (!list_empty(&dpm_list)) { + dev = to_device(dpm_list.next); get_device(dev); - if (dev->power.status >= DPM_OFF) { + if (dev->power.status >= DPM_OFF && !is_async(dev)) { int error; - dev->power.status = DPM_RESUMING; mutex_unlock(&dpm_list_mtx); - error = device_resume(dev); + error = device_resume(dev, state, false); mutex_lock(&dpm_list_mtx); if (error) -- cgit v1.2.3 From f8824cee405c62ba465b85365201166d9cf86a14 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 27 Jan 2010 23:47:38 +0100 Subject: PM: Allow device drivers to use dpm_wait() There are some dependencies between devices (in particular, between EHCI USB controllers and their OHCI/UHCI siblings) which are not reflected by the structure of the device tree. With synchronous suspend and resume these dependencies are taken into accout automatically, because the devices in question are always registered in the right order, but to meet these constraints with asynchronous suspend and resume the drivers of these devices will need to use dpm_wait() in their suspend/resume routines, so introduce a helper function allowing them to do that. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 11 +++++++++++ include/linux/pm.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 6efef9fb23a1..0e26a6f6fd48 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1046,3 +1046,14 @@ void __suspend_report_result(const char *function, void *fn, int ret) printk(KERN_ERR "%s(): %pF returns %d\n", function, fn, ret); } EXPORT_SYMBOL_GPL(__suspend_report_result); + +/** + * device_pm_wait_for_dev - Wait for suspend/resume of a device to complete. + * @dev: Device to wait for. + * @subordinate: Device that needs to wait for @dev. + */ +void device_pm_wait_for_dev(struct device *subordinate, struct device *dev) +{ + dpm_wait(dev, subordinate->power.async_suspend); +} +EXPORT_SYMBOL_GPL(device_pm_wait_for_dev); diff --git a/include/linux/pm.h b/include/linux/pm.h index 9c16cd20fc96..e80df06ad22a 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -512,6 +512,7 @@ extern void __suspend_report_result(const char *function, void *fn, int ret); __suspend_report_result(__func__, fn, ret); \ } while (0) +extern void device_pm_wait_for_dev(struct device *sub, struct device *dev); #else /* !CONFIG_PM_SLEEP */ #define device_pm_lock() do {} while (0) @@ -524,6 +525,7 @@ static inline int dpm_suspend_start(pm_message_t state) #define suspend_report_result(fn, ret) do {} while (0) +static inline void device_pm_wait_for_dev(struct device *a, struct device *b) {} #endif /* !CONFIG_PM_SLEEP */ /* How to reorder dpm_list after device_move() */ -- cgit v1.2.3 From b694e52ebdd439e4591d4a6651658026196174f6 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Wed, 27 Jan 2010 23:47:50 +0100 Subject: PM / Hibernate: Really deprecate deprecated user ioctls They were deprecated and removed from exported headers more than 2 years ago. Inform users about their removal in the future now. (Switch cases needed to be reorderded for an easy fall through.) And add an entry to feature-removal-schedule. Signed-off-by: Jiri Slaby Acked-by: Pavel Machek Signed-off-by: Rafael J. Wysocki --- Documentation/feature-removal-schedule.txt | 11 +++++++++++ kernel/power/user.c | 23 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 0a46833c1b76..b9eba900e0f0 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -64,6 +64,17 @@ Who: Robin Getz & Matt Mackall --------------------------- +What: Deprecated snapshot ioctls +When: 2.6.36 + +Why: The ioctls in kernel/power/user.c were marked as deprecated long time + ago. Now they notify users about that so that they need to replace + their userspace. After some more time, remove them completely. + +Who: Jiri Slaby + +--------------------------- + What: The ieee80211_regdom module parameter When: March 2010 / desktop catchup diff --git a/kernel/power/user.c b/kernel/power/user.c index bf0014d6a5f0..4d2289626a84 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -195,6 +195,15 @@ static ssize_t snapshot_write(struct file *filp, const char __user *buf, return res; } +static void snapshot_deprecated_ioctl(unsigned int cmd) +{ + if (printk_ratelimit()) + printk(KERN_NOTICE "%pf: ioctl '%.8x' is deprecated and will " + "be removed soon, update your suspend-to-disk " + "utilities\n", + __builtin_return_address(0), cmd); +} + static long snapshot_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -246,8 +255,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, data->frozen = 0; break; - case SNAPSHOT_CREATE_IMAGE: case SNAPSHOT_ATOMIC_SNAPSHOT: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_CREATE_IMAGE: if (data->mode != O_RDONLY || !data->frozen || data->ready) { error = -EPERM; break; @@ -275,8 +285,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, data->ready = 0; break; - case SNAPSHOT_PREF_IMAGE_SIZE: case SNAPSHOT_SET_IMAGE_SIZE: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_PREF_IMAGE_SIZE: image_size = arg; break; @@ -290,15 +301,17 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, error = put_user(size, (loff_t __user *)arg); break; - case SNAPSHOT_AVAIL_SWAP_SIZE: case SNAPSHOT_AVAIL_SWAP: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_AVAIL_SWAP_SIZE: size = count_swap_pages(data->swap, 1); size <<= PAGE_SHIFT; error = put_user(size, (loff_t __user *)arg); break; - case SNAPSHOT_ALLOC_SWAP_PAGE: case SNAPSHOT_GET_SWAP_PAGE: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_ALLOC_SWAP_PAGE: if (data->swap < 0 || data->swap >= MAX_SWAPFILES) { error = -ENODEV; break; @@ -321,6 +334,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, break; case SNAPSHOT_SET_SWAP_FILE: /* This ioctl is deprecated */ + snapshot_deprecated_ioctl(cmd); if (!swsusp_swap_in_use()) { /* * User space encodes device types as two-byte values, @@ -362,6 +376,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, break; case SNAPSHOT_PMOPS: /* This ioctl is deprecated */ + snapshot_deprecated_ioctl(cmd); error = -EINVAL; switch (arg) { -- cgit v1.2.3 From 09c09bc618a4ceec387c57542031b4fc35826e16 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Wed, 27 Jan 2010 23:47:56 +0100 Subject: PM / Hibernate: Swap, remove useless check from swsusp_read() It will never reach here if the sws_resume_bdev is erratic. swsusp_read() is called only from software_resume(), but after swsusp_check() which would catch the error state. Signed-off-by: Jiri Slaby Signed-off-by: Rafael J. Wysocki --- kernel/power/swap.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/kernel/power/swap.c b/kernel/power/swap.c index 09b2b0ae9e9d..1d575733d4e1 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -657,10 +657,6 @@ int swsusp_read(unsigned int *flags_p) struct swsusp_info *header; *flags_p = swsusp_header->flags; - if (IS_ERR(resume_bdev)) { - pr_debug("PM: Image device not initialised\n"); - return PTR_ERR(resume_bdev); - } memset(&snapshot, 0, sizeof(struct snapshot_handle)); error = snapshot_write_next(&snapshot, PAGE_SIZE); -- cgit v1.2.3 From a1e4d72cd3024999bfb6703092ea271438805c89 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Feb 2010 19:16:33 +0100 Subject: PM: Allow PCI devices to suspend/resume asynchronously Set power.async_suspend for all PCI devices and PCIe port services, so that they can be suspended and resumed in parallel with other devices they don't depend on in a known way (i.e. devices which are not their parents or children). This only affects the "regular" suspend and resume stages, which means in particular that the restoration of the PCI devices' standard configuration registers during resume will still be carried out synchronously (at the "early" resume stage). Signed-off-by: Rafael J. Wysocki --- drivers/pci/pci.c | 1 + drivers/pci/pcie/portdrv_core.c | 1 + drivers/pci/probe.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index f4a2738bf0bf..2b9ac9e594af 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1540,6 +1540,7 @@ void pci_pm_init(struct pci_dev *dev) int pm; u16 pmc; + device_enable_async_suspend(&dev->dev); dev->wakeup_prepared = false; dev->pm_cap = 0; diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 0d34ff415399..e73effbe402c 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -285,6 +285,7 @@ static int pcie_device_init(struct pci_dev *pdev, int service, int irq) pci_name(pdev), get_descriptor_id(pdev->pcie_type, service)); device->parent = &pdev->dev; + device_enable_async_suspend(device); retval = device_register(device); if (retval) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 270d069819f7..2a943090a3b7 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1436,6 +1436,7 @@ struct pci_bus * pci_create_bus(struct device *parent, if (error) goto dev_reg_err; b->bridge = get_device(dev); + device_enable_async_suspend(b->bridge); if (!parent) set_dev_node(b->bridge, pcibus_to_node(b)); -- cgit v1.2.3 From 6d19c009cc780c63de25a046509ebc9473809fd6 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 12 Feb 2010 12:21:11 +0100 Subject: USB: implement non-tree resume ordering constraints for PCI host controllers This patch (as1331) adds non-tree ordering constraints needed for proper resume of PCI USB host controllers from hibernation. The main issue is that non-high-speed devices must not be resumed before the high-speed root hub, because it is the ehci_bus_resume() routine which takes care of handing the device connection over to the companion controller. If the device resume is attempted before the handover then the device won't be found and it will be treated as though it had disconnected. The patch adds a new field to the usb_bus structure; for each full/low-speed bus this field will contain a pointer to the companion high-speed bus (if one exists). It is used during normal device resume; if the hs_companion pointer isn't NULL then we wait for the root-hub device on the hs_companion bus. A secondary issue is that an EHCI controlller shouldn't be resumed before any of its companions. On some machines I have observed handovers failing if the companion controller is reinitialized after the handover. Thus, the EHCI resume routine must wait for the companion controllers to be resumed. The patch also fixes a small bug in usb_hcd_pci_probe(); an error path jumps to the wrong label, causing a memory leak. [rjw: Fixed compilation for CONFIG_PM_SLEEP unset.] Signed-off-by: Alan Stern Acked-by: Greg Kroah-Hartman Signed-off-by: Rafael J. Wysocki --- drivers/usb/core/driver.c | 8 +++ drivers/usb/core/hcd-pci.c | 127 ++++++++++++++++++++++++++++++++++++++++++++- include/linux/usb.h | 1 + 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 60a45f1e3a67..f2f055eb6831 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1022,6 +1022,14 @@ static int usb_resume_device(struct usb_device *udev, pm_message_t msg) goto done; } + /* Non-root devices on a full/low-speed bus must wait for their + * companion high-speed root hub, in case a handoff is needed. + */ + if (!(msg.event & PM_EVENT_AUTO) && udev->parent && + udev->bus->hs_companion) + device_pm_wait_for_dev(&udev->dev, + &udev->bus->hs_companion->root_hub->dev); + if (udev->quirks & USB_QUIRK_RESET_RESUME) udev->reset_resume = 1; diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 2dcf906df569..15286533c15a 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,122 @@ /* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ +#ifdef CONFIG_PM_SLEEP + +/* Coordinate handoffs between EHCI and companion controllers + * during system resume + */ + +static DEFINE_MUTEX(companions_mutex); + +#define CL_UHCI PCI_CLASS_SERIAL_USB_UHCI +#define CL_OHCI PCI_CLASS_SERIAL_USB_OHCI +#define CL_EHCI PCI_CLASS_SERIAL_USB_EHCI + +enum companion_action { + SET_HS_COMPANION, CLEAR_HS_COMPANION, WAIT_FOR_COMPANIONS +}; + +static void companion_common(struct pci_dev *pdev, struct usb_hcd *hcd, + enum companion_action action) +{ + struct pci_dev *companion; + struct usb_hcd *companion_hcd; + unsigned int slot = PCI_SLOT(pdev->devfn); + + /* Iterate through other PCI functions in the same slot. + * If pdev is OHCI or UHCI then we are looking for EHCI, and + * vice versa. + */ + companion = NULL; + for (;;) { + companion = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, companion); + if (!companion) + break; + if (companion->bus != pdev->bus || + PCI_SLOT(companion->devfn) != slot) + continue; + + companion_hcd = pci_get_drvdata(companion); + if (!companion_hcd) + continue; + + /* For SET_HS_COMPANION, store a pointer to the EHCI bus in + * the OHCI/UHCI companion bus structure. + * For CLEAR_HS_COMPANION, clear the pointer to the EHCI bus + * in the OHCI/UHCI companion bus structure. + * For WAIT_FOR_COMPANIONS, wait until the OHCI/UHCI + * companion controllers have fully resumed. + */ + + if ((pdev->class == CL_OHCI || pdev->class == CL_UHCI) && + companion->class == CL_EHCI) { + /* action must be SET_HS_COMPANION */ + dev_dbg(&companion->dev, "HS companion for %s\n", + dev_name(&pdev->dev)); + hcd->self.hs_companion = &companion_hcd->self; + + } else if (pdev->class == CL_EHCI && + (companion->class == CL_OHCI || + companion->class == CL_UHCI)) { + switch (action) { + case SET_HS_COMPANION: + dev_dbg(&pdev->dev, "HS companion for %s\n", + dev_name(&companion->dev)); + companion_hcd->self.hs_companion = &hcd->self; + break; + case CLEAR_HS_COMPANION: + companion_hcd->self.hs_companion = NULL; + break; + case WAIT_FOR_COMPANIONS: + device_pm_wait_for_dev(&pdev->dev, + &companion->dev); + break; + } + } + } +} + +static void set_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd) +{ + mutex_lock(&companions_mutex); + dev_set_drvdata(&pdev->dev, hcd); + companion_common(pdev, hcd, SET_HS_COMPANION); + mutex_unlock(&companions_mutex); +} + +static void clear_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd) +{ + mutex_lock(&companions_mutex); + dev_set_drvdata(&pdev->dev, NULL); + + /* If pdev is OHCI or UHCI, just clear its hs_companion pointer */ + if (pdev->class == CL_OHCI || pdev->class == CL_UHCI) + hcd->self.hs_companion = NULL; + + /* Otherwise search for companion buses and clear their pointers */ + else + companion_common(pdev, hcd, CLEAR_HS_COMPANION); + mutex_unlock(&companions_mutex); +} + +static void wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd) +{ + /* Only EHCI controllers need to wait. + * No locking is needed because a controller cannot be resumed + * while one of its companions is getting unbound. + */ + if (pdev->class == CL_EHCI) + companion_common(pdev, hcd, WAIT_FOR_COMPANIONS); +} + +#else /* !CONFIG_PM_SLEEP */ + +static inline void set_hs_companion(struct pci_dev *d, struct usb_hcd *h) {} +static inline void clear_hs_companion(struct pci_dev *d, struct usb_hcd *h) {} +static inline void wait_for_companions(struct pci_dev *d, struct usb_hcd *h) {} + +#endif /* !CONFIG_PM_SLEEP */ /*-------------------------------------------------------------------------*/ @@ -123,7 +240,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) if (region == PCI_ROM_RESOURCE) { dev_dbg(&dev->dev, "no i/o regions available\n"); retval = -EBUSY; - goto err1; + goto err2; } } @@ -132,6 +249,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); if (retval != 0) goto err4; + set_hs_companion(dev, hcd); return retval; err4: @@ -142,6 +260,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) } else release_region(hcd->rsrc_start, hcd->rsrc_len); err2: + clear_hs_companion(dev, hcd); usb_put_hcd(hcd); err1: pci_disable_device(dev); @@ -180,6 +299,7 @@ void usb_hcd_pci_remove(struct pci_dev *dev) } else { release_region(hcd->rsrc_start, hcd->rsrc_len); } + clear_hs_companion(dev, hcd); usb_put_hcd(hcd); pci_disable_device(dev); } @@ -344,6 +464,11 @@ static int resume_common(struct device *dev, bool hibernated) clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); if (hcd->driver->pci_resume) { + /* This call should be made only during system resume, + * not during runtime resume. + */ + wait_for_companions(pci_dev, hcd); + retval = hcd->driver->pci_resume(hcd, hibernated); if (retval) { dev_err(dev, "PCI post-resume error %d!\n", retval); diff --git a/include/linux/usb.h b/include/linux/usb.h index d7ace1b80f09..332eaea61021 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -339,6 +339,7 @@ struct usb_bus { struct usb_devmap devmap; /* device address allocation map */ struct usb_device *root_hub; /* Root hub */ + struct usb_bus *hs_companion; /* Companion EHCI bus, if any */ struct list_head bus_list; /* list of busses */ int bandwidth_allocated; /* on this bus: how much of the time -- cgit v1.2.3 From 927bc9165d3cd01054b472f9b25d91a8707209c8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Feb 2010 19:18:16 +0100 Subject: PM: Allow USB devices to suspend/resume asynchronously Set power.async_suspend for USB devices, endpoints and interfaces, allowing them to be suspended and resumed asynchronously during system sleep transitions. The power.async_suspend flag is also set for devices that don't have suspend or resume callbacks, because otherwise they would make the main suspend/resume thread wait for their "asynchronous" children (during suspend) or parents (during resume), effectively negating the possible gains from executing these devices' suspend and resume callbacks asynchronously. Signed-off-by: Rafael J. Wysocki --- drivers/usb/core/endpoint.c | 1 + drivers/usb/core/hub.c | 1 + drivers/usb/core/message.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c index fdfaa7885515..d26b9ea981f9 100644 --- a/drivers/usb/core/endpoint.c +++ b/drivers/usb/core/endpoint.c @@ -186,6 +186,7 @@ int usb_create_ep_devs(struct device *parent, ep_dev->dev.parent = parent; ep_dev->dev.release = ep_device_release; dev_set_name(&ep_dev->dev, "ep_%02x", endpoint->desc.bEndpointAddress); + device_enable_async_suspend(&ep_dev->dev); retval = device_register(&ep_dev->dev); if (retval) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 35cc8b9ba1f5..20ecb4cec8de 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1817,6 +1817,7 @@ int usb_new_device(struct usb_device *udev) /* Tell the world! */ announce_device(udev); + device_enable_async_suspend(&udev->dev); /* Register the device. The device driver is responsible * for configuring the device and invoking the add-device * notifier chain (used by usbfs and possibly others). diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 9bc95fec793f..df73574a9cc9 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1867,6 +1867,7 @@ free_interfaces: "adding %s (config #%d, interface %d)\n", dev_name(&intf->dev), configuration, intf->cur_altsetting->desc.bInterfaceNumber); + device_enable_async_suspend(&intf->dev); ret = device_add(&intf->dev); if (ret != 0) { dev_err(&dev->dev, "device_add(%s) --> %d\n", -- cgit v1.2.3 From 4cb077d93a57fb8bb64c277ec4ce637823cd76f4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 8 Feb 2010 19:18:26 +0100 Subject: PM: Allow SCSI devices to suspend/resume asynchronously Set power.async_suspend for all SCSI devices, targets and hosts, so that they can be suspended and resumed in parallel with the main suspend/resume thread and possibly with other devices they don't depend on in a known way (i.e. devices which are not their parents or children). The power.async_suspend flag is also set for devices that don't have suspend or resume callbacks, because otherwise they would make the main suspend/resume thread wait for their "asynchronous" children (during suspend) or parents (during resume), effectively negating the possible gains from executing these devices' suspend and resume callbacks asynchronously. Signed-off-by: Rafael J. Wysocki --- drivers/scsi/hosts.c | 4 ++++ drivers/scsi/scsi_sysfs.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 554626e18062..09dbcb847b73 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -215,6 +215,8 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, shost->shost_gendev.parent = dev ? dev : &platform_bus; shost->dma_dev = dma_dev; + device_enable_async_suspend(&shost->shost_gendev); + error = device_add(&shost->shost_gendev); if (error) goto out; @@ -222,6 +224,8 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, scsi_host_set_state(shost, SHOST_RUNNING); get_device(shost->shost_gendev.parent); + device_enable_async_suspend(&shost->shost_dev); + error = device_add(&shost->shost_dev); if (error) goto out_del_gendev; diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 5a065055e68a..c5580805a359 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -847,6 +847,8 @@ static int scsi_target_add(struct scsi_target *starget) if (starget->state != STARGET_CREATED) return 0; + device_enable_async_suspend(&starget->dev); + error = device_add(&starget->dev); if (error) { dev_err(&starget->dev, "target device_add failed, error %d\n", error); @@ -886,11 +888,13 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev) return error; transport_configure_device(&starget->dev); + device_enable_async_suspend(&sdev->sdev_gendev); error = device_add(&sdev->sdev_gendev); if (error) { printk(KERN_INFO "error 1\n"); goto out_remove; } + device_enable_async_suspend(&sdev->sdev_dev); error = device_add(&sdev->sdev_dev); if (error) { printk(KERN_INFO "error 2\n"); -- cgit v1.2.3 From 07c3bb5797d0a9a48524cca17ceb95711b48890b Mon Sep 17 00:00:00 2001 From: Frans Pop Date: Thu, 11 Feb 2010 23:09:08 +0100 Subject: PM / Hibernate: Remove trailing space in message Remove a trailing space from a message in swsusp_save(). Signed-off-by: Frans Pop Acked-by: Pavel Machek Signed-off-by: Rafael J. Wysocki --- kernel/power/snapshot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 36cb168e4330..2eda40741aaa 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -1500,7 +1500,7 @@ asmlinkage int swsusp_save(void) { unsigned int nr_pages, nr_highmem; - printk(KERN_INFO "PM: Creating hibernation image: \n"); + printk(KERN_INFO "PM: Creating hibernation image:\n"); drain_local_pages(NULL); nr_pages = count_data_pages(); -- cgit v1.2.3 From f8bb0db8188c7ef9ef08515883dae9f9eb980984 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Sun, 21 Feb 2010 22:14:44 +0100 Subject: PM / Hibernate: Remove swsusp.c finally Its contents and entry in Makefile were already removed in 8e60c6a1348e17e68ad73589a52a03876e7059be (Shift remaining code from swsusp.c to hibernate.c) but somehow it remained in-place (rjw: which most likely was my mistake). Signed-off-by: Jiri Slaby Acked-by: Nigel Cunningham Signed-off-by: Rafael J. Wysocki --- kernel/power/swsusp.c | 58 --------------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 kernel/power/swsusp.c diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c deleted file mode 100644 index 5b3601bd1893..000000000000 --- a/kernel/power/swsusp.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * linux/kernel/power/swsusp.c - * - * This file provides code to write suspend image to swap and read it back. - * - * Copyright (C) 1998-2001 Gabor Kuti - * Copyright (C) 1998,2001-2005 Pavel Machek - * - * This file is released under the GPLv2. - * - * I'd like to thank the following people for their work: - * - * Pavel Machek : - * Modifications, defectiveness pointing, being with me at the very beginning, - * suspend to swap space, stop all tasks. Port to 2.4.18-ac and 2.5.17. - * - * Steve Doddi : - * Support the possibility of hardware state restoring. - * - * Raph : - * Support for preserving states of network devices and virtual console - * (including X and svgatextmode) - * - * Kurt Garloff : - * Straightened the critical function in order to prevent compilers from - * playing tricks with local variables. - * - * Andreas Mohr - * - * Alex Badea : - * Fixed runaway init - * - * Rafael J. Wysocki - * Reworked the freeing of memory and the handling of swap - * - * More state savers are welcome. Especially for the scsi layer... - * - * For TODOs,FIXMEs also look in Documentation/power/swsusp.txt - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "power.h" - -int in_suspend __nosavedata = 0; -- cgit v1.2.3 From a9c9b4429df437982d2fbfab1f4b46b01329e9ed Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Feb 2010 22:32:37 +0100 Subject: PM / Hibernate: Fix preallocating of memory The hibernate memory preallocation code allocates memory to push some user space data out of physical RAM, so that the hibernation image is not too large. It allocates more memory than necessary for creating the image, so it has to release some pages to make room for allocations made while suspending devices and disabling nonboot CPUs, or the system will hang due to the lack of free pages to allocate from. Unfortunately, the function used for freeing these pages, free_unnecessary_pages(), contains a bug that prevents it from doing the job on all systems without highmem. Fix this problem, which is a regression from the 2.6.30 kernel, by using the right condition for the termination of the loop in free_unnecessary_pages(). Signed-off-by: Rafael J. Wysocki Reported-and-tested-by: Alan Jenkins Cc: stable@kernel.org --- kernel/power/snapshot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 2eda40741aaa..830cadecbdfc 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -1181,7 +1181,7 @@ static void free_unnecessary_pages(void) memory_bm_position_reset(©_bm); - while (to_free_normal > 0 && to_free_highmem > 0) { + while (to_free_normal > 0 || to_free_highmem > 0) { unsigned long pfn = memory_bm_next_pfn(©_bm); struct page *page = pfn_to_page(pfn); -- cgit v1.2.3