diff options
| author | Rafael J. Wysocki <rjw@sisk.pl> | 2007-11-19 17:41:19 -0500 |
|---|---|---|
| committer | Len Brown <len.brown@intel.com> | 2008-02-01 18:30:54 -0500 |
| commit | 0e7d56e3d9d7e37c79d0e05ffb3994e34beb3bbc (patch) | |
| tree | cac3e72f882085bc869df9302248a079c657bb10 /kernel/power | |
| parent | c3e94d899c864e558f938f9845ddb8c2e5d5ccd0 (diff) | |
Suspend: Testing facility (rev. 2)
Introduce sysfs attribute /sys/power/pm_test allowing one to test the suspend
core code. Namely, writing one of the strings:
freezer
devices
platform
processors
core
to this file causes the suspend code to work in one of the test modes defined as
follows:
freezer
- test the freezing of processes
devices
- test the freezing of processes and suspending of devices
platform
- test the freezing of processes, suspending of devices and platform global
control methods(*)
processors
- test the freezing of processes, suspending of devices, platform global
control methods and the disabling of nonboot CPUs
core
- test the freezing of processes, suspending of devices, platform global
control methods, the disabling of nonboot CPUs and suspending of
platform/system devices
(*) These are ACPI global control methods on ACPI systems
Then, if a suspend is started by normal means, the suspend core will perform
its normal operations up to the point indicated by given test level. Next, it
will wait for 5 seconds and carry out the resume operations needed to transition
the system back to the fully functional state.
Writing "none" to /sys/power/pm_test turns the testing off.
When open for reading, /sys/power/pm_test contains a space-separated list of all
available tests (including "none" that represents the normal functionality) in
which the current test level is indicated by square brackets.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Len Brown <len.brown@intel.com>
Diffstat (limited to 'kernel/power')
| -rw-r--r-- | kernel/power/main.c | 108 | ||||
| -rw-r--r-- | kernel/power/power.h | 18 |
2 files changed, 117 insertions, 9 deletions
diff --git a/kernel/power/main.c b/kernel/power/main.c index efc08360e627..84e1ae63bb8c 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c | |||
| @@ -31,6 +31,79 @@ DEFINE_MUTEX(pm_mutex); | |||
| 31 | unsigned int pm_flags; | 31 | unsigned int pm_flags; |
| 32 | EXPORT_SYMBOL(pm_flags); | 32 | EXPORT_SYMBOL(pm_flags); |
| 33 | 33 | ||
| 34 | #ifdef CONFIG_PM_DEBUG | ||
| 35 | int pm_test_level = TEST_NONE; | ||
| 36 | |||
| 37 | static int suspend_test(int level) | ||
| 38 | { | ||
| 39 | if (pm_test_level == level) { | ||
| 40 | printk(KERN_INFO "suspend debug: Waiting for 5 seconds.\n"); | ||
| 41 | mdelay(5000); | ||
| 42 | return 1; | ||
| 43 | } | ||
| 44 | return 0; | ||
| 45 | } | ||
| 46 | |||
| 47 | static const char * const pm_tests[__TEST_AFTER_LAST] = { | ||
| 48 | [TEST_NONE] = "none", | ||
| 49 | [TEST_CORE] = "core", | ||
| 50 | [TEST_CPUS] = "processors", | ||
| 51 | [TEST_PLATFORM] = "platform", | ||
| 52 | [TEST_DEVICES] = "devices", | ||
| 53 | [TEST_FREEZER] = "freezer", | ||
| 54 | }; | ||
| 55 | |||
| 56 | static ssize_t pm_test_show(struct kset *kset, char *buf) | ||
| 57 | { | ||
| 58 | char *s = buf; | ||
| 59 | int level; | ||
| 60 | |||
| 61 | for (level = TEST_FIRST; level <= TEST_MAX; level++) | ||
| 62 | if (pm_tests[level]) { | ||
| 63 | if (level == pm_test_level) | ||
| 64 | s += sprintf(s, "[%s] ", pm_tests[level]); | ||
| 65 | else | ||
| 66 | s += sprintf(s, "%s ", pm_tests[level]); | ||
| 67 | } | ||
| 68 | |||
| 69 | if (s != buf) | ||
| 70 | /* convert the last space to a newline */ | ||
| 71 | *(s-1) = '\n'; | ||
| 72 | |||
| 73 | return (s - buf); | ||
| 74 | } | ||
| 75 | |||
| 76 | static ssize_t pm_test_store(struct kset *kset, const char *buf, size_t n) | ||
| 77 | { | ||
| 78 | const char * const *s; | ||
| 79 | int level; | ||
| 80 | char *p; | ||
| 81 | int len; | ||
| 82 | int error = -EINVAL; | ||
| 83 | |||
| 84 | p = memchr(buf, '\n', n); | ||
| 85 | len = p ? p - buf : n; | ||
| 86 | |||
| 87 | mutex_lock(&pm_mutex); | ||
| 88 | |||
| 89 | level = TEST_FIRST; | ||
| 90 | for (s = &pm_tests[level]; level <= TEST_MAX; s++, level++) | ||
| 91 | if (*s && len == strlen(*s) && !strncmp(buf, *s, len)) { | ||
| 92 | pm_test_level = level; | ||
| 93 | error = 0; | ||
| 94 | break; | ||
| 95 | } | ||
| 96 | |||
| 97 | mutex_unlock(&pm_mutex); | ||
| 98 | |||
| 99 | return error ? error : n; | ||
| 100 | } | ||
| 101 | |||
| 102 | power_attr(pm_test); | ||
| 103 | #else /* !CONFIG_PM_DEBUG */ | ||
| 104 | static inline int suspend_test(int level) { return 0; } | ||
| 105 | #endif /* !CONFIG_PM_DEBUG */ | ||
| 106 | |||
| 34 | #ifdef CONFIG_SUSPEND | 107 | #ifdef CONFIG_SUSPEND |
| 35 | 108 | ||
| 36 | /* This is just an arbitrary number */ | 109 | /* This is just an arbitrary number */ |
| @@ -136,7 +209,10 @@ static int suspend_enter(suspend_state_t state) | |||
| 136 | printk(KERN_ERR "Some devices failed to power down\n"); | 209 | printk(KERN_ERR "Some devices failed to power down\n"); |
| 137 | goto Done; | 210 | goto Done; |
| 138 | } | 211 | } |
| 139 | error = suspend_ops->enter(state); | 212 | |
| 213 | if (!suspend_test(TEST_CORE)) | ||
| 214 | error = suspend_ops->enter(state); | ||
| 215 | |||
| 140 | device_power_up(); | 216 | device_power_up(); |
| 141 | Done: | 217 | Done: |
| 142 | arch_suspend_enable_irqs(); | 218 | arch_suspend_enable_irqs(); |
| @@ -167,16 +243,25 @@ int suspend_devices_and_enter(suspend_state_t state) | |||
| 167 | printk(KERN_ERR "Some devices failed to suspend\n"); | 243 | printk(KERN_ERR "Some devices failed to suspend\n"); |
| 168 | goto Resume_console; | 244 | goto Resume_console; |
| 169 | } | 245 | } |
| 246 | |||
| 247 | if (suspend_test(TEST_DEVICES)) | ||
| 248 | goto Resume_devices; | ||
| 249 | |||
| 170 | if (suspend_ops->prepare) { | 250 | if (suspend_ops->prepare) { |
| 171 | error = suspend_ops->prepare(); | 251 | error = suspend_ops->prepare(); |
| 172 | if (error) | 252 | if (error) |
| 173 | goto Resume_devices; | 253 | goto Resume_devices; |
| 174 | } | 254 | } |
| 255 | |||
| 256 | if (suspend_test(TEST_PLATFORM)) | ||
| 257 | goto Finish; | ||
| 258 | |||
| 175 | error = disable_nonboot_cpus(); | 259 | error = disable_nonboot_cpus(); |
| 176 | if (!error) | 260 | if (!error && !suspend_test(TEST_CPUS)) |
| 177 | suspend_enter(state); | 261 | suspend_enter(state); |
| 178 | 262 | ||
| 179 | enable_nonboot_cpus(); | 263 | enable_nonboot_cpus(); |
| 264 | Finish: | ||
| 180 | if (suspend_ops->finish) | 265 | if (suspend_ops->finish) |
| 181 | suspend_ops->finish(); | 266 | suspend_ops->finish(); |
| 182 | Resume_devices: | 267 | Resume_devices: |
| @@ -243,12 +328,17 @@ static int enter_state(suspend_state_t state) | |||
| 243 | printk("done.\n"); | 328 | printk("done.\n"); |
| 244 | 329 | ||
| 245 | pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); | 330 | pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); |
| 246 | if ((error = suspend_prepare())) | 331 | error = suspend_prepare(); |
| 332 | if (error) | ||
| 247 | goto Unlock; | 333 | goto Unlock; |
| 248 | 334 | ||
| 335 | if (suspend_test(TEST_FREEZER)) | ||
| 336 | goto Finish; | ||
| 337 | |||
| 249 | pr_debug("PM: Entering %s sleep\n", pm_states[state]); | 338 | pr_debug("PM: Entering %s sleep\n", pm_states[state]); |
| 250 | error = suspend_devices_and_enter(state); | 339 | error = suspend_devices_and_enter(state); |
| 251 | 340 | ||
| 341 | Finish: | ||
| 252 | pr_debug("PM: Finishing wakeup.\n"); | 342 | pr_debug("PM: Finishing wakeup.\n"); |
| 253 | suspend_finish(); | 343 | suspend_finish(); |
| 254 | Unlock: | 344 | Unlock: |
| @@ -369,18 +459,18 @@ pm_trace_store(struct kobject *kobj, struct kobj_attribute *attr, | |||
| 369 | } | 459 | } |
| 370 | 460 | ||
| 371 | power_attr(pm_trace); | 461 | power_attr(pm_trace); |
| 462 | #endif /* CONFIG_PM_TRACE */ | ||
| 372 | 463 | ||
| 373 | static struct attribute * g[] = { | 464 | static struct attribute * g[] = { |
| 374 | &state_attr.attr, | 465 | &state_attr.attr, |
| 466 | #ifdef CONFIG_PM_TRACE | ||
| 375 | &pm_trace_attr.attr, | 467 | &pm_trace_attr.attr, |
| 468 | #endif | ||
| 469 | #ifdef CONFIG_PM_DEBUG | ||
| 470 | &pm_test_attr.attr, | ||
| 471 | #endif | ||
| 376 | NULL, | 472 | NULL, |
| 377 | }; | 473 | }; |
| 378 | #else | ||
| 379 | static struct attribute * g[] = { | ||
| 380 | &state_attr.attr, | ||
| 381 | NULL, | ||
| 382 | }; | ||
| 383 | #endif /* CONFIG_PM_TRACE */ | ||
| 384 | 474 | ||
| 385 | static struct attribute_group attr_group = { | 475 | static struct attribute_group attr_group = { |
| 386 | .attrs = g, | 476 | .attrs = g, |
diff --git a/kernel/power/power.h b/kernel/power/power.h index c5321eb1f7c8..9f9e16e33962 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h | |||
| @@ -188,3 +188,21 @@ int restore_highmem(void); | |||
| 188 | static inline unsigned int count_highmem_pages(void) { return 0; } | 188 | static inline unsigned int count_highmem_pages(void) { return 0; } |
| 189 | static inline int restore_highmem(void) { return 0; } | 189 | static inline int restore_highmem(void) { return 0; } |
| 190 | #endif | 190 | #endif |
| 191 | |||
| 192 | /* | ||
| 193 | * Suspend test levels | ||
| 194 | */ | ||
| 195 | enum { | ||
| 196 | /* keep first */ | ||
| 197 | TEST_NONE, | ||
| 198 | TEST_CORE, | ||
| 199 | TEST_CPUS, | ||
| 200 | TEST_PLATFORM, | ||
| 201 | TEST_DEVICES, | ||
| 202 | TEST_FREEZER, | ||
| 203 | /* keep last */ | ||
| 204 | __TEST_AFTER_LAST | ||
| 205 | }; | ||
| 206 | |||
| 207 | #define TEST_FIRST TEST_NONE | ||
| 208 | #define TEST_MAX (__TEST_AFTER_LAST - 1) | ||
