浏览代码

Merge back earlier suspend/hibernate material for v4.1.

Rafael J. Wysocki 10 年之前
父节点
当前提交
be77002101

+ 7 - 0
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
 			improve throughput, but will also increase the
 			amount of memory reserved for use by the client.
 			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]
 	swapaccount=[0|1]
 			[KNL] Enable accounting of swap in memory resource
 			[KNL] Enable accounting of swap in memory resource
 			controller if no parameter or 1 is given or disable
 			controller if no parameter or 1 is given or disable

+ 6 - 4
Documentation/power/basic-pm-debugging.txt

@@ -75,12 +75,14 @@ you should do the following:
 # echo platform > /sys/power/disk
 # echo platform > /sys/power/disk
 # echo disk > /sys/power/state
 # 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
 /sys/power/pm_test , then after suspending devices the kernel will additionally
 invoke the global control methods (eg. ACPI global control methods) used to
 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
 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
 hibernation/suspend operations.  Also, when open for reading, /sys/power/pm_test

+ 6 - 4
arch/x86/include/asm/resume-trace.h → 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 <asm/asm.h>
 #include <asm/asm.h>
 
 
@@ -14,8 +14,10 @@ do {								\
 			     ".previous"			\
 			     ".previous"			\
 			     :"=r" (tracedata)			\
 			     :"=r" (tracedata)			\
 			     : "i" (__LINE__), "i" (__FILE__));	\
 			     : "i" (__LINE__), "i" (__FILE__));	\
-		generate_resume_trace(tracedata, user);		\
+		generate_pm_trace(tracedata, user);		\
 	}							\
 	}							\
 } while (0)
 } while (0)
 
 
-#endif /* _ASM_X86_RESUME_TRACE_H */
+#define TRACE_SUSPEND(user)	TRACE_RESUME(user)
+
+#endif /* _ASM_X86_PM_TRACE_H */

+ 16 - 4
drivers/base/power/main.c

@@ -23,7 +23,7 @@
 #include <linux/mutex.h>
 #include <linux/mutex.h>
 #include <linux/pm.h>
 #include <linux/pm.h>
 #include <linux/pm_runtime.h>
 #include <linux/pm_runtime.h>
-#include <linux/resume-trace.h>
+#include <linux/pm-trace.h>
 #include <linux/interrupt.h>
 #include <linux/interrupt.h>
 #include <linux/sched.h>
 #include <linux/sched.h>
 #include <linux/async.h>
 #include <linux/async.h>
@@ -1017,6 +1017,9 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
 	char *info = NULL;
 	char *info = NULL;
 	int error = 0;
 	int error = 0;
 
 
+	TRACE_DEVICE(dev);
+	TRACE_SUSPEND(0);
+
 	if (async_error)
 	if (async_error)
 		goto Complete;
 		goto Complete;
 
 
@@ -1057,6 +1060,7 @@ static int __device_suspend_noirq(struct device *dev, pm_message_t state, bool a
 
 
 Complete:
 Complete:
 	complete_all(&dev->power.completion);
 	complete_all(&dev->power.completion);
+	TRACE_SUSPEND(error);
 	return error;
 	return error;
 }
 }
 
 
