Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/base/power/trace.c
26444 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* drivers/base/power/trace.c
4
*
5
* Copyright (C) 2006 Linus Torvalds
6
*
7
* Trace facility for suspend/resume problems, when none of the
8
* devices may be working.
9
*/
10
#define pr_fmt(fmt) "PM: " fmt
11
12
#include <linux/pm-trace.h>
13
#include <linux/export.h>
14
#include <linux/rtc.h>
15
#include <linux/suspend.h>
16
#include <linux/init.h>
17
18
#include <linux/mc146818rtc.h>
19
20
#include "power.h"
21
22
/*
23
* Horrid, horrid, horrid.
24
*
25
* It turns out that the _only_ piece of hardware that actually
26
* keeps its value across a hard boot (and, more importantly, the
27
* POST init sequence) is literally the realtime clock.
28
*
29
* Never mind that an RTC chip has 114 bytes (and often a whole
30
* other bank of an additional 128 bytes) of nice SRAM that is
31
* _designed_ to keep data - the POST will clear it. So we literally
32
* can just use the few bytes of actual time data, which means that
33
* we're really limited.
34
*
35
* It means, for example, that we can't use the seconds at all
36
* (since the time between the hang and the boot might be more
37
* than a minute), and we'd better not depend on the low bits of
38
* the minutes either.
39
*
40
* There are the wday fields etc, but I wouldn't guarantee those
41
* are dependable either. And if the date isn't valid, either the
42
* hw or POST will do strange things.
43
*
44
* So we're left with:
45
* - year: 0-99
46
* - month: 0-11
47
* - day-of-month: 1-28
48
* - hour: 0-23
49
* - min: (0-30)*2
50
*
51
* Giving us a total range of 0-16128000 (0xf61800), ie less
52
* than 24 bits of actual data we can save across reboots.
53
*
54
* And if your box can't boot in less than three minutes,
55
* you're screwed.
56
*
57
* Now, almost 24 bits of data is pitifully small, so we need
58
* to be pretty dense if we want to use it for anything nice.
59
* What we do is that instead of saving off nice readable info,
60
* we save off _hashes_ of information that we can hopefully
61
* regenerate after the reboot.
62
*
63
* In particular, this means that we might be unlucky, and hit
64
* a case where we have a hash collision, and we end up not
65
* being able to tell for certain exactly which case happened.
66
* But that's hopefully unlikely.
67
*
68
* What we do is to take the bits we can fit, and split them
69
* into three parts (16*997*1009 = 16095568), and use the values
70
* for:
71
* - 0-15: user-settable
72
* - 0-996: file + line number
73
* - 0-1008: device
74
*/
75
#define USERHASH (16)
76
#define FILEHASH (997)
77
#define DEVHASH (1009)
78
79
#define DEVSEED (7919)
80
81
bool pm_trace_rtc_abused __read_mostly;
82
EXPORT_SYMBOL_GPL(pm_trace_rtc_abused);
83
84
static unsigned int dev_hash_value;
85
86
static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
87
{
88
unsigned int n = user + USERHASH*(file + FILEHASH*device);
89
90
// June 7th, 2006
91
static struct rtc_time time = {
92
.tm_sec = 0,
93
.tm_min = 0,
94
.tm_hour = 0,
95
.tm_mday = 7,
96
.tm_mon = 5, // June - counting from zero
97
.tm_year = 106,
98
.tm_wday = 3,
99
.tm_yday = 160,
100
.tm_isdst = 1
101
};
102
103
time.tm_year = (n % 100);
104
n /= 100;
105
time.tm_mon = (n % 12);
106
n /= 12;
107
time.tm_mday = (n % 28) + 1;
108
n /= 28;
109
time.tm_hour = (n % 24);
110
n /= 24;
111
time.tm_min = (n % 20) * 3;
112
n /= 20;
113
mc146818_set_time(&time);
114
pm_trace_rtc_abused = true;
115
return n ? -1 : 0;
116
}
117
118
static unsigned int read_magic_time(void)
119
{
120
struct rtc_time time;
121
unsigned int val;
122
123
if (mc146818_get_time(&time, 1000) < 0) {
124
pr_err("Unable to read current time from RTC\n");
125
return 0;
126
}
127
128
pr_info("RTC time: %ptRt, date: %ptRd\n", &time, &time);
129
val = time.tm_year; /* 100 years */
130
if (val > 100)
131
val -= 100;
132
val += time.tm_mon * 100; /* 12 months */
133
val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */
134
val += time.tm_hour * 100 * 12 * 28; /* 24 hours */
135
val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */
136
return val;
137
}
138
139
/*
140
* This is just the sdbm hash function with a user-supplied
141
* seed and final size parameter.
142
*/
143
static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
144
{
145
unsigned char c;
146
while ((c = *data++) != 0) {
147
seed = (seed << 16) + (seed << 6) - seed + c;
148
}
149
return seed % mod;
150
}
151
152
void set_trace_device(struct device *dev)
153
{
154
dev_hash_value = hash_string(DEVSEED, dev_name(dev), DEVHASH);
155
}
156
EXPORT_SYMBOL(set_trace_device);
157
158
/*
159
* We could just take the "tracedata" index into the .tracedata
160
* section instead. Generating a hash of the data gives us a
161
* chance to work across kernel versions, and perhaps more
162
* importantly it also gives us valid/invalid check (ie we will
163
* likely not give totally bogus reports - if the hash matches,
164
* it's not any guarantee, but it's a high _likelihood_ that
165
* the match is valid).
166
*/
167
void generate_pm_trace(const void *tracedata, unsigned int user)
168
{
169
unsigned short lineno = *(unsigned short *)tracedata;
170
const char *file = *(const char **)(tracedata + 2);
171
unsigned int user_hash_value, file_hash_value;
172
173
if (!x86_platform.legacy.rtc)
174
return;
175
176
user_hash_value = user % USERHASH;
177
file_hash_value = hash_string(lineno, file, FILEHASH);
178
set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
179
}
180
EXPORT_SYMBOL(generate_pm_trace);
181
182
extern char __tracedata_start[], __tracedata_end[];
183
static int show_file_hash(unsigned int value)
184
{
185
int match;
186
char *tracedata;
187
188
match = 0;
189
for (tracedata = __tracedata_start ; tracedata < __tracedata_end ;
190
tracedata += 2 + sizeof(unsigned long)) {
191
unsigned short lineno = *(unsigned short *)tracedata;
192
const char *file = *(const char **)(tracedata + 2);
193
unsigned int hash = hash_string(lineno, file, FILEHASH);
194
if (hash != value)
195
continue;
196
pr_info(" hash matches %s:%u\n", file, lineno);
197
match++;
198
}
199
return match;
200
}
201
202
static int show_dev_hash(unsigned int value)
203
{
204
int match = 0;
205
struct list_head *entry;
206
207
device_pm_lock();
208
entry = dpm_list.prev;
209
while (entry != &dpm_list) {
210
struct device * dev = to_device(entry);
211
unsigned int hash = hash_string(DEVSEED, dev_name(dev), DEVHASH);
212
if (hash == value) {
213
dev_info(dev, "hash matches\n");
214
match++;
215
}
216
entry = entry->prev;
217
}
218
device_pm_unlock();
219
return match;
220
}
221
222
static unsigned int hash_value_early_read;
223
224
int show_trace_dev_match(char *buf, size_t size)
225
{
226
unsigned int value = hash_value_early_read / (USERHASH * FILEHASH);
227
int ret = 0;
228
struct list_head *entry;
229
230
/*
231
* It's possible that multiple devices will match the hash and we can't
232
* tell which is the culprit, so it's best to output them all.
233
*/
234
device_pm_lock();
235
entry = dpm_list.prev;
236
while (size && entry != &dpm_list) {
237
struct device *dev = to_device(entry);
238
unsigned int hash = hash_string(DEVSEED, dev_name(dev),
239
DEVHASH);
240
if (hash == value) {
241
int len = snprintf(buf, size, "%s\n",
242
dev_driver_string(dev));
243
if (len > size)
244
len = size;
245
buf += len;
246
ret += len;
247
size -= len;
248
}
249
entry = entry->prev;
250
}
251
device_pm_unlock();
252
return ret;
253
}
254
255
static int
256
pm_trace_notify(struct notifier_block *nb, unsigned long mode, void *_unused)
257
{
258
switch (mode) {
259
case PM_POST_HIBERNATION:
260
case PM_POST_SUSPEND:
261
if (pm_trace_rtc_abused) {
262
pm_trace_rtc_abused = false;
263
pr_warn("Possible incorrect RTC due to pm_trace, please use 'ntpdate' or 'rdate' to reset it.\n");
264
}
265
break;
266
default:
267
break;
268
}
269
return 0;
270
}
271
272
static struct notifier_block pm_trace_nb = {
273
.notifier_call = pm_trace_notify,
274
};
275
276
static int __init early_resume_init(void)
277
{
278
if (!x86_platform.legacy.rtc)
279
return 0;
280
281
hash_value_early_read = read_magic_time();
282
register_pm_notifier(&pm_trace_nb);
283
return 0;
284
}
285
286
static int __init late_resume_init(void)
287
{
288
unsigned int val = hash_value_early_read;
289
unsigned int user, file, dev;
290
291
if (!x86_platform.legacy.rtc)
292
return 0;
293
294
user = val % USERHASH;
295
val = val / USERHASH;
296
file = val % FILEHASH;
297
val = val / FILEHASH;
298
dev = val /* % DEVHASH */;
299
300
pr_info(" Magic number: %d:%d:%d\n", user, file, dev);
301
show_file_hash(file);
302
show_dev_hash(dev);
303
return 0;
304
}
305
306
core_initcall(early_resume_init);
307
late_initcall(late_resume_init);
308
309