diff options
Diffstat (limited to 'kernel/power/main.c')
-rw-r--r-- | kernel/power/main.c | 194 |
1 files changed, 193 insertions, 1 deletions
diff --git a/kernel/power/main.c b/kernel/power/main.c index 3398f4651aa1..95bff23ecdaa 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c | |||
@@ -132,6 +132,61 @@ static inline int suspend_test(int level) { return 0; } | |||
132 | 132 | ||
133 | #ifdef CONFIG_SUSPEND | 133 | #ifdef CONFIG_SUSPEND |
134 | 134 | ||
135 | #ifdef CONFIG_PM_TEST_SUSPEND | ||
136 | |||
137 | /* | ||
138 | * We test the system suspend code by setting an RTC wakealarm a short | ||
139 | * time in the future, then suspending. Suspending the devices won't | ||
140 | * normally take long ... some systems only need a few milliseconds. | ||
141 | * | ||
142 | * The time it takes is system-specific though, so when we test this | ||
143 | * during system bootup we allow a LOT of time. | ||
144 | */ | ||
145 | #define TEST_SUSPEND_SECONDS 5 | ||
146 | |||
147 | static unsigned long suspend_test_start_time; | ||
148 | |||
149 | static void suspend_test_start(void) | ||
150 | { | ||
151 | /* FIXME Use better timebase than "jiffies", ideally a clocksource. | ||
152 | * What we want is a hardware counter that will work correctly even | ||
153 | * during the irqs-are-off stages of the suspend/resume cycle... | ||
154 | */ | ||
155 | suspend_test_start_time = jiffies; | ||
156 | } | ||
157 | |||
158 | static void suspend_test_finish(const char *label) | ||
159 | { | ||
160 | long nj = jiffies - suspend_test_start_time; | ||
161 | unsigned msec; | ||
162 | |||
163 | msec = jiffies_to_msecs(abs(nj)); | ||
164 | pr_info("PM: %s took %d.%03d seconds\n", label, | ||
165 | msec / 1000, msec % 1000); | ||
166 | |||
167 | /* Warning on suspend means the RTC alarm period needs to be | ||
168 | * larger -- the system was sooo slooowwww to suspend that the | ||
169 | * alarm (should have) fired before the system went to sleep! | ||
170 | * | ||
171 | * Warning on either suspend or resume also means the system | ||
172 | * has some performance issues. The stack dump of a WARN_ON | ||
173 | * is more likely to get the right attention than a printk... | ||
174 | */ | ||
175 | WARN_ON(msec > (TEST_SUSPEND_SECONDS * 1000)); | ||
176 | } | ||
177 | |||
178 | #else | ||
179 | |||
180 | static void suspend_test_start(void) | ||
181 | { | ||
182 | } | ||
183 | |||
184 | static void suspend_test_finish(const char *label) | ||
185 | { | ||
186 | } | ||
187 | |||
188 | #endif | ||
189 | |||
135 | /* This is just an arbitrary number */ | 190 | /* This is just an arbitrary number */ |
136 | #define FREE_PAGE_NUMBER (100) | 191 | #define FREE_PAGE_NUMBER (100) |
137 | 192 | ||
@@ -266,12 +321,13 @@ int suspend_devices_and_enter(suspend_state_t state) | |||
266 | goto Close; | 321 | goto Close; |
267 | } | 322 | } |
268 | suspend_console(); | 323 | suspend_console(); |
324 | suspend_test_start(); | ||
269 | error = device_suspend(PMSG_SUSPEND); | 325 | error = device_suspend(PMSG_SUSPEND); |
270 | if (error) { | 326 | if (error) { |
271 | printk(KERN_ERR "PM: Some devices failed to suspend\n"); | 327 | printk(KERN_ERR "PM: Some devices failed to suspend\n"); |
272 | goto Recover_platform; | 328 | goto Recover_platform; |
273 | } | 329 | } |
274 | 330 | suspend_test_finish("suspend devices"); | |
275 | if (suspend_test(TEST_DEVICES)) | 331 | if (suspend_test(TEST_DEVICES)) |
276 | goto Recover_platform; | 332 | goto Recover_platform; |
277 | 333 | ||
@@ -293,7 +349,9 @@ int suspend_devices_and_enter(suspend_state_t state) | |||
293 | if (suspend_ops->finish) | 349 | if (suspend_ops->finish) |
294 | suspend_ops->finish(); | 350 | suspend_ops->finish(); |
295 | Resume_devices: | 351 | Resume_devices: |
352 | suspend_test_start(); | ||
296 | device_resume(PMSG_RESUME); | 353 | device_resume(PMSG_RESUME); |
354 | suspend_test_finish("resume devices"); | ||
297 | resume_console(); | 355 | resume_console(); |
298 | Close: | 356 | Close: |
299 | if (suspend_ops->end) | 357 | if (suspend_ops->end) |
@@ -521,3 +579,137 @@ static int __init pm_init(void) | |||
521 | } | 579 | } |
522 | 580 | ||
523 | core_initcall(pm_init); | 581 | core_initcall(pm_init); |
582 | |||
583 | |||
584 | #ifdef CONFIG_PM_TEST_SUSPEND | ||
585 | |||
586 | #include <linux/rtc.h> | ||
587 | |||
588 | /* | ||
589 | * To test system suspend, we need a hands-off mechanism to resume the | ||
590 | * system. RTCs wake alarms are a common self-contained mechanism. | ||
591 | */ | ||
592 | |||
593 | static void __init test_wakealarm(struct rtc_device *rtc, suspend_state_t state) | ||
594 | { | ||
595 | static char err_readtime[] __initdata = | ||
596 | KERN_ERR "PM: can't read %s time, err %d\n"; | ||
597 | static char err_wakealarm [] __initdata = | ||
598 | KERN_ERR "PM: can't set %s wakealarm, err %d\n"; | ||
599 | static char err_suspend[] __initdata = | ||
600 | KERN_ERR "PM: suspend test failed, error %d\n"; | ||
601 | static char info_test[] __initdata = | ||
602 | KERN_INFO "PM: test RTC wakeup from '%s' suspend\n"; | ||
603 | |||
604 | unsigned long now; | ||
605 | struct rtc_wkalrm alm; | ||
606 | int status; | ||
607 | |||
608 | /* this may fail if the RTC hasn't been initialized */ | ||
609 | status = rtc_read_time(rtc, &alm.time); | ||
610 | if (status < 0) { | ||
611 | printk(err_readtime, rtc->dev.bus_id, status); | ||
612 | return; | ||
613 | } | ||
614 | rtc_tm_to_time(&alm.time, &now); | ||
615 | |||
616 | memset(&alm, 0, sizeof alm); | ||
617 | rtc_time_to_tm(now + TEST_SUSPEND_SECONDS, &alm.time); | ||
618 | alm.enabled = true; | ||
619 | |||
620 | status = rtc_set_alarm(rtc, &alm); | ||
621 | if (status < 0) { | ||
622 | printk(err_wakealarm, rtc->dev.bus_id, status); | ||
623 | return; | ||
624 | } | ||
625 | |||
626 | if (state == PM_SUSPEND_MEM) { | ||
627 | printk(info_test, pm_states[state]); | ||
628 | status = pm_suspend(state); | ||
629 | if (status == -ENODEV) | ||
630 | state = PM_SUSPEND_STANDBY; | ||
631 | } | ||
632 | if (state == PM_SUSPEND_STANDBY) { | ||
633 | printk(info_test, pm_states[state]); | ||
634 | status = pm_suspend(state); | ||
635 | } | ||
636 | if (status < 0) | ||
637 | printk(err_suspend, status); | ||
638 | } | ||
639 | |||
640 | static int __init has_wakealarm(struct device *dev, void *name_ptr) | ||
641 | { | ||
642 | struct rtc_device *candidate = to_rtc_device(dev); | ||
643 | |||
644 | if (!candidate->ops->set_alarm) | ||
645 | return 0; | ||
646 | if (!device_may_wakeup(candidate->dev.parent)) | ||
647 | return 0; | ||
648 | |||
649 | *(char **)name_ptr = dev->bus_id; | ||
650 | return 1; | ||
651 | } | ||
652 | |||
653 | /* | ||
654 | * Kernel options like "test_suspend=mem" force suspend/resume sanity tests | ||
655 | * at startup time. They're normally disabled, for faster boot and because | ||
656 | * we can't know which states really work on this particular system. | ||
657 | */ | ||
658 | static suspend_state_t test_state __initdata = PM_SUSPEND_ON; | ||
659 | |||
660 | static char warn_bad_state[] __initdata = | ||
661 | KERN_WARNING "PM: can't test '%s' suspend state\n"; | ||
662 | |||
663 | static int __init setup_test_suspend(char *value) | ||
664 | { | ||
665 | unsigned i; | ||
666 | |||
667 | /* "=mem" ==> "mem" */ | ||
668 | value++; | ||
669 | for (i = 0; i < PM_SUSPEND_MAX; i++) { | ||
670 | if (!pm_states[i]) | ||
671 | continue; | ||
672 | if (strcmp(pm_states[i], value) != 0) | ||
673 | continue; | ||
674 | test_state = (__force suspend_state_t) i; | ||
675 | return 0; | ||
676 | } | ||
677 | printk(warn_bad_state, value); | ||
678 | return 0; | ||
679 | } | ||
680 | __setup("test_suspend", setup_test_suspend); | ||
681 | |||
682 | static int __init test_suspend(void) | ||
683 | { | ||
684 | static char warn_no_rtc[] __initdata = | ||
685 | KERN_WARNING "PM: no wakealarm-capable RTC driver is ready\n"; | ||
686 | |||
687 | char *pony = NULL; | ||
688 | struct rtc_device *rtc = NULL; | ||
689 | |||
690 | /* PM is initialized by now; is that state testable? */ | ||
691 | if (test_state == PM_SUSPEND_ON) | ||
692 | goto done; | ||
693 | if (!valid_state(test_state)) { | ||
694 | printk(warn_bad_state, pm_states[test_state]); | ||
695 | goto done; | ||
696 | } | ||
697 | |||
698 | /* RTCs have initialized by now too ... can we use one? */ | ||
699 | class_find_device(rtc_class, NULL, &pony, has_wakealarm); | ||
700 | if (pony) | ||
701 | rtc = rtc_class_open(pony); | ||
702 | if (!rtc) { | ||
703 | printk(warn_no_rtc); | ||
704 | goto done; | ||
705 | } | ||
706 | |||
707 | /* go for it */ | ||
708 | test_wakealarm(rtc, test_state); | ||
709 | rtc_class_close(rtc); | ||
710 | done: | ||
711 | return 0; | ||
712 | } | ||
713 | late_initcall(test_suspend); | ||
714 | |||
715 | #endif /* CONFIG_PM_TEST_SUSPEND */ | ||