diff options
-rw-r--r-- | Documentation/power/basic-pm-debugging.txt | 24 | ||||
-rw-r--r-- | drivers/base/power/main.c | 31 | ||||
-rw-r--r-- | include/linux/suspend.h | 52 | ||||
-rw-r--r-- | kernel/power/main.c | 102 | ||||
-rw-r--r-- | kernel/power/suspend.c | 17 |
5 files changed, 218 insertions, 8 deletions
diff --git a/Documentation/power/basic-pm-debugging.txt b/Documentation/power/basic-pm-debugging.txt index ddd78172ef73..62eca080a71b 100644 --- a/Documentation/power/basic-pm-debugging.txt +++ b/Documentation/power/basic-pm-debugging.txt | |||
@@ -201,3 +201,27 @@ case, you may be able to search for failing drivers by following the procedure | |||
201 | analogous to the one described in section 1. If you find some failing drivers, | 201 | analogous to the one described in section 1. If you find some failing drivers, |
202 | you will have to unload them every time before an STR transition (ie. before | 202 | you will have to unload them every time before an STR transition (ie. before |
203 | you run s2ram), and please report the problems with them. | 203 | you run s2ram), and please report the problems with them. |
204 | |||
205 | There is a debugfs entry which shows the suspend to RAM statistics. Here is an | ||
206 | example of its output. | ||
207 | # mount -t debugfs none /sys/kernel/debug | ||
208 | # cat /sys/kernel/debug/suspend_stats | ||
209 | success: 20 | ||
210 | fail: 5 | ||
211 | failed_freeze: 0 | ||
212 | failed_prepare: 0 | ||
213 | failed_suspend: 5 | ||
214 | failed_suspend_noirq: 0 | ||
215 | failed_resume: 0 | ||
216 | failed_resume_noirq: 0 | ||
217 | failures: | ||
218 | last_failed_dev: alarm | ||
219 | adc | ||
220 | last_failed_errno: -16 | ||
221 | -16 | ||
222 | last_failed_step: suspend | ||
223 | suspend | ||
224 | Field success means the success number of suspend to RAM, and field fail means | ||
225 | the failure number. Others are the failure number of different steps of suspend | ||
226 | to RAM. suspend_stats just lists the last 2 failed devices, error number and | ||
227 | failed step of suspend. | ||
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index c6291ab725a3..b1b58260b4ff 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
@@ -46,6 +46,7 @@ LIST_HEAD(dpm_prepared_list); | |||
46 | LIST_HEAD(dpm_suspended_list); | 46 | LIST_HEAD(dpm_suspended_list); |
47 | LIST_HEAD(dpm_noirq_list); | 47 | LIST_HEAD(dpm_noirq_list); |
48 | 48 | ||
49 | struct suspend_stats suspend_stats; | ||
49 | static DEFINE_MUTEX(dpm_list_mtx); | 50 | static DEFINE_MUTEX(dpm_list_mtx); |
50 | static pm_message_t pm_transition; | 51 | static pm_message_t pm_transition; |
51 | 52 | ||
@@ -467,8 +468,12 @@ void dpm_resume_noirq(pm_message_t state) | |||
467 | mutex_unlock(&dpm_list_mtx); | 468 | mutex_unlock(&dpm_list_mtx); |
468 | 469 | ||
469 | error = device_resume_noirq(dev, state); | 470 | error = device_resume_noirq(dev, state); |
470 | if (error) | 471 | if (error) { |
472 | suspend_stats.failed_resume_noirq++; | ||
473 | dpm_save_failed_step(SUSPEND_RESUME_NOIRQ); | ||
474 | dpm_save_failed_dev(dev_name(dev)); | ||
471 | pm_dev_err(dev, state, " early", error); | 475 | pm_dev_err(dev, state, " early", error); |
476 | } | ||
472 | 477 | ||
473 | mutex_lock(&dpm_list_mtx); | 478 | mutex_lock(&dpm_list_mtx); |
474 | put_device(dev); | 479 | put_device(dev); |
@@ -629,8 +634,12 @@ void dpm_resume(pm_message_t state) | |||
629 | mutex_unlock(&dpm_list_mtx); | 634 | mutex_unlock(&dpm_list_mtx); |
630 | 635 | ||
631 | error = device_resume(dev, state, false); | 636 | error = device_resume(dev, state, false); |
632 | if (error) | 637 | if (error) { |
638 | suspend_stats.failed_resume++; | ||
639 | dpm_save_failed_step(SUSPEND_RESUME); | ||
640 | dpm_save_failed_dev(dev_name(dev)); | ||
633 | pm_dev_err(dev, state, "", error); | 641 | pm_dev_err(dev, state, "", error); |
642 | } | ||
634 | 643 | ||
635 | mutex_lock(&dpm_list_mtx); | 644 | mutex_lock(&dpm_list_mtx); |
636 | } | 645 | } |
@@ -805,6 +814,9 @@ int dpm_suspend_noirq(pm_message_t state) | |||
805 | mutex_lock(&dpm_list_mtx); | 814 | mutex_lock(&dpm_list_mtx); |
806 | if (error) { | 815 | if (error) { |
807 | pm_dev_err(dev, state, " late", error); | 816 | pm_dev_err(dev, state, " late", error); |
817 | suspend_stats.failed_suspend_noirq++; | ||
818 | dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ); | ||
819 | dpm_save_failed_dev(dev_name(dev)); | ||
808 | put_device(dev); | 820 | put_device(dev); |
809 | break; | 821 | break; |
810 | } | 822 | } |
@@ -926,8 +938,10 @@ static void async_suspend(void *data, async_cookie_t cookie) | |||
926 | int error; | 938 | int error; |
927 | 939 | ||
928 | error = __device_suspend(dev, pm_transition, true); | 940 | error = __device_suspend(dev, pm_transition, true); |
929 | if (error) | 941 | if (error) { |
942 | dpm_save_failed_dev(dev_name(dev)); | ||
930 | pm_dev_err(dev, pm_transition, " async", error); | 943 | pm_dev_err(dev, pm_transition, " async", error); |
944 | } | ||
931 | 945 | ||
932 | put_device(dev); | 946 | put_device(dev); |
933 | } | 947 | } |
@@ -970,6 +984,7 @@ int dpm_suspend(pm_message_t state) | |||
970 | mutex_lock(&dpm_list_mtx); | 984 | mutex_lock(&dpm_list_mtx); |
971 | if (error) { | 985 | if (error) { |
972 | pm_dev_err(dev, state, "", error); | 986 | pm_dev_err(dev, state, "", error); |
987 | dpm_save_failed_dev(dev_name(dev)); | ||
973 | put_device(dev); | 988 | put_device(dev); |
974 | break; | 989 | break; |
975 | } | 990 | } |
@@ -983,7 +998,10 @@ int dpm_suspend(pm_message_t state) | |||
983 | async_synchronize_full(); | 998 | async_synchronize_full(); |
984 | if (!error) | 999 | if (!error) |
985 | error = async_error; | 1000 | error = async_error; |
986 | if (!error) | 1001 | if (error) { |
1002 | suspend_stats.failed_suspend++; | ||
1003 | dpm_save_failed_step(SUSPEND_SUSPEND); | ||
1004 | } else | ||
987 | dpm_show_time(starttime, state, NULL); | 1005 | dpm_show_time(starttime, state, NULL); |
988 | return error; | 1006 | return error; |
989 | } | 1007 | } |
@@ -1091,7 +1109,10 @@ int dpm_suspend_start(pm_message_t state) | |||
1091 | int error; | 1109 | int error; |
1092 | 1110 | ||
1093 | error = dpm_prepare(state); | 1111 | error = dpm_prepare(state); |
1094 | if (!error) | 1112 | if (error) { |
1113 | suspend_stats.failed_prepare++; | ||
1114 | dpm_save_failed_step(SUSPEND_PREPARE); | ||
1115 | } else | ||
1095 | error = dpm_suspend(state); | 1116 | error = dpm_suspend(state); |
1096 | return error; | 1117 | return error; |
1097 | } | 1118 | } |
diff --git a/include/linux/suspend.h b/include/linux/suspend.h index 6bbcef22e105..76f42e49b72d 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h | |||
@@ -34,6 +34,58 @@ typedef int __bitwise suspend_state_t; | |||
34 | #define PM_SUSPEND_MEM ((__force suspend_state_t) 3) | 34 | #define PM_SUSPEND_MEM ((__force suspend_state_t) 3) |
35 | #define PM_SUSPEND_MAX ((__force suspend_state_t) 4) | 35 | #define PM_SUSPEND_MAX ((__force suspend_state_t) 4) |
36 | 36 | ||
37 | enum suspend_stat_step { | ||
38 | SUSPEND_FREEZE = 1, | ||
39 | SUSPEND_PREPARE, | ||
40 | SUSPEND_SUSPEND, | ||
41 | SUSPEND_SUSPEND_NOIRQ, | ||
42 | SUSPEND_RESUME_NOIRQ, | ||
43 | SUSPEND_RESUME | ||
44 | }; | ||
45 | |||
46 | struct suspend_stats { | ||
47 | int success; | ||
48 | int fail; | ||
49 | int failed_freeze; | ||
50 | int failed_prepare; | ||
51 | int failed_suspend; | ||
52 | int failed_suspend_noirq; | ||
53 | int failed_resume; | ||
54 | int failed_resume_noirq; | ||
55 | #define REC_FAILED_NUM 2 | ||
56 | int last_failed_dev; | ||
57 | char failed_devs[REC_FAILED_NUM][40]; | ||
58 | int last_failed_errno; | ||
59 | int errno[REC_FAILED_NUM]; | ||
60 | int last_failed_step; | ||
61 | enum suspend_stat_step failed_steps[REC_FAILED_NUM]; | ||
62 | }; | ||
63 | |||
64 | extern struct suspend_stats suspend_stats; | ||
65 | |||
66 | static inline void dpm_save_failed_dev(const char *name) | ||
67 | { | ||
68 | strlcpy(suspend_stats.failed_devs[suspend_stats.last_failed_dev], | ||
69 | name, | ||
70 | sizeof(suspend_stats.failed_devs[0])); | ||
71 | suspend_stats.last_failed_dev++; | ||
72 | suspend_stats.last_failed_dev %= REC_FAILED_NUM; | ||
73 | } | ||
74 | |||
75 | static inline void dpm_save_failed_errno(int err) | ||
76 | { | ||
77 | suspend_stats.errno[suspend_stats.last_failed_errno] = err; | ||
78 | suspend_stats.last_failed_errno++; | ||
79 | suspend_stats.last_failed_errno %= REC_FAILED_NUM; | ||
80 | } | ||
81 | |||
82 | static inline void dpm_save_failed_step(enum suspend_stat_step step) | ||
83 | { | ||
84 | suspend_stats.failed_steps[suspend_stats.last_failed_step] = step; | ||
85 | suspend_stats.last_failed_step++; | ||
86 | suspend_stats.last_failed_step %= REC_FAILED_NUM; | ||
87 | } | ||
88 | |||
37 | /** | 89 | /** |
38 | * struct platform_suspend_ops - Callbacks for managing platform dependent | 90 | * struct platform_suspend_ops - Callbacks for managing platform dependent |
39 | * system sleep states. | 91 | * system sleep states. |
diff --git a/kernel/power/main.c b/kernel/power/main.c index 6c601f871964..2757acba8e8a 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c | |||
@@ -12,6 +12,8 @@ | |||
12 | #include <linux/string.h> | 12 | #include <linux/string.h> |
13 | #include <linux/resume-trace.h> | 13 | #include <linux/resume-trace.h> |
14 | #include <linux/workqueue.h> | 14 | #include <linux/workqueue.h> |
15 | #include <linux/debugfs.h> | ||
16 | #include <linux/seq_file.h> | ||
15 | 17 | ||
16 | #include "power.h" | 18 | #include "power.h" |
17 | 19 | ||
@@ -133,6 +135,101 @@ power_attr(pm_test); | |||
133 | 135 | ||
134 | #endif /* CONFIG_PM_SLEEP */ | 136 | #endif /* CONFIG_PM_SLEEP */ |
135 | 137 | ||
138 | #ifdef CONFIG_DEBUG_FS | ||
139 | static char *suspend_step_name(enum suspend_stat_step step) | ||
140 | { | ||
141 | switch (step) { | ||
142 | case SUSPEND_FREEZE: | ||
143 | return "freeze"; | ||
144 | case SUSPEND_PREPARE: | ||
145 | return "prepare"; | ||
146 | case SUSPEND_SUSPEND: | ||
147 | return "suspend"; | ||
148 | case SUSPEND_SUSPEND_NOIRQ: | ||
149 | return "suspend_noirq"; | ||
150 | case SUSPEND_RESUME_NOIRQ: | ||
151 | return "resume_noirq"; | ||
152 | case SUSPEND_RESUME: | ||
153 | return "resume"; | ||
154 | default: | ||
155 | return ""; | ||
156 | } | ||
157 | } | ||
158 | |||
159 | static int suspend_stats_show(struct seq_file *s, void *unused) | ||
160 | { | ||
161 | int i, index, last_dev, last_errno, last_step; | ||
162 | |||
163 | last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1; | ||
164 | last_dev %= REC_FAILED_NUM; | ||
165 | last_errno = suspend_stats.last_failed_errno + REC_FAILED_NUM - 1; | ||
166 | last_errno %= REC_FAILED_NUM; | ||
167 | last_step = suspend_stats.last_failed_step + REC_FAILED_NUM - 1; | ||
168 | last_step %= REC_FAILED_NUM; | ||
169 | seq_printf(s, "%s: %d\n%s: %d\n%s: %d\n%s: %d\n" | ||
170 | "%s: %d\n%s: %d\n%s: %d\n%s: %d\n", | ||
171 | "success", suspend_stats.success, | ||
172 | "fail", suspend_stats.fail, | ||
173 | "failed_freeze", suspend_stats.failed_freeze, | ||
174 | "failed_prepare", suspend_stats.failed_prepare, | ||
175 | "failed_suspend", suspend_stats.failed_suspend, | ||
176 | "failed_suspend_noirq", | ||
177 | suspend_stats.failed_suspend_noirq, | ||
178 | "failed_resume", suspend_stats.failed_resume, | ||
179 | "failed_resume_noirq", | ||
180 | suspend_stats.failed_resume_noirq); | ||
181 | seq_printf(s, "failures:\n last_failed_dev:\t%-s\n", | ||
182 | suspend_stats.failed_devs[last_dev]); | ||
183 | for (i = 1; i < REC_FAILED_NUM; i++) { | ||
184 | index = last_dev + REC_FAILED_NUM - i; | ||
185 | index %= REC_FAILED_NUM; | ||
186 | seq_printf(s, "\t\t\t%-s\n", | ||
187 | suspend_stats.failed_devs[index]); | ||
188 | } | ||
189 | seq_printf(s, " last_failed_errno:\t%-d\n", | ||
190 | suspend_stats.errno[last_errno]); | ||
191 | for (i = 1; i < REC_FAILED_NUM; i++) { | ||
192 | index = last_errno + REC_FAILED_NUM - i; | ||
193 | index %= REC_FAILED_NUM; | ||
194 | seq_printf(s, "\t\t\t%-d\n", | ||
195 | suspend_stats.errno[index]); | ||
196 | } | ||
197 | seq_printf(s, " last_failed_step:\t%-s\n", | ||
198 | suspend_step_name( | ||
199 | suspend_stats.failed_steps[last_step])); | ||
200 | for (i = 1; i < REC_FAILED_NUM; i++) { | ||
201 | index = last_step + REC_FAILED_NUM - i; | ||
202 | index %= REC_FAILED_NUM; | ||
203 | seq_printf(s, "\t\t\t%-s\n", | ||
204 | suspend_step_name( | ||
205 | suspend_stats.failed_steps[index])); | ||
206 | } | ||
207 | |||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | static int suspend_stats_open(struct inode *inode, struct file *file) | ||
212 | { | ||
213 | return single_open(file, suspend_stats_show, NULL); | ||
214 | } | ||
215 | |||
216 | static const struct file_operations suspend_stats_operations = { | ||
217 | .open = suspend_stats_open, | ||
218 | .read = seq_read, | ||
219 | .llseek = seq_lseek, | ||
220 | .release = single_release, | ||
221 | }; | ||
222 | |||
223 | static int __init pm_debugfs_init(void) | ||
224 | { | ||
225 | debugfs_create_file("suspend_stats", S_IFREG | S_IRUGO, | ||
226 | NULL, NULL, &suspend_stats_operations); | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | late_initcall(pm_debugfs_init); | ||
231 | #endif /* CONFIG_DEBUG_FS */ | ||
232 | |||
136 | struct kobject *power_kobj; | 233 | struct kobject *power_kobj; |
137 | 234 | ||
138 | /** | 235 | /** |
@@ -194,6 +291,11 @@ static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr, | |||
194 | } | 291 | } |
195 | if (state < PM_SUSPEND_MAX && *s) | 292 | if (state < PM_SUSPEND_MAX && *s) |
196 | error = enter_state(state); | 293 | error = enter_state(state); |
294 | if (error) { | ||
295 | suspend_stats.fail++; | ||
296 | dpm_save_failed_errno(error); | ||
297 | } else | ||
298 | suspend_stats.success++; | ||
197 | #endif | 299 | #endif |
198 | 300 | ||
199 | Exit: | 301 | Exit: |
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index b6b71ad2208f..595a3dd56a8a 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c | |||
@@ -104,7 +104,10 @@ static int suspend_prepare(void) | |||
104 | goto Finish; | 104 | goto Finish; |
105 | 105 | ||
106 | error = suspend_freeze_processes(); | 106 | error = suspend_freeze_processes(); |
107 | if (!error) | 107 | if (error) { |
108 | suspend_stats.failed_freeze++; | ||
109 | dpm_save_failed_step(SUSPEND_FREEZE); | ||
110 | } else | ||
108 | return 0; | 111 | return 0; |
109 | 112 | ||
110 | suspend_thaw_processes(); | 113 | suspend_thaw_processes(); |
@@ -315,8 +318,16 @@ int enter_state(suspend_state_t state) | |||
315 | */ | 318 | */ |
316 | int pm_suspend(suspend_state_t state) | 319 | int pm_suspend(suspend_state_t state) |
317 | { | 320 | { |
318 | if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX) | 321 | int ret; |
319 | return enter_state(state); | 322 | if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX) { |
323 | ret = enter_state(state); | ||
324 | if (ret) { | ||
325 | suspend_stats.fail++; | ||
326 | dpm_save_failed_errno(ret); | ||
327 | } else | ||
328 | suspend_stats.success++; | ||
329 | return ret; | ||
330 | } | ||
320 | return -EINVAL; | 331 | return -EINVAL; |
321 | } | 332 | } |
322 | EXPORT_SYMBOL(pm_suspend); | 333 | EXPORT_SYMBOL(pm_suspend); |