summaryrefslogtreecommitdiff
path: root/drivers/base/power/trace.c
blob: 977d27bd1a2207e3095badcdefd905ac955d940a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// SPDX-License-Identifier: GPL-2.0
/*
 * drivers/base/power/trace.c
 *
 * Copyright (C) 2006 Linus Torvalds
 *
 * Trace facility for suspend/resume problems, when none of the
 * devices may be working.
 */
#define pr_fmt(fmt) "PM: " fmt

#include <linux/pm-trace.h>
#include <linux/export.h>
#include <linux/rtc.h>
#include <linux/suspend.h>

#include <linux/mc146818rtc.h>

#include "power.h"

/*
 * Horrid, horrid, horrid.
 *
 * It turns out that the _only_ piece of hardware that actually
 * keeps its value across a hard boot (and, more importantly, the
 * POST init sequence) is literally the realtime clock.
 *
 * Never mind that an RTC chip has 114 bytes (and often a whole
 * other bank of an additional 128 bytes) of nice SRAM that is
 * _designed_ to keep data - the POST will clear it. So we literally
 * can just use the few bytes of actual time data, which means that
 * we're really limited.
 *
 * It means, for example, that we can't use the seconds at all
 * (since the time between the hang and the boot might be more
 * than a minute), and we'd better not depend on the low bits of
 * the minutes either.
 *
 * There are the wday fields etc, but I wouldn't guarantee those
 * are dependable either. And if the date isn't valid, either the
 * hw or POST will do strange things.
 *
 * So we're left with:
 *  - year: 0-99
 *  - month: 0-11
 *  - day-of-month: 1-28
 *  - hour: 0-23
 *  - min: (0-30)*2
 *
 * Giving us a total range of 0-16128000 (0xf61800), ie less
 * than 24 bits of actual data we can save across reboots.
 *
 * And if your box can't boot in less than three minutes,
 * you're screwed.
 *
 * Now, almost 24 bits of data is pitifully small, so we need
 * to be pretty dense if we want to use it for anything nice.
 * What we do is that instead of saving off nice readable info,
 * we save off _hashes_ of information that we can hopefully
 * regenerate after the reboot.
 *
 * In particular, this means that we might be unlucky, and hit
 * a case where we have a hash collision, and we end up not
 * being able to tell for certain exactly which case happened.
 * But that's hopefully unlikely.
 *
 * What we do is to take the bits we can fit, and split them
 * into three parts (16*997*1009 = 16095568), and use the values
 * for:
 *  - 0-15: user-settable
 *  - 0-996: file + line number
 *  - 0-1008: device
 */
#define USERHASH (16)
#define FILEHASH (997)
#define DEVHASH (1009)

#define DEVSEED (7919)

bool pm_trace_rtc_abused __read_mostly;
EXPORT_SYMBOL_GPL(pm_trace_rtc_abused);

static unsigned int dev_hash_value;

static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
{
	unsigned int n = user + USERHASH*(file + FILEHASH*device);

	// June 7th, 2006
	static struct rtc_time time = {
		.tm_sec = 0,
		.tm_min = 0,
		.tm_hour = 0,
		.tm_mday = 7,
		.tm_mon = 5,	// June - counting from zero
		.tm_year = 106,
		.tm_wday = 3,
		.tm_yday = 160,
		.tm_isdst = 1
	};

	time.tm_year = (n % 100);
	n /= 100;
	time.tm_mon = (n % 12);
	n /= 12;
	time.tm_mday = (n % 28) + 1;
	n /= 28;
	time.tm_hour = (n % 24);
	n /= 24;
	time.tm_min = (n % 20) * 3;
	n /= 20;
	mc146818_set_time(&time);
	pm_trace_rtc_abused = true;
	return n ? -1 : 0;
}

static unsigned int read_magic_time(void)
{
	struct rtc_time time;
	unsigned int val;

	mc146818_get_time(&time);
	pr_info("RTC time: %ptRt, date: %ptRd\n", &time, &time);
	val = time.tm_year;				/* 100 years */
	if (val > 100)
		val -= 100;
	val += time.tm_mon * 100;			/* 12 months */
	val += (time.tm_mday-1) * 100 * 12;		/* 28 month-days */
	val += time.tm_hour * 100 * 12 * 28;		/* 24 hours */
	val += (time.tm_min / 3) * 100 * 12 * 28 * 24;	/* 20 3-minute intervals */
	return val;
}

