|
@@ -132,6 +132,61 @@ static inline int suspend_test(int level) { return 0; }
|
|
|
|
|
|
#ifdef CONFIG_SUSPEND
|
|
#ifdef CONFIG_SUSPEND
|
|
|
|
|
|
|
|
+#ifdef CONFIG_PM_TEST_SUSPEND
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * We test the system suspend code by setting an RTC wakealarm a short
|
|
|
|
+ * time in the future, then suspending. Suspending the devices won't
|
|
|
|
+ * normally take long ... some systems only need a few milliseconds.
|
|
|
|
+ *
|
|
|
|
+ * The time it takes is system-specific though, so when we test this
|
|
|
|
+ * during system bootup we allow a LOT of time.
|
|
|
|
+ */
|
|
|
|
+#define TEST_SUSPEND_SECONDS 5
|
|
|
|
+
|
|
|
|
+static unsigned long suspend_test_start_time;
|
|
|
|
+
|
|
|
|
+static void suspend_test_start(void)
|
|
|
|
+{
|
|
|
|
+ /* FIXME Use better timebase than "jiffies", ideally a clocksource.
|
|
|
|
+ * What we want is a hardware counter that will work correctly even
|
|
|
|
+ * during the irqs-are-off stages of the suspend/resume cycle...
|
|
|
|
+ */
|
|
|
|
+ suspend_test_start_time = jiffies;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void suspend_test_finish(const char *label)
|
|
|
|
+{
|
|
|
|
+ long nj = jiffies - suspend_test_start_time;
|
|
|
|
+ unsigned msec;
|
|
|
|
+
|
|
|
|
+ msec = jiffies_to_msecs(abs(nj));
|
|
|
|
+ pr_info("PM: %s took %d.%03d seconds\n", label,
|
|
|
|
+ msec / 1000, msec % 1000);
|
|
|
|
+
|
|
|
|
+ /* Warning on suspend means the RTC alarm period needs to be
|
|
|
|
+ * larger -- the system was sooo slooowwww to suspend that the
|
|
|
|
+ * alarm (should have) fired before the system went to sleep!
|
|
|
|
+ *
|
|
|
|
+ * Warning on either suspend or resume also means the system
|
|
|
|
+ * has some performance issues. The stack dump of a WARN_ON
|
|
|
|
+ * is more likely to get the right attention than a printk...
|
|
|
|
+ */
|
|
|
|
+ WARN_ON(msec > (TEST_SUSPEND_SECONDS * 1000));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#else
|
|
|
|
+
|
|
|
|
+static void suspend_test_start(void)
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void suspend_test_finish(const char *label)
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#endif
|
|
|
|
+
|
|
/* This is just an arbitrary number */
|
|
/* This is just an arbitrary number */
|
|
#define FREE_PAGE_NUMBER (100)
|
|
#define FREE_PAGE_NUMBER (100)
|
|
|
|
|
|
@@ -266,12 +321,13 @@ int suspend_devices_and_enter(suspend_state_t state)
|
|
goto Close;
|
|
goto Close;
|
|
}
|
|
}
|
|
suspend_console();
|
|
suspend_console();
|
|
|
|
+ suspend_test_start();
|
|
error = device_suspend(PMSG_SUSPEND);
|
|
error = device_suspend(PMSG_SUSPEND);
|
|
if (error) {
|
|
if (error) {
|
|
printk(KERN_ERR "PM: Some devices failed to suspend\n");
|
|
printk(KERN_ERR "PM: Some devices failed to suspend\n");
|
|
goto Recover_platform;
|
|
goto Recover_platform;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+ suspend_test_finish("suspend devices");
|
|
if (suspend_test(TEST_DEVICES))
|
|
if (suspend_test(TEST_DEVICES))
|
|
goto Recover_platform;
|
|
goto Recover_platform;
|
|
|
|
|
|
@@ -293,7 +349,9 @@ int suspend_devices_and_enter(suspend_state_t state)
|
|
if (suspend_ops->finish)
|
|
if (suspend_ops->finish)
|
|
suspend_ops->finish();
|
|
suspend_ops->finish();
|
|
Resume_devices:
|
|
Resume_devices:
|
|
|
|
+ suspend_test_start();
|
|
device_resume(PMSG_RESUME);
|
|
device_resume(PMSG_RESUME);
|
|
|
|
+ suspend_test_finish("resume devices");
|
|
resume_console();
|
|
resume_console();
|
|
Close:
|
|
Close:
|
|
if (suspend_ops->end)
|
|
if (suspend_ops->end)
|
|
@@ -521,3 +579,137 @@ static int __init pm_init(void)
|
|
}
|
|
}
|
|
|
|
|
|
core_initcall(pm_init);
|
|
core_initcall(pm_init);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#ifdef CONFIG_PM_TEST_SUSPEND
|
|
|
|
+
|
|
|
|
+#include <linux/rtc.h>
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * To test system suspend, we need a hands-off mechanism to resume the
|
|
|
|
+ * system. RTCs wake alarms are a common self-contained mechanism.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static void __init test_wakealarm(struct rtc_device *rtc, suspend_state_t state)
|
|
|
|
+{
|
|
|
|
+ static char err_readtime[] __initdata =
|
|
|
|
+ KERN_ERR "PM: can't read %s time, err %d\n";
|
|
|
|
+ static char err_wakealarm [] __initdata =
|
|
|
|
+ KERN_ERR "PM: can't set %s wakealarm, err %d\n";
|
|
|
|
+ static char err_suspend[] __initdata =
|
|
|
|
+ KERN_ERR "PM: suspend test failed, error %d\n";
|
|
|
|
+ static char info_test[] __initdata =
|
|
|
|
+ KERN_INFO "PM: test RTC wakeup from '%s' suspend\n";
|
|
|
|
+
|
|
|
|
+ unsigned long now;
|
|
|
|
+ struct rtc_wkalrm alm;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ /* this may fail if the RTC hasn't been initialized */
|
|
|
|
+ status = rtc_read_time(rtc, &alm.time);
|
|
|
|
+ if (status < 0) {
|
|
|
|
+ printk(err_readtime, rtc->dev.bus_id, status);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ rtc_tm_to_time(&alm.time, &now);
|
|
|
|
+
|
|
|
|
+ memset(&alm, 0, sizeof alm);
|
|
|
|
+ rtc_time_to_tm(now + TEST_SUSPEND_SECONDS, &alm.time);
|
|
|
|
+ alm.enabled = true;
|
|
|
|
+
|
|
|
|
+ status = rtc_set_alarm(rtc, &alm);
|
|
|
|
+ if (status < 0) {
|
|
|
|
+ printk(err_wakealarm, rtc->dev.bus_id, status);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (state == PM_SUSPEND_MEM) {
|
|
|
|
+ printk(info_test, pm_states[state]);
|
|
|
|
+ status = pm_suspend(state);
|
|
|
|
+ if (status == -ENODEV)
|
|
|
|
+ state = PM_SUSPEND_STANDBY;
|
|
|
|
+ }
|
|
|
|
+ if (state == PM_SUSPEND_STANDBY) {
|
|
|
|
+ printk(info_test, pm_states[state]);
|
|
|
|
+ status = pm_suspend(state);
|
|
|
|
+ }
|
|
|
|
+ if (status < 0)
|
|
|
|
+ printk(err_suspend, status);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int __init has_wakealarm(struct device *dev, void *name_ptr)
|
|
|
|
+{
|
|
|
|
+ struct rtc_device *candidate = to_rtc_device(dev);
|
|
|
|
+
|
|
|
|
+ if (!candidate->ops->set_alarm)
|
|
|
|
+ return 0;
|
|
|
|
+ if (!device_may_wakeup(candidate->dev.parent))
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ *(char **)name_ptr = dev->bus_id;
|
|
|
|
+ return 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Kernel options like "test_suspend=mem" force suspend/resume sanity tests
|
|
|
|
+ * at startup time. They're normally disabled, for faster boot and because
|
|
|
|
+ * we can't know which states really work on this particular system.
|
|
|
|
+ */
|
|
|
|
+static suspend_state_t test_state __initdata = PM_SUSPEND_ON;
|
|
|
|
+
|
|
|
|
+static char warn_bad_state[] __initdata =
|
|
|
|
+ KERN_WARNING "PM: can't test '%s' suspend state\n";
|
|
|
|
+
|
|
|
|
+static int __init setup_test_suspend(char *value)
|
|
|
|
+{
|
|
|
|
+ unsigned i;
|
|
|
|
+
|
|
|
|
+ /* "=mem" ==> "mem" */
|
|
|
|
+ value++;
|
|
|
|
+ for (i = 0; i < PM_SUSPEND_MAX; i++) {
|
|
|
|
+ if (!pm_states[i])
|
|
|
|
+ continue;
|
|
|
|
+ if (strcmp(pm_states[i], value) != 0)
|
|
|
|
+ continue;
|
|
|
|
+ test_state = (__force suspend_state_t) i;
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ printk(warn_bad_state, value);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+__setup("test_suspend", setup_test_suspend);
|
|
|
|
+
|
|
|
|
+static int __init test_suspend(void)
|
|
|
|
+{
|
|
|
|
+ static char warn_no_rtc[] __initdata =
|
|
|
|
+ KERN_WARNING "PM: no wakealarm-capable RTC driver is ready\n";
|
|
|
|
+
|
|
|
|
+ char *pony = NULL;
|
|
|
|
+ struct rtc_device *rtc = NULL;
|
|
|
|
+
|
|
|
|
+ /* PM is initialized by now; is that state testable? */
|
|
|
|
+ if (test_state == PM_SUSPEND_ON)
|
|
|
|
+ goto done;
|
|
|
|
+ if (!valid_state(test_state)) {
|
|
|
|
+ printk(warn_bad_state, pm_states[test_state]);
|
|
|
|
+ goto done;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* RTCs have initialized by now too ... can we use one? */
|
|
|
|
+ class_find_device(rtc_class, NULL, &pony, has_wakealarm);
|
|
|
|
+ if (pony)
|
|
|
|
+ rtc = rtc_class_open(pony);
|
|
|
|
+ if (!rtc) {
|
|
|
|
+ printk(warn_no_rtc);
|
|
|
|
+ goto done;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* go for it */
|
|
|
|
+ test_wakealarm(rtc, test_state);
|
|
|
|
+ rtc_class_close(rtc);
|
|
|
|
+done:
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+late_initcall(test_suspend);
|
|
|
|
+
|
|
|
|
+#endif /* CONFIG_PM_TEST_SUSPEND */
|