diff options
author | Rafael J. Wysocki <rjw@sisk.pl> | 2010-07-05 16:43:53 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rjw@sisk.pl> | 2010-07-18 19:58:48 -0400 |
commit | c125e96f044427f38d106fab7bc5e4a5e6a18262 (patch) | |
tree | d9bbd40cc933fe522dbdf8ca2f7edf7b6f2f7ca4 /drivers/pci | |
parent | b14e033e17d0ea0ba12668d0d2f371cd31586994 (diff) |
PM: Make it possible to avoid races between wakeup and system sleep
One of the arguments during the suspend blockers discussion was that
the mainline kernel didn't contain any mechanisms making it possible
to avoid races between wakeup and system suspend.
Generally, there are two problems in that area. First, if a wakeup
event occurs exactly when /sys/power/state is being written to, it
may be delivered to user space right before the freezer kicks in, so
the user space consumer of the event may not be able to process it
before the system is suspended. Second, if a wakeup event occurs
after user space has been frozen, it is not generally guaranteed that
the ongoing transition of the system into a sleep state will be
aborted.
To address these issues introduce a new global sysfs attribute,
/sys/power/wakeup_count, associated with a running counter of wakeup
events and three helper functions, pm_stay_awake(), pm_relax(), and
pm_wakeup_event(), that may be used by kernel subsystems to control
the behavior of this attribute and to request the PM core to abort
system transitions into a sleep state already in progress.
The /sys/power/wakeup_count file may be read from or written to by
user space. Reads will always succeed (unless interrupted by a
signal) and return the current value of the wakeup events counter.
Writes, however, will only succeed if the written number is equal to
the current value of the wakeup events counter. If a write is
successful, it will cause the kernel to save the current value of the
wakeup events counter and to abort the subsequent system transition
into a sleep state if any wakeup events are reported after the write
has returned.
[The assumption is that before writing to /sys/power/state user space
will first read from /sys/power/wakeup_count. Next, user space
consumers of wakeup events will have a chance to acknowledge or
veto the upcoming system transition to a sleep state. Finally, if
the transition is allowed to proceed, /sys/power/wakeup_count will
be written to and if that succeeds, /sys/power/state will be written
to as well. Still, if any wakeup events are reported to the PM core
by kernel subsystems after that point, the transition will be
aborted.]
Additionally, put a wakeup events counter into struct dev_pm_info and
make these per-device wakeup event counters available via sysfs,
so that it's possible to check the activity of various wakeup event
sources within the kernel.
To illustrate how subsystems can use pm_wakeup_event(), make the
low-level PCI runtime PM wakeup-handling code use it.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Acked-by: markgross <markgross@thegnar.org>
Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/pci-acpi.c | 1 | ||||
-rw-r--r-- | drivers/pci/pci.c | 20 | ||||
-rw-r--r-- | drivers/pci/pci.h | 1 | ||||
-rw-r--r-- | drivers/pci/pcie/pme/pcie_pme.c | 5 |
4 files changed, 25 insertions, 2 deletions
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 2e7a3bf13824..1ab98bbe58dd 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c | |||
@@ -48,6 +48,7 @@ static void pci_acpi_wake_dev(acpi_handle handle, u32 event, void *context) | |||
48 | if (event == ACPI_NOTIFY_DEVICE_WAKE && pci_dev) { | 48 | if (event == ACPI_NOTIFY_DEVICE_WAKE && pci_dev) { |
49 | pci_check_pme_status(pci_dev); | 49 | pci_check_pme_status(pci_dev); |
50 | pm_runtime_resume(&pci_dev->dev); | 50 | pm_runtime_resume(&pci_dev->dev); |
51 | pci_wakeup_event(pci_dev); | ||
51 | if (pci_dev->subordinate) | 52 | if (pci_dev->subordinate) |
52 | pci_pme_wakeup_bus(pci_dev->subordinate); | 53 | pci_pme_wakeup_bus(pci_dev->subordinate); |
53 | } | 54 | } |
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 740fb4ea9669..130ed1daf0f8 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c | |||
@@ -1275,6 +1275,22 @@ bool pci_check_pme_status(struct pci_dev *dev) | |||
1275 | return ret; | 1275 | return ret; |
1276 | } | 1276 | } |
1277 | 1277 | ||
1278 | /* | ||
1279 | * Time to wait before the system can be put into a sleep state after reporting | ||
1280 | * a wakeup event signaled by a PCI device. | ||
1281 | */ | ||
1282 | #define PCI_WAKEUP_COOLDOWN 100 | ||
1283 | |||
1284 | /** | ||
1285 | * pci_wakeup_event - Report a wakeup event related to a given PCI device. | ||
1286 | * @dev: Device to report the wakeup event for. | ||
1287 | */ | ||
1288 | void pci_wakeup_event(struct pci_dev *dev) | ||
1289 | { | ||
1290 | if (device_may_wakeup(&dev->dev)) | ||
1291 | pm_wakeup_event(&dev->dev, PCI_WAKEUP_COOLDOWN); | ||
1292 | } | ||
1293 | |||
1278 | /** | 1294 | /** |
1279 | * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set. | 1295 | * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set. |
1280 | * @dev: Device to handle. | 1296 | * @dev: Device to handle. |
@@ -1285,8 +1301,10 @@ bool pci_check_pme_status(struct pci_dev *dev) | |||
1285 | */ | 1301 | */ |
1286 | static int pci_pme_wakeup(struct pci_dev *dev, void *ign) | 1302 | static int pci_pme_wakeup(struct pci_dev *dev, void *ign) |
1287 | { | 1303 | { |
1288 | if (pci_check_pme_status(dev)) | 1304 | if (pci_check_pme_status(dev)) { |
1289 | pm_request_resume(&dev->dev); | 1305 | pm_request_resume(&dev->dev); |
1306 | pci_wakeup_event(dev); | ||
1307 | } | ||
1290 | return 0; | 1308 | return 0; |
1291 | } | 1309 | } |
1292 | 1310 | ||
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index f8077b3c8c8c..c8b7fd056ccd 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h | |||
@@ -56,6 +56,7 @@ extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state); | |||
56 | extern void pci_disable_enabled_device(struct pci_dev *dev); | 56 | extern void pci_disable_enabled_device(struct pci_dev *dev); |
57 | extern bool pci_check_pme_status(struct pci_dev *dev); | 57 | extern bool pci_check_pme_status(struct pci_dev *dev); |
58 | extern int pci_finish_runtime_suspend(struct pci_dev *dev); | 58 | extern int pci_finish_runtime_suspend(struct pci_dev *dev); |
59 | extern void pci_wakeup_event(struct pci_dev *dev); | ||
59 | extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign); | 60 | extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign); |
60 | extern void pci_pme_wakeup_bus(struct pci_bus *bus); | 61 | extern void pci_pme_wakeup_bus(struct pci_bus *bus); |
61 | extern void pci_pm_init(struct pci_dev *dev); | 62 | extern void pci_pm_init(struct pci_dev *dev); |
diff --git a/drivers/pci/pcie/pme/pcie_pme.c b/drivers/pci/pcie/pme/pcie_pme.c index d672a0a63816..bbdea18693d9 100644 --- a/drivers/pci/pcie/pme/pcie_pme.c +++ b/drivers/pci/pcie/pme/pcie_pme.c | |||
@@ -154,6 +154,7 @@ static bool pcie_pme_walk_bus(struct pci_bus *bus) | |||
154 | /* Skip PCIe devices in case we started from a root port. */ | 154 | /* Skip PCIe devices in case we started from a root port. */ |
155 | if (!pci_is_pcie(dev) && pci_check_pme_status(dev)) { | 155 | if (!pci_is_pcie(dev) && pci_check_pme_status(dev)) { |
156 | pm_request_resume(&dev->dev); | 156 | pm_request_resume(&dev->dev); |
157 | pci_wakeup_event(dev); | ||
157 | ret = true; | 158 | ret = true; |
158 | } | 159 | } |
159 | 160 | ||
@@ -254,8 +255,10 @@ static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id) | |||
254 | if (found) { | 255 | if (found) { |
255 | /* The device is there, but we have to check its PME status. */ | 256 | /* The device is there, but we have to check its PME status. */ |
256 | found = pci_check_pme_status(dev); | 257 | found = pci_check_pme_status(dev); |
257 | if (found) | 258 | if (found) { |
258 | pm_request_resume(&dev->dev); | 259 | pm_request_resume(&dev->dev); |
260 | pci_wakeup_event(dev); | ||
261 | } | ||
259 | pci_dev_put(dev); | 262 | pci_dev_put(dev); |
260 | } else if (devfn) { | 263 | } else if (devfn) { |
261 | /* | 264 | /* |