Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6:
PM / Hibernate: Fix preallocating of memory
PM / Hibernate: Remove swsusp.c finally
PM / Hibernate: Remove trailing space in message
PM: Allow SCSI devices to suspend/resume asynchronously
PM: Allow USB devices to suspend/resume asynchronously
USB: implement non-tree resume ordering constraints for PCI host controllers
PM: Allow PCI devices to suspend/resume asynchronously
PM / Hibernate: Swap, remove useless check from swsusp_read()
PM / Hibernate: Really deprecate deprecated user ioctls
PM: Allow device drivers to use dpm_wait()
PM: Start asynchronous resume threads upfront
PM: Add facility for advanced testing of async suspend/resume
PM: Add a switch for disabling/enabling asynchronous suspend/resume
PM: Asynchronous suspend and resume of devices
PM: Add parent information to timing messages
PM: Document device power attributes in sysfs
PM / Runtime: Add sysfs switch for disabling device run-time PM
diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power
new file mode 100644
index 0000000..6123c52
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-power
@@ -0,0 +1,79 @@
+What: /sys/devices/.../power/
+Date: January 2009
+Contact: Rafael J. Wysocki <rjw@sisk.pl>
+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 <rjw@sisk.pl>
+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 <rjw@sisk.pl>
+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.
+
+What: /sys/devices/.../power/async
+Date: January 2009
+Contact: Rafael J. Wysocki <rjw@sisk.pl>
+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/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power
index dcff4d0..d6a801f 100644
--- a/Documentation/ABI/testing/sysfs-power
+++ b/Documentation/ABI/testing/sysfs-power
@@ -101,3 +101,16 @@
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 <rjw@sisk.pl>
+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/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt
index 0a46833..b9eba90 100644
--- a/Documentation/feature-removal-schedule.txt
+++ b/Documentation/feature-removal-schedule.txt
@@ -64,6 +64,17 @@
---------------------------
+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 <jirislaby@gmail.com>
+
+---------------------------
+
What: The ieee80211_regdom module parameter
When: March 2010 / desktop catchup
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index a5142bd..0e26a6f 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -25,6 +25,7 @@
#include <linux/resume-trace.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
+#include <linux/async.h>
#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 @@
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 @@
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);
@@ -188,6 +192,31 @@
}
/**
+ * 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 || (pm_async_enabled && 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.
* @ops: PM operations to choose from.
@@ -271,8 +300,9 @@
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();
}
@@ -468,16 +498,20 @@
* 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);
+ dev->power.status = DPM_RESUMING;
+
if (dev->bus) {
if (dev->bus->pm) {
pm_dev_dbg(dev, state, "");
@@ -510,11 +544,29 @@
}
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 bool is_async(struct device *dev)
+{
+ return dev->power.async_suspend && pm_async_enabled
+ && !pm_trace_is_enabled();
+}
+
/**
* dpm_resume - Execute "resume" callbacks for non-sysdev devices.
* @state: PM transition of the system being carried out.
@@ -525,21 +577,33 @@
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);
- while (!list_empty(&dpm_list)) {
- struct device *dev = to_device(dpm_list.next);
+ pm_transition = state;
+ 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, state);
+ error = device_resume(dev, state, false);
mutex_lock(&dpm_list_mtx);
if (error)
@@ -554,6 +618,7 @@
}
list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
+ async_synchronize_full();
dpm_show_time(starttime, state, NULL);
}
@@ -731,17 +796,24 @@
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 ");
@@ -772,12 +844,44 @@
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 (pm_async_enabled && 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.
@@ -790,13 +894,15 @@
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) {
@@ -804,13 +910,17 @@
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;
@@ -936,3 +1046,14 @@
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/drivers/base/power/power.h b/drivers/base/power/power.h
index b8fa1aa..c0bd03c 100644
--- a/drivers/base/power/power.h
+++ b/drivers/base/power/power.h
@@ -12,10 +12,10 @@
#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/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index f8b044e..626dd14 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -1011,6 +1011,50 @@
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 @@
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 596aeec..86fd937 100644
--- a/drivers/base/power/sysfs.c
+++ b/drivers/base/power/sysfs.c
@@ -4,9 +4,25 @@
#include <linux/device.h>
#include <linux/string.h>
+#include <linux/pm_runtime.h>
#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
@@ -38,11 +54,61 @@
* 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";
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)
{
@@ -77,9 +143,43 @@
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/drivers/pci/pci.c b/drivers/pci/pci.c
index f4a2738..2b9ac9e 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1540,6 +1540,7 @@
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 0d34ff4..e73effb 100644
--- a/drivers/pci/pcie/portdrv_core.c
+++ b/drivers/pci/pcie/portdrv_core.c
@@ -285,6 +285,7 @@
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 270d069..2a94309 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1436,6 +1436,7 @@
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));
diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index 554626e..09dbcb8 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -215,6 +215,8 @@
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 @@
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 a4936c4..19ec9e2 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -847,6 +847,8 @@
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);
@@ -887,11 +889,13 @@
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");
return error;
}
+ device_enable_async_suspend(&sdev->sdev_dev);
error = device_add(&sdev->sdev_dev);
if (error) {
printk(KERN_INFO "error 2\n");
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 60a45f1..f2f055e 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1022,6 +1022,14 @@
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/endpoint.c b/drivers/usb/core/endpoint.c
index fdfaa78..d26b9ea 100644
--- a/drivers/usb/core/endpoint.c
+++ b/drivers/usb/core/endpoint.c
@@ -186,6 +186,7 @@
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/hcd-pci.c b/drivers/usb/core/hcd-pci.c
index 2dcf906..1528653 100644
--- a/drivers/usb/core/hcd-pci.c
+++ b/drivers/usb/core/hcd-pci.c
@@ -19,6 +19,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/pm_runtime.h>
#include <linux/usb.h>
#include <asm/io.h>
@@ -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 @@
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 @@
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 @@
} 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 @@
} 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 @@
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/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 35cc8b9..20ecb4c 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -1817,6 +1817,7 @@
/* 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 9bc95fe..df73574 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1867,6 +1867,7 @@
"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",
diff --git a/include/linux/device.h b/include/linux/device.h
index a62799f..b30527d 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -472,6 +472,23 @@
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;
+}
+
+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/include/linux/pm.h b/include/linux/pm.h
index 198b8f9..e80df06 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -26,6 +26,7 @@
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/timer.h>
+#include <linux/completion.h>
/*
* Callbacks for platform drivers to implement.
@@ -412,9 +413,11 @@
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;
@@ -430,6 +433,7 @@
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;
@@ -508,6 +512,7 @@
__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)
@@ -520,6 +525,7 @@
#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() */
diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h
index 370ce0a..7d773aa 100644
--- a/include/linux/pm_runtime.h
+++ b/include/linux/pm_runtime.h
@@ -28,6 +28,8 @@
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_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) {}
diff --git a/include/linux/resume-trace.h b/include/linux/resume-trace.h
index c9ba2fd..bc8c388 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 @@
#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)
diff --git a/include/linux/usb.h b/include/linux/usb.h
index d7ace1b..332eaea 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -339,6 +339,7 @@
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
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 4c9cffc..5c36ea9 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -27,6 +27,15 @@
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 @@
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
diff --git a/kernel/power/main.c b/kernel/power/main.c
index 0998c71..b58800b 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -44,6 +44,32 @@
== 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,9 +234,12 @@
#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,
};
diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c
index 36cb168..830cade 100644
--- a/kernel/power/snapshot.c
+++ b/kernel/power/snapshot.c
@@ -1181,7 +1181,7 @@
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);
@@ -1500,7 +1500,7 @@
{
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();
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index 09b2b0a..1d57573 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -657,10 +657,6 @@
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);
diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c
deleted file mode 100644
index 5b3601b..0000000
--- 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 <seasons@fornax.hu>
- * Copyright (C) 1998,2001-2005 Pavel Machek <pavel@suse.cz>
- *
- * This file is released under the GPLv2.
- *
- * I'd like to thank the following people for their work:
- *
- * Pavel Machek <pavel@ucw.cz>:
- * 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 <dirk@loth.demon.co.uk>:
- * Support the possibility of hardware state restoring.
- *
- * Raph <grey.havens@earthling.net>:
- * Support for preserving states of network devices and virtual console
- * (including X and svgatextmode)
- *
- * Kurt Garloff <garloff@suse.de>:
- * Straightened the critical function in order to prevent compilers from
- * playing tricks with local variables.
- *
- * Andreas Mohr <a.mohr@mailto.de>
- *
- * Alex Badea <vampire@go.ro>:
- * Fixed runaway init
- *
- * Rafael J. Wysocki <rjw@sisk.pl>
- * 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 <linux/mm.h>
-#include <linux/suspend.h>
-#include <linux/spinlock.h>
-#include <linux/kernel.h>
-#include <linux/major.h>
-#include <linux/swap.h>
-#include <linux/pm.h>
-#include <linux/swapops.h>
-#include <linux/bootmem.h>
-#include <linux/syscalls.h>
-#include <linux/highmem.h>
-#include <linux/time.h>
-#include <linux/rbtree.h>
-#include <linux/io.h>
-
-#include "power.h"
-
-int in_suspend __nosavedata = 0;
diff --git a/kernel/power/user.c b/kernel/power/user.c
index bf0014d..4d22896 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -195,6 +195,15 @@
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 @@
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 @@
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 @@
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 @@
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 @@
break;
case SNAPSHOT_PMOPS: /* This ioctl is deprecated */
+ snapshot_deprecated_ioctl(cmd);
error = -EINVAL;
switch (arg) {