aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@g5.osdl.org>2006-06-24 17:27:42 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2006-06-24 17:44:01 -0400
commiteb71c87a492b7090ff9e8ac46912c480a1687e38 (patch)
tree1136213dee0f942866b6c2c65de7e7c63ca94fda
parentd384ea691fe4ea8c2dd5b9b8d9042eb181776f18 (diff)
Add some basic resume trace facilities
Considering that there isn't a lot of hw we can depend on during resume, this is about as good as it gets. This is x86-only for now, although the basic concept (and most of the code) will certainly work on almost any platform. Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--arch/i386/kernel/vmlinux.lds.S7
-rw-r--r--drivers/base/power/Makefile1
-rw-r--r--drivers/base/power/trace.c228
-rw-r--r--include/asm-generic/rtc.h7
-rw-r--r--include/linux/resume-trace.h30
-rw-r--r--kernel/power/Kconfig9
6 files changed, 279 insertions, 3 deletions
diff --git a/arch/i386/kernel/vmlinux.lds.S b/arch/i386/kernel/vmlinux.lds.S
index 8831303a473f..7512f39c9f25 100644
--- a/arch/i386/kernel/vmlinux.lds.S
+++ b/arch/i386/kernel/vmlinux.lds.S
@@ -37,6 +37,13 @@ SECTIONS
37 37
38 RODATA 38 RODATA
39 39
40 . = ALIGN(4);
41 __tracedata_start = .;
42 .tracedata : AT(ADDR(.tracedata) - LOAD_OFFSET) {
43 *(.tracedata)
44 }
45 __tracedata_end = .;
46
40 /* writeable */ 47 /* writeable */
41 .data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */ 48 .data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */
42 *(.data) 49 *(.data)
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
index ceeeba2c56c7..91f230939c1e 100644
--- a/drivers/base/power/Makefile
+++ b/drivers/base/power/Makefile
@@ -1,5 +1,6 @@
1obj-y := shutdown.o 1obj-y := shutdown.o
2obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o 2obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
3obj-$(CONFIG_PM_TRACE) += trace.o
3 4
4ifeq ($(CONFIG_DEBUG_DRIVER),y) 5ifeq ($(CONFIG_DEBUG_DRIVER),y)
5EXTRA_CFLAGS += -DDEBUG 6EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/base/power/trace.c b/drivers/base/power/trace.c
new file mode 100644
index 000000000000..a9ab30fefffc
--- /dev/null
+++ b/drivers/base/power/trace.c
@@ -0,0 +1,228 @@
1/*
2 * drivers/base/power/trace.c
3 *
4 * Copyright (C) 2006 Linus Torvalds
5 *
6 * Trace facility for suspend/resume problems, when none of the
7 * devices may be working.
8 */
9
10#include <linux/resume-trace.h>
11#include <linux/rtc.h>
12
13#include <asm/rtc.h>
14
15#include "power.h"
16
17/*
18 * Horrid, horrid, horrid.
19 *
20 * It turns out that the _only_ piece of hardware that actually
21 * keeps its value across a hard boot (and, more importantly, the
22 * POST init sequence) is literally the realtime clock.
23 *
24 * Never mind that an RTC chip has 114 bytes (and often a whole
25 * other bank of an additional 128 bytes) of nice SRAM that is
26 * _designed_ to keep data - the POST will clear it. So we literally
27 * can just use the few bytes of actual time data, which means that
28 * we're really limited.
29 *
30 * It means, for example, that we can't use the seconds at all
31 * (since the time between the hang and the boot might be more
32 * than a minute), and we'd better not depend on the low bits of
33 * the minutes either.
34 *
35 * There are the wday fields etc, but I wouldn't guarantee those
36 * are dependable either. And if the date isn't valid, either the
37 * hw or POST will do strange things.
38 *
39 * So we're left with:
40 * - year: 0-99
41 * - month: 0-11
42 * - day-of-month: 1-28
43 * - hour: 0-23
44 * - min: (0-30)*2
45 *
46 * Giving us a total range of 0-16128000 (0xf61800), ie less
47 * than 24 bits of actual data we can save across reboots.
48 *
49 * And if your box can't boot in less than three minutes,
50 * you're screwed.
51 *
52 * Now, almost 24 bits of data is pitifully small, so we need
53 * to be pretty dense if we want to use it for anything nice.
54 * What we do is that instead of saving off nice readable info,
55 * we save off _hashes_ of information that we can hopefully
56 * regenerate after the reboot.
57 *
58 * In particular, this means that we might be unlucky, and hit
59 * a case where we have a hash collision, and we end up not
60 * being able to tell for certain exactly which case happened.
61 * But that's hopefully unlikely.
62 *
63 * What we do is to take the bits we can fit, and split them
64 * into three parts (16*997*1009 = 16095568), and use the values
65 * for:
66 * - 0-15: user-settable
67 * - 0-996: file + line number
68 * - 0-1008: device
69 */
70#define USERHASH (16)
71#define FILEHASH (997)
72#define DEVHASH (1009)
73
74#define DEVSEED (7919)
75
76static unsigned int dev_hash_value;
77
78static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
79{
80 unsigned int n = user + USERHASH*(file + FILEHASH*device);
81
82 // June 7th, 2006
83 static struct rtc_time time = {
84 .tm_sec = 0,
85 .tm_min = 0,
86 .tm_hour = 0,
87 .tm_mday = 7,
88 .tm_mon = 5, // June - counting from zero
89 .tm_year = 106,
90 .tm_wday = 3,
91 .tm_yday = 160,
92 .tm_isdst = 1
93 };
94
95 time.tm_year = (n % 100);
96 n /= 100;
97 time.tm_mon = (n % 12);
98 n /= 12;
99 time.tm_mday = (n % 28) + 1;
100 n /= 28;
101 time.tm_hour = (n % 24);
102 n /= 24;
103 time.tm_min = (n % 20) * 3;
104 n /= 20;
105 set_rtc_time(&time);
106 return n ? -1 : 0;
107}
108
109static unsigned int read_magic_time(void)
110{
111 struct rtc_time time;
112 unsigned int val;
113
114 get_rtc_time(&time);
115 printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n",
116 time.tm_hour, time.tm_min, time.tm_sec,
117 time.tm_mon, time.tm_mday, time.tm_year);
118 val = time.tm_year; /* 100 years */
119 if (val > 100)
120 val -= 100;
121 val += time.tm_mon * 100; /* 12 months */
122 val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */
123 val += time.tm_hour * 100 * 12 * 28; /* 24 hours */
124 val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */
125 return val;
126}
127
128/*
129 * This is just the sdbm hash function with a user-supplied
130 * seed and final size parameter.
131 */
132static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
133{
134 unsigned char c;
135 while ((c = *data++) != 0) {
136 seed = (seed << 16) + (seed << 6) - seed + c;
137 }
138 return seed % mod;
139}
140
141void set_trace_device(struct device *dev)
142{
143 dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH);
144}
145
146/*
147 * We could just take the "tracedata" index into the .tracedata
148 * section instead. Generating a hash of the data gives us a
149 * chance to work across kernel versions, and perhaps more
150 * importantly it also gives us valid/invalid check (ie we will
151 * likely not give totally bogus reports - if the hash matches,
152 * it's not any guarantee, but it's a high _likelihood_ that
153 * the match is valid).
154 */
155void generate_resume_trace(void *tracedata, unsigned int user)
156{
157 unsigned short lineno = *(unsigned short *)tracedata;
158 const char *file = *(const char **)(tracedata + 2);
159 unsigned int user_hash_value, file_hash_value;
160
161 user_hash_value = user % USERHASH;
162 file_hash_value = hash_string(lineno, file, FILEHASH);
163 set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
164}
165
166extern char __tracedata_start, __tracedata_end;
167static int show_file_hash(unsigned int value)
168{
169 int match;
170 char *tracedata;
171
172 match = 0;
173 for (tracedata = &__tracedata_start ; tracedata < &__tracedata_end ; tracedata += 6) {
174 unsigned short lineno = *(unsigned short *)tracedata;
175 const char *file = *(const char **)(tracedata + 2);
176 unsigned int hash = hash_string(lineno, file, FILEHASH);
177 if (hash != value)
178 continue;
179 printk(" hash matches %s:%u\n", file, lineno);
180 match++;
181 }
182 return match;
183}
184
185static int show_dev_hash(unsigned int value)
186{
187 int match = 0;
188 struct list_head * entry = dpm_active.prev;
189
190 while (entry != &dpm_active) {
191 struct device * dev = to_device(entry);
192 unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH);
193 if (hash == value) {
194 printk(" hash matches device %s\n", dev->bus_id);
195 match++;
196 }
197 entry = entry->prev;
198 }
199 return match;
200}
201
202static unsigned int hash_value_early_read;
203
204static int early_resume_init(void)
205{
206 hash_value_early_read = read_magic_time();
207 return 0;
208}
209
210static int late_resume_init(void)
211{
212 unsigned int val = hash_value_early_read;
213 unsigned int user, file, dev;
214
215 user = val % USERHASH;
216 val = val / USERHASH;
217 file = val % FILEHASH;
218 val = val / FILEHASH;
219 dev = val /* % DEVHASH */;
220
221 printk(" Magic number: %d:%d:%d\n", user, file, dev);
222 show_file_hash(file);
223 show_dev_hash(dev);
224 return 0;
225}
226
227core_initcall(early_resume_init);
228late_initcall(late_resume_init);
diff --git a/include/asm-generic/rtc.h b/include/asm-generic/rtc.h
index cef08db34ada..4087037a4225 100644
--- a/include/asm-generic/rtc.h
+++ b/include/asm-generic/rtc.h
@@ -114,6 +114,7 @@ static inline unsigned int get_rtc_time(struct rtc_time *time)
114/* Set the current date and time in the real time clock. */ 114/* Set the current date and time in the real time clock. */
115static inline int set_rtc_time(struct rtc_time *time) 115static inline int set_rtc_time(struct rtc_time *time)
116{ 116{
117 unsigned long flags;
117 unsigned char mon, day, hrs, min, sec; 118 unsigned char mon, day, hrs, min, sec;
118 unsigned char save_control, save_freq_select; 119 unsigned char save_control, save_freq_select;
119 unsigned int yrs; 120 unsigned int yrs;
@@ -131,7 +132,7 @@ static inline int set_rtc_time(struct rtc_time *time)
131 if (yrs > 255) /* They are unsigned */ 132 if (yrs > 255) /* They are unsigned */
132 return -EINVAL; 133 return -EINVAL;
133 134
134 spin_lock_irq(&rtc_lock); 135 spin_lock_irqsave(&rtc_lock, flags);
135#ifdef CONFIG_MACH_DECSTATION 136#ifdef CONFIG_MACH_DECSTATION
136 real_yrs = yrs; 137 real_yrs = yrs;
137 leap_yr = ((!((yrs + 1900) % 4) && ((yrs + 1900) % 100)) || 138 leap_yr = ((!((yrs + 1900) % 4) && ((yrs + 1900) % 100)) ||
@@ -152,7 +153,7 @@ static inline int set_rtc_time(struct rtc_time *time)
152 * whether the chip is in binary mode or not. 153 * whether the chip is in binary mode or not.
153 */ 154 */
154 if (yrs > 169) { 155 if (yrs > 169) {
155 spin_unlock_irq(&rtc_lock); 156 spin_unlock_irqrestore(&rtc_lock, flags);
156 return -EINVAL; 157 return -EINVAL;
157 } 158 }
158 159
@@ -187,7 +188,7 @@ static inline int set_rtc_time(struct rtc_time *time)
187 CMOS_WRITE(save_control, RTC_CONTROL); 188 CMOS_WRITE(save_control, RTC_CONTROL);
188 CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); 189 CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
189 190
190 spin_unlock_irq(&rtc_lock); 191 spin_unlock_irqrestore(&rtc_lock, flags);
191 192
192 return 0; 193 return 0;
193} 194}
diff --git a/include/linux/resume-trace.h b/include/linux/resume-trace.h
new file mode 100644
index 000000000000..a376bd4ade39
--- /dev/null
+++ b/include/linux/resume-trace.h
@@ -0,0 +1,30 @@
1#ifndef RESUME_TRACE_H
2#define RESUME_TRACE_H
3
4#ifdef CONFIG_PM_TRACE
5
6struct device;
7extern void set_trace_device(struct device *);
8extern void generate_resume_trace(void *tracedata, unsigned int user);
9
10#define TRACE_DEVICE(dev) set_trace_device(dev)
11#define TRACE_RESUME(user) do { \
12 void *tracedata; \
13 asm volatile("movl $1f,%0\n" \
14 ".section .tracedata,\"a\"\n" \
15 "1:\t.word %c1\n" \
16 "\t.long %c2\n" \
17 ".previous" \
18 :"=r" (tracedata) \
19 : "i" (__LINE__), "i" (__FILE__)); \
20 generate_resume_trace(tracedata, user); \
21} while (0)
22
23#else
24
25#define TRACE_DEVICE(dev) do { } while (0)
26#define TRACE_RESUME(dev) do { } while (0)
27
28#endif
29
30#endif
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index ce0dfb8f4a4e..cdf315e794ff 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -36,6 +36,15 @@ config PM_DEBUG
36 code. This is helpful when debugging and reporting various PM bugs, 36 code. This is helpful when debugging and reporting various PM bugs,
37 like suspend support. 37 like suspend support.
38 38
39config PM_TRACE
40 bool "Suspend/resume event tracing"
41 depends on PM && PM_DEBUG && X86
42 default y
43 ---help---
44 This enables some cheesy code to save the last PM event point in the
45 RTC across reboots, so that you can debug a machine that just hangs
46 during suspend (or more commonly, during resume).
47
39config SOFTWARE_SUSPEND 48config SOFTWARE_SUSPEND
40 bool "Software Suspend" 49 bool "Software Suspend"
41 depends on PM && SWAP && (X86 && (!SMP || SUSPEND_SMP)) || ((FRV || PPC32) && !SMP) 50 depends on PM && SWAP && (X86 && (!SMP || SUSPEND_SMP)) || ((FRV || PPC32) && !SMP)