@@ -1078,7 +1082,7 @@ static int device_suspend_noirq(struct device *dev)
 {
 {
 	reinit_completion(&dev->power.completion);
 	reinit_completion(&dev->power.completion);
 
 
-	if (pm_async_enabled && dev->power.async_suspend) {
+	if (is_async(dev)) {
 		get_device(dev);
 		get_device(dev);
 		async_schedule(async_suspend_noirq, dev);
 		async_schedule(async_suspend_noirq, dev);
 		return 0;
 		return 0;
@@ -1157,6 +1161,9 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
 	char *info = NULL;
 	char *info = NULL;
 	int error = 0;
 	int error = 0;
 
 
+	TRACE_DEVICE(dev);
+	TRACE_SUSPEND(0);
+
 	__pm_runtime_disable(dev, false);
 	__pm_runtime_disable(dev, false);
 
 
 	if (async_error)
 	if (async_error)
@@ -1198,6 +1205,7 @@ static int __device_suspend_late(struct device *dev, pm_message_t state, bool as
 		async_error = error;
 		async_error = error;
 
 
 Complete:
 Complete:
+	TRACE_SUSPEND(error);
 	complete_all(&dev->power.completion);
 	complete_all(&dev->power.completion);
 	return error;
 	return error;
 }
 }
@@ -1219,7 +1227,7 @@ static int device_suspend_late(struct device *dev)
 {
 {
 	reinit_completion(&dev->power.completion);
 	reinit_completion(&dev->power.completion);
 
 
-	if (pm_async_enabled && dev->power.async_suspend) {
+	if (is_async(dev)) {
 		get_device(dev);
 		get_device(dev);
 		async_schedule(async_suspend_late, dev);
 		async_schedule(async_suspend_late, dev);
 		return 0;
 		return 0;
@@ -1338,6 +1346,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
 	int error = 0;
 	int error = 0;
 	DECLARE_DPM_WATCHDOG_ON_STACK(wd);
 	DECLARE_DPM_WATCHDOG_ON_STACK(wd);
 
 
+	TRACE_DEVICE(dev);
+	TRACE_SUSPEND(0);
+
 	dpm_wait_for_children(dev, async);
 	dpm_wait_for_children(dev, async);
 
 
 	if (async_error)
 	if (async_error)
@@ -1444,6 +1455,7 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async)
 	if (error)
 	if (error)
 		async_error = error;
 		async_error = error;
 
 
+	TRACE_SUSPEND(error);
 	return error;
 	return error;
 }
 }
 
 
@@ -1465,7 +1477,7 @@ static int device_suspend(struct device *dev)
 {
 {
 	reinit_completion(&dev->power.completion);
 	reinit_completion(&dev->power.completion);
 
 
-	if (pm_async_enabled && dev->power.async_suspend) {
+	if (is_async(dev)) {
 		get_device(dev);
 		get_device(dev);
 		async_schedule(async_suspend, dev);
 		async_schedule(async_suspend, dev);
 		return 0;
 		return 0;

+ 3 - 3
drivers/base/power/trace.c

@@ -7,7 +7,7 @@
  * devices may be working.
  * devices may be working.
  */
  */
 
 
-#include <linux/resume-trace.h>
+#include <linux/pm-trace.h>
 #include <linux/export.h>
 #include <linux/export.h>
 #include <linux/rtc.h>
 #include <linux/rtc.h>
 
 
@@ -154,7 +154,7 @@ EXPORT_SYMBOL(set_trace_device);
  * it's not any guarantee, but it's a high _likelihood_ that
  * it's not any guarantee, but it's a high _likelihood_ that
  * the match is valid).
  * 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;
 	unsigned short lineno = *(unsigned short *)tracedata;
 	const char *file = *(const char **)(tracedata + 2);
 	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);
 	file_hash_value = hash_string(lineno, file, FILEHASH);
 	set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
 	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;
 extern char __tracedata_start, __tracedata_end;
 static int show_file_hash(unsigned int value)
 static int show_file_hash(unsigned int value)

+ 51 - 0
drivers/watchdog/iTCO_wdt.c

@@ -51,6 +51,7 @@
 #define DRV_VERSION	"1.11"
 #define DRV_VERSION	"1.11"
 
 
 /* Includes */
 /* Includes */
+#include <linux/acpi.h>			/* For ACPI support */
 #include <linux/module.h>		/* For module specific items */
 #include <linux/module.h>		/* For module specific items */
 #include <linux/moduleparam.h>		/* For new moduleparam's */
 #include <linux/moduleparam.h>		/* For new moduleparam's */
 #include <linux/types.h>		/* For standard types (like size_t) */
 #include <linux/types.h>		/* 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;
 	struct platform_device *dev;
 	/* the PCI-device */
 	/* the PCI-device */
 	struct pci_dev *pdev;
 	struct pci_dev *pdev;
+	/* whether or not the watchdog has been suspended */
+	bool suspended;
 } iTCO_wdt_private;
 } iTCO_wdt_private;
 
 
 /* module parameters */
 /* module parameters */
@@ -571,12 +574,60 @@ static void iTCO_wdt_shutdown(struct platform_device *dev)
 	iTCO_wdt_stop(NULL);
 	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 = {
 static struct platform_driver iTCO_wdt_driver = {
 	.probe          = iTCO_wdt_probe,
 	.probe          = iTCO_wdt_probe,
 	.remove         = iTCO_wdt_remove,
 	.remove         = iTCO_wdt_remove,
 	.shutdown       = iTCO_wdt_shutdown,
 	.shutdown       = iTCO_wdt_shutdown,
 	.driver         = {
 	.driver         = {
 		.name   = DRV_NAME,
 		.name   = DRV_NAME,
+		.pm     = ITCO_WDT_PM_OPS,
 	},
 	},
 };
 };
 
 

+ 5 - 4
include/linux/resume-trace.h → 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
 #ifdef CONFIG_PM_TRACE
-#include <asm/resume-trace.h>
+#include <asm/pm-trace.h>
 #include <linux/types.h>
 #include <linux/types.h>
 
 
 extern int pm_trace_enabled;
 extern int pm_trace_enabled;
@@ -14,7 +14,7 @@ static inline int pm_trace_is_enabled(void)
 
 
 struct device;
 struct device;
 extern void set_trace_device(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);
 extern int show_trace_dev_match(char *buf, size_t size);
 
 
 #define TRACE_DEVICE(dev) do { \
 #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_DEVICE(dev) do { } while (0)
 #define TRACE_RESUME(dev) do { } while (0)
 #define TRACE_RESUME(dev) do { } while (0)
+#define TRACE_SUSPEND(dev) do { } while (0)
 
 
 #endif
 #endif
 
 

+ 1 - 1
kernel/power/main.c

@@ -11,7 +11,7 @@
 #include <linux/export.h>
 #include <linux/export.h>
 #include <linux/kobject.h>
 #include <linux/kobject.h>
 #include <linux/string.h>
 #include <linux/string.h>
-#include <linux/resume-trace.h>
+#include <linux/pm-trace.h>
 #include <linux/workqueue.h>
 #include <linux/workqueue.h>
 #include <linux/debugfs.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #include <linux/seq_file.h>

+ 11 - 2
kernel/power/suspend.c

@@ -28,6 +28,7 @@
 #include <linux/ftrace.h>
 #include <linux/ftrace.h>
 #include <trace/events/power.h>
 #include <trace/events/power.h>
 #include <linux/compiler.h>
 #include <linux/compiler.h>
+#include <linux/moduleparam.h>
 
 
 #include "power.h"
 #include "power.h"
 
 
@@ -233,12 +234,20 @@ static bool platform_suspend_again(suspend_state_t state)
 		suspend_ops->suspend_again() : false;
 		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)
 static int suspend_test(int level)
 {
 {
 #ifdef CONFIG_PM_DEBUG
 #ifdef CONFIG_PM_DEBUG
 	if (pm_test_level == level) {
 	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;
 		return 1;
 	}
 	}
 #endif /* !CONFIG_PM_DEBUG */
 #endif /* !CONFIG_PM_DEBUG */