diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index bfcb1a62a7b4..8b1fa5e129ac 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -3462,6 +3462,13 @@ bytes respectively. Such letter suffixes can also be entirely omitted. improve throughput, but will also increase the amount of memory reserved for use by the client. + suspend.pm_test_delay= + [SUSPEND] + Sets the number of seconds to remain in a suspend test + mode before resuming the system (see + /sys/power/pm_test). Only available when CONFIG_PM_DEBUG + is set. Default value is 5. + swapaccount=[0|1] [KNL] Enable accounting of swap in memory resource controller if no parameter or 1 is given or disable diff --git a/Documentation/power/basic-pm-debugging.txt b/Documentation/power/basic-pm-debugging.txt index edeecd447d23..b96098ccfe69 100644 --- a/Documentation/power/basic-pm-debugging.txt +++ b/Documentation/power/basic-pm-debugging.txt @@ -75,12 +75,14 @@ you should do the following: # echo platform > /sys/power/disk # echo disk > /sys/power/state -Then, the kernel will try to freeze processes, suspend devices, wait 5 seconds, -resume devices and thaw processes. If "platform" is written to +Then, the kernel will try to freeze processes, suspend devices, wait a few +seconds (5 by default, but configurable by the suspend.pm_test_delay module +parameter), resume devices and thaw processes. If "platform" is written to /sys/power/pm_test , then after suspending devices the kernel will additionally invoke the global control methods (eg. ACPI global control methods) used to -prepare the platform firmware for hibernation. Next, it will wait 5 seconds and -invoke the platform (eg. ACPI) global methods used to cancel hibernation etc. +prepare the platform firmware for hibernation. Next, it will wait a +configurable number of seconds and invoke the platform (eg. ACPI) global +methods used to cancel hibernation etc. Writing "none" to /sys/power/pm_test causes the kernel to switch to the normal hibernation/suspend operations. Also, when open for reading, /sys/power/pm_test diff --git a/arch/x86/include/asm/resume-trace.h b/arch/x86/include/asm/pm-trace.h similarity index 68% rename from arch/x86/include/asm/resume-trace.h rename to arch/x86/include/asm/pm-trace.h index 3ff1c2cb1da5..7b7ac42c3661 100644 --- a/arch/x86/include/asm/resume-trace.h +++ b/arch/x86/include/asm/pm-trace.h @@ -1,5 +1,5 @@ -#ifndef _ASM_X86_RESUME_TRACE_H -#define _ASM_X86_RESUME_TRACE_H +#ifndef _ASM_X86_PM_TRACE_H +#define _ASM_X86_PM_TRACE_H #include @@ -14,8 +14,10 @@ do { \ ".previous" \ :"=r" (tracedata) \ : "i" (__LINE__), "i" (__FILE__)); \ - generate_resume_trace(tracedata, user); \ + generate_pm_trace(tracedata, user); \ } \ } while (0) -#endif /* _ASM_X86_RESUME_TRACE_H */ +#define TRACE_SUSPEND(user) TRACE_RESUME(user) + +#endif /* _ASM_X86_PM_TRACE_H */ diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 9717d5f20139..3d874eca7104 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -1017,6 +1017,9 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a char *info = NULL; int error = 0; + TRACE_DEVICE(dev); + TRACE_SUSPEND(0); + if (async_error) goto Complete; @@ -1057,6 +1060,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a Complete: complete_all(&dev->power.completion); + TRACE_SUSPEND(error); return error; } @@ -1078,7 +1082,7 @@ static int device_suspend_noirq(struct device *dev) { reinit_completion(&dev->power.completion); - if (pm_async_enabled && dev->power.async_suspend) { + if (is_async(dev)) { get_device(dev); async_schedule(async_suspend_noirq, dev); return 0; @@ -1157,6 +1161,9 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as char *info = NULL; int error = 0; + TRACE_DEVICE(dev); + TRACE_SUSPEND(0); + __pm_runtime_disable(dev, false); if (async_error) @@ -1198,6 +1205,7 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as async_error = error; Complete: + TRACE_SUSPEND(error); complete_all(&dev->power.completion); return error; } @@ -1219,7 +1227,7 @@ static int device_suspend_late(struct device *dev) { reinit_completion(&dev->power.completion); - if (pm_async_enabled && dev->power.async_suspend) { + if (is_async(dev)) { get_device(dev); async_schedule(async_suspend_late, dev); return 0; @@ -1338,6 +1346,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) int error = 0; DECLARE_DPM_WATCHDOG_ON_STACK(wd); + TRACE_DEVICE(dev); + TRACE_SUSPEND(0); + dpm_wait_for_children(dev, async); if (async_error) @@ -1444,6 +1455,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) if (error) async_error = error; + TRACE_SUSPEND(error); return error; } @@ -1465,7 +1477,7 @@ static int device_suspend(struct device *dev) { reinit_completion(&dev->power.completion); - if (pm_async_enabled && dev->power.async_suspend) { + if (is_async(dev)) { get_device(dev); async_schedule(async_suspend, dev); return 0; diff --git a/drivers/base/power/trace.c b/drivers/base/power/trace.c index d94a1f5121cf..a311cfa4c5bd 100644 --- a/drivers/base/power/trace.c +++ b/drivers/base/power/trace.c @@ -7,7 +7,7 @@ * devices may be working. */ -#include +#include #include #include @@ -154,7 +154,7 @@ EXPORT_SYMBOL(set_trace_device); * it's not any guarantee, but it's a high _likelihood_ that * the match is valid). */ -void generate_resume_trace(const void *tracedata, unsigned int user) +void generate_pm_trace(const void *tracedata, unsigned int user) { unsigned short lineno = *(unsigned short *)tracedata; const char *file = *(const char **)(tracedata + 2); @@ -164,7 +164,7 @@ void generate_resume_trace(const void *tracedata, unsigned int user) file_hash_value = hash_string(lineno, file, FILEHASH); set_magic_time(user_hash_value, file_hash_value, dev_hash_value); } -EXPORT_SYMBOL(generate_resume_trace); +EXPORT_SYMBOL(generate_pm_trace); extern char __tracedata_start, __tracedata_end; static int show_file_hash(unsigned int value) diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index 05ee0bf88ce9..3c3fd417ddeb 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -51,6 +51,7 @@ #define DRV_VERSION "1.11" /* Includes */ +#include /* For ACPI support */ #include /* For module specific items */ #include /* For new moduleparam's */ #include /* For standard types (like size_t) */ @@ -103,6 +104,8 @@ static struct { /* this is private data for the iTCO_wdt device */ struct platform_device *dev; /* the PCI-device */ struct pci_dev *pdev; + /* whether or not the watchdog has been suspended */ + bool suspended; } iTCO_wdt_private; /* module parameters */ @@ -571,12 +574,60 @@ static void iTCO_wdt_shutdown(struct platform_device *dev) iTCO_wdt_stop(NULL); } +#ifdef CONFIG_PM_SLEEP +/* + * Suspend-to-idle requires this, because it stops the ticks and timekeeping, so + * the watchdog cannot be pinged while in that state. In ACPI sleep states the + * watchdog is stopped by the platform firmware. + */ + +#ifdef CONFIG_ACPI +static inline bool need_suspend(void) +{ + return acpi_target_system_state() == ACPI_STATE_S0; +} +#else +static inline bool need_suspend(void) { return true; } +#endif + +static int iTCO_wdt_suspend_noirq(struct device *dev) +{ + int ret = 0; + + iTCO_wdt_private.suspended = false; + if (watchdog_active(&iTCO_wdt_watchdog_dev) && need_suspend()) { + ret = iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + if (!ret) + iTCO_wdt_private.suspended = true; + } + return ret; +} + +static int iTCO_wdt_resume_noirq(struct device *dev) +{ + if (iTCO_wdt_private.suspended) + iTCO_wdt_start(&iTCO_wdt_watchdog_dev); + + return 0; +} + +static struct dev_pm_ops iTCO_wdt_pm = { + .suspend_noirq = iTCO_wdt_suspend_noirq, + .resume_noirq = iTCO_wdt_resume_noirq, +}; + +#define ITCO_WDT_PM_OPS (&iTCO_wdt_pm) +#else +#define ITCO_WDT_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + static struct platform_driver iTCO_wdt_driver = { .probe = iTCO_wdt_probe, .remove = iTCO_wdt_remove, .shutdown = iTCO_wdt_shutdown, .driver = { .name = DRV_NAME, + .pm = ITCO_WDT_PM_OPS, }, }; diff --git a/include/linux/resume-trace.h b/include/linux/pm-trace.h similarity index 75% rename from include/linux/resume-trace.h rename to include/linux/pm-trace.h index f31db2368782..ecbde7a5548e 100644 --- a/include/linux/resume-trace.h +++ b/include/linux/pm-trace.h @@ -1,8 +1,8 @@ -#ifndef RESUME_TRACE_H -#define RESUME_TRACE_H +#ifndef PM_TRACE_H +#define PM_TRACE_H #ifdef CONFIG_PM_TRACE -#include +#include #include extern int pm_trace_enabled; @@ -14,7 +14,7 @@ static inline int pm_trace_is_enabled(void) struct device; extern void set_trace_device(struct device *); -extern void generate_resume_trace(const void *tracedata, unsigned int user); +extern void generate_pm_trace(const void *tracedata, unsigned int user); extern int show_trace_dev_match(char *buf, size_t size); #define TRACE_DEVICE(dev) do { \ @@ -28,6 +28,7 @@ static inline int pm_trace_is_enabled(void) { return 0; } #define TRACE_DEVICE(dev) do { } while (0) #define TRACE_RESUME(dev) do { } while (0) +#define TRACE_SUSPEND(dev) do { } while (0) #endif diff --git a/kernel/power/main.c b/kernel/power/main.c index 9a59d042ea84..86e8157a450f 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index b7d6b3a721b1..8d7a1ef72758 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "power.h" @@ -233,12 +234,20 @@ static bool platform_suspend_again(suspend_state_t state) suspend_ops->suspend_again() : false; } +#ifdef CONFIG_PM_DEBUG +static unsigned int pm_test_delay = 5; +module_param(pm_test_delay, uint, 0644); +MODULE_PARM_DESC(pm_test_delay, + "Number of seconds to wait before resuming from suspend test"); +#endif + static int suspend_test(int level) { #ifdef CONFIG_PM_DEBUG if (pm_test_level == level) { - printk(KERN_INFO "suspend debug: Waiting for 5 seconds.\n"); - mdelay(5000); + printk(KERN_INFO "suspend debug: Waiting for %d second(s).\n", + pm_test_delay); + mdelay(pm_test_delay * 1000); return 1; } #endif /* !CONFIG_PM_DEBUG */