/*
 * This is just the sdbm hash function with a user-supplied
 * seed and final size parameter.
 */
static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
{
	unsigned char c;
	while ((c = *data++) != 0) {
		seed = (seed << 16) + (seed << 6) - seed + c;
	}
	return seed % mod;
}

void set_trace_device(struct device *dev)
{
	dev_hash_value = hash_string(DEVSEED, dev_name(dev), DEVHASH);
}
EXPORT_SYMBOL(set_trace_device);

/*
 * We could just take the "tracedata" index into the .tracedata
 * section instead. Generating a hash of the data gives us a
 * chance to work across kernel versions, and perhaps more
 * importantly it also gives us valid/invalid check (ie we will
 * likely not give totally bogus reports - if the hash matches,
 * it's not any guarantee, but it's a high _likelihood_ that
 * the match is valid).
 */
void generate_pm_trace(const void *tracedata, unsigned int user)
{
	unsigned short lineno = *(unsigned short *)tracedata;
	const char *file = *(const char **)(tracedata + 2);
	unsigned int user_hash_value, file_hash_value;

	user_hash_value = user % USERHASH;
	file_hash_value = hash_string(lineno, file, FILEHASH);
	set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
}
EXPORT_SYMBOL(generate_pm_trace);

extern char __tracedata_start[], __tracedata_end[];
static int show_file_hash(unsigned int value)
{
	int match;
	char *tracedata;

	match = 0;
	for (tracedata = __tracedata_start ; tracedata < __tracedata_end ;
			tracedata += 2 + sizeof(unsigned long)) {
		unsigned short lineno = *(unsigned short *)tracedata;
		const char *file = *(const char **)(tracedata + 2);
		unsigned int hash = hash_string(lineno, file, FILEHASH);
		if (hash != value)
			continue;
		pr_info("  hash matches %s:%u\n", file, lineno);
		match++;
	}
	return match;
}

static int show_dev_hash(unsigned int value)
{
	int match = 0;
	struct list_head *entry;

	device_pm_lock();
	entry = dpm_list.prev;
	while (entry != &dpm_list) {
		struct device * dev = to_device(entry);
		unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH);
		if (hash == value) {
			dev_info(dev, "hash matches\n");
			match++;
		}
		entry = entry->prev;
	}
	device_pm_unlock();
	return match;
}

static unsigned int hash_value_early_read;

int show_trace_dev_match(char *buf, size_t size)
{
	unsigned int value = hash_value_early_read / (USERHASH * FILEHASH);
	int ret = 0;
	struct list_head *entry;

	/*
	 * It's possible that multiple devices will match the hash and we can't
	 * tell which is the culprit, so it's best to output them all.
	 */
	device_pm_lock();
	entry = dpm_list.prev;
	while (size && entry != &dpm_list) {
		struct device *dev = to_device(entry);
		unsigned int hash = hash_string(DEVSEED, dev_name(dev),
						DEVHASH);
		if (hash == value) {
			int len = snprintf(buf, size, "%s\n",
					    dev_driver_string(dev));
			if (len > size)
				len = size;
			buf += len;
			ret += len;
			size -= len;
		}
		entry = entry->prev;
	}
	device_pm_unlock();
	return ret;
}

static int
pm_trace_notify(struct notifier_block *nb, unsigned long mode, void *_unused)
{
	switch (mode) {
	case PM_POST_HIBERNATION:
	case PM_POST_SUSPEND:
		if (pm_trace_rtc_abused) {
			pm_trace_rtc_abused = false;
			pr_warn("Possible incorrect RTC due to pm_trace, please use 'ntpdate' or 'rdate' to reset it.\n");
		}
		break;
	default:
		break;
	}
	return 0;
}

static struct notifier_block pm_trace_nb = {
	.notifier_call = pm_trace_notify,
};

static int early_resume_init(void)
{
	hash_value_early_read = read_magic_time();
	register_pm_notifier(&pm_trace_nb);
	return 0;
}

static int late_resume_init(void)
{
	unsigned int val = hash_value_early_read;
	unsigned int user, file, dev;

	user = val % USERHASH;
	val = val / USERHASH;
	file = val % FILEHASH;
	val = val / FILEHASH;
	dev = val /* % DEVHASH */;

	pr_info("  Magic number: %d:%d:%d\n", user, file, dev);
	show_file_hash(file);
	show_dev_hash(dev);
	return 0;
}

core_initcall(early_resume_init);
late_initcall(late_resume_